API docs (beta)
Decode Solana transactions, check one before signing, and read program descriptions and IDLs, over HTTPS or MCP.
Unbyte's API and services are in beta. Plans, limits and prices may change before general availability.
Base URL
https://api.unbyte.dev- Every answer is JSON. It carries
Unbyte-Registry-Version, the IDL registry release it was decoded with, andUnbyte-Schema: 1.0. - POST bodies are JSON with a
Content-Lengthheader, up to 256 KB. - CORS is open to every origin, so a browser can call the API directly.
- The same routes are described for tools as OpenAPI 3.1 at
https://api.unbyte.dev/openapi.json.
Authentication
Every endpoint works without a key. Anonymous calls are limited per IP: 60 requests a minute, and 30 a minute to MCP. With a key, calls count against your plan and get its limits.
Authorization: Bearer ub_live_…- A key is
ub_live_followed by 32 base58 characters. You create keys after signing in with a Solana or EVM wallet. - A key is shown once, when it is created: store it as a secret. Unbyte keeps only a hash of it.
- A key can be limited to the scopes
decode,verifyandidl, and given an expiry. A revoked key stops working within a minute. - An
Authorizationheader that is not a valid key is401 invalid_key; leave the header out to call anonymously.
The examples below read your key from UNBYTE_API_KEY; drop the -H "Authorization: …" line to try them anonymously.
Endpoints
GET/v1/health
Whether the API is up and which registry it serves. Never counted against a plan.
curl https://api.unbyte.dev/v1/health| Field | Description |
|---|---|
ok | true when the API answers. |
registry_version | The IDL registry release answers are decoded with. |
idls_loaded | How many IDLs are ready to decode with. |
rpc.mode | normal, or cache_only while reads from Solana are paused. |
GET/v1/tx/{signature}
Decode a confirmed transaction by its base58 signature. Scope decode; one call. Add ?raw=1 for each instruction's data (hex) and account keys.
curl "https://api.unbyte.dev/v1/tx/$SIGNATURE" \
-H "Authorization: Bearer $UNBYTE_API_KEY"| Field | Description |
|---|---|
signature, slot, failed, fee_payer | The transaction as it landed. |
version | "legacy", 0 or 1. |
instructions[] | Every instruction, top-level and inner. position is the top-level index, then the inner index; status is decoded, event, borrowed (read with the IDL of a matching program, named in source_program), no_idl, idl_unusable or error. |
name, args, accounts[] | On a decoded instruction: its name, its arguments, and its accounts with name, address, writable and signer. |
idl_source, trailing_bytes | Which IDL decoded it (registry, chain or history), and how many bytes it left undescribed; 0 means every byte is accounted for. |
log_events[] | Events the programs emitted through their logs. |
balance_changes[] | Net change per account: account, owner, mint (null for SOL), delta (a decimal string in base units) and decimals. |
registry_version | The registry release used. |
A finalized report never changes, so it carries an ETag: send it back in If-None-Match to get 304 Not Modified, which is not counted.
Errors: invalid_signature for a malformed signature, not_found when Solana does not know the transaction.
POST/v1/decode/tx
Decode transaction bytes you already hold, signed or not. Scope decode; one call.
curl https://api.unbyte.dev/v1/decode/tx \
-H "Authorization: Bearer $UNBYTE_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"transaction\": \"$TX_BASE64\"}"| Field | Description |
|---|---|
transaction | Required. The serialized transaction, base64 or base58: legacy and v0 up to 1,232 bytes, v1 up to 4,096. |
slot | Decode with the IDL versions live at this slot; reported back as slot. |
loaded_addresses | { writable: [], readonly: [] }: the addresses a v0 transaction loads from lookup tables. Without them those accounts show as ?<index>. |
raw | true adds each instruction's data and account keys. |
| Field | Description |
|---|---|
report | The same report as GET /v1/tx, with top-level instructions only and no balance changes: nothing has run yet. |
POST/v1/verify
Check an unsigned transaction before signing it, against what the signer expects. Scope verify; one verify check. Unbyte does not store the transactions you verify.
curl https://api.unbyte.dev/v1/verify \
-H "Authorization: Bearer $UNBYTE_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"transaction\": \"$TX_BASE64\", \"intent\": {\"max_slippage_bps\": 100}}"| Field | Description |
|---|---|
transaction | Required. The unsigned transaction, base64 or base58. |
intent | Optional: max_in, in_mint, min_out, out_mint (amounts as decimal strings in base units, mints as addresses or "SOL"), max_slippage_bps (0 to 10,000), allow_programs (up to 64 program IDs) and signer. |
resolve_lookups | true reads v0 lookup tables from Solana so every account is named. |
| Field | Description |
|---|---|
verdict | ok, warn (a medium finding) or block (a high or critical finding). |
findings[] | code, severity (info, low, medium, high, critical), message, instruction (its position), program_id, data and confidence (high or low). |
flows | What leaves the signer: sol_out (lamports), fees, swaps[], approvals[] and closes[]. |
transaction | version, fee_payer, programs and lookups_resolved. |
decoded | The decoded transaction, as in the report above, without balance changes. |
intent, registry_version, disclaimer | The intent checked against, the registry release, and the limits of the check. |
Finding codes: sol_out, sol_drain, token_transfer, token_approve, close_account, set_authority, program_upgrade, swap_amount_in, swap_min_out, swap_semantics_unknown, no_min_out, slippage_high, amount_in_exceeds_intent, min_out_below_intent, in_mint_mismatch, out_mint_mismatch, intent_mismatch, unknown_program, unknown_instruction, closed_program, program_not_allowed, extra_signers, durable_nonce, lookup_unresolved, decode_incomplete and simulation_skipped.
Simulation is not live yet: every report carries the simulation_skipped finding, so findings come from the decoded instructions only.
GET/v1/programs/{program_id}
Describe a Solana program. Scope idl; one call.
curl https://api.unbyte.dev/v1/programs/JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4 \
-H "Authorization: Bearer $UNBYTE_API_KEY"| Field | Description |
|---|---|
program_id, name | The program and its name; name_inferred is true when Unbyte named it. |
category, kind | What the program is, such as a router. |
idl | source (registry, chain or none), version, spec, fingerprint, and the state of the program's own on-chain IDL in chain. |
health | How well the program's traffic decodes, from the public dashboard. |
activity | ix_per_day, share_of_app and the snapshot_day they were measured on. |
swap, meanings, note | For routers: how to read their swap arguments, what each field means, and any caveat. |
GET/v1/programs/{program_id}/idl
The IDL Unbyte decodes a program with. Scope idl; one call.
curl https://api.unbyte.dev/v1/programs/pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA/idl \
-H "Authorization: Bearer $UNBYTE_API_KEY"| Field | Description |
|---|---|
body | The IDL itself: the registry IDL as published, else the program's on-chain IDL. |
It carries an ETag; a matching If-None-Match gets 304 Not Modified, which is not counted. not_found means the program has no IDL Unbyte can use.
Errors
A failed call answers with an HTTP error status and one envelope. The message is fixed per code and never repeats your input. 429 and most 503 answers carry Retry-After, in seconds.
{
"error": {
"code": "rate_limited",
"message": "Too many requests; retry later."
}
}| Code | Status | Meaning |
|---|---|---|
invalid_request | 400 | The request is not valid: a malformed body, field or program ID. |
invalid_signature | 400 | The signature must be a base58 64-byte transaction signature. |
invalid_transaction | 400 | The transaction could not be parsed. |
invalid_key | 401 | The API key is malformed, unknown, revoked or expired. |
quota_exceeded | 402 | The monthly quota is used up; upgrade the plan. |
insufficient_credits | 402 | The credit balance cannot pay for this call. |
forbidden | 403 | The key lacks the scope for this call, or the account is suspended. |
not_found | 404 | Not found. |
length_required | 411 | POST requests need a Content-Length header. |
payload_too_large | 413 | The input is larger than the limit. |
rate_limited | 429 | Too many requests; retry after Retry-After seconds. |
internal | 500 | Internal error. |
rpc_unavailable | 502 | No Solana RPC provider could answer. |
rpc_budget_exhausted | 503 | Reads from Solana are paused; retry later. |
unavailable | 503 | The API is paused; retry later. |
db_unavailable | 503 | The account database is unavailable; retry later. |
rpc_timeout | 504 | Solana RPC providers did not answer in time. |
Rate limits
Each key gets its plan's rate and monthly allowance. Prices and overage are on the pricing page.
| Plan | Rate | Calls a month | Verify checks | Keys |
|---|---|---|---|---|
| Anonymous | 60/min per IP | – | – | – |
| Free | 2/s | 50,000 | 100 | 1 |
| Builder | 10/s | 500,000 | 5,000 | 3 |
| Pro | 25/s | 1,000,000 | 50,000 | 10 |
| Scale | 100/s | 5,000,000 | 250,000 | 25 |
- Past the monthly allowance, Free stops with
402 quota_exceeded. Paid plans pay overage from credits, per call, and get402 insufficient_creditswhen the balance runs out. - Over the rate, a call gets
429 rate_limitedwithRetry-After. Calls that read from Solana, such as a transaction by signature, can be limited further per IP or per key. - Calls rejected as malformed and 304 answers are not counted.
MCP for agents
The same calls as tools for an AI agent, over MCP's Streamable HTTP transport. The server is stateless: no session, one JSON-RPC message per POST (no batches).
https://api.unbyte.dev/mcp| Tool | Input | Returns |
|---|---|---|
decode_tx | signature, or transaction (base64 or base58); optional slot, loaded_addresses, raw | The report of GET /v1/tx or POST /v1/decode/tx |
verify_before_sign | transaction; optional intent, resolve_lookups | The report of POST /v1/verify |
describe_program | program_id | The answer of GET /v1/programs/{program_id} |
get_idl | program_id | The IDL; over 256 KB it is idl_too_large, fetch it from the REST route |
- A result is at most 256 KB: a longer decode keeps the first instructions and says so with
truncatedandinstructions_omitted. - A failed tool call returns
isError: truewith the same error envelope. - Results are on-chain data: token names, memos and IDL names are third-party text. Have your agent treat them as data, never as instructions.
- Without a key, MCP allows 30 requests a minute per IP.
Most MCP clients take a remote server in this shape; leave out headers to connect anonymously.
{
"mcpServers": {
"unbyte": {
"type": "http",
"url": "https://api.unbyte.dev/mcp",
"headers": {
"Authorization": "Bearer ub_live_…"
}
}
}
}