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

# Record a Decision

> Records a decision with a reason code (`reviews.decide`). `waiting_on_customer` pauses the due time.
A decision your second-approver setting covers waits for a person to approve it in the portal.
A decision your **API credential** proposes that clears the subject (`not_a_match`, `no_concern`)
**always** waits for a person, whatever the setting (`approval_rule: api_clearance`), and that person
must be independent of your API credentials: not the one who created the credential, and not in a role
that can create, rotate or revoke credentials (Admin, Technical) — in practice your Compliance role.
With no such person on your team the decision is refused (`409 second_approver_unavailable`).
Approving, sending back and withdrawing are done by people in the portal.

| Review kind | Decisions |
|---|---|
| `possible_sanctions_match`, `possible_pep_match`, `screening_review` | `waiting_on_customer`, `not_a_match`, `confirmed_match` |
| `manual` | `waiting_on_customer`, `no_concern`, `concern_confirmed` |




## OpenAPI

````yaml POST /v1/intelligence/reviews/{id}/decisions
openapi: 3.1.0
info:
  title: Anton Payments API
  version: 1.0.0
  summary: Cross-border payout infrastructure. Fiat, stablecoin, and crypto.
  description: >
    The Anton Payments merchant API. Versioned, idempotent, cursor-paginated.


    - **Authentication**: OAuth 2.0 client_credentials with DPoP
    proof-of-possession (RFC 6749 §4.4 + RFC 9449). Programmatic clients
    exchange `(client_id, client_secret)` for a short-lived access token at
    `POST /oauth/token`, then send each request with `Authorization: DPoP
    <access_token>` plus a per-request `DPoP: <proof>` header. Static API keys
    are not accepted on v1.

    - Base URLs: `https://api.antonpayments.com` (production),
    `https://api.antonpayments.dev` (sandbox).

    - Monetary amounts are decimal strings. Never floats.

    - Timestamps are RFC 3339 UTC.

    - All mutating endpoints support the `Idempotency-Key` header.

    - Anton's OAuth public signing key is published at `GET
    /.well-known/jwks.json` (5-minute cache; RFC 8615).


    **Capabilities and scopes.** Every account carries a capability set
    (`payouts`, `intelligence`) and every

    credential carries a scope set validated against it at creation.
    AI-compliance-only accounts hold only

    `intelligence` — all payout-surface endpoints return `403
    capability_required` for them. A credential

    minted without a scope returns `403 insufficient_scope` on that surface.
    Effective access is

    `scopes ∩ capabilities`. Access tokens embed the grants as a space-delimited
    `scope` claim, echoed in the

    token response.


    **What machine credentials can never do.** Approval decisions (payout
    approve/reject — maker-checker

    requires a human identity) and detokenized PII readback (beneficiary PII,
    verification name reveal,

    captured-document viewing) are dashboard-only: those operations require a
    signed-in portal user with the

    right role, and every access is audit-logged. Machine credentials can
    deposit and replace PII — your

    system is its source — but can never read it back, so a leaked credential
    cannot harvest identities.


    See the Cross-Cutting section of the docs for idempotency, pagination, rate
    limits, errors, webhook events, and webhook signing.
  contact:
    name: Anton Payments Support
    url: https://help.antonpayments.com
  license:
    name: Proprietary
    url: https://www.antonpayments.com
servers:
  - url: https://api.antonpayments.com
    description: Production
  - url: https://api.antonpayments.dev
    description: Sandbox
security:
  - oauthDPoP: []
    dpopHeader: []
tags:
  - name: Authentication
    description: >-
      OAuth 2.0 token endpoint and JWKS publication. See the API description for
      the full DPoP-bound flow.
  - name: OAuth Clients
    description: >-
      Manage OAuth credentials (programmatic access). Portal-only — JWT auth
      required.
  - name: Reference Data
    description: Currencies, countries, and supported payment methods.
  - name: Beneficiaries
    description: Create and manage the people and businesses you pay.
  - name: Instruments
    description: >-
      Payment instruments attached to beneficiaries — bank accounts, wallets,
      cards.
  - name: Payouts
    description: Initiate and track payouts.
  - name: Batches
    description: CSV-driven bulk payout processing.
  - name: Balances
    description: Merchant balance by currency.
  - name: Accounts
    description: Virtual accounts for merchant funding.
  - name: Webhooks
    description: Subscribe to event notifications.
  - name: Documents
    description: Upload and manage KYB and supporting documents.
  - name: RFIs
    description: Respond to Requests for Information raised during review.
  - name: Pricing
    description: Read merchant pricing plans and quote fees.
  - name: FX
    description: Quote, lock, and execute currency exchanges between balances.
  - name: Corridors
    description: Active cross-border corridors for your merchant.
  - name: Balance Alerts
    description: >-
      Per-currency low-balance thresholds that drive the `balance.low` webhook
      event.
  - name: Sandbox
    description: Reset and seed your sandbox environment.
  - name: Merchant
    description: Merchant profile, branding, preferences, and security posture.
  - name: API Keys
    description: Issue, list, and revoke API keys.
  - name: Users
    description: Self-service profile, MFA, and team management.
  - name: Notifications
    description: Per-user notification channels and preferences.
  - name: Velocity
    description: Merchant-scoped risk rules and what-if simulation.
  - name: Engine
    description: Glass-box view of Anton's risk intelligence.
  - name: Intelligence
    description: >-
      Normalized payee, instrument, payout, and merchant risk intelligence
      evaluations.
  - name: Reviews
    description: >-
      Your team's own compliance reviews over the API, with webhooks and exports
      for your own auditors. Needs the Reviews API and Webhooks add-on; exports
      need the Audit Pack.
  - name: Verifications
    description: >-
      KYC/KYB-as-a-service — verify the identity of your customers and their
      businesses through Anton-hosted flows, with sanctions screening built in.
  - name: Onboarding
    description: Authenticated KYB onboarding draft and submission.
  - name: Public Onboarding
    description: >-
      Session-authenticated hosted onboarding flow. Not intended for SDK
      consumption.
paths:
  /v1/intelligence/reviews/{id}/decisions:
    post:
      tags:
        - Reviews
      summary: Record a decision
      description: >
        Records a decision with a reason code (`reviews.decide`).
        `waiting_on_customer` pauses the due time.

        A decision your second-approver setting covers waits for a person to
        approve it in the portal.

        A decision your **API credential** proposes that clears the subject
        (`not_a_match`, `no_concern`)

        **always** waits for a person, whatever the setting (`approval_rule:
        api_clearance`), and that person

        must be independent of your API credentials: not the one who created the
        credential, and not in a role

        that can create, rotate or revoke credentials (Admin, Technical) — in
        practice your Compliance role.

        With no such person on your team the decision is refused (`409
        second_approver_unavailable`).

        Approving, sending back and withdrawing are done by people in the
        portal.


        | Review kind | Decisions |

        |---|---|

        | `possible_sanctions_match`, `possible_pep_match`, `screening_review` |
        `waiting_on_customer`, `not_a_match`, `confirmed_match` |

        | `manual` | `waiting_on_customer`, `no_concern`, `concern_confirmed` |
      parameters:
        - $ref: '#/components/parameters/ReviewID'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ReviewDecisionRequest'
      responses:
        '201':
          description: The decision and the review after it.
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                properties:
                  data:
                    type: object
                    required:
                      - review
                      - decision
                    properties:
                      review:
                        $ref: '#/components/schemas/Review'
                      decision:
                        $ref: '#/components/schemas/ReviewDecision'
        '403':
          $ref: '#/components/responses/ModuleNotEnabled'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          description: >
            `review_closed`, `decision_pending` (a decision is waiting for
            approval), or

            `second_approver_unavailable` (nobody else on your team can
            approve).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '422':
          description: '`invalid_decision`, `invalid_reason_code` or `note_too_long`.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
      security:
        - oauthDPoP: []
          dpopHeader: []
components:
  parameters:
    ReviewID:
      name: id
      in: path
      required: true
      description: Review ID.
      schema:
        type: string
        pattern: ^irv_[a-zA-Z0-9]+$
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      description: >
        Unique key identifying this operation. Sending the same key twice
        returns the original

        response instead of creating a duplicate. Keys are retained for 24
        hours.
      required: true
      schema:
        type: string
        maxLength: 255
  schemas:
    ReviewDecisionRequest:
      type: object
      required:
        - decision
        - reason_code
      additionalProperties: false
      properties:
        decision:
          $ref: '#/components/schemas/ReviewDecisionType'
        reason_code:
          $ref: '#/components/schemas/ReviewReasonCode'
        note:
          type: string
          maxLength: 2000
          description: Stored in a vault; never returned by the API.
    Review:
      type: object
      required:
        - id
        - number
        - kind
        - priority
        - status
        - source
        - subject
        - opened_by
        - overdue
        - created_at
        - updated_at
      properties:
        id:
          type: string
          example: irv_01JB8QK2
        number:
          type: string
          example: RV-0142
        kind:
          $ref: '#/components/schemas/ReviewKind'
        priority:
          type: string
          enum:
            - high
            - medium
            - low
        status:
          $ref: '#/components/schemas/ReviewStatus'
        outcome:
          $ref: '#/components/schemas/ReviewDecisionType'
        source:
          type: string
          enum:
            - evaluation
            - manual
            - monitoring
            - list_screening
            - payout_rule
        evaluation_id:
          type: string
          description: The evaluation of yours that opened the review.
        linked_evaluation_id:
          type: string
        linked_review_id:
          type: string
        subject:
          type: object
          required:
            - type
            - display
          properties:
            type:
              type: string
              enum:
                - person
                - business
                - account
                - transaction_pattern
                - unknown
            display:
              type: string
              description: Masked (first letter of each word).
              example: V*** S***
            reference:
              type: string
              description: Your own reference for the subject.
              example: cust_7731
        open_reason:
          type: string
          enum:
            - unusual_activity
            - due_diligence_refresh
            - external_referral
            - screening_follow_up
            - other
        opened_by:
          $ref: '#/components/schemas/ReviewActor'
        owner_id:
          type: string
        due_at:
          $ref: '#/components/schemas/Timestamp'
        paused_at:
          $ref: '#/components/schemas/Timestamp'
        overdue:
          type: boolean
        closed_at:
          $ref: '#/components/schemas/Timestamp'
        created_at:
          $ref: '#/components/schemas/Timestamp'
        updated_at:
          $ref: '#/components/schemas/Timestamp'
    ReviewDecision:
      type: object
      required:
        - id
        - decision
        - reason_code
        - note_recorded
        - proposed_by
        - requires_approval
        - created_at
      properties:
        id:
          type: string
          example: ird_01JB9A
        decision:
          $ref: '#/components/schemas/ReviewDecisionType'
        reason_code:
          $ref: '#/components/schemas/ReviewReasonCode'
        note_recorded:
          type: boolean
        proposed_by:
          $ref: '#/components/schemas/ReviewActor'
        requires_approval:
          type: boolean
        approval_rule:
          type: string
          description: Why a second approver is needed.
          enum:
            - sanctions_pep
            - all
            - api_clearance
        approval:
          type: object
          required:
            - id
            - outcome
            - by
            - note_recorded
            - created_at
          properties:
            id:
              type: string
            outcome:
              type: string
              enum:
                - approved
                - sent_back
                - withdrawn
            by:
              $ref: '#/components/schemas/ReviewActor'
            note_recorded:
              type: boolean
            created_at:
              $ref: '#/components/schemas/Timestamp'
        created_at:
          $ref: '#/components/schemas/Timestamp'
    ErrorEnvelope:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - message
          properties:
            message:
              type: string
              description: Human-readable description of what went wrong.
            code:
              type: string
              description: Machine-readable error code. Branch on this, not on `message`.
              example: insufficient_balance
      example:
        error:
          message: insufficient funds for this payout
          code: insufficient_balance
    ReviewDecisionType:
      type: string
      description: >
        The first five are decisions you can record through this API. `release`
        and `cancel` are votes on a

        payout your rule held, and `approved`, `rejected` and `expired` are how
        such a review can close; both are

        made in the portal.
      enum:
        - waiting_on_customer
        - not_a_match
        - confirmed_match
        - no_concern
        - concern_confirmed
        - release
        - cancel
        - approved
        - rejected
        - expired
    ReviewReasonCode:
      type: string
      description: >
        Accepted per decision: `waiting_on_customer` — `dob_needed`,
        `id_document_needed`, `more_information_needed`;

        `not_a_match` — `dob_differs`, `nationality_differs`,
        `id_document_verified`, `different_entity_type`, `location_differs`;

        `confirmed_match` — `identity_confirmed`, `exact_identifier_match`,
        `customer_confirmed`;

        `no_concern` — `activity_explained`, `documents_verified`;
        `concern_confirmed` — `suspicious_activity`, `policy_breach`;

        `other` with any decision.
      enum:
        - dob_needed
        - id_document_needed
        - more_information_needed
        - dob_differs
        - nationality_differs
        - id_document_verified
        - different_entity_type
        - location_differs
        - identity_confirmed
        - exact_identifier_match
        - customer_confirmed
        - activity_explained
        - documents_verified
        - suspicious_activity
        - policy_breach
        - other
    ReviewKind:
      type: string
      description: >
        `payout_held_by_rule` and `payee_risk_by_rule` are opened by your own
        payout rules; their money

        decision is made on the linked approval in the portal, not through this
        API.
      enum:
        - possible_sanctions_match
        - possible_pep_match
        - screening_review
        - manual
        - payout_held_by_rule
        - payee_risk_by_rule
    ReviewStatus:
      type: string
      enum:
        - open
        - waiting_on_customer
        - awaiting_approval
        - closed
    ReviewActor:
      type: object
      required:
        - type
      properties:
        type:
          type: string
          enum:
            - user
            - api_client
            - system
        id:
          type: string
          description: The user id or your API credential's client id. Absent for `system`.
    Timestamp:
      type: string
      format: date-time
      description: RFC 3339 / ISO 8601 timestamp in UTC.
      example: '2026-04-15T14:30:00Z'
  responses:
    ModuleNotEnabled:
      description: >
        `module_not_enabled` — your account does not have the add-on this
        endpoint needs (the Reviews API and

        Webhooks add-on, or the Audit Pack for exports). Also
        `capability_required` / `insufficient_scope`

        (the `intelligence` scope) and `insufficient_permissions`.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
          examples:
            module_not_enabled:
              value:
                error:
                  code: module_not_enabled
                  message: this feature is not enabled for your account
    NotFound:
      description: The resource does not exist, or belongs to a different merchant.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
  securitySchemes:
    oauthDPoP:
      type: http
      scheme: DPoP
      description: >
        OAuth 2.0 client_credentials grant (RFC 6749 §4.4) bound to a DPoP
        keypair (RFC 9449).


        **Flow** (every authenticated `/v1` call requires both an access token
        AND a fresh per-request DPoP proof):


        1. Register a credential via the merchant portal. Anton issues a
        `client_id` (`ant_oc_<env>_<32hex>`) and a `client_secret`
        (`ant_ocs_<env>_<48hex>`, shown ONCE). The portal generates an ES256 or
        Ed25519 DPoP keypair in your browser; you store the private half.

        2. Mint an access token: `POST /oauth/token` with `Authorization: Basic
        <client_id:client_secret>` and `Content-Type:
        application/x-www-form-urlencoded`. Body:
        `grant_type=client_credentials`. A `DPoP` header carrying a proof signed
        for the token endpoint is required (no `ath` claim on this proof).

        3. Use the token: send `Authorization: DPoP <access_token>` plus a fresh
        `DPoP: <proof>` header on every `/v1` request. The proof JWT MUST carry
        `htm` (request method), `htu` (request URL, no query/fragment), `iat`
        (within ±60s), `jti` (unique within 5 min), and `ath` (SHA-256 of the
        access token, base64url).


        Tokens expire in **1 hour** in production / staging and **8 hours** in
        sandbox. There are no refresh tokens — call `/oauth/token` again with
        your secret. Anton's public signing key is published at
        `/.well-known/jwks.json`.


        **Scopes** (requested at `POST /oauth/token`, returned in the `scope`
        response field):


        | Scope | Grants |

        |---|---|

        | `payouts` | Money movement — payouts, batches, beneficiaries,
        instruments, balances, funding, FX. |

        | `intelligence` | Anton Intelligence — screening evaluations and
        evidence. |


        This scheme is declared as `http`/`DPoP` rather than `oauth2` so that
        generated examples show the correct `Authorization: DPoP <access_token>`
        header. Sending `Authorization: Bearer <access_token>` returns `401
        unauthorized` — the token is sender-constrained and the scheme is not
        interchangeable.
    dpopHeader:
      type: apiKey
      in: header
      name: DPoP
      description: >
        Per-request DPoP proof JWT (RFC 9449). MUST accompany the
        `Authorization: DPoP <access_token>` header on every protected
        operation. The proof is signed by the merchant's private DPoP key and
        carries `htm`, `htu`, `iat`, `jti`, and `ath` claims.

````