Limits at a glance
| Limit | Value |
|---|---|
| Requests per minute per IP address | 120 |
| Requests per minute per key | 60 |
| Requests per minute per office (all keys) | 240 |
| Writes per minute per key | 30 |
| Writes per minute per office | 120 |
| Records per list page | 1–100, default 25 |
| Write request body | 64 KB |
| Key lifetime (days) | 30, 90 or 365 |
| AI assistant: calls per minute per connection | 30 |
| AI assistant: calls per minute per office | 120 |
| AI assistant: saved changes per minute per connection | 20 |
Rate limits
Per minute: 60 requests per key, 240 per office (all keys together) and 120 per client IP address. Writes count against those and against their own windows: 30 writes per key and 120 per office. Successful responses include X-RateLimit-Limit and X-RateLimit-Remaining for the key window (the write window on writes). A 429 always carries Retry-After (seconds until the window resets) and no X-RateLimit-* headers. If our rate-limit store is unavailable the API refuses requests with 429 rather than serving them unlimited.
What gets logged
Each authenticated read leaves an audit row with the key, the route template (/api/v1/leads/{id}, never the id itself), the HTTP status, the number of records returned and the /24 (IPv4) or /48 (IPv6) network of the caller. No filter values, ids or response data.
Each write attempt, refused ones included, leaves a row with the key, the resource, the operation (create, update, delete), the record id, the names of the fields sent, the HTTP status and the network as above. Never the values.
Not in v1
No hard deletes of clients or listings (see the table above), no DELETE for inquiries and sales, no bulk endpoints. Commission, settlement, internal notes, portal publication and photos are not writable. There's no OAuth in this API: keys only. AI assistants (Claude) connect through the separate, OAuth-protected MCP server at https://estalo.pl/api/mcp, which reads through the same registry and scoped reader and, with change scopes, writes through the same write service in two steps (preview, then the user's confirmation): mcp.md.
Office data isolation
Every record in Estalo belongs to one office, and the database has row-level security (RLS) policies on every table with office data. The API applies the same rules as the app:
- Reads always filter by the key's office, then by assignment (for roles other than leadership), and finally leave out hidden records (trash, archived clients). A record of another office answers
404, like one that doesn't exist. - Writes run as the key's creator, through the same functions as the web and mobile apps, under the database's RLS policies. On every row it writes, the database also checks that the key has the resource's write scope.
- A key never sees more than its creator, and the creator's rights are checked on every request: a role change, leaving the office or a lapsed plan take effect on the next request.
Keys and secrets
- Estalo stores only a SHA-256 hash of a key. You see the full key once, when you create it; afterwards only its beginning.
- Every key has an expiry (30, 90 or 365 days). Replacement and revocation take effect on the next request.
- A key with a write scope can be created only by an owner with two-step verification on, and changes must have been switched on for the office first. When the key's creator switches two-step verification off or the owner switches changes off, the key stops writing.
- A webhook secret (
whsec_…) is shown once and stored encrypted.
Recommendations for integrators
- Keep the key on a server: in a secret manager or an environment variable. Never in website code, a mobile app, a repository, a spreadsheet or a support ticket.
- One integration, one key, only the scopes it needs. Give a write scope only to the integration that writes.
- Pick the shortest expiry that works and replace the key before it runs out. Revoke it at once if you suspect a leak.
- A key sees what its creator sees. For an integration that must see the whole office, the owner creates the key; an agent's key sees only that agent's records.
- Handle
429by waitingRetry-After. Retry writes with the sameIdempotency-Key, and sendexpected_updated_atwith edits so you don't overwrite someone else's change. - In a webhook receiver, verify the signature, reject stale timestamps, deduplicate on
X-Estalo-Event-Idand fetch the record's current state through the API. - Don't log the
Authorizationheader or full API responses. Data from the API is personal data of the office's clients: store it like the rest of the office's data and only for as long as the integration needs it.
Reporting a vulnerability
Report a suspected vulnerability privately to biuro@programo.pl, not in a public issue. Describe the impact and the steps to reproduce (URL, request, account role) and don't include real keys or client data.