BIM VPN logoBIM VPN Docs

API reference

The public endpoints used by the BIM VPN apps and the checkout page. Everything here is taken from the server code. Administrative endpoints are not part of the public API and are not documented.

Service Base URL
Accounts and payments https://api.bimvpn.xyz
Exit network https://exit-api.bimvpn.xyz

Requests and responses are JSON. Errors come back as {"detail": ...}, where detail is either a string or an object with an error field.

Authentication

Sign-in returns two tokens:

  • Access token: send it as Authorization: Bearer <access_token>. It is short lived (expires_in seconds, one hour by default).
  • Refresh token: exchange it for a new pair with /auth/refresh. It is bound to the machine_id it was issued for and is rotated on every use.

machine_id is a stable identifier for the device, chosen by the client.

POST /auth/register

Create an account. Give an email, a username, or both.

{"email": "you@example.com", "username": "", "password": "...", "machine_id": "..."}

Response 200:

{
  "access_token": "...", "refresh_token": "...", "expires_in": 3600, "ip_token": "...",
  "user": {"id": "9f2c...", "email": "you@example.com", "plan": "free"}
}
Status detail
409 Email already registered or Username already taken

When an email is given, an activation link valid for 24 hours is emailed.

POST /auth/login

{"identifier": "you@example.com or username", "password": "...", "machine_id": "..."}

Response 200: same shape as register, with the account's current plan.

Status detail
401 Invalid credentials

POST /auth/refresh

Header X-Machine-Id: <machine_id>. Body:

{"refresh_token": "..."}

Response 200: {"access_token", "refresh_token", "ip_token", "expires_in"}. The refresh token you sent is revoked; store the new one.

Status detail.error Meaning
401 invalid_refresh Token malformed or not a refresh token
401 reuse_detected Token was already used; every session of the account is signed out
401 device_mismatch X-Machine-Id differs from the device the token was issued to

POST /auth/logout

Bearer auth. Body {"refresh_token": "..."}. Revokes that session. Returns 204.

POST /auth/logout-all

Bearer auth. Signs out every session of the account on every device, immediately. Returns 204.

GET /auth/sessions

Bearer auth. Lists active sessions: jti_short, device_id, created_at, last_used_at, ip, ua, app_version, is_current. Pass ?current=<jti_short> to flag the calling session.

DELETE /auth/sessions/{jti_short}

Bearer auth. Revokes one session. Returns 204 whether or not it matched.

GET /me

Bearer auth. Response:

{
  "id": "9f2c...", "email": "you@example.com", "username": "", "plan": "gold",
  "device_count": 1, "max_devices": 3, "sub_expires": "2026-10-15",
  "email_verified": true, "ip_token": "...", "update_nudge": null
}

POST /auth/resend-verification

Bearer auth. Sends a new activation link. Returns {"ok": true}. 400 with Email already verified if there is nothing to do.

GET /auth/verify-email?token=...

The link in the activation email. Returns an HTML page, not JSON.

Payments

GET /payment/coins

Public, no authentication. The coins the server accepts right now, so a client never hard-codes availability.

{
  "coins": [
    {"id": "btc", "label": "Bitcoin"},
    {"id": "xmr", "label": "Monero"},
    {"id": "usdt-erc20", "label": "USDT (Ethereum)"},
    {"id": "usdt-trc20", "label": "USDT (Tron)"},
    {"id": "bim-base", "label": "BIM (Base)", "symbol": "BIM", "chain_id": 8453,
     "token": "0x555fff48549c1a25a723bd8e7ed10870d82e8379",
     "pricing": "dex_twap", "twap_secs": 1800, "invoice_ttl_secs": 1800}
  ]
}
Field Meaning
id Value to send as coin to POST /payment/invoice
label Name to show at checkout
symbol, chain_id, token Token coins only: ticker, EVM chain id and token contract
pricing, twap_secs Token coins priced from the market: dex_twap is a time-weighted average over twap_secs seconds
invoice_ttl_secs How long the invoice amount is held

A coin that is temporarily unavailable, for example BIM checkout paused because of abnormal market conditions, may be refused when you create an invoice.

POST /buy-session

Bearer auth. Returns a one-hour token that lets the checkout page act for the account without a password:

{"s": "...", "expires_at": 1789370000}

POST /payment/invoice

Auth: Authorization: Bearer <access_token>, or ?s=<buy session>.

{"tier": "gold", "period": "monthly", "coin": "usdt-trc20"}
Field Values
tier bronze, gold
period weekly, monthly, quarterly, yearly
coin btc, xmr, usdt-erc20, usdt-trc20, bim-base. Use GET /payment/coins for the coins enabled right now.

Response 200:

{
  "invoice_id": "a1b2c3d4e5f6", "coin": "usdt-trc20", "address": "T...",
  "amount": "9.990417", "amount_discriminator": 417,
  "expires_at": 1789368000, "qr_data": "T...?amount=9.990417",
  "status_url": "/payment/invoice/a1b2c3d4e5f6"
}
  • The invoice expires 30 minutes after creation.
  • amount must be paid exactly. Its last four decimals are unique among pending invoices for that coin.
  • Asking again with the same tier, period and coin returns the same pending invoice. Changing any of them cancels the old one.
Status detail
400 {"error": "invalid_tier"}, invalid_period, invalid_coin
401 invalid_token
502 {"error": "btc_rate_unavailable"} or xmr_rate_unavailable
503 {"error": "usdt_not_configured"}, btc_not_configured, xmr_not_configured

GET /payment/invoice/{invoice_id}

Same auth as above. Poll this until status changes.

{
  "invoice_id": "a1b2c3d4e5f6", "status": "pending", "coin": "usdt-trc20",
  "amount": "9.990417", "address": "T...", "expires_at": 1789368000,
  "paid_amount": null, "paid_at": null, "tx_hash": null
}
status Meaning
pending Waiting for the payment
paid Payment matched, plan credited
expired 30 minutes passed without a match
cancelled Replaced by a newer invoice

404 {"error": "not_found"} for an unknown invoice or one that belongs to another account.

Service

GET /health

Liveness check.

GET /version?platform=

Latest app build for a platform, for example macos-mojave-compat or windows-x64:

{"latest": "1.2.44", "min": "0.2.32", "url": "https://bimvpn.xyz/downloads/...", "sha256": "..."}

Exit network

Base URL https://exit-api.bimvpn.xyz.

These endpoints are served by exit broker 1.1.0.

Key auth: Authorization: Bearer bxk_<key id>.<secret>. Keys are issued by BIM. Errors use the shape {"detail": "<code>"}.

GET /healthz

{"ok": true, "connected_exits": 1, "time": 1789362814}

Broker 1.1.0 also returns "version": "1.1.0".

GET /v1/policy

The signed policy bundle every exit node enforces, published so anyone can check what an exit is allowed to carry. Its body includes version, expires_at and allowed_ports (currently [443]).

GET /v1/exits

Key auth. The active nodes this key may use: public nodes and nodes owned by the key's owner. Private nodes of other owners never appear.

{"owner": "9f2c...",
 "exits": [{"node_id": "nd_EXAMPLE", "label": "Paris home", "geo": "FR",
            "visibility": "private", "mine": true, "online": true,
            "proxy_username": "bxk_EXAMPLE-node-nd_EXAMPLE"}]}

owner is the BIM VPN account id (user.id from /auth/login). geo is an ISO 3166 two-letter code, ZZ when unknown.

Status detail
401 unauthorized

GET /v1/nodes/mine

Key auth. Every node owned by the key's owner, in any state, with the reason a node is paused.

{"owner": "9f2c...",
 "nodes": [{"node_id": "nd_EXAMPLE", "state": "quarantine",
            "state_reason": "new node awaiting activation",
            "visibility": "private", "label": "", "geo": "ZZ", "online": false}]}

state is one of quarantine, active, drain, stop.

PATCH /v1/nodes/{node_id}

Key auth, for a key whose owner owns the node. Body, all fields optional:

{"visibility": "public", "label": "Paris home"}
Field Values
visibility public or private
label Up to 64 characters

Response: the node object as in /v1/nodes/mine, plus owner.

Status detail Meaning
401 unauthorized Missing, wrong or revoked key
404 unknown_node No such node, or the key does not own it
403 admin_only_field The body tried to change owner or geo
422 bad_visibility, bad_label Invalid value

Customer proxy

exit-api.bimvpn.xyz:3128, HTTP CONNECT with Basic auth. Routing hints go in the proxy username. See Connect through an exit node for the username forms, usage and error codes.

Plain HTTP

The proxy hop on port 3128 is not encrypted, so credentials sent to it are readable on the network path.