API Documentation
Build custom websites and applications with The Digital Fair CMS. Our REST API gives you full access to your content.
Getting Started
The Digital Fair CMS provides a headless CMS API that allows you to build custom websites and applications using your content. With our REST API, you can:
- Retrieve portfolio/project content - Projects, artwork, case studies
- Retrieve blog posts - Articles, news, updates
- Retrieve links page data - Page configuration and links
- Access media attachments - Images and files associated with content
Key Concepts
Tenant
A user/organization with their own content namespace
API Key
Unique key for authenticating API requests
Content Item
A single piece of content (project, blog post, etc.)
Content Type
Category: project, blog, event, book, page, etc.
Authentication
All external API endpoints require an API Key passed as a query parameter:
?api_key=your_api_key_hereAPI Key Types
Each tenant has two API keys for different use cases:
| Key Type | Purpose | Content Access |
|---|---|---|
| Production API Key | For production websites | Published content only (default) |
| Preview API Key | For staging/development environments | All content (draft, published, archived) |
Preview Key Warning
The Preview API Key exposes unpublished content (drafts and archived items).
- Do not use the preview key in production environments
- Responses include a
preview_mode: trueflag when using the preview key - Use it only for staging sites, development, and content preview
Getting Your API Keys
Both API keys are available in your tenant dashboard under Settings > API Configuration. Each key can be regenerated independently without affecting the other.
Security Notes:
- Production keys provide read-only access to published content by default
- Preview keys expose unpublished content and should never be used in production
- Never expose API keys in client-side code (use server-side fetching)
- API keys are scoped to a single tenant
Base URLs
Production
https://thedigitalfair.com/api/v1Development
http://localhost:3000/api/v1Content API
/api/v1/contentRetrieve all content items for a tenant with optional filtering.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
api_key* | string | Tenant's API key |
type | string | Filter by content type: project, blog, event, book, page |
status | string | Content status: published (default), draft, archived |
series_id | string | Filter by series UUID |
limit | number | Number of items to return (default: 50, max: 50) |
offset | number | Pagination offset (default: 0) |
Example Request
curl "https://thedigitalfair.com/api/v1/content?api_key=your_api_key_here&type=project&status=published&limit=10"Example Response
{
"success": true,
"data": {
"items": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"tenant_id": "tenant-uuid",
"content_type": "project",
"title": "Website Redesign for Acme Corp",
"slug": "acme-corp-redesign",
"subtitle": "A complete brand refresh",
"description": "Led the complete redesign of Acme Corp's digital presence...",
"content_body": "<p>Full HTML content here...</p>",
"featured_image": "https://storage.supabase.co/media/tenant-id/image.jpg",
"status": "published",
"date_published": "2024-01-15T10:00:00Z",
"sort_order": 10,
"tags": ["web-design", "branding", "case-study"],
"project_link": "https://acme-corp.com",
"created_at": "2024-01-10T08:00:00Z",
"updated_at": "2024-01-15T10:00:00Z"
}
],
"pagination": {
"limit": 50,
"offset": 0,
"total": 25
}
}
}/api/v1/content/{id_or_slug}Retrieve a specific content item by ID or slug. Includes media attachments.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
id_or_slug* | string | Either the UUID or the slug of the content item |
Example Request
// By slug
curl "https://thedigitalfair.com/api/v1/content/acme-corp-redesign?api_key=your_api_key_here"
// By UUID
curl "https://thedigitalfair.com/api/v1/content/550e8400-e29b-41d4-a716-446655440000?api_key=your_api_key_here"Example Response
{
"success": true,
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"title": "Website Redesign for Acme Corp",
"slug": "acme-corp-redesign",
"content_body": "<p>Full HTML content with rich text formatting...</p>",
"featured_image": "https://storage.supabase.co/media/tenant-id/hero.jpg",
// ... all content fields
"media": [
{
"id": "media-uuid-1",
"media_type": "image",
"url": "https://storage.supabase.co/media/tenant-id/screenshot1.jpg",
"alt_text": "Homepage design",
"sort_order": 0
},
{
"id": "media-uuid-2",
"media_type": "image",
"url": "https://storage.supabase.co/media/tenant-id/screenshot2.jpg",
"alt_text": "Mobile view",
"sort_order": 1
}
]
}
}Event Timezone Handling
For event content types, the event_date is stored in UTC. Use the event_timezone field (IANA timezone identifier) to display the event in the correct local time.
// Example response for an event
{
"event_date": "2025-01-15T20:00:00.000Z", // UTC
"event_timezone": "America/New_York", // IANA timezone
"location": "Barnes & Noble, 5th Avenue, New York, NY"
}
// Display as: "Jan 15, 2025 3:00 PM EST"Content Types
The Digital Fair CMS supports multiple content types, each designed for specific use cases. All content types share a common set of fields, with additional type-specific fields for specialized data.
Content Type URL Paths
When building URLs for your website, use these user-friendly paths instead of the raw content_type values from the API:
| Database content_type | URL Path | Example URL |
|---|---|---|
blog_post | /blog | yoursite.com/blog/my-article |
book | /books | yoursite.com/books/my-novel |
series | /series | yoursite.com/series/my-series |
event | /events | yoursite.com/events/book-signing |
artwork | /artwork | yoursite.com/artwork/landscape-1 |
project | /projects | yoursite.com/projects/client-work |
page | (root) | yoursite.com/about |
Helper Function (TypeScript)
const CONTENT_TYPE_PATHS: Record<string, string> = {
blog_post: 'blog',
book: 'books',
series: 'series',
event: 'events',
artwork: 'artwork',
project: 'projects',
page: '',
}
function getContentTypePath(contentType: string): string {
return CONTENT_TYPE_PATHS[contentType] ?? contentType
}
// Usage
const item = await fetchContent('my-post')
const url = `https://yoursite.com/${getContentTypePath(item.content_type)}/${item.slug}`
// For blog_post: https://yoursite.com/blog/my-postImportant: Always use these user-friendly paths in your website URLs rather than the raw content_type value from the API. This ensures consistency with The Digital Fair CMS social media sharing and preview links.
Common Fields (All Content Types)
These fields are available on every content item, regardless of type:
| Field | Type | Description |
|---|---|---|
id | string (UUID) | Unique identifier for the content item |
tenant_id | string (UUID) | ID of the tenant who owns this content |
content_type | string | Type of content (see Content Types section) |
title | string | Display title of the content |
slug | string | URL-safe identifier used in URLs |
subtitle | string | null | Optional subtitle or tagline |
description | string | null | Short description or excerpt (typically shown in lists) |
content_body | string | null | Full HTML content (rich text) |
featured_image | string | null | URL to the main/hero image |
status | string | Content status: "draft", "published", or "archived" |
sort_order | number | Display order (higher values appear first) |
tags | string[] | null | Array of tag strings for categorization |
core_attributes | object | Standard attributes specific to the content type |
custom_attributes | object | User-defined custom fields |
meta_title | string | null | SEO title (falls back to title if not set) |
meta_description | string | null | SEO description (falls back to description if not set) |
created_at | timestamp | When the content was created (ISO 8601) |
updated_at | timestamp | When the content was last updated (ISO 8601) |
Blog Post
Articles, news updates, and written content. Ideal for sharing thoughts, tutorials, announcements, and regular updates.
type=blogtype=blog_postType-Specific Fields
| Field | Type | Description |
|---|---|---|
published_date | date | Original publication date (YYYY-MM-DD format) |
content_updated_date | timestamp | Auto-set when content is edited after publishing |
Example Response
{
"content_type": "blog",
"title": "10 Tips for Better Web Design",
"slug": "10-tips-better-web-design",
"description": "Learn the essential principles of effective web design...",
"content_body": "<p>Full article HTML content...</p>",
"featured_image": "https://storage.supabase.co/media/tenant-id/blog-hero.jpg",
"published_date": "2024-06-15",
"tags": ["web-design", "tips", "ux"],
"meta_title": "10 Tips for Better Web Design | Your Site",
"meta_description": "Discover the essential principles..."
}Project
Portfolio pieces, case studies, and work samples. Perfect for showcasing completed work with details about the process and outcomes.
type=projectType-Specific Fields
| Field | Type | Description |
|---|---|---|
project_link | string | null | External URL to the live project or demo |
core_attributes.client_name | string | Name of the client (if applicable) |
core_attributes.project_year | string | Year the project was completed |
core_attributes.services | string[] | List of services provided (e.g., ["Web Design", "Branding"]) |
Example Response
{
"content_type": "project",
"title": "Website Redesign for Acme Corp",
"slug": "acme-corp-redesign",
"subtitle": "A complete brand refresh",
"description": "Led the complete redesign of Acme Corp's digital presence...",
"content_body": "<p>Detailed case study...</p>",
"featured_image": "https://storage.supabase.co/media/tenant-id/project-hero.jpg",
"project_link": "https://acme-corp.com",
"core_attributes": {
"client_name": "Acme Corp",
"project_year": "2024",
"services": ["Web Design", "Branding", "Development"]
},
"tags": ["web-design", "branding", "case-study"],
"media": [
{ "url": "...", "alt_text": "Homepage design", "sort_order": 0 },
{ "url": "...", "alt_text": "Mobile view", "sort_order": 1 }
]
}Book
Published books, novels, and written works. Includes support for series organization and purchase links.
type=bookType-Specific Fields
| Field | Type | Description |
|---|---|---|
subtitle | string | null | Book subtitle or tagline |
published_date | date | Publication date of the book |
purchase_link | string | null | URL to purchase the book (Amazon, bookstore, etc.) |
series_id | string | null | UUID of the parent series (if part of a series) |
series_number | number | null | Position within the series (1, 2, 3, etc.) |
custom_attributes.isbn | string | ISBN number |
custom_attributes.page_count | number | Number of pages |
custom_attributes.publisher | string | Publisher name |
Example Response
{
"content_type": "book",
"title": "The Great Adventure",
"slug": "the-great-adventure",
"subtitle": "A Tale of Discovery",
"description": "An epic journey through uncharted territories...",
"content_body": "<p>Book synopsis and details...</p>",
"featured_image": "https://storage.supabase.co/media/tenant-id/book-cover.jpg",
"published_date": "2024-03-15",
"purchase_link": "https://amazon.com/dp/B0123456789",
"series_id": "series-uuid-here",
"series_number": 1,
"custom_attributes": {
"isbn": "978-3-16-148410-0",
"page_count": 350,
"publisher": "Acme Publishing"
},
"tags": ["fiction", "adventure", "fantasy"]
}Series
Book series or collections. Use this to group related books together and provide series-level information.
type=seriesBooks linked to a series will have the series_id field set to this item's ID. Query books in a series using the series_id filter parameter.
Type-Specific Fields
| Field | Type | Description |
|---|---|---|
subtitle | string | null | Series subtitle or tagline |
Example Response
{
"content_type": "series",
"title": "The Adventure Chronicles",
"slug": "adventure-chronicles",
"subtitle": "An Epic Fantasy Saga",
"description": "Follow our heroes across five thrilling novels...",
"content_body": "<p>Series overview and reading order...</p>",
"featured_image": "https://storage.supabase.co/media/tenant-id/series-banner.jpg",
"tags": ["fantasy", "adventure", "series"]
}
// To get all books in this series:
// GET /api/v1/content?type=book&series_id={series-uuid}Event
Upcoming and past events, appearances, signings, and performances. Includes timezone-aware date handling.
type=eventThe event_date is stored in UTC. Use the event_timezone field to display the event in the correct local time. For example, event_date "2025-01-15T20:00:00Z" with event_timezone "America/New_York" should display as "Jan 15, 2025 3:00 PM EST".
Type-Specific Fields
| Field | Type | Description |
|---|---|---|
event_date | timestamp | Event date and time stored in UTC (ISO 8601 format) |
event_timezone | string | IANA timezone identifier (e.g., "America/New_York") |
location | string | null | Venue name and address |
Example Response
{
"content_type": "event",
"title": "Book Signing at Barnes & Noble",
"slug": "book-signing-barnes-noble-2025",
"description": "Join me for a signing of my latest novel...",
"content_body": "<p>Event details and what to expect...</p>",
"featured_image": "https://storage.supabase.co/media/tenant-id/event.jpg",
"event_date": "2025-01-15T20:00:00.000Z",
"event_timezone": "America/New_York",
"location": "Barnes & Noble, 5th Avenue, New York, NY",
"tags": ["book-signing", "nyc", "meet-and-greet"]
}Artwork
Visual art pieces, illustrations, photography, and creative works. Ideal for artists showcasing their portfolio.
type=artworkType-Specific Fields
| Field | Type | Description |
|---|---|---|
published_date | date | Date the artwork was created or published |
purchase_link | string | null | URL to purchase prints or originals |
custom_attributes.medium | string | Art medium (e.g., "Oil on canvas", "Digital") |
custom_attributes.dimensions | string | Physical dimensions (e.g., "24x36 inches") |
custom_attributes.available | boolean | Whether the piece is available for purchase |
Example Response
{
"content_type": "artwork",
"title": "Sunset Over Mountains",
"slug": "sunset-over-mountains",
"description": "A vibrant landscape capturing the golden hour...",
"featured_image": "https://storage.supabase.co/media/tenant-id/artwork.jpg",
"published_date": "2024-08-20",
"purchase_link": "https://shop.example.com/prints/sunset-mountains",
"custom_attributes": {
"medium": "Oil on canvas",
"dimensions": "24x36 inches",
"available": true
},
"tags": ["landscape", "oil-painting", "nature"],
"media": [
{ "url": "...", "alt_text": "Detail shot", "sort_order": 0 },
{ "url": "...", "alt_text": "Framed view", "sort_order": 1 }
]
}Page
Static pages like About, Contact, or custom landing pages. General-purpose content for any page on your site.
type=pageExample Response
{
"content_type": "page",
"title": "About Me",
"slug": "about",
"description": "Learn more about who I am and my creative journey.",
"content_body": "<p>Full page content with rich formatting...</p>",
"featured_image": "https://storage.supabase.co/media/tenant-id/about-hero.jpg",
"meta_title": "About | Jane Smith",
"meta_description": "Learn about Jane Smith, an award-winning author..."
}Music / Performance
Albums, singles, performances, and musical works. For musicians and performers showcasing their audio content.
type=musictype=albumtype=singleType-Specific Fields
| Field | Type | Description |
|---|---|---|
published_date | date | Release date |
purchase_link | string | null | URL to streaming service or purchase page |
custom_attributes.spotify_url | string | Spotify album/track link |
custom_attributes.apple_music_url | string | Apple Music link |
custom_attributes.duration | string | Track/album duration |
Example Response
{
"content_type": "music",
"title": "Midnight Dreams",
"slug": "midnight-dreams-album",
"subtitle": "Debut Studio Album",
"description": "A collection of 12 original songs exploring...",
"content_body": "<p>Track listing and album notes...</p>",
"featured_image": "https://storage.supabase.co/media/tenant-id/album-cover.jpg",
"published_date": "2024-09-01",
"purchase_link": "https://open.spotify.com/album/...",
"custom_attributes": {
"spotify_url": "https://open.spotify.com/album/...",
"apple_music_url": "https://music.apple.com/album/...",
"duration": "45:32"
},
"tags": ["indie", "folk", "acoustic"]
}Media Attachments
When fetching a single content item by ID or slug, the response includes a media array containing all attached images and files. Media is not included in list responses to reduce payload size.
// Media array structure (included in single-item responses)
"media": [
{
"id": "media-uuid-1",
"content_id": "content-uuid",
"tenant_id": "tenant-uuid",
"media_type": "image", // 'image' | 'video' | 'document'
"url": "https://storage.supabase.co/media/tenant-id/image.jpg",
"alt_text": "Description for accessibility",
"size_bytes": 245000,
"metadata": {}, // Additional metadata (dimensions, etc.)
"sort_order": 0, // Display order (lower = first)
"created_at": "2024-01-10T08:00:00Z"
}
]Custom Fields
Beyond the standard fields, tenants can define custom fields for any content type. These are stored in the custom_attributes object and can contain any JSON-compatible data.
// Example custom_attributes for a book
"custom_attributes": {
"isbn": "978-3-16-148410-0",
"page_count": 350,
"publisher": "Acme Publishing",
"awards": ["Hugo Award 2024", "Nebula Finalist"],
"reading_level": "adult"
}
// Custom fields are defined per-tenant in the admin dashboard
// and can be of types: text, number, boolean, date, or selectLinks Page API
/api/v1/links-pageRetrieve the complete links page configuration, links, and sections for rendering.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
api_key* | string | Tenant's API key |
include_disabled | string | Set to "true" to include disabled links |
Example Request
curl "https://thedigitalfair.com/api/v1/links-page?api_key=your_api_key_here"Example Response
{
"success": true,
"data": {
"config": {
"display_name": "Jane Smith",
"bio": "Designer & Creative Director based in NYC.",
"avatar_url": "https://storage.supabase.co/media/tenant-id/avatar.jpg",
"theme": {
"background": {
"type": "gradient",
"value": "linear-gradient(135deg, #667eea 0%, #764ba2 100%)"
},
"buttonStyle": "filled",
"buttonShape": "rounded",
"buttonColor": "#ffffff",
"textColor": "#ffffff",
"fontFamily": "default"
},
"social_icons": [
{
"platform_id": "instagram",
"username": "janesmith",
"url": "https://instagram.com/janesmith",
"show_in_header": true
}
]
},
"links": [
{
"id": "link-uuid-1",
"title": "My Portfolio",
"url": "https://janesmith.com",
"platform_type": "website",
"is_featured": true,
"sort_order": 0
}
],
"sections": [
{
"id": "section-uuid-1",
"title": "Latest Content",
"is_collapsed": false,
"sort_order": 0
}
],
"platforms": {
"instagram": {
"name": "Instagram",
"icon_url": "https://cdn.simpleicons.org/instagram/E4405F",
"brand_color": "#E4405F"
}
}
}
}Press Kit API
Retrieve a tenant's press kit data to build custom press kit pages on your own domain. Press kits include bios, images, media embeds, and section-specific content.
/api/v1/press-kitRetrieve the complete press kit including bios, images, embeds, and all section content.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
api_key* | string | Tenant's API key |
password | string | Password for password-protected press kits |
Example Request
curl "https://thedigitalfair.com/api/v1/press-kit?api_key=your_api_key_here"Example Response
{
"success": true,
"data": {
"pressKit": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"tenantId": "tenant-uuid",
"title": "Jane Smith",
"slug": "press",
"isPublished": true,
"isPasswordProtected": false,
"allowIndexing": true,
"bios": {
"short": "Jane Smith is a bestselling author...",
"medium": "Jane Smith is a bestselling author known for...",
"full": "Jane Smith began writing at age twelve..."
},
"contactEmail": "press@janesmith.com",
"socialLinks": [
{ "platform": "instagram", "url": "https://instagram.com/janesmith" }
],
"interviewQuestions": [
"What inspired you to become a writer?",
"How do you develop your characters?"
],
"images": [
{
"id": "image-uuid",
"caption": "Author headshot",
"originalUrl": "https://storage.supabase.co/...",
"thumbnailUrl": "https://storage.supabase.co/...",
"width": 2400,
"height": 3200
}
],
"embeds": [
{
"id": "embed-uuid",
"platform": "youtube",
"embedUrl": "https://youtube.com/watch?v=...",
"embedHtml": "<iframe ...>",
"title": "Book Trailer"
}
]
},
"tenant": {
"subdomain": "janesmith",
"name": "Jane Smith",
"siteType": "author"
},
"enabledSections": ["bios", "images", "embeds", "interview_questions", "contact"]
},
"preview_mode": false
}Password-Protected Response
If the press kit is password-protected and no password is provided:
{
"success": true,
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"title": "Jane Smith",
"slug": "press",
"isPasswordProtected": true,
"requiresPassword": true
}
}Section-Specific Fields
Different site types have different sections available:
| Site Type | Special Sections |
|---|---|
| Author | sampleChapterUrl, comparisonTitles, speakingTopics |
| Artist | artistStatement, exhibitionHistory, commissionInfo |
| Musician | technicalRider, stagePlotUrl, genreDescription, notableVenues |
| Professional | Common sections only (bios, images, embeds, contact) |
Analytics & Tracking
Track user engagement on your custom website using Umami analytics. This enables you to see purchase link clicks and other custom events in your tenant analytics dashboard.
Setting Up Umami Tracking
If your tenant has analytics enabled, you'll receive an Umami Website ID from your admin. Add the Umami tracking script to your website's <head> section:
<!-- Add to your HTML <head> section -->
<script
defer
src="https://cloud.umami.is/script.js"
data-website-id="YOUR_WEBSITE_ID"
></script>Replace YOUR_WEBSITE_ID with the Website ID provided in your tenant settings.
Framework Integration
Next.js (App Router)
// app/layout.tsx
import Script from 'next/script'
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html>
<head>
<Script
defer
src="https://cloud.umami.is/script.js"
data-website-id={process.env.NEXT_PUBLIC_UMAMI_WEBSITE_ID}
/>
</head>
<body>{children}</body>
</html>
)
}React (Vite/CRA)
// index.html
<!DOCTYPE html>
<html>
<head>
<script
defer
src="https://cloud.umami.is/script.js"
data-website-id="YOUR_WEBSITE_ID"
></script>
</head>
<body>
<div id="root"></div>
</body>
</html>Tracking Purchase Clicks
To track when users click purchase links (e.g., links to Amazon, bookstores, etc.), use the purchase_click event:
// Track a purchase link click
window.umami?.track('purchase_click', {
retailer: 'Amazon', // The retailer name (e.g., 'Amazon', 'Barnes & Noble')
book: 'The Great Adventure', // The book/product title
})This data will appear in your Analytics Dashboard under "Purchase Clicks", showing total clicks, clicks by retailer, and clicks by book.
React Component Example
interface PurchaseLink {
retailer: string
url: string
}
interface BookCardProps {
title: string
purchaseLinks: PurchaseLink[]
}
function BookCard({ title, purchaseLinks }: BookCardProps) {
const handlePurchaseClick = (retailer: string) => {
// Track the click before navigation
if (typeof window !== 'undefined' && window.umami) {
window.umami.track('purchase_click', {
retailer,
book: title,
})
}
}
return (
<div className="book-card">
<h3>{title}</h3>
<div className="purchase-links">
{purchaseLinks.map((link) => (
<a
key={link.retailer}
href={link.url}
target="_blank"
rel="noopener noreferrer"
onClick={() => handlePurchaseClick(link.retailer)}
>
Buy on {link.retailer}
</a>
))}
</div>
</div>
)
}TypeScript Declaration
Add this type declaration for proper TypeScript support:
// types/umami.d.ts
interface UmamiTracker {
track: (eventName: string, eventData?: Record<string, string | number>) => void
}
declare global {
interface Window {
umami?: UmamiTracker
}
}
export {}Supported Events
| Event Name | Properties | Description |
|---|---|---|
purchase_click | retailer, book | User clicked a purchase/buy link |
Additional events may be added in future versions. Check the changelog for updates.
TypeScript Types
Content Item Type
interface ContentItem {
id: string; // UUID
tenant_id: string; // UUID of the tenant
content_type: string; // 'project' | 'blog' | 'event' | 'book' | 'page'
title: string; // Display title
slug: string; // URL-safe identifier
subtitle: string | null; // Optional subtitle
description: string | null; // Short description/excerpt
content_body: string | null; // Full HTML content
featured_image: string | null; // URL to main image
status: 'draft' | 'published' | 'archived';
date_published: string | null; // ISO 8601 timestamp
event_date: string | null; // For events - UTC ISO 8601
event_timezone: string | null; // For events - IANA timezone
sort_order: number; // Display order (higher = first)
tags: string[] | null; // Array of tag strings
core_attributes: Record<string, any>;
custom_attributes: Record<string, any>;
project_link: string | null; // External project URL
purchase_link: string | null; // Buy/purchase URL
series_id: string | null; // UUID of parent series
series_number: number | null; // Order within series
location: string | null; // For events - location
meta_title: string | null; // SEO title
meta_description: string | null; // SEO description
created_at: string; // ISO 8601 timestamp
updated_at: string; // ISO 8601 timestamp
}Content Media Type
interface ContentMedia {
id: string; // UUID
content_id: string; // UUID of parent content item
tenant_id: string; // UUID of tenant
media_type: string; // 'image' | 'video' | 'document'
url: string; // Public URL to media file
alt_text: string | null; // Accessibility text
size_bytes: number | null; // File size
metadata: Record<string, any>; // Additional metadata
sort_order: number; // Display order
created_at: string; // ISO 8601 timestamp
}API Response Types
// List response with pagination
interface ContentListResponse {
success: true;
data: {
items: ContentItem[];
pagination: {
limit: number;
offset: number;
total: number;
};
};
}
// Single item response (includes media)
interface ContentItemResponse {
success: true;
data: ContentItem & {
media: ContentMedia[];
};
}
// Error response
interface ErrorResponse {
success: false;
error: string;
code?: string;
details?: string;
}Code Examples
JavaScript / TypeScript
const API_KEY = process.env.CMS_API_KEY; // Server-side only!
const BASE_URL = 'https://thedigitalfair.com/api/v1';
async function fetchProjects() {
const response = await fetch(
`${BASE_URL}/content?api_key=${API_KEY}&type=project&status=published`
);
if (!response.ok) {
throw new Error(`API Error: ${response.status}`);
}
const data = await response.json();
if (!data.success) {
throw new Error('Failed to fetch projects');
}
return data.data.items;
}
// Usage
const projects = await fetchProjects();
projects.forEach(project => {
console.log(`${project.title}: ${project.description}`);
});Next.js Server-Side Fetching
// app/portfolio/page.tsx
import { Metadata } from 'next';
const API_KEY = process.env.CMS_API_KEY!; // Server-side only
const BASE_URL = 'https://thedigitalfair.com/api/v1';
async function getProjects() {
const response = await fetch(
`${BASE_URL}/content?api_key=${API_KEY}&type=project&status=published`,
{ next: { revalidate: 60 } } // Revalidate every 60 seconds
);
if (!response.ok) {
throw new Error('Failed to fetch projects');
}
const data = await response.json();
return data.data.items;
}
export default async function PortfolioPage() {
const projects = await getProjects();
return (
<main className="container mx-auto py-12">
<h1 className="text-4xl font-bold mb-8">Portfolio</h1>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
{projects.map((project) => (
<a key={project.id} href={`/portfolio/${project.slug}`}>
{project.featured_image && (
<img
src={project.featured_image}
alt={project.title}
className="w-full aspect-video object-cover rounded-lg"
/>
)}
<h2 className="text-xl font-semibold mt-4">{project.title}</h2>
<p className="text-gray-600 mt-2">{project.description}</p>
</a>
))}
</div>
</main>
);
}Python
import requests
from typing import List, Dict, Any
API_KEY = "your-api-key"
BASE_URL = "https://thedigitalfair.com/api/v1"
def fetch_projects(
content_type: str = "project",
status: str = "published",
limit: int = 50,
offset: int = 0
) -> List[Dict[str, Any]]:
"""Fetch content items from The Digital Fair CMS."""
params = {
"api_key": API_KEY,
"type": content_type,
"status": status,
"limit": limit,
"offset": offset
}
response = requests.get(f"{BASE_URL}/content", params=params)
response.raise_for_status()
data = response.json()
if not data.get("success"):
raise Exception("API request failed")
return data["data"]["items"]
# Usage
if __name__ == "__main__":
projects = fetch_projects()
for project in projects:
print(f"- {project['title']} ({project['slug']})"Error Handling
HTTP Status Codes
| Code | Meaning | Common Causes |
|---|---|---|
200 | Success | Request completed successfully |
400 | Bad Request | Invalid parameters, missing required fields |
401 | Unauthorized | Missing or invalid API key |
404 | Not Found | Content item doesn't exist, or links page not configured |
500 | Server Error | Internal server error |
Error Response Format
{
"success": false,
"error": "Human-readable error message",
"code": "ERROR_CODE",
"details": "Additional details (optional)"
}Common Error Codes
| Code | Description |
|---|---|
INVALID_API_KEY | The provided API key is invalid or expired |
NOT_CONFIGURED | Links page has not been set up for this tenant |
NOT_PUBLISHED | Links page exists but is not published |
CONTENT_NOT_FOUND | The requested content item was not found |
Error Handling Example
async function fetchWithErrorHandling(endpoint: string) {
try {
const response = await fetch(`${BASE_URL}${endpoint}?api_key=${API_KEY}`);
if (!response.ok) {
const errorData = await response.json();
switch (response.status) {
case 401:
throw new Error('Invalid API key. Please check your credentials.');
case 404:
if (errorData.code === 'NOT_CONFIGURED') {
throw new Error('This feature has not been set up yet.');
}
if (errorData.code === 'NOT_PUBLISHED') {
throw new Error('This content is not publicly available.');
}
throw new Error('The requested resource was not found.');
case 500:
throw new Error('Server error. Please try again later.');
default:
throw new Error(errorData.error || 'An unknown error occurred.');
}
}
return await response.json();
} catch (error) {
if (error instanceof TypeError) {
throw new Error('Network error. Please check your connection.');
}
throw error;
}
}Rate Limiting
Current Status: No rate limiting is currently implemented.
Best Practices
- Cache responses on your server to reduce API calls
- Use Incremental Static Regeneration (ISR) in Next.js
- Avoid making API calls on every page load
- Use server-side fetching to protect your API key
Need Help?
If you have questions about the API or need assistance with your integration, we're here to help. Reach out to our support team.
Contact Support