Billing
Prepaid credits, published rates, per-second metering, auto top-up, and spend limits for orgs and members.
Platinum billing is prepaid. You buy credits, and metered usage draws down the org balance. Manage billing in the dashboard or with the /v1/billing/* endpoints.
What is billed
Platinum bills each meter below from the org balance. A month is 730 hours.
| Meter | Price | What Platinum measures |
|---|---|---|
| vCPU | $0.0324 per vCPU-hour | The vCPUs of a running sandbox, per second |
| RAM | $0.0108 per GiB-hour | The RAM of a running sandbox, per second |
| Disk | $0.000072 per GB-hour ($0.0526 per GB-month) | The disk_gb of a running or stopped sandbox |
| Archived disk | $0.000036 per GB-hour ($0.0263 per GB-month) | The disk_gb of an archived sandbox |
| Snapshots | $0.000072 per GiB-hour ($0.0526 per GiB-month) | The data that each snapshot you take holds on disk |
| Volumes | $0.000082 per GiB-hour ($0.0599 per GiB-month) | The compressed unique data that each volume stores |
| Scratch disks | $0.000036 per GiB-hour ($0.0263 per GiB-month) | The bytes a sandbox's scratch disk holds on its host. Proposed rate, pending sign-off. |
| Worker storage | $0.000082 per GiB-hour ($0.0599 per GiB-month) | The bytes in each Worker's object-storage folder. New charge, not charged yet: recorded at $0 until it is announced (see below). |
| Legacy volume snapshots | $0.000055 per GiB-hour ($0.0402 per GiB-month) | Snapshots of the retired volume engines. None bill today. |
| Egress | $0.04 per GiB | The data that a sandbox sends to the internet |
| Residential proxy | $8 per GiB | The traffic through the residential proxy, in both directions |
| Template builds | $0.0018 per build-minute | The run time of each template build, per second |
| Inference | per model | The tokens of each request. See Inference. |
GPU attachments are free. Requests that the sandbox proxy serves are metered at $0.
Sandboxes
Platinum samples sandbox usage every 60 seconds.
| Sandbox state | CPU | RAM | Disk |
|---|---|---|---|
running | billed | billed | billed |
stopped | — | — | billed |
archived | — | — | billed at the archived rate |
deleted | — | — | — |
Compute charges stop when a sandbox stops. A stopped sandbox bills only its disk and its snapshots. Disk bills the size you set in disk_gb, not the bytes in use. Archive a stopped sandbox to halve its disk rate. Set auto_stop_minutes (Sandboxes) to control idle cost.
Snapshots and volumes
Only the snapshots you take with POST /v1/sandboxes/:id/snapshot are billed. A snapshot bills the blocks it holds on disk, not the disk_gb of its sandbox. It bills from creation until you delete the snapshot or its sandbox.
A volume bills the compressed size of the unique data in its history. Platinum measures that size once a day. A deleted volume stops billing.
A volume's history includes what its mount images hold. A sandbox that mounts a sub_path of a volume boots from an image that Platinum derives from one commit. The image keeps that commit, and the data it reaches, for 7 days after its last use, even when the volume's retention would have expired it. GET /v1/billing/volume-usage reports this per volume in mount_image_pins: the images, their stored size, the commits they keep past retention, and when the last pin lapses.
Scratch disks
A scratch disk bills the bytes its sparse file holds on the host (st_blocks), not its scratch_gb. It bills while its host holds it, running or stopped, until it is evicted or its sandbox is deleted. The rate is a proposal pending sign-off and can change before scratch disks are generally available.
Worker storage
Worker storage is a new charge, and it is not charged yet. Platinum measures and records it, but at $0: until the charge is announced, each Worker storage receipt and volume.storage event shows the GiB-hours with unit_price_micros 0, amount_micros 0 and nothing debited. Platinum announces the start date before it turns the charge on. Billing then starts at that moment; hours recorded before it stay at $0.
Worker storage bills the bytes in each Worker's folder, at the volume rate. That folder holds the deployed bundles and everything the Worker's cells keep in object storage. Platinum lists the folder every 6 hours, and each measurement bills from its own time on. A folder with nothing in it stops billing. A PUT through /v1/worker-storage is also recorded as a worker.storage event at $0 per GiB written; the stored bytes are the charge.
Legacy volume snapshots
The legacy volume snapshot rate applies only to snapshots of the volume engines Platinum retired. No current API creates one, and they have billed nothing since the retirement. A volume's own history is billed as part of the volume.
Network
Egress is the data that a sandbox sends to public internet addresses. Data that a sandbox receives does not count. Preview URL traffic, sandbox-to-sandbox traffic, and volume traffic do not count either.
Residential proxy traffic bills in both directions, on top of egress.
Template builds
A template build bills the run time of its final attempt. Time in the build queue does not bill. A build bills when it ends ready, fails in your own step, or you cancel it. A build that fails from a platform fault does not bill. A build whose host stops reporting is a platform fault: it does not bill, unless the build log already shows your own step failed.
Zero balance
Usage debits the org balance, and the balance can go below $0. At balance ≤ 0:
| Surface | What happens |
|---|---|
| New compute — create, start, resume, restore, restore-from-backup, clone, fork | 402 insufficient_credits |
| New volume data — create, fork, write, upload, copy | 402 insufficient_credits; reading and deleting volumes still work |
| New sandbox copies — memory snapshot, backup | 402 insufficient_credits; a sandbox that boots from a volume can still save its root disk with /backup |
| Running sandboxes | A 60-minute grace period starts when the balance goes below $0. Then they are auto-stopped (never deleted) |
| Stopped sandboxes | Platinum keeps the disk. After the balance is positive, call /start or /resume. Credits alone do not restart them. |
| Preview URLs | Traffic to an exposed port does not restart a stopped sandbox |
Platinum emails the org once when the balance reaches $0.
Your data at zero
Platinum never deletes data because a balance ran out.
- A stopped sandbox keeps its disk. After 7 days it is archived, at the lower archived rate, unless you set
auto_archive_days. - Disks, snapshots and volumes keep billing, so the balance can stay below $0.
- Add enough credit to bring the balance above $0, then call
/startor/resume. - An
auto_delete_daystimer you set still applies: it deletes an archived sandbox even at a negative balance. See Sandboxes.
Promo credit
Promo credit lasts as long as its campaign says, counted from the claim. Usage spends it first. When it ends, Platinum removes what is left of it, up to 20 minutes later. Credit you paid for is never removed. If the balance is then $0, this section applies.
Add credits
A new org starts at $0. Add credits before you create a sandbox. A promo code adds credit too.
Only owners and admins can buy credits. The API lets any member read the balance, ledger, limits, and usage; the dashboard shows members their own cap and spend rather than the organization-wide billing panels.
To add credits:
- Open dashboard → Billing.
- Pick a credit pack of $10, $50, or $200, or enter a custom amount from $5 to $1,000.
- Complete the Stripe-hosted checkout.
The balance updates when the payment clears, usually seconds after checkout. The dashboard uses POST /v1/billing/checkout; there is no dedicated CLI billing command.
Turn on auto top-up
Auto top-up charges your saved card when the balance drops below your threshold. The charge brings the balance back up to your target. Only owners and admins can turn it on. Add a card under Payment methods first; without one, the call returns 409.
await client.request("POST", "/v1/billing/auto-topup", {
enabled: true,
threshold_usd: 10,
target_usd: 50,
});client.request("POST", "/v1/billing/auto-topup", {"enabled": True, "threshold_usd": 10, "target_usd": 50})pt api POST /v1/billing/auto-topup -d '{"enabled":true,"threshold_usd":10,"target_usd":50}'curl -X POST "$PT_API_URL/v1/billing/auto-topup" -H "Authorization: Bearer $PT_TOKEN" \
-H "Content-Type: application/json" -d '{"enabled":true,"threshold_usd":10,"target_usd":50}'| Rule | Value |
|---|---|
| Threshold | More than $0 |
| Target | At least $10 above the threshold |
| One charge | $5 to $2,000 |
| Any 24 hours | At most 6 attempts and $2,000 charged |
| Between attempts | At least 15 minutes |
Send {"enabled": false} to turn auto top-up off.
Set spend limits
Only owners and admins can set limits. Limits reset on the first day of each calendar month (UTC). A limit is independent of the balance: it blocks spend even when credits remain.
An alert only warns. A limit blocks. After you hit a limit, spend-increasing calls (POST/PUT/PATCH) return 402. Reads, deletes, /v1/billing/*, and calls that only reduce cost (stop, pause, and archive a sandbox) stay open, so you can watch usage, shed cost, or raise the limit. A member's spend includes the volumes that member created.
| Setting | Applies to | Notifies at | Blocks at |
|---|---|---|---|
usage_alert_usd | whole org | the amount you set — email + org.usage_alert webhook | never |
usage_limit_usd | whole org | 50 / 80 / 90 / 100% — email + org.usage_limit_reached webhook | 100% → 402 usage_limit_exceeded |
limit_usd | one member | 50 / 80 / 90 / 100% — email to that member | 100% → 402 user_usage_limit_exceeded |
Webhook payloads: Webhooks.
The SDKs have no typed billing helpers. Use client.request().
Read current limits
Any member can read /v1/billing/limits. It returns the org caps plus the caller's own cap and spend. /v1/billing/user-limits is owner/admin only. It lists each member's cap and spend.
const limits = await client.request("GET", "/v1/billing/limits");
const userLimits = await client.request("GET", "/v1/billing/user-limits");limits = client.request("GET", "/v1/billing/limits")
user_limits = client.request("GET", "/v1/billing/user-limits")pt api /v1/billing/limits
pt api /v1/billing/user-limitscurl -H "Authorization: Bearer $PT_TOKEN" "$PT_API_URL/v1/billing/limits"
curl -H "Authorization: Bearer $PT_TOKEN" "$PT_API_URL/v1/billing/user-limits"Set the org cap
The org limit cannot drop below the total already allocated to members.
await client.request("PUT", "/v1/billing/limits", {
usage_limit_usd: 200,
usage_alert_usd: 150,
});client.request("PUT", "/v1/billing/limits", {"usage_limit_usd": 200, "usage_alert_usd": 150})pt api PUT /v1/billing/limits -d '{"usage_limit_usd":200,"usage_alert_usd":150}'curl -X PUT "$PT_API_URL/v1/billing/limits" -H "Authorization: Bearer $PT_TOKEN" \
-H "Content-Type: application/json" -d '{"usage_limit_usd":200,"usage_alert_usd":150}'Cap a member
You cannot cap an owner. Member caps cannot sum past the org limit; over-allocation returns 400. limit_usd: null clears a cap. The API rejects the value 0.
await client.request("PUT", `/v1/billing/user-limits/${userId}`, { limit_usd: 50 });client.request("PUT", f"/v1/billing/user-limits/{user_id}", {"limit_usd": 50})pt api PUT /v1/billing/user-limits/$USER_ID -d '{"limit_usd":50}'curl -X PUT "$PT_API_URL/v1/billing/user-limits/$USER_ID" -H "Authorization: Bearer $PT_TOKEN" \
-H "Content-Type: application/json" -d '{"limit_usd":50}'Query usage
GET /v1/billing/usage rolls up the org's metered usage. The default window is the last 30 days. For one sandbox, use GET /v1/sandboxes/:id/usage (SDK: sbx.usage()).
const usage = await client.request("GET", "/v1/billing/usage?group=sandbox");
const one = await sbx.usage(); // one sandboxusage = client.request("GET", "/v1/billing/usage?group=sandbox")
one = sbx.usage() # one sandboxpt api /v1/billing/usage --query "group=sandbox"
pt api /v1/sandboxes/$SBX/usagecurl -H "Authorization: Bearer $PT_TOKEN" "$PT_API_URL/v1/billing/usage?group=sandbox"
curl -H "Authorization: Bearer $PT_TOKEN" "$PT_API_URL/v1/sandboxes/$SBX/usage"| Param | Effect |
|---|---|
since_ms | window start (epoch ms) |
group=sandbox | per-sandbox breakdown, spend-ordered |
series | hour or day — time-bucketed points; hour defaults to the last 24 h |
Stored resources
GET /v1/billing/volume-usage reports volumes, scratch disks and Worker storage from their daily receipts. The default window is the last 30 days.
| Param | Effect |
|---|---|
since_ms | window start (epoch ms) |
until_ms | window end (epoch ms) |
limit | resources listed, 1 to 1000 (default 200); the totals always cover the whole window |
The volume stored-bytes limits (GET /v1/volumes/limits) count the same bytes the bill does: each volume's last measured unique bytes, plus every byte its commits uploaded since that measurement, a mount's commits included, plus open writes and uploads.
total_metered_micros is what the meter computed. total_debited_micros is what left the balance, and total_pending_micros is charged but not yet taken. Each resources row is one resource; a volume's row carries mount_image_pins.
Read every charge
GET /v1/billing/events lists the org's charges oldest first, one row per settlement. Use it to re-bill your own customers. Store the cursor of the last event you processed, and pass it back as ?cursor= on the next call. next_cursor is null when you have read everything available. Filter with ?event_type=sandbox.residential_proxy, and page with ?limit= (1 to 500, default 100).
curl "$PT_API_URL/v1/billing/events?event_type=sandbox.residential_proxy&limit=500" \
-H "Authorization: Bearer $PT_TOKEN"
# {"events":[{"id":"bill_…","cursor":"…","sandbox_id":"sbx_…","event_type":"sandbox.residential_proxy",
# "size_class":"residential_bytes","quantity":52428800,"unit_price_micros":8000000,"amount_micros":390625,"ts":"…"}],
# "next_cursor":null}Each id is stable, so use it as your idempotency key. A charge appears 2 minutes after it settles.
Volumes, scratch disks and Worker storage appear as volume.storage events, one per resource per UTC day. A day splits into more events when the resource's size or the rate changes, and the last event ends when the resource is deleted. An event appears once its span is complete: after midnight UTC, or after the change that ends it. size_class is the resource kind (volume, scratch, worker_storage, volume_snapshot), quantity is GiB-hours and unit_price_micros is µ$ per GiB-hour. resource names what was billed; it is null on every other event. The events of one resource and day add up to that day's receipt in /v1/billing/volume-usage.
curl "$PT_API_URL/v1/billing/events?event_type=volume.storage" -H "Authorization: Bearer $PT_TOKEN"
# {"events":[{"id":"vbe_…","cursor":"…","sandbox_id":null,"event_type":"volume.storage","size_class":"volume",
# "quantity":48,"unit_price_micros":82,"amount_micros":3936,"ts":"…",
# "resource":{"kind":"volume","id":"vol_…","name":"data","region":"…","bytes":2147483648,
# "started_at":"…T00:00:00.000Z","ended_at":"…T00:00:00.000Z","debited_micros":3936}}],
# "next_cursor":null}