DevelopersRate limits & quotas

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

PlanSustainedBurst
Basic5,000 requests / dayup to 60 requests / minute
Pro100,000 requests / dayup to 300 requests / minute
Team500,000 requests / dayup to 600 requests / minute
EnterpriseCustomCustom

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'.

Honour Retry-After. If you must implement your own backoff:

  1. Start with the Retry-After value (or 1 second if missing).
  2. Double on every subsequent 429, capped at 60 seconds.
  3. 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 / monthPhiSo visitor questions
Basic50,000not available
Pro500,0005,000
Team1,000,00015,000
Enterprise5,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 state

A 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