Scalar LogoScalar
API Reference

API Reference

Complete API reference for Scalar CMS

Authentication

All API requests require authentication using JWT tokens or API keys.

Getting an API Key

Navigate to the admin panel at /admin
Go to Settings → API Keys
Click "Generate New Key" and copy the generated key
Include the key in your requests using the Authorization header
Authorization: Bearer your-api-key-here

REST API

The REST API follows RESTful conventions and returns JSON responses.

Base URL

https://your-domain.com/api

Content Operations

Get All Records

GET /api/{model}

Parameters:

  • limit (number): Number of records to return (default: 20, max: 100)
  • offset (number): Number of records to skip (default: 0)
  • sort (string): Field to sort by (prefix with - for descending)
  • filter (object): Filtering conditions
  • populate (string[]): Related fields to include

Example:

curl -X GET "https://your-domain.com/api/blogPost?limit=10&sort=-publishedAt&populate=author,categories" \
  -H "Authorization: Bearer your-api-key"

Response:

{
  "data": [
    {
      "id": "1",
      "title": "My First Blog Post",
      "slug": "my-first-blog-post",
      "content": "<p>This is the content...</p>",
      "publishedAt": "2024-01-15T10:00:00Z",
      "author": {
        "id": "1",
        "name": "John Doe",
        "email": "john@example.com"
      },
      "categories": [
        {
          "id": "1",
          "name": "Technology",
          "slug": "technology"
        }
      ]
    }
  ],
  "meta": {
    "total": 150,
    "limit": 10,
    "offset": 0,
    "hasMore": true
  }
}

Get Single Record

GET /api/{model}/{id}

Example:

curl -X GET "https://your-domain.com/api/blogPost/1?populate=author" \
  -H "Authorization: Bearer your-api-key"

Create Record

POST /api/{model}

Example:

curl -X POST "https://your-domain.com/api/blogPost" \
  -H "Authorization: Bearer your-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "New Blog Post",
    "content": "<p>This is the content</p>",
    "status": "published"
  }'

Update Record

PUT /api/{model}/{id}
PATCH /api/{model}/{id}

Example:

curl -X PATCH "https://your-domain.com/api/blogPost/1" \
  -H "Authorization: Bearer your-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Updated Blog Post Title"
  }'

Delete Record

DELETE /api/{model}/{id}

Example:

curl -X DELETE "https://your-domain.com/api/blogPost/1" \
  -H "Authorization: Bearer your-api-key"

Advanced Filtering

Use the filter parameter for complex queries:

# Filter by status
GET /api/blogPost?filter[status]=published

# Filter by date range
GET /api/blogPost?filter[publishedAt][$gte]=2024-01-01&filter[publishedAt][$lt]=2024-02-01

# Filter by related field
GET /api/blogPost?filter[author.name]=John Doe

# Multiple conditions
GET /api/blogPost?filter[status]=published&filter[featured]=true

Filtering Operators

OperatorDescriptionExample
$eqEqual tofilter[status][$eq]=published
$neNot equal tofilter[status][$ne]=draft
$gtGreater thanfilter[views][$gt]=100
$gteGreater than or equalfilter[publishedAt][$gte]=2024-01-01
$ltLess thanfilter[views][$lt]=1000
$lteLess than or equalfilter[publishedAt][$lte]=2024-12-31
$inIn arrayfilter[status][$in]=published,featured
$ninNot in arrayfilter[status][$nin]=draft,archived
$containsContains substringfilter[title][$contains]=tutorial
$startsWithStarts withfilter[slug][$startsWith]=how-to
$endsWithEnds withfilter[slug][$endsWith]=guide

GraphQL API

GraphQL endpoint provides a flexible query language for fetching exactly the data you need.

Endpoint

https://your-domain.com/graphql

Schema

The GraphQL schema is automatically generated from your content models:

type BlogPost {
  id: ID!
  title: String!
  slug: String!
  content: String
  excerpt: String
  featuredImage: Image
  author: User
  categories: [Category!]!
  tags: [String!]!
  publishedAt: DateTime
  status: BlogPostStatus!
  featured: Boolean!
  createdAt: DateTime!
  updatedAt: DateTime!
}

type Query {
  blogPost(id: ID!): BlogPost
  blogPosts(
    limit: Int = 20
    offset: Int = 0
    sort: String
    filter: BlogPostFilter
  ): BlogPostConnection!
}

type Mutation {
  createBlogPost(data: CreateBlogPostInput!): BlogPost!
  updateBlogPost(id: ID!, data: UpdateBlogPostInput!): BlogPost!
  deleteBlogPost(id: ID!): Boolean!
}

Query Examples

Fetch Blog Posts

query GetBlogPosts {
  blogPosts(limit: 10, sort: "-publishedAt") {
    nodes {
      id
      title
      slug
      excerpt
      publishedAt
      author {
        id
        name
        avatar
      }
      categories {
        id
        name
        slug
      }
    }
    pageInfo {
      hasNextPage
      hasPreviousPage
      total
    }
  }
}

Fetch Single Post

query GetBlogPost($id: ID!) {
  blogPost(id: $id) {
    id
    title
    content
    publishedAt
    author {
      name
      bio
      socialLinks
    }
    categories {
      name
      description
    }
    tags
  }
}

Create New Post

mutation CreateBlogPost($data: CreateBlogPostInput!) {
  createBlogPost(data: $data) {
    id
    title
    slug
    status
    createdAt
  }
}

Variables:

{
  "data": {
    "title": "My New Blog Post",
    "content": "<p>This is the content</p>",
    "status": "PUBLISHED",
    "authorId": "1"
  }
}

Using with Different Clients

// Using fetch
const response = await fetch('https://your-domain.com/graphql', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer your-api-key',
  },
  body: JSON.stringify({
    query: `
      query GetBlogPosts {
        blogPosts(limit: 5) {
          nodes {
            id
            title
            publishedAt
          }
        }
      }
    `,
  }),
});

const data = await response.json();
console.log(data.data.blogPosts.nodes);
import requests

url = 'https://your-domain.com/graphql'
headers = {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer your-api-key',
}

query = """
query GetBlogPosts {
    blogPosts(limit: 5) {
        nodes {
            id
            title
            publishedAt
        }
    }
}
"""

response = requests.post(url, json={'query': query}, headers=headers)
data = response.json()
print(data['data']['blogPosts']['nodes'])
<?php
$url = 'https://your-domain.com/graphql';
$headers = [
    'Content-Type: application/json',
    'Authorization: Bearer your-api-key',
];

$query = '
query GetBlogPosts {
    blogPosts(limit: 5) {
        nodes {
            id
            title
            publishedAt
        }
    }
}
';

$data = json_encode(['query' => $query]);

$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);

$response = curl_exec($curl);
curl_close($curl);

$result = json_decode($response, true);
print_r($result['data']['blogPosts']['nodes']);
?>
package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "net/http"
)

type GraphQLRequest struct {
    Query string `json:"query"`
}

func main() {
    url := "https://your-domain.com/graphql"

    query := `
    query GetBlogPosts {
        blogPosts(limit: 5) {
            nodes {
                id
                title
                publishedAt
            }
        }
    }
    `

    reqBody := GraphQLRequest{Query: query}
    jsonData, _ := json.Marshal(reqBody)

    req, _ := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
    req.Header.Set("Content-Type", "application/json")
    req.Header.Set("Authorization", "Bearer your-api-key")

    client := &http.Client{}
    resp, _ := client.Do(req)
    defer resp.Body.Close()

    var result map[string]interface{}
    json.NewDecoder(resp.Body).Decode(&result)

    fmt.Println(result["data"])
}

File Upload API

Handle file uploads for images, documents, and other media.

Upload Single File

POST /api/upload

Form Data:

  • file: The file to upload
  • alt: Alternative text (for images)
  • folder: Folder path (optional)

Example:

curl -X POST "https://your-domain.com/api/upload" \
  -H "Authorization: Bearer your-api-key" \
  -F "file=@image.jpg" \
  -F "alt=Sample image" \
  -F "folder=blog/images"

Response:

{
  "data": {
    "id": "file_123",
    "filename": "image.jpg",
    "originalName": "my-image.jpg",
    "mimeType": "image/jpeg",
    "size": 2048576,
    "url": "https://your-domain.com/uploads/image.jpg",
    "alt": "Sample image",
    "folder": "blog/images",
    "createdAt": "2024-01-15T10:00:00Z"
  }
}

Upload Multiple Files

POST /api/upload/multiple

Example:

curl -X POST "https://your-domain.com/api/upload/multiple" \
  -H "Authorization: Bearer your-api-key" \
  -F "files[]=@image1.jpg" \
  -F "files[]=@image2.jpg"

Webhooks

Set up webhooks to receive real-time notifications when content changes.

Configuration

scalar.config.ts
export default defineConfig({
  webhooks: {
    enabled: true,
    endpoints: [
      {
        url: 'https://your-app.com/webhook',
        events: ['create', 'update', 'delete'],
        models: ['blogPost', 'page'],
        secret: 'webhook-secret',
      },
    ],
  },
});

Webhook Payload

{
  "event": "create",
  "model": "blogPost",
  "data": {
    "id": "1",
    "title": "New Blog Post",
    "status": "published",
    "createdAt": "2024-01-15T10:00:00Z"
  },
  "timestamp": "2024-01-15T10:00:00Z",
  "signature": "sha256=..."
}

Verifying Webhooks

const crypto = require('crypto');

function verifyWebhook(payload, signature, secret) {
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');

  return `sha256=${expectedSignature}` === signature;
}

Rate Limiting

API requests are rate-limited to prevent abuse:

  • Default: 100 requests per 15 minutes per IP
  • Authenticated: 1000 requests per 15 minutes per API key

Rate limit headers are included in responses:

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1642252800

Error Handling

Error Response Format

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Validation failed",
    "details": [
      {
        "field": "title",
        "message": "Title is required"
      }
    ]
  }
}

Common Error Codes

CodeStatusDescription
UNAUTHORIZED401Invalid or missing authentication
FORBIDDEN403Insufficient permissions
NOT_FOUND404Resource not found
VALIDATION_ERROR422Request validation failed
RATE_LIMITED429Too many requests
INTERNAL_ERROR500Server error

For more advanced use cases, check out our SDK documentation and examples.