Text Content API
Partner Documentation
Integrate the DCS Text Content API to enable real-time text editing.
API Overview
The Text Content API provides:
- Fetch text overrides for a site
- Real-time updates without deployment
- Fallback to default values
Endpoints
Get Text Overrides
Fetch all text overrides for a site:
http
GET /api/sites/{siteId}/text-contentParameters
| Parameter | Type | Required | Description |
|---|---|---|---|
siteId | string | Yes | Site identifier (slug or ID) |
Response
json
{
"overrides": {
"home.hero.title": "Welcome to Our Platform",
"home.hero.subtitle": "Building the future together",
"about.team.description": "Our amazing team..."
},
"updatedAt": "2024-01-15T10:30:00Z",
"version": "1.4.2"
}Example Request
bash
curl -X GET "https://portal.duffcloudservices.com/api/sites/my-site/text-content" \
-H "Accept: application/json"Get Single Override
Fetch a specific text override:
http
GET /api/sites/{siteId}/text-content/{key}Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
siteId | string | Yes | Site identifier |
key | string | Yes | Text key (URL-encoded) |
Response
json
{
"key": "home.hero.title",
"value": "Welcome to Our Platform",
"updatedAt": "2024-01-15T10:30:00Z",
"updatedBy": "user@example.com"
}Get Overrides by Page
Fetch overrides for a specific page:
http
GET /api/sites/{siteId}/text-content?page={pageSlug}Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
page | string | No | Page slug to filter by |
Response
json
{
"overrides": {
"home.hero.title": "Welcome",
"home.hero.subtitle": "Description",
"home.features.title": "Features"
},
"page": "home"
}Authentication
The Text Content API is public for read access. No authentication required for fetching overrides.
TIP
Write operations (creating/updating overrides) require authentication and are performed through the portal.
Caching
Cache Headers
Responses include cache headers:
http
Cache-Control: public, max-age=300, stale-while-revalidate=60
ETag: "abc123"Conditional Requests
Use ETags for efficient caching:
bash
curl -X GET "https://portal.duffcloudservices.com/api/sites/my-site/text-content" \
-H "If-None-Match: \"abc123\""Returns 304 Not Modified if content unchanged.
Client Implementation
Vue/VitePress Composable
typescript
// lib/use-text-content.ts
import { ref, onMounted, readonly } from 'vue'
interface TextOverrides {
[key: string]: string
}
const overrides = ref<TextOverrides>({})
const isLoading = ref(true)
const error = ref<Error | null>(null)
// Singleton fetch
let fetchPromise: Promise<void> | null = null
async function fetchOverrides(): Promise<void> {
if (fetchPromise) return fetchPromise
fetchPromise = (async () => {
try {
const apiUrl = import.meta.env.VITE_DCS_API
const siteId = import.meta.env.VITE_SITE_ID
const response = await fetch(
`${apiUrl}/sites/${siteId}/text-content`,
{
headers: { 'Accept': 'application/json' }
}
)
if (!response.ok) {
throw new Error(`HTTP ${response.status}`)
}
const data = await response.json()
overrides.value = data.overrides || {}
} catch (e) {
error.value = e as Error
console.warn('Text overrides unavailable, using defaults')
} finally {
isLoading.value = false
}
})()
return fetchPromise
}
export function useTextContent() {
onMounted(() => {
fetchOverrides()
})
function t(key: string, defaultValue: string): string {
return overrides.value[key] ?? defaultValue
}
return {
t,
isLoading: readonly(isLoading),
error: readonly(error),
refresh: fetchOverrides
}
}React Hook
typescript
// hooks/useTextContent.ts
import { useState, useEffect, useCallback } from 'react'
interface TextOverrides {
[key: string]: string
}
let cachedOverrides: TextOverrides | null = null
let fetchPromise: Promise<TextOverrides> | null = null
async function fetchOverrides(): Promise<TextOverrides> {
if (cachedOverrides) return cachedOverrides
if (fetchPromise) return fetchPromise
fetchPromise = fetch(
`${process.env.REACT_APP_DCS_API}/sites/${process.env.REACT_APP_SITE_ID}/text-content`
)
.then(res => res.json())
.then(data => {
cachedOverrides = data.overrides || {}
return cachedOverrides
})
return fetchPromise
}
export function useTextContent() {
const [overrides, setOverrides] = useState<TextOverrides>({})
const [isLoading, setIsLoading] = useState(true)
useEffect(() => {
fetchOverrides()
.then(setOverrides)
.finally(() => setIsLoading(false))
}, [])
const t = useCallback((key: string, defaultValue: string): string => {
return overrides[key] ?? defaultValue
}, [overrides])
return { t, isLoading }
}Error Handling
Common Errors
| Status | Meaning | Action |
|---|---|---|
| 404 | Site not found | Verify site ID |
| 500 | Server error | Use cached/default values |
| 503 | Service unavailable | Retry with backoff |
Graceful Degradation
Always provide default values:
typescript
function t(key: string, defaultValue: string): string {
// Always return something useful
return overrides[key] ?? defaultValue
}Retry Logic
typescript
async function fetchWithRetry(
url: string,
retries = 3
): Promise<Response> {
for (let i = 0; i < retries; i++) {
try {
const response = await fetch(url)
if (response.ok) return response
} catch (e) {
if (i === retries - 1) throw e
await new Promise(r => setTimeout(r, 1000 * (i + 1)))
}
}
throw new Error('Max retries exceeded')
}Rate Limits
| Tier | Requests/minute | Burst |
|---|---|---|
| Free | 100 | 20 |
| Pro | 1000 | 100 |
| Enterprise | Unlimited | 500 |
Rate Limit Headers
http
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1705320000Best Practices
1. Fetch Once, Use Everywhere
typescript
// Singleton pattern - fetch once per session
let fetchPromise: Promise<void> | null = null
export function initTextContent() {
if (!fetchPromise) {
fetchPromise = fetchOverrides()
}
return fetchPromise
}2. Preload on App Start
typescript
// main.ts
import { initTextContent } from './lib/text-content'
// Start fetching immediately
initTextContent()
// Then mount app
app.mount('#app')3. Handle Loading States
vue
<template>
<h1 :class="{ 'opacity-50': isLoading }">
{{ t('home.title', 'Welcome') }}
</h1>
</template>4. Log Errors in Development
typescript
if (import.meta.env.DEV && error.value) {
console.warn('Text content API error:', error.value)
}Testing
Mock API for Tests
typescript
// __mocks__/text-content.ts
export function useTextContent() {
return {
t: (key: string, defaultValue: string) => defaultValue,
isLoading: false,
error: null
}
}Integration Tests
typescript
describe('Text Content API', () => {
it('fetches overrides successfully', async () => {
const response = await fetch(
`${API_URL}/sites/test-site/text-content`
)
expect(response.ok).toBe(true)
const data = await response.json()
expect(data).toHaveProperty('overrides')
})
})Next Steps
- SEO API — SEO metadata API
- pages.yaml Reference — Page configuration
- Text Editing Guide — User documentation
