Get started

Limits and plans

One call tells your integration or app what the workspace's plan allows and how fast it may call the API. Read it once, cache it, and read it again when the plan changes.

Read the limits

GET /v1/limits works with any credential: an API key (no scope needed), an app's access token or a signed-in member. It describes the workspace the credential belongs to.

Request
curl https://api.kweko.uz/v1/limits \
  -H "Authorization: Bearer kwk_live_…"
Response
{
  "object": "limits",
  "credential": "api_key",
  "plan": { "id": "pro", "name": "Pro", "effective": "pro", "status": "active", "seats": 5, "trial_ends_at": null },
  "caps": {
    "members": null, "pipelines": 50, "active_leads": null, "contacts": null, "custom_fields": 500,
    "automations_grid": null, "automation_runs_month": 125000, "webhooks": 100, "api_keys": 50,
    "app_installs": 100, "api_requests_day": 250000, "storage_mb": 25600, "ai_credits": 2500, "import_rows": 500000
  },
  "rates": {
    "api_key": { "per_second": 25, "burst": 100 },
    "workspace": { "per_second": 50, "burst": 250 },
    "app": { "per_second": 25, "burst": 100 },
    "in_flight_max": 10
  },
  "api_requests_day": 250000,
  "trust_halved": false,
  "caller": {
    "rate": { "per_second": 25, "burst": 100 },
    "workspace_rate": { "per_second": 50, "burst": 250 },
    "in_flight_max": 10,
    "trigger_rate": null,
    "daily": { "limit": 250000, "used": 1840, "remaining": 248160, "resets_at": "2026-10-08T00:00:00Z", "counter": "api_requests_day" }
  },
  "requests": { "max_body_bytes": 1048576, "page_size_default": 50, "page_size_max": 250, "bulk_max_records": 10000, "webhook_max_events": 100 }
}

The numbers above are a Pro workspace with 5 seats (some caps grow with seats); read your own.

FieldMeaning
planThe plan's id and display name. effective is the plan whose limits apply (the trial is trial with effective: "pro"). status is the subscription's state.
capsHow many of each thing the workspace may have. null means unlimited. Going over answers limit_reached. Support can raise a cap for one workspace; the value here already includes that.
ratesThe plan's token buckets: per API key, per workspace (all keys together), per app installation, and the most requests one credential may have in flight.
api_requests_dayThe plan's daily quota, or null when there is none.
trust_halvedtrue while a new workspace refills at half rate. The rates shown are already halved.
callerThe buckets this credential is counted against right now. trigger_rate is set for app installations (how fast they may fire triggers). daily is today's quota for this credential (api_requests_day for keys, app_requests_day for an app installation), or null.
requestsCaps every request has, whatever the plan: body size, page size, records per bulk job, event patterns per webhook.

GET /v1/usage (scope workspace:read) is still there and adds how much of each cap is used.

Caching

The answer carries Cache-Control: private, max-age=60 and a weak ETag. Send it back as If-None-Match and you get 304 Not Modified while nothing changed.

The ETag covers everything except caller.daily, so a 304 means the plan, caps and rates are the same. Today's usage moves with every request: read it from the RateLimit-Daily-* headers of any response instead of asking again.

When the plan changes

Upgrades, downgrades, a trial ending and a lapsed payment all change what the workspace may do. Kweko sends workspace.plan_changed when the plan or the number of seats changes:

json
{
  "type": "workspace.plan_changed",
  "data": {
    "object": {
      "from": { "plan": "trial", "plan_name": "Pro trial", "seats": 3 },
      "to": { "plan": "free", "plan_name": "Free", "seats": 3 },
      "previous_limits": { "plan": { "id": "trial", … }, "caps": { … }, "rates": { … }, "api_requests_day": 250000, "trust_halved": false },
      "limits": { "plan": { "id": "free", … }, "caps": { … }, "rates": { … }, "api_requests_day": 20000, "trust_halved": false }
    }
  }
}
  • Integrations subscribe to it like any other event on a webhook.
  • Apps get it as a lifecycle webhook on every installation, with no scope needed and no subscription. An installation that is turned off misses it: read GET /v1/limits again on app.enabled.
  • The new limits apply at once. The API's limiter forgets its cached rates for the workspace when the event fires.

The snapshot

The plan, caps, rates, api_requests_day and trust_halved part of GET /v1/limits is the limits snapshot. Apps get the same object without asking:

  • in data.limits of the app.installed lifecycle webhook;
  • in workspace.limits of the frame context (kweko.init()), so your UI can size itself before it calls your backend;
  • in limits and previous_limits of workspace.plan_changed.

How a client should behave

  1. Read GET /v1/limits when you connect (an app: on app.installed), and keep the answer.
  2. Size your work to it: poll no faster than caller.rate allows, page with requests.page_size_max, split bulk jobs at requests.bulk_max_records, and stop before caller.daily.remaining reaches 0.
  3. Read it again on workspace.plan_changed, or with If-None-Match at most once a minute.
  4. On a 429, wait Retry-After seconds before trying again. A reason of api_requests_day or app_requests_day means the day is used up: Retry-After runs to 00:00 UTC, so stop and pick up after the reset instead of retrying.
  5. Don't hard-code plan numbers. They change, and support can change them for one workspace.

With @kweko/sdk/server:

ts
import { getLimits, fetchWithRetry } from "@kweko/sdk/server"

let limits = await getLimits({ accessToken })              // the full answer, with its etag
const fresh = await getLimits({ accessToken, etag: limits!.etag })
if (fresh) limits = fresh                                   // null: nothing changed

// fetch that waits out 429s and 503s for Retry-After (3 retries, 60 s at most per wait)
const res = await fetchWithRetry(`https://api.kweko.uz/v1/leads?limit=250`, { headers: { Authorization: `Bearer ${accessToken}` } })