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, also browsable as the API reference). Authentication details — Authentication; webhook contract — Webhooks; the payment page — checkout API reference.
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 invoice1. 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.
# 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"{
"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.
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) 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 §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 |
xlsxto export),GET /v1/invoices/{id}, POST /v1/invoices/{id}/cancel` |
| Static addresses | POST /v1/addresses, GET /v1/addresses, GET /v1/addresses/{id} (with deposit history) |
create_address / read |
| Transactions | GET /v1/transactions (`?format=csv |
xlsxto export),GET /v1/transactions/{id}` |
| 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 |
xlsxto export),GET /v1/withdrawals/{id}, POST /v1/withdrawals/estimate` |
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_cryptois what you receive;payer_amountis what the payer must send (equal, unlessfee_paid_by: payer— then the platform fee is on top). A small shortfall within the underpayment tolerance of your account still counts aspaid. - 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 answers503 unavailable— retry, or create the invoice in crypto. A rate is never guessed. - Late payments. Funds that arrive after
expired/cancelled/finalunderpaidare still credited to your balance, the invoice getslate_payment: trueand you receiveinvoice.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 onpaid, and handlereverted.
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) — SDKs.
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 §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 §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 §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 §7). |
6. Conventions
- Amounts are strings.
"0.00000001", never1e-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). Filtersfrom(inclusive) /to(exclusive) take the same format. - Pagination is keyset:
next_cursoris opaque, valid for the same filters,nullon 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/v1at 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. 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 §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.
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): 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.