POST /v1/posts
Create a new post on the site that owns the API key.
- URL:
https://api.canverly.com/v1/posts - Method:
POST - Auth: Bearer API key
- Required scope:
posts:write - Content-Type:
application/json - Rate limit: 60 req/min per key (default)
Request
Headers
| Header | Required | Description |
|---|---|---|
Authorization | yes | Bearer ck_live_... |
Content-Type | yes | application/json |
Idempotency-Key | no | Arbitrary string ≤ 128 chars. Replaying the same key within 24h returns the original response without re-creating the post. |
Body
{ "post_type": "post", "status": "published", "title": "Hello from the API", "slug": "hello-from-the-api", "excerpt": "A short summary that shows up in lists.", "content_html": "<p>Full HTML body.</p>", "cover_image_url": "https://cdn.example.com/cover.jpg", "tags": ["news", "api"], "published_at": "2026-06-07T18:00:00Z", "meta": { "seo_title": "Hello from the API", "seo_description": "How to call the Canverly API." }}Fields
| Field | Type | Required | Notes |
|---|---|---|---|
post_type | string | yes | Slug from GET /v1/post-types. Defaults to post. |
status | enum | yes | draft | published | scheduled. |
title | string | yes | ≤ 240 chars. |
slug | string | no | Auto-derived from title if omitted. |
excerpt | string | no | ≤ 500 chars, plain text. |
content_html | string | conditional | Required when status is published or scheduled. Sanitized server-side. |
cover_image_url | string (URL) | no | HTTPS only. Canverly mirrors the asset. |
tags | string[] | no | Auto-created if missing. |
published_at | string (ISO 8601) | conditional | Required when status is scheduled, must be in the future. |
meta | object | no | Free-form SEO/extension fields. |
Response
201 Created
{ "id": "01HZX9K3F7T8M2QYV5N6B4WJ8R", "site_id": "01HZX0000000000000000000SITE", "post_type": "post", "status": "published", "title": "Hello from the API", "slug": "hello-from-the-api", "excerpt": "A short summary that shows up in lists.", "cover_image_url": "https://cdn.canverly.com/sites/abc/cover.jpg", "tags": ["news", "api"], "published_at": "2026-06-07T18:00:00Z", "created_at": "2026-06-07T18:00:00Z", "updated_at": "2026-06-07T18:00:00Z", "url": "https://example-site.canverly.com/hello-from-the-api"}id is a ULID. url is the canonical public URL — use it for verification or to back-link from the source system.
Example: curl
curl -X POST https://api.canverly.com/v1/posts \ -H "Authorization: Bearer ck_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: chimpee-item-42" \ -d '{ "post_type": "post", "status": "published", "title": "Hello from the API", "content_html": "<p>Hi.</p>", "tags": ["news"] }'Errors
All errors are returned as application/problem+json (RFC 7807).
| Status | type slug | When |
|---|---|---|
400 | bad-request | Malformed JSON. |
401 | unauthenticated | Missing/invalid Authorization. |
403 | insufficient-scope | Key lacks posts:write. |
404 | post-type-not-found | post_type slug does not exist on this site. |
409 | slug-conflict | A post with the given slug already exists for this post_type. |
422 | validation-failed | Field-level validation errors — see errors[] in body. |
429 | rate-limited | Exceeded 60 req/min. Honour Retry-After. |
500 | internal | Server error — safe to retry with Idempotency-Key. |
422 example
{ "type": "https://docs.canverly.com/errors/validation-failed", "title": "Validation failed", "status": 422, "errors": [ { "field": "title", "code": "too_long", "message": "title must be ≤ 240 chars" }, { "field": "content_html", "code": "required", "message": "content_html is required when status is published" } ]}Idempotency
Sending the same Idempotency-Key twice within 24 hours returns the original 201 response and does not create a duplicate post. After 24 hours the key is forgotten and a replay creates a new post. The server stores (api_key_id, idempotency_key) → response — keys are scoped per API key, so two different consumers using the same string do not collide.
If you replay a key with a different body, the server returns 409 Conflict with type: idempotency-mismatch rather than silently overwriting.