CMS API
Manage structured content with collections and entries. Arky’s CMS uses a flexible blocks-based system for dynamic content modeling.
Integration Patterns
Section titled “Integration Patterns”Before using the raw API, see the CMS Content Integration Guide for complete patterns:
- SSR Content Fetching: Server-side initialization with API keys
- Multilingual Content: Locale-based fetching with fallbacks
- Block Value Extraction: Use SDK utilities for clean value parsing
- Media CDN URLs: Build optimized image URLs from storage paths
Typical SSR Content Flow
Section titled “Typical SSR Content Flow”import { createArkySDK } from 'arky-sdk';
// Server-side init (Astro, Next.js, etc.)const sdk = createArkySDK({ baseUrl: 'https://api.arky.io', businessId: process.env.ARKY_BUSINESS_ID, autoGuest: false, getToken: () => ({ accessToken: process.env.ARKY_API_KEY, provider: 'API', expiresAt: 0, }), setToken: () => {}, logout: () => {},});
// Fetch collectionconst collection = await sdk.cms.getCollection({ id: 'blog' });
// Fetch entriesconst { items: posts } = await sdk.cms.getCollectionEntries({ collectionId: 'blog', limit: 20,});
// Extract block valuesposts.forEach(post => { const title = sdk.utils.getBlockTextValue( post.blocks.find(b => b.key === 'title'), 'en' // locale );
const imageBlock = post.blocks.find(b => b.key === 'featured_image'); const imageUrl = sdk.utils.getImageUrl(imageBlock);
console.log({ title, imageUrl });});See CMS Integration Guide for full patterns.
Base URL
Section titled “Base URL”All CMS endpoints are prefixed with /v1/businesses/{businessId}.
Authentication
Section titled “Authentication”Requires Authorization: Bearer <access_token> header with appropriate permissions.
Collections
Section titled “Collections”Collections are content types (like “Blog Posts”, “Products”, “Pages”). They define the structure and blocks schema.
Create Collection
Section titled “Create Collection”Endpoint: POST /v1/businesses/{businessId}/collections
const collection = await sdk.cms.createCollection({ name: 'Blog Posts', slug: 'blog-posts', description: 'Articles and blog content', blocks: [ { id: 'title', type: 'TEXT', label: 'Title', required: true, settings: { maxLength: 200 }, }, { id: 'content', type: 'TEXT', label: 'Content', required: true, settings: { variant: 'HTML' }, }, { id: 'author', type: 'RELATIONSHIP', label: 'Author', settings: { targetType: 'USER' }, }, { id: 'published', type: 'BOOLEAN', label: 'Published', }, ], statuses: [ { id: 'draft', status: 'DRAFT' }, { id: 'published', status: 'PUBLISHED' }, ],});curl -X POST https://api.arky.io/v1/businesses/YOUR_BUSINESS_ID/collections \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Blog Posts", "slug": "blog-posts", "description": "Articles and blog content", "blocks": [...], "statuses": [...] }'Response:
{ "id": "coll_abc123", "businessId": "biz_123", "name": "Blog Posts", "slug": "blog-posts", "description": "Articles and blog content", "blocks": [...], "statuses": [ { "id": "draft", "status": "DRAFT", "changedBy": "SYSTEM", "timestamp": 1698765432 } ], "createdAt": 1698765432, "updatedAt": 1698765432}Get Collections
Section titled “Get Collections”List all collections.
Endpoint: GET /v1/businesses/{businessId}/collections
const { items, cursor } = await sdk.cms.getCollections({ limit: 20, cursor: null,});curl "https://api.arky.io/v1/businesses/YOUR_BUSINESS_ID/collections?limit=20" \ -H "Authorization: Bearer YOUR_TOKEN"Get Collection
Section titled “Get Collection”Fetch a specific collection by ID.
Endpoint: GET /v1/businesses/{businessId}/collections/{id}
const collection = await sdk.cms.getCollection({ id: 'coll_abc123',});curl https://api.arky.io/v1/businesses/YOUR_BUSINESS_ID/collections/COLLECTION_ID \ -H "Authorization: Bearer YOUR_TOKEN"Update Collection
Section titled “Update Collection”Endpoint: PUT /v1/businesses/{businessId}/collections/{id}
const updated = await sdk.cms.updateCollection({ id: 'coll_abc123', name: 'Updated Blog Posts', blocks: [...], // Updated schema});curl -X PUT https://api.arky.io/v1/businesses/YOUR_BUSINESS_ID/collections/COLLECTION_ID \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Updated Blog Posts", "blocks": [...] }'Delete Collection
Section titled “Delete Collection”Endpoint: DELETE /v1/businesses/{businessId}/collections/{id}
await sdk.cms.deleteCollection({ id: 'coll_abc123',});curl -X DELETE https://api.arky.io/v1/businesses/YOUR_BUSINESS_ID/collections/COLLECTION_ID \ -H "Authorization: Bearer YOUR_TOKEN"Entries
Section titled “Entries”Entries are individual content items (like a specific blog post) that belong to a collection.
Owner Model
Section titled “Owner Model”Entries use an owner field to specify their parent:
collection:{collectionId}- Entry belongs to a collectionorder:{orderId}- Entry belongs to an orderreservation:{reservationId}- Entry belongs to a reservation
The SDK automatically converts collectionId to owner: "collection:{collectionId}".
Create Entry
Section titled “Create Entry”Endpoint: POST /v1/businesses/{businessId}/entries
const entry = await sdk.cms.createCollectionEntry({ collectionId: 'coll_abc123', // Converted to owner: "collection:coll_abc123" locale: 'en', blocks: [ { id: 'title', type: 'TEXT', value: 'My First Blog Post', }, { id: 'content', type: 'TEXT', value: { en: '<p>This is the content...</p>' }, }, { id: 'author', type: 'RELATIONSHIP', value: 'user_xyz', }, { id: 'published', type: 'BOOLEAN', value: true, }, ], statuses: [ { id: 'status_1', status: 'PUBLISHED', changedBy: 'USER', userId: 'user_xyz', timestamp: Date.now() / 1000, }, ],});curl -X POST https://api.arky.io/v1/businesses/YOUR_BUSINESS_ID/entries \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "owner": "collection:coll_abc123", "locale": "en", "blocks": [...], "statuses": [...] }'Get Entries
Section titled “Get Entries”List entries (optionally filtered by collection).
Endpoint: GET /v1/businesses/{businessId}/entries
const { items, cursor } = await sdk.cms.getCollectionEntries({ collectionId: 'coll_abc123', // Optional: filter by collection limit: 20, cursor: null,});
// Or get all entries across collections:const allEntries = await sdk.cms.getCollectionEntries({ limit: 50,});# Filter by collectioncurl "https://api.arky.io/v1/businesses/YOUR_BUSINESS_ID/entries?owner=collection:COLLECTION_ID&limit=20" \ -H "Authorization: Bearer YOUR_TOKEN"
# All entriescurl "https://api.arky.io/v1/businesses/YOUR_BUSINESS_ID/entries?limit=50" \ -H "Authorization: Bearer YOUR_TOKEN"Get Entry
Section titled “Get Entry”Fetch a specific entry by ID.
Endpoint: GET /v1/businesses/{businessId}/entries/{id}
const entry = await sdk.cms.getCollectionEntry({ id: 'entry_abc123',});
console.log('Blocks:', entry.blocks);console.log('Owner:', entry.owner); // e.g., "collection:coll_abc123"curl https://api.arky.io/v1/businesses/YOUR_BUSINESS_ID/entries/ENTRY_ID \ -H "Authorization: Bearer YOUR_TOKEN"Update Entry
Section titled “Update Entry”Endpoint: PUT /v1/businesses/{businessId}/entries/{id}
const updated = await sdk.cms.updateCollectionEntry({ id: 'entry_abc123', collectionId: 'coll_abc123', locale: 'en', blocks: [ { id: 'title', type: 'TEXT', value: 'Updated Title', }, // ... other blocks ], statuses: [...],});curl -X PUT https://api.arky.io/v1/businesses/YOUR_BUSINESS_ID/entries/ENTRY_ID \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "owner": "collection:coll_abc123", "locale": "en", "blocks": [...] }'Delete Entry
Section titled “Delete Entry”Endpoint: DELETE /v1/businesses/{businessId}/entries/{id}
await sdk.cms.deleteCollectionEntry({ id: 'entry_abc123',});curl -X DELETE https://api.arky.io/v1/businesses/YOUR_BUSINESS_ID/entries/ENTRY_ID \ -H "Authorization: Bearer YOUR_TOKEN"AI Block Generation
Section titled “AI Block Generation”Generate block content using AI.
Endpoint: POST /v1/businesses/{businessId}/collections/blocks/generate
const generated = await sdk.cms.generateBlocks({ prompt: 'Write a blog post about TypeScript best practices', blocks: [ { id: 'title', type: 'TEXT' }, { id: 'content', type: 'TEXT', settings: { variant: 'HTML' } }, { id: 'tags', type: 'TEXT' }, ],});
console.log('Generated blocks:', generated.blocks);// Use in createCollectionEntrycurl -X POST https://api.arky.io/v1/businesses/YOUR_BUSINESS_ID/collections/blocks/generate \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "prompt": "Write a blog post about TypeScript best practices", "blocks": [ { "id": "title", "type": "TEXT" }, { "id": "content", "type": "TEXT", "settings": { "variant": "HTML" } } ] }'Response:
{ "blocks": [ { "id": "title", "type": "TEXT", "value": "10 TypeScript Best Practices for 2024" }, { "id": "content", "type": "TEXT", "value": { "en": "<p>TypeScript has become...</p>" } } ]}Newsletters
Section titled “Newsletters”Newsletters are Collections with type: 'NEWSLETTER'. They support subscriber management, paid plans, and email sending.
Create Newsletter Collection
Section titled “Create Newsletter Collection”Create a collection with newsletter-specific settings:
const newsletter = await sdk.cms.createCollection({ name: 'Weekly Tech Digest', slug: 'weekly-tech', type: 'NEWSLETTER', description: 'Weekly technology news and insights', plans: [ { id: 'plan_free', name: 'Free Edition', description: 'Basic weekly digest', price: { amount: 0, currency: 'usd', interval: 'MONTHLY', }, features: ['Weekly newsletter', 'Archive access'], }, { id: 'plan_premium', name: 'Premium', description: 'Full access with exclusive content', price: { amount: 999, // $9.99 currency: 'usd', interval: 'MONTHLY', }, features: ['Daily newsletter', 'Exclusive content', 'Archive access', 'Ad-free'], }, ], blocks: [ { id: 'subject', type: 'TEXT', label: 'Email Subject', required: true, }, { id: 'content', type: 'TEXT', label: 'Content', required: true, settings: { variant: 'HTML' }, }, ],});curl -X POST https://api.arky.io/v1/businesses/YOUR_BUSINESS_ID/collections \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Weekly Tech Digest", "slug": "weekly-tech", "type": "NEWSLETTER", "plans": [...], "blocks": [...] }'Subscribe to Newsletter
Section titled “Subscribe to Newsletter”Subscribe a user to a newsletter with a specific plan.
Endpoint: POST /v1/businesses/{businessId}/collections/{collectionId}/subscribe
// Free newsletter subscriptionconst subscription = await sdk.cms.subscribeToCollection({ collectionId: 'coll_newsletter_123', planId: 'plan_free', // Required - must exist in collection.plans});
// Paid newsletter subscription (returns Stripe checkout URL)const { checkoutUrl } = await sdk.cms.subscribeToCollection({ collectionId: 'coll_newsletter_123', planId: 'plan_premium',});
// Redirect user to checkoutUrl to complete paymentwindow.location.href = checkoutUrl;curl -X POST https://api.arky.io/v1/businesses/YOUR_BUSINESS_ID/collections/COLLECTION_ID/subscribe \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "id": "COLLECTION_ID", "email": "[email protected]", "market": "us", "plan_id": "plan_free" }'Request Body:
id(string, required): Collection IDemail(string, required): Subscriber emailmarket(string, required): Market code (e.g., “us”, “uk”)plan_id(string, required): Plan ID from collection.plans
Response (Free Plan):
{ "id": "sub_abc123", "entity": "collection:coll_newsletter_123", "planId": "plan_free", "status": "ACTIVE", "createdAt": 1698765432}Response (Paid Plan):
{ "checkoutUrl": "https://checkout.stripe.com/c/pay/cs_test_..."}Get Newsletter Subscribers
Section titled “Get Newsletter Subscribers”List all subscribers to a newsletter.
Endpoint: GET /v1/businesses/{businessId}/collections/{id}/subscribers
const { items, cursor } = await sdk.cms.getCollectionSubscribers({ id: 'coll_newsletter_123', limit: 50, cursor: null,});
items.forEach(sub => { console.log(`${sub.email} - Plan: ${sub.planId} - Status: ${sub.status}`);});curl "https://api.arky.io/v1/businesses/YOUR_BUSINESS_ID/collections/COLLECTION_ID/subscribers?limit=50" \ -H "Authorization: Bearer YOUR_TOKEN"Response:
{ "items": [ { "id": "sub_abc123", "entity": "collection:coll_newsletter_123", "planId": "plan_free", "status": "ACTIVE", "createdAt": 1698765432, "updatedAt": 1698765432 } ], "cursor": "next_page_token"}Unsubscribe from Newsletter
Section titled “Unsubscribe from Newsletter”Unsubscribe using a token (typically sent in newsletter emails).
Endpoint: POST /v1/businesses/{businessId}/collections/unsubscribe
await sdk.cms.unsubscribeFromCollection({ token: 'unsubscribe_token_from_email',});curl -X POST https://api.arky.io/v1/businesses/YOUR_BUSINESS_ID/collections/unsubscribe \ -H "Content-Type: application/json" \ -d '{ "token": "unsubscribe_token_from_email" }'Send Newsletter Entry
Section titled “Send Newsletter Entry”Send an entry as a newsletter email to all subscribers.
Endpoint: POST /v1/businesses/{businessId}/entries/{entryId}/send
// Send immediatelyawait sdk.cms.sendEntry({ entryId: 'entry_abc123',});
// Schedule for laterawait sdk.cms.sendEntry({ entryId: 'entry_abc123', scheduledAt: Math.floor(Date.now() / 1000) + 3600, // Send in 1 hour});curl -X POST https://api.arky.io/v1/businesses/YOUR_BUSINESS_ID/entries/ENTRY_ID/send \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "businessId": "YOUR_BUSINESS_ID", "entryId": "ENTRY_ID", "scheduledAt": 1698765432 }'Note: Entry must belong to a newsletter collection with owner: "collection:{newsletterId}".
Variable Metadata
Section titled “Variable Metadata”Get available variables for entry templates.
Endpoint: GET /v1/collections/entry-types/{entryType}/variables
const metadata = await sdk.cms.getVariableMetadata({ entryType: 'NEWSLETTER',});
console.log('Available variables:', metadata.variables);// e.g., {{ subscriber.name }}, {{ unsubscribe_url }}curl https://api.arky.io/v1/collections/entry-types/NEWSLETTER/variables \ -H "Authorization: Bearer YOUR_TOKEN"Block Types
Section titled “Block Types”Common block types available:
TEXT: Text content (usevariant: "HTML"for rich content)NUMBER: Numeric valuesBOOLEAN: True/falseDATE: TimestampRELATIONSHIP: Reference to another entity (user, product, etc.)GEO_LOCATION: Latitude/longitudeMEDIA: Image/video referencesSELECT: Single selection from optionsMULTI_SELECT: Multiple selections
See Blocks Guide for detailed documentation.
Related
Section titled “Related”- Core Concepts: CMS
- Blocks System Guide
- SDK Usage Guide: Learn how to use the SDK