Overview
Skayle CMS allows Skayle to act as your primary headless content management system, exposing content through a JSON:API-compliant REST API. This is ideal when you:
- Don’t have an existing CMS
- Want to build a custom frontend
- Need a simple, fast content API
- Want full control over your content structure
How It Works
In Skayle CMS mode:
- Content is created and managed in Skayle
- Skayle exposes a REST API for your content
- Your frontend fetches content directly from Skayle
- No external CMS synchronization needed
┌─────────────┐ REST API ┌─────────────┐
│ Skayle │ ───────────────► │ Frontend │
│ (CMS) │ JSON responses │ (Next.js, │
│ │ │ Astro, │
└─────────────┘ │ etc.) │
└─────────────┘
Setup Guide
Step 1: Enable Skayle CMS
- Go to Settings → Connectors in Skayle
- Select Skayle CMS as your connector type
- Configure your API settings
Step 2: Generate API Key
- Go to Settings → API Keys
- Click Generate New Key
- Set permissions (read-only or read-write)
- Copy the generated key
API keys provide access to your content. Use read-only keys for public frontends and keep write keys secure.
Use the Skayle API endpoint in your frontend:
const SKAYLE_API = "https://api.skayle.ai/v1";
const API_KEY = process.env.SKAYLE_API_KEY;
async function getPosts() {
const response = await fetch(`${SKAYLE_API}/posts`, {
headers: {
"Authorization": `Bearer ${API_KEY}`,
"Content-Type": "application/json"
}
});
return response.json();
}
API Endpoints
Posts
| Method | Endpoint | Description |
|---|
| GET | /posts | List all posts |
| GET | /posts/:id | Get single post |
| GET | /posts/slug/:slug | Get post by slug |
Categories
| Method | Endpoint | Description |
|---|
| GET | /categories | List all categories |
| GET | /categories/:id | Get single category |
| GET | /categories/:id/posts | Get posts in category |
| Method | Endpoint | Description |
|---|
| GET | /tags | List all tags |
| GET | /tags/:id | Get single tag |
| GET | /tags/:id/posts | Get posts with tag |
Authors
| Method | Endpoint | Description |
|---|
| GET | /authors | List all authors |
| GET | /authors/:id | Get single author |
| GET | /authors/:id/posts | Get posts by author |
| Method | Endpoint | Description |
|---|
| GET | /media | List all media |
| GET | /media/:id | Get single media item |
All responses follow JSON:API specification:
Single Resource
{
"data": {
"id": "post-123",
"type": "post",
"attributes": {
"title": "Getting Started with Skayle",
"slug": "getting-started-with-skayle",
"excerpt": "Learn how to use Skayle...",
"content": "<p>Full HTML content...</p>",
"publishedAt": "2024-01-15T10:00:00Z",
"createdAt": "2024-01-14T08:30:00Z",
"updatedAt": "2024-01-15T09:45:00Z"
},
"relationships": {
"author": {
"data": { "id": "author-1", "type": "author" }
},
"categories": {
"data": [
{ "id": "cat-1", "type": "category" }
]
},
"tags": {
"data": [
{ "id": "tag-1", "type": "tag" },
{ "id": "tag-2", "type": "tag" }
]
}
}
},
"included": [
{
"id": "author-1",
"type": "author",
"attributes": {
"name": "John Doe",
"slug": "john-doe"
}
}
]
}
Collection
{
"data": [
{ "id": "post-1", "type": "post", "attributes": {...} },
{ "id": "post-2", "type": "post", "attributes": {...} }
],
"meta": {
"total": 42,
"page": 1,
"perPage": 10,
"totalPages": 5
},
"links": {
"self": "/posts?page=1",
"first": "/posts?page=1",
"last": "/posts?page=5",
"next": "/posts?page=2"
}
}
Query Parameters
GET /posts?page=2&perPage=20
| Parameter | Default | Max | Description |
|---|
page | 1 | - | Page number |
perPage | 10 | 100 | Items per page |
Filtering
GET /posts?filter[status]=published
GET /posts?filter[category]=technology
GET /posts?filter[author]=john-doe
Sorting
GET /posts?sort=-publishedAt # Newest first
GET /posts?sort=title # Alphabetical
GET /posts?sort=-updatedAt # Recently updated
Including Relations
GET /posts?include=author,categories,tags
GET /posts/123?include=author
Sparse Fieldsets
GET /posts?fields[post]=title,slug,excerpt
GET /posts?fields[author]=name
Content Formats
HTML Content
By default, content is returned as HTML:
{
"content": "<h2>Introduction</h2><p>Welcome to...</p>"
}
Raw BlockNote JSON
Request raw editor format:
GET /posts/123?format=raw
{
"content": {
"type": "doc",
"content": [
{
"type": "heading",
"attrs": { "level": 2 },
"content": [{ "type": "text", "text": "Introduction" }]
}
]
}
}
Framework Examples
// lib/skayle.ts
const API_URL = process.env.SKAYLE_API_URL;
const API_KEY = process.env.SKAYLE_API_KEY;
export async function getPosts() {
const res = await fetch(`${API_URL}/posts?include=author,categories`, {
headers: { Authorization: `Bearer ${API_KEY}` },
next: { revalidate: 60 }
});
return res.json();
}
export async function getPost(slug: string) {
const res = await fetch(`${API_URL}/posts/slug/${slug}?include=author`, {
headers: { Authorization: `Bearer ${API_KEY}` },
next: { revalidate: 60 }
});
return res.json();
}
// app/blog/page.tsx
export default async function BlogPage() {
const { data: posts } = await getPosts();
return (
<ul>
{posts.map(post => (
<li key={post.id}>{post.attributes.title}</li>
))}
</ul>
);
}
---
// src/pages/blog/index.astro
const API_URL = import.meta.env.SKAYLE_API_URL;
const API_KEY = import.meta.env.SKAYLE_API_KEY;
const response = await fetch(`${API_URL}/posts?include=author`, {
headers: { Authorization: `Bearer ${API_KEY}` }
});
const { data: posts } = await response.json();
---
<ul>
{posts.map(post => (
<li>
<a href={`/blog/${post.attributes.slug}`}>
{post.attributes.title}
</a>
</li>
))}
</ul>
// composables/useSkayle.ts
export const useSkayle = () => {
const config = useRuntimeConfig();
const fetchPosts = async () => {
return await $fetch('/posts?include=author', {
baseURL: config.public.skayleApiUrl,
headers: {
Authorization: `Bearer ${config.skayleApiKey}`
}
});
};
return { fetchPosts };
};
// pages/blog/index.vue
<script setup>
const { fetchPosts } = useSkayle();
const { data: posts } = await useAsyncData('posts', fetchPosts);
</script>
Webhooks
Configure webhooks to receive notifications when content changes:
Available Events
| Event | Description |
|---|
post.created | New post created |
post.updated | Post updated |
post.published | Post published |
post.unpublished | Post unpublished |
post.deleted | Post deleted |
Webhook Payload
{
"event": "post.published",
"timestamp": "2024-01-15T10:00:00Z",
"data": {
"id": "post-123",
"type": "post",
"attributes": {
"title": "New Post",
"slug": "new-post"
}
}
}
Setting Up Webhooks
- Go to Settings → Webhooks
- Click Add Webhook
- Enter your endpoint URL
- Select events to subscribe to
- Save and test
Use webhooks to trigger rebuilds of static sites or invalidate caches when content changes.
Caching Strategies
CDN Caching
Skayle sets appropriate cache headers:
Cache-Control: public, max-age=60, stale-while-revalidate=300
ISR (Incremental Static Regeneration)
For Next.js or similar frameworks:
export const revalidate = 60; // Revalidate every 60 seconds
On-Demand Revalidation
Use webhooks to trigger revalidation:
// pages/api/revalidate.ts
export default async function handler(req, res) {
const { slug } = req.body.data.attributes;
await res.revalidate(`/blog/${slug}`);
return res.json({ revalidated: true });
}
Rate Limits
| Plan | Requests/Minute | Requests/Day |
|---|
| Free | 60 | 10,000 |
| Pro | 300 | 100,000 |
| Enterprise | Custom | Custom |
Best Practices
- Use caching: Implement proper caching to reduce API calls
- Include relations: Fetch related data in single requests
- Use sparse fieldsets: Only request fields you need
- Set up webhooks: Automate cache invalidation
- Monitor usage: Track API usage to stay within limits
Next Steps