Conventions
These hold across every module; a module page calls out exceptions.
Base URL and versioning
https://api.naturali.ai/v1. The API is versioned in the path; within a
version, changes are additive — new fields, new routes, new enum values — so a
client should ignore what it does not recognise.
Resources that belong to a project live under
/v1/projects/{project_id}/…. Account-level ones — your user, API keys,
billing — live directly under /v1.
Fields and IDs
Wire casing is snake_case (project_id, created_at). Timestamps are ISO 8601
date-times.
Public IDs are prefixed per resource — proj_, agent_, tool_, ses_, … —
so an ID says what it names. Treat the rest as opaque.
Pagination
Every list endpoint is paginated, in one of two styles. Each operation's page in the API Reference says which.
| Style | Request | Response | End of the list |
|---|---|---|---|
| Offset — most project resources | limit, offset | { data, total, limit, offset } | offset + data.length reaches total |
| Cursor — account resources, channels, webhooks, the marketplace, activity | limit, cursor | { data, next_cursor } | next_cursor is null |
A cursor is opaque: pass back the next_cursor you were given, never one you
built.
- CLI
- SDK
- curl
naturali list-agents \
--project-id proj_V1StGXR8Z5jdHi6B \
--limit 50 \
--offset 50
const { data: page } = await naturali.agents.listAgents({
path: { project_id: 'proj_V1StGXR8Z5jdHi6B' },
query: { limit: 50, offset: 50 },
});
// page.total is the full count; stop once offset + page.data.length reaches it.
curl "https://api.naturali.ai/v1/projects/proj_V1StGXR8Z5jdHi6B/agents?limit=50&offset=50" \
-H "Authorization: Bearer $NATURALI_TOKEN"
Idempotency
Where a duplicate would be expensive, a mutating POST takes an
idempotency_key in its body: the first request under a key does the work, a
repeat returns that same result. It is on
creating a project, connecting a
channel and starting an orchestration
run, not on every POST — a module page says so
where it applies.