Rate limits & quotas
The PhiWebs API protects every World with two separate ceilings:
- REST rate limits — count requests per rolling window, per World.
- Φ (AI) grant — counts Φ spent on accepted Receipts, PhiCo turns, PhiSo answers; resets each billing period.
Both ceilings degrade gracefully — the API answers with a typed error shape, never a silent drop or a 500.
REST rate limits per plan
| Plan | Sustained | Burst |
|---|---|---|
| Basic | 5,000 requests / day | up to 60 requests / minute |
| Pro | 100,000 requests / day | up to 300 requests / minute |
| Team | 500,000 requests / day | up to 600 requests / minute |
| Enterprise | Custom | Custom |
Requests are counted per worldId resolved from your bearer token. Webhook
inbound endpoints (/api/billing/webhook, marketplace webhooks) bypass
the user rate limit — they have their own per-signature limiter.
429 response shape
When you exhaust either ceiling, the API returns HTTP 429 Too Many Requests with a Retry-After header in seconds and a typed JSON body:
HTTP/1.1 429 Too Many Requests
Retry-After: 42
Content-Type: application/json
{
"ok": false,
"error": {
"code": "rate_limit_exceeded",
"message": "World wld_123 exceeded its sustained REST quota for plan pro.",
"statusCode": 429,
"details": {
"limit": "sustained",
"window": "rolling-24h",
"remaining": 0,
"resetSeconds": 42
}
}
}The SDK surfaces this through the same discriminated union every other
method uses — pattern-match on result.ok === false and check
result.error.code === 'rate_limit_exceeded'.
Recommended backoff
Honour Retry-After. If you must implement your own backoff:
- Start with the
Retry-Aftervalue (or 1 second if missing). - Double on every subsequent 429, capped at 60 seconds.
- Add ±25% jitter to avoid thundering-herd reconnects.
@pwfabric/sdk will ship a withRetry() helper that does this in a
future release; until then, wrap calls in your own retry loop.
Φ (AI) grant
Φ tracks AI cost separately from REST volume. Each plan ships with a monthly Φ grant:
| Plan | Φ grant / month | PhiSo visitor questions |
|---|---|---|
| Basic | 50,000 | not available |
| Pro | 500,000 | 5,000 |
| Team | 1,000,000 | 15,000 |
| Enterprise | 5,000,000 (custom on request) | unlimited |
When you hit the cap
- PhiCo accept — the Receipt is rejected with
error.code = 'insufficient_phi'. Top up from World settings → Billing to continue. - PhiSo answer — the widget responds with a polite “I’ll have someone get back to you” using the configured escalation path. Visitor experience never breaks.
- No Φ is charged for rejected Receipts, refused PhiSo answers, or 4xx errors. Only accepted Receipts and answered PhiSo turns count.
Φ grace
If a billing cycle ends mid-conversation, in-flight PhiCo / PhiSo turns are honoured against the new period’s grant. The cap is recomputed on the first request of the new cycle.
Concurrency
A single World can have up to:
- 10 concurrent SSE streams open (PhiCo + PhiSo combined).
- 20 concurrent long-running requests (uploads, exports).
- 100 concurrent short requests (CRUD).
Excess concurrent requests queue server-side for up to 5 seconds before
returning 503 Service Unavailable with Retry-After: 1.
Inspecting your usage
Hit the REST endpoints directly:
GET /api/ai/usage # Φ ledger spend per period
GET /api/ai/credits/balance # remaining Φ
GET /api/billing/overview # plan, period, REST quota stateA typed SDK helper (client.usage.current()) is planned for a future
release — until it ships, call the endpoints above with your bearer
token.
See also
- API reference — every endpoint, including the usage routes above.
- SDK reference —
ApiResultdiscriminated union. - Billing & plans — Φ grants per plan, top-up flow.