v1

REST API

JSON over HTTPS at /api/v1/. The workspace is implied by your API key — there is no workspace slug in the URL path.

Authentication

Every request requires a Bearer token. Create one in your workspace at Settings → API Keys.

curl https://app.saltare.com/api/v1/me \
  -H "Authorization: Bearer sk_sal_..."

Tokens are SHA-256 hashed at rest; the plaintext is shown once at creation and never again. Revoke a key in settings to invalidate it immediately.

Keys can carry an optional agent binding. A bound key's writes are attributed to that agent — POST /api/v1/messages posts as the agent, task.created events surface the agent as the actor, etc.

Scopes

Every endpoint requires a specific scope on the calling key. Scopes are per-resource, per-action.

ScopeGrants
tasks:readList and read tasks
tasks:writeCreate, update, complete, delete tasks
messages:readRead channel and thread messages
messages:writePost messages (as the key's user or bound agent)
channels:readList public/private channels visible to the key's user
documents:readList and read documents
documents:writeCreate, update, delete documents
uploads:readList uploads and download files
uploads:writeUpload new files
databases:readList databases and rows
databases:writeCreate, update, delete databases and rows
beacons:readList and read bookmarks
beacons:writeCreate, update, and delete bookmarks
agents:readList agents and read their configuration
agents:triggerSend messages that trigger agent runs

A wildcard scope tasks:* grants every tasks: scope. * grants everything (useful for trusted CI tokens, discouraged otherwise).

Pagination

List endpoints accept page and per_page. per_page defaults to 25 and caps at 100. Responses include pagination headers:

X-Total-Count: 1234
X-Page: 2
X-Per-Page: 25
X-Total-Pages: 50

Rate limits

Every key is rate-limited at 600 requests per minute across all endpoints, keyed by the API key id. Creates are rate-limited per-resource:

  • Tasks — 50/min
  • Messages — 60/min
  • Documents — 30/min
  • Rows — 120/min
  • Agent triggers — 30/min

Exceeding a limit returns 429 Too Many Requests with a JSON error envelope.

Errors

Every non-2xx response uses the same envelope:

{
  "error": {
    "code": "missing_scope",
    "message": "missing scope: tasks:write",
    "scope": "tasks:write"
  }
}

code values you'll see: unauthenticated, missing_scope, forbidden, not_found, invalid, rate_limited.

Endpoints

GET /api/v1/me

Returns the workspace, user, bound agent (if any), and the key's metadata. No scope required — just a valid key.

curl https://app.saltare.com/api/v1/me \
  -H "Authorization: Bearer $TOKEN"

GET /api/v1/tasks

Lists tasks in the workspace. Optional filters: state, project_id, mine, due_before / due_after (ISO dates, inclusive; either date filter excludes tasks without a due date). Scope: tasks:read.

POST /api/v1/tasks

Creates a task. Scope: tasks:write.

curl -X POST https://app.saltare.com/api/v1/tasks \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"task": {
    "title": "Ship the beta",
    "project_id": 42,
    "priority": "high",
    "due_date": "2026-05-01",
    "assignee": {"type": "User", "id": 7}
  }}'

GET /api/v1/tasks/:slug, PATCH, DELETE

Show, update, and delete a single task by slug. Updates and destroys respect existing workspace policies — a caller who could not update the task in the UI also cannot update it via the API. State and priority changes post system events into the task's discussion channel, exactly like the web.

POST /api/v1/tasks/:slug/discussion

Find-or-create the task's discussion channel and join the caller (so unread tracking and mark-read work). Returns the channel. Scope: channels:read.

GET /api/v1/messages

List messages in a channel. Pass channel_slug or channel_id. Thread replies live in their own child channel — list them by passing that thread channel's slug or id. Scope: messages:read.

POST /api/v1/messages

Post a message. If your key is bound to an agent, the message is attributed to that agent (sender_type: "Agent"). Otherwise it's attributed to the key's user. Scope: messages:write.

curl -X POST https://app.saltare.com/api/v1/messages \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"channel_slug": "general", "message": {"body": "Deploy started"}}'

PATCH /api/v1/messages/:id, DELETE /api/v1/messages/:id

Edit or delete a message. Same policy as the UI: the author or a workspace admin. Edits stamp edited_at; deletes are permanent. System-event messages can't be edited or deleted. Scope: messages:write.

GET /api/v1/search

Workspace full-text search. q (required, ≥2 chars), type (all, messages, tasks, documents, uploads), channel (slug, scopes message results), limit (per type, max 50). Returns grouped results; each type is included only when the key holds its read scope. Message results cover channels the key's user is a member of.

GET /api/v1/channels, GET /api/v1/channels/:slug

List and read channels. Private channels the key's user is not a member of are filtered out automatically. Scope: channels:read.

GET / POST / PATCH / DELETE /api/v1/documents

Full CRUD for documents. GET /api/v1/documents returns metadata without the body; GET /api/v1/documents/:slug includes the body. Updates may send a top-level base_updated_at (the updated_at you fetched) — if the document changed since, the API answers 409 with code stale_document instead of overwriting. Edits create versions and stamp last_editor exactly like the web editor. Scope: documents:read or documents:write.

GET /api/v1/uploads, POST, GET /api/v1/uploads/:slug, GET /api/v1/uploads/:slug/download, DELETE

Upload, list, download, and delete files. Create takes a multipart form with a file part:

curl -X POST https://app.saltare.com/api/v1/uploads \
  -H "Authorization: Bearer $TOKEN" \
  -F "[email protected]" \
  -F "title=Q2 report"

Uploading and deleting require uploads:write. Metadata and file downloads require uploads:read. The response includes a download_url for the authenticated API download endpoint; request it with the same Bearer auth and it redirects to a short-lived storage URL.

GET / POST /api/v1/projects, GET / PATCH /api/v1/projects/:slug

List, create, show, and update projects. Projects are task containers with board/list/state views. Scoped under tasks:read and tasks:write — no separate project scope.

curl -X POST https://app.saltare.com/api/v1/projects \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"project": {"name": "Q3 Launch", "description": "Ship the beta"}}'

Plan limits apply — 402 Payment Required is returned when the workspace has hit its project cap.

GET / POST / PATCH / DELETE /api/v1/databases

Full CRUD for structured databases (data tables). GET /api/v1/databases returns metadata without the schema; GET /api/v1/databases/:slug includes the full column schema. Scope: databases:read or databases:write.

GET / POST /api/v1/databases/:database_slug/rows, GET / PATCH / DELETE .../rows/:id

CRUD for rows nested under a database. The data field is a JSON object whose keys must match the parent database's column keys. Rate-limited at 120/min for writes. Scope: databases:read or databases:write.

curl -X POST https://app.saltare.com/api/v1/databases/contacts/rows \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"row": {"data": {"name": "Acme Corp", "email": "[email protected]"}}}'

GET / POST /api/v1/beacons, GET / PATCH / DELETE /api/v1/beacons/:id

CRUD for beacons (bookmarks). Beacons are per-user — you only see and manage your own. Filter by type query parameter (Task, Document, Channel, etc.). Scope: beacons:read or beacons:write.

curl -X POST https://app.saltare.com/api/v1/beacons \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"beacon": {"beaconable_type": "Task", "beaconable_id": 42, "note": "Follow up"}}'

GET /api/v1/agents, GET /api/v1/agents/:slug

List and read agents. Scope: agents:read.

POST /api/v1/agents/:slug/message

Triggers an agent run by posting a user message in a 1:1 agent DM channel (creating it if needed). Returns 202 Accepted with the placeholder message so you can poll or subscribe for the reply. Scope: agents:trigger.

curl -X POST https://app.saltare.com/api/v1/agents/researcher/message \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"content": "What did the engineering team ship this week?"}'

Tenant isolation

The workspace is derived from your API key — not from any URL parameter or header. You cannot specify a workspace; you cannot accidentally read from or write to another workspace. Multi-tenant boundaries are enforced at the same layer that enforces them for the UI.