Skip to content

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-content

Parameters

ParameterTypeRequiredDescription
siteIdstringYesSite 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

ParameterTypeRequiredDescription
siteIdstringYesSite identifier
keystringYesText 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

ParameterTypeRequiredDescription
pagestringNoPage 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

StatusMeaningAction
404Site not foundVerify site ID
500Server errorUse cached/default values
503Service unavailableRetry 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

TierRequests/minuteBurst
Free10020
Pro1000100
EnterpriseUnlimited500

Rate Limit Headers

http
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1705320000

Best 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

Duff Cloud Services Documentation