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