---
title: "Ask whether a payment to a counterparty should proceed."
method: POST
path: "/v1/decision"
tags: ["Decisions"]
---

# Ask whether a payment to a counterparty should proceed.

`POST /v1/decision`

## Request

- Base URL: `https://api.fidren.net`
- URL: `https://api.fidren.net/v1/decision`
- Auth: one of:
  - HTTP bearer
  - API key in header `X-API-Key`
  - none

Returns a verdict, not a number to interpret. Every call is recorded append-only with the scoring model and decision policy versions that produced it, so the answer can be reproduced during a later dispute.

**Three ways to call this, and a credential decides between them.**

- *Anonymous.* No credential: served from the free tier while the caller's daily allowance lasts. 100 decisions per caller per UTC day, reported in the `Fidren-Free-*` response headers. No account, no key, no wallet.
- *Enterprise.* A key in `Authorization: Bearer` or `X-API-Key`. A presented key is authoritative: valid means Enterprise and spends no free allowance; invalid means `401` and stops there. It never falls back to the free tier and never falls back to being charged.
- *Agent-native (x402).* Paying per decision over x402. Built and settled on Base mainnet, but **public charging is currently switched off**, so `402` is not returned in production today. See the `402` response below for the exact condition.

The engine is the same on all three. The tier affects access and billing, never the verdict, the score, the confidence or the evidence.

## Request body

- DecisionRequest
  - `address` string, required: Counterparty address, any casing.
  - `chain` 'base' | 'polygon', required
  - `amount_cents` integer, required: Intended payment, in USD-equivalent minor units.
  - `currency` 'USD' | 'USDC' | 'USDT': Settlement currency. Stablecoins are treated as 1:1 with USD.
  - `service_identifier` string: Optional caller-side identifier for the service being paid.

## Response `200`

A verdict. On the free tier the response also carries the quota headers below; on the Enterprise path it does not.

- DecisionResponse
  - `decision_id` string, required: The durable, opaque identifier of this Decision. Store it beside your own payment record: it is how a verdict is named in a later dispute, and the Portal serves it. Distinct from `request_id`, which names the HTTP call, appears on errors, and does not identify a Decision.
  - `decision` 'ALLOW' | 'ALLOW_WITH_LIMIT' | 'ALLOW_WITH_ESCROW' | 'REVIEW' | 'BLOCK', required: `ALLOW_WITH_ESCROW` and `PROCEED_WITH_ESCROW` are recommendations to the caller, not offers: Fidren provides no escrow, holds no funds, and neither moves nor signs payments. Any escrow, hold or enforcement is external to Fidren and is arranged and operated by the caller or its facilitator.
  - `trust_score` integer, required
  - `exposure_level` integer, required
  - `confidence` 'LOW' | 'MEDIUM' | 'HIGH', required
  - `recommended_limit_cents` integer, required: The exposure this counterparty’s observed evidence supports, per transaction. Derived from evidence alone: it does not move with the amount you asked for.
  - `requested_amount_cents` integer, required: The amount this decision was asked about, echoed back.
  - `amount_status` 'WITHIN_SUPPORTED_EXPOSURE' | 'AT_SUPPORTED_EXPOSURE' | 'EXCEEDS_SUPPORTED_EXPOSURE' | 'NO_SUPPORTED_EXPOSURE', required: Where the requested amount sits against `recommended_limit_cents`. `NO_SUPPORTED_EXPOSURE` means the evidence supports no exposure at all, so a smaller request would not help.
  - `payment_action` 'PROCEED' | 'PROCEED_WITH_ESCROW' | 'PROCEED_UP_TO_LIMIT' | 'REVIEW' | 'DO_NOT_PAY_AS_REQUESTED', required: What to do about this payment, as distinct from what `decision` says about the counterparty. `DO_NOT_PAY_AS_REQUESTED` withholds this payment at this size and is not a verdict on the recipient — a smaller request may be supported. Treat an unrecognised value as unhandled and fail closed. `ALLOW_WITH_ESCROW` and `PROCEED_WITH_ESCROW` are recommendations to the caller, not offers: Fidren provides no escrow, holds no funds, and neither moves nor signs payments. Any escrow, hold or enforcement is external to Fidren and is arranged and operated by the caller or its facilitator.
  - `authorized_limit_cents` integer: The exposure THIS decision authorises, per transaction — what you may actually pay. Not the same field as `recommended_limit_cents`, and the difference is the point: that one is what the counterparty’s evidence supports, this one is what the decision in front of you grants. On `REVIEW`, `DO_NOT_PAY_AS_REQUESTED` and any `BLOCK` it is `0` while `recommended_limit_cents` keeps whatever the evidence said — a `REVIEW` beside `recommended_limit_cents: 10000` supports $100 and authorises nothing until a human says otherwise. On `PROCEED` and `PROCEED_WITH_ESCROW` it is the requested amount; on `PROCEED_UP_TO_LIMIT` it is the limit. It is never greater than `recommended_limit_cents` and never greater than `requested_amount_cents`. Derived from `payment_action` and the two amounts rather than stored, so it is identical on the response that creates a decision and on the one handed back from the record after an x402 retry. **Optional, and its absence has exactly one meaning: the authorised monetary exposure cannot be derived exactly and consistently from the persisted decision.** It never means zero was authorised, and that condition is never expressed as `0` — so test for the field, and treat its absence as unknown rather than as a refusal. A decision carries this field for its whole life or for none of it. Absence arises when the stored record cannot produce the number on its own terms: a decision issued before `policy-v1.5.0`, which came from a policy that emitted no `payment_action` at all; a `payment_action` this version does not recognise; or a stored decision that contradicts its own payment action about an amount, such as a `BLOCK` beside an action that grants money. Fidren withholds the figure in those cases rather than reinterpreting an older policy through today’s mapping, which would report an authorisation the original caller was never given.
  - `currency` string, required
  - `reasons` string[], required
  - `reason_codes` object[]: The same justifications as `reasons`, as stable codes, in the same order: index i here is the code for index i there. Branch on `code` and render from `reasons` — the prose may be reworded, the codes may not. Fields appear only where the sentence carries a value, and the numbers are raw rather than formatted so you can render them in your own locale. `ADVERSE_SIGNAL` names a signal instead of restating it: its detail and value are already in `evidence[]` under the same `signal_code`. Treat an unrecognised `code` as unhandled rather than as an error. Optional, and absent on the whole life of a decision or on none of it: the field is omitted whenever an exact and complete coded set cannot be produced from the stored decision, both on the response that creates it and on the one handed back from the record after an x402 retry. Today only a decision granted a cold-start bootstrap allowance is affected, because the allowance’s two numbers are carried in its sentence and nothing stores them; no stored decision currently triggers it. An absent array means unreconstructed — never “no reasons” — and this condition is never expressed as an empty array, so test for the field rather than for its length.
    - `code` 'RECORD_CONTESTED' | 'RELATIONSHIP_RISK' | 'RELATIONSHIP_RISK_CONTESTED' | 'CONFIDENCE_LIMITED_BY_BREADTH' | 'NOTIFICATION_PENDING' | 'NOTIFICATION_UNDELIVERABLE' | 'NOTIFICATION_UNREACHABLE' | 'HIGH_SEVERITY_ON_SHORT_RECORD' | 'SHORT_RECORD_WITH_ADVERSE_EVIDENCE' | 'SHORT_RECORD_CORROBORATED' | 'BOOTSTRAP_ALLOWANCE' | 'INSUFFICIENT_OBSERVATIONS' | 'LOW_SCORE_WITH_HIGH_SEVERITY' | 'LOW_SCORE_WITH_ADVERSE_EVIDENCE' | 'LOW_SCORE_FROM_ABSENCE' | 'LIMITED_TRACK_RECORD' | 'MODERATE_TRUST' | 'ESTABLISHED_TRACK_RECORD' | 'CONSISTENT_HISTORY' | 'NO_ADVERSE_FINDINGS' | 'DOWNGRADED_NO_ADVERSE_EVIDENCE' | 'ADVERSE_EVIDENCE_CANNOT_LOOSEN' | 'AMOUNT_EXCEEDS_GRANTED_EXPOSURE' | 'NEAR_IDENTICAL_AMOUNTS_CAPPED' | 'NEAR_IDENTICAL_CONCENTRATED' | 'HIGH_SEVERITY_PATTERN' | 'PATTERN_FINDINGS_THIN_RECORD_CAPPED' | 'PATTERN_FINDINGS_EXPOSURE_REDUCED' | 'ADVERSE_SIGNAL', required
    - `exposure_level` integer
    - `max_per_transaction_cents` integer
    - `max_operations` integer
    - `signal_code` string
  - `evidence` Evidence[], required
    - `code` string, required: A labelled finding. Most codes describe the counterparty. Three do not, and are about Fidren: INDEXED_EVIDENCE_STALE, INDEXED_EVIDENCE_UNMEASURABLE and INDEXED_EVIDENCE_INCONSISTENT mean indexed transaction evidence was withheld because Fidren’s materialised corpus was behind the chain, could not be measured, or disagreed with the materialiser that produced it. When one of them is present, a NO_HISTORY alongside it reflects what was served, not what is known about the counterparty, and the decision is unknown rather than adverse — retry later.
    - `detail` string, required
    - `value` number
  - `evaluated_at` string, date-time, required: When Fidren produced this decision. Distinct from `latest_chain_observed_at`, which says how current the on-chain evidence behind it was — see that field.
  - `latest_chain_observed_at` string, date-time: When Fidren last OBSERVED a settlement belonging to the evidence this decision used — the most recent ingestion timestamp among the on-chain events in that evidence snapshot. Provenance of the corpus, NOT `evaluated_at`: a decision taken this second over evidence whose newest settlement is three weeks old carries today’s `evaluated_at` and a three-week-old value here. Read it to judge how current the assessment’s inputs were; a large gap between the two is a reason to weigh the answer differently, not an error. **This is the ingestion clock, not the block clock.** It answers *when did Fidren see it*, not *when did the chain produce it*. The two differ by the pipeline’s own latency, and this field deliberately reports the one that says how current Fidren’s knowledge is — a settlement mined an hour ago but not yet indexed cannot have informed this decision, and a field carrying its block time would imply it did. **Optional, and its absence has exactly one meaning: no on-chain observation contributed to this decision.** Three situations produce it — the counterparty has no observed settlement; no transactional evidence was served; or indexed evidence existed and was WITHHELD, which the `INDEXED_EVIDENCE_STALE`, `INDEXED_EVIDENCE_UNMEASURABLE` and `INDEXED_EVIDENCE_INCONSISTENT` evidence codes report. In that third case the field is absent rather than carrying the timestamp of data the engine was not allowed to use: a decision may not claim provenance for evidence it did not use. Absence never means “the evidence is current” and is never expressed as a zero or an epoch date, so test for the field rather than comparing its value. A decision carries this field for its whole life or for none of it. It is stored at creation and served from the record on every later read — an x402 retry or a historical lookup returns the value the original caller was given, never a recomputation against today’s corpus. Decisions issued before the field existed are served without it and are never backfilled.
  - `disputed` boolean, required: The assessed party is contesting this record, having proved control of the address. A contested record is served with confidence held back; the verdict itself is unaffected.
  - `notification` 'NOTIFIED' | 'PENDING' | 'UNDELIVERABLE' | 'UNREACHABLE', required: Whether the assessed party could be told about an adverse record. UNREACHABLE is the common case: a bare address has no contact channel, and an assessment the subject could not respond to is a weaker assessment.
  - `scoring_model_version` string, required
  - `decision_policy_version` string, required

## Other responses

- `202`: Payment submitted, settlement outcome not yet established. No decision is returned and the same challenge will not be charged again — repeat the same request with the same binding to collect the decision once settlement resolves. `status` distinguishes a broadcast awaiting confirmation from a settlement whose outcome never reached us.
- `400`: The request was rejected by validation.
- `401`: No usable access path. Either a key was presented and is not valid, or no key was presented, the free allowance is spent, and x402 charging is off. A presented key never falls back to the free tier or to being charged.
- `402`: Payment Required — the agent-native path. **Conditional, and not returned in production today.** Requires all of: no API key presented, the free allowance spent (or skipped with `Fidren-Payment-Mode: x402`), and x402 charging enabled in the deployment. Public charging is currently off, so an anonymous caller whose allowance is spent receives `401` instead — Fidren does not quote a price it is not currently taking. When it is returned, the body and the `PAYMENT-REQUIRED` header carry an x402 v2 PaymentRequired object whose `extensions` include a Fidren request binding; echo that binding back in the `PAYMENT-SIGNATURE` payload. An API key, valid or not, never reaches this path.
- `403`: The API key is valid, but its organization is not currently permitted to make this request. Do not rotate the key: it is not the problem.
- `429`: Rate limit exceeded. Retry after the number of seconds in `Retry-After`.
- `503`: A required dependency is unavailable. Safe to retry.

---

[API](https://skmtc.dev/fidren/apis/fidren-decision-api.md) · [All operations](https://skmtc.dev/fidren/apis/fidren-decision-api/llms.txt) · [OpenAPI document](https://skmtc.dev/fidren/apis/fidren-decision-api/revisions/d4b4dafcb345?raw)
