Skip to content
Tacet
Launch app

Developers

The same API that powers this app. JSON over HTTPS; public endpoints need no key.

Per client: 30 quotes and 120 lookups per minute; orders burst to 30, then 6 per minute. API keys unlock higher limits and webhooks.

Machine-readable spec (OpenAPI 3.1): /api/openapi.json

Endpoints

https://test.tacet.dea.web.id

The main endpoints are below. Every endpoint, schema, error code and webhook payload is in /api/openapi.json (OpenAPI 3.1), which you can load into Postman, Insomnia or a client generator.

  • GET/api/assets

    Supported assets with live USD price and per-order min/max in asset units.

    curl -s https://test.tacet.dea.web.id/api/assets
  • POST/api/quote

    Estimate a swap and get a quoteId: amount out, fee lines in USD, ETA range in minutes and validUntil (about 2 minutes). Send amountOut instead of amount to ask how much to send to receive a given amount (reverse quote; amountOut in the result is at least the target). Requests without demo follow the server mode. Optional rateType: "fixed" guarantees amountOut (a rate-guarantee fee line is added) if exactly amountIn arrives within the deposit window; otherwise the order continues at the floating rate. Fixed is only offered with Express liquidity (422 FIXED_RATE_UNAVAILABLE).

    curl -s -X POST https://test.tacet.dea.web.id/api/quote \
      -H 'content-type: application/json' \
      -d '{"from":"BTC","to":"ETH","amount":0.01}'
  • POST/api/order

    Create an order from a valid quoteId. Returns 201 with the order ID and a deposit address unique to the order. Destination and refund addresses are required. Sending the same quoteId again returns the same order (200, replayed: true) instead of a second one. If the rate dropped more than 1% since the quote (0.3% for fixed rate), you get 409 RATE_MOVED with a new quoteId to confirm. With recipients, each one gets its own guaranteed amount (split.legs[].fixedAmountOut). The order carries rateType and fixedRate {amountOut, status: locked | voided | settled}. Optional payoutDelay: true adds a random delay before payout.

    curl -s -X POST https://test.tacet.dea.web.id/api/order \
      -H 'content-type: application/json' \
      -d '{"quoteId":"q_…",
           "destinationAddress":"0x…","refundAddress":"bc1…"}'
  • GET/api/order/:id

    Order status: displayStatus (stable contract, see below), per-leg status, settlement confirmations, payout or refund transaction IDs.

    curl -s https://test.tacet.dea.web.id/api/order/aB3dE5fG7hJ9
  • GET/api/order/:id/stream

    Server-Sent Events. Sends the order once, then again on every change (typically within half a second). Ends with an end event: reason final when the order is finished, or reconnect after 10 minutes. Open streams are capped per client (behind a trusted proxy) and in total.

    curl -N https://test.tacet.dea.web.id/api/order/aB3dE5fG7hJ9/stream
  • GET/api/order/:id/receipt

    Ed25519-signed receipt for a COMPLETED or REFUNDED order (409 RECEIPT_NOT_READY before that). Verify it against /api/receipt-key without trusting the channel that delivered it. Users can check a downloaded receipt in their browser at /verify.

    curl -s https://test.tacet.dea.web.id/api/order/aB3dE5fG7hJ9/receipt
    curl -s https://test.tacet.dea.web.id/api/receipt-key
  • POST/api/paylinks

    Create a payment link: {to, amount, address, memo?, description?}. Payers pay with any token via /api/paylinks/:id/quote {from} and /api/paylinks/:id/order {quoteId, refundAddress}, with the same quote rules as /api/order (a used quoteId returns the existing order; RATE_MOVED carries a new quoteId). The recipient address is never returned to anyone.

    curl -s -X POST https://test.tacet.dea.web.id/api/paylinks \
      -H 'content-type: application/json' \
      -d '{"to":"USDT-TRX","amount":100,"address":"T…"}'
  • GET/api/partner/me

    With your API key: orders created with it, completed volume, recent orders. POST /api/partner/webhook {url} sets your webhook; POST /api/partner/webhook/test sends a signed test.ping.

    curl -s https://test.tacet.dea.web.id/api/partner/me \
      -H 'authorization: Bearer sk_…'
  • POST/api/address/validate

    Full checksum validation of a destination or refund address for an asset.

    curl -s -X POST https://test.tacet.dea.web.id/api/address/validate \
      -H 'content-type: application/json' \
      -d '{"asset":"BTC","address":"bc1…"}'
  • GET/api/routes/onchain

    Optional on-chain route through LI.FI for EVM assets (also POST with a JSON body). Not private: both ends are linked on-chain. Unsupported pairs return 200 with available: false and a reason. LI.FI on-chain routes earn your markup via LI.FI integrator fees.

    curl -s 'https://test.tacet.dea.web.id/api/routes/onchain?from=USDC-ETH&to=BNB&amount=100'
  • GET/api/status

    System status: network, router heartbeat, settlement layer height, venue market data and order counts. For probes use /api/livez (process up, always 200) and /api/readyz (503 when the router heartbeat is stale or storage isn't writable).

    curl -s https://test.tacet.dea.web.id/api/status

Integrators

API keys. Send Authorization: Bearer sk_… to get per-key limits (300 quotes, 60 orders and 1,200 lookups per minute) and webhooks. Keys are issued by the operator and stored only as a hash; a revoked or wrong key gets 401 INVALID_API_KEY.

Idempotency. Add an Idempotency-Key header (8–128 characters) to POST /api/order or POST /api/paylinks/:id/order. Retrying with the same key and body returns the original response with idempotent-replay: true; the same key with a different body is rejected with IDEMPOTENCY_KEY_REUSED. Keys are remembered for 24 hours.

Webhooks. If your key has a webhook URL (public https only), Tacet POSTs order.created and order.updated events whenever an order's displayStatus changes. Payloads carry the order ID, statuses and transaction IDs — never addresses. Failed deliveries are retried up to 8 times over about 31 hours with backoff. Respond with any 2xx. Verify every request:

// Header: X-Webhook-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256(secret, "<t>.<raw body>")>
import { createHmac, timingSafeEqual } from "node:crypto";

function verify(secret, rawBody, header, toleranceSec = 300) {
  const m = /^t=(\d+),v1=([0-9a-f]{64})$/.exec(header ?? "");
  if (!m || Math.abs(Date.now() / 1000 - Number(m[1])) > toleranceSec) return false;
  const want = createHmac("sha256", secret).update(`${m[1]}.${rawBody}`).digest();
  return timingSafeEqual(want, Buffer.from(m[2], "hex"));
}

Embed

Put the swap widget on your site with an iframe. /embed renders only the widget; the order page stays inside the iframe at /embed/order/:id, so the user sees the deposit address without leaving your page or being caught by a popup blocker. Preselect with query parameters: from and to (asset IDs from /api/assets), amount, mode (swap or send) and rate (floating or fixed). Unknown values are ignored.

Allowed origins. Framing is blocked by default. The operator adds your exact origin (scheme, host and port, e.g. https://partner.example) to the allowlist; every other page of Tacet refuses to be framed.

<iframe
  src="https://test.tacet.dea.web.id/embed?from=BTC&to=ETH&amount=0.01"
  title="Tacet swap"
  width="100%" height="760"
  style="border:0;border-radius:12px"
></iframe>

Events. When the user creates an order, the widget posts {"type": "tacet:order_created", "orderId": "…"} to the parent window, addressed to your allowed origin only (never "*"). Check event.origin before trusting it, then follow the order with GET /api/order/:id or your webhook. The message carries no addresses or amounts.

window.addEventListener("message", (event) => {
  if (event.origin !== "https://test.tacet.dea.web.id") return; // only trust messages from Tacet
  if (event.data?.type === "tacet:order_created") {
    console.log("order created", event.data.orderId);
  }
});

AI agents (MCP)

Tacet ships a Model Context Protocol server with seven tools: get_assets, get_quote, validate_address, create_order, get_order, wait_for_order and get_receipt. create_order only runs with an explicit confirm: true and reuses the quote as its idempotency key, so a retry never creates a second order. wait_for_order follows the live stream, so the agent waits instead of asking the user to check back. The agent never handles private keys: the user sends the deposit from their own wallet. Run it with npm run mcp and set SWAP_API_URL (and optionally SWAP_API_KEY).

Order status

Read displayStatus. Its values and meanings won't change. Terminal values are COMPLETED, REFUNDED, EXPIRED and FAILED. hops.hop1 and hops.hop2 are pending, in_progress, done or failed.

WAITING_FOR_DEPOSIT
Order created; waiting for your deposit.
DEPOSIT_DETECTED
Deposit seen on-chain; waiting for confirmations.
EXCHANGING_SOURCE
Leg 1: your deposit is being converted at the first liquidity venue.
PRIVATE_SETTLEMENT
Funds are moving through private settlement to the second liquidity venue.
EXCHANGING_TO_DESTINATION
Leg 2: converting to your destination asset.
SENDING
Payout to your destination address is in progress.
COMPLETED
Done. The payout transaction ID is in progress.payoutTxid.
REFUNDING
The swap stopped; your deposit is being returned to the refund address.
REFUNDED
Refund sent. The transaction ID is in refund.txid.
ON_HOLD
Held for manual review (funds mid-route, screening, or an interrupted action).
EXPIRED
No deposit arrived in time. Late deposits are refunded.
FAILED
Stopped with nothing to refund (no funds reached Tacet).

Errors

Errors return {"error": CODE, "message": "…", "requestId": "…", "fields"?: {…}} with a matching HTTP status. Every response carries an x-request-id header.

400
BAD_JSON, BAD_IDMalformed request body or order ID.
403
LIVE_DISABLEDLive trading isn't enabled on this server.
404
NOT_FOUNDNo order with that ID.
409
RATE_MOVEDThe rate dropped more than 1% since the quote. Response includes a new quoteId and quote.
422
QUOTE_REQUIRED, QUOTE_NOT_FOUND, QUOTE_EXPIRED, QUOTE_MISMATCHThe order must reference a quote that is still valid and matches it.
422
BELOW_MIN, ABOVE_MAX, SAME_ASSET, INVALID_ADDRESS, INVALID_REFUND_ADDRESS, …The request can't be quoted or ordered as given. Field errors are listed in fields.
413
PAYLOAD_TOO_LARGERequest bodies are limited to 16 KB.
429
RATE_LIMITEDToo many requests. Wait retryAfter seconds (also in the Retry-After header).
500
INTERNAL_ERRORUnexpected error. Quote the requestId if you contact support.
502
VENUE_UNAVAILABLEThe liquidity venue couldn't issue a deposit address. Retry.
503
PRICES_UNAVAILABLELive market prices are unreachable; real-funds quotes are refused rather than priced from fallbacks. Retry shortly.
Developers · Tacet