The product API with curl
These requests call the Synapse and Locus API directly. Availability depends on the
services enabled for your environment. The examples use curl and jq, and keep your
key and the IDs they find in shell variables, so each block can be pasted as it
stands, in order.
Before you start
Section titled “Before you start”Create a Test key on the platform’s API Keys page with these scopes:
org:readto find the organization and its environmentorg:service_accounts:writeto create a service accountlocus:grants:readandlocus:grants:writeto register and list agent identities
Put the key in KATARA_API_KEY from wherever you keep secrets, rather than typing it
into your shell history, and set the API origin:
export KATARA_API=https://api.katara.comFind the organization and environment
Section titled “Find the organization and environment”A key belongs to one organization and one environment, so the organization list holds a single entry:
curl -sS "$KATARA_API/katara/v1/orgs" \ -H "Authorization: Bearer $KATARA_API_KEY"{ "data": [ { "id": "0c73f1f4-5356-4253-b75c-9e5afe17f413", "name": "Northwind Labs", "role": "owner", "version": 2, "permissions": ["locus:grants:read", "locus:grants:write", "org:read", "org:service_accounts:write"] } ]}permissions is what this key may do there: its scopes, bounded by your current role.
Keep the organization ID and the ID of its Test environment:
export KATARA_ORG=$(curl -sS "$KATARA_API/katara/v1/orgs" \ -H "Authorization: Bearer $KATARA_API_KEY" | jq -r '.data[0].id')export KATARA_ENV=$(curl -sS "$KATARA_API/katara/v1/orgs/$KATARA_ORG/environments" \ -H "Authorization: Bearer $KATARA_API_KEY" | jq -r '.data[] | select(.mode == "test") | .id')Read the environment:
curl -sS "$KATARA_API/katara/v1/orgs/$KATARA_ORG/environments/$KATARA_ENV" \ -H "Authorization: Bearer $KATARA_API_KEY"{ "org_id": "0c73f1f4-5356-4253-b75c-9e5afe17f413", "environment_id": "1dde350c-d18c-47ad-b665-ef1266c29419", "id": "1dde350c-d18c-47ad-b665-ef1266c29419", "mode": "test", "authority_epoch": 1, "reset_generation": 1, "state": "active"}authority_epoch and reset_generation identify the state you read. A change that
depends on them sends both back, and the server refuses it if either has moved on.
Create a service account
Section titled “Create a service account”Every change carries an Idempotency-Key of 8–128 letters, digits, dots, underscores,
colons or hyphens. Use a new key for each new change, and the same key only to retry
the same request.
export KATARA_SERVICE_ACCOUNT=$(curl -sS -X POST "$KATARA_API/katara/v1/orgs/$KATARA_ORG/service-accounts" \ -H "Authorization: Bearer $KATARA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: curl-guide-service-account-1" \ -d '{"name": "Document assistant"}' | jq -r '.resource_id')A change answers with an operation receipt, and resource_id is the ID of what it
created:
{ "operation_id": "848660a3-c0f0-420c-b88d-da4e85711e7d", "resource_id": "050b433c-9ad0-40b5-a95b-e34d07ab7105", "completed": true}Register an agent identity
Section titled “Register an agent identity”An agent identity binds the service account to this environment. Build the request from the environment you read a moment ago, so its pins are current, and keep it in a file:
curl -sS "$KATARA_API/katara/v1/orgs/$KATARA_ORG/environments/$KATARA_ENV" \ -H "Authorization: Bearer $KATARA_API_KEY" | jq '{expected_environment_epoch: .authority_epoch, expected_reset_generation: .reset_generation, name: "Document assistant", service_account_id: env.KATARA_SERVICE_ACCOUNT, execution_kind: "managed"}' > identity.jsonSend it:
curl -sS -X POST "$KATARA_API/katara/v1/orgs/$KATARA_ORG/environments/$KATARA_ENV/agent-identities" \ -H "Authorization: Bearer $KATARA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: curl-guide-agent-identity-1" \ -d @identity.json{ "operation_id": "7887df99-f4bc-410c-b18e-b8e624565c25", "resource_id": "a681edf0-757c-4a3e-994f-2e2dbf643120", "completed": true}List the environment’s identities:
curl -sS "$KATARA_API/katara/v1/orgs/$KATARA_ORG/environments/$KATARA_ENV/agent-identities" \ -H "Authorization: Bearer $KATARA_API_KEY"{ "data": [ { "org_id": "0c73f1f4-5356-4253-b75c-9e5afe17f413", "environment_id": "1dde350c-d18c-47ad-b665-ef1266c29419", "id": "a681edf0-757c-4a3e-994f-2e2dbf643120", "service_account_id": "050b433c-9ad0-40b5-a95b-e34d07ab7105", "name": "Document assistant", "execution_kind": "managed", "active": true, "authority_epoch": 1, "created_by": "7c8dfbbc-ac06-494d-a3f5-4698009b63a4", "created_at": "2026-09-30T21:29:58.132458Z" } ]}An identity alone publishes no definition, starts no worker and grants no spending authority. Agent identities and Runs continue from here.
Retry a change safely
Section titled “Retry a change safely”When a reply is lost, send the identical request with the same key. The server answers with the original receipt and changes nothing:
curl -sS -X POST "$KATARA_API/katara/v1/orgs/$KATARA_ORG/environments/$KATARA_ENV/agent-identities" \ -H "Authorization: Bearer $KATARA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: curl-guide-agent-identity-1" \ -d @identity.jsonThe operation_id and resource_id are the ones the first reply carried.
Read errors
Section titled “Read errors”Every error has the same shape. error.code says what happened, error.request_id
identifies the request if you contact support, and error.action, when present, says
what to do next.
A used key sent with a different body is refused, however small the difference:
jq '.name = "Research assistant"' identity.json | curl -sS -X POST "$KATARA_API/katara/v1/orgs/$KATARA_ORG/environments/$KATARA_ENV/agent-identities" \ -H "Authorization: Bearer $KATARA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: curl-guide-agent-identity-1" \ -d @-{ "error": { "message": "The resource changed or the idempotency key was used for another request.", "type": "conflict_error", "code": "conflict", "param": null, "request_id": "hc_8b80b327c41049a999396e52", "upstream_status": null, "action": "refresh" }}refresh means read the current state and review the change again before sending it
under a new key. A stale expected_environment_epoch answers the same way.
A request without a key, or with a revoked or expired one, answers 401. In a script,
--fail-with-body makes curl exit with status 22 on an error while still printing the
body:
curl -sS --fail-with-body "$KATARA_API/katara/v1/orgs/$KATARA_ORG/environments"{ "error": { "message": "missing or invalid session", "type": "authentication_error", "code": "unauthorized", "param": null, "request_id": "hc_51d8cf6de148b50220144963", "upstream_status": null }}Access and API keys lists the scopes for other tasks, and the product API reference describes every route these requests use.