Skip to content
SteamLabs API

Endpoints

Billing

Read your credit balance, top-up history, ledger, invoices and Steam wallet payments. Read only, by design.

Everything the Billing pages show, over HTTP: what your balance is, where it came from, and the invoices for it.

One scope covers the domain: billing.read.

Nothing here moves money

There is no billing.write scope, and no endpoint on this surface accepts anything but GET.

You cannot start a top-up, mint a checkout session, cancel a deposit, or adjust a balance with an API key. That is not an oversight to be filled in later, it is the design: a key lives in a config file on a server, and a key that leaks should be able to tell you what you paid, never to cause a payment. Paying happens in the dashboard, where you are present to complete a hosted checkout.

Subscription plans, renewals and cancellation are not on the API at all, for the same reason.

Balance

GET/api/v1/billing/balance

Your current credit balance.

API key required
Bash
curl "https://dashboard.steamlabs.dev/api/v1/billing/balance" \
  -H "Authorization: Bearer $STEAMLABS_API_KEY"
JSON
{
    "balance_cents": 12345,
    "currency": "EUR",
    "preferred_currency": "USD",
    "pending_deposits": { "count": 1, "max": 3 }
}

currency is what your balance is denominated in and what you are charged in. preferred_currency is only your display preference, and is reported so you can decide whether to convert; nothing here is converted for you, because an approximate figure has no business being the answer to "what is my balance".

pending_deposits mirrors the cap on the top-up page. At count equal to max, the dashboard refuses another top-up until one settles or is cancelled.

Deposits

A deposit is one attempted top-up of your credit. The charge breakdown is frozen when it is created, so what you see here is exactly what the card was asked for and what the invoice says.

GET/api/v1/billing/deposits

Your top-up history, newest first.

API key required
JSON
{
    "data": [
        {
            "id": "019fb42e-9a61-70d2-818a-f6a56593f3a5",
            "status": "complete",
            "method": "card",
            "currency": "EUR",
            "credited_cents": 5000,
            "charged_ex_vat_cents": 5000,
            "vat_cents": 1050,
            "charged_total_cents": 6050,
            "vat_rate_bps": 2100,
            "vat_treatment": "domestic",
            "gateway": "stripe",
            "gateway_reference": "cs_test_a1b2c3d4",
            "settled_amount_cents": 6050,
            "settled_currency": "EUR",
            "checkout_url": "https://checkout.stripe.com/c/pay/cs_test_a1b2c3d4",
            "checkout_expires_at": "2026-07-30T16:02:11+00:00",
            "invoice": {
                "id": "019fb431-4d10-7a92-b0c1-8e7f2a5b9c33",
                "invoice_number": "SL-2026-000481",
                "status": "paid",
                "total_cents": 6050,
                "currency": "EUR",
                "issued_at": "2026-07-30T00:00:00+00:00",
                "pdf_url": "https://dashboard.steamlabs.dev/billing/invoices/019fb431-4d10-7a92-b0c1-8e7f2a5b9c33"
            },
            "completed_at": "2026-07-30T14:03:52+00:00",
            "expired_at": null,
            "created_at": "2026-07-30T14:02:11+00:00",
            "updated_at": "2026-07-30T14:03:52+00:00"
        }
    ],
    "meta": { "page": 1, "per_page": 50, "total": 1, "last_page": 1 }
}

credited_cents is what lands on your balance; charged_total_cents is what you paid, VAT included. settled_amount_cents is what the gateway actually captured, once it has, which is what you reconcile against.

checkout_url is the session that was minted when the deposit was started. Reading it starts nothing, and it does expire. There is deliberately no API equivalent of the dashboard's "resume checkout" button: that one asks the gateway for a fresh session, which is exactly the kind of side effect this surface will not have.

Filters

Parameter Values
status pending, complete, failed, expired, cancelled, refunded, charged_back, pending_review
method card, crypto, manual
GET/api/v1/billing/deposits/{id}

One deposit.

API key required

Same shape, unwrapped. A deposit belonging to another account is a 404, exactly like one that never existed.

Ledger

The ledger is the source of truth. Your balance is only a cached sum of these rows, and each one records what moved, why, and what the balance was afterwards.

GET/api/v1/billing/ledger

Every change to your balance, newest first.

API key required
JSON
{
    "data": [
        {
            "id": "019fb44a-2c30-7bd1-9f22-51ac6e0d7712",
            "type": "spend",
            "amount_cents": -420,
            "currency": "EUR",
            "balance_after_cents": 11925,
            "reason": "Market listing fee",
            "source": null,
            "created_at": "2026-07-30T15:41:52+00:00"
        },
        {
            "id": "019fb42e-9a61-70d2-818a-f6a56593f3a5",
            "type": "deposit",
            "amount_cents": 5000,
            "currency": "EUR",
            "balance_after_cents": 12345,
            "reason": null,
            "source": { "type": "billing_deposit", "id": "019fb42e-9a61-70d2-818a-f6a56593f3a5" },
            "created_at": "2026-07-30T14:03:52+00:00"
        }
    ],
    "meta": { "page": 1, "per_page": 50, "total": 2, "last_page": 1 }
}

Amounts are signed: positive credits your balance, negative claws it back. They are never converted, because this is the audit trail of what actually moved.

Filter with type: deposit, refund, chargeback, admin_credit, admin_debit, adjustment, spend, transfer_in, transfer_out.

source names what caused the entry, as a stable short type rather than an internal class name. It is null for entries with no origin object.

Invoices

GET/api/v1/billing/invoices

Issued invoices, newest first.

API key required
JSON
{
    "data": [
        {
            "id": "019fb431-4d10-7a92-b0c1-8e7f2a5b9c33",
            "invoice_number": "SL-2026-000481",
            "series": "SL",
            "year": 2026,
            "status": "paid",
            "currency": "EUR",
            "subtotal_cents": 5000,
            "vat_cents": 1050,
            "total_cents": 6050,
            "vat_rate_bps": 2100,
            "vat_treatment": "domestic",
            "is_reverse_charge": false,
            "deposit_id": "019fb42e-9a61-70d2-818a-f6a56593f3a5",
            "issued_at": "2026-07-30T00:00:00+00:00",
            "created_at": "2026-07-30T14:03:53+00:00",
            "pdf_url": "https://dashboard.steamlabs.dev/billing/invoices/019fb431-4d10-7a92-b0c1-8e7f2a5b9c33"
        }
    ],
    "meta": { "page": 1, "per_page": 50, "total": 1, "last_page": 1 }
}

Filter with status (issued, paid, credit_note) and year.

GET/api/v1/billing/invoices/{id}

One invoice, with its line items and party snapshots.

API key required

Reading a single invoice adds line_items, seller and customer: the frozen snapshots the PDF is printed from. They are snapshots on purpose, so editing your billing address never rewrites an invoice that has already gone out.

The PDF

pdf_url points at the dashboard's own download route, not at this API, and it is authenticated by your session, not by your API key. Open it in a browser, or hand it to your user; do not send your bearer token to it.

That is deliberate. An invoice is a document a person opens, not a payload a script parses, so you get a link rather than megabytes proxied through an API call.

Steam wallet payments

A different kind of money, worth keeping straight. Deposits credit the SteamLabs balance you spend on the platform. These are paysafecard top-ups on an individual bot account's own Steam wallet, opened by an add-funds task, so that account can buy on the Community Market.

GET/api/v1/billing/wallet-payments

Steam wallet top-up records, newest first.

API key required
JSON
{
    "data": [
        {
            "id": "019fb45c-77a1-7c31-8db4-2f0e91a3c6d8",
            "steam_account": {
                "id": "019fb3f0-1e44-70aa-b8c2-77d6e2a11b90",
                "username": "bot_one"
            },
            "task_id": "019fb45c-1120-7f0a-9c3d-4a2b8e7f5511",
            "transid": "108423995678901234",
            "mtid": "912345678901",
            "amount": 20,
            "currency": "EUR",
            "payment_url": "https://customer.cc.at.paysafecard.com/psccustomer/GetCustomerPanelServlet?mid=1000000433&mtid=912345678901&amount=20.00&currency=EUR&language=en",
            "status": "pending",
            "opened_at": null,
            "paid_at": null,
            "created_at": "2026-07-30T15:10:00+00:00"
        }
    ],
    "meta": { "page": 1, "per_page": 50, "total": 1, "last_page": 1 }
}

amount is whole major units, as Steam resolved the top-up, not cents. It is the one money field on the API without a _cents suffix, and that naming is the warning.

Filters

Parameter Values
status pending, paid, discarded, expired
opened true or false, on whether the payment link has been opened
account_id A Steam account id

The dashboard's tabs are these two filters combined: "To pay" is status=pending&opened=false, "Pending transactions" is status=pending&opened=true. Splitting them means you can also ask for things a tab cannot, like every unopened link across all statuses.

The row actions on that page (open the link, recheck a transaction with Steam, discard, move back to pay) are not on the API. Two of them either move money or spend worker fleet capacity, and this surface stays read only.