Skip to main content

REST API Reference

Base URL​

https://your-soat-server.com

For development, http://localhost:3000.

Authentication​

User Authentication (JWT Bearer Token)​

# 1. Bootstrap the first admin user
curl -X POST https://your-soat-server.com/api/v1/users/bootstrap \
-H "Content-Type: application/json" \
-d '{"username": "admin", "password": "supersecret"}'

# 2. Login to get a token
curl -X POST https://your-soat-server.com/api/v1/users/login \
-H "Content-Type: application/json" \
-d '{"username": "admin", "password": "supersecret"}'
# Response: {"token": "eyJhbGc..."}

# 3. Use the token in requests
curl https://your-soat-server.com/api/v1/users \
-H "Authorization: Bearer eyJhbGc..."

JWT tokens expire after 7 days.

Project Key Authentication​

# Create a project key (requires user authentication first)
curl -X POST https://your-soat-server.com/api/v1/project-keys \
-H "Authorization: Bearer <user-token>" \
-H "Content-Type: application/json" \
-d '{"projectPublicId": "proj_xyz", "policyIds": [1]}'
# Response: {"id": "sk_...", "secret": "sk_..."}

# Use the key in requests (set the full "ID" string as bearer token)
curl https://your-soat-server.com/api/v1/projects/proj_xyz/files \
-H "Authorization: Bearer sk_..."

Project keys inherit the associated policy's permissions.

Common Patterns​

Error Responses​

Errors return a 4xx or 5xx status with a structured body:

{
"error": {
"code": "RESOURCE_NOT_FOUND",
"message": "Project 'proj_abc123' not found.",
"hint": "Check the id, and check that the credential can see the project that owns the resource — a resource in another project is indistinguishable from one that does not exist. List the collection to confirm.",
"docs_url": "https://soat.ttoss.dev/docs/error-codes#resource_not_found",
"meta": { "id": "proj_abc123" }
}
}

code is stable and safe to branch on; message describes this occurrence; hint says what to do; docs_url points at that code's section on the Error Codes page; meta is optional, present only for some codes.

Every error response uses this shape (error is always an object with a code and a message), including:

SituationStatusBody
Missing or invalid credentials401code: "UNAUTHORIZED", message: "Unauthorized"
Insufficient permissions403code: "FORBIDDEN", message: "Forbidden"
Unparseable request body400code: "VALIDATION_FAILED", message: "Malformed request body: …"
Rejected by the HTTP layer before routingvariescode: "REQUEST_REJECTED"
Unhandled server failure500code: "INTERNAL_ERROR", message: "Internal Server Error"

Each also carries hint and docs_url.

The full catalog (every error.code, its HTTP status, meaning and remedy) is generated from the server source and published at /errors.json, as the x-error-codes extension of /openapi.json, and on the Error Codes page. INTERNAL_ERROR always carries exactly that message; the underlying exception is logged server-side, never forwarded.

Common status codes:

  • 200 — Success
  • 201 — Created
  • 400 — Bad Request (invalid parameters)
  • 401 — Unauthorized (missing or invalid token)
  • 403 — Forbidden (insufficient permissions)
  • 404 — Not Found
  • 409 — Conflict (e.g., duplicate resource)
  • 500 — Internal Server Error

Pagination​

Every GET list endpoint returns the same envelope and accepts limit/offset:

{
"data": [/* the page of resources */],
"total": 128, // total rows matching the query, across all pages
"limit": 50, // the effective page size applied
"offset": 0, // the offset this page started at
}
curl 'https://your-soat-server.com/api/v1/agents?project_id=proj_abc&limit=25&offset=0' \
-H "Authorization: Bearer <token>"
  • limit — results per page. Default 50, clamped to 100: a larger value is capped, and limit in the response reports the page served. The audit log defaults to 25 and caps at 200.
  • offset — results to skip (default 0).
  • A limit that is not an integer of at least 1, or an offset that is not a non-negative integer (abc, 1.5, 0, -1), is 400 VALIDATION_FAILED.
  • Items are in response.data, never the top-level body.
  • No cursor, page, or sort/order parameter. Sort order is fixed per endpoint (see the module doc), and rows that tie on it are broken by creation order, so a tie never reorders between pages.

The server enforces no per-project or per-API-key request-rate limits or throttling; every authenticated request is processed immediately, bounded only by the limits above and the 1 MiB inbound webhook body cap.

Path and Query Parameters​

# Path parameter: file ID in the URL
GET /api/v1/files/{id}
curl https://your-soat-server.com/api/v1/files/file_abc123

# Query parameters: appended to the URL
GET /api/v1/files?projectPublicId=proj_123&limit=10
curl 'https://your-soat-server.com/api/v1/files?projectPublicId=proj_123&limit=10'

Request Body​

POST and PUT bodies are JSON with Content-Type: application/json:

curl -X POST https://your-soat-server.com/api/v1/files \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"name": "report.pdf",
"projectPublicId": "proj_123"
}'

File uploads use multipart/form-data:

curl -X POST https://your-soat-server.com/api/v1/files/upload \
-H "Authorization: Bearer <token>" \
-F "file=@report.pdf" \
-F "projectPublicId=proj_123"

Modules​

ModuleDescription
UsersUser accounts, authentication, and bootstrap
ProjectsProjects, membership, and access control
API KeysAPI keys scoped to projects
SecretsEncrypted project secrets
FilesFile storage and retrieval
DocumentsDocument management and processing
ConversationsConversation sessions and state
ChatsReal-time messaging and AI interactions
AgentsAutonomous agents and tool execution
WebhooksEvent subscriptions and deliveries
AI ProvidersLLM provider configuration

TypeScript SDK​

@soat/sdk wraps the REST API with typing:

import { createSoatClient } from '@soat/sdk';

const soat = createSoatClient({
baseUrl: 'https://your-soat-server.com',
token: 'your-bearer-token',
});

const { data: page } = await soat.GET('/api/v1/files', {
params: { query: { projectPublicId: 'proj_123' } },
});
// List endpoints return the paginated envelope:
const files = page?.data;

OpenAPI Specification​

GET https://your-soat-server.com/openapi.yaml

OpenAPI 3.1; use it to generate clients in any language.