DevelopersHTTP and JSON
The API behind every swap.
The front end hides your backend behind one interface. This page documents that contract: the JSON endpoints for currencies, quotes and orders, the Order object and the error codes the site understands. Point the config at your backend and the five agents run on it.
Overview
One interface, one backend
Ghostex is a static front end. Everything that touches money, prices and orders lives in your backend, and the site reaches it through a single JSON API. This page is that contract. It documents exactly what the front end sends and what it expects back, so your backend can drive the five agents without any change to the site.
- You bring the backend. The reference client in
assets/js/api.jstalks to it. With no backend configured, the site runs a built-in mock or a local wallet preview, so pages work while you build. - The agents map to the flow. Oracle quotes, Router routes, Sentinel watches the chain, Executor swaps and Courier delivers. Your backend fills the Order object; the agent tags are just labels on the events it returns.
- No account model. There is no login, no user object and no session. An order is a standalone record with its own ID, and the browser is the only place a visitor keeps a list of them.
This documentation is for integrators building the exchange backend. Using the API means accepting the API Terms of Use. Anything you need that is not described here is available on request at support@ghostex.ai.
Configuration
Base URL and config
Set your backend in assets/js/config.js. The site appends the paths below to it, verbatim.
api.baseUrlis your API root, for examplehttps://api.ghostex.ai/v1. Trailing slashes are removed, then each path is appended. Leave it empty to run the local mock or wallet preview.api.timeoutMsaborts a request that takes too long (default 15000).api.pollMsis how often the order page refreshes an open order (default 5000).api.headersis an optional map of extra request headers. It is public, because it ships in the page source, so never put a secret in it.
CORS and CSP. The browser calls your API directly from the site origin, so your API must answer the OPTIONS preflight and return Access-Control-Allow-Origin for your site. You also add the API origin to connect-src in the site headers, or the browser blocks the call.
Requests
Conventions
Every request and response is JSON, and a few rules hold across all endpoints.
- JSON in, JSON out. Requests send
Accept: application/json, plusContent-Type: application/jsonon a POST, plus anything inapi.headers. - No cookies. Requests are sent with
credentials: 'omit'andcache: 'no-store', and are aborted afterapi.timeoutMs. - Authentication. The reference client sends no credentials of its own. If your integration needs keys, they travel in the public
api.headers, so treat them as non-secret. Arrange anything stronger with us at support@ghostex.ai. - Amounts and times. Amounts are JSON numbers in whole units of the asset, so 0.1 BTC is
0.1. Times are milliseconds since the Unix epoch. - Currency codes. Use the
codevalues fromassets/js/currencies.js, currently 43 assets across 31 networks. Keep the two lists in sync. The Supported assets page shows every code.
Errors
How failures are reported
Any non-2xx response is an error. Send a body the front end can show and, when it fits, point it at the field to highlight.
{ "error": { "code": "ADDRESS", "message": "This is not a valid Ethereum address.", "field": "address" } }
message is shown to the visitor, so write it for people. field is optional and highlights an input: from, to, amount, address, extraId, refundAddress or refundExtraId. A plain { "code", "message" } or { "error": "message" } body works too.
NOT_FOUND and any other status becomes HTTP.
A problem with the amount or pair on a quote is not an HTTP error. Return 200 with error set to MIN, MAX, SAME, PAIR or RATES, plus whatever figures you can compute. Keep non-2xx for malformed requests.
Endpoints
The four core endpoints
Paths are appended to api.baseUrl. A fifth endpoint exists for staging only, described at the end.
Returns the asset list, either a JSON array of Currency objects or { "currencies": [...] }. Use the same shape and the same codes as currencies.js. The site builds its pickers from the local list, so keep availability (send and receive) in sync.
{
"currencies": [
{ "code": "BTC", "symbol": "BTC", "name": "Bitcoin", "network": "Bitcoin",
"networkCode": "BTC", "chain": "bitcoin", "decimals": 8, "confirmations": 2,
"send": true, "receive": true },
{ "code": "USDTTRC", "symbol": "USDT", "name": "Tether", "network": "Tron",
"networkCode": "TRC20", "chain": "tron", "decimals": 2, "confirmations": 20,
"extraId": null, "send": true, "receive": true }
]
}
direction is from when amount is what the user sends, or to when it is what the user wants to receive. type is fixed (rate locked for the order window) or float (market rate at execution). An amount of 0 asks only for the pair limits.
{ "from": "BTC", "to": "XMR", "amount": 0.1, "direction": "from", "type": "fixed" }
{
"from": "BTC", "to": "XMR", "type": "fixed", "direction": "from",
"amountFrom": 0.1, "amountTo": 42.1234,
"rate": 421.23, "effectiveRate": 421.234,
"fee": { "percent": 0.48, "amountFrom": 0.00048, "usd": 31.2 },
"usd": { "from": 6500.1, "to": 6468.9 },
"min": 0.00015, "max": 3.2, "minTo": 0.063, "maxTo": 1347.9,
"error": null,
"validUntil": 1727250000000,
"source": "live"
}
amountTois after the 0.48% fee.rateis the gross market rate before the fee, andeffectiveRateisamountTo / amountFrom. The site showseffectiveRateas the rate you get.- The payout network fee is covered and already reflected in
amountTo, so the order pays outto.amountin full. If your backend deducts it on top, the site copy has to change first. - For
direction: "to", roundamountFromup to the send asset decimals.minandmaxare in send units,minToandmaxToin receive units.
extraId, refundAddress and refundExtraId may be null. The site validates addresses locally first, but the backend must validate everything again. A success returns an Order (200 or 201). A validation failure is a 4xx with an error body and a field.
{
"from": "BTC", "to": "XMR", "amount": 0.1, "direction": "from", "type": "fixed",
"address": "4AdUndXHHZ...", "extraId": null,
"refundAddress": "bc1q...", "refundExtraId": null
}
Refunds are automatic. With a refund address, a refund goes there. Without one, it goes back to the address the deposit was sent from, in the same asset and network. A refund address is required when the send asset has hiddenSender: true (Monero, Lightning, Zcash), which show no sending address. Reject such an order with a 4xx, code REFUND_ADDRESS, field refundAddress.
{id} is the order ID in upper case: GX plus 8 characters from 0-9 A-Z without I L O U. A 200 returns the current Order. An unknown ID returns 404, and the page shows order not found. The order page polls this every api.pollMs until the status is completed, expired or refunded.
Optional, and only called against a staging backend (demo mode on with a base URL set). The body is {}. Pretend a deposit arrived and return the updated Order. Production backends should not implement it, or should return 404. A live site never calls it.
Schema
The Order object
The one shape that POST {base}/orders and GET {base}/orders/{id} return. The deposit address is the value that must be right.
{
"id": "GX7K2QF9MA",
"createdAt": 1727250000000,
"expiresAt": 1727251800000,
"type": "fixed",
"direction": "from",
"status": "awaiting_deposit",
"statusAt": 1727250000000,
"from": { "code": "BTC", "amount": 0.1, "depositAddress": "bc1q...",
"depositExtraId": null, "depositExtraIdName": null },
"to": { "code": "XMR", "amount": 42.1234, "address": "4AdUndXHHZ...",
"extraId": null, "estimated": false },
"refund": { "address": "bc1q...", "extraId": null },
"rate": 421.23,
"effectiveRate": 421.234,
"fee": { "percent": 0.48, "amountFrom": 0.00048, "usd": 31.2 },
"usd": { "from": 6500.1, "to": 6468.9 },
"confirmations": { "current": 0, "required": 2 },
"txIn": null,
"txOut": null,
"depositAt": null,
"completedAt": null,
"events": [
{ "t": 1727250000000, "agent": "SYSTEM", "text": "Order GX7K2QF9MA created", "level": "info" }
],
"demo": false
}
statusmovesawaiting_deposit,confirming,exchanging,sending,completed. Terminal alternatives areexpired(no deposit beforeexpiresAt) andrefunded.from.depositAddressis where the user sends funds.depositExtraIdanddepositExtraIdNamecarry a required memo or destination tag, ornull.txInandtxOutarenullor{ "hash": "..." }.txIn.fromis the sending address of the deposit once detected. It is where a refund goes when the order has no refund address. Leave it out for hidden-sender assets.eventsis the log, oldest first:tin ms,agent(ORACLE,ROUTER,SENTINEL,EXECUTOR,COURIERorSYSTEM), plain-texttext, and an optionallevel.demomust befalsein production. Whentrue, the order page shows a demo notice.
A refunded order also says where the money went. Return these in the same response that first reports refunded, because the order page stops polling at a terminal status.
"txIn": { "hash": "9f2c...", "from": "bc1q..." },
"refund": { "address": "bc1q...", "extraId": null, "source": "sender", "tx": { "hash": "4e1a..." } }
refund.addressis the address actually refunded, set even when the order had no refund address.refund.sourceisrefund_addressorsender.refund.txis the refund transaction.
Orders are also remembered in the visitor browser (localStorage, key gx:orders) so the tracking page can list orders on this device. Nothing else is stored, and there is no server-side account to sync.
Before you connect
Match the site, then switch
The front end assumes your backend behaves like the contract above. A few facts have to line up, or the copy on the site would no longer be true.
- Charge the same flat 0.48% fee the site quotes, included in the amount, with the payout network fee already covered.
- Use the same currency codes as
currencies.js, and issue IDs with the configured prefix (GXplus 8 characters). - Send every refund automatically, never on a support request: to the refund address, or back to the sending address when none was given.
- Hold a fixed rate for 30 minutes, reprice a floating order at execution, and enforce the Lightning payout rules the site already checks.
- Add the API origin to CORS and to
connect-src, returndemo: falseon every order, and keep secrets out of the public config.
Something you need is not documented here? It is available on request. Write to support@ghostex.ai, and read the API Terms of Use before you build.
For integratorsOne JSON contract
Build the backend. The agents do the rest.
Implement the endpoints above and the five agents run on your infrastructure, with no change to the site. No humans. No KYC. Fully automated.