Content API reference
Read-only endpoints for one blog. JSON in, JSON out, GET only.
Basics
- Base address:
https://drive.example.com/api/content/v1/blogs/:blog, where:blogis the blog's address name (likeweeknight-kitchen) or its id. - The key goes in
?key=or a headerAuthorization: Key stc_.... - All URLs in answers are absolute and use the blog's custom domain once it has one.
- HTML is rendered and cleaned by the server: no scripts, no inline handlers, only safe links. Cards keep stable classes (
card card-recipe,card-product,card-gallery,card-embed,card-bookmark,card-button,callout callout-tip) so you can style them.
curl "https://drive.example.com/api/content/v1/blogs/weeknight-kitchen/posts?key=stc_YOUR_CONTENT_KEY&limit=5"Posts
GET /posts
Published posts, newest first.
| Parameter | What it does |
|---|---|
page | Page number, from 1. |
limit | Posts per page, 1 to 100 (default 15). all means 100. |
tag | Only posts with this tag (its slug, like pasta). |
author | Only posts by this username. |
featured | true or false. |
order | published_at desc (default), published_at asc, title asc, updated_at desc. |
fields | Only these fields, like id,title,slug,url. |
include | tags,authors adds the full lists. |
formats | html (default), markdown, or both. |
{
"posts": [
{
"id": "bp_8fQ2kL1mNp0x",
"slug": "lemon-pasta",
"type": "post",
"title": "Lemon pasta",
"url": "https://food.example.com/lemon-pasta",
"html": "<p>Weeknight lemon pasta.</p><section class=\"card card-recipe\" id=\"recipe\">...</section>",
"excerpt": "Weeknight lemon pasta.",
"custom_excerpt": null,
"featured": false,
"visibility": "public",
"published_at": "2026-10-08T12:00:00.000Z",
"updated_at": "2026-10-08T12:30:00.000Z",
"reading_time": 3,
"word_count": 512,
"toc": [{ "level": 2, "id": "the-sauce", "text": "The sauce" }],
"feature_image": "https://food.example.com/media/Xq7pL2a/k3Jd9sLqP0aZbYcX/pasta.jpg",
"feature_image_alt": "A bowl of pasta",
"feature_image_width": 2400,
"feature_image_height": 1600,
"feature_image_sizes": { "thumbnail": "...?size=thumb", "full": "..." },
"feature_image_srcset": "...?size=thumb 400w, ... 2400w",
"meta_title": null,
"meta_description": null,
"og_image": null,
"canonical_url": null,
"json_ld": [{ "@context": "https://schema.org", "@type": "BlogPosting", "headline": "Lemon pasta" }],
"rating": { "average": 4.8, "count": 126, "reviews": 31 },
"made": 340,
"recipe": { "total_label": "22 min", "total_min": 22, "course": ["main"], "cuisine": ["italian"], "diet": ["vegetarian"], "difficulty": "easy" },
"primary_tag": { "slug": "pasta", "name": "Pasta" },
"primary_author": { "username": "sam", "name": "Sam Rivera" },
"tags": [{ "slug": "pasta", "name": "Pasta", "url": "https://food.example.com/tag/pasta" }]
}
],
"meta": { "pagination": { "page": 1, "limit": 15, "pages": 4, "total": 52, "next": 2, "prev": null } }
}Recipe posts also carry rating (readers' average stars, how many ratings and written reviews), made (how many readers said they made it) and recipe (time, course, cuisine, diet, difficulty). Their json_ld includes a Recipe with aggregateRating, so search results can show the stars. Other posts have rating: null.
GET /posts/:slugOrId
One post, with its tags and authors. Answers { "posts": [post] }, or 404 when there's no such published post. Unlisted posts only answer to their slug.
curl -H "Authorization: Key stc_YOUR_CONTENT_KEY" "https://drive.example.com/api/content/v1/blogs/weeknight-kitchen/posts/lemon-pasta"Pages
GET /pages and GET /pages/:slugOrId work like posts, for pages such as About or Contact. Answers use pages instead of posts.
Tags
GET /tags lists tags that have published posts, most used first. GET /tags/:slug answers one.
{ "tags": [{ "slug": "pasta", "name": "Pasta", "description": "", "url": "https://food.example.com/tag/pasta", "count": { "posts": 12 } }] }Authors
GET /authors lists the people shown on the blog's team page. GET /authors/:username answers one.
{ "authors": [{ "username": "sam", "name": "Sam Rivera", "bio": "Cooks on weeknights.", "profile_image": "https://drive.example.com/u/sam/avatar.png", "url": "https://food.example.com/author/sam", "links": [{ "label": "Website", "url": "https://example.com" }], "role": "owner", "count": { "posts": 40 } }] }Settings
GET /settings answers the blog's name, look and navigation, to build your header and footer.
{ "settings": { "title": "Weeknight Kitchen", "description": "Recipes that fit a Tuesday.", "url": "https://food.example.com", "lang": "en", "accent_color": "#E4572E", "logo": null, "icon": null, "cover_image": null, "meta_title": null, "meta_description": null, "navigation": [{ "label": "Home", "url": "https://food.example.com" }], "secondary_navigation": [], "rss_url": "https://food.example.com/rss.xml", "atom_url": "https://food.example.com/atom.xml", "posts_per_page": 10 } }Errors
{ "errors": [{ "type": "NotFoundError", "code": "not_found", "message": "Post not found." }] }| Status | Code | When |
|---|---|---|
| 401 | missing_key | No key sent. |
| 401 | invalid_key | Unknown, revoked, or a key for another blog. |
| 403 | origin_not_allowed | A browser on a site the key doesn't allow. |
| 404 | not_found | No such published item, or the blog is private. |
| 429 | rate_limited | Too many requests; wait for Retry-After seconds. |
CORS and caching
- GET and OPTIONS only. Preflights answer 204 and are cached for a day.
Access-Control-Allow-Originis*, or your site's origin when the key lists allowed sites.ETagon every answer; sendIf-None-Matchfor a 304.Cache-Control: public, max-age=60, stale-while-revalidate=300.