Skip to content

Pre-release. v0.1 is not out yet, so there is nothing to install and no public source to clone — the quickstart builds from a checkout.

Errors

The control plane answers errors in two shapes, depending on the endpoint.

The token, introspection and revocation endpoints answer with RFC 6749 §5.2 error bodies: a code and a description for developers. The description never contains a token, a key or a stack trace.

400 Bad Request
{
"error": "invalid_scope",
"error_description": "No requested scope is both held by the user and allowed for the agent."
}
Error Status Meaning
invalid_request 400 A parameter is missing, repeated, or outside its limits
invalid_client 401 Client authentication failed
invalid_grant 400 The subject token is expired, invalid, from an untrusted issuer, or has no value for the sponsor key claim; or the task grant is unknown, revoked, or belongs to another agent
unsupported_grant_type 400 The grant_type is not token exchange or refresh_token
invalid_scope 400 The scope intersection is empty, or a refresh asked for a scope the task does not hold
invalid_target 400 The audience is not in the agent’s allowed_audiences, or a refresh names a different audience than the task’s
access_denied 400 The agent is disabled, the task is revoked or expired, the human is one Subact ID will not act for, or the task has less than five seconds left (task_ending: stop, do not retry). On an exchange, no task was created
unsupported_token_type 400 On revocation, a token_type_hint other than access_token or refresh_token
temporarily_unavailable 503 The decision cannot be made now: the identity provider’s keys, the agent’s keys or (in poll mode) the identity provider’s answer about the human could not be fetched, the database did not answer, or the instance is at capacity. Retry
slow_down 429 Too many requests from this source. Retry-After says when to retry

temporarily_unavailable is never treated as a yes. Retry the same request; do not get a new credential. When the database or capacity is the cause, the response carries Retry-After.

invalid_client covers several causes on purpose: an unknown agent, a bad signature, a replayed assertion jti, an assertion that claims to live longer than five minutes, and an instance claim longer than 128 characters. Telling them apart would reveal which agent ids exist. The audit ledger records the exact reason.

A disabled agent gets access_denied, not invalid_client, and only when its assertion verified. Only the agent itself can learn that it is disabled.

The admin API answers 400 with an RFC 9110 problem document that lists every invalid field:

400 Bad Request
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
"title": "The request is invalid.",
"status": 400,
"errors": {
"display_name": ["is required."],
"max_delegation_depth": ["is required."]
}
}

Field names are the JSON names you sent. Validation runs in two passes: first the request’s shape, then the registration as a whole. Errors from the second pass appear only once the first pass succeeds:

400 Bad Request
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
"title": "The request is invalid.",
"status": 400,
"errors": {
"sponsor_required": [
"must be true: issuing without a human subject token is not supported yet."
]
}
}

Registration rules:

Field Rejected when
agent_id Not 1 to 128 lowercase letters, digits, -, _ or ., starting with a letter or digit
sponsor_required false. v0.1 issues nothing without a human subject token
allowed_scopes Empty, containing duplicates, or containing a scope with spaces, quotes, backslashes or non-printable characters
allowed_audiences Empty, containing duplicates, or containing anything but an absolute http or https URL
max_task_ttl Outside SubactId:Agents:MinTaskTtl…MaxTaskTtl (one minute to one day by default)
max_token_ttl Outside SubactId:Agents:MinTokenTtl…MaxTokenTtl (30 seconds to one hour by default), or longer than max_task_ttl
max_delegation_depth Missing, or outside 1 to 5
jwks_uri Not an absolute HTTPS URL
jwks Empty, or any key carrying a private member (d, p, q, dp, dq, qi, k, oth)
jwks and jwks_uri Both set, or neither

Every authorization decision is an audit record, and every denial carries a machine-readable reason. To see why a request was refused, query the ledger:

Terminal window
curl -s "https://subactid.internal.example.com/audit?decision=deny&limit=5" \
-H "Authorization: Bearer $SUBACTID_ADMIN_KEY"

See the audit ledger and section 8 of the spec.

A tool server’s refusals are its own. @subactid/server and @subactid/mcp answer 401, 403 or 503 (when keys or introspection cannot be reached) with a WWW-Authenticate header and a stable reason. See the @subactid/server reference.

Subact ID Pre-release. v0.1 is not out yet.

© 2026 Nikola Živković PR Agencija za programerske usluge Novi Sad. Subact ID is its product.

LegalTermsPrivacy