# Limits and best practices

> The numbers that bound a run and a key, and how to work well within them.

## Limits

| Limit | Value |
| --- | --- |
| `input` length | 20,000 characters |
| `metadata` size | 2 KB (JSON) |
| Request body | 64 KB |
| Steps per run | 150 |
| Runs in progress per account | 5 (more answer `429 too_many_runs`) |
| Event stream connection | 10 minutes, then reconnect with `?after=` |
| Requests per minute | Per key: 10, 30 (default), 60 or 120 |
| API keys per account | 25 |

## Best practices

<AccordionGroup>
<Accordion title="Keep keys on the server" icon="shield">
Never ship a `clsk_` key in a browser, mobile app or public repository. Call the API from your backend and keep the key in a secret manager or environment variable. Revoke it in the Console the moment it leaks.
</Accordion>
<Accordion title="Give each integration its own key" icon="key-round">
Separate keys per service make logs readable and let you revoke one without breaking the rest. Give each key only the permissions it needs; a dashboard only needs `runs:read` and `account:read`.
</Accordion>
<Accordion title="Stream instead of polling fast" icon="radio">
One stream connection replaces dozens of `GET` calls and never touches your rate limit while it's open. If you poll, wait at least 2 seconds between calls.
</Accordion>
<Accordion title="Handle 429 and 402" icon="circle-alert">
On `429` wait for the `Retry-After` seconds before retrying. On `402 out_of_credits` stop creating runs and alert someone; retrying won't help.
</Accordion>
<Accordion title="Use metadata to find runs later" icon="tag">
Put your own ids in `metadata` (`{"ticket": "OPS-112"}`) so you can match runs to your records.
</Accordion>
</AccordionGroup>
