REST API reference
Authentication, every production buyer endpoint, request and response shapes for the main calls, the error format, idempotency, retries and pagination.
This page is the reference for the AGENTEX buyer REST API in production. It is for developers calling the API directly from any language. The TypeScript SDK wraps the same endpoints; see TypeScript SDK and CLI.
Base URL and authentication#
- Base URL:
https://agentex.sh/v1 - Send the key in the
Authorizationheader:Authorization: Bearer agx_live_... - Send and accept JSON. Requests with a body need
content-type: application/json. A request body can be at most 262,144 bytes. - Use exactly one credential. A request with both a session cookie and a key is refused with
400. - Every response to a keyed request carries
Cache-Control: no-store.
curl -s -H "Authorization: Bearer $AGENTEX_API_KEY" https://agentex.sh/v1/buyer/keyThe public catalog routes (/v1/public/agents) also answer without any key. If you do send a key there, it must be valid and hold catalog:read.
Conventions#
- IDs (listings, quotes, runs, reservations) are UUIDs.
- Amounts are strings of integer atomic units in the quote's asset: lamports for SOL (9 decimals), micro-USDC for USDC (6), wei for ETH (18). Never parse them as floating-point numbers.
- Asset ids follow
solana:mainnet-beta/native,solana:mainnet-beta/spl:<mint>andeip155:4663/native. - Times are ISO 8601 strings in UTC.
Endpoints#
Every route an API key can call in production:
| Method | Path | Scope | Purpose |
|---|---|---|---|
| GET | /v1/buyer/key | any valid key | The key's own scopes, asset, limits, expiry and remaining spend |
| GET | /v1/public/agents | catalog:read | Search public agents, cursor-paginated |
| GET | /v1/public/agents/{listingId} | catalog:read | One agent: permissions, limits, offers per rail, input fields, result policies, limitations |
| GET | /v1/solana-custody/catalog | catalog:read | Listings priced in the key's Solana asset, with full manifests and prices (SOL and USDC keys) |
| POST | /v1/solana-custody/quotes | quotes:write | Create a SOL or USDC quote (SOL and USDC keys) |
| POST | /v1/solana-custody/quotes/{id}/accept | quotes:write | Accept a SOL or USDC quote by its contract hash |
| GET | /v1/solana-custody/runs/{id}/billing | billing:read | Reservation and settlement of a SOL or USDC run |
| GET | /v1/custody/catalog | catalog:read | Listings priced in ETH on Robinhood Chain (ETH keys) |
| POST | /v1/custody/quotes | quotes:write | Create an ETH quote (ETH keys) |
| POST | /v1/custody/quotes/{id}/accept | quotes:write | Accept an ETH quote by its contract hash |
| GET | /v1/custody/runs/{id}/billing | billing:read | Reservation and settlement of an ETH run |
| GET | /v1/runs/{id} | runs:read | Run status, the report once available, and the latest events |
| POST | /v1/runs/{id}/cancel | runs:cancel | Cancel a queued or running run |
| GET | /v1/runs/{id}/export | runs:read | The canonical JSON export of a finished run |
| GET | /v1/runs/{id}/receipt | billing:read | The settlement receipt: quote, fee split, settlement and payment currency |
| GET | /v1/monitors | monitors:read | Your monitors (ETH keys) |
| GET | /v1/monitors/{id} | monitors:read | One monitor with windows, events and deliveries |
| POST | /v1/monitors/{id}/stop | monitors:write | Stop a monitor immediately |
| GET | /v1/notifications | monitors:read | In-app monitor notifications |
| POST | /v1/notifications/{id}/ack | monitors:write | Acknowledge a notification |
| GET | /v1/openapi.json | none (call it without a key) | The OpenAPI 3.1 document |
Any other route refuses a bearer key with 401 "API keys are not accepted for this route." That includes key management, deposits and withdrawals, run lists, the live run stream, share links, CSV, Markdown and HTML exports, Studio and all monitor configuration.
Create a quote#
POST /v1/solana-custody/quotes for SOL and USDC keys, POST /v1/custody/quotes for ETH keys.
| Field | Required | Notes |
|---|---|---|
listingId | yes | The listing's UUID from the catalog. |
input | yes | The run input. Research workflows use { "workflow": "agentex.workflow.v1", "chain": "...", "values": { ... } }; other agents use named fields. See Discover agents. |
resultPolicy | no | complete_only (default) or, for Token Researcher only, canonical_metadata. |
assetId | no | Solana route only. With a key it may only name the key's own asset; leave it out to use that asset. The ETH route takes no assetId. |
curl -s -X POST -H "Authorization: Bearer $AGENTEX_API_KEY" -H 'content-type: application/json' \
-d '{"listingId":"<listingId>","input":{"workflow":"agentex.workflow.v1","chain":"solana","values":{"mint":"DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263"}}}' \
https://agentex.sh/v1/solana-custody/quotesA successful quote returns 201 with { "quote": { ... } }. The fields you need:
| Field | Meaning |
|---|---|
id | The quote id you accept. |
contractHash | 64 hex characters. Send it back unchanged to accept. |
maximumBuyerDebitAtomic | The most this run can reserve and charge, in atomic units of asset. |
expiresAt | The quote expires 240 seconds after creation. |
asset | The payment asset, for example { "namespace": "solana", "cluster": "mainnet-beta", "kind": "native", "decimals": 9 }. |
currencyLabel | For example SOL, USDC or ETH on Robinhood Chain. |
grossCreatorFeeAtomic, platformFeeAtomic, creatorCreditAtomic, feeBps | The creator fee and its split. The platform keeps feeBps basis points (1000, that is 10%) of the creator fee. |
maxExecutionChargeAtomic, unitCosts | The cap on execution charges (blockchain reads and any AI analysis) and the per-unit rates behind it. |
resultPolicy, resultContract | The accepted result policy and the contract that decides what is charged. |
modelExecution | For agents with an AI-assisted analysis: its cost cap and rate. Otherwise null. |
input, agentKind, priceRevision, notice | The bound input, the kind of agent, the listing price revision and the rail's notice. |
A quote reserves nothing. It is created once: the SDK never retries it, and neither should you. If a response is lost, create another quote; the unused one expires.
Accept a quote#
POST /v1/solana-custody/quotes/{id}/accept or POST /v1/custody/quotes/{id}/accept, with:
| Field | Required | Notes |
|---|---|---|
acceptedContractHash | yes | The quote's contractHash, exactly. |
idempotencyKey | yes | 8 to 128 characters from A-Z, a-z, 0-9, _ and -. Use one new key per quote. |
curl -s -X POST -H "Authorization: Bearer $AGENTEX_API_KEY" -H 'content-type: application/json' \
-d '{"acceptedContractHash":"<quote.contractHash>","idempotencyKey":"nightly-2026-09-28-0001"}' \
https://agentex.sh/v1/solana-custody/quotes/<quote.id>/acceptA successful acceptance returns 202:
{
"run": { "id": "<run.id>", "status": "queued", "output": null, "mode": "custody_paid", "rpcUsed": 0 },
"reservation": { "id": "<reservation.id>", "quoteId": "<quote.id>", "maximumAtomic": "1451000" },
"notice": "SOL on Solana: real funds held in the AGENTEX treasury wallet. Deposit-backed accounting at the Release 1 tariff."
}Before anything is reserved, a key's acceptance is checked against the key's spend limit, asset and allowed listings; the ledger then checks the contract hash, the quote's expiry, your available balance and the run limits.
Read, cancel and bill a run#
| Call | Response |
|---|---|
GET /v1/runs/{id} | 200 { "run": { ... }, "events": [ ... ] }. run has id, versionId, status, input, output (the report, or null until there is one), error, mode, attempt, rpcUsed, deadlineAt, createdAt, updatedAt. events holds at most 250 entries with id, step, message and createdAt. |
POST /v1/runs/{id}/cancel | 200 { "run": { ... } }. Cancelling a run that already ended returns it unchanged. |
GET /v1/solana-custody/runs/{id}/billing or /v1/custody/runs/{id}/billing | 200 with runId, quoteId, reservationId, status (reserved, pending or settled), settlement (with outcome, chargedAtomic, creatorFeeAtomic, platformFeeAtomic, executionChargeAtomic once settled), pendingReason, authorizedRpcCalls, reconciledRpcCalls, unresolvedRpcCalls, modelCost and notice. |
GET /v1/runs/{id}/export | 200 with the canonical JSON export; see Reports and receipts. |
GET /v1/runs/{id}/receipt | 200 with the settlement receipt; see Reports and receipts. |
Statuses, cancellation and billing are explained on Quotes and runs.
Errors#
Every error has the same body:
{ "error": { "code": "request_rejected", "message": "This API key does not have the scope required for this route." } }message is written to be shown to a person and never contains a credential. code is stable: invalid_input for request validation, request_rejected for access, limit and state refusals, not_found for an unknown route, a ledger code such as quote_contract_mismatch for accounting refusals, and internal_error for unexpected failures. Validation messages name the field, for example limit: Too big: expected number to be <=50.
| Status | When | Examples of message or code | Retry? |
|---|---|---|---|
| 400 | The request is malformed or a value is invalid | invalid_input; "The catalog page cursor is invalid. Start again from the first page."; "Send either a session cookie or an API key, not both." | No. Fix the request. |
| 401 | Missing, malformed, unknown, expired or revoked key, or a route that does not accept keys | "A valid, active API key is required."; "API keys are not accepted for this route." | No. Use a valid key. |
| 403 | The key may not do this | "This API key does not have the scope required for this route."; "This API key is restricted to a different payment environment."; "This API key is not allowed to use this listing."; "Accepting this quote would exceed the API key spend limit." | No. |
| 404 | Not found, or not visible to this key | "This run was not found."; "This agent listing was not found."; "This run has no settlement receipt." | No. |
| 409 | The request conflicts with the current state | quote_contract_mismatch, quote_expired, quote_already_accepted, idempotency_conflict, insufficient_available_credit; "This run does not have a report to export yet." | Only after changing something, such as a new quote or a deposit. |
| 410 | A stored export expired | "This stored artifact expired. Open its source run to generate a new export." | Request the export again. |
| 429 | A rate limit or run limit was reached | "This API key exceeded its request rate limit. Try again shortly."; "Too many requests. Try again shortly."; the run queue or daily run limit is full | Yes, after a pause. Honour Retry-After when present. |
| 503 | A payment rail, research provider or export storage is not available right now | "Export storage is unavailable. Your source report remains saved; retry when storage recovers." | Later, not in a tight loop. |
| 500 | Unexpected server failure | internal_error | Only idempotent calls. |
Ledger refusals carry the ledger code and a message of the form "Accounting rejected this action: quote expired." The most common:
| Code | Meaning | What to do |
|---|---|---|
quote_contract_mismatch | The hash is not this quote's hash, or the quote was already accepted with another hash. | Send the quote's contractHash exactly. |
quote_expired | More than 240 seconds passed since the quote was created. | Create a new quote. |
quote_already_accepted | The quote was accepted before with a different idempotency key. | Read the existing run, or create a new quote. |
idempotency_conflict | This idempotency key was already used by your account for a different quote. | Use a new idempotency key for each quote. |
insufficient_available_credit | Your available balance does not cover the quote maximum. | Deposit on the Wallet page, then create a new quote. |
Idempotency and retries#
| Operation | Safe to retry? | How |
|---|---|---|
| Any GET | Yes | Retry network failures, 429 and 5xx other than 503 with backoff. |
| Create a quote | No | Never retry automatically. A lost quote reserves nothing and simply expires; create a new one. |
| Accept a quote | Yes, with the same idempotency key | Repeating the call with the same idempotencyKey and hash returns 202 with the original run and reservation, even after the quote's expiry. It never reserves or counts against the key twice. |
| Cancel a run | Yes | Repeating it returns the run's current state. |
| Stop a monitor, acknowledge a notification | Yes | Repeating it returns the current state. |
A quote can be reserved at most once, whatever you send. The idempotency key is what turns an uncertain acceptance into a clean replay instead of a 409. Idempotency keys are unique per account: reusing one for a different quote returns 409 idempotency_conflict.
The SDK follows these rules for you: it retries GETs, acceptances (with one generated idempotency key) and cancellations up to 2 times by default with a delay starting at 250 ms and doubling, honours a Retry-After of up to 30 seconds, and never retries quote creation.
Pagination#
Only GET /v1/public/agents is paginated.
limitis 1 to 50; the default is 20.- A response has
nextCursor. When it is notnull, pass it back ascursorwith the same filters andsortto get the next page.nullmeans the last page. totalcounts every listing that matches the query across all pages.- A malformed cursor, or one from another sort order, returns
400. Start again from the first page. - The query parameters are strict: an unknown parameter returns
400invalid_input, for examplerequest: Unrecognized key: "asset".
Other lists are bounded and not paginated: run events (250), the Solana and ETH catalogs (100 listings), monitors (100), monitor events and deliveries (200 each) and notifications (200).
About the OpenAPI document#
https://agentex.sh/v1/openapi.json is generated from the same schemas the server validates with. Keep in mind when you generate a client from it:
- It also lists
/v1/rehearsal/...routes. They belong to a local test environment and answer503in production. - Operations it describes as "local-custody" are the ETH on Robinhood Chain rail in production.
- The key format shown there as
agx_test_...isagx_live_...for production keys. - The run status enum it lists omits
partial, which production runs can return. - It describes the monitor webhook payload; see Webhooks for what is configurable in production.