Errors and limits
Every error comes back as JSON with the same shape:
{
"statusCode": 404,
"timestamp": "2026-10-06T03:03:08.534Z",
"path": "/v1/pets/doesnotexist",
"message": "Pet with id \"doesnotexist\" not found",
"error": "Not Found"
}
message is written for a developer to read, so log it. Some errors also carry a code you can branch on, such as weight_required or pet_upload_rate_exceeded. Branch on statusCode and code, never on the wording of message, which can change.
Status codes
| Status | What it means | What to do |
|---|---|---|
400 | The request is invalid: a missing or malformed field, a pet with no photos to analyse, or your monthly upload quota is used up. For validation errors message is a list, one entry per problem. | Fix the request. Don't retry it unchanged. |
401 | The key belongs to an account that is inactive. | Check the account at thedogapi.com. |
403 | No key, an invalid key, a feature your account doesn't have, a capability you've switched off, or a pet or image that belongs to another account. | Read message. For features, see Choose what's switched on. |
404 | The id doesn't exist, or it has been deleted. | Don't retry. |
409 | It already exists, for example a pending request to make an image public. | Read the existing record instead. |
415 | The upload isn't a format we accept, for example a video that isn't MP4, MOV, WebM, AVI, MPEG or 3GP. | Convert it, or check the file is what its name says. |
422 | The request is valid but we can't work it out from what you sent, for example a calorie calculation with no usable weight. | Read code and send the missing input. |
429 | Too many requests or uploads in a short time. | Back off and retry later (see below). |
500 | Something broke on our side. | Retry with backoff. If it keeps happening, tell us on Discord with the path and timestamp. |
503 | Photo analysis is temporarily unavailable. | Retry with backoff. |
Examples of the bodies you'll see most:
// 403: missing or invalid key
{ "statusCode": 403, "message": "Authentication required. Please provide a valid API key.", "error": "Forbidden" }
// 403: feature not on your account
{ "statusCode": 403, "message": "This feature requires account access to: calculators", "error": "Forbidden" }
// 403: photo analysis not included in your plan
{ "statusCode": 403, "message": "Body condition score is not enabled for this account. Contact us to have the body_condition feature switched on.", "error": "Forbidden" }
// 400: validation
{ "statusCode": 400, "message": ["weight_kg must be a positive number"], "error": "Bad Request" }
// 422: not enough to work with
{ "statusCode": 422, "code": "weight_required", "message": "no usable weight: send weight_kg, a breed_id or a pet with a weight" }
// 503: analysis unavailable
{ "statusCode": 503, "message": "The AI analysis service is temporarily unavailable. Please try again later." }
Limits
- Requests: limits depend on your plan. See Authorization.
- Image uploads: each account has a monthly upload quota. Check it with
GET /accounts/quota, which returnsquota,usageandremaining. Uploading past it returns400. - Photos per pet: 100 new photos per pet in 24 hours. More returns
429withcode: "pet_upload_rate_exceeded", pluslimit,window_hours,recent_uploadsandrequested. Ask us if you need more. - File size: 50 MB per photo and 200 MB per video.
Retrying safely
Retry only 429, 500 and 503. Every other error will fail the same way again.
- Wait before each retry and double the wait each time: 1 s, 2 s, 4 s, 8 s, plus a random fraction of a second so many clients don't retry together.
- Stop after 4 or 5 attempts and show the user an error.
- Never retry in a tight loop, and never retry on a timer that ignores the response.
async function withRetry(call: () => Promise<Response>, attempts = 5): Promise<Response> {
for (let i = 0; i < attempts; i++) {
const res = await call();
if (![429, 500, 503].includes(res.status)) return res;
const wait = 2 ** i * 1000 + Math.random() * 500;
await new Promise((r) => setTimeout(r, wait));
}
throw new Error('Still failing after retries');
}
Re-sending the same photo to a pet is safe: if the pet already has a photo with the same bytes, you get the existing image back instead of a copy. Store analysis results on your side rather than calling the endpoint again, and use webhooks to get results without polling.