Skip to main content

Provisioning API Error Reference

Every failure from the Agent Provisioning API is machine classifiable. This page is the contract an automation should be written against: which code means retry, which means edit the document, which means change a setting in the console, and which means page a human.

The error envelope

{
"error": "package_exceeds_key_ceiling",
"message": "The document requests capabilities beyond this key's ceiling.",
"violations": [
{
"path": "spec.agent.outbound.email",
"requested": "any",
"ceiling": "internal",
"reason": "exceeds_ordinal_ceiling"
}
]
}
FieldAlways presentNotes
erroryesThe stable, contractual code. Branch on this
messageyesHuman-readable prose. Not contractual; it may be reworded at any time, so never branch on it
fields[]400 onlyWhat was wrong with the document's structure
violations[]409 package_exceeds_key_ceiling onlyWhich requests exceeded the key's ceiling
unmet[]422 preflight_failed and 409 name_conflict onlyWhich tenant prerequisites are not satisfied

A response carries at most one of fields[], violations[] and unmet[].

Detail lists are complete rather than first-failure. One call tells you everything that is wrong at that stage, so you are not fixing one field per round trip.

Status codes

StatuserrorWhat it meansYour move
400invalid_documentThe document failed structural validationFix the document; read fields[]
400unknown_fieldThe document carries fields this API does not recogniseFix the spelling; read fields[]
400tenant_id_not_permitted_in_bodyA tenant identifier appeared in the bodyRemove it. The key already carries the tenant
400unsupported_api_versionapiVersion is not a version this API servesSet it to provisioning.outermind.ai/v1
400unsupported_kindThe document's root kind is not one this API servesSet it to AgentPackage. fields[0].reason lists the kinds that are served
401invalid_keyNo key, a malformed key, or a key that does not existCheck the Authorization header
401key_revokedThe key was revoked in the consoleMint a replacement
401key_expiredThe key passed its expiry dateMint a replacement
403scope_not_grantedThe key lacks a scope this request needsMint a key with the scope. The response names it in a scope field
404package_not_foundNo such package for this tenantCheck the name
404operation_not_foundNo such operation for this tenantCheck the operation id
404source_not_foundNo such knowledge source under that index for this tenant. A malformed id, a source under a different index, and a source of a connector kind the sync route does not serve all answer thisCheck the index id and the source id
404sync_run_not_foundNo such sync run for that tenant, index and sourceCheck the run id, and that it belongs to the source you are polling
409package_exceeds_key_ceilingThe document asked for more than this key may grantNarrow the document, or mint a wider key
409name_conflictA resource this package does not own already holds the nameRename the package, or remove the conflicting agent
409operation_in_flightAnother operation for this package is pending or runningPoll that one, or retry shortly
409sync_already_runningA sync for this knowledge source is already in flightPoll the run the body's runId names, or retry shortly
422preflight_failedThe tenant is not ready for what the document asksConfigure the tenant; read unmet[]
429rate_limitedThe key exceeded its per-minute or per-day limitBack off for the seconds given in the Retry-After header
500internal_errorSomething failed on our sideRetry. Apply is idempotent, so retrying is safe. The sync route is deliberately not idempotent, but retrying a 500 from it is still safe: the retry either starts a fresh run or answers 409 sync_already_running, never a second crawl alongside the first

Every 400, 403, 409 and 422 is a zero-write rejection. The request is refused before anything is written, so the tenant is byte identical afterwards and iterating on a document never leaves debris behind.

409 versus 422: which human do you page

This is the single most important distinction in the API, and it exists so that an automation never has to guess.

409 means the document asked for more than this key may grant. That is a policy decision. Nothing about the tenant is wrong. Someone with console access has to decide either to narrow the document, or to mint a key with a wider ceiling. Because a ceiling is fixed at mint time, "widening the key" always means minting a new one.

422 means the tenant is not ready. That is an environmental fact. The document may be perfectly reasonable, and the key may be perfectly adequate. Something has to be created or configured in the tenant first: a mailbox that does not exist, an authorization that was never completed, a knowledge base that has not been built.

Collapsing them into one code would leave every automation guessing which of two very different people to notify. Branch on the code, not on the message.

409 operation_in_flight is the one 409 that is neither. It is a timing conflict, resolved by waiting rather than by editing anything, and it is the only 409 that carries neither violations[] nor unmet[]. Retry it and page nobody.

409 name_conflict sits with the 422s in spirit but is a 409 by status, because a non-owned resource holding the name is a harder stop than an unmet prerequisite. It carries its finding in unmet[], not violations[]. When a document has both a name conflict and other unmet prerequisites, the response is 409 and unmet[] still lists everything, so you still learn it all in one call.

400: fields[]

Each entry describes one structural problem.

{
"error": "invalid_document",
"message": "Document failed structural validation.",
"fields": [
{ "path": "spec.agent.friendlyName", "reason": "value must be a string" },
{ "path": "metadata.version", "reason": "value must match the required pattern", "received": "<string, 3 chars>" }
]
}
FieldNotes
pathDotted path into the document, with array indices
reasonWhat was wrong at that path
receivedOptional. A bounded description of what you sent, never the value itself

received is deliberately a shape rather than a value: sending back the content of spec.agent.instructions in an error body would put your own text somewhere it does not belong.

unknown_field versus invalid_document. When every problem is an unrecognised field, the code is unknown_field. When there is any other kind of problem in the mix, the code is invalid_document and the unknown fields still appear in fields[]. So a document that is wrong in two ways is never reported as merely misspelled.

409: violations[]

Each entry names one place the document exceeded the key's ceiling.

{
"error": "package_exceeds_key_ceiling",
"message": "The document requests capabilities beyond this key's ceiling.",
"violations": [
{ "path": "spec.tools.grant[0]", "requested": "onedrive_delete", "ceiling": "not granted", "reason": "tool_not_in_ceiling" },
{ "path": "spec.agent.limits.maxDailyUsageCost", "requested": 100, "ceiling": 25, "reason": "exceeds_numeric_ceiling" }
]
}
FieldNotes
pathWhere in the document the request was made
requestedWhat the document asked for, or a bounded description of it
ceilingWhat the key permits at that path
reasonOne of the eight codes below
reasonMeaning
exceeds_ordinal_ceilingAn outbound channel asked for more reach than the key permits. The order is none, internal, any, and a ceiling permits its own level and everything narrower
exceeds_numeric_ceilingA cost limit is above the key's cap, or is not a number
not_in_enum_ceilingA value is not in the key's permitted list for that field, such as an autonomy level or a review policy
tool_not_in_ceilingA granted tool name is not covered by the key's tool ceiling
wildcard_not_allowed_in_packageA grant contains a *. Ceilings may use wildcards; documents may not
capability_not_grantedThe document declared a section the key may not use at all, such as spec.mailbox on a key without mailbox binding, or spec.agent.sme on a key without SME provisioning
capability_not_resolvedA value stood where the document format expects an object that gates a capability, so the gate could not work out what was being asked for. Denied rather than assumed harmless
unknown_field_not_covered_by_ceilingThe document contains a path the ceiling grammar does not recognise, so no bound could be applied to it

Remember that omitted fields with defaults are checked as though you had written the default. A document with no outbound section still requests internal on all three channels, and will violate a none ceiling.

422: unmet[]

Each entry names one tenant prerequisite that is not satisfied.

{
"error": "preflight_failed",
"message": "The target tenant does not satisfy this document's prerequisites.",
"unmet": [
{
"code": "mailbox_not_found",
"detail": "No mailbox found in Microsoft 365 for 'billing@contoso.com'.",
"remediation": "Create the mailbox in Microsoft 365, then re-apply. The provisioning API never creates mailboxes.",
"blocks": ["spec.mailbox.address"]
}
]
}
FieldNotes
codeThe stable code. Branch on this. Always present
detailWhat specifically was not satisfied. Present on entries a submitted document produced
remediationOptional. The suggested fix, in prose
blocks[]The document paths this prerequisite blocks. Present on entries a submitted document produced
sourceStatusknowledge_source_not_syncable only. The knowledge source's current status

Only code is guaranteed. The sync route submits no document, so its one entry carries code and sourceStatus and neither detail nor blocks[] - branch on code and read the rest defensively.

codeMeaning and fix
mailbox_not_foundspec.mailbox.address does not resolve to a mailbox in Microsoft 365, or resolves to a distribution list. Create a shared or individual mailbox, then re-apply. The API never creates mailboxes
exchange_authorization_missingThe tenant has not completed Exchange authorization, so no mailbox can be bound. Run the Exchange Authorization wizard in the console once
knowledge_source_not_foundA spec.knowledgeSources[].name does not match a knowledge base this tenant has. Check GET app/provisioning/catalog/knowledge-bases
supervisor_not_resolvablespec.agent.supervisorEmail does not resolve to a mailbox or mail-enabled group in this tenant
tool_not_availableA granted tool, or a tool the chosen review policy needs, is not enabled for this tenant. Enable it in the console, or change the document. Check GET app/provisioning/catalog/tools
escalation_router_not_configuredThe chosen review policy needs somewhere for an escalation to go, and the tenant has neither a monitored mailbox nor a configured Escalation Router
name_conflictAn agent with this name already exists and is not owned by this package. Rename the package, or remove the conflicting agent. This one is reported with status 409, not 422
instructions_rejectedspec.agent.instructions was rejected by instruction-safety screening. Revise the instructions, and the design evidence if present
instruction_screening_unavailableInstructions could not be screened because the tenant has no usable LLM provider. Configure one, then re-apply. This fails closed on purpose: unscreened instructions are never accepted
sme_supervisor_requiredAn SME package must declare spec.agent.supervisorEmail
sme_description_too_shortAn SME's spec.agent.description must be at least 80 characters, because that description is how other agents discover it
sme_conversion_not_supportedThe named agent already exists as a non-SME agent, and this API will not convert it. Use a new package name, or convert it in the console
sme_design_evidence_requiredCreating a new SME requires a spec.designEvidence block
sme_escalation_contact_not_resolvableAn SME escalation contact address does not resolve to a recipient in this tenant. This is checked for both escalationContact and alternateEscalationContact
knowledge_source_not_syncablePOST app/knowledge-indexes/{indexId}/sources/{sourceId}/sync named a source whose status is paused or deleting, which the sync processor skips. Resume the source in the console, then retry. Carries sourceStatus; it is the one code not raised by preflight, so it has no blocks[]

Partial success is not an error

An operation that reaches partially_succeeded did so after a 202. It is a terminal operation status, not an HTTP error, and it does not mean the apply is unrecoverable.

{
"operationId": "6f2a9c14-2f1e-4a7b-8f0a-1e2d3c4b5a69",
"status": "partially_succeeded",
"packageName": "billing-triage",
"resources": [
{ "kind": "agent", "name": "billing-triage", "status": "applied" },
{ "kind": "toolGrant", "name": "search_knowledge_base", "status": "applied" },
{
"kind": "knowledgeSource",
"name": "Billing Policies",
"status": "failed",
"error": { "code": "reconcile_failed", "message": "knowledge source could not be attached" }
}
]
}

The remedy is to re-apply the same document. Apply is idempotent: the resources that already converged report noop, and only the failed ones are attempted again. Do not build a compensating "undo" path and do not construct a second, smaller document; both are more likely to cause damage than the retry is.

Read resources[] to see which resource failed and why. An entry with status: "skipped" and reason: "prerequisite_failed" was not attempted because something it depended on failed first; it will be attempted on the next apply once the blocker clears.

status: "failed" on the whole operation means nothing converged, and is handled the same way: re-apply.

Writing an automation against this

A practical branch table:

ResponseAutomated handling
429 rate_limitedSleep for Retry-After seconds, retry
500 internal_errorRetry with backoff, a bounded number of times
409 operation_in_flightPoll the existing operation, or retry shortly. Never an alert
409 sync_already_runningPoll the run the body's runId names, or retry shortly. Never an alert
operation status partially_succeeded or failedRe-apply the same document once, then alert if it recurs
422 preflight_failedAlert the tenant's operator, quoting unmet[].remediation
409 package_exceeds_key_ceilingAlert whoever owns the key, quoting violations[]
409 name_conflictAlert a human; this needs a naming decision
403 scope_not_grantedAlert whoever owns the key, quoting the scope field
401 key_revoked or key_expiredAlert whoever owns the key. Do not retry
400 of any kindA bug in whatever generated the document. Fail the pipeline, do not retry