Source: https://cryptolike.net/docs/api

# CryptoLike API — Overview

From API call to payment status. One REST API, JSON in and out, signed with an API key. This
page is the map; every route, field and error is in the OpenAPI document
([OpenAPI spec](https://api.cryptolike.net/docs/openapi.yaml), also browsable as the API reference). Authentication details —
[Authentication](https://cryptolike.net/docs/auth); webhook contract — [Webhooks](https://cryptolike.net/docs/webhooks); the payment page — [checkout API reference](https://api.cryptolike.net/docs).

```
Base URL      https://api.cryptolike.net      (version in the path: /v1)
Auth          X-API-Key + X-Timestamp + X-Signature (HMAC-SHA256)   https://cryptolike.net/docs/auth
Format        JSON, snake_case; amounts are decimal strings; times RFC 3339 UTC; ids UUID
Pagination    ?limit=50&cursor=…  →  {"items":[…],"next_cursor":"…"|null}
Errors        {"error":{"code","message","details"}}   details.field names the bad field
Retries       POST + Idempotency-Key  →  same response for 24 h (a 5xx included), never a second invoice
```

## 1. Quickstart — to the first confirmed payment

**1. Create a key.** Cabinet → API keys → Create key with `create_invoice` (and `read`). Copy the
secret: it is shown once. Keep it on your server (env var / secret manager) — never in a web
page or mobile app: whoever reads it can sign requests as you. Browser code talks to your
backend, your backend talks to us.

**2. Create a payment.** One call; the response carries the page to send the payer to.

```bash
# see https://cryptolike.net/docs/auth §4 for the signing lines (TS, SIG); body first, then sign it
BODY='{"amount":"10.50","currency":"USD","order_id":"A-1001","return_url":"https://shop.example/thanks"}'
curl -s https://api.cryptolike.net/v1/invoices \
  -H "Content-Type: application/json" -H "Idempotency-Key: order-A-1001" \
  -H "X-API-Key: $KEY_ID" -H "X-Timestamp: $TS" -H "X-Signature: $SIG" -d "$BODY"
```

```json
{
  "id": "4e2bc88c-cb79-4afc-b864-c1ba8e9d50c9",
  "status": "new",
  "currency": "USD", "amount": "10.5",
  "coin_id": null, "ticker": null, "network": null, "address": null,
  "amount_crypto": null, "payer_amount": null,
  "paid_amount": "0", "remaining_amount": "0", "overpaid_amount": "0",
  "amount_fiat": "10.5", "fiat_currency": "USD", "rate_locked": null, "rate_locked_at": null,
  "late_payment": false, "fee_paid_by": "merchant",
  "order_id": "A-1001", "description": null, "return_url": "https://shop.example/thanks", "webhook_url": null,
  "metadata": null, "allowed_coin_ids": null,
  "checkout_url": "https://cryptolike.net/pay/4e2bc88c-cb79-4afc-b864-c1ba8e9d50c9",
  "ttl_expires_at": "2026-09-16T12:30:00Z", "expired_at": null,
  "created_at": "2026-09-16T12:00:00Z", "updated_at": "2026-09-16T12:00:00Z"
}
```

`currency` may be a fiat code (`USD`, `EUR`, `UAH`, `GBP` or `CAD`, 2 decimals each; your list is
`GET /v1/fiat-currencies` — the payer chooses the coin on the page and the rate is locked at that moment) or a coin ticker (`BTC`, `USDT`). Pass `coin_id` (from
`GET /v1/currencies`) to fix the coin now: the address and the exact amount are in the response.
Without `coin_id` the payer chooses on the page among the coins you offer for invoices (cabinet →
Coins: on and «available for invoices»), each shown with an informative `≈` amount; to narrow the
choice for this payment send `"allowed_coin_ids": ["<coin_id>", …]` — the response's
`allowed_coin_ids` is the kept set (coins you offer for invoices that match `currency`; a ticker
keeps only its own networks), `422 no_allowed_coins` when none is left, `null` when not sent. Pick
the ids from the signed `GET /v1/currencies` (`invoice_enabled: true`, not `maintenance`). The
SDKs send the list as given and refuse an empty one before the request.

**Shops: one payment per order.** Send `"idempotency_by_order_id": true` with your `order_id` and
a repeated request for the same order (double click, reload, retry) returns the order's open
payment with `200` instead of creating a second one; the same order with another amount is
`409 conflict` (`details.reason = order_invoice_mismatch`), an order that already received funds
is `409 conflict` (`order_has_payment`) — both name `details.invoice_id`. `metadata` (up to 20
string pairs, ≤ 4 KB) comes back in the API and in `invoice.*` webhooks and is never shown to the
payer. The full contract for shop plugins is [integration guides](https://cryptolike.net/integrations).

**3. Send the payer to `checkout_url`.** The page shows the address, QR, amount, network warning
and live status. After payment it offers `return_url`.

**4. Learn the result.** Either subscribe a webhook endpoint (`POST /v1/webhooks/endpoints`,
events `invoice.paid`, `invoice.underpaid`, `invoice.expired`, …; [Webhooks](https://cryptolike.net/docs/webhooks)) or poll
`GET /v1/invoices/{id}` — no more often than every few seconds. `status: paid` means the funds
are confirmed and credited to your balance (`GET /v1/balances`).

Test on test networks first: a `ck_test_` key with the test API URL from CryptoLike support
(on mainnet a test key gets `401 auth.test_key`). The reference signature vector in
[Authentication](https://cryptolike.net/docs/auth) §3 tells you in one call whether your signing is right.

## 2. Resources

| Resource | Routes | Permission |
|---|---|---|
| Currencies | `GET /v1/currencies` (signed: the coins enabled for you, each with `invoice_enabled` — «available for invoices»; unsigned: the public catalogue) | `read` |
| Rates | `GET /v1/rates?fiat=USD` | `read` |
| Fiat currencies | `GET /v1/fiat-currencies` (your default fiat; every enabled fiat with `decimals` — fractional digits an amount may carry, 0 for JPY — and `invoice_enabled` — usable as an invoice `currency`; the cabinet's invoice form offers the same list) | `read` |
| Balances | `GET /v1/balances` | `read` |
| Invoices | `POST /v1/invoices`, `GET /v1/invoices` (`?format=csv|xlsx` to export), `GET /v1/invoices/{id}`, `POST /v1/invoices/{id}/cancel` | `create_invoice` / `read` |
| Static addresses | `POST /v1/addresses`, `GET /v1/addresses`, `GET /v1/addresses/{id}` (with deposit history) | `create_address` / `read` |
| Transactions | `GET /v1/transactions` (`?format=csv|xlsx` to export), `GET /v1/transactions/{id}` | `read` |
| Webhooks | `GET/POST /v1/webhooks/endpoints`, `GET/PATCH/DELETE …/{id}`, `POST …/{id}/rotate-secret`, `GET /v1/webhooks/deliveries`, `GET …/{id}`, `POST …/{id}/retry` | `create_invoice` for changes, `read` for reads |
| Withdrawals | `POST /v1/withdrawals`, `POST /v1/withdrawals/{id}/cancel`; `GET /v1/withdrawals` (`?format=csv|xlsx` to export), `GET /v1/withdrawals/{id}`, `POST /v1/withdrawals/estimate` | `withdraw` / `read` |

## 3. Payment (invoice) status model

| Status | Meaning | Terminal |
|---|---|---|
| `new` | Created; nothing received. | no |
| `pending` | A transfer was detected; waiting for confirmations. | no |
| `paid` | Full amount confirmed and credited. An overpayment is still `paid`, with `overpaid_amount > 0` (`invoice.paid` carries it). | yes |
| `underpaid` | Less than the amount confirmed; open for a top-up to the same address until `ttl_expires_at`, then final. `remaining_amount` says how much is missing. | after the TTL |
| `expired` | TTL passed with nothing confirmed. | yes |
| `cancelled` | You cancelled it (`POST …/cancel`, only from `new`). | yes |

Rules worth knowing:

- **Amounts.** `amount_crypto` is what you receive; `payer_amount` is what the payer must send
  (equal, unless `fee_paid_by: payer` — then the platform fee is on top). A small shortfall
  within the underpayment tolerance of your account still counts as `paid`.
- **Fiat pricing.** The rate is locked when the coin is known (creation with `coin_id`, or the
  payer's choice) and does not change for the TTL. If no fresh rate is available, or the two rate
  sources disagree on the pair beyond the limit, the request answers `503 unavailable` — retry, or
  create the invoice in crypto. A rate is never guessed.
- **Late payments.** Funds that arrive after `expired`/`cancelled`/final `underpaid` are still
  credited to your balance, the invoice gets `late_payment: true` and you receive
  `invoice.late_payment`. Decide with the customer what to do; the status does not change.
- **Reorganisations.** If a chain reorganisation reverts a confirmed transfer, the invoice is
  recomputed and you receive `invoice.reverted`. Ship on `paid`, and handle `reverted`.

## 4. Static addresses and transactions

A static address is a permanent deposit address of one currency for one of your customers
(`customer_id`). Every deposit is credited after the currency's confirmations and announced by
`deposit.pending` / `deposit.confirmed` (`deposit.reverted` after a reorganisation). One active
address per (currency, customer); archive the old one (`POST /v1/addresses/{id}/archive`, or in
the cabinet) to issue a new one. Addresses are never deleted and an index is never reused: an
archived address leaves real-time scanning but is checked for incoming transfers regularly
(every 24 h by default) — a transfer found is credited as usual and restores the address
automatically (`static_address.restored`, reason `deposit`). `POST /v1/addresses/{id}/restore`
brings an archived address back (`409` with `details.active_address_id` while the customer has
another active address for the currency). `GET /v1/addresses?status=archived&search=…` is the
archive (search by address, `customer_id` or label; filter `coin_id`; cursor pages). An address
without incoming transfers for 180 days (platform setting) is archived automatically after a
7-day `static_address.archive_warning`.

In the SDKs the archive is `archiveStaticAddress` / `restoreStaticAddress` /
`listArchivedStaticAddresses` (Python `archive_static_address` …, Go `ArchiveStaticAddress` …), and
the `data` of the three archive webhooks is typed (`StaticAddressArchivedData`,
`StaticAddressRestoredData`, `StaticAddressArchiveWarningData` in [OpenAPI spec](https://api.cryptolike.net/docs/openapi.yaml)) — [SDKs](https://cryptolike.net/docs/sdk).
In the Telegram bot an archived address card has **Restore** (same rule, `409` explained with a
button to the active address), and a notice of an automatic archive carries the same button.

`GET /v1/transactions` lists every movement: `invoice_payment`, `deposit`, `withdrawal`,
`adjustment`. Each row has the gross `amount`, the platform `fee` (known once confirmed) and
`net_amount`, the chain `txid`, and the related invoice or static address. `?format=csv` or
`?format=xlsx` streams the same list with the same filters as a file (also on `GET /v1/invoices`
and `GET /v1/withdrawals`); every cell is text, so amounts keep their exact decimals. An XLSX of
more than 100 000 rows is answered as CSV — check `Content-Type` / the file name.

## 4a. Withdrawals

A withdrawal sends `amount` of a coin from your available balance to `to_address`. Ask
`POST /v1/withdrawals/estimate` first: it returns the platform fee, the reserved network fee
and `total_debit` — what leaves `available` the moment the withdrawal is created. The whole
`total_debit` sits in `hold` until the payout ends:

| Status | Meaning | Hold |
|---|---|---|
| `pending` | queued for the payout worker | held |
| `review` | a manual check is needed (`review_reason`: `limit`, `pattern`, `manual`, `compliance`, `signer_limit`); up to 24 h | held |
| `awaiting_approval` | above your account's dual-approval threshold: a second owner/admin confirms in the cabinet | held |
| `processing` | being signed and sent — can no longer be cancelled | held |
| `broadcast` | in the network (`txid`, `confirmations` growing) | held |
| `completed` | confirmed; `amount` left, `platform_fee` and `network_fee_charged` were charged | released |
| `failed` | the network refused or dropped the transaction (`failure_reason`) — everything is back in `available`; create a new withdrawal | released |
| `rejected` | refused on review — everything is back in `available` | released |
| `cancelled` | you cancelled before processing (`POST …/{id}/cancel`) | released |

Rules: the key needs `withdraw`; the account must be verified (`403 kyb_required`); when the
account enforces its withdrawal whitelist only an active whitelisted address is accepted
(`403`); a destination the platform does not accept is `422 address_refused` (no detail is
given); an insufficient balance is `409` with `details.reason = insufficient_funds`. Send your
own `payout_id`: repeating the request with the same coin, amount and address returns the
existing withdrawal (`200`; a repeat within the first seconds after creation may still answer
`201` — the body and `id` are the same; use `Idempotency-Key` for exact replay semantics),
different parameters are `409` — your retries can never pay twice.
Security review: limits, unusual patterns and recent security changes of the account (a change
of the allowed payout addresses, an API key that received the `withdraw` permission, a sign-in
from a new device) send a payout to `review` with the generic `review_reason: pattern` — nothing
is refused, a person looks at it. If a member froze the account in the cabinet, every key answers
`403 account_frozen` until support unfreezes it (incoming payments keep being credited).
The network fee you are charged is either the reserved estimate (default) or the fee actually
paid (`network_fee_mode: actual` on the estimate; the unused reservation returns to
`available` on completion). Webhooks: `withdrawal.processing`, `withdrawal.completed`,
`withdrawal.failed`, `withdrawal.rejected` (plus the progress events of [Webhooks](https://cryptolike.net/docs/webhooks) §5).

## 5. Errors

Every error is one envelope; branch on `code`, show `message` to a human, read `details` for
specifics (`field`, `required`, `retry_after`, `server_time`).

| HTTP | `code` | Recovery |
|---|---|---|
| 400 | `validation_error` | Fix `details.field` and resend. |
| 401 | `auth.missing_header`, `auth.invalid_key`, `auth.invalid_timestamp`, `auth.expired_timestamp`, `auth.invalid_signature`, `auth.replay` | [Authentication](https://cryptolike.net/docs/auth) §5 and §10. |
| 401 | `auth.test_key` | A test key on an environment that runs on mainnets — nothing is created; use a live key. `details.docs_url` links [Authentication](https://cryptolike.net/docs/auth) §9. |
| 403 | `auth.permission_denied` (`details.required`), `auth.ip_not_allowed` | Grant the permission / whitelist the IP in the cabinet. |
| 403 | `kyb_required`, `forbidden` (whitelist) | Complete the account verification in the cabinet / whitelist the destination (or switch the whitelist off) in the cabinet. |
| 403 | `account_frozen` | A member froze the account (cabinet → Settings → Security); contact support to unfreeze it. |
| 422 | `address_refused` | The destination is not accepted by the platform; use another address. |
| 422 | `no_allowed_coins` | None of `allowed_coin_ids` is a coin you offer for invoices in this `currency` — switch a coin on (and «available for invoices») in the cabinet or send other ids. |
| 404 | `not_found` | Wrong id, or the resource belongs to another account. |
| 409 | `conflict` | The state does not allow it (cancel a paid invoice, second active address, retry of a pending delivery; `idempotency_by_order_id`: `details.reason` `order_invoice_mismatch` / `order_has_payment` + `details.invoice_id`). Read the current state first. |
| 409 | `idempotency.in_progress` | The first request with this key is still running; wait and retry. |
| 413 | `payload_too_large` | Body over 1 MiB. |
| 422 | `idempotency.mismatch` | Same `Idempotency-Key`, different request — use a new key. |
| 429 | `rate_limited` | Wait `Retry-After` seconds; the limit is per key (600/min by default). |
| 503 | `unavailable` | Rate feed, fee estimate, screening or coin temporarily unavailable — nothing was created; retry with backoff (the same `Idempotency-Key` runs again). |
| 500, 504 | `internal_error`, `timeout` | The request may or may not have been applied. A retry with the same `Idempotency-Key` replays this answer for 24 h and never runs the request twice; check the object (`GET` by `payout_id` / `order_id`) and send a new key only if it does not exist ([Authentication](https://cryptolike.net/docs/auth) §7). |

## 6. Conventions

- **Amounts are strings.** `"0.00000001"`, never `1e-8`; up to 18 decimals; the API refuses
  more precision than the currency has.
- **Times** are RFC 3339 in UTC (`2026-09-16T12:00:00Z`). Filters `from` (inclusive) / `to`
  (exclusive) take the same format.
- **Pagination** is keyset: `next_cursor` is opaque, valid for the same filters, `null` on the
  last page; rows inserted meanwhile never shift or duplicate pages.
- **Stable shape.** Optional blocks are present as `null`, so a client can parse every response
  the same way.
- **Versioning.** Breaking changes get a new prefix (`/v2`); fields may be added under `/v1`
  at any time — ignore unknown fields.
- **Providers are never named.** Rates, nodes and screening vendors are internal; you see
  values and timestamps.

## 7. Client libraries

Official SDKs — `@cryptolike/sdk` (TypeScript / Node.js), `cryptolike` (Python),
`github.com/Petya88/Cryptolike/sdk/go` (Go) and `cryptolike/sdk` (PHP) — with a quickstart to
the first confirmed payment in each language: [SDKs](https://cryptolike.net/docs/sdk). They sign, retry, add
`Idempotency-Key` to every `POST` and verify webhooks the same way. Any other language works
with the four signing lines of [Authentication](https://cryptolike.net/docs/auth) §4 (curl, Node, Python, Go examples there).
Reference vectors for your own SDK's tests — request signing and webhook verification,
positive and negative — are in [`vectors.json`](https://cryptolike.net/docs/sdk/vectors.json).

**The secret stays on your server.** `createMerchantClient` refuses to run in a browser. To
show the payment page inside your site, embed it ([Checkout widget](https://cryptolike.net/docs/widget)): your server creates the
invoice, your page gets the invoice id only.

## 8. Versioning

`/v1` is the contract version; every response carries `X-API-Version: v1` and you may send the
same header to pin it. Only additive changes ship within v1 — API changelog lists
them and the rules.
