Skip to content
Ghostex.ai
5 AI agents · 24/7 Launch swap
Launch swap
5 AI agents · 24/7 No humans. No KYC. Fully automated.

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.

  • JSON over HTTPS
  • Four core endpoints
  • Same asset codes
  • No account, no KYC

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.js talks 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.baseUrl is your API root, for example https://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.timeoutMs aborts a request that takes too long (default 15000). api.pollMs is how often the order page refreshes an open order (default 5000).
  • api.headers is 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, plus Content-Type: application/json on a POST, plus anything in api.headers.
  • No cookies. Requests are sent with credentials: 'omit' and cache: 'no-store', and are aborted after api.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 code values from assets/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 body
{ "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.

Codes the front end understands Return one of these in an error body so the site can explain the problem in the right place.
  • CURRENCY
  • SAME
  • PAIR
  • RATES
  • AMOUNT
  • MIN
  • MAX
  • ADDRESS
  • EXTRA_ID
  • REFUND_ADDRESS
  • REFUND_EXTRA_ID
  • NOT_FOUND
  • EXPIRED
  • STATE
  • UNSUPPORTED
Produced by the browser The client raises these itself when your API cannot be reached, does not answer in time, or returns invalid JSON with a 2xx status. You never send them.
  • NETWORK
  • TIMEOUT
  • BAD_RESPONSE
Without a body, a 404 becomes 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.

GET {base}/currencies List assets

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.

Response 200
{
  "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 }
  ]
}
POST {base}/quote Price a swap

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.

Request
{ "from": "BTC", "to": "XMR", "amount": 0.1, "direction": "from", "type": "fixed" }
Response 200
{
  "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"
}
  • amountTo is after the 0.48% fee. rate is the gross market rate before the fee, and effectiveRate is amountTo / amountFrom. The site shows effectiveRate as the rate you get.
  • The payout network fee is covered and already reflected in amountTo, so the order pays out to.amount in full. If your backend deducts it on top, the site copy has to change first.
  • For direction: "to", round amountFrom up to the send asset decimals. min and max are in send units, minTo and maxTo in receive units.
POST {base}/orders Create an order

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.

Request
{
  "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.

GET {base}/orders/{id} Order status

{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.

POST {base}/orders/{id}/simulate-deposit Staging only

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.

Order
{
  "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
}
  • status moves awaiting_deposit, confirming, exchanging, sending, completed. Terminal alternatives are expired (no deposit before expiresAt) and refunded.
  • from.depositAddress is where the user sends funds. depositExtraId and depositExtraIdName carry a required memo or destination tag, or null.
  • txIn and txOut are null or { "hash": "..." }. txIn.from is 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.
  • events is the log, oldest first: t in ms, agent (ORACLE, ROUTER, SENTINEL, EXECUTOR, COURIER or SYSTEM), plain-text text, and an optional level.
  • demo must be false in production. When true, 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.

Refunded order fields
"txIn":   { "hash": "9f2c...", "from": "bc1q..." },
"refund": { "address": "bc1q...", "extraId": null, "source": "sender", "tx": { "hash": "4e1a..." } }
  • refund.address is the address actually refunded, set even when the order had no refund address. refund.source is refund_address or sender. refund.tx is 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 (GX plus 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, return demo: false on 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.