Envelope and errors
Every Openhandle response uses one envelope. Every error has a code, a request ID, and a retry rule.
Every successful response wraps its data in the same envelope, and every error returns the same error shape with a machine-readable code and an explicit retry flag. You handle responses once, for every platform and every endpoint.
The success envelope
{
"platform": "instagram",
"resource": "profile",
"capturedAt": "2026-08-06T12:04:11Z",
"source": "live",
"data": { "…": "…" },
"meta": {
"cursors": { "next": "opaque-cursor" }
}
}| Field | Meaning |
|---|---|
platform | instagram, tiktok, or twitter |
resource | profile, post, comment, hashtag, location, music, category, list, or normalized extended entity |
capturedAt | RFC 3339 timestamp of when the data was captured |
source | live (fetched now) or cache (served from an earlier capture) |
data | The normalized resource, or an array of resources for lists |
meta | Only on list responses. See pagination |
Shared cores and platform schemas
Openhandle reuses schemas only when their meaning is stable across platforms.
For example, every post composes the shared PostCore, PostMetrics,
PostAuthor, PostMedia, and MediaReference components. The final response
is still an InstagramPost, TikTokPost, or TwitterPost, with a matching
platform metrics schema and platform-only relationships.
Unavailable values are null; measured zeroes are 0. Platform fields are
not discarded merely because another platform lacks an equivalent. TikTok can
return metrics.downloads, while Twitter can return metrics.quotes,
metrics.bookmarks, quoted_post, and reposted_post. Media keeps a preferred
url for simple clients and exposes every usable public rendition in
variants when the upstream response supplies alternatives.
Profiles and comments follow the same contract pattern: a shared core plus a
platform-specific final schema. Profile extended nodes preserve public fields
that exist only on Instagram, TikTok, or Twitter. Domain-specific endpoints use
dedicated schemas where the provider data is stable enough—for example,
InstagramStory, InstagramHighlight, TikTokPlaylist, and
TikTokEffect. Heterogeneous search endpoints use discriminated search-result
nodes, and filter/type endpoints use typed option nodes. No published operation
falls back to the generic Entity schema. See the generated OpenAPI document
for the exact schema attached to each operation.
The error envelope
{
"error": {
"code": "PROFILE_NOT_FOUND",
"message": "This profile was not found.",
"requestId": "req_01j8…",
"retryable": false
}
}Always branch on code, not on message. Include requestId when you
contact support. When retryable is true, retry with backoff; when it is
false, retrying will not help.
Error catalog
| Code | Status | Retryable | Meaning |
|---|---|---|---|
UNAUTHENTICATED | 401 | No | A valid API key is required |
EMAIL_VERIFICATION_REQUIRED | 403 | No | Verify the workspace-owner email before using Live |
PAYMENT_METHOD_REQUIRED | 402 | No | Add a payment method to continue |
BILLING_SUSPENDED | 402 | No | Billing for this workspace is suspended |
SECURITY_SUSPENDED | 403 | No | API access for this workspace is suspended |
PROFILE_NOT_FOUND | 404 | No | The profile does not exist |
PROFILE_PRIVATE | 403 | No | The profile is private |
POST_NOT_FOUND | 404 | No | The post does not exist |
COMMENT_NOT_FOUND | 404 | No | The comment does not exist |
INVALID_IDENTIFIER | 400 | No | The identifier is invalid |
INVALID_FRESHNESS | 400 | No | Freshness must be live, 24h, 7d, or 30d |
INVALID_SINCE | 400 | No | since must be an RFC 3339 timestamp |
CURSOR_MISMATCH | 400 | No | The cursor does not belong to this request |
UPSTREAM_SWITCHED | 409 | Yes | The upstream source changed. Restart pagination |
RATE_LIMITED | 429 | Yes | Rate limit exceeded. Honor the Retry-After header |
SERVICE_UNAVAILABLE | 503 | Yes | The service is temporarily unavailable |
UPSTREAM_DEGRADED | 503 | Yes | The upstream platform is temporarily unavailable |
INTERNAL_ERROR | 500 | Yes | The request could not be completed |
ROUTE_NOT_FOUND | 404 | No | The API route does not exist |
METHOD_NOT_ALLOWED | 405 | No | The HTTP method is not allowed on this route |
Billing and errors
You pay for answers, not attempts. PROFILE_NOT_FOUND, PROFILE_PRIVATE, and
other definitive answers are billable: the API did the work and gave you a
usable result. Provider failures and internal errors are never charged.