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 fetch download URLs
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. 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.

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"}}'

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. Scope: documents:read or documents:write.

GET /api/v1/uploads, POST, GET /api/v1/uploads/:slug, 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"

The response includes a download_url that's a time-limited signed URL. Scope: uploads:read or uploads:write.

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.