Skip to content

Error Reference

All Picora API errors return a consistent JSON envelope with both a human message and a machine-readable code.

Response shape

{
"success": false,
"error": "Human-readable description",
"code": "MACHINE_CODE",
"meta": { ...endpoint-specific context... }
}

The HTTP status code indicates the category; code identifies the specific error; meta provides structured context (e.g., meta.type for QUOTA_EXCEEDED, meta.existingId for DOC_HASH_DUPLICATE).

Every response also includes an x-request-id header — keep it when reporting issues to support; it lets us correlate logs across services.

HTTP status overview

StatusMeaning
400Bad Request — malformed request syntax
401Unauthorized — missing or invalid auth
403Forbidden — authenticated but not permitted
404Not Found — resource doesn’t exist
409Conflict — resource state collision
413Payload Too Large — body exceeds limit
422Unprocessable Entity — validation failed
429Too Many Requests — rate limit
451Unavailable for Legal / Quota Reasons — used for video bandwidth suspended and CN content moderation block
500Internal Server Error
502Bad Gateway — upstream (object storage / Bunny / Aliyun) error

Authentication errors

CodeStatusDescriptionHow to fix
UNAUTHORIZED401Missing Authorization header, malformed API key, or expired JWT.Include Authorization: Bearer sk_live_... (API Key) or refresh JWT via /v1/auth/refresh.
API_KEY_REVOKED401API Key was manually revoked.Create a new key in Settings → API Keys.
TOKEN_EXPIRED401JWT access token (15-min lifetime) expired.Refresh via /v1/auth/refresh.
OAUTH_BEARER_INVALID401OAuth bearer token signature invalid or token revoked (v0.14+ MCP HTTP).Re-authorize the AI client in Authorized apps.
OAUTH_REFRESH_REPLAYED401A previously-rotated refresh token was replayed (v0.14+; auto-revokes the entire token chain as a security measure).Re-authorize from scratch.

Permission errors

CodeStatusDescriptionHow to fix
FORBIDDEN403No permission for this resource (e.g., another user’s image, admin route without admin role).Only access your own resources; for /v1/admin/* endpoints you need is_admin = 1.
OAUTH_SCOPE_INSUFFICIENT403OAuth token lacks required scope (e.g., calling delete_doc with only docs:read).Re-authorize with broader scopes.
QUOTA_EXCEEDED403A quota dimension is exhausted. meta.type indicates which: img_storage / media_storage / doc_count / etc.; meta.used / meta.limit show numbers.Delete unused resources or upgrade plan.
PLAN_REQUIRED403Feature requires a higher tier (e.g., video upload requires pro).Upgrade plan.
COMPLIANCE_BLOCKED451(CN platform only) Aliyun content safety API flagged the upload as policy-violating.See the included meta.reasons and submit appeal if you believe it’s a false positive.

Resource errors

CodeStatusDescriptionHow to fix
NOT_FOUND404Resource doesn’t exist. meta.entity indicates type (Image / Video / Document / CdnWhitelistEntry / etc.).Verify the ID; the resource may have been deleted.
CONFLICT409Conflicting state — e.g., email already registered.Use a different value or resolve the conflict.
DOC_HASH_DUPLICATE409Markdown content (rewritten + hashed) already exists for this user; meta.existingId returns the original document ID.This is semantic — the duplicate is treated as a no-op; use the existing ID. Does not consume doc_count quota.

Validation errors

CodeStatusDescriptionHow to fix
VALIDATION_ERROR422Generic Zod schema validation failure; meta.path and meta.issues describe specifics.Fix the indicated fields.
INVALID_FILE_TYPE422Uploaded file format not supported.Use accepted formats (see images / videos / audio).
FILE_TOO_LARGE / 413413 / 422File exceeds plan / endpoint limit.Compress / split / upgrade.
DOC_FILE_TOO_LARGE422Markdown content exceeds plan’s doc_max_file_bytes.Split into multiple docs or upgrade.
DOC_IMAGE_LIMIT_EXCEEDED422Embedded image count in Markdown exceeds plan’s doc_max_images_per_doc.Reduce embedded images or upgrade.
DOC_INVALID_PATTERN422(Admin CDN allowlist) Pattern contains protocol / path / illegal chars.Use bare hostname, e.g., media.example.com (exact) or .example.com (suffix).

Rate limit errors

CodeStatusDescriptionHow to fix
RATE_LIMITED429Tier rate limit exceeded. Retry-After response header indicates seconds until reset.Wait and retry; consider request batching or backoff.
OAUTH_CLIENT_RATE_LIMITED429OAuth dynamic client registration rate limit hit (v0.14+).Reduce registration frequency.

Server errors

CodeStatusDescriptionHow to fix
INTERNAL500Unexpected error; report with x-request-id.Retry with exponential backoff; if persistent, contact support with the request ID.
STORAGE_ERROR502Object storage (R2 / OSS / COS) PUT/GET failed after retries.Retry; if persistent, see status page.
DEPENDENCY_UNAVAILABLE502Upstream provider (Bunny / Aliyun VOD / Lemon Squeezy / etc.) timeout or 5xx.Retry; if persistent, check status page.

Bandwidth-specific status

Video playback respects monthly bandwidth quota with three states:

StateBehaviorAPI code on playback
okFull quality served(no error)
degradedOnly 360p variants in HLS playlist(no error; status visible in metadata)
suspendedAll playback returns 451BANDWIDTH_SUSPENDED (in 451 body)

The API itself (e.g., GET /v1/videos/{id}) is not rate-limited by bandwidth state — only the actual video.picora.me playback edge enforces it. See bandwidth & degradation.


Handling errors in code

const response = await fetch('https://api.picora.me/v1/images', {
method: 'POST',
headers: { 'Authorization': 'Bearer sk_live_YOUR_KEY' },
body: formData,
});
const json = await response.json();
if (!response.ok) {
// Use json.code for programmatic decisions, json.error for display
switch (json.code) {
case 'QUOTA_EXCEEDED':
console.error(`Quota exhausted: ${json.meta?.type}`);
// prompt user to delete or upgrade
break;
case 'RATE_LIMITED':
const retryAfter = response.headers.get('Retry-After');
console.warn(`Rate limited; retry in ${retryAfter}s`);
break;
case 'PLAN_REQUIRED':
// route user to upgrade flow
break;
default:
console.error(`Upload failed (${response.status} ${json.code}): ${json.error}`);
}
return;
}
console.log('Uploaded:', json.data.url);

For long-running scripts, also handle 429 retries with respect for the Retry-After header; many APIs silently rate-limit clients that ignore it.


Reporting bugs

When opening a support ticket, include:

  • The x-request-id from the failing response
  • Approximate timestamp
  • Full request URL and method (sanitize any API Keys)
  • Browser / OS / SDK version

Without x-request-id, locating logs is much harder.