guides
Intended Documentation
Agent Intent API
Submit an agent's proposed action to POST /v1/agent-intent for a governed decision. The response is a decision, a signed token on approval and a receipt; your agent acts and can report the outcome. inputQuality feedback when the action text names no action.
Agent Intent API#
POST /v1/agent-intent is the route the SDK (guard(), evaluate(), authorize()), the MCP server, the MCP gateway and the LLM gateway use. It takes one proposed agent action and returns a governed decision: APPROVED (HTTP 200), ESCALATED (202) or DENIED (403). Every decision is classified, recorded in the tenant's hash-linked audit chain and gets a receipt. An approved decision also carries a signed authority token.
Intended decides; your agent acts#
Intended decides and proves; your agent acts. The response is the decision, a signed authority token when the decision is APPROVED, and a receipt. Intended never runs the action and never calls the target system: not when the decision is made, not when a person later approves an escalation, and not when an operator overrides the decision. It holds none of your third-party credentials.
Once the verdict is APPROVED, your agent performs the action with its own credentials and passes the authority token to the downstream service, which verifies it offline against the published JWKS before acting. See Verify Tokens & Decisions.
Request#
| Field | Required | What to send |
|---|---|---|
tenantId | yes | Your tenant id. |
actor | yes | { id, type }. type is agent, service, user or system. |
proposedAction | yes | The action to take, as a verb phrase: "Send renewal reminder email", "Revoke expired API keys", "Deploy billing to staging". Not a status line or an alert. |
targetSystem | no | The system the action touches, such as stripe/subscriptions or github/acme/api. Recorded as you send it (trimmed). |
arguments | no | The action's details: ids, counts, recipients, the tool call's arguments. Policies and the classifier read them in memory; they are stored only as a SHA-256 digest. |
metadata | no | Flat context to record with the decision (stored as sent, up to 25 keys). Do not put secrets or personal data here; use arguments. |
riskContext | no | Risk hints (touchesProduction, requiresPrivilegedAccess, containsSensitiveData, baseRiskScore, policyCompliant, justification). Hints can only raise scrutiny; see Risk hints can only raise scrutiny. |
source | no | Attribution: { connector, harness, tool }, naming the integration that submitted the intent (for example the MCP gateway or a framework integration). |
targetSystem when you leave it out#
Intended does not guess a target from your sentence. If targetSystem is missing:
- A tool-call action names the tool:
Call MCP tool "run_query" with args: sqlis recorded asmcp/run_query, andCall tool "Bash"astool/bash. - Otherwise the tool in
source.tool(ormetadata.tool) is used. - Otherwise the decision records
unspecified. Per-resource signals, such as "first time this agent touches this resource", are not computed for an unspecified target.
Risk hints can only raise scrutiny#
Intended works out the risk of every action itself, from the action it classified, the action's verb, the target system and the arguments. It reads that text after decoding and normalising it, so encoded or disguised text (percent-encoding, HTML entities, look-alike letters, split SQL keywords, base64) is read as what it means. Your riskContext is then combined with what Intended worked out:
| Field | How your value is used |
|---|---|
touchesProduction, requiresPrivilegedAccess, containsSensitiveData | true adds scrutiny. false does not remove it: if the target, action or arguments name production, privileged access or sensitive data, the decision treats the action that way. |
baseRiskScore | Raises the score. The score is never lower than the floor Intended works out (for example, any write that is not a confirmed read, a high-risk action category or a destructive verb). |
policyCompliant | false denies the action under the starter policy. true is the default and changes nothing. |
When Intended raises a field above what you sent, the decision's rationale says which field and why. The value you sent is recorded unchanged. The same applies to POST /intent and every SDK.
Reporting the outcome#
After your agent acts, it can report what happened. Outcome reports never change the decision, the token or the receipt; they append an immutable signal to the decision's record.
outcome is one of success, failure, rolled_back, incident or retried. The key needs the intent:outcome:write scope. The TypeScript SDK wraps this as reportOutcome(intentId, outcome, options).
inputQuality#
When proposedAction does not name an action up front, the response carries an inputQuality object. The decision is still made, fail-closed: an action the classifier cannot place is escalated under the default policy. inputQuality tells your agent how to phrase the next request.
status | Meaning |
|---|---|
no_action_detected | The text has no action verb, for example "escalation queue depth is 156, queue may be backed up". |
action_not_leading | The action is buried after a description, for example "5 API keys unused for 90 days, send rotation reminders". Lead with the action. |
The field is absent when the action text is fine. The SDKs expose it as decision.inputQuality (TypeScript, @intended-inc/sdk 0.6.1 and later) and decision.input_quality (Python, intended 0.2.3 and later); the MCP server's submit_intent result includes it as inputQuality (@intended-inc/mcp-server 0.1.4 and later).
Responses#
| HTTP | Decision | Notes |
|---|---|---|
200 | APPROVED | authorityDecisionToken is set. Your agent may now act, carrying the token. |
202 | ESCALATED | escalationId names the approval request. |
403 | DENIED | No token. |
400 | none | The body failed validation. |