Objects
Decision and signing
The authorizer's signed verdict, how it is signed, verified and versioned.
A decision is the authorizer’s answer to an intent. It is signed with Ed25519 over canonical JSON, and it is the only thing an executor will act on.
Verdicts
Section titled “Verdicts”| Verdict | Executable | Meaning |
|---|---|---|
ACCEPT |
yes | Every rule passed. The buying power the order needs is reserved. |
RESIZE |
yes | Accepted at a smaller qty because the mandate has allow_resize. requested_qty keeps the original. |
REJECT |
no | A rule failed. code and message say which. Signed, so the refusal is attributable. |
PENDING_APPROVAL |
no | Above approval_above_notional. Carries an approval_id. After a human approves, every rule is re-run and an executable decision is issued. |
CHECK_PASS |
no | The answer of a dry run (/v1/check). Never executable. |
Signed payload (version 2)
Section titled “Signed payload (version 2)”| Field | Notes |
|---|---|
v |
Payload version, 2 |
decision_id, intent_id |
|
verdict |
See above |
agent_id, user_id, mandate_id |
Who asked, on whose behalf, under which mandate |
instrument, side, qty, order_type, limit_price, venue |
The order exactly as it may be sent |
requested_qty |
RESIZE only |
account_id, account_ref |
The user’s account at the venue, and the venue’s own account reference passed to the adapter |
protected_unit |
The venue and product class whose rules applied |
reference_price, price_collar_bps |
Used to bound market orders |
reservation_id, reserved_notional, currency |
The hold taken on buying power |
reduce_only |
|
time_in_force |
Canonical value (post_only, not alo) |
enforcement |
Who stands between this decision and the venue. See below |
expires_at |
Short-lived. The reference implementation defaults to 30 seconds |
client_order_id |
The idempotency and reconciliation key at the venue |
approval_id |
PENDING_APPROVAL only |
code, message |
Reason for a rejection |
key_id |
Identifies the signing key |
Signing
Section titled “Signing”- Build the payload above, including
key_id. - Serialise it as canonical JSON.
- Sign the bytes with Ed25519. The signature is hex-encoded and travels next to the decision, not inside it.
Verifiers fetch public keys from GET /v1/keys, which returns entries of key_id, public_key (hex) and alg. A verifier selects the key by the payload’s key_id. An unknown key_id SHOULD trigger a bounded refresh of the key set, so keys can rotate without restarts.
Any change to a signed field makes verification fail. The executor answers A2M_BAD_SIGNATURE with HTTP 422.
Enforcement
Section titled “Enforcement”| Value | Meaning | Status |
|---|---|---|
custodial_fairx |
The executor holds the venue credential and is the only path to the venue. Enforced. | Implemented. Every decision issued today. |
client_hook |
A hook in the agent’s own client asks the authorizer first. Bypassable. | Planned |
watcher |
A read-only connection reconciles the account afterwards and flags orders with no decision. Detects, does not prevent. | Planned |
venue_verified |
The venue verifies the authorizer’s signature itself. | Planned |
An executor MUST refuse an enforcement mode it does not implement (A2M_ENFORCEMENT).
Versions
Section titled “Versions”- Adding a signed field, or changing the meaning of one, is a version bump.
- A verifier MUST refuse a version it does not know, and MUST refuse a payload that carries a field its version does not define.
v: 1(legacy) has notime_in_forceand noenforcement. It is still accepted so a decision issued before an upgrade stays usable until it expires, and executes asgtcwithcustodial_fairx. A v1 payload carrying either v2 field is rejected.v: 2(current) requires both fields.
Single use
Section titled “Single use”A decision is issued, then either consumed by the executor or voided. It cannot be sent twice (A2M_DECISION_CONSUMED). A decision that expires before it is sent is refused (A2M_DECISION_EXPIRED) and its reservation is released.
Rule pipeline
Section titled “Rule pipeline”The authorizer runs these rules in this order and stops at the first failure. Rules after kya.mandate may assume exactly one live mandate.
| # | Rule | Layer |
|---|---|---|
| 1 | kya.agent |
kya |
| 2 | kya.mandate |
kya |
| 3 | allowlist.instrument |
user |
| 4 | allowlist.venue |
user |
| 5 | market.instrument |
market |
| 6 | broker.unit |
broker |
| 7 | account.open |
account |
| 8 | market.session |
market |
| 9 | price.reference |
market |
| 10 | market.lot_tick |
market |
| 11 | market.price_band |
market |
| 12 | price.collar |
user |
| 13 | broker.notional |
broker |
| 14 | fat_finger.qty |
user |
| 15 | fat_finger.notional |
user |
| 16 | kill.halt |
user |
| 17 | direction |
user |
| 18 | position.limit |
user |
| 19 | throttle.open_orders |
user |
| 20 | throttle.rate |
user |
| 21 | buying_power |
account |
| 22 | budget |
account |
| 23 | account.cash |
account |
The authorize response carries the rule results as hits (each with rule, layer, passed, code, message), next to verdict, decision, signature and key_id.
Example response
Section titled “Example response”Shape only; identifiers are placeholders.
{ "verdict": "REJECT", "code": "A2M_FAT_FINGER_QTY", "message": "quantity exceeds the fat-finger cap", "decision": { "v": 2, "decision_id": "dec_...", "intent_id": "demo-0002", "verdict": "REJECT", "instrument": "ETH", "side": "buy", "qty": "5", "venue": "hyperliquid", "enforcement": "custodial_fairx", "time_in_force": "gtc", "code": "A2M_FAT_FINGER_QTY", "key_id": "...", "...": "..." }, "signature": "<hex>", "key_id": "..."}