How v3 works
In v3 you sign the payment. The server prepares it, checks your signature and watches the payment go through, but it never holds the key to your wallet.
- Sign inYour wallet signs a one-time string from the server (the TON Connect
ton_proofformat), and you get an access key. No account, no API key. - OrderThe API finds the recipient on Fragment, gets the invoice and returns a payment request: which messages to send, where, and by when.
- Check and sign, on your sideYour code checks the request against what you ordered and signs it with your key. The SDK does this for you.
- Submit and confirmThe API checks the signed message, sends it to the TON network and waits until the payment shows up on chain.
The easiest start is the official SDK for Node.js or Python: it runs all four steps and checks every payment before signing it. If you are writing your own client, the whole protocol is described under Without an SDK.
What you need
For any purchase
- A TON wallet, v4r2 or W5 (v5r1), and its 24-word seed phrase.
- TON on the wallet for the order and the network fees. A brand-new wallet works too: the first payment deploys it.
With your own Fragment account
- A KYC-verified Fragment account with a TON wallet and a Telegram account linked.
- The account's cookies. The easiest way is the Cookie-Editor extension on fragment.com: Export, as Header String.
- Pay in USDT from the wallet linked to that account, and keep a little TON on it for gas.
Without KYC
- Nothing else: the order is bought through our verified account.
- Paid in TON only.
Keep the seed phrase and cookies out of your code: in environment variables or a secrets manager. The seed phrase is only used to sign on your side, and the SDK never sends it anywhere.
Quick start with the SDK
Install the SDK and make your first purchase. The example buys 50 Stars with your own Fragment account.
import { FragmentAPIv3 } from "fragment-api"; const api = new FragmentAPIv3({ mnemonic: process.env.TON_SEED, // 24 words, used here to sign and never sent walletType: "v5r1", // your wallet: "v4r2" or "v5r1" (W5) fragmentCookies: process.env.FRAGMENT_COOKIES, // your Fragment account, not needed without KYC trust: { maxTonPerOrder: 50 }, // refuse to sign any order above 50 TON }); console.log("Paying from", api.address); // must be your wallet's address const result = await api.buy({ product: "stars", username: "durov", amount: 50, idempotencyKey: "myshop:1001", // a retry with the same key never buys twice }); console.log(result.success, result.orders[0].ref_id, result.transaction_hash);
import os from fragment_api_lib.v3 import FragmentAPIv3 api = FragmentAPIv3( mnemonic=os.environ["TON_SEED"], # 24 words, used here to sign and never sent wallet_type="v5r1", # your wallet: "v4r2" or "v5r1" (W5) fragment_cookies=os.environ["FRAGMENT_COOKIES"], # your Fragment account, not needed without KYC trust={"max_ton_per_order": 50}, # refuse to sign any order above 50 TON ) print("Paying from", api.address) # must be your wallet's address result = api.buy("stars", "durov", 50, idempotency_key="myshop:1001") print(result["success"], result["orders"][0]["ref_id"], result.get("transaction_hash"))
Check that api.address is your wallet's address. A wrong wallet version gives a different address, and the SDK would sign payments for an empty wallet.
On the first call buy signs in, then creates the order, checks the payment request, signs it, submits it and waits for the confirmation on chain. A successful answer:
{
"success": true,
"message": "Payment completed",
"transaction_hash": "…",
"orders": [{
"id": "…", "ref_id": "Ref#…", "status": "success",
"product": "stars", "amount": 50, "username": "durov",
"cost": 0.75, "currency": "TON", "txid": "…"
}]
}
SDK settings
Constructor options. The Node.js and Python names differ only in case style.
| Node.js | Python | Default | What it is |
|---|---|---|---|
mnemonic | mnemonic | 24 words. Only used to sign on your side | |
walletType | wallet_type | "v4r2" | Wallet version: "v4r2" or "v5r1" (W5) |
fragmentCookies | fragment_cookies | Fragment account cookies, needed for KYC orders | |
trust.maxTonPerOrder | max_ton_per_order | Required. An order above this many TON is not signed | |
trust.maxUsdtPerOrder | max_usdt_per_order | The same in USDT. Required to pay in USDT | |
trust.maxFeePercent | max_fee_percent | 5 | The highest service fee accepted, in percent of the order |
trust.feeWallets, middleWallets, fragmentAddresses | fee_wallets, middle_wallets, fragment_addresses | Your own addresses, added to the ones built into the SDK | |
trust.usdtWallet | usdt_wallet | Your USDT wallet, if it is not the standard one | |
trust.trustServerConfig | trust_server_config | false | With true the SDK also accepts wallets the server names. For testing only |
externalTtlSeconds | external_ttl_seconds | 120 | How long a signed payment stays valid, 300 at most |
baseUrl | base_url | https://api.fragment-api.net | The API address |
timeout | None | HTTP timeout. None waits for the answer while a payment is being confirmed |
In Python the trust options go in a dict: trust={"max_ton_per_order": 50}.
Order fields
| Node.js | Python | What it is |
|---|---|---|
product | product | "stars", "premium" or "ton" |
username | username | Telegram username: durov, @durov or https://t.me/durov |
amount | amount | Stars (at least 50), TON (at least 1) or Premium months (3, 6, 12) |
kyc | kyc | Yes by default: bought with your Fragment account. No: bought through the service, paid in TON |
paymentMethod | payment_method | "ton" (default) or "usdt_ton", with KYC only |
showSender | show_sender | Show you as the sender, yes by default |
idempotencyKey | idempotency_key | Your own id for the order, e.g. "myshop:1001". Strongly recommended |
customOrderInfo | custom_order_info | Any note for your own reference |
Methods
| Node.js | Python | What it does |
|---|---|---|
buy(order) | buy(product, username, amount, ...) | Creates and pays one order |
createOrder(order) | create_order(...) | Creates an order without paying: the order and its payment request |
checkOrder(created) | check_order(created) | Runs the checks on one order without signing anything |
payOrders([...]) | pay_orders([...]) | Pays several orders with one transaction |
prepare([...]) | prepare([...]) | Signs a payment without sending it |
submit(prepared, created) | submit(prepared, created) | Sends a prepared payment and waits for the result |
getOrder(id) | get_order(order_id) | An order and its payment request |
listOrders(limit, offset) | list_orders(limit, offset) | Your orders, newest first |
userInfo(username) | user_info(username) | Looks a Telegram user up on Fragment |
walletInfo() | wallet_info() | Your wallet's address, state, seqno and balances |
config() | config() | The service's network, wallets and fees |
revoke() | revoke() | Deletes the access key on the server |
address | address | Your wallet address (UQ…) |
Examples
Premium without KYC
No Fragment account and no cookies, paid in TON.
const api = new FragmentAPIv3({ mnemonic: process.env.TON_SEED, walletType: "v5r1", trust: { maxTonPerOrder: 50 } }); await api.buy({ product: "premium", username: "durov", amount: 3, kyc: false });
api = FragmentAPIv3(mnemonic=os.environ["TON_SEED"], wallet_type="v5r1", trust={"max_ton_per_order": 50}) api.buy("premium", "durov", 3, kyc=False)
Paying in USDT
With your own Fragment account, from the wallet linked to it, and with a USDT limit set. Keep a little TON on the wallet: every USDT transfer carries some TON for gas, and what is not used comes back.
const api = new FragmentAPIv3({ mnemonic: process.env.TON_SEED, walletType: "v5r1", fragmentCookies: process.env.FRAGMENT_COOKIES, trust: { maxTonPerOrder: 50, maxUsdtPerOrder: 100 }, }); await api.buy({ product: "stars", username: "durov", amount: 500, paymentMethod: "usdt_ton" });
api = FragmentAPIv3(
mnemonic=os.environ["TON_SEED"],
wallet_type="v5r1",
fragment_cookies=os.environ["FRAGMENT_COOKIES"],
trust={"max_ton_per_order": 50, "max_usdt_per_order": 100},
)
api.buy("stars", "durov", 500, payment_method="usdt_ton")
Many orders in one transaction
A W5 wallet sends up to 255 messages at once, a v4r2 wallet up to 4. A KYC order usually takes two messages (Fragment and the fee), an order without KYC one.
const orders = []; for (const username of ["alice", "bob", "carol"]) { const created = await api.createOrder({ product: "stars", username, amount: 50, idempotencyKey: `myshop:${username}:50` }); await api.checkOrder(created); // a bad order is refused on its own, not with the whole batch orders.push(created); } const result = await api.payOrders(orders);
orders = [] for username in ["alice", "bob", "carol"]: created = api.create_order("stars", username, 50, idempotency_key=f"myshop:{username}:50") api.check_order(created) # a bad order is refused on its own, not with the whole batch orders.append(created) result = api.pay_orders(orders)
Every signed payment takes the wallet's next seqno, so pay from one wallet one payment at a time: batch concurrent orders together, or queue them.
Surviving a crash
Store the signed payment before sending it. Sending the same payment again is always safe: the network applies it only once.
const created = await api.createOrder({ product: "stars", username: "durov", amount: 50, idempotencyKey: "myshop:1002" }); const prepared = await api.prepare([created]); await db.save("payment:myshop:1002", prepared); // your storage const result = await api.submit(prepared, [created]); // after a restart, with the same prepared payment: // await api.submit(await db.load("payment:myshop:1002"));
created = api.create_order("stars", "durov", 50, idempotency_key="myshop:1002") prepared = api.prepare([created]) db.save("payment:myshop:1002", prepared) # your storage result = api.submit(prepared, [created]) # after a restart, with the same prepared payment: # api.submit(db.load("payment:myshop:1002"))
Handling errors
Errors come as an exception with an error_code. Two of them need care: the money may have left the wallet, so reconcile the order instead of buying it again.
try { await api.buy({ product: "stars", username: "durov", amount: 50, idempotencyKey: "myshop:1003" }); } catch (e) { if (e.error_code === "TRANSFER_AMBIGUOUS" || e.error_code === "SUBMIT_OUTCOME_UNKNOWN") { // the money may have left the wallet: reconcile later, never buy it again } else { console.error(e.error_code, e.message); } }
from fragment_api_lib.exceptions import FragmentAPIError try: api.buy("stars", "durov", 50, idempotency_key="myshop:1003") except FragmentAPIError as e: code = getattr(e, "error_code", None) if code in ("TRANSFER_AMBIGUOUS", "SUBMIT_OUTCOME_UNKNOWN"): pass # the money may have left the wallet: reconcile later, never buy it again else: print(code, e)
Without an SDK, step by step
You can also talk to the API over plain HTTP. This is the whole protocol: what to send, what to sign and what to check. The wallet key stays with you here too.
Ground rules
- Every request goes over HTTPS to
https://api.fragment-api.net. A request with data sent tohttp://getsHTTPS_REQUIRED. - Request and response bodies are JSON. Every answer has
success, and an error also haserror_codeandmessage. - The access key goes in the
Authorization: Bearer <auth_key>header, never in a URL: URLs end up in logs. - Only
GET /v3/auth/challenge,POST /v3/authandGET /v3/configwork without a key.
Step 1. Sign in with your wallet
Ask for a one-time string. It is valid for 5 minutes and accepted once, so a signature someone intercepts can not be used again.
curl https://api.fragment-api.net/v3/auth/challenge
{ "success": true, "nonce": "<base64url>", "expires_at": 1790000300 }
Sign a ton_proof with your wallet key. It is the standard TON Connect format, so any wallet that supports TON Connect can sign you in as well.
domain:api.fragment-api.net. A signature made for another domain is refused.timestamp: the current time in seconds, within 5 minutes either way.payload:fragment-api/v3:<nonce>:<sha256 of the cookie string, hex>. Without cookies the last part is empty:fragment-api/v3:<nonce>:- The wallet address: workchain 0, the default subwallet. The server computes it from the public key and the wallet version, so a wallet that is not deployed yet signs in the same way.
message = "ton-proof-item-v2/"
‖ workchain (int32, big-endian) ‖ address hash (32 bytes)
‖ domain length (uint32, little-endian) ‖ domain (utf-8)
‖ timestamp (uint64, little-endian)
‖ payload (utf-8)
signature = Ed25519(private_key, sha256(0xffff ‖ "ton-connect" ‖ sha256(message)))
The same in Python. The address (workchain and its 32-byte hash) comes from your wallet library, such as tonutils or pytoniq.
import hashlib, struct from nacl.signing import SigningKey # pip install pynacl def ton_proof_signature(private_key: bytes, workchain: int, address_hash: bytes, domain: str, timestamp: int, payload: str) -> bytes: domain_bytes = domain.encode() message = (b"ton-proof-item-v2/" + struct.pack(">i", workchain) + address_hash + struct.pack("<I", len(domain_bytes)) + domain_bytes + struct.pack("<Q", timestamp) + payload.encode()) digest = hashlib.sha256(b"\xff\xff" + b"ton-connect" + hashlib.sha256(message).digest()).digest() return SigningKey(private_key[:32]).sign(digest).signature
Send the signature and get your access key. fragment_cookies is only needed for KYC orders, up to 8 KB.
curl https://api.fragment-api.net/v3/auth \ -H "Content-Type: application/json" \ -d '{ "public_key": "<64 hex>", "wallet_type": "v5r1", "fragment_cookies": "stel_ssid=…; stel_token=…", "proof": { "timestamp": 1790000000, "domain": "api.fragment-api.net", "payload": "fragment-api/v3:<nonce>:<sha256 hex>", "signature": "<base64, 64 bytes>" } }'
{
"success": true,
"auth_key": "<64 characters>",
"wallet": { "address": "0:…", "address_friendly": "UQ…", "wallet_type": "v5r1" }
}
A key expires after 30 idle days and after 365 days at most. The server keeps the cookies encrypted under that key. To delete a key sooner, send DELETE /v3/auth with it in the header.
Sign-in errors: PROOF_DOMAIN (a signature for another domain), PROOF_PAYLOAD (no nonce in the payload, or the cookie hash does not match), PROOF_INVALID (the signature, the time or the nonce: ask for a new nonce), INVALID_FRAGMENT_COOKIES.
Step 2. The order and its payment request
curl https://api.fragment-api.net/v3/orders \ -H "Authorization: Bearer $AUTH_KEY" \ -H "Content-Type: application/json" \ -d '{ "product": "stars", "username": "durov", "amount": 50, "kyc": true, "payment_method": "ton", "show_sender": true, "idempotency_key": "myshop:1001" }'
The fields are the same as in the SDK: product, username, amount (Stars and TON up to 1,000,000), kyc, payment_method, show_sender, idempotency_key, custom_order_info.
{
"success": true,
"order": {
"id": "…", "ref_id": "Ref#…", "status": "created", "product": "stars",
"amount": 50, "username": "durov", "cost": 0.75, "currency": "TON",
"payment_method": "ton", "valid_until": 1790000900
},
"payment": {
"valid_until": 1790000900,
"network": "-239",
"from": "0:…",
"messages": [
{ "address": "EQBAjaOy…", "amount": "750000000", "payload": "<base64 BoC>", "role": "fragment" },
{ "address": "UQ…", "amount": "15000000", "payload": "<base64 BoC>", "role": "fee" }
]
},
"recipient_id": "…"
}
messages is exactly what TON Connect's sendTransaction takes. Each message has a role:
| Role | When | What it is |
|---|---|---|
fragment | with KYC | Fragment's own message, as is. To pay in USDT it goes to your USDT wallet and carries a USDT transfer to Fragment's address |
fee | with KYC | The service fee: TON, or a USDT transfer, to the fee wallet. Absent when no fee is charged |
middle | without KYC | The order price and the fee in one TON transfer to the service's wallet. The service pays Fragment itself once it lands, exactly once |
A repeat with the same idempotency_key returns the same order and the same payment request. If the order under that key expired unpaid, a new one is created. The answer also has recipient_id, the recipient's id on Fragment, so you can apply your own block list before paying.
Step 3. Checks before signing
The server does not hold your seed phrase, but it does tell you what to sign. So sign nothing on its word alone. The SDK checks the following, and your own client should do the same:
- The order is the one you asked for: product, amount, recipient, KYC or not, currency. Check against your own request, not against the server's copy of it. The messages follow from that: a KYC order never needs a
middlemessage. - The
fragmentmessage goes only to a Fragment address from your own list, never one the server names. To pay in USDT that address must be the recipient inside the payload, and the message itself goes to your USDT wallet. - A USDT transfer message goes only to your own USDT wallet. Compute its address yourself from the USDT master and the wallet code: otherwise the server could point it at your wallet of another token.
feeandmiddlemessages go only to the service wallets you have pinned yourself.GET /v3/configlists them, but it should not be your source of trust.- The fee is within your ceiling (5% of the order in the SDK), in the same currency.
- The order costs no more than your TON limit, and your USDT limit when paying in USDT. What Fragment's invoice buys can not be checked, so a per-order limit is required.
- The payment is short-lived:
valid_until = min(payment.valid_until, now + 120 s), send mode 3.
The Fragment addresses pinned in the SDK:
UQBAjaOyi2wGWlk-EDkSabqqnF-MrrwMadnwqrurKpkla4QB UQCFJEP4WZ_mpdo0_kMEmsTgvrMHG7K_tWY16pQhKHwoOtFz UQBeab7D38RIwypegbN7YZgQzwDbb8QfMMwY8ouJc3qPl4CJ
Step 4. Signing
Get your wallet's state:
curl https://api.fragment-api.net/v3/wallet -H "Authorization: Bearer $AUTH_KEY"
{
"success": true,
"address": "0:…", "address_friendly": "UQ…", "wallet_type": "v5r1",
"state": "active", "seqno": 42, "balance_nano": "…", "usdt_raw": "…"
}
Build your wallet's external message:
- with exactly the messages of
payment.messages, in the same order, and for several orders their messages one order after another; - send mode 3 on every message;
seqnofrom/v3/wallet,valid_untilas in step 3;- while
stateis notactive, include theStateInit: the first payment deploys the wallet.
Sign it with your wallet key. The result, the external message's BoC in base64, is what step 5 sends. On mainnet the signature has no domain prefix and covers the cell hash.
In a browser you can do the same through TON Connect: pass messages and validUntil to sendTransaction and send the boc it returns in step 5. Your code runs the checks from step 3 before calling the wallet.
Never sign an order again while you do not know how its last submit ended. Sign again only after TRANSFER_NOT_SENT with resign: true, and only with the same seqno: then the old and the new message can not both land.
Step 5. Submit and the result
curl https://api.fragment-api.net/v3/orders/submit \ -H "Authorization: Bearer $AUTH_KEY" \ -H "Content-Type: application/json" \ -d '{ "orders": ["<order id>"], "boc": "<base64 of the signed message>" }'
One message can pay several orders: list them in orders in the order their messages appear in the transaction. A W5 wallet pays up to 127 KYC orders this way. The BoC is at most 64 KB.
Before anything is sent, the server checks that:
- every order was created by this wallet, otherwise
ORDER_WALLET_MISMATCH; - the message is addressed to your wallet and signed with its key, and a
StateInit, if present, matches the address; - every message matches the request: address, amount, payload and mode 3, nothing added and nothing missing;
valid_untilhas not passed and is not after the orders' payment deadline;- the orders are yours and not paid yet.
Then the server sends the message to the network and waits for it on chain. A successful answer:
{
"success": true,
"message": "Payment completed",
"transaction_hash": "…",
"orders": [{ "id": "…", "ref_id": "Ref#…", "status": "success", "txid": "…" }]
}
What to do with other answers:
| Answer | What it means | What to do |
|---|---|---|
500 TRANSFER_FAILED | The network rejected the payment of some orders | See the statuses in orders: those orders are failed |
500 TRANSFER_AMBIGUOUS | The outcome is not known yet, the orders are processing | Do not sign again. Check the order later with GET /v3/orders/{id} |
503 TRANSFER_NOT_SENT, resign: true | The message expired without landing | Sign again with the same seqno and submit |
503 TRANSFER_NOT_SENT | The message did not reach the network | Submit the same BoC again |
503 TON_SERVICE_UNAVAILABLE | The TON network is out of reach for a moment, nothing was charged | Submit the same BoC later |
400 INVALID_SIGNED_MESSAGE | What was signed is not what was requested. The reason is in reason | Fix how the message is built |
400 INSUFFICIENT_BALANCE | Not enough TON or USDT, nothing was sent. What is needed is in need_ton_nano and need_usdt_raw | Top up and send the payment again: the same BoC while it is valid, or one signed again with the same seqno |
400 ORDER_EXPIRED | Fragment's invoice expired | Create the order again; the same idempotency_key is fine |
You can check an order's status at any time. While the order is created, the answer also has its payment request.
curl https://api.fragment-api.net/v3/orders/<order id> -H "Authorization: Bearer $AUTH_KEY"
Endpoint reference
The full schema, with every field of every answer, is in Swagger. Here is each endpoint in short.
/v3/auth/challengeno keyA one-time string to sign in with: nonce and expires_at.
/v3/authno keySign in with a ton_proof. Body: public_key, wallet_type, fragment_cookies (optional) and proof. Answer: auth_key and wallet.
/v3/authkeyDeletes the key the request was sent with.
/v3/configno keyThe service's settings: network, fee_wallet, fee_percent, middle_wallet, no_kyc_fee_percent, fragment_addresses, usdt_jetton_master.
/v3/walletkeyYour wallet: address, address_friendly, wallet_type, state (active, uninit, nonexist or frozen), seqno, balance_nano, usdt_raw.
/v3/user_info?username=durovkeyLooks a user up on Fragment: found, name, recipient_id, photo.
/v3/orderskeyCreates an order and its payment request, see step 2.
/v3/orders?limit=10&offset=0keyYour orders, newest first. limit is 1 to 100.
/v3/orders/{id}keyAn order, plus its payment request while it is unpaid.
/v3/orders/submitkeyPays with a signed message, see step 5.
The order object
| Field | What it is |
|---|---|
id | The order id |
ref_id | Fragment's order number, like Ref#… |
status | created, processing, success or failed |
product, amount, username | What, how much and for whom |
cost, currency | The order price and its currency: TON or USDT |
payment_method, kyc | How it is paid, and whether it is bought with KYC |
valid_until | Until when (unix time) the invoice can be paid |
txid | The payment's transaction hash |
error | Why, if the order is failed |
api_version | 3 for v3 orders |
Order statuses
| Status | What it means |
|---|---|
created | The order exists and waits for payment until valid_until |
processing | The payment was sent and its outcome is still being confirmed. Never pay such an order again |
success | Paid: Fragment received the payment |
failed | The network rejected the order's payment; the reason is in error |
When the outcome is unclear, the order stays processing until it is reconciled with the chain. The service never marks such an order failed and never pays it again on a guess.
Errors
An error comes with an HTTP status and the body {"success": false, "error_code": "…", "message": "…"}. The main codes:
| Code | What happened | What to do |
|---|---|---|
AUTH_KEY_INVALID | The key is wrong, expired or deleted | Sign in again |
PROOF_DOMAIN, PROOF_PAYLOAD, PROOF_INVALID | The sign-in signature was refused | Ask for a new nonce and sign again |
INVALID_FRAGMENT_COOKIES | The cookies do not work | Export the cookies again |
FRAGMENT_COOKIES_REQUIRED | A KYC order without cookies | Pass the cookies when signing in, or order without KYC |
INVALID_AMOUNT, INVALID_DURATION, INVALID_USERNAME_FORMAT, INVALID_PAYMENT_METHOD | Wrong order fields | Fix the request |
ORDER_NOT_POSSIBLE | Fragment can not make this order, for example because of the recipient's minimum | Do not retry; the reason is in message |
ORDER_CREATION_FAILED | A temporary failure on Fragment's side | Retry later with the same idempotency_key |
NO_KYC_UNAVAILABLE | Buying without KYC is not available right now | Try later |
NO_KYC_USDT_UNAVAILABLE | USDT without KYC | Orders without KYC are paid in TON |
WALLET_NOT_CONNECTED_TO_FRAGMENT | USDT from the wrong wallet | Pay USDT orders from the wallet linked to your Fragment account |
IDEMPOTENCY_KEY_HELD_BY_V2_ORDER | The key belongs to an unpaid v2 order | Pay it through v2, or wait until it expires |
ORDER_WALLET_MISMATCH | The order was created by another wallet | Pay with the wallet that created it |
ORDER_ALREADY_PROCESSING, ORDER_ALREADY_PROCESSED | The order is being paid or already paid | Check its status |
ORDER_EXPIRED | The invoice expired | Create the order again |
INSUFFICIENT_BALANCE | Not enough funds | Top up the wallet |
INVALID_SIGNED_MESSAGE | What was signed is not what was requested | See reason |
TRANSFER_NOT_SENT | The payment did not reach the network | See step 5 |
TRANSFER_FAILED | The network rejected the payment | See the order statuses |
TRANSFER_AMBIGUOUS | The outcome is not known | Do not pay again; check the order later |
TON_SERVICE_UNAVAILABLE, BALANCE_CHECK_ERROR | A temporary failure, nothing was charged | Repeat the same request later |
HTTPS_REQUIRED | The request came over http:// | Use https:// |
SDK only:
| Code | What happened | What to do |
|---|---|---|
UNTRUSTED_PAYMENT | The SDK refused to sign the payment | Nothing was sent. Check your trust settings: a per-order limit is required |
SUBMIT_OUTCOME_UNKNOWN | The API did not answer the submit | Submit the same prepared payment later. Never sign it again |
Retries without paying twice
- Creating an order. Pass an
idempotency_key. A repeat with the same key returns the same order, even after your server restarts. Keys are scoped to your wallet and never clash with other clients' keys. - Submitting a payment. The same BoC can be submitted any number of times: the network applies it once, and the server answers with the orders' current state.
- An unknown outcome. After
TRANSFER_AMBIGUOUSor a lost answer, do not sign the order again. Check its status until it issuccessorfailed. - Signing again. Only after
TRANSFER_NOT_SENTwithresign: true, and only with the sameseqno. - One wallet, one payment at a time. Each payment takes the next
seqno. Put concurrent orders into one transaction, or queue them.
Wallets and batch payments
| Wallet | Messages per transaction | KYC orders at once | Subwallet |
|---|---|---|---|
v4r2 | up to 4 | 2 | 698983191 |
v5r1 (W5) | up to 255 | up to 127 | wallet id 2147483409 on mainnet |
Workchain 0, the default subwallet. A new wallet is deployed by its first payment: until it is active, the message must carry the StateInit.
For many orders, use W5: one transaction pays for more than a hundred orders, with fewer network fees.
Fees
- With your own Fragment account: 2% of the order, as a separate
feemessage in the same transaction as the payment. - Without KYC: 3% of the order, included in the one transfer to the service's wallet.
- Your wallet pays the TON network fees, as with any transfer.
GET /v3/configshows the current rates. The SDK will not sign an order whose fee is abovemaxFeePercent.
Moving from v2
v1 and v2 send your seed phrase to the server. They are deprecated and will be switched off. Their answers already carry Deprecation: true and a Link header to this guide, plus Sunset once a date is set. After that they answer 410 with API_VERSION_GONE.
v2 (FragmentAPIClient) | v3 (FragmentAPIv3) |
|---|---|
new FragmentAPIClient(...) and auth(cookies, seed) | new FragmentAPIv3({ mnemonic, walletType, fragmentCookies }) |
buyStars(username, amount, authKey, ...) | buy({ product: "stars", username, amount }) |
buyStarsWithoutKYC(username, amount, authKey) | buy({ product: "stars", username, amount, kyc: false }) |
buyPremium(username, months, ...) | buy({ product: "premium", username, amount: months }) |
buyTon(username, amount, ...) | buy({ product: "ton", username, amount }) |
createStarsOrder, payStarsOrder, getStarsOrderStatus | createOrder, payOrders, getOrder |
getUserInfo(username) | userInfo(username) |
getBalance(authKey) | walletInfo() |
getOrders(authKey, limit, offset) | listOrders(limit, offset) |
For v3 we recommend a new wallet: the old one's seed phrase has already been sent to a server, while in v3 it never leaves your machine. Orders of the old wallet stay under it, and we can link them to the new one on request.
Help
Ask the developer on Telegram about integration and fees. Swagger lists every endpoint with its fields, and the SDK source code is open.