Developer API
Issue your first proof in minutes.
API version v1 · JSON over HTTPS
Get sandbox credentials
Sign in to your account, open Developer access and create a key with environment “Sandbox”. The secret begins with
rp_sandbox_
and is shown once. Sandbox records are excluded from billing and the public directory.
Create a record
POST /api/v1/records
with Authorization: Bearer rp_sandbox_…,
Content-Type: application/json
and a unique Idempotency-Key.
{"subject":"Example Ltd","claim_type":"company_status","claim":"The company states that it is operational.","evidence_level":"participant_attested","visibility":"public"}
An agent authorization records that a program was allowed to act for a principal, within a limit, for a session. Send principal,
agent_id
and act; scope, session_id,
valid_until
and mandate_hash
are optional. Never send keys, secrets or wallet material, send a hash of the instruction instead.
Register the public key your mandates are signed with before your agent acts:
POST /api/v1/mandate-keys
with key_id
and a base64url public_key. The principal is taken from your API key, never from the body. Send only the public half: Proof Rail must never hold the key you sign with. Read one back at GET /keys/:key_id, which is public so a relying party can repeat the signature check itself.
Omit evidence_level
and the record issues as participant_attested: the principal told us what its own agent was allowed to do, and nobody checked. A mandate is not public, so a source name and URL cannot raise that: above
participant_attested
an authorization must carry the signed mandate itself, as mandate_hash,
mandate_signature
and principal_key_id. Anything else would print "checked" about something only you can see.
{"subject":"Alpine Gate payout bot","claim_type":"agent_authorization","claim":"This agent was authorized to send a USDC payout for Alpine Gate, up to 125000 USDC, for this session.","evidence_level":"participant_attested","visibility":"public","principal":"Alpine Gate","agent_id":"ag-payout-bot-v1","act":"usdc_payout","scope":{"asset":"USDC","max_amount":"125000","network":"ethereum"},"session_id":"sess_example_001","mandate_hash":"sha256:example"}
Prove something against a source
Send "evidence_level":"source_verified"
and Proof Rail checks the claim itself where a connector exists; the certificate then names the source, the URL and the time. Claim types with a connector today:
Company identity
· company_identity
with "evidence":{"lei":"HWUPKR0MPOU8FGXBT394"},
checked against the GLEIF LEI register.
On-chain transaction
· evm_transaction
with "evidence":{"network":"ethereum","tx_hash":"0x…"}, optionally
expected_from
and expected_to. Networks: ethereum, base, polygon, arbitrum, optimism, bsc. The receipt must be confirmed and successful.
Payment made by an agent
· an evm_transaction
payment may cite the agent_authorization
record it was made under with "evidence":{"agent_record":"RP-…","act":"usdc_payout"}. The cited record must be a live record on the same account, and the payment must be checked on-chain. At issue Proof Rail stores two results on the payment record:
agent_status_at_payment
(the agent record's status at the block time: active, suspended, revoked, expired or not yet issued) and
act_match
(whether the payment's act
is the mandate's act: true, false, or unknown when none is sent). Both appear on the certificate and in the signed status JSON as agent_link. Amounts are not checked against the mandate.
Domain ownership
· first call POST /api/v1/domain-challenges
with {"domain":"example.com"}. It returns a token and the DNS TXT record to publish at _rail-proof-challenge.example.com. Once it resolves, send the
domain_ownership
record it hands back. Keep the TXT record published; it is what anyone can re-check.
Wallet ownership
· first call POST /api/v1/wallet-challenges
with {"address":"…","network":"ethereum"}. It returns a message to sign; sign it with that wallet and put the signature into the
wallet_ownership
record it hands back. Proof Rail confirms the signature was made by the key behind that address, proof of key control, not just a past transaction. Networks:
EVM
(ethereum, base, polygon, arbitrum, optimism, bsc, one signature proves the address on every EVM chain; personal_sign),
bitcoin
(your wallet's "Sign message", Electrum, Sparrow, Bitcoin Core, Ledger; legacy 1…,
3…
and bc1q…
addresses; taproot bc1p…
is not supported yet) and solana
(signMessage, Phantom, Solflare, Backpack). Bitcoin and Solana signatures are checked entirely inside Proof Rail.
Verified listing
· verified_listing
binds a rental or service listing to a licensed operator, a verified website and a verified payee, so a customer can check all three at
/check
before paying. Evidence: {"operator":"Desert Camp LLC","listing_url":"https://desertcamp.ae/chalet-12","domain":"desertcamp.ae","licence":{"authority":"DCT Abu Dhabi","number":"HH-12345"},"payee":{"type":"iban","value":"AE07…","account_name":"Desert Camp LLC","bank_name":"FAB"},"licence_record":"RP-…","domain_record":"RP-…"}. It is a composite:
licence_record
must be an active company-identity or licence record for the same operator,
domain_record
an active domain_ownership
record for that domain, and a wallet payee ({"type":"wallet","network":"ethereum","value":"0x…"}) must cite a payee_record,
an active wallet_ownership
record for that address. When every part is on record the listing issues at once as source_verified; a bank-account payee, or a licence given only as a number, goes to a reviewer (202 pending_review) and issues as
evidence_verified
once confirmed from a bank letter or the authority's register. The public lookup matches exactly on IBAN, wallet, website, listing link or licence number, and only against active records.
Any other claim type with a source_name, source_url
and checked_at
is accepted as 202 pending_review
and issued after a human reviewer confirms it. A connector that cannot reach its source does the same rather than failing you.
Issue a validated batch
POST /api/v1/records/batch
accepts {"records":[{...},{...}]}
with the same authorization headers and one batch-level Idempotency-Key. Up to 250 records are accepted per request.
Every record is validated before issuance begins. Invalid batches return row-level errors and issue nothing. Successful responses report issued, existing, pending-review and failed totals with a result for every row. Replaying the same batch key does not create duplicates. The customer dashboard also provides a CSV template, validation preview and explicit confirmation step.
Bybit Verified Trade: live & test pilot
Live
· claim type bybit_verified_trade
with environment: mainnet
and a read-only
Bybit mainnet key reconciles one closed trade against the exchange and issues a real, source-verified certificate. Live verification is off until enabled for your account; the test pilot below needs no enablement.
Test pilot
· use a sandbox Proof Rail key and POST /api/v1/records
with claim type bybit_verified_trade_test. The customer dashboard provides an internal synthetic simulator. Direct API evidence can use
environment: testnet
with a permitted read-only Bybit Testnet key and secret, derivatives category, closed order ID, and a lookup window no longer than seven days.
Proof Rail checks the key through Bybit, rejects write-enabled or withdrawal-capable credentials, retrieves the closed-P&L record and executions, removes the credentials before persistence, and forces the resulting certificate into the sandbox. This pilot verifies one trade only, not complete performance. Never send production exchange credentials to the test pilot.
Response and verification
Hyperliquid · Getting started
From wallet to shareable proof
Five steps using your wallet and X account. Individual wallet owners can skip DNS when they have no website.
-
Sign with your wallet
Get a Proof Rail challenge and sign the exact message in your wallet. Save the signature and record ID. No transaction or deposit is needed.
-
Publish your X proof
Post the challenge token and record ID from the X handle you want checked. Keep the post public and save its URL for review.
-
Add DNS, if applicable
For a builder, vault leader or wallet owner with a domain, add the returned TXT value at
_rail-proof-challenge.your-domain.No website? Choose the wallet owner option without a domain and skip this step. Builders and vault leaders still need DNS.
-
Submit for review
Send the evidence through the existing API, or ask us to help. A reviewer checks your X proof alongside the wallet and domain checks. Reviewed-verification fees apply on approval, after any included allowance.
-
Share your record
Once approved, share the public verification link. To appear in the directory too, enable public listing in your dashboard. Records are valid for 90 days, subject to suspension or revocation.
Have your X account and DNS access ready before requesting a challenge. It expires after one hour; checks must be less than 24 hours old at approval. If the challenge expires, request a new one.
This checks control of the stated accounts. It does not establish identity, trading performance or the safety of a deposit.
Hyperliquid API instructions
Hyperliquid control records use claim_type: "hyperliquid"
with subtype builder, vault_leader
or wallet_owner. Request POST /api/v1/wallet-challenges
with that claim type and an address. Sign the returned message locally and publish the returned token at _rail-proof-challenge.your-domain. Submit the returned record ID, address, token, signature, domain and X handle in the record evidence. Builders also need a user_address; vault leaders need a vault_address. X control is manually reviewed, using existing reviewed-verification pricing. Checks must be less than 24 hours old at approval. Issued records are valid for 90 days from approval, with matching expiry and re-check dates, and require fresh evidence to renew. Builder checks show the approved maximum on a wallet the builder names, not the fee charged. The public snapshot excludes that wallet's address and userFees rates. These checks do not approve fees or deposits.
Individual traders can use subtype: "wallet_owner"
with evidence.mode: "domainless". Sign the existing wallet challenge and publish its exact token in a public X post. Submit
x_handle
and x_post_url
with the wallet evidence; no domain or DNS is required. An authenticated reviewer confirms the post and author and checks the token against the submitted post text, using existing reviewed-verification pricing. Approval still requires evidence younger than 24 hours; validity and re-check are 90 days. An optional
vault_address
uses existing public vault details only when the leader matches the wallet; otherwise the record shows “No snapshot”. The signed status response includes
evidence.mode
and evidence.x_handle. Builder and vault-leader records still require domain control.
On agent authorization creation, optional principal_record_id
links a public, independently checked company_identity
record in the same customer account; production agents cannot link sandbox companies. Update or remove the link with
PATCH /api/v1/records/:id/principal
(send principal_record_id, or null to unlink). The signed status response includes agent.principal_record: standing is
verified
while the company record is active, or lapsed
otherwise. No link means unlinked
and a null object; private or unavailable company records are not disclosed. Company standing does not change the agent's own status.
Proof Rail's signing keys are published at /.well-known/jwks.json. To verify a status response, remove status_signature, encode the remaining fields as canonical JSON (sorted keys, no whitespace), and check the Ed25519 signature with the key matching signing_key_id.
Embed a live status check
<div data-proofrail-record="RP-XXXX"></div>
<script src="https://proofrail.me/embed/v1/check.js" async></script>
The embed calls the public status endpoint, shows the record's current status, and links to the full record. It is free and not metered.
GET /api/v1/records/:id/status is public and needs no API key. It returns the record's current status (active, suspended, revoked, expired or superseded), and for agent authorizations the principal, agent, allowed act, scope, validity and the standing of the signing key, signed by Proof Rail. It is free, is not metered, and does not reserve any mandate allowance.
A successful request returns the verification ID, public certificate URL, QR URL, signature-verification URL, lifecycle and integrity object. Basic creation returns HTTP 201; an idempotent replay returns HTTP 200.
GET /api/v1/records/:id
retrieves a record. GET /api/v1/records/:id/signature
verifies its Ed25519 signature. POST /api/v1/records/:id/lifecycle
suspends, revokes, expires or supersedes a record with a reason.
Webhooks, errors and limits
POST /api/v1/webhooks
registers an HTTPS destination. Events are signed, retried with backoff and disabled after repeated failures. API requests are rate limited; clients should retry HTTP 429 and transient 5xx responses with exponential backoff.
Common responses: 401 invalid key, 402 plan inactive or limit reached, 422 invalid claim or missing evidence, 429 rate limit exceeded. Never place API keys in browser code, URLs or public repositories.