Skip to content

Text Content API ​

Partner Documentation

The Text Content API serves portal-managed text overrides to a live site at runtime, so copy edits made in the portal appear without a redeploy.

Overview ​

  • One public, read-only endpoint returns the text overrides for a single page.
  • The site is resolved server-side — from the request Host (shared server) or the dedicated Container App's DCS_SITE_SLUG. The site slug never appears in the URL path.
  • Write operations are portal-only. There is no public write endpoint; edits are made in the portal and committed through the managed content pipeline.

Endpoint ​

Get text overrides for a page ​

http
GET /api/v1/pages/{pageSlug}/text

Path parameters ​

ParameterTypeRequiredDescription
pageSlugstringYesPage slug (e.g. home, about)

The site is not part of the path. It is determined from the request Host on the shared server, or from the DCS_SITE_SLUG environment variable on a site's dedicated Container App.

Response ​

200 OK

json
{
  "pageSlug": "home",
  "overrides": {
    "hero.title": "Welcome to Our Platform",
    "hero.subtitle": "Building the future together"
  }
}
  • pageSlug — the page the overrides belong to.
  • overrides — a flat map of text key → override value. Empty ({}) when the page has no overrides.

Example request ​

bash
# Shared server: the Host header selects the site
curl -X GET "https://portal.duffcloudservices.com/api/v1/pages/home/text" \
  -H "Host: mysite.com" \
  -H "Accept: application/json"

Authentication ​

None. The endpoint is public and read-only. Write access is available only through the portal UI.

CORS & caching ​

The response is cross-origin friendly and CDN-cacheable:

http
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, OPTIONS
Access-Control-Allow-Headers: Content-Type
Cache-Control: public, max-age=60, s-maxage=300
Content-Type: application/json
  • max-age=60 — 1 minute in the browser.
  • s-maxage=300 — 5 minutes at the CDN edge.

Errors ​

StatusMeaningNotes
400Bad requestMissing page slug, or the site could not be resolved from the request
404Site not foundThe resolved slug has no matching site — treat as "no overrides" and fall back to defaults
500Server errorFall back to build-time / default values
503Service unavailableRetry with backoff; fall back to defaults

Clients should always render default text when a fetch fails, so a transient API problem never blanks out the page.

Rate limits ​

All /api/* routes share one global limit: 120 requests per minute per client IP. There are no pricing tiers or per-plan quotas. Responses are heavily cached (see above), so a normal site issues very few uncached requests.

Sites built on the DCS stack should use the official @duffcloudservices/cms package rather than calling the endpoint directly. The useTextContent composable fetches this endpoint, merges the result over build-time defaults, caches responses, and degrades gracefully:

vue
<script setup lang="ts">
import { useTextContent } from '@duffcloudservices/cms'

const { t } = useTextContent({
  pageSlug: 'home',
  defaults: {
    'hero.title': 'Welcome to Our Site',
    'hero.subtitle': 'Default subtitle text'
  }
})
</script>

<template>
  <section class="hero">
    <h1>{{ t('hero.title') }}</h1>
    <p>{{ t('hero.subtitle') }}</p>
  </section>
</template>

The composable reads its API base URL from VITE_API_BASE_URL. Because the site is resolved server-side, no site-id variable is needed for routing.

Calling the endpoint directly ​

If you are not on Vue, fetch the endpoint and merge over your own defaults:

ts
async function loadOverrides(
  apiBaseUrl: string,
  pageSlug: string,
): Promise<Record<string, string>> {
  try {
    const res = await fetch(`${apiBaseUrl}/api/v1/pages/${pageSlug}/text`, {
      headers: { Accept: 'application/json' },
    })
    if (res.status === 404) return {}       // no overrides for this page
    if (!res.ok) throw new Error(`HTTP ${res.status}`)
    const data = await res.json()
    return data.overrides ?? {}
  } catch {
    return {}                                // fall back to defaults
  }
}

Text keys ​

Override keys are flat strings scoped to a page, conventionally {section}.{element} — for example hero.title or features.first.description. Keys are declared as page defaults in your components and surfaced for editing in the portal.

Next Steps ​