FoodPerfect Docs

Errors, rate limits and idempotency

#Error format

Every error has the same envelope:

Error envelope
{
  "error": {
    "code": "rate_limited",
    "message": "Human-readable explanation.",
    "requestId": "7f3a…",
    "retryable": true,
    "details": { }
  }
}

It arrives in one of two places:

  • Tool errors come back as a normal tool result with isError: true, and the envelope in structuredContent (and as JSON text in content). Most MCP clients show these to the model so it can react.
  • Request errors, such as a bad key, a disallowed origin or the rate limit, are returned as an HTTP status (401, 403, 429) with the envelope as the JSON body, before any tool runs.

Every response has an x-request-id header. Include it when you contact support.

#Error codes

Code Retry? Meaning and what to do
invalid_input No Arguments don't match the schema, or an idempotency key was reused with different arguments. Fix the call.
unauthorized No Key missing, wrong, revoked or expired; the key's scopes don't cover the tool; or the paid subscription isn't active.
not_found No The id doesn't exist or belongs to another account.
payment_required No The account has no active plan or trial (canceled, or the last payment failed). Fix billing in the app's Billing page, then call again.
insufficient_credits No The balance can't cover the call. details has availableMicros and requiredMicros. Top up, don't retry.
requires_approval No Not a failure: the action needs confirmation. See Approvals.
rate_limited Yes Too many calls. Wait for Retry-After / details.retryAfter seconds.
retryable_upstream_failure Yes An image, video or AI provider failed or timed out, or the same idempotent call is still running. Retry with backoff.
internal No Something broke on our side. Try once more later, then contact support with the request id.

Retry only when retryable is true: exponential backoff starting around 2 seconds, at most 3 attempts, with the same Idempotency-Key.

#Rate limits

Limit Default
Tool calls per API key 60 per minute (initialize and tools/list are free)
generate_reel_storyboard 60 per account per day (UTC)
scrape_trending_hooks, trending hooks in resolve_reel_generators 10 searches per account per hour
Request body 1 MB

Over the per-key limit you get HTTP 429:

Response
HTTP/1.1 429 Too Many Requests
Retry-After: 12
Content-Type: application/json

{"error":{"code":"rate_limited","retryable":true,"details":{"rateLimit":60,"retryAfter":12}, …}}

Higher per-key limits (up to 600 a minute) are available on request.

#Idempotency

A network error can leave you unsure whether a call ran. Idempotency keys make retries safe: send the same key again and you get the first result back instead of a second charge or a duplicate post.

Send the header on any tool that creates, changes or spends (everything marked Write in the tool reference). It's optional, but recommended whenever your code retries:

Header
Idempotency-Key: 5f0c2d6e-2a8b-4a61-9d0f-6a1c3e7b9c42

(X-Idempotency-Key works too.) Read tools ignore it.

Situation Result
New key The call runs; its result is stored for 24 hours.
Same key, same tool, same arguments The stored result is returned. Nothing runs or is charged again.
Same key, different arguments invalid_input. Use a new key for a new call.
Same key while the first call is still running retryable_upstream_failure. Wait and retry.
Same key after the call failed The failed call was refunded, so the retry runs and is charged like a new call.
Same key after the first call never finished (server crash) Within 30 minutes its charge is refunded automatically, and the retry runs as a new call.
No key The call runs normally, without duplicate protection.

Generate a fresh random key (a UUID is ideal) for every new action, and reuse it only to retry that action. Keys are scoped to your account and the tool, so they can't collide with anyone else's.