> ## Documentation Index
> Fetch the complete documentation index at: https://widgetic.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Usage Limits

> Rate limits and quotas for the Widgetic API

The Widgetic API implements rate limits and usage quotas to ensure fair usage and service stability.

## Rate Limits

Requests are counted over a 15-minute window. A recognized API key is counted on its own, so one customer's traffic never uses up another's. Anything else is counted by IP address.

| Counted | Allowance |
| - | - |
| An API key | About 3,000 requests per 15 minutes |
| A partner API key | 1,000,000 requests per 15 minutes |

The allowance that applies to a call is in the `RateLimit-Limit` header of its response, so read it there instead of hard-coding a number. The partner ceiling is there to stop a runaway loop, not to meter a platform that serves hundreds of thousands of its own users.

### Rate Limit Headers

Responses that were counted include rate limit information:

```
RateLimit-Policy: 3000;w=900
RateLimit-Limit: 3000
RateLimit-Remaining: 2995
RateLimit-Reset: 540
```

| Header | Description |
| - | - |
| `RateLimit-Policy` | The allowance and the length of the window in seconds: `3000;w=900` is 3,000 requests per 900 seconds |
| `RateLimit-Limit` | Maximum requests per window |
| `RateLimit-Remaining` | Requests remaining in window |
| `RateLimit-Reset` | Seconds until the window ends (a number of seconds to wait, not a timestamp) |

The older `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset` headers are sent as well, for clients that still read them. The first two carry the same numbers. `X-RateLimit-Reset` does not: it is the time the window ends, in Unix seconds, not a number of seconds to wait. Read the `RateLimit-*` headers.

## When Rate Limited

If you exceed a limit, you'll receive a `429 Too Many Requests` response with a `Retry-After` header. `Retry-After` and `error.retryAfter` are the same number, in whole seconds:

```json theme={null}
{
  "success": false,
  "message": "Too many requests, please try again later.",
  "error": {
    "code": "RATE_LIMIT_API_KEY_EXCEEDED",
    "message": "Too many requests, please try again later.",
    "retryAfter": 540,
    "details": { "limit": 3000 }
  }
}
```

`error.code` tells you which limit it was. See [Rate Limits](https://widgetic.com/docs/reference/rate-limits.md) for every code and for how to retry.

## Endpoint-Specific Limits

Some operations have a limit of their own, on top of the allowance on your key. Each one is counted for each person, across all of their keys and sessions: a partner key and the same person's dashboard session count together. A refusal has the code `RATE_LIMIT_USER_EXCEEDED`.

| Operation | Limit |
| - | - |
| Creating a widget (`POST /widgets`) | 120 per minute |
| Billing operations | 20 per minute |
| Buying extra credits (`POST /partners/{partnerId}/credits/extra-invoice`) | 10 per hour |
| Changing, removing or checking a partner's storage destination (`PUT`, `DELETE` and `POST …/verify` on `/partners/{partnerId}/storage`). Reading it is not counted | 10 per 10 minutes |
| Partner reports (`/partners/{partnerId}/storage/usage` and `/partners/{partnerId}/analytics/…`) | 60 per minute |
| Reading partner notifications, deliveries and settings | 60 per minute |
| Changing the notification settings, or making a new webhook secret | 20 per minute |
| Replaying a delivery | 5 per minute |
| Sending a test event | 5 per minute |
| Reading the entry context signing setting (`GET /partners/{partnerId}/entry-context-signing`) | 60 per minute |
| Changing it, or making its secret | 20 per minute |
| A managed user's entries feed (`GET /managed-users/{id}/entries`) | 30 per minute |
| Listing the items of an entry table (`GET /compositions/{id}/entries/{table}/items`) | 120 per minute |
| Deleting items of an entry table | 30 per minute |
| Exporting an entry table | 20 per minute |

Each gateway server counts these on its own, so when several servers answer, the number let through can be higher than the figure shown.

AI generations are also held to a usage budget per hour and per day, and to a number of generations running at once. A refusal says which (`COST_LIMIT_EXCEEDED` or `CONCURRENCY_LIMIT_EXCEEDED`).

## Usage Quotas

### Plan Allowances

| Resource | Free | Solo | Teams | Partner |
| - | - | - | - | - |
| AI credits | 100 starter credits, for the 7-day trial window | 400 a month | 2,000 a month for each member | By contract |
| Storage | 10 GB | 100 GB | 500 GB shared by the team | By contract |

One credit pays for one AI generation. See [Credits & Billing](https://widgetic.com/docs/partners/credits-billing.md#what-a-credit-costs) for what spends credits.

**Largest single file.** The same on every plan: a file can be up to 250 MB, whether it is uploaded from the app (a resumable upload) or saved to the media library by an AI assistant. A file sent in one API request (`POST /uploads`) can be up to 10 MB. `GET /uploads/limits` returns both limits (`multipart_max_bytes` for one request, `tus_max_bytes` for a resumable upload).

### Checking Your Credits

Read the credit balance of the account behind your key. Credits left are `balance` minus `reservedBalance`:

```bash theme={null}
curl -sS -H "Authorization: Bearer $WIDGETIC_API_KEY" \
  "https://api.widgetic.com/v1/credits/user/$WIDGETIC_ACCOUNT_ID/balance"
```

See [Credits & Billing](https://widgetic.com/docs/partners/credits-billing.md#checking-your-balance) for how to find your account id.

## Best Practices

### Implement Backoff

```typescript theme={null}
import { ResponseError } from '@widgetic/api-sdk-ts';

async function withBackoff<T>(call: () => Promise<T>, maxRetries = 3): Promise<T> {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await call();
    } catch (error) {
      if (!(error instanceof ResponseError) || error.response.status !== 429 || attempt === maxRetries) {
        throw error;
      }
      // Retry-After is a number of seconds. A CONCURRENCY_LIMIT_EXCEEDED refusal has none: wait a few seconds.
      const waitSeconds = Number(error.response.headers.get('Retry-After')) || 5;
      await new Promise((resolve) => setTimeout(resolve, waitSeconds * 1000));
    }
  }
  throw new Error('Max retries exceeded');
}
```

### Batch Operations

Use batch endpoints to reduce API calls:

```typescript theme={null}
// Instead of one call for every widget
const { widgets: foundWidgets, errors } = await widgetsApi.getWidgetsBatch({
  getWidgetsBatchRequest: { widgetIds }
});
```

### Cache Responses

Cache read-only data to reduce API usage, and let it expire so that a change you make is picked up:

```typescript theme={null}
const cache = new Map<string, { value: unknown; expiresAt: number }>();
const cacheLifetimeMs = 60_000;

async function getCachedComposition(id: string) {
  const cached = cache.get(id);
  if (cached && cached.expiresAt > Date.now()) {
    return cached.value;
  }

  const { data } = await compositionsApi.getCompositionById({ id });
  cache.set(id, { value: data, expiresAt: Date.now() + cacheLifetimeMs });
  return data;
}
```

## Requesting Higher Limits

If you need higher limits, contact [support@widgetic.com](mailto:support@widgetic.com) with your use case and the volume you expect.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.