Partner quick start
Partner API v1 · revision 1.13 · 9 November 2026 · What changed
From an API key to a working subscription in Happ: the account, the catalogue, a subscriber and its URL, step by step.
All the guides · OpenAPI spec and explorer · Need more? Talk to us · Partner terms
This guide takes you from an API key to a working subscription in Happ, once your partner is set up. Every call is server to server. The key goes in the X-Partner-Key header on your backend and never into an app or a web page you serve. Browsers cannot send the header to us from another site anyway. To try calls by hand, use the Partner API explorer on proofkit.org. For every endpoint, field and error code, see the reference, Partner API. Paying for traffic is covered in Paying for your subscribers' traffic, and subscribers in Russia in Transports and Russia.
Before you start
- No backend? You do not need this guide. A partner that adds its subscribers by hand works from the dashboard alone, with no key: see Without code: the dashboard.
- Six people or more. The partner program is for a service of six people or more. Five people or fewer are served by an ordinary ProofKit account: its own subscription link and up to four Extra links, paid from its balance, each with a monthly limit of its own.
- Access. You sign your partner up yourself, in the partner dashboard; nobody at ProofKit approves it. Sign in at proofkit.org (or create a free account), confirm your email address, turn on two-factor sign-in and press Become a partner on proofkit.org/partners, or open
#/. A checklist takes you through your service, a partner wallet and one deposit: a stake of 5 TON and at least 0.1 TON for traffic, so at least 5.1 TON (the dashboard suggests 5.5 TON). The stake is locked while the partner runs and comes back 30 days after you close the partner. The partner goes live by itself once the deposit has landed. There is no free trial on this path: every GB is paid for from the first byte. For a bigger start, an agreed price, an entry server of your own or olcRTC, talk to us. The terms (prices, the stake, Torrent Guard, acceptable use, suspension; no monthly fee, no minimum spend, no SLA) are at proofkit.org/partner-terms.partner - An owner account. Your partner belongs to one ProofKit account, its owner: the account that signed it up, or, for a partner ProofKit sets up by hand, the email account you named. That account must have two-factor sign-in on (an authenticator app or Telegram codes). It gets the partner dashboard,
#/on proofkit.org, with the plan, the trial if you have one, the balance and the stake, the wallet, top-up, withdrawal, API key, the subscription's settings and, when olcRTC is on for you, olcRTC in use against your share. An account that does not run a partner is offered the sign-up there, or sees "This account does not run a partner" if it cannot start one.partner - An API key (
pk_…). The owner account creates your first key in the dashboard (Create key); ProofKit issues the first key of a partner it sets up by hand. A key is shown once; we keep only its prefix and a hash of it. The owner account rotates the key in the dashboard, at most 5 times an hour. The new key is shown once, and the key it replaces keeps working for 24 hours. Only one previous key is kept: rotating again within those 24 hours ends the older key at once. A key ProofKit issues by hand, without an overlap, ends every older key at once. - Subscriptions switched on for your partner. They are on from the moment a partner that signed itself up goes live; ProofKit switches them on for a partner it sets up by hand, and olcRTC too if agreed. Your branding and which routes the subscription carries are yours to set in the dashboard (step 3). Until subscriptions are on, step 5 answers
403 SUBSCRIPTION_NOT_ENABLED. - A balance. There is no separate test environment: you test on the live network, with traffic you pay for per GB from a balance above your stake. A free trial exists only by agreement with ProofKit. See Trial, then prepaid.
- Happ on a phone, plus an app that runs olcRTC, such as Ghostlane, if ProofKit has switched olcRTC on for you.
PK=https://proofkit.org/api/v1/partner
KEY=pk_xxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx # your key
Conventions used throughout:
- Money is always an integer number of nanoTON: 1 TON = 1,000,000,000 nano.
- GB, in prices and limits, means 1,073,741,824 bytes, with upload and download added together.
- Errors come back as
{"error": {"code": "…", "message": "…"}}. Branch oncode. Every response carries anX-Request-IDheader: quote it when you write to us about a call. - A plain-text 400, 413, 415 or 422 is not from our handlers. The request was refused before a handler ran: no
Content-Type: application/, broken JSON, a missing field, a query value or path id that does not parse (for examplejson from=yesterday, or asession_idthat is not a UUID), or a body over 2 MB.
1. Check the account
curl -s -H "X-Partner-Key: $KEY" $PK/account
{
"partner_id": "…",
"billable": true,
"billing_mode": "metered",
"billing_plan": "trial",
"balance_nano": 0,
"stake_nano": 0,
"credit_limit_nano": 0,
"suspended": false,
"rate_limit_per_min": 300,
"wallet": null,
"escrow_address": "…",
"deposit_link": null,
"trial": {
"until": "2026-10-03T12:00:00Z",
"max_subscribers": 10,
"subscribers": 0,
"max_bytes": 53687091200,
"bytes_used": 0,
"active": true
}
}
| Field | What it tells you |
|---|---|
billable | Must be true. If it is false, step 4 answers 400 PARTNER_NOT_BILLABLE, and ProofKit has to set your terms first. |
billing_plan | trial, prepaid, or exempt (ProofKit covers the traffic). |
billing_mode | Kept for existing integrations. It reads free only on exempt and metered otherwise, including during a free trial. Read billing_plan and trial.active instead. |
trial | Present only while billing_plan is trial. active: true means traffic is free right now. |
balance_nano | Your prepaid balance, before any stake is taken off. Once you pay for traffic, a new session needs balance_nano − stake_nano above zero, because credit_limit_nano is 0 on prepaid. |
stake_nano | The stake locked on your partner: 0 for a partner without one. See GET /. |
wallet, deposit_link | The partner wallet linked for top-ups, and a ton:// deposit link for it. Both are null until a wallet is linked (see Link the partner wallet). |
2. Read the catalogue
curl -s -H "X-Partner-Key: $KEY" $PK/catalogue
The catalogue has one entry for each node you may sell: the nodes ProofKit has approved for automatic routing, public ones plus nodes granted to you. Here is an edge. The prices are illustrative.
{
"node_id": "…",
"node_type": "edge",
"country": "RU",
"price_per_gb_nano": 10000000,
"total_price_per_gb_nano": 12000000,
"fee_bps": 2000,
"entry_region": "RU",
"supported_transports": ["vless_reality", "hysteria2", "vless_xhttp"],
"olcrtc_carriers": [],
"is_public": true,
"sessions_count": 32,
"max_users": 0,
"max_users_effective": 500,
"agent_online": true
}
price_per_gb_nanois the node's price for you, with your agreed price applied if you have one. It is the price before the network fee.total_price_per_gb_nanois what a GB through this node costs you: the price plus the 20% fee (fee_bps: 2000). A session's price is fixed when the session starts. A later drop in the node's price lowers it once the lower price has stood for 3 hours; a rise does not reach a running session. olcRTC traffic is the exception: from 20 November 2026 it has a price of its own, read when the traffic moves (see The olcRTC price).- A route through an edge costs the origin's price plus the edge's price, with the fee taken once on the sum: the two nodes' totals added together, within a nanoton. For example, 10,000,000 + 10,000,000 gives a route price of 24,000,000.
entry_region: "RU"marks an edge in Russia. The field is absent on every other node.supported_transportslists some ofvless_reality,hysteria2,vless_xhttpandolcrtc.agent_onlinesays whether the node is connected right now.sessions_countcounts the sessions provisioned on the node, not people: a subscription keeps a session on every route of every subscriber, used or not. An exit is full when the people on it (its sessions that carried traffic in the last 15 minutes) reachmax_users_effective, or when its live load is too high. Asessions_countabovemax_users_effectivedoes not mean the node is full.- Origins that serve olcRTC also carry
olcrtc_*capacity fields.
You do not pick nodes for a subscription one by one. It carries every route you may use, less what your filters leave out (step 3). The catalogue tells you which exits there are and what their routes cost.
Before you have a key, the public marketplace, proofkit.org/providers, lists the exits and their prices. Those prices already include the 20% fee: they are the total_price_per_gb_nano you pay, unless we have agreed a price with you.
3. Choose what the subscription carries
Sign in with the owner account, open the dashboard (#/) and find the Subscription tab ("branding & filters"). You can fill it in before subscriptions are switched on, and change it at any time.
What ProofKit switches on, and the tab does not change:
- Subscriptions. The tab's first line says whether they are on for your partner. Until they are, step 5 answers
403 SUBSCRIPTION_NOT_ENABLED. - olcRTC, and your olcRTC share. The dashboard's olcRTC figure, "in rooms / your share", shows how many of your subscribers' olcRTC keys count as present against your share, or
off(see step 7). The share binds only once subscriptions are on; until then it is for information.
Ask ProofKit to change either.
What you set:
| Field | What it does |
|---|---|
| profile title | The subscription's name in the app. Up to 64 characters, any script and emoji. |
| support URL, web page URL | Links the app shows. https:// only, visible ASCII with no spaces, up to 512 characters each. |
| line-name prefix | Put in front of every line's name, for example MyVPN in MyVPN DE Direct. Up to 32 characters, none of #, ?, &, $. |
| update interval, hours | How often the app re-reads the list, 1 to 168 hours; 1 by default. |
| exit countries | The exits to keep, as comma-separated country codes. Blank keeps every exit. |
| entry | All (the default), Direct only, or Via entry edge only (for example only the via RU routes). |
| transports | Reality, Hysteria2, XHTTP and WebTunnel, the lines each route carries. The first three are checked by default; WebTunnel is off until you tick it, and is offered once an entry or an exit you can sell serves the line (see WebTunnel). |
A blank field means none, and no value may contain control characters.
For example, Europe only: enter DE, ES, FR, GB, IT, NL, PL, SE under exit countries, keep entry at All and the three transports checked by default, and press Save. The preview under Save then shows the first lines a subscriber would get, such as MyVPN DE Direct, MyVPN DE Direct · Hysteria2 and MyVPN DE via RU. "No route matches these filters yet." means the filters keep nothing. The catalogue (step 2) lists the exit countries there are; a code with no exit behind it keeps nothing.
What a save does:
- Nothing is sent to the nodes and no session ends. Each subscriber's next fetch serves the new settings, and the app shows them after its next refresh.
- A route the filters leave out is never started. A route a subscriber already holds that a new filter leaves out drops out of its list: if its session never carried traffic, it is closed once it is an hour old; otherwise it keeps working until it ends for another reason. See Route filters.
- The filters apply to every subscriber of your partner at once, and only to the subscription:
POST …/and the catalogue ignore them. A new olcRTC place is made only on an exit you keep.connect - The dashboard takes up to 30 saves an hour, refused ones included.
The tab is the owner's alone: the API key cannot read or change these settings. The dashboard's own endpoint behind it is described under Subscription settings.
4. Create a subscriber
curl -s -X POST $PK/subscribers \
-H "X-Partner-Key: $KEY" -H "Content-Type: application/json" \
-d '{"external_id": "tg_123456789"}'
{
"external_id": "tg_123456789",
"account_id": "…",
"sub_token": "…64 hex characters…",
"created": true,
"disabled": false,
"created_at": "2026-09-26T15:04:05.123456Z"
}
external_idis your own id for the person. It must be 1 to 128 bytes of UTF-8 once surrounding whitespace is trimmed; anything else answers400 INVALID_EXTERNAL_ID.- The call is idempotent. The same
external_idreturns the same subscriber withcreated: false, so retrying is safe. If the subscriber was disabled, this call enables it again. sub_tokenis the subscriber's secret for the hosted subscription URLs. It changes only on a reset (step 8).- Nothing starts on the nodes and nothing is charged yet.
- During a trial, creating a subscriber beyond
trial.max_subscribersis refused with403 TRIAL_LIMIT. Disabled subscribers count toward that cap.
5. Get the subscription
Your server fetches it. URL-encode the external_id in the path.
curl -s -D - -H "X-Partner-Key: $KEY" \
"$PK/subscribers/tg_123456789/subscription?format=happ"
format | Body | Read by |
|---|---|---|
happ (default) | Base64 of the share lines | Happ, Hiddify, v2rayNG |
ghostlane | The same lines as plain text, followed by olcRTC lines | Ghostlane |
json | {"entries": [...], "upload": N, "download": N}, where each entry has url, transport, carrier (olcRTC lines only), remark, session_id, origin_id, edge_id, exit_country, entry_country | Your own code |
The response carries these headers. Return them together with the body:
| Header | Value |
|---|---|
profile-title | base64: followed by your title in base64, when a title is set |
profile-update-interval | Hours between refreshes, 1 by default |
support-url, profile-web-page-url | Your links, when set |
subscription-userinfo | upload=…; download=…; total=…; expire=…: this calendar month in UTC with total=0; expire=0, or the subscriber's quota and end date once you set them |
content-type | text/, or application/ for json |
cache-control | no-store |
content-disposition | inline |
A device limit, once you set one on a subscriber, is enforced on this fetch: forward the app's x-hwid, x-device-os, x-ver-os, x-device-model and user-agent request headers with your call and pass the answer's headers back with the body, because without x-hwid the default (strict) mode refuses the fetch as one from an app that names no device (see Subscriber device limit).
There are three ways to hand the subscription to the app:
- Under your own domain, your server (production). Your server answers its own URL, for example
https://sub.example.com/. It maps that URL to the subscriber's<your token> external_id, calls the endpoint above, and returns the body unchanged, with the headers. You may rewrite any header: to show your own plan in the app, settotal=andexpire=insubscription-userinfoand keep ourupload=anddownload=. The user only ever sees your domain, and the key stays on your server. - Under your own domain, our serving (production, one DNS record). Add
sub.yourdomain.com CNAME subs.proofkit.org— a plain record, DNS-only — and request the domain on the Partner page. Once we approve it, your subscribers' links answer on it with our certificate;GET /'saccount hosted_basesays which host they ride on. Free outside the trial, up to three domains. See Serving it under your domain. - Hosted by ProofKit (quickest for a test).
https://proofkit.org/serves the samesub/ <sub_token> happbody with the same headers. It needs no key, so the URL itself is the secret, and it shows proofkit.org. It allows 60 requests a minute per IP. Until ProofKit switches subscriptions on for your partner, this URL does not answer403: it serves an older, unbranded list with a VLESS Reality line for each session the subscriber already holds fromconnectorPOST …/, usually none. When olcRTC is on for you, building that list also issues the subscriber an olcRTC key on every olcRTC exit where it holds a session, outside your share, and ends the list with a localolcrtc socks://127.0.0.1:10808#olcRTCline, which does not work in Happ. The hosted URLs serve these older lists again if ProofKit later switches subscriptions off for you. See Hosted subscription URLs.
Rate limits. Budgets are counted per key prefix (pk_ and 8 hex characters), before the key is checked: 1,200 subscription fetches a minute, and 300 a minute for every other call. A request with a wrong key that carries your prefix spends your budget, and requests without X-Partner-Key share one bucket. During a rotation's 24-hour overlap, the old and the new key each have their own. 1,200 fetches a minute carry about 72,000 subscribers refreshing every hour; see At scale under Serving it under your domain for more. Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset; pace your proxy by them. See Rate limits.
First fetch. The first fetch starts a session on every route the subscriber lacks among those your filters keep, and spends up to 5 seconds doing so. Only traffic is charged: a route nobody uses costs nothing. Some routes cannot be made right away, and those are left out while the rest of the body comes back:
- a node of the route is offline, full or overloaded;
- your balance does not allow a new session;
- the subscriber already holds 50 sessions, the most one subscriber may hold;
- Torrent Guard ended the subscriber's session on that exit in the last hour;
- the 5 seconds run out. Each of our servers starts at most 8 sessions at a time, for every fetch of every partner together, so when you import many subscribers, spread their first fetches over time.
A later fetch adds a left-out route once its cause has passed; a route past the 50-session cap stays out while the subscriber holds 50. While nothing changes, later fetches return the same body.
| Answer | Meaning |
|---|---|
403 SUBSCRIPTION_NOT_ENABLED | ProofKit has not switched subscriptions on for your partner. |
404 NOT_FOUND | No such subscriber, or the subscriber is disabled. |
400 INVALID_FORMAT | format is not happ, ghostlane or json. |
6. Open it in Happ
Copy the URL, either your own or the hosted one, and add it in Happ as a subscription. Happ lists the lines; the user picks one and connects. Hiddify and v2rayNG read the same URL.
Or give the hosted link to the person as it is. Opened in a browser it is a page that says how to connect: an app to install, one tap to add, and the link to copy for any other app (see Hosted subscription URLs). On the Partner page, a subscriber's QR button shows that link as a code to scan, share or print.
What the user sees:
- Branding. The subscription carries your title, support link and web-page link, when they are set (step 3).
- Line names. Each route gets a line named
<prefix> <exit> Director<prefix> <exit> via <entry>, by country code, for exampleMyVPN DE DirectandMyVPN DE via RU. When one country has two exits, the second is numberedDE 2; a second entry in one country for the same exit is numberedvia RU 2. The numbers count the routes in that body, so a route that is left out on one fetch (its node offline or full) and back on the next can rename the others:DEbecomesDE 2and back. Apps key their lists on names, so this can show as a duplicate or a missing line until the next refresh. - Transports. Every route has a VLESS Reality line. Routes that carry them also get a Hysteria2 line (
… · Hysteria2) and an XHTTP line (… · XHTTP). A transport you unchecked in step 3 is missing from every route. - List length. Without filters the list is long: every exit the subscriber may use, both direct and through each entry edge. To hand out fewer lines to everyone, set the filters in step 3, for example Europe only. To give different subscribers different lists, fetch
format=jsonand build your own body from each entry'sexit_countryandentry_country. A JSON fetch still starts every route your filters keep. If olcRTC is on for your partner, everyjsonfetch also gives the subscriber an olcRTC key and takes one of your olcRTC places (step 7), whether or not you pass the olcRTC entries on. For subscribers whose app cannot run olcRTC, filter the decodedhappbody instead (each line's name, percent-encoded after#, carries its exit and entry), or ask ProofKit to leave olcRTC off for you. - Usage and limits.
subscription-userinfoshows this calendar month's traffic, withtotal=0; expire=0, until you give the subscriber limits. A subscriber can carry a traffic quota, its own exit countries, an end date and a device limit, and ProofKit enforces them: see Subscriber quotas, Subscriber exit countries, Subscriber expiry and Subscriber device limit. The header then carries the quota and the date. If you keep your own limits instead, your proxy may settotal=andexpire=to show them in the app (step 5), and you enforce them by disabling the subscriber (step 8). - Refresh. The app re-reads the list every
profile-update-intervalhours. - Changing credentials. A line's credentials last as long as its session. A session ends on a reset, a disable, your
DELETE /, a balance at zero, Torrent Guard, its node being offline for 30 minutes, or ProofKit taking its node or link out of service. An active line is not closed for being idle, except on a node ProofKit has paused, where a line that has carried nothing for two hours is closed and not made again (see the catalogue in the API reference); a line still pending (its node never confirmed it) that carried nothing is closed once it is an hour old. The next fetch then makes a new session, and the app picks up the new line on its next refresh.subscribers/ {external_id}/ sessions/ {session_id} - Russia. For subscribers in Russia, see Subscribers in Russia.
7. olcRTC lines
In a subscription, olcRTC lines come in the ghostlane and json formats, and from the hosted https://proofkit.org/ and https://proofkit.org/ (the olcRTC lines alone). The happ format never carries them, because Happ, Hiddify and v2rayNG cannot run olcRTC. Ghostlane is the client that also runs olcRTC. It also reads the Reality, Hysteria2 and XHTTP lines of the same body, so one URL is enough.
- Switch. The lines appear only after ProofKit switches olcRTC on for your partner. With subscriptions on, the switch governs the per-session calls below too.
- One exit, one key. A subscriber is given an olcRTC key on one exit of its body, so on an exit your filters keep. That one place counts whichever call made it: a subscriber that got its key from the per-session calls below gets no second one from the subscription. Any exit in its body where it holds a key keeps its lines. Each exit has one line for each call service it has a room on, all sharing one key:
MyVPN DE · olcRTC(Yandex Telemost),MyVPN DE · olcRTC · WB(WB Stream),MyVPN DE · olcRTC · SJ(SaluteJazz) andMyVPN DE · olcRTC · VK(VK Calls). - Limited places. Two caps apply to a new key. The number of your subscribers' keys that count as present at once is capped at your share: the larger of 20% of the network's olcRTC slots and 8 plus one for every 15.625 GiB of olcRTC traffic your subscribers moved in the last 30 days. A key counts as present while its subscriber has moved traffic through that exit in the last 5 minutes on any line, Reality, Hysteria2 and XHTTP included, not only olcRTC, and each key counts on its own. And in each room you may hold only your part of its keys, idle keys included: a fifth at the default share (7 of 32 today), up to half as your traffic grows. So a room can be closed to you while your share still has room. The subscriber is then seated on another exit; when no exit has a place, its olcRTC lines are left out and the rest of the body still comes back. They appear on a later fetch once a place frees up. Your new keys never make a room take back anyone else's key, so the catalogue's
olcrtc_keys_freeis only an upper bound for you. - Keys and formats. A
ghostlaneorjsonfetch gives the subscriber an olcRTC key, and ahappfetch never does. Fetchhappfor Happ users. - A quiet key can be taken back. A key held for 30 minutes, whose subscriber has sent no traffic through that exit for 30 minutes, can be taken back when its room needs the place. Its lines stop working until a later
ghostlaneorjsonfetch seats the subscriber again. On the same exit it gets the same key back, so the line it holds works again unchanged. - Per-session calls.
POST /andsubscribers/ {external_id}/ olcrtc GET /also hand out olcRTC lines (subscribers/ {external_id}/ sessions/ {session_id}/ configs olcrtc_url,olcrtc_urls), one session at a time. With subscriptions switched on for you, they follow the same terms: the olcRTC switch (while it is off,POST …/answersolcrtc 403 OLCRTC_NOT_ENABLEDand configs carry no olcRTC fields), your share and your part of each room, one place per subscriber, and a new place only on an exit your filters keep. A subscriber that holds its place is answered from it and never refused for the share. When no place can be made,POST …/answersolcrtc 503 NO_OLCRTC_NODE. SeePOST …/.olcrtc
8. Disable, re-enable, reset
Disable:
curl -s -X DELETE -H "X-Partner-Key: $KEY" $PK/subscribers/tg_123456789 # 204
- Every running session ends, and its credentials are removed from the nodes. From then on, the subscription call and the hosted URLs answer
404. - The subscriber's olcRTC keys stop working at the next room check, about 5 minutes later, sometimes longer. Until then an olcRTC line they already hold can still carry traffic.
- The subscriber and its usage history are kept, and
GET $PK/showssubscribers/ tg_123456789 disabled: true. Calling DELETE again changes nothing. - To re-enable, call
POST /again with the samesubscribers external_id. Thesub_tokenstays the same, and the next fetch makes new lines. Note thatPOST /also re-enables a disabled subscriber.subscribers/ {external_id}/ olcrtc - A re-enabled subscriber does not get its olcRTC place back: it needs a new one, under your share and a room's capacity, with a new key. Until one is free, its
ghostlaneandjsonbodies come without olcRTC lines.
Reset, for a link that leaked:
curl -s -X POST -H "X-Partner-Key: $KEY" $PK/subscribers/tg_123456789/reset
- The subscriber gets a new
sub_token, which comes back in the answer. The old hosted URL answers404from now on. - Every running session ends and leaves its node, and the subscriber's olcRTC keys are revoked at once.
- The subscriber stays enabled or disabled, as it was. The next fetch makes fresh credentials on every route it can start. Its olcRTC place is given up: the next
ghostlaneorjsonfetch needs a new place under your share and a room's capacity, with a new key, and comes without olcRTC lines until one is free. - A reset is not idempotent: each call issues a new token and ends the sessions again.
- The S2S path (
external_id) does not change. If you serve the subscription under your own URL and that URL leaked, replacing it is up to you.
9. Check usage
curl -s -H "X-Partner-Key: $KEY" \
"$PK/subscribers/tg_123456789/usage?from=2026-09-01T00:00:00Z&to=2026-10-01T00:00:00Z"
{
"external_id": "tg_123456789",
"from": "2026-09-01T00:00:00Z",
"to": "2026-10-01T00:00:00Z",
"bytes": 2147483648,
"bytes_up": 214748364,
"bytes_down": 1932735284,
"cost_nano": 24000000
}
For every subscriber at once:
curl -s -H "X-Partner-Key: $KEY" "$PK/usage?from=2026-09-01T00:00:00Z&to=2026-10-01T00:00:00Z"
- Window.
fromandtoare RFC 3339 timestamps, and the window is[from, to). WriteZ, or encode a+offset as%2B. By default the window is the last 30 days. Consecutive windows never overlap, so monthly reports add up. Iffromis afterto, the answer is400 INVALID_RANGE. - All subscribers.
/usagereturnstotal_bytes,total_bytes_up,total_bytes_down,total_cost_nanoand asubscriberslist of{external_id, bytes, bytes_up, bytes_down, cost_nano}. The list includes every subscriber you created, including disabled ones and ones with no traffic, busiest first. It is not paginated. - Cost.
cost_nanois what the traffic charged to your balance. It is 0 inside the free trial. - Balance. For the money you have left, read
balance_nano − stake_nanofrom/account(stake_nanois0unless your partner holds a stake). ProofKit emails the partner's owner when the balance has about two days of traffic left and when it reaches zero; your backend is not notified.
Subscribers in Russia
The full guide is Transports and Russia, with its recommendations for Russian mobile networks. In short:
- We have no measurements from inside Russian mobile networks. The advice below follows from what each transport is built to survive, not from results per mobile operator.
- Hand out the whole list: in step 3, keep the entry at All and all three transports, and narrow only the exits if you must. A subscriber whose line stops working switches to another line in the same list, with no call from your backend. Route filters in the dashboard says how to offer only the routes through the entry in Russia, or only direct ones, if you choose to.
- Start with the direct Reality lines. When an exit's direct lines fail or crawl, try the same exit's
… via RUline: the phone then connects to a server in Russia, and the route costs both nodes' prices plus the fee. - When Reality connects but stalls, try Hysteria2 where UDP passes (it runs over UDP), and XHTTP where it does not (it runs over TCP, on port 8444 by default).
- On days when only whitelisted services work, olcRTC in an app that runs it, such as Ghostlane, is what is left: it works where the video service it rides on is on the whitelist. See olcRTC for whitelist days; its price from 20 November 2026 is in What it costs. Subscribers who use only Happ, Hiddify or v2rayNG have no line for those days: olcRTC comes only in the
ghostlaneandjsonformats. - We do not rank exits for Russian networks. The node-level fallback is the entry in Russia (the
via RUlines), not a particular exit.
Trial, then prepaid
The free trial
A free trial exists only by agreement with ProofKit: a partner that signs itself up has none and is charged from the first GB. If you agree one, ProofKit starts it and sets its three limits: an end date, a number of subscribers and a traffic allowance. The defaults are 7 days, 10 subscribers and 50 GB. trial on /account shows the limits and what you have used (step 1).
While trial.active is true:
- Traffic is free. Nothing is charged to your balance,
cost_nanois 0, and sessions start even with an empty balance. Steps 3 to 9 work (step 5 once subscriptions are switched on). - The subscriber cap applies. Creating a subscriber beyond
max_subscribersis refused with403 TRIAL_LIMIT. Disabled subscribers still count toward the cap. To add more subscribers during the trial, top up and ask ProofKit to end the trial.
When the trial ends
The trial ends as soon as any one of these happens:
untilpasses.bytes_usedreachesmax_bytes.- ProofKit ends it, or lowers
max_subscribersbelow the number of subscribers you hold.
Then:
- Charging starts.
trial.activebecomesfalse. From the next traffic report on, traffic is charged to your balance at each session's price (step 2): the exit'stotal_price_per_gb_nanoon a direct route, and the exit's plus the entry's, within a nanoton, on a route through an entry (olcRTC traffic, from 20 November 2026, at the olcRTC price). The report that uses up the allowance is the last free one. - Sessions keep running. Nothing restarts, and sessions keep running for as long as the balance stays above zero.
- At zero balance:
- The session whose traffic report took the balance to zero is disconnected at once, and the other active sessions on priced routes within about 15 seconds. Their lines stop working. A session still pending is closed once it turns active; one on a route priced at 0 keeps running and keeps its lines.
- From then on, every subscription fetch starts no new route, so a subscription whose sessions were all closed comes back with no lines (still
200) until you top up. The next fetch after a top-up makes new credentials on every route. POST …/andconnect POST …/answerolcrtc 400 INSUFFICIENT_BALANCE.
- Top up before the trial ends to avoid this.
- No subscriber cap. Prepaid has no cap on subscribers.
- Plan fields.
billing_plankeeps readingtrialuntil ProofKit closes the trial. After that it readsprepaid, and thetrialobject is gone. - No notice. Nothing is sent to you when the trial ends, so watch
GET /.account
Top up (prepaid)
The full rules are in Paying for your subscribers' traffic.
- Toncoin only, from your linked wallet. Top-ups go into the ProofKit escrow contract (
escrow_address) from your partner wallet. Link that wallet first, with TonConnect, in the partner dashboard.walleton/accountthen shows it. - Which wallet. Use a wallet that is not linked to any other ProofKit account, your own personal account included: such a wallet is refused with
TON_ADDRESS_TAKEN, and one that still holds another account's money in the escrow withWALLET_HOLDS_OTHER_BALANCE. You can link another wallet at any moment. What the old wallet holds in the escrow stays on your balance and pays for your subscribers' traffic first; top up from the new wallet only; to withdraw what the old one holds, link it again (see Link the partner wallet). - How to send. Send either the escrow's Deposit message (the dashboard's Top up button sends it) or a transfer with the text comment
deposit.deposit_linkis exactly such a transfer. - Transfers that are not credited.
- A plain transfer without the comment is not credited to anyone. A transfer with any other comment is rejected.
- A transfer from any other wallet is not credited to your balance. That includes a withdrawal from an exchange straight to the escrow, whose sender is the exchange's wallet: move the TON to your linked wallet first, then deposit from there.
- USDT and other jettons sent to the escrow are not credited.
- Minimum. The contract refuses deposits under 0.05 TON. The dashboard asks for at least 0.1 TON for a top-up, and for at least 5.1 TON for the first deposit of a new partner, which also locks the stake.
- Crediting. Our payment checker reads the escrow every 30 seconds.
balance_nanorises once the checker has seen the deposit. If a deposit from the linked wallet has not appeared 15 minutes after its transaction was confirmed, write to support with the transaction hash. - Withdrawing. The part of your deposit that your subscribers' traffic has not spent, less any stake you hold, can be withdrawn to the linked wallet from the dashboard. See Withdraw the unspent deposit.
If something is off
Open a ticket under Support on proofkit.org, signed in as your owner account, and quote the call's X-Request-ID. The full list of codes is in Error codes.
| You get | Why |
|---|---|
401 INVALID_PARTNER_KEY | The key is missing, wrong or revoked, a rotated-out key is past its 24 hours, the partner is disabled, or it has not finished setting itself up. |
400 PARTNER_NOT_BILLABLE | There is no billing account yet. ProofKit has to set your terms. |
400 INVALID_EXTERNAL_ID | The external_id is empty, or longer than 128 bytes once trimmed (step 4). |
400 INVALID_FORMAT | format is not happ, ghostlane or json (step 5). |
400 INVALID_RANGE | from is after to (step 9). |
400 TORRENT_GUARD_COOLDOWN | Torrent Guard ended the subscriber's session on that exit in the last hour. Every exit runs it today: it blocks BitTorrent on VLESS Reality, XHTTP, WebTunnel and Hysteria2, and ends a session that has uploaded more than 30 GB and more than one and a half times what it downloaded, whatever the traffic is, or, on the exits that run the connection watch (at the time of writing, ProofKit's), whose count for one minute reaches 300 different destinations, 200 in each of two minutes in a row, 100 destinations on ports other than 80 and 443 (at least 80 % of all of them) or 20 on ports other than 80 and 443 refused by Torrent Guard's rules. For an hour that exit then refuses the subscriber, and its subscription leaves that exit's routes out. GET / says why. See Torrent Guard in the partner terms. |
400 INSUFFICIENT_BALANCE | From POST …/ or POST …/: the balance is at zero and no trial is active. A subscription fetch never answers this; it leaves the routes out. |
403 TRIAL_LIMIT | The trial's subscriber cap is reached. |
403 SUBSCRIPTION_NOT_ENABLED | Subscriptions are not switched on for your partner. |
403 OLCRTC_NOT_ENABLED | From POST …/: olcRTC is not switched on for your partner. Nothing was created. |
503 NO_OLCRTC_NODE | From POST …/: no room has a place for the subscriber, your subscribers' keys that count as present fill your share, or the subscriber holds its olcRTC place on another exit than the one asked for. Fall back to another transport; retry later. |
404 NOT_FOUND on the subscription | The subscriber is unknown or disabled. |
404 NOT_FOUND "Unknown partner API endpoint" | The path does not exist. |
405 METHOD_NOT_ALLOWED | The path exists, but not with this method. |
429 RATE_LIMITED | Wait Retry-After seconds when it is present. The per-IP limits (6,000 a minute on /api/, 60 a minute on the hosted /sub/ URLs) send no Retry-After: wait a minute. |
| Fewer lines than expected | Your filters leave them out (step 3); a node is offline, full or overloaded; the subscriber already holds 50 sessions; Torrent Guard ended its session on that exit in the last hour; the 5-second budget ran out (spread a bulk import's first fetches over time); or, for olcRTC lines only, your olcRTC share or your part of a room is reached, or the subscriber's olcRTC place is on an exit your filters have since left out (end its sessions on that exit to move it: see olcRTC in the subscription). Fetch again, and check /account. |
No lines at all (200) | The balance is at zero (or the trial is over with no balance): top up, and the next fetch makes new lines. Or your filters keep no route: the preview in step 3 then says "No route matches these filters yet." On the hosted URL, it can also mean subscriptions are not switched on yet. |