Skip to content

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.

Create a Test key on the platform’s API Keys page with these scopes:

  • org:read to find the organization and its environment
  • org:service_accounts:write to create a service account
  • locus:grants:read and locus:grants:write to 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:

Terminal window
export KATARA_API=https://api.katara.com

A key belongs to one organization and one environment, so the organization list holds a single entry:

Terminal window
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:

Terminal window
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:

Terminal window
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.

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.

Terminal window
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
}

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:

Terminal window
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.json

Send it:

Terminal window
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:

Terminal window
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.

When a reply is lost, send the identical request with the same key. The server answers with the original receipt and changes nothing:

Terminal window
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

The operation_id and resource_id are the ones the first reply carried.

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:

Terminal window
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:

Terminal window
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.