Skip to main content

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​

StatusWhat it meansWhat to do
400The 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.
401The key belongs to an account that is inactive.Check the account at thedogapi.com.
403No 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.
404The id doesn't exist, or it has been deleted.Don't retry.
409It already exists, for example a pending request to make an image public.Read the existing record instead.
415The 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.
422The 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.
429Too many requests or uploads in a short time.Back off and retry later (see below).
500Something broke on our side.Retry with backoff. If it keeps happening, tell us on Discord with the path and timestamp.
503Photo 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 returns quota, usage and remaining. Uploading past it returns 400.
  • Photos per pet: 100 new photos per pet in 24 hours. More returns 429 with code: "pet_upload_rate_exceeded", plus limit, window_hours, recent_uploads and requested. 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.