SEO API
Manage SEO metadata for your site through the DCS portal API.
Overview
The SEO API allows you to:
- Get/set page metadata (title, description, keywords)
- Manage Open Graph tags
- Configure Twitter cards
- Generate sitemaps
Endpoints
Get Page SEO
Fetch SEO metadata for a page:
http
GET /api/sites/{siteId}/seo/{pageSlug}Response
json
{
"pageSlug": "home",
"title": "Welcome to Acme Corp",
"description": "Industry-leading solutions for modern businesses",
"keywords": ["business", "solutions", "enterprise"],
"canonical": "https://acme.com/",
"openGraph": {
"title": "Acme Corp - Business Solutions",
"description": "Industry-leading solutions",
"image": "https://acme.com/og-home.jpg",
"type": "website"
},
"twitter": {
"card": "summary_large_image",
"title": "Acme Corp",
"description": "Business solutions",
"image": "https://acme.com/twitter-home.jpg"
},
"updatedAt": "2024-01-15T10:30:00Z"
}Get All SEO
Fetch SEO for all pages:
http
GET /api/sites/{siteId}/seoResponse
json
{
"pages": [
{
"pageSlug": "home",
"title": "Welcome",
"description": "..."
},
{
"pageSlug": "about",
"title": "About Us",
"description": "..."
}
],
"defaults": {
"titleSuffix": " | Acme Corp",
"defaultImage": "https://acme.com/og-default.jpg"
}
}Update Page SEO
Update SEO for a page (requires authentication):
http
PUT /api/sites/{siteId}/seo/{pageSlug}Request Body
json
{
"title": "New Page Title",
"description": "Updated description for SEO",
"keywords": ["keyword1", "keyword2"],
"openGraph": {
"title": "OG Title",
"description": "OG Description",
"image": "https://example.com/og.jpg"
}
}Response
json
{
"success": true,
"pageSlug": "home",
"updatedAt": "2024-01-15T10:30:00Z"
}Get Site Defaults
Fetch site-wide SEO defaults:
http
GET /api/sites/{siteId}/seo/defaultsResponse
json
{
"titleSuffix": " | Acme Corp",
"defaultDescription": "Industry-leading business solutions",
"defaultImage": "https://acme.com/og-default.jpg",
"twitterHandle": "@acmecorp",
"locale": "en_US"
}Update Site Defaults
http
PUT /api/sites/{siteId}/seo/defaultsData Types
SEO Metadata
typescript
interface SEOMetadata {
title: string
description: string
keywords?: string[]
canonical?: string
noIndex?: boolean
noFollow?: boolean
openGraph?: OpenGraphData
twitter?: TwitterCardData
structuredData?: object
}Open Graph
typescript
interface OpenGraphData {
title?: string
description?: string
image?: string
imageWidth?: number
imageHeight?: number
type?: 'website' | 'article' | 'product'
locale?: string
siteName?: string
}Twitter Card
typescript
interface TwitterCardData {
card?: 'summary' | 'summary_large_image' | 'app' | 'player'
title?: string
description?: string
image?: string
site?: string // @username
creator?: string // @username
}Integration
VitePress Head Config
typescript
// .vitepress/config.ts
export default defineConfig({
async transformHead(context) {
const seo = await fetchSEO(context.pageData.relativePath)
return [
['meta', { name: 'description', content: seo.description }],
['meta', { property: 'og:title', content: seo.openGraph?.title }],
['meta', { property: 'og:description', content: seo.openGraph?.description }],
['meta', { property: 'og:image', content: seo.openGraph?.image }],
['meta', { name: 'twitter:card', content: seo.twitter?.card }]
]
}
})Vue Head Component
vue
<!-- components/SEOHead.vue -->
<script setup lang="ts">
import { useHead } from '@vueuse/head'
import { useSEO } from '@/lib/use-seo'
const props = defineProps<{
pageSlug: string
}>()
const { seo, isLoading } = useSEO(props.pageSlug)
useHead({
title: () => seo.value?.title,
meta: [
{ name: 'description', content: () => seo.value?.description },
{ property: 'og:title', content: () => seo.value?.openGraph?.title },
{ property: 'og:description', content: () => seo.value?.openGraph?.description },
{ property: 'og:image', content: () => seo.value?.openGraph?.image }
]
})
</script>Build-Time Generation
typescript
// scripts/generate-seo.ts
import { writeFileSync } from 'fs'
async function generateSEOFiles() {
const response = await fetch(`${API_URL}/sites/${SITE_ID}/seo`)
const { pages, defaults } = await response.json()
for (const page of pages) {
const seoJson = JSON.stringify({
...defaults,
...page
}, null, 2)
writeFileSync(`public/seo/${page.pageSlug}.json`, seoJson)
}
}Sitemap Generation
Request Sitemap
http
GET /api/sites/{siteId}/sitemap.xmlReturns XML sitemap:
xml
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://acme.com/</loc>
<lastmod>2024-01-15</lastmod>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
</url>
<url>
<loc>https://acme.com/about</loc>
<lastmod>2024-01-10</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
</urlset>Configure Sitemap
http
PUT /api/sites/{siteId}/seo/sitemap-configjson
{
"excludePages": ["admin", "preview"],
"defaultChangeFreq": "weekly",
"priorities": {
"home": 1.0,
"about": 0.8,
"blogs": 0.9,
"contact": 0.6
}
}Structured Data
Article Schema
json
{
"pageSlug": "blog-post",
"structuredData": {
"@context": "https://schema.org",
"@type": "Article",
"headline": "Article Title",
"author": {
"@type": "Person",
"name": "Author Name"
},
"datePublished": "2024-01-15",
"image": "https://acme.com/article-image.jpg"
}
}Organization Schema
json
{
"pageSlug": "home",
"structuredData": {
"@context": "https://schema.org",
"@type": "Organization",
"name": "Acme Corp",
"url": "https://acme.com",
"logo": "https://acme.com/logo.png",
"sameAs": [
"https://twitter.com/acmecorp",
"https://linkedin.com/company/acmecorp"
]
}
}Validation
Character Limits
| Field | Recommended | Maximum |
|---|---|---|
| Title | 50-60 chars | 70 chars |
| Description | 150-160 chars | 320 chars |
| OG Title | 60 chars | 95 chars |
| OG Description | 110 chars | 300 chars |
Validation Response
json
{
"valid": false,
"warnings": [
{
"field": "title",
"message": "Title is 75 characters, recommended max is 60"
}
],
"errors": [
{
"field": "description",
"message": "Description is required"
}
]
}Caching
Cache Headers
http
Cache-Control: public, max-age=3600, stale-while-revalidate=300Cache Invalidation
SEO cache is invalidated when:
- Page SEO is updated via portal
- Site defaults change
- Manual purge requested
Error Handling
| Status | Meaning |
|---|---|
| 400 | Invalid SEO data |
| 401 | Not authenticated |
| 403 | No permission for site |
| 404 | Page not found |
| 422 | Validation failed |
Best Practices
1. Unique Titles
Each page should have a unique, descriptive title:
Home: "Acme Corp - Business Solutions"
About: "About Us - Acme Corp"
Contact: "Contact Sales - Acme Corp"2. Compelling Descriptions
Write action-oriented descriptions:
❌ "This is the about page for our company"
✅ "Learn how Acme Corp has helped 500+ businesses grow. Meet our team and discover our story."3. Consistent Branding
Use site defaults for consistency:
json
{
"titleSuffix": " | Acme Corp",
"defaultImage": "https://acme.com/og-default.jpg"
}4. Image Optimization
- OG images: 1200x630px
- Twitter images: 1200x675px
- Keep file size < 1MB
Next Steps
- Authentication — API authentication
- SEO Management — Portal guide
- Pages Configuration — Page definitions
