Countersignby APEX

API reference

One question before money moves: should this payment go ahead? Base URL https://apexfaucet.xyz. Answers are JSON.

Check a payment

GET  /api/countersign/check?payTo=…&chain=…&amount=…
POST /api/countersign/check        { "payTo": "…", "chain": "…", … }
Header: X-Countersign-Key: csk_…   (or Authorization: Bearer csk_…)
FieldMeaning
payToThe address you are about to pay. 0x on EVM chains, base58 on Solana and X1. Required unless requirement is given.
chainbase, ethereum, arc, bnb, solana or x1. x402 network ids such as eip155:8453 work too.
amountOptional. A plain number in the asset's units: dollars for a stablecoin. Caps need it together with asset (an official USDC, USDT or USD1 contract); without them a check under caps is REVIEW.
assetOptional. The token contract being paid. Checked against the official USDC, USDT and USD1 contracts; caps apply only to those.
payerOptional. Your paying wallet: screened too, and on Arc it enables the PayGuard voucher.
requirementOptional. An x402 payment request as it arrived: the 402 JSON body, its accepts array, one entry, or the base64 PAYMENT-REQUIRED header. Pass chain too: the entry for that chain is checked, and payTo, asset and amount are read from it. A payTo, asset or amount you also pass that contradicts it is refused.
resourceOptional. What you are paying for (a URL). The query string is dropped before it is stored.
knownOptional. Up to 50 comma-separated payees you already pay, compared for lookalikes. With a key, your book and history are used automatically.
agentOptional. A label for the agent or script, kept in your audit log.

The answer

{
  "verdict": "ALLOW",            // ALLOW | REVIEW | DENY | COULD_NOT_READ
  "verdictCode": 0,              // 0 | 1 | 2 | 3
  "summary": "No check found a reason to stop this payment. …",
  "checks": [ { "id": "sanctions", "label": "Sanctions (OFAC SDN)",
                "result": "pass", "detail": "…", "source": "…" }, … ],
  "reasons": [ { "code": "payee_known", "severity": "info", "text": "…" } ],
  "signature": { "attester": "0x…", "domain": {…}, "types": {…},
                 "primaryType": "Countersign", "message": {…}, "signature": "0x…" },
  "review": { "id": "…", "approve": "…" },   // on REVIEW with a key
  "arc": { "payeeVerdict": "…", "voucher": {…}, "payGuard": {…} },   // on Arc
  "account": { "plan": "builder", "checksLeftThisMonth": 2999 },
  "ms": 212
}
VerdictWhat to do
ALLOWNo check found a reason to stop the payment. Go ahead. Not a guarantee.
REVIEWWait for a person. With a key the payment is in your review queue; approving adds the payee to your book (clearing a new-payee review) and you check again. Other review reasons, such as a repeat, are judged afresh.
DENYDo not pay. The reasons say why.
COULD_NOT_READA source could not be read just now. Retry in a minute. Never counted against your plan.

Precedence: any deny reason gives DENY, then any unread source gives COULD_NOT_READ, then any review reason gives REVIEW, otherwise ALLOW. Info reasons never change the verdict.

Reason codes

Loaded from /api/countersign/attester, the same table the server uses. Each code is one bit in the signed reasons mask.

CodeBitSeverityMeaning
Loading…

Verify a signature

Every answer is EIP-712 typed data (domain APEX Countersign, version 2) signed by our attester, which holds no funds and never sends. Reject an answer whose expiresAt has passed (answers are valid 10 minutes), and require message.account to equal your accountHash from GET /api/countersign/account: free and x402 answers carry 0x00…00 because no rules of yours were applied to them.

import { verifyTypedData } from 'viem';
const { attester, domain, types, primaryType, message, signature } = answer.signature;
const ok = await verifyTypedData({ address: attester, domain, types, primaryType, message, signature })
  && message.account === MY_ACCOUNT_HASH && Number(message.expiresAt) > Date.now() / 1000;

No account: free and x402

GET  /api/countersign/try?payTo=…&chain=…     // free, 20 a day per address, no rules or memory
GET  /api/x402/countersign?payTo=…&chain=…    // $0.005 per check over x402

The x402 route answers 402 with a payment request in USDC on Base, Arc or Solana, or XNT on X1; pay and repeat the call with the payment header. Input that cannot be checked is refused before payment, and a check that cannot read a source is not sold. Both take the same fields as /check except agent, and neither applies an account's rules.

Account routes

All take the key header.

GET /api/countersign/accountPlan, status, accountHash (the value your signed answers carry), checks used this month, rules, approved payees, review queue.
POST /api/countersign/book{"add":[{"address":"0x…","chain":"base","label":"Hosting"}],"remove":["0x…"]}. An approval holds for that chain only. Refuses a lookalike of a payee already in the book or already paid unless the entry says "force":true.
POST /api/countersign/policy{"policy":{"maxPerPaymentUsd":250,"maxPerDayUsd":1000,"chains":["base","arc"],"reviewNewPayees":true,"allowlistOnly":false,"denylist":["0x…"]}}
POST /api/countersign/review{"id":"…","action":"approve"} adds the payee to the book; "reject" adds it to the deny list.
GET /api/countersign/logYour audit log as JSON, or ?format=csv; ?days= up to your plan's period.
POST /api/countersign/cancelCancel at the end of the paid period ({"undo":true} to keep it).
POST /api/countersign/portalA link to Stripe's billing page for invoices and card details.

Limits and errors

Errors are JSON with ok:false and a plain sentence in error: 400 bad input (nothing counted), 401 unknown key, 402 subscription inactive or the month's checks used (the answer names the x402 route), 429 too many requests (600 a minute per address with a key; free: 30 a minute and 20 a day per address, IPv6 counted per /48).

Questions: [email protected].