For developers

API documentation

Accept payments with invoices and send withdrawals using an API key with an HMAC signature.

Base URLhttps://devonesite.site/api

01 Getting started

The OneSix API lets you accept payments with invoices and send withdrawals (transfers) programmatically. Requests are authorized with a project API key and an HMAC signature.

Integration takes three steps:

  1. Create a project in your dashboard and confirm domain ownership (DNS).
  2. Get the project API key: an id + secret pair.
  3. Sign requests with the key (HMAC-SHA256) and call the API methods.
text
Registration → Project (pending) → Domain verification → Moderation → Project (active) → API key → API requests

An API key works only when both the key and the project are active. While the project is under moderation (pending) or rejected (rejected), key requests are refused.

Identifier formats

EntityFormat
id of a project, invoice, API keyPlain UUID (aabbccdd-...)
user.id, logo_img_id, swap id in conversions, operation idEncoded string (as elsewhere in the dashboard)

02 Request signing (HMAC)

Every public API request is authorized with an API key and signed with HMAC-SHA256. Sessions (cookie / Authorization: Bearer) are not used for the API.

Keys

KeyWhere to get itPurpose
id (X-API-Key-ID)Dashboard → project → API keys, field idKey identifier sent in a header
key (secret)Same place, field key (hex, 64 chars)Secret used to compute the signature

A project has one active key (status: active). Key rotation is not available yet. The key secret is returned by GET /api/projects/{project_id}/api-keys/, keep it somewhere safe.

Request headers

HeaderDescription
X-API-Key-IDKey UUID (field id, not the secret)
X-TimestampUnix timestamp in seconds. Must be within ±15 seconds of server time
X-SignatureHMAC-SHA256 in hex
Content-Typeapplication/json for requests with a body

The signature is required for every public API method (GET and POST).

Signing algorithm

Step 1. Build the data object:

json
{
  "params": "url_encoded_query_string",
  "body": {},
  "path": "/api/invoice/"
}
  • params: the query string as in the URL, without ? (e.g. project_id=...&page=1). Empty string "" when there is no query.
  • body: the parsed JSON request body. Empty object {} for GET requests without a body.
  • path: the request path with slashes, including the /api prefix (e.g. /api/invoice/).

Step 2. Serialize the object with sorted keys:

python
json.dumps(data, separators=(",", ":"), sort_keys=True)

sort_keys=True is mandatory. You sign the object after the body is parsed as JSON, not the raw string.

Step 3. Compute the signature:

text
payload   = str(timestamp) + json_string
signature = HMAC-SHA256(key=secret, message=payload).hexdigest()

Step 4. Send X-API-Key-ID, X-Timestamp and X-Signature as headers.

Important

  • X-Timestamp must be within ±15 seconds of server time. If your clock drifts, sync with GET /api/-/time/.
  • A signature is single-use. Reusing the same X-Signature returns 401 Signature already used!. Every request needs a fresh timestamp and signature.
  • TOTP (2FA) is not required for API calls.
  • If the key has an IP allowlist, requests are accepted only from those addresses.

Generating the signature

import hmac, hashlib, json, time

def sign(secret: str, timestamp: int, data: dict) -> str:
    s = json.dumps(data, separators=(",", ":"), sort_keys=True)
    payload = f"{timestamp}{s}"
    return hmac.new(secret.encode(), payload.encode(), hashlib.sha256).hexdigest()

secret     = "<key>"
api_key_id = "<uuid>"
path       = "/api/invoice/"
params     = ""
body       = {
    "name": "Order 1",
    "target_amount": 100,
    "is_termless": True,
    "is_payer_comission": True,
    "description": "",
    "private_description": "",
}

ts   = int(time.time())
data = {"params": params, "body": body, "path": path}
signature = sign(secret, ts, data)

headers = {
    "X-API-Key-ID": api_key_id,
    "X-Timestamp": str(ts),
    "X-Signature": signature,
    "Content-Type": "application/json",
}
$secret = "<key>";
$path   = "/api/invoice/";
$params = "";
$body   = [
    "name"               => "Order 1",
    "target_amount"      => 100,
    "is_termless"        => true,
    "is_payer_comission" => true,
    "description"        => "",
    "private_description"=> "",
];

$data = ["params" => $params, "body" => $body, "path" => $path];
ksort($data); // params, path, body -> sort_keys=True
$json      = json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
$timestamp = time();
$signature = hash_hmac("sha256", $timestamp . $json, $secret);
const crypto = require("crypto");

function sign(secret, timestamp, data) {
  // sort top-level keys, compact JSON without spaces
  const ordered = {};
  Object.keys(data).sort().forEach((k) => (ordered[k] = data[k]));
  const s = JSON.stringify(ordered);
  return crypto.createHmac("sha256", secret).update(`${timestamp}${s}`).digest("hex");
}

const secret = "<key>";
const data = {
  params: "",
  body: { name: "Order 1", target_amount: 100, is_termless: true, is_payer_comission: true, description: "", private_description: "" },
  path: "/api/invoice/",
};
const ts = Math.floor(Date.now() / 1000);
const signature = sign(secret, ts, data);

In JS make sure JSON.stringify produces the same output as Python json.dumps(..., separators=(",",":"), sort_keys=True): no spaces and keys sorted at every level. Sort keys of nested objects in body recursively if needed.

Request examples

# params = ""  (no query)
# body   = {}  (no body)
# path   = "/api/invoice/my/<invoice_id>/"
# data   = {"body":{},"params":"","path":"/api/invoice/my/<invoice_id>/"}

curl "https://devonesite.site/api/invoice/my/<invoice_id>/" \
  -H "X-API-Key-ID: <UUID>" \
  -H "X-Timestamp: 1718659200" \
  -H "X-Signature: <hex>"
curl "https://devonesite.site/api/invoice/" \
  -X POST \
  -H "Content-Type: application/json" \
  -H "X-API-Key-ID: <UUID>" \
  -H "X-Timestamp: 1718659200" \
  -H "X-Signature: <hex>" \
  -d '{"name":"Order 1","target_amount":100,"is_termless":true,"is_payer_comission":true,"description":"","private_description":""}'

Server time

GET/api/-/time/No authServer time (unix)

Returns the current server time (unix, a number). Use it to sync X-Timestamp if the client clock may drift by more than 15 seconds.

Which methods accept an API key

AuthorizationMethods
API key (HMAC)POST /api/invoice/ · GET /api/invoice/my/{invoice_id}/ · POST /api/gw/transfer/
No authorizationGET /api/invoice/{invoice_id}/ · GET /api/gw/currencies/ · GET /api/gw/currencies/rates/ · GET /api/gw/fees/ · GET /api/gw/fees/calculate/ · GET /api/-/time/
Session only (dashboard)Project management, verification, api-keys, operations, conversions, lists filtered by project_id

Any method outside the "API key (HMAC)" list returns 401 API key not allowed for this endpoint! when called with a key.

Signing and authorization errors

Code 401, detailReason
Invalid API key!Wrong X-API-Key-ID
API key deactivated!The key is archived or the project is not active
Invalid signature!Signature mismatch (check serialization, path, timestamp)
Signature already used!A one-time signature was reused
API key not allowed for this endpoint!The method does not accept API key authorization
Token not provided!No authorization provided

03 Invoices

An invoice is a request to pay a fixed amount. Once created, it returns a payment address and a payment page. The amount is set in fiat (target_amount), payment arrives in USDT at the current rate (rate_pair: USDT_RUB, rate is in the response).

POST/api/invoice/API keyCreate an invoice
GET/api/invoice/my/{invoice_id}/API keyYour invoice (with private_description)
GET/api/invoice/{invoice_id}/No authPublic invoice page

POST /api/invoice/

Request body (InvoiceCreateDTO)

FieldTypeReq.Description
namestring✔Name. Min 3 chars, Latin/Cyrillic
target_amountnumber✔Amount to pay. Minimum 0.1
is_termlessboolNo deadline. Defaults to false
deadlinedatetime (ISO 8601)Invoice deadline. Required when is_termless=false; must be null or omitted when is_termless=true
is_payer_comissionboolThe payer covers the fee. Defaults to true
descriptionstringPublic description
private_descriptionstringPrivate description (visible only to you)
imgstring | nullInvoice image
project_iduuid | nullNot used on this method. With a key, project_id is taken from the key’s project automatically

2FA is not required with an API key.

Response (MyInvoiceDTO)

FieldTypeDescription
iduuidInvoice ID
addressstringPayment address
namestringName
imgstring | nullImage
descriptionstringPublic description
private_descriptionstringPrivate description
statusenumopened / expired / executed / closed
target_amountnumberAmount to pay
current_amountnumberAmount paid so far
comissionnumberFee
is_termlessboolNo deadline
is_payer_comissionboolThe payer covers the fee
deadlinedatetime | nullDeadline
rate_pairenumRate pair (USDT_RUB, ALTYN_USDT_RUB)
ratenumberRate at creation time
created_atdatetimeCreated at
closed_atdatetime | nullClosed at
project_iduuid | nullInvoice project

GET /api/invoice/my/{invoice_id}/

Returns your invoice (MyInvoiceDTO, with private_description). With a key an extra filter applies: only invoices of the key’s project are available.

GET /api/invoice/{invoice_id}/

Public invoice page, no authorization. Returns PublicInvoiceDTO: same as MyInvoiceDTO but without private_description and project_id. Used for the payment page.

Invoice statuses

statusDescription
openedOpen, awaiting payment
executedPaid (amount reached)
expiredDeadline passed without full payment
closedClosed

Status tracking. The current API version has no outgoing status webhooks. Poll GET /api/invoice/my/{invoice_id}/ (status, current_amount) for the current status.

04 Withdrawals (Transfer)

Create a withdrawal to an external blockchain address.

POST/api/gw/transfer/API keyCreate a withdrawal

POST /api/gw/transfer/

Request body (WithdrawalCreateDTO)

FieldTypeReq.Description
symbol_idstring✔Symbol ID (currency on a network), e.g. USDTTRC20. List: GET /api/gw/currencies/
to_addressstring✔Recipient address
amountnumber✔Amount. Greater than 0

2FA is not required with an API key.

Response: a JSON object of the withdrawal transaction (type, status, operation ID).

As with invoices, there are no withdrawal webhooks. Track the status by polling project operations (GET /api/projects/{project_id}/operations/) or in the dashboard.

05 Reference data

Public methods without authorization that are useful for integration.

GET/api/gw/currencies/No authCurrencies and symbols
GET/api/gw/currencies/rates/No authCurrency pair rates
GET/api/gw/fees/No authFees and limits per symbol
GET/api/gw/fees/calculate/No authFee calculation for an amount
GET/api/-/time/No authServer time (unix)

GET /api/gw/currencies/

Supported currencies. Use the symbol field as symbol_id when creating a withdrawal.

Query: search - filter. The response is paginated (data[], pages_count, current_page, total).

Each item (CurrencyDTO):

FieldDescription
symbolSymbol ID for the API (symbol_id)
short_nameShort currency name
nameFull name
blockchainNetwork (id, name, explorer links)
contract_addressToken contract address, if any
precisionPrecision
is_activeWhether the symbol is available
logo_urlLogo
min_deposit_confirms / min_withdrawal_confirmsConfirmations for deposit / withdrawal

GET /api/gw/fees/

Fees and minimum amounts per symbol (GwFeeDTO[]):

FieldDescription
symbol_idSymbol ID
min_deposit_amountMinimum deposit
min_withdrawal_amountMinimum withdrawal
deposit_fee_amount / deposit_fee_percentageDeposit fee (fixed / percent)
withdrawal_fee_amount / withdrawal_fee_percentageWithdrawal fee (fixed / percent)
swap_fee_amount / swap_fee_percentageSwap fee
swap_from_min_amount / swap_to_min_amountSwap minimums

GET /api/gw/fees/calculate/

Fee calculation for a specific amount.

Query (all required): amount, operation_type (deposit / withdrawal / swap), symbol_id.

Response (GwServiceFeeCalculationDTO): symbol_id, operation_type, amount, fee_amount, net_amount, gross_amount.

GET /api/gw/currencies/rates/

Currency pair rates (CurrencyRateDTO[]):

FieldDescription
from_symbol_idSource currency symbol/code
to_symbol_nameQuote currency code
valueRate
created_atUpdated at

06 Project management

Creating and configuring projects, domain verification and key issuance are done from the dashboard with a session (cookie or Authorization: Bearer), without an API key. You only need this section to automate the dashboard; for a regular integration just follow the steps in the UI.

Projects

GET/api/projects/SessionYour projects. Without status - all except disabled. Filter ?status=pending|active|rejected|disabled
POST/api/projects/SessionCreate a project (status pending). The key is created right away but is not in the response: get it via api-keys
GET/api/projects/{project_id}/SessionProject details
PATCH/api/projects/{project_id}/SessionUpdate a project (all fields optional). disabled projects cannot be edited
DELETE/api/projects/{project_id}/SessionSoft delete → status: disabled. Response {"status": true}

POST /api/projects/ (ProjectCreateDTO)

FieldTypeReq.Description
namestring✔3-128 chars, unique per user
categoryenum✔goods / services / digital / other
site_urlstring (uri)✔http(s), a public host (not localhost or a private IP)
contact_telegramstring✔1-64 chars
descriptionstring | nullDescription

PATCH /api/projects/{project_id}/ (ProjectUpdateDTO): category, description, logo_img_id (encoded id of your image), brand_color (# + 6 hex). The logo must belong to you.

Project details (ProjectDTO)

json
{
  "id": "uuid",
  "public_id": "aabbccddeeff",
  "name": "Shop",
  "status": "pending",
  "category": "goods",
  "site_url": "https://shop.example.com",
  "contact_telegram": "@shop",
  "description": null,
  "logo_img_id": null,
  "brand_color": null,
  "is_site_verified": false,
  "verify_method": null,
  "verify_token": null,
  "verified_at": null,
  "rejected_reason": null,
  "created_at": "...",
  "updated_at": "..."
}

status: pending → active (after verification and moderator approval) / rejected / disabled.

Domain verification (DNS)

POST/api/projects/{project_id}/verification/SessionStart verification. Only when status=pending and is_site_verified=false
POST/api/projects/{project_id}/verification/check/SessionCheck the TXT record (no body)

POST /verification/: body {"method": "dns"}. Response:

json
{
  "method": "dns",
  "verify_token": "<hex>",
  "instruction": "Add a TXT record for shop.example.com with value j1ulwfmkyj0-site-verification=<token>"
}

Tell the user to add a TXT record on the site_url host with the value j1ulwfmkyj0-site-verification=<verify_token>.

POST /verification/check/: if the TXT record is found, is_site_verified=true and verified_at is set, the status stays pending (awaiting moderation). If already verified, returns 200 without an error. If there is no TXT record, returns 400 Site verification failed.

Project API keys

GET/api/projects/{project_id}/api-keys/SessionProject keys with the secret
json
[
  {
    "id": "key uuid, this is X-API-Key-ID",
    "key": "hex secret, 64 chars",
    "status": "active",
    "created_at": "...",
    "archived_at": null
  }
]

status: active / archived. A project has one active key. Rotation is not available yet.

Project operations and conversions

GET/api/projects/{project_id}/operations/?page=1&per_page=10SessionProject transactions
GET/api/projects/{project_id}/conversions/?page=1&per_page=10SessionProject swaps

Pagination: page starts at 1, per_page defaults to 10. The response has current_page = page - 1 (zero-based), pages_count, total, data[].

Operation (ProjectAdminTransactionDTO): id, type, subject (usdt/rub), symbol_id, is_incoming, amount, state (created/pending/dirty_lock/executed/cancelled), tx_hash, description, executed_at, created_at, project_id.

Conversion (SwapOrderResponseDTO): id, from_symbol_id, to_symbol_id, from_amount, to_amount, rate, status (created/processing/executed/cancelled/failed), created_at, project_id, user.

Dashboard lists filtered by project

http
GET /api/invoice/?project_id=<uuid>
GET /api/transaction/?project_id=<uuid>
GET /api/gw/swap_order/?project_id=<uuid>

07 Error codes

Projects and verification (400)

detailReason
Project with this name already existsThe project name is taken
site_url must be an http(s) URLInvalid URL format
site host must be a public addressThe host is localhost / a private IP
brand_color must be a HEX color like #1A2B3CInvalid color
Image not foundLogo image not found
Project is disabledThe project is disabled, editing is not allowed
Project is not pending verificationVerification cannot start in the current status
Site is already verifiedThe domain is already verified
Verification is not startedCheck called before verification started
Site verification failedTXT record not found
Project is not pendingOnly available for pending
Site is not verifiedThe domain is not verified

Authorization and signature (401)

detailReason
Invalid API key!Wrong X-API-Key-ID
API key deactivated!The key is archived or the project is not active
Invalid signature!Signature mismatch (check serialization, path, timestamp)
Signature already used!A one-time signature was reused
API key not allowed for this endpoint!The method does not accept API key authorization
Token not provided!No authorization provided

Not found (404)

detailReason
Project not foundProject not found / not accessible
Invoice not foundInvoice not found
Ready to start?
Create a project in your dashboard, get an API key and start accepting payments.
Open dashboard