This page covers what the three share. Each language’s page has its install, configuration and what is particular to it.
Need an SDK in another language? Let us know which one. Meanwhile, anything can call the API over HTTP, or generate a client from
/openapi.json.
Install and connect
NOHEAD_API_KEY from the environment when you don’t pass one; none reads the key that nohead login saves for the CLI, so give your app a key of its own. Writing content, drafts, uploads and webhooks needs a key with Published records only off and the write scopes (records:write, assets:write, …); new keys are published-only by default. API keys belong to a project, so you never pass a project ID, and you can name collections by slug.
Read content
List methods give you a page, which you can also loop over: the SDK fetches the following pages as you go. Filters are the API’s: a value to equal, or operators such as{ exists: true }.
nohead.records.get<Post>(id) makes post.data.title a string. In Python, results are Pydantic models, and a record’s values are in its data dict. In Ruby, read fields as methods or with [] (post.data["title"]); timestamps are Times.
Write content
changeNote (TypeScript) or change_note (Python, Ruby).
Review changes before they go live
A draft holds changes to a record until they are merged into it, now or at a set time, while the record keeps its current content. A record has one draft at most, and a merge changes content only: a live record stays live. Agents can propose changes this way for a person to review and merge.Retries and idempotency
Failed requests are retried twice by default:- connection errors and timeouts
429 rate_limited, waiting forRetry-Afterwhen it is at most a minute (a longer wait raisesRateLimitErrorat once)500,502,503and504409with detail codein_progress: the same write, still running
timeout and maxRetries (max_retries) on the client to change them. TypeScript also takes them for one call, and in Python with_options returns a client with other settings. Errors and retries covers what each error means.
Every write carries an Idempotency-Key that stays the same across its retries, so a retry after a lost response never writes twice.
To make a write safe across retries of your own (a job that may run twice), pass the key yourself:
Concurrent edits
Pass the record you read asifMatch (if_match). If someone changed it since, the API refuses the write with 412 and nothing is written.
Errors
Each errortype of the API is an error class: ValidationError (422), NotFoundError (404), RateLimitError (429) and so on, all subclasses of APIError. They carry the status, type, message, the request ID and the field-level details.
Upload files
Files go straight to storage, never through the API.assets.upload does the whole flow in one call: it creates the upload, sends the bytes to storage, completes the upload (which checks the file) and returns the ready asset. Reference the asset from an asset field.
Verify webhooks
Webhook requests are signed.unwrap (unwrapWebhook in TypeScript) checks the signature and the timestamp and returns the event; it raises (or throws) when either is off. Pass the raw body, before any JSON parsing.
webhook-id header. Webhooks covers the event types, the payload and rotating the secret.
Versions
The SDKs have their own version numbers, separate from the API’s/v1. Until 1.0, the SDKs, the CLI and the API may change in ways that break your code: pin a version, and read each release’s Breaking entries before upgrading. From 1.0, the API only adds within /v1, so an older SDK keeps working; anything an SDK depends on is deprecated at least 12 months before it is removed, and the SDKs warn when they call something deprecated. Each SDK’s releases on GitHub (linked from the table at the top) list what changed in each version.