Digitalyser.io for developers

Error reference

Every refusal this API can return, what causes it, and what to do about it.

Every error carries a type pointing at its own section on this page. That URI is stable: it is part of the contract, and safe to branch on.

How to read a refusal

Refusals follow RFC 9457 and arrive as application/problem+json:

{
  "type": "https://developers.digitalyser.io/reference/errors#insufficient_permission",
  "title": "agent_authorization_denied",
  "status": 403,
  "reason": "insufficient_permission"
}

Branch on reason, never on title or on the wording of a message. reason is the stable identifier — the one listed below and the one after the # in type. title names the error class, which is coarser: several reasons share one.

Note that MCP and REST differ here, and only here. MCP wraps every error in a 200 JSON-RPC envelope, so the reason travels in the body; REST returns the real status code. The catalogue is the same on both.

401 — Authentication — the credential itself

The transport did not accept the credential. Re-authenticate; do not treat these as permission problems.

credential_expired

The credential expired · HTTP 401

Access tokens obtained by OAuth consent last one hour.

What to do — Use your refresh token (dgr_…) to obtain a new access token. A conforming MCP client does this on its own.

credential_invalid

The credential was not recognised · HTTP 401

The token is malformed, was never issued, or is not the kind this surface accepts. A session JWT and a scoped API key both authenticate the transport but open no capability.

What to do — Send an agent project token (Authorization: Bearer dga_…). The WWW-Authenticate header on the response points at the resource metadata your client needs to authorise itself.

credential_revoked

The credential was revoked · HTTP 401

An organisation administrator revoked this token, or the service key it derives from. Revoking a service key revokes every token derived from it, in one gesture.

What to do — Ask an administrator to issue a new token. Retrying will not help — revocation is final.

membership_revoked

The person behind this token left the organisation · HTTP 401

An agent token carries the authority of the person who consented. That membership is rechecked on every call, not frozen at issue time.

What to do — Re-run the consent flow with a current member of the organisation.

support_session_unsupported

This project is not reachable from a support session · HTTP 401

The bearer of this token has no membership on the project, and is currently in a support session on its organisation. A support session never grants agent chat access by itself.

What to do — Re-run the consent flow with a member of the organisation who is not in a support session.

400 — The request — malformed, not forbidden

The call never reached authorisation. Something about the request itself is wrong.

capability_not_invokable

That capability is not invoked directly · HTTP 400

Write capabilities are not called; they are proposed, then applied. Invoking one directly is a malformed request, not a forbidden one — hence 400 and not 403.

What to do — Invoke the matching propose capability, review the returned proposal, then call applyProposal with its handle.

project_required

This capability needs a project · HTTP 400

The capability is project-scoped and the credential carries no project.

What to do — Use a token derived for the project you mean. One token, one project — for machines exactly as for people.

request_invalid

The request body did not validate · HTTP 400

A field is missing, of the wrong type, or outside its allowed range.

What to do — Check the parameter schema of the operation in the reference. The detail stays in our logs rather than the response body, deliberately: a public contract does not freeze the wording of a validation message.

scope_in_params

Scope cannot be passed as a parameter · HTTP 400

An organisation or project identifier was sent in the request body. A capability takes its scope from the credential, never from a parameter — that is the tenant-isolation boundary.

What to do — Remove the scope field from the parameters and use a token issued for that project.

403 — Authorisation — the call was understood and refused

The request is well-formed and the credential is valid. What differs is what this caller may do.

audience_mismatch

That capability belongs to another audience · HTTP 403

A session exposes one audience and one only. A support capability is not reachable from a tenant session, and the reverse holds too.

What to do — Use the surface that matches what you are doing. This is a boundary, not a setting.

capability_not_allowed

This capability is outside the grant · HTTP 403

The capability exists and the credential is valid, but the consent that produced this token did not include it. The allowlist is at the grain of the capability.

What to do — Re-run the consent flow and tick this capability. On MCP the server tells your client which scope to request; on REST the identifier in the message is what to look for on the consent screen.

insufficient_permission

The bearer lacks the permission · HTTP 403

Distinct from the previous one, and the difference matters: the grant allows this capability, but the person or key behind the token does not hold the permission it requires.

What to do — Ask an organisation administrator to grant the missing permission. Re-consenting changes nothing.

project_out_of_scope

That project is outside the credential · HTTP 403

The requested project is not the one this token carries.

What to do — Use the token derived for that project. An agency following several brands derives one token per client, so a workflow wired for one cannot reach another.

request_refused

The request was refused, without a more specific reason · HTTP 403

A generic refusal that no more precise reason covered. Seeing this is itself worth reporting: every refusal should carry a specific reason, and this one means a case escaped classification.

What to do — Report it with the response body. It tells us a reason is missing from the catalogue.

routing_hint_mismatch

The project in the path is not the one in the token · HTTP 403

\{projectId} in the URL is a routing hint, kept so multi-project code stays uniform. The authorizer compares it with the credential and refuses when they diverge — the project comes from the token, never from the path.

What to do — Put the project of your token in the path, or switch tokens.

404 — Not found

The identifier or the route does not exist on this surface.

capability_unknown

No capability by that identifier · HTTP 404

Usually a typo. The response carries a suggestions array with the closest identifiers in the catalogue.

What to do — Call GET /v1/capabilities to list what this credential may use, and copy the identifier from there.

route_unknown

No route at that path · HTTP 404

The path does not exist on this surface, or the HTTP method is wrong for it.

What to do — Check the path and method against the reference. Note that /v1 is served under the /api prefix: the full URL is https://app.digitalyser.io/api/v1/….

409 — State — right call, wrong moment

Nothing is wrong with the request or your rights. The target is not in a state that allows it.

diff_forbidden

The proposal targets something it may not change · HTTP 409

The diff reaches a field or an item outside what this capability is allowed to write.

What to do — Narrow the proposal to the fields the capability covers. Re-consenting does not widen this — it is a property of the capability, not of the grant.

diff_stale

The content changed since the proposal was computed · HTTP 409

Someone — a person or another agent — modified the target between the proposal and the apply. Applying now would silently discard their change.

What to do — Recompute the proposal against the current content. This is the most common refusal on the write path, and it is a safety net, not a fault.

expired

The proposal expired · HTTP 409

Proposals are short-lived on purpose: applying a stale diff would overwrite work done since it was computed.

What to do — Recompute the proposal and review the fresh diff before applying it.

invalid_handle

That continuation handle cannot be used · HTTP 409

The handle is forged, expired, already used, or belongs to another principal. Possession of a handle is never treated as authentication.

What to do — Resubmit the request that produces a fresh handle. Do not retry with the same one.

not_found

No proposal by that handle · HTTP 409

The handle does not name any proposal. Usually a handle from another session, or one already garbage-collected.

What to do — Invoke the propose capability again and apply the handle it returns.

not_pending

That proposal was already resolved · HTTP 409

It has already been applied, or explicitly discarded. A proposal is applied once.

What to do — Nothing to retry. Read the current state before proposing a new change.

project_unavailable

The project exists but cannot be worked on right now · HTTP 409

Its setup is incomplete, or an operation in progress holds it. This is a state, not a permission — hence 409.

What to do — Retry later. If it persists, the project needs attention in the dashboard.

422 — Content refused

The call was well-formed and authorised; what it carried was not accepted.

diff_unsafe_content

The outgoing content was refused · HTTP 422

The output filter rejected what the proposal contained. The call was well-formed and authorised — hence 422 and not 400 or 403.

What to do — Adjust the inputs and propose again. The offending detail stays in our logs rather than the response, deliberately: echoing it back would make the refusal a way to read what it blocks.

429 — Limits — two mechanisms, one status code

A rate limit resets within the minute; a quota resets with the billing period. The reason field tells you which one you hit.

quota_exceeded

The plan quota is spent · HTTP 429

A quota counts work performed over the billing period — audits run, items produced. It is not the rate limit, even though both answer 429.

What to do — Wait for the period to reset, or upgrade the plan. Retrying sooner returns the same answer.

rate_limited

Too many requests, too fast · HTTP 429

The rate limit counts requests per minute, per credential and per project. Distinct from the quota above: this one resets within the minute.

What to do — Back off and retry. The limit is shared across MCP and REST — the same credential is counted once, whichever transport it uses.

500 — Our side

These are failures, not refusals. Retry with backoff and check the status page.

internal_error

Something failed on our side · HTTP 500

An unhandled failure. The detail stays in our logs.

What to do — Retry with backoff, and check status.digitalyser.io. If it reproduces, the developer contact on the portal is the way through.

503 — Our side

authorization_unavailable

Authorisation could not be computed · HTTP 503

A dependency needed to decide was unreachable. This is our failure, not a refusal — answering 403 would send you looking for permissions you already have.

What to do — Retry with backoff. If it persists, status.digitalyser.io carries the incident.

Generated from the capability registry — 29 refusal reasons, from e38b2b7 (2026-10-09).

Was this page helpful?

On this page