Quickstart
This walks through the whole loop with curl: create a site, define a page type, write a page, publish it and read it back. In practice editors do the middle part in the Avvyr app; you set up the site and the types and build the frontend.
You need an Avvyr API key with product:write (or an unrestricted key) on a tenant with the CMS add-on.
1. Create a site
curl -X POST https://api.avvyr.com/v1/cms/sites \
-H "x-api-key: $AVVYR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Main site",
"locales": [{ "code": "sv", "label": "Svenska", "default": true }, { "code": "en", "label": "English" }],
"previewUrl": "https://www.example.com",
"siteUrl": "https://www.example.com"
}'
{
"site": { "id": "6701f0c2a4d3e5f6a7b8c9d0", "name": "Main site", "defaultLocale": "sv", "version": 0, "...": "..." },
"keys": { "public": "cms_pub_…", "preview": "cms_prv_…" }
}
POST /v1/cms/sites/{siteId}/keys/{public|preview}/rotate.2. Define a page type
Types are best kept in code, next to the components that render them. Install @avvyr/cms and create cms.config.ts:
import { defineType, f } from '@avvyr/cms'
export const hero = defineType({
key: 'hero',
kind: 'block',
fields: {
title: f.text({ localized: true, required: true }),
image: f.media({ accept: ['image'] }),
},
})
export const page = defineType({
key: 'page',
kind: 'entry',
routable: true,
titleField: 'title',
fields: {
title: f.text({ localized: true, required: true }),
body: f.blocks([hero]),
seo: f.object({ title: f.text(), description: f.longText() }, { localized: true }),
},
})
Push the types to the site:
AVVYR_API_KEY=... AVVYR_CMS_SITE_ID=6701f0c2a4d3e5f6a7b8c9d0 npx avvyr-cms push
Pushed types are locked in the editor, so code stays the source of truth. You can also create types without code (POST /v1/cms/sites/{siteId}/types, or in the app). See Types and fields.
3. Create and publish a page
SITE=6701f0c2a4d3e5f6a7b8c9d0
curl -X POST https://api.avvyr.com/v1/cms/sites/$SITE/entries \
-H "x-api-key: $AVVYR_API_KEY" -H "Content-Type: application/json" \
-d '{
"typeKey": "page",
"title": "About us",
"slug": { "sv": "om-oss", "en": "about-us" },
"fields": {
"title": { "sv": "Om oss", "en": "About us" },
"body": [
{ "id": "h1x9k2", "type": "hero", "fields": { "title": { "sv": "Vi gör möbler", "en": "We make furniture" } } }
]
}
}'
The entry is created as a draft. A routable type needs a slug in the default language before it can be published. Block ids are strings you choose, unique within the entry.
curl -X POST https://api.avvyr.com/v1/cms/sites/$SITE/entries/{entryId}/publish \
-H "x-api-key: $AVVYR_API_KEY"
{ "entry": { "id": "…", "status": "published", "...": "..." }, "version": 1 }
4. Read it from the delivery API
The delivery API uses the site's public key. Ask for the current version, then for the page under that version:
curl https://api.avvyr.com/v1/content/$SITE/version -H "x-cms-key: cms_pub_…"
# { "version": 1, "locales": ["sv", "en"], "defaultLocale": "sv" }
curl https://api.avvyr.com/v1/content/$SITE/v/1/sv/pages/om-oss -H "x-cms-key: cms_pub_…"
{
"id": "…",
"type": "page",
"title": "About us",
"path": "/om-oss",
"locale": "sv",
"alternates": { "en": "/about-us" },
"fields": {
"title": "Om oss",
"body": [{ "id": "h1x9k2", "type": "hero", "fields": { "title": "Vi gör möbler", "image": null } }],
"seo": null
},
"...": "..."
}
Localized fields come back as the value for the requested language, and empty fields are null. The response is immutable for version 1, so it can be cached for a year.
The same page with the JavaScript client:
import { createCmsClient } from '@avvyr/cms'
const cms = createCmsClient({ siteId: '6701f0c2a4d3e5f6a7b8c9d0', key: process.env.CMS_KEY! })
const page = await cms.page('/om-oss', { locale: 'sv' })
Next steps
- Keys and access
- Entries and publishing: folders, paths, scheduling, versions
- Nuxt layer: render the blocks on a Nuxt site