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'sDCS_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
GET /api/v1/pages/{pageSlug}/textPath parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
pageSlug | string | Yes | Page 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
{
"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
# 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:
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/jsonmax-age=60— 1 minute in the browser.s-maxage=300— 5 minutes at the CDN edge.
Errors
| Status | Meaning | Notes |
|---|---|---|
| 400 | Bad request | Missing page slug, or the site could not be resolved from the request |
| 404 | Site not found | The resolved slug has no matching site — treat as "no overrides" and fall back to defaults |
| 500 | Server error | Fall back to build-time / default values |
| 503 | Service unavailable | Retry 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.
Recommended integration
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:
<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:
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
- CMS Package Reference —
useTextContentand related composables - Text Editing Guide — Editing copy in the portal
- pages.yaml Reference — Page registration
