> ## Documentation Index
> Fetch the complete documentation index at: https://docs.kordio.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Authorize a payment

> Evaluated as the action type `payment.create` with the counterparty as its
resource, so an agent's caps, windows and allowlists apply across payments and
other actions together. Spend on one consumes headroom for the other.

One difference from `POST /v1/agent/actions` that will bite you if you assume
symmetry:

- **Idempotency is a body field here**, `idempotency_key`, not the
  `Idempotency-Key` header. A missing one is a `400`, not a `422`.

Kordio authorizes the payment; it never moves the money. An allowed payment comes
back `pending` with a cosignature. Settle it on your own rail, then report the
outcome with `complete` or `fail`.




## OpenAPI

````yaml /agents/api-reference/openapi.yaml post /control/v1/agent/payment_intents
openapi: 3.1.0
info:
  title: Kordio Agent Control API
  version: 1.0.0
  description: >
    The control layer decides whether an agent may take an action before it
    takes it, and

    signs the answer so the executor can verify authority without trusting the
    agent.


    Two credentials reach this API and they are deliberately not
    interchangeable.


    An **agent key** (`krt_live_...` or `krt_test_...`) is what an agent
    carries. It reaches

    `/v1/agent/*` and can ask for authorization, but it can never write policy.
    An agent

    able to widen its own limits is not governed by them.


    An **identity token** is what a person or your backend carries. It reaches

    `/v1/workspaces/{workspace_slug}/*` and manages agents, policy, approvals
    and

    members. It can never authorize an action as an agent.


    Cosignature verification needs no credential at all.
  contact:
    name: Kordio support
    email: support@kordio.io
  license:
    name: Proprietary
servers:
  - url: https://api.kordio.io
    description: Production
security:
  - AgentKey: []
tags:
  - name: Actions
    description: >-
      The authorization call that goes in front of a spend, and the outcome
      reports that settle or release its reservation. Agent key.
  - name: Budgets
    description: Budget envelopes for one agent run. Agent key.
  - name: Spend tokens
    description: >-
      Single-use, pre-authorized ceilings an agent mints against its own session
      and presents later on a payment. Agent key.
  - name: Payment intents
    description: Authorize a payment. You settle it and report back. Agent key.
  - name: Cosignatures
    description: Verify a signed authorization. Unauthenticated.
  - name: Agents
    description: Scoped identities for agent workloads. Identity token.
  - name: Policies
    description: The rules attached to an agent. Identity token.
  - name: Policy modules
    description: Named, reusable rule sets a policy imports by name. Identity token.
  - name: Policy previews
    description: Evaluate a draft policy without saving it. Identity token.
  - name: Approvals
    description: >-
      The queue of intents a rule held for a person, and the calls that resolve
      them. Identity token.
  - name: Funds
    description: Committed, held and unprojected spend across a workspace. Identity token.
  - name: Audit events
    description: >-
      The append-only record of everything the control layer did. Identity
      token.
  - name: Webhook endpoints
    description: Control-layer event delivery. Identity token.
  - name: Workspaces
    description: Workspace lifecycle and export. Identity token.
  - name: Members
    description: Memberships and invitations. Identity token.
  - name: Billing
    description: >-
      Plan, entitlements and prepaid volume. Console-facing; agents never call
      these. Identity token.
externalDocs:
  description: Control layer guides
  url: https://docs.kordio.io/agents
paths:
  /control/v1/agent/payment_intents:
    post:
      tags:
        - Payment intents
      summary: Authorize a payment
      description: >
        Evaluated as the action type `payment.create` with the counterparty as
        its

        resource, so an agent's caps, windows and allowlists apply across
        payments and

        other actions together. Spend on one consumes headroom for the other.


        One difference from `POST /v1/agent/actions` that will bite you if you
        assume

        symmetry:


        - **Idempotency is a body field here**, `idempotency_key`, not the
          `Idempotency-Key` header. A missing one is a `400`, not a `422`.

        Kordio authorizes the payment; it never moves the money. An allowed
        payment comes

        back `pending` with a cosignature. Settle it on your own rail, then
        report the

        outcome with `complete` or `fail`.
      operationId: createPaymentIntent
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - budget_id
                - amount_cents
                - idempotency_key
              properties:
                budget_id:
                  type: string
                  format: uuid
                amount_cents:
                  type: integer
                  minimum: 1
                  example: 12000
                idempotency_key:
                  type: string
                  description: >-
                    Required, and sent in the body rather than as a header.
                    Unique per agent. A replay returns the original intent and
                    decision at the original status.
                  example: order-4471-attempt-1
                counterparty:
                  type: string
                  nullable: true
                  example: acme-supplies.example
                spend_token_id:
                  type: string
                  format: uuid
                  nullable: true
                  description: A token minted by this agent against the same session.
                trace_id:
                  type: string
                  nullable: true
                decision_context:
                  type: object
                  additionalProperties: true
                  description: >-
                    Free-form context stored with the intent and echoed back,
                    with the decision merged in under the key `decision`.
                    Provenance only: policy never reads it. Put anything a rule
                    must evaluate in `metadata`.
                metadata:
                  type: object
                  additionalProperties: true
                  description: >-
                    Facts the policy engine evaluates, reachable in a rule as
                    `metadata.<key>` with dot paths for nested values. Same
                    field, same meaning, as on `POST /v1/agent/actions`.
      responses:
        '201':
          description: >-
            `allowed`. The payment is `pending` until you settle it and report
            back.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaymentDecisionResponse'
              example:
                data:
                  id: 4a7c2e18-9b05-4f63-8d21-6e0a3c7f5b94
                  workspace_id: 1c4f8b20-9e77-4a31-b5d2-06a8c3e91f4d
                  agent_id: 7d2a6c15-3f89-4b02-9e64-8a15d7c0b3f2
                  budget_id: 3f1c8a92-5d41-4c8e-9f2b-7a6e1d0c4b83
                  spend_token_id: null
                  amount_cents: 12000
                  currency: USD
                  counterparty: acme-supplies.example
                  state: pending
                  failure_reason: null
                  idempotency_key: order-4471-attempt-1
                  created_at: '2026-08-04T09:12:04Z'
                decision:
                  outcome: allowed
                  rule: null
                  detail: {}
                  headroom:
                    session_remaining_cents: 38000
                  policy_snapshot: []
                  policy_version: pv_8f2c1d0a4b6e93571ac2e8d045f7b312
                  frame_hash: >-
                    fh_1b9a0c7e5d2f483610badc0ffee1234567890abcdef1234567890abcdef123456
                  remedy: null
                cosignature: eyJhbGciOiJFUzI1NiIsImtpZCI6ImtleV8xIiwidHlwIjoiSldUIn0
        '202':
          description: >-
            `requires_approval`. Nothing has moved. Approving returns the intent
            to `pending` for you to settle and report; denying releases the
            reservation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaymentDecisionResponse'
        '400':
          description: '`idempotency_key` is missing from the body.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          $ref: '#/components/responses/AgentUnauthorized'
        '403':
          description: '`denied`. Nothing was executed.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaymentDecisionResponse'
        '404':
          description: The budget or spend token does not belong to this agent.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
        - AgentKey: []
components:
  schemas:
    PaymentDecisionResponse:
      type: object
      required:
        - data
        - decision
      properties:
        data:
          $ref: '#/components/schemas/PaymentIntent'
        decision:
          $ref: '#/components/schemas/Decision'
        cosignature:
          type: string
    Error:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - message
          properties:
            message:
              type: string
              description: For humans. Do not switch on it.
            code:
              type: string
              nullable: true
              description: >-
                Stable where present, `null` otherwise. Only a defined set of
                failures carry a code.
              enum:
                - runtime_key_required
                - invalid_runtime_key
                - identity_token_required
                - invalid_identity_token
                - budget_ceiling
                - parent_budget_exhausted
                - already_consumed
                - idempotency_conflict
                - insufficient_role
                - idempotency_key_required
                - authorization_required
                - rate_limited
                - billing_unavailable
                - invalid_signature
                - identity_unavailable
                - identity_not_configured
                - mfa_required
                - email_verification_required
    PaymentIntent:
      type: object
      properties:
        id:
          type: string
          format: uuid
        workspace_id:
          type: string
          format: uuid
        agent_id:
          type: string
          format: uuid
        budget_id:
          type: string
          format: uuid
        spend_token_id:
          type: string
          format: uuid
          nullable: true
        amount_cents:
          type: integer
        currency:
          type: string
        counterparty:
          type: string
          nullable: true
        state:
          type: string
          enum:
            - pending
            - requires_approval
            - executed
            - failed
            - denied
        failure_reason:
          type: string
          nullable: true
        resolved_by_sub:
          type: string
          nullable: true
        resolved_at:
          type: string
          format: date-time
          nullable: true
        idempotency_key:
          type: string
        trace_id:
          type: string
          nullable: true
        ledger_transaction_id:
          type: string
          nullable: true
        decision_context:
          type: object
          additionalProperties: true
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    Decision:
      type: object
      required:
        - outcome
        - rule
        - detail
        - headroom
      properties:
        outcome:
          type: string
          enum:
            - allowed
            - denied
            - requires_approval
        rule:
          type: string
          nullable: true
          description: >-
            The rule that produced this outcome, `null` when allowed with
            nothing to report. Rules from the typed policy columns surface under
            their column name: `per_transaction_cap`, `velocity_cap`,
            `monthly_cap`, `counterparty_allowlist`, `approval_threshold`. Rules
            written in the `rules` array surface under their `rule_name`,
            defaulting to the rule kind.
          examples:
            - per_transaction_cap
            - session_budget
            - agent_scope
            - session_inactive
            - no_policy
            - not_allowlisted
            - spend_token_ceiling
        detail:
          type: object
          additionalProperties: true
          description: The numbers behind the decision, keyed by what the rule cared about.
        headroom:
          type: object
          additionalProperties:
            type: integer
          description: >-
            What is left on every limit that applied, reduced to the tightest
            value per key. Give it to your agent: one that knows it has $380
            left picks a cheaper vendor, one that only knows it was refused
            retries into the same wall.
        policy_snapshot:
          type: array
          items:
            type: object
            additionalProperties: true
          description: >-
            The compiled policies as they stood when this decision was made,
            each with an `imported_rules` entry carrying the rules of every
            module it imports, resolved at decision time. Editing a module later
            does not change what this records.
        policy_version:
          type: string
          nullable: true
          description: >-
            Content digest of `policy_snapshot`, stable for a given set of rules
            and different for any other. Two decisions sharing a
            `policy_version` were judged by identical rules, so the same frame
            under the same version replays to the same outcome. `pv_none` when
            the agent has no policy at all.
          examples:
            - pv_8f2c1d0a4b6e93571ac2e8d045f7b312
        frame_hash:
          type: string
          nullable: true
          description: >-
            SHA-256 over the canonical request frame: action type, resource,
            cost, currency, agent, budget, workspace and the full `metadata`. It
            binds the decision to the exact request that produced it, including
            metadata no response echoes back.
          examples:
            - >-
              fh_1b9a0c7e5d2f483610badc0ffee1234567890abcdef1234567890abcdef123456
        remedy:
          allOf:
            - $ref: '#/components/schemas/Remedy'
          nullable: true
          description: >-
            What would make this call succeed, or `null` on an allow. Read
            `kind` to branch in code and show `summary` to a person.
    Remedy:
      type: object
      required:
        - kind
        - summary
      description: >-
        The fix for a decision that did not go through. Denials and holds carry
        one whenever the outcome has a mechanical cause; a rule written as a
        free-form condition may not.
      properties:
        kind:
          type: string
          enum:
            - lower_amount
            - raise_budget
            - reopen_budget
            - wait
            - approve
            - allow_resource
            - allow_action
            - grant_scope
            - create_policy
            - use_pinned_counterparty
            - new_spend_token
          description: >-
            What has to change. `lower_amount`, `wait` and
            `use_pinned_counterparty` are the agent's to act on unaided. The
            rest need a person to edit a policy, a budget or an agent's scopes.
        summary:
          type: string
          description: One sentence a person can act on, with amounts already formatted.
          examples:
            - A per-action cap applies. Retry at $100.00 or less.
        max_cents:
          type: integer
          nullable: true
          description: >-
            The largest amount that would have gone through. Present on
            `lower_amount`, and on `approve` where it is the amount that would
            clear without a person.
        retry_after_seconds:
          type: integer
          nullable: true
          description: >-
            How long until the limit clears, on `wait`. Null when the limit
            resets on a calendar boundary rather than a rolling window.
        resource:
          type: string
          nullable: true
          description: The counterparty to allow, or the one a spend token is pinned to.
        action_type:
          type: string
          nullable: true
          description: The action type to allow or to add to the agent's scopes.
  responses:
    AgentUnauthorized:
      description: >-
        Missing (`runtime_key_required`) or unrecognised (`invalid_runtime_key`)
        agent key.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              message: Agent key is required
              code: runtime_key_required
  securitySchemes:
    AgentKey:
      type: http
      scheme: bearer
      description: >-
        An agent API key, `krt_live_...` or `krt_test_...`, shown exactly once
        at creation. Kordio stores only a digest. This credential can ask for
        authorization and can never write policy.

````