Skip to content

guides

Intended Documentation

API Quickstart

Obtain an intended_live_ API key, submit your first intent to POST /intent, and handle the APPROVED (200), ESCALATED (202), and DENIED (403) outcomes.

API Quickstart#

This quickstart submits an action to the canonical runtime route, POST /intent, and walks through the three decision outcomes. The submitted action is an IntentRequest — not an action/resource/subject triple — and the route returns the legacy decision vocabulary APPROVED / ESCALATED / DENIED mapped to HTTP 200 / 202 / 403.

Note

POST /intent returns APPROVED / ESCALATED / DENIED. The modern POST /authority/evaluate route returns a different vocabulary (ALLOW / DENY / REQUIRE_APPROVAL / ESCALATE). They are distinct — don't mix them.

Step 1 — Get an API key#

Mint a tenant API key from the console. Customer keys are prefixed intended_live_…. There is no separate sandbox or test key — one kind of key covers every use, and the environment label you can attach to a key is cosmetic (it does not gate authorization, billing, or data isolation). A legacy mrt_… prefix is still accepted for older staff keys.

Store the key and your tenant id as shell variables. Keep the key server-side; it is stored only as a SHA-256 hash and can never be recovered from Intended.

bash
export INTENDED_API_KEY="intended_live_your_key_here"
export TENANT_ID="tenant_acme_prod"

Step 2 — Submit an intent#

The request body is an IntentRequest. The fields below are the full schema for the GitHub-workflow target the runtime ships today. Note two schema rules enforced server-side:

  • riskContext.github is required — the intent describes a GitHub Actions workflow dispatch / PR target.
  • targetSystem must equal "<owner>/<repo>" from riskContext.github, or the request is rejected with 400 VALIDATION_ERROR.
bash
curl -X POST https://api.intended.so/intent \
  -H "Authorization: Bearer $INTENDED_API_KEY" \
  -H "x-tenant-id: $TENANT_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "tenantId": "tenant_acme_prod",
    "actor": { "id": "svc-ci-bot", "type": "service" },
    "targetSystem": "acme/platform-api",
    "proposedAction": "dispatch release workflow",
    "riskContext": {
      "baseRiskScore": 42,
      "policyCompliant": true,
      "requiresPrivilegedAccess": false,
      "touchesProduction": false,
      "containsSensitiveData": false,
      "github": {
        "owner": "acme",
        "repo": "platform-api",
        "workflowId": "release.yml",
        "ref": "refs/heads/main"
      }
    }
  }'

Request fields#

FieldTypeRequiredNotes
tenantIdstringyesMust match the tenant your credential resolves to.
actor.idstringyesThe acting identity.
actor.type"user" | "service"yes
targetSystemstringyesMust equal "<github.owner>/<github.repo>".
proposedActionstringyesHuman-readable description of the action.
riskContext.baseRiskScoreinteger 0–100yesStarting risk before modifiers.
riskContext.policyCompliantbooleanyes
riskContext.requiresPrivilegedAccessbooleanno (default false)Adds +20 to risk when true.
riskContext.touchesProductionbooleanno (default false)Adds +20 to risk when true.
riskContext.containsSensitiveDatabooleanno (default false)Adds +15 to risk when true.
riskContext.githubobjectyes{ owner, repo, ref, workflowId?, inputs?, pullRequest? }.

The effective risk score is baseRiskScore + (privileged ? 20 : 0) + (production ? 20 : 0) + (sensitive ? 15 : 0), capped at 100.

Step 3 — Handle the outcome#

The response body carries authorityDecision, and on approval, the signed token:

json
{
  "intentId": "8aa3f5f6-b1a9-4c5b-a29f-b489f7d0be58",
  "correlationId": "…",
  "authorityDecision": {
    "decision": "APPROVED",
    "riskScore": 42,
    "policyCompliant": true,
    "rationale": ["…"],
    "gateTrace": []
  },
  "authorityDecisionToken": "eyJhbGciOiJFZERTQSIsImtpZCI6…",
  "execution": { "attempted": true, "status": "executed", "adapterId": "github-actions-adapter" },
  "audit": { "entriesWritten": 3, "latestHash": "…" }
}
OutcomeHTTPWhat you getWhat to do
APPROVED200authorityDecisionToken present; execution attempted through the adapter.Verify the token at the point of execution (see below).
ESCALATED202escalationId returned; no token issued.Route to your approval queue; resubmit or resume after a human approves.
DENIED403No token; execution skipped (fail-closed).Surface the rationale; the action is not authorized.

A validation failure returns 400 VALIDATION_ERROR. Any internal failure during evaluation returns 500 AUTHORITY_LOOP_FAILED — the runtime fails closed rather than letting an action through on error.

Step 4 — Verify the token before you execute#

The authorityDecisionToken is an Ed25519 JWT bound to this one intent. The system that performs the action must verify it locally before acting — cloud approval alone is not sufficient.

ts
const verification = await sdk.verifyAuthorityToken({
  token: result.authorityDecisionToken as string,
  publicKeyPem,                 // tenant public key for the token's kid
  expectedKid: kid,             // pin the key id (required)
  expectedTenantId: "tenant_acme_prod",
  expectedAdapterId: "github-actions-adapter",
  maxTokenTtlSeconds: 300,
  clockSkewSeconds: 60,
});
if (!verification.valid) throw new Error(verification.reason ?? "TOKEN_INVALID");

sdk.verifyAuthorityToken(...) delegates to @intended/verify's verifyToken. See Verify Decision Tokens for the full flow, including the single-use nonce that the connector SDK consumes.

Next steps#

API Quickstart | Intended