PLATINUM DOCS

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.

GPU attachments are free. Requests that the sandbox proxy serves are metered at $0.

Sandboxes

Platinum samples sandbox usage every 60 seconds.

Sandbox stateCPURAMDisk
runningbilledbilledbilled
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:

SurfaceWhat happens
New compute — create, start, resume, restore, restore-from-backup, clone, fork402 insufficient_credits
New volume data — create, fork, write, upload, copy402 insufficient_credits; reading and deleting volumes still work
New sandbox copies — memory snapshot, backup402 insufficient_credits; a sandbox that boots from a volume can still save its root disk with /backup
Running sandboxesA 60-minute grace period starts when the balance goes below $0. Then they are auto-stopped (never deleted)
Stopped sandboxesPlatinum keeps the disk. After the balance is positive, call /start or /resume. Credits alone do not restart them.
Preview URLsTraffic 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 /start or /resume.
  • An auto_delete_days timer 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:

  1. Open dashboard → Billing.
  2. Pick a credit pack of $10, $50, or $200, or enter a custom amount from $5 to $1,000.
  3. 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}'
RuleValue
ThresholdMore than $0
TargetAt least $10 above the threshold
One charge$5 to $2,000
Any 24 hoursAt most 6 attempts and $2,000 charged
Between attemptsAt 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.

SettingApplies toNotifies atBlocks at
usage_alert_usdwhole orgthe amount you set — email + org.usage_alert webhooknever
usage_limit_usdwhole org50 / 80 / 90 / 100% — email + org.usage_limit_reached webhook100% → 402 usage_limit_exceeded
limit_usdone member50 / 80 / 90 / 100% — email to that member100% → 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-limits
curl -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 sandbox
usage = client.request("GET", "/v1/billing/usage?group=sandbox")
one = sbx.usage()  # one sandbox
pt api /v1/billing/usage --query "group=sandbox"
pt api /v1/sandboxes/$SBX/usage
curl -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"
ParamEffect
since_mswindow start (epoch ms)
group=sandboxper-sandbox breakdown, spend-ordered
serieshour 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.

ParamEffect
since_mswindow start (epoch ms)
until_mswindow end (epoch ms)
limitresources 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}