Get started
Limits
Rate limits for organization and clinic keys, the 10-minute request timeout, body, file and text size limits, object counts and how long signed links stay valid.
This page collects every limit you can run into when you call the Vetnio API: how many requests a key may make, how long a request may run, how large bodies, files and texts may be, how many objects you can create, and how long signed links last. Design your integration around them from the start; most of them fail loudly, but a few currently fail with a generic 500.
Rate limits
Organization keys (sk_org_…) and clinic keys (sk_ws_…) are rate limited per key:
| Limit | Value | Window |
|---|---|---|
| Requests | 60 per minute | Fixed calendar minute (UTC), not a rolling 60 seconds |
| Requests | 10,000 per day | Calendar day in UTC; resets at 00:00 UTC |
| Usage export requests with note-quality columns | 20 per day | Calendar day in UTC |
Every request the key is allowed to make counts, including requests that then fail validation and requests that are themselves rejected with 429. Retrying in a tight loop while you are limited therefore uses up the daily quota. GET/api-key shows the limits and how many requests the key has made today and over the last 30 days.
Personal keys (sk_live_…) have no per-key rate limit today. Treat that as headroom, not as permission: keep your request rate reasonable, run long jobs sequentially, and handle 429 anyway so your integration keeps working if limits are introduced.
When you hit a limit
The response is 429 Too Many Requests. The message tells you how long to wait; there is no Retry-After header.
{
"message": "Rate limit of 60 requests per minute reached. Retry in 53 seconds.",
"code": "TOO_MANY_REQUESTS",
"data": {
"code": "TOO_MANY_REQUESTS",
"httpStatus": 429,
"path": "apiKeys.me",
"zodError": null
}
}The other two limits say when they reset:
Daily limit of 10000 requests reached. It resets at 2026-10-05T00:00:00.000Z.Daily limit of 20 requests with note-quality columns (avgReadingSeconds, avgEditingSeconds, acceptanceRate, draftOutcome) reached. Leave those columns out, or retry after 2026-10-05T00:00:00.000Z.Wait until the next full minute, or until the stated time, before you send the next request. A request without note-quality columns still works when only the note-quality limit is reached. A simple approach in code:
import re, time
from datetime import datetime, timezone
def wait_after_429(message: str) -> None:
seconds = re.search(r"Retry in (\d+) seconds", message)
if seconds:
time.sleep(int(seconds.group(1)) + 1)
return
reset = re.search(r"(\d{4}-\d{2}-\d{2}T[\d:.]+Z)", message)
if reset:
resets_at = datetime.fromisoformat(reset.group(1).replace("Z", "+00:00"))
raise RuntimeError(f"Daily limit reached; resume after {resets_at.isoformat()}")Request timeout
A request may run for up to 10 minutes. Most requests finish in well under a second; the ones that can take long are those that wait for AI work, such as generating a clinical note or asking VetsGPT.
If a request runs past 10 minutes, the hosting platform ends it with 504 Gateway Timeout, in the platform's own format rather than Vetnio's JSON error object. The work may still have finished on Vetnio's side. Before you retry a request that creates or generates something, check its result first; see Retrying safely. Set your HTTP client's timeout a little above 10 minutes for these endpoints so you do not give up before Vetnio does.
Request body and file sizes
| What | Limit |
|---|---|
| JSON request body | About 4.5 MB, enforced by the hosting platform. Larger bodies are rejected with 413 before they reach Vetnio. |
| Audio recordings | Not sent through the API. Upload each recording with one PUT to the signed URL from POST/workspaces/{workspaceId}/consultations or GET/workspaces/{workspaceId}/consultations/{consultationId}/recording-upload-url; storage accepts up to 5 GB per PUT. |
| Files attached to a consultation | Uploaded with one PUT to the URL from POST/workspaces/{workspaceId}/consultations/{consultationId}/uploaded-files. Vetnio sets no size limit, but the bytes must match sizeBytes exactly. Allowed types: PDF, PNG, JPEG, WebP, GIF, CSV, plain text, Word (.docx) and Excel (.xlsx, .xls). Up to 1,000,000 characters of extracted text are kept per file. |
On v2, every POST, PUT and PATCH must send Content-Type: application/json and a JSON body, {} if there is nothing to send. Otherwise the request fails with 415; see Versioning.
Text lengths
| Field | Limit | Over the limit |
|---|---|---|
Record content in PUT/workspaces/{workspaceId}/consultations/{consultationId}/records and when creating a record | 50,000 characters | 500 (currently not 400) |
Additional info in PUT/workspaces/{workspaceId}/consultations/{consultationId}/additional-info | 1 to 10,000 characters | 400 |
Text in POST/workspaces/{workspaceId}/consultations/{consultationId}/partial-transcriptions | 1 to 50,000 characters | 400 |
VetsGPT message in POST/workspaces/{workspaceId}/vetsgpt/ask | 1 to 50,000 characters | 400 |
VetsGPT idempotencyKey | 8 to 128 characters | 400 |
| Template body | No limit in the API; Vetnio's template editor stops at 150,000 characters | |
| Organization and clinic key names | 80 characters | Set in the app |
| Transcription terms | 200 terms per list, each up to 50 characters and 4 words | Set in the app; read them with the configuration export |
Object counts
| Object | Limit | Over the limit |
|---|---|---|
| Personal API keys | 20 per user (mobile app devices do not count) | The app refuses to create more. |
| Organization and clinic keys | 10 active per organization, both kinds together; a key being replaced counts until its grace period ends | The app refuses to create or replace a key. |
| Templates | A clinic that already has more than 100 templates, Vetnio's default templates included, cannot create more with POST/workspaces/{workspaceId}/templates | 500 (currently not 4xx) |
| Clinics | POST/workspaces creates one clinic per user; more need Vetnio to enable it for the account | 500 (currently not 4xx) |
| Consultations attached to one VetsGPT chat | 50 | 500 |
Where a limit currently surfaces as 500 with An unexpected error occurred, check your input against this table before you retry. The same request will keep failing.
Usage export
| Limit | Value |
|---|---|
| Date range | At most 366 days per request |
pageSize with grain=consultation | 1 to 5,000 rows, default 1,000 |
| Furthest page | Pages that start after row 50,000 are refused with 400; split the date range instead |
| Note-quality columns | 20 requests per key per day, as above |
Page sizes for the other list endpoints are in Pagination.
Link lifetimes
Several endpoints return signed storage URLs. They work without your API key, so treat them as secrets, and they stop working after a fixed time:
| URL | Valid for |
|---|---|
Recording upload URL (recordingUploadUrl) | 24 hours |
Processed recording URLs from process-recordings | 24 hours |
Uploaded-file upload URL (uploadUrl) | 1 hour |
Uploaded-file download URL from GET/workspaces/{workspaceId}/consultations/{consultationId}/uploaded-files/{fileName}/download-url | 1 hour |
| VetsGPT attachment upload URL | 24 hours |
| Clinical data export download links | 1 hour, see expiresAt |
| Configuration export download links | 1 hour, see expiresAt |
When a link has expired, request a new one from the same endpoint; the stored content is not affected. For an uploaded file whose upload URL expired before you used it, delete the file and start the upload again.
The old key's grace period after Replace key on an organization or clinic key is 24 hours; see Authentication.
Next steps
- Errors: the error format and retry rules.
- Pagination: page sizes and how to page through lists.
- Export usage statistics: working within the usage export limits.
Endpoints used in this guide
- GET
/api-keyCheck the API key you are using - GET
/organizations/{organizationId}/data-export/usageExport usage statistics - POST
/workspaces/{workspaceId}/consultationsCreate a new consultation - GET
/workspaces/{workspaceId}/consultations/{consultationId}/recording-upload-urlGet a signed URL to upload a recording - POST
/workspaces/{workspaceId}/consultations/{consultationId}/uploaded-filesInitialize a file upload - GET
/workspaces/{workspaceId}/consultations/{consultationId}/uploaded-files/{fileName}/download-urlGet a download URL for an uploaded file - PUT
/workspaces/{workspaceId}/consultations/{consultationId}/recordsUpdate a record - PUT
/workspaces/{workspaceId}/consultations/{consultationId}/additional-infoUpdate additional info - POST
/workspaces/{workspaceId}/consultations/{consultationId}/partial-transcriptionsCreate partial transcription - POST
/workspaces/{workspaceId}/vetsgpt/askSend a message to VetsGPT - POST
/workspaces/{workspaceId}/templatesCreate a template - POST
/workspacesCreate a workspace
Last reviewed against the API on 2026-10-26.