Coin API — Implementation-matching Reference

Exact request/response shapes and behavior derived from api.js and logic.js.
Coin Website Privacy Policy API Documentation Terms of Use GitHub Repo

Table of contents

  1. Overview
  2. Authentication
  3. Queue & idempotency
  4. Endpoints (complete & exact)
  5. txId: generation & when responses include it
  6. Concrete examples
  7. Errors & status codes
  8. Appendix — op names

1) Overview

This documentation mirrors the exact behavior implemented in the server files you provided (api.js and logic.js).

Key architecture: the HTTP API enqueues write operations into a bounded in-memory queue (with optional DB persistence for pending jobs) and then either waits for the queue worker result (preferred) or returns a legacy-shaped response for backward compatibility. The queue attaches a txId to payloads for idempotency; whether that txId appears in the HTTP response depends on per-route mapping rules implemented in doEnqueueAndMap. See Queue & idempotency below for exact mapping rules. (source: api.js, logic.js).

2) Authentication

Endpoints that require authentication use an Authorization header with a session token:

Authorization: Bearer <sessionId>


Content-Type: application/json

The middleware resolves the session using db.getSession and sets req.userId. If session lookup fails the route returns 403 with legacy error shapes. (see /api/login to create sessions). fileciteturn1file12 fileciteturn1file6

3) Queue & idempotency

Important implementation details (exact):

  • The server uses queue.enqueueAndWait(payload). Before enqueueing, doEnqueueAndMap ensures payload.args.ip is set and attaches payload.txId = a random UUID if not provided. (see api.js) fileciteturn1file12
  • If the queue refuses with ENQUEUE_REJECTED the wrapper will attempt an in-memory cache and a DB pending-job. If that also fails, the API returns 429 / { error: "QUEUE_FULL" }. The cache worker will later flush pending items into the queue. fileciteturn1file10
  • Per-route legacy mapping: when doEnqueueAndMap is called with { legacyReturn: 'transfer' } the API responds with { success: true } on success (hides txId). When called with { legacyReturn: 'transfer_card' } the API will return { success: true, txId?, date? } if the worker result contains a txId, otherwise it returns { success: true }. Other endpoints return the worker result as-is by default. fileciteturn1file10

4) Endpoints (complete & exact)

POST /api/login

Body: { username, password } (server hashes password before checking). Returns sessionCreated + sessionId + userId + saldo + cooldownRemainingMs per logic.login.

Request body:


{
"username": "alice",
"password": "secret"
}

Response (success):
{
"sessionCreated": true,
"passwordCorrect": true,
"userId": "123456789012345678",
"sessionId": "",
"saldo": 12.345678,
"cooldownRemainingMs": 0
} 

Notes: the server enforces IP registration/login protections (see logic.login) including IP try counts and blocking windows. fileciteturn1file1

POST /api/register

Creates a user. Body: { username, password }. Server may enforce "one-registration-per-IP" within 24h. On success returns { success:true, userId, sessionId } (session created server-side). fileciteturn1file1

Request:


{ "username": "bob", "password": "s3cret" }

Response:
{ "success": true, "userId": "987654321", "sessionId": "" } 

POST /api/transfer — authenticated transfer (legacy response)

Headers: Authorization: Bearer <sessionId>. Body: { toId, amount }. This route enqueues op: 'transfer' with args: { from: req.userId, to, amount }. The legacy behavior is to return { success: true } on success and not include the txId in the HTTP response (even though the queue payload has a txId internally). Use one of the alternatives below if you need the txId. fileciteturn1file12

Request (auth):


Authorization: Bearer 
{
"toId": "987654321",
"amount": 0.01234567
}

Response:
{ "success": true } 

Alternatives to obtain txId:

  1. Provide txId in the request body yourself — the server will keep it for idempotency (it sets payload.txId only if not provided).
  2. If you can persist the pending job record (DB), correlate it to the eventual transaction when the worker finalizes and write the txId into your tracking table (server may persist pending jobs into DB if configured).
  3. Use card-based endpoints (which may return txId) or GET /api/tx/:txid once you have the id. fileciteturn1file10

POST /api/transfer/card — transfer using a card code

Body: { cardCode, toId, amount }. This route hashes the cardCode to find the ownerId, ensures recipient user exists, enqueues op: 'transfer_card', and uses legacyReturn 'transfer_card'. That means:

  • If the worker result contains txId (common when DB helper supports atomic transfer & returns txId) the HTTP response will be { success:true, txId, date }.
  • If the worker result does not include txId, the response will fall back to legacy { success:true }.

Request:


{ "cardCode": "abcd-efgh-1234", "toId": "11111", "amount": 0.01 }

Response (if txId available):
{ "success": true, "txId": "uuid-12345-abcdef", "date": "2025-12-07T12:00:00.000Z" }

Response (legacy fallback):
{ "success": true } 

Notes: server truncates amounts to 8 decimals (sats) before enqueueing. Card lookup helpers may be named db.getCardOwnerByHash or similar. fileciteturn1file9

POST /api/card/pay or /api/transfer_between_cards — card-to-card payment

Body: { fromCard, toCard, amount }. Server enqueues op: 'transfer_between_cards' and uses the same legacy mapping as transfer_card, returning txId when the worker returns it, otherwise { success:true }. Amounts truncated to 8 decimals. fileciteturn1file18

Request:


{ "fromCard": "abcd-ef", "toCard": "wxyz-12", "amount": 0.002 }

Possible response:
{ "success": true, "txId": "uuid-xxxxx", "date": "2025-12-07T12:01:00.000Z" } 

POST /api/claim — faucet / reward claim

Headers: Authorization: Bearer <sessionId>. Enqueues op: 'claim' (args: { userId }). The API calls the queue directly via queue.enqueueAndWait for claim, and then returns the worker result as-is. The worker (logic.claimCoins) will try to use an atomic DB helper (claimReward) if available; as a fallback it will add coins, set cooldown, and insert a transaction record internally. However, claimCoins returns only the domain result (e.g. { success:true, claimed: X }) and intentionally does not expose the internal txId. If you need the txId for claims you must correlate via server-side pending-job records or change the logic to return txId. fileciteturn1file4

Request (auth):


Authorization: Bearer 
Body: {}

Response (success):
{ "success": true, "claimed": 0.05 }

Response (cooldown):
{ "error": "Cooldown active" } 

GET /api/tx/:txid — lookup transaction

Use to retrieve transaction info when you have a txId. This route delegates to logic.checkTransaction which uses DB helpers getTransaction|getTransactionById. Response shape:

Response (found):


{
"success": true,
"tx": {
"id": "uuid-12345",
"date": "2025-12-07T12:00:00.000Z",
"from": "11111",
"to": "22222",
"amountSats": 12345,
"amountCoins": "0.00012345"
}
}

Response (not found):
{ "success": false, "errorCode": "INVALID_TRANSACTION", "message": "Transaction not found" } 

Notes: amountSats is the integer internal representation (satoshis). The public string amount amountCoins mirrors the DB column. fileciteturn1file4

GET /api/transactions?userId=<id>&page=<n> — list transactions (paginated)

Returns { transactions: [ { id, date, from_id, to_id, amount } ], page } where amount is in coins (string/float formatted via fromSats). The API enqueues or delegates to logic.getTransactions. fileciteturn1file16

Response:


{
"transactions": [
{ "id": "tx-1", "date": "2025-12-07T12:00:00.000Z", "from_id": "11111", "to_id": "22222", "amount": "0.001" }
],
"page": 1
} 

GET /api/card/info (or similar helpers) & POST /api/card/reset

The server exposes card helpers via queue ops: get_card (returns { cardCode }) and reset_card (returns { newCode }). Implementation delegates to logic.getCardCode and logic.resetUserCard which call DB helpers. These endpoints usually are protected by auth. fileciteturn1file10

Backup endpoints

Operations: create/list/restore backups. They use logic.createBackup, logic.listBackups, logic.restoreBackup. Examples:

POST /api/backup/create  (auth)


-> returns: { success: true }  (or an array of codes when createBackup returns codes)

GET /api/backup/list (auth)
-> returns: { backups: ["code1","code2"] }

POST /api/backup/restore (body: { backupId: "..." }, auth)
-> returns: { success: true } on success 

Notes: Restore consumes the backup code and will transfer the full balance from the original owner to the requester (see logic.restoreBackup). There are checks to avoid restoring your own backup and to ensure the source wallet has funds. fileciteturn1file7

Billing endpoints (create / pay / list)

Ops supported: bill_create, bill_pay, bill_list, and card variants bill_create_card, bill_pay_card. Examples:

POST /api/bill/create  (body: { toId, amount, time? })


-> { success: true, billId: "..." }

POST /api/bill/pay  (auth, body: { billId })
-> { success: true }

POST /api/bill/create/card  (body: { fromCard?, toCard?, amount, time? })
-> returns logic result (e.g. { success:true, billId })

POST /api/bill/pay/card  (body: { cardCode, billId })
-> { success:true } or { success:false, error: '...' } 

Card bill flows resolve owner ids from card codes and reuse the bill logic. fileciteturn1file6

Queue inspection (optional)

If EXPOSE_INTERNALS is enabled then:

GET /api/queue/info  -> { queued, usedBytes, maxBytes, maxOpsPerSecond }


GET /api/queue/status/:id -> { state, ts, result? } 

Useful to debug pending items or to correlate a client-side stored txId with the queue status. fileciteturn1file18

5) txId: generation & when responses include it

  • Every enqueued payload receives a payload.txId (UUID) for idempotency; the server will only set it if the client didn't provide one. This is implemented in doEnqueueAndMap. fileciteturn1file10
  • Whether the HTTP response includes the txId depends on the route mapping rules:
    • legacyReturn: 'transfer' → response is always { success:true } (txId hidden).
    • legacyReturn: 'transfer_card' → response is { success:true, txId?, date? } if worker result contains a txId; otherwise { success:true }.
    • Default behavior (no legacyReturn) → returns the worker result as-is (which may include txId if the logic function returned it). Examples: some logic functions like transferCoins return { txId, date } when DB helpers provide it. fileciteturn1file4
  • If you must always receive the txId for a transfer, either provide it yourself in the request body or modify the server mapping (in doEnqueueAndMap) to include payload.txId in the response for transfers. fileciteturn1file17

6) Concrete examples (requests & responses)

cURL — authenticated transfer (legacy)

curl -X POST "https://your.host/api/transfer" \


-H "Authorization: Bearer " 
-H "Content-Type: application/json" 
-d '{"toId":"987654321","amount":0.001}'

Response:
{ "success": true } 

cURL — transfer using card (may return txId)

curl -X POST "https://your.host/api/transfer/card" \


-H "Content-Type: application/json" 
-d '{"cardCode":"abcd-efgh-1234","toId":"11111","amount":0.01}'

Possible response:
{ "success": true, "txId": "uuid-12345-abcdef", "date": "2025-12-07T12:00:00.000Z" }

Or (legacy fallback):
{ "success": true } 

Node (fetch) — claim

await fetch('https://your.host/api/claim', {


method: 'POST',
headers: { 'Content-Type':'application/json', 'Authorization': 'Bearer ' }
}).then(r => r.json()) // -> { success: true, claimed: 0.05 } 

GET tx lookup

curl -X GET "https://your.host/api/tx/uuid-12345-abcdef"


Response:
{
"success": true,
"tx": {
"id": "uuid-12345-abcdef",
"date": "2025-12-07T12:00:00.000Z",
"from": "11111",
"to": "22222",
"amountSats": 12345,
"amountCoins": "0.00012345"
}
} 

7) Errors & mappings

  • 429 — rate-limit exceeded or queue full: { error: "RATE_LIMIT_EXCEEDED" } or { error: "QUEUE_FULL" }. The wrapper tries caching/persistence before returning 429. fileciteturn1file10
  • 400 — invalid parameters: { error: 'Invalid parameters' }.
  • 403 — auth failure: legacy responses often return { error: 'operation failed' } with HTTP 403.
  • 404 — resource not found (eg. card owner not found) or invalid tx lookup.
  • 504 — queue wait timeout: { error: 'QUEUE_TIMEOUT' } (or legacy code mapping).
  • 500 — internal server error: generic { error: 'Internal error' } or { success: false, error: 'Internal error' } depending on route.

8) Appendix — op names & developer notes

Queue operations (payload.op values used by the API):


login, register, logout, account_change, account_unregister, account_update,
transfer, transfer_card, claim, claim_status, get_card, reset_card,
backup_create, backup_list, backup_restore,
get_balance, rank, total_users, tx_lookup, transactions,
bill_list, bill_list_from, bill_list_to, bill_create, bill_pay,
card_info, card_claim, transfer_between_cards,
bill_create_card, bill_pay_card 

Developer note: to change whether a route returns txId, edit doEnqueueAndMap mapping in api.js. The server already provides a recommended path: supply your own txId in the request to keep control of idempotency and later correlate with /api/tx/:txid. fileciteturn1file10

This documentation was generated to match the behavior in your api.js and logic.js files.
Last updated: