Heista API Reference
The complete developer reference for the Heista API. Six APIs, one platform, shared credits, structured JSON.
Introduction
Heista exposes creative intelligence as an API. Decode any video ad into its structural formula. Extract brand intelligence from a URL or documents. Generate ad scripts in any brand's voice. Browse the decoded corpus for free.
Every endpoint returns structured JSON. Every response uses the same envelope, same error format, same authentication. Learn once, use everywhere.
Authentication
Every request needs a Bearer token in the Authorization header. API keys are issued from the API Console.
Authorization: Bearer hst_live_your_keyKeys come in two flavours:
hst_live_*— production traffic, real charges, real datahst_test_*— test traffic (when supported)
Keys are stored as SHA-256 hashes at rest. The plaintext key is shown once at creation — copy it then. There's no way to retrieve it again.
Up to 10 active keys per organization. Revoking a key takes effect immediately.
Quickstart
From zero to first decode in 4 steps.
- Sign up at heista.co/api-console
- Create an API key, copy it
- Buy a credit pack
- Make your first call:
curl -X POST https://api.heista.co/v1/decode \
-H "Authorization: Bearer hst_live_your_key" \
-H "Content-Type: application/json" \
-d '{"url": "https://www.tiktok.com/@brand/video/1234567"}'You'll get back a job ID. Poll it until status is completed:
curl https://api.heista.co/v1/decode/JOB_ID \
-H "Authorization: Bearer hst_live_your_key"Rate limits
Rate limits are applied per API key, per endpoint. Limits are returned in headers on every response.
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 58
X-RateLimit-Reset: 1762704000000X-RateLimit-Reset is a Unix timestamp in milliseconds. When you exceed a limit you also get a Retry-After header in seconds on the 429 response.
| Endpoint group | Limit |
|---|---|
| POST /v1/decode | 60/min |
| GET /v1/decode/:id | 120/min |
| POST /v1/powersource | 30/min |
| GET /v1/powersource/:id | 120/min |
| POST /v1/adscript | 30/min |
| /v1/files (any) | 60/min |
| /v1/intelligence/* (any) | 120/min |
When you exceed a limit you get a 429 with Retry-After set.
Spending caps
Every paid call passes through a balance + caps reservation before any compute runs. This enforces three layers of protection on top of your balance:
- Org-level caps — hourly, daily, monthly spend limits per organization
- Per-key caps — daily and monthly spend limits per API key
- Circuit breaker — automatic pause on anomalous usage (set by anomaly detection)
If a call would breach any of these, you get a 402 (caps) or 403 (circuit breaker) with a specific reason field instead of insufficient-credits. The reasons:
| Reason | Status | What it means |
|---|---|---|
| cap_hourly | 402 | Org hourly spending cap reached — try again next hour |
| cap_daily | 402 | Org daily spending cap reached — resets at UTC midnight |
| cap_monthly | 402 | Org monthly spending cap reached — raise the cap to continue |
| cap_key_daily | 402 | API key daily cap reached — use a different key or raise the cap |
| cap_key_monthly | 402 | API key monthly cap reached |
| circuit_open | 403 | Account paused by anomaly detection — review at /api-console/billing |
Example cap-breach response:
{
"type": "https://api.heista.co/errors/cap-daily",
"title": "Daily spending cap reached",
"status": 402,
"detail": "Daily spending cap reached. Caps reset at UTC midnight, or raise the cap at https://heista.co/api-console/billing.",
"request_id": "req_a1b2c3d4",
"reason": "cap_daily",
"top_up_url": "https://heista.co/api-console/billing"
}Caps and circuit breaker can be configured per org at the API Console. Defaults are set conservatively to prevent runaway costs.
Pricing & credits
One shared balance across every paid endpoint. Free reads on the corpus.
Credit packs:
Failed jobs auto-refund. Duplicate inputs (same org) return cached results at no charge.
Base URL
https://api.heista.co/v1All endpoints are versioned under /v1. We'll never break compatibility within a version — additive changes only.
Request format
All POST endpoints accept JSON bodies. Required headers:
Authorization: Bearer hst_live_your_key
Content-Type: application/jsonAll field names are snake_case. All timestamps are ISO 8601. All IDs are returned as strings.
For paid endpoints, pass an optional idempotency_key in the body to make the call retry-safe — same key + same org returns the original result without re-charging.
Response format
Successful responses are JSON with HTTP status 200 (sync), 201 (created), or 202 (accepted/queued). Standard headers on every response:
X-Request-Id: req_a1b2c3d4
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 58
X-RateLimit-Reset: 1762704000000
X-Balance-Remaining: 98.50Save X-Request-Id from your responses. Support uses it to correlate logs.
Successful responses often echo the inputs that produced them (source_id, powersource_id, filters, etc.) so consumers running parallel calls can correlate request to response.
Errors
Errors follow RFC 9457 Problem Details. Same envelope across every endpoint.
{
"type": "https://api.heista.co/errors/insufficient-credits",
"title": "Insufficient credits",
"status": 402,
"detail": "This decode requires $0.20 but your balance is $0.10. Top up at https://heista.co/api-console/billing",
"request_id": "req_a1b2c3d4",
"resource": "decode",
"balance": "0.10",
"required": "0.20",
"top_up_url": "https://heista.co/api-console/billing"
}| Status | Title | When it happens |
|---|---|---|
| 400 | Invalid request | Body malformed or fails validation |
| 401 | Authentication failed | Missing or invalid API key |
| 402 | Insufficient credits | Balance below required cost — top up |
| 402 | Spending cap reached | Org or per-key cap hit — see Spending caps section for the reason field |
| 403 | Account paused | Circuit breaker tripped by anomaly detection |
| 404 | Not found | Resource (job, file, source) does not exist or not yours |
| 429 | Rate limit exceeded | Too many requests — back off, retry after Retry-After |
| 500 | Internal error | Unexpected. Use request_id when contacting support. |
Idempotency
All paid POST endpoints accept an optional idempotency_key in the body (max 128 chars). Same key + same org returns the original result without re-charging. Use it for:
- Network retries where you can't tell if the previous call succeeded
- Background workers that may fire the same job twice
- Webhook receivers replaying events
Recommended pattern: generate a UUID per logical operation, pass it on every retry.
APIs
Full request, response, and error reference for every endpoint — inline below. Prefer an interactive explorer? Open the API Reference.
Decode
Reverse-engineer any video ad into its structural formula. Beat-by-beat psychology, visual playbook with per-shot frame thumbnails, classification with confidence, and Meta runtime signals.
/v1/decodeSubmit a video URL for decoding. Returns a job ID to poll.
Request body (application/json)
| Field | Type | Description |
|---|---|---|
| urloptional | string (URI) | Video URL. Supports Facebook Ad Library, TikTok, Instagram Reels, YouTube Shorts, and direct .mp4. Either `url` or `video_url` is required. |
| video_urloptional | string (URI) | Alias of `url`. Use whichever feels natural. |
| idempotency_keyoptional | string (≤128) | Optional. Makes the call safe to retry. Same key + same org → original result, no re-charge. |
Responses
200Deduplicated — this URL was decoded before. No charge. Use GET to retrieve.{
"id": "9c2f0f9a-2c4e-4a7f-8b3c-8f9c2f0f9a2c",
"status": "completed",
"deduplicated": true,
"price": 0,
"created_at": "2026-05-10T14:30:00Z",
"completed_at": "2026-05-10T14:31:12Z",
"poll_url": "/v1/decode/9c2f0f9a-2c4e-4a7f-8b3c-8f9c2f0f9a2c"
}202Queued — pipeline running in background. Poll `poll_url`.{
"id": "9c2f0f9a-2c4e-4a7f-8b3c-8f9c2f0f9a2c",
"status": "queued",
"estimated_seconds": 45,
"created_at": "2026-05-11T11:00:00Z",
"poll_url": "/v1/decode/9c2f0f9a-2c4e-4a7f-8b3c-8f9c2f0f9a2c"
}Example request
curl -X POST https://www.heista.co/api/v1/decode \
-H "Authorization: Bearer hst_live_your_key" \
-H "Content-Type: application/json" \
-d '{"url":"https://www.tiktok.com/@brand/video/1234"}'/v1/decode/{id}Poll a decode job. Returns status while running, full report when complete.
Path parameters
| Field | Type | Description |
|---|---|---|
| idrequired | string (UUID) | Job ID from the POST response. |
Responses
200 (completed)Full 5-section decode — patternmap, formula, beats, visual_playbook, psychology, ad_intel.{
"id": "9c2f0f9a-2c4e-4a7f-8b3c-8f9c2f0f9a2c",
"status": "completed",
"price": 0.15,
"created_at": "2026-05-11T11:00:00Z",
"completed_at": "2026-05-11T11:00:47Z",
"video": { "url": "...signed-url...", "duration_seconds": 47, "platform": "tiktok", ... },
"patternmap": [ /* per-beat structural map with timings */ ],
"formula": { "ad_blueprint": {...}, "hook_type": {...}, "psychology_card": {...} },
"beats": [ /* per-beat strategy + psychology + visual_direction */ ],
"visual_playbook": { "directors_read": "...", "total_cuts": 12, "dna": {...}, "shot_breakdown": [...] },
"psychology": { "viewer_psychology": {...}, "messaging_playbook": [...], "persuasion_stack": {...} },
"ad_intel": { "ad_identity": {...}, "publisher_platforms": [...], "classification_dna": {...} },
"total_beats": 7
}200 (processing)Still running. Wait and call again.{
"id": "9c2f0f9a-2c4e-4a7f-8b3c-8f9c2f0f9a2c",
"status": "processing",
"created_at": "2026-05-11T11:00:00Z",
"estimated_seconds": 30
}Example request
curl https://www.heista.co/api/v1/decode/9c2f0f9a-2c4e-4a7f-8b3c-8f9c2f0f9a2c \
-H "Authorization: Bearer hst_live_your_key"Error responses
All errors follow the standard RFC 9457 Problem Details envelope. See the Errors section above for the full shape.
| Status | When |
|---|---|
| 400 | Validation — bad/missing fields |
| 401 | Unauthorized — missing or invalid API key |
| 402 | Insufficient credits OR spending cap reached (see `reason` field) |
| 403 | Circuit breaker — account paused by anomaly detection |
| 429 | Rate limit exceeded — see Retry-After header |
| 500 | Internal error |
PowerSource
Brand intelligence from a URL, internal documents, or both. Buyer profile, behavioural tensions, brand voice, creative angles, visual identity, and proof assets across 14 sections.
/v1/powersourceSubmit a job. Mode auto-detected from body shape: URL only ($1), docs only ($1), URL + docs ($2).
Request body (application/json)
| Field | Type | Description |
|---|---|---|
| urloptional | string | Primary URL. Required for URL-only or full mode. |
| context_urloptional | string | Supplementary URL — docs mode only. Mutually exclusive with `url`. |
| file_idsoptional | string[] (UUID, ≤10) | File IDs from POST /v1/files. |
| document_urlsoptional | string[] (URI, ≤10) | Public URLs to PDF/DOCX/TXT/MD. |
| documents_inlineoptional | object[] (≤10) | Inline base64 documents — `[{filename, content_base64}]`. Max 5MB per file. Pipeline reads text only — extract text from PDFs first. |
| force_refreshoptional | boolean | Bypass the 30-day brand cache. Rate-limited to once per 24h per domain. |
| webhook_urloptional | string (HTTPS) | Receive a signed POST when the job completes or fails. Eliminates polling. |
| idempotency_keyoptional | string (≤128) | Optional. Makes the call safe to retry. |
Responses
202Queued. Poll `poll_url`. URL mode ~75s · docs mode ~90s · full mode ~130s.{
"id": "ps_5fa7c0b8-1b3e-4d9f-9a2c-5fa7c0b81b3e",
"status": "queued",
"estimated_seconds": 75,
"created_at": "2026-05-11T11:00:00Z",
"poll_url": "/v1/powersource/ps_5fa7c0b8-1b3e-4d9f-9a2c-5fa7c0b81b3e"
}200 (deduped)Same-domain cache hit. No charge. Use GET to retrieve.{
"id": "ps_5fa7c0b8-1b3e-4d9f-9a2c-5fa7c0b81b3e",
"brief_id": "brief_a1b2c3d4",
"status": "completed",
"deduplicated": true,
"price": 0,
"created_at": "2026-05-10T09:15:00Z",
"poll_url": "/v1/powersource/ps_5fa7c0b8-1b3e-4d9f-9a2c-5fa7c0b81b3e"
}Example request
curl -X POST https://www.heista.co/api/v1/powersource \
-H "Authorization: Bearer hst_live_your_key" \
-H "Content-Type: application/json" \
-d '{"url":"https://gymshark.com"}'/v1/powersource/{id}Poll job. Returns progress while running, the full 14-section profile when complete.
Path parameters
| Field | Type | Description |
|---|---|---|
| idrequired | string (UUID) | Job ID from POST. |
Responses
200 (completed)Full PowerSource. Every section carries a `_scope` tag — `product` / `brand` / `synthesized` / `mixed`.{
"id": "ps_5fa7c0b8-1b3e-4d9f-9a2c-5fa7c0b81b3e",
"brief_id": "brief_a1b2c3d4",
"status": "completed",
"price": 1.00,
"created_at": "2026-05-11T11:00:00Z",
"completed_at": "2026-05-11T11:01:14Z",
"powersource": {
"identity": { "_scope": "product", "brand_name": "Gymshark", "category": {...}, "vertical": "FITNESS_APPAREL", ... },
"offer": { "_scope": "product", "name": "...", "primary_promise": {...}, "pricing": {...}, "promotions": {...} },
"selling_points": [ {"id": "sp_1", "name": "...", "description": "..."}, ... ],
"brand_story": { "_scope": "brand", "founding_story": "...", "emotional_identity": {...}, "narrative_motifs": {...} },
"brand_style": { "_scope": "brand", "colors": [...], "primary_color": "...", "color_mode": "..." },
"brand_assets": { "_scope": "brand", "logo_url": "...", "heading_font": "...", "brand_media": [...] },
"brand_voice": "Writes like a friend who already owns three...",
"buyer_profile": { "_scope": "synthesized", "archetype_name": "...", "core_tension": "...", "primary_emotion": "...", ... },
"tensions": [ {"tension_id": "...", "label": "...", "bias": "...", "emotion_path": "...", "angle_seed": "...", "expression": "..."}, ... ],
"angles": { "_scope": "synthesized", "categories": [...], "seeds": [...], "openers": [...], "rotation_axes": {...} },
"emotional_arcs": { "_scope": "synthesized", "polarities": [...], "escalation_ladders": [...] },
"ctas": { "_scope": "product", "options": [...] },
"proof_assets": { "_scope": "mixed", "social_proof": [...], "press_mentions": [...], "certifications": [...] },
"narrative": { "_scope": "synthesized", "direction": "...", "storyline_summary": "..." }
}
}200 (processing)Phase + progress + partial intelligence as agents complete.{
"id": "ps_5fa7c0b8-1b3e-4d9f-9a2c-5fa7c0b81b3e",
"status": "processing",
"estimated_seconds": 30,
"progress": {
"phase": "synthesizing",
"scanning": { "completed": ["primary","homepage","about","tone","offering","sales"], "failed": [] },
"synthesis": { "completed": ["buyer","offer","angles"] },
"partial_data": {
"buyer_psychology": { "archetype": "The Comfort-Seeker Realist", "core_tension": "...", "tension_count": 12 }
}
}
}Example request
curl https://www.heista.co/api/v1/powersource/ps_5fa7c0b8-1b3e-4d9f-9a2c-5fa7c0b81b3e \
-H "Authorization: Bearer hst_live_your_key"Error responses
All errors follow the standard RFC 9457 Problem Details envelope. See the Errors section above for the full shape.
| Status | When |
|---|---|
| 400 | Validation — bad/missing fields |
| 401 | Unauthorized — missing or invalid API key |
| 402 | Insufficient credits OR spending cap reached (see `reason` field) |
| 403 | Circuit breaker — account paused by anomaly detection |
| 429 | Rate limit exceeded — see Retry-After header |
| 500 | Internal error |
Adscript
Generate 1–5 ad scripts per call from a structural source (decoded ad or formula) + a brand PowerSource. Auto-rotated tensions and selling points for variety. Synchronous — one call, scripts back.
/v1/adscriptGenerate 1–5 scripts. Returns synchronously. Token-metered — each script charges 2–5¢ depending on length.
Request body (application/json)
| Field | Type | Description |
|---|---|---|
| source_idrequired | string (≤128) | A formula id (from /v1/intelligence/formulas) OR a decoded-ad id (from /v1/intelligence/decoder or your own decode_ad call). |
| source_typerequired | enum | `decode` (single ad as structural source) or `formula` (clustered blueprint).values: decodeformula |
| powersource_idrequired | string (≤128) | Brief id or job id from any /v1/powersource call. Locks voice, tensions, selling points, audience. |
| countoptional | integer 1–5 | Number of scripts.default: 1 |
| script_modeoptional | enum | `blueprint` follows the source exactly; `remix` keeps the architecture but writes original copy.default: blueprintvalues: blueprintremix |
| durationoptional | integer 10–120 | Target seconds — `remix` mode only. Blueprint locks to source duration. |
| audienceoptional | string (≤128) | Audience segment from the PowerSource. Defaults to the composite buyer. |
| tensionoptional | string (≤256) | Lock a specific behavioral tension. Omit to auto-rotate. |
| selling_pointsoptional | string[] (≤5) | Lock specific selling points. Omit to auto-select per beat. |
| voice_modeoptional | enum | `creator` for UGC (PowerSource locks facts not register); `brand` for brand-owned content.default: creatorvalues: creatorbrand |
| idempotency_keyoptional | string (≤128) | Optional. Makes the call safe to retry. |
Responses
200All scripts returned in one response with per-script credits charged.{
"status": "completed",
"source_id": "formula_abc123",
"source_type": "formula",
"powersource_id": "ps_xyz",
"scripts": [
{
"index": 1,
"script": "If you can't stop eating sugar, this is the only video you need to watch today...",
"formula_name": "Problem-Agitate-Solve UGC",
"identity": {
"creative_format": "UGC_TESTIMONIAL",
"algo_intent": "PROBLEM_AGITATE_SOLVE",
"marketing_angle": "PROBLEM_SOLUTION",
"psychological_mission": "LOSS_AVERSION",
"behavioural_role": "SELL_PRODUCT_DIRECT"
},
"beat_count": 7,
"duration_seconds": 30,
"credits_cost": 3
}
],
"credits_charged": 3,
"price": 0.03,
"created_at": "2026-05-11T11:00:00Z"
}Example request
curl -X POST https://www.heista.co/api/v1/adscript \
-H "Authorization: Bearer hst_live_your_key" \
-H "Content-Type: application/json" \
-d '{
"source_id":"formula_abc123",
"source_type":"formula",
"powersource_id":"ps_xyz",
"count":3
}'Error responses
All errors follow the standard RFC 9457 Problem Details envelope. See the Errors section above for the full shape.
| Status | When |
|---|---|
| 400 | Validation — bad/missing fields |
| 401 | Unauthorized — missing or invalid API key |
| 402 | Insufficient credits OR spending cap reached (see `reason` field) |
| 403 | Circuit breaker — account paused by anomaly detection |
| 429 | Rate limit exceeded — see Retry-After header |
| 500 | Internal error |
| 404 | source_id or powersource_id not found |
Hook Intelligence
Proven hook patterns from the decoded ad corpus. Parameterized templates with slots, real example hooks, corpus stats, cadence profiles. Filter by vertical, hook type, marketing angle.
/v1/intelligence/hooksReturns hook patterns matched to your filters. Omit hook_type for the top 3 performing types in the vertical.
Query parameters
| Field | Type | Description |
|---|---|---|
| verticaloptional | string | Industry vertical. Examples: BEAUTY_SKINCARE, HEALTH_SUPPLEMENTS, FITNESS, FOOD_BEVERAGE, FASHION_APPAREL, SAAS_SOFTWARE. |
| hook_typeoptional | string | Specific hook subtype. Examples: CURIOSITY_SPIKE, IDENTITY_HOOK, CONTRADICTION_HOOK, PROVOCATION, DIRECT_QUESTION_HOOK. |
| marketing_angleoptional | string | Examples: PROBLEM_SOLUTION, SOCIAL_PROOF_RESULTS, HOW_TO_TUTORIAL, ASPIRATIONAL_IDENTITY. |
Responses
200Filters echoed + array of hook intelligence results.{
"filters": { "vertical": "BEAUTY_SKINCARE", "hook_type": "CURIOSITY_SPIKE", "marketing_angle": null },
"count": 1,
"results": [
{
"hook_type": "CURIOSITY_SPIKE",
"hook_type_name": "Curiosity Spike",
"psychology": "Opens an information gap...",
"short_def": "...",
"long_description": "...",
"corpus_count": 247,
"intelligence": {
"proven_shells": [
{ "structural_shell": "What if the reason your {pain_point} is {hidden_cause}?",
"slots": [...], "exact_text": "...", "active_days": 247, "behavioural_principles": [...] }
],
"example_hooks": [ "I tried 47 products. Only one cleared my skin.", ... ],
"corpus_stats": { "total_matches": 247, "avg_active_days": 134 },
"cadence_profile": { "median_token_bucket": "S", "dominant_pov": "second_person" }
}
}
]
}Example request
curl "https://www.heista.co/api/v1/intelligence/hooks?vertical=BEAUTY_SKINCARE&hook_type=CURIOSITY_SPIKE" \
-H "Authorization: Bearer hst_live_your_key"Error responses
All errors follow the standard RFC 9457 Problem Details envelope. See the Errors section above for the full shape.
| Status | When |
|---|---|
| 400 | Validation — bad query params |
| 401 | Unauthorized — missing or invalid API key |
| 429 | Rate limit exceeded |
Ad Formulas
Structural ad formulas — patterns clustered from multiple winning ads that converged on the same beat architecture. Beat-by-beat blueprints with runtime proof, confidence scores, and source-ad links.
/v1/intelligence/formulasReturns formula blueprints sorted by confidence score. Pass `id` into /v1/adscript with source_type "formula".
Query parameters
| Field | Type | Description |
|---|---|---|
| verticaloptional | string | Industry vertical filter. |
| creative_formatoptional | string | Examples: UGC_TESTIMONIAL, TALKING_HEAD_BROLL, VOICEOVER_BROLL, PRODUCT_DEMO. |
| marketing_angleoptional | string | Marketing angle filter. |
| algo_intentoptional | string | Structural engine. Examples: PROBLEM_AGITATE_SOLVE, MECHANISM_REVEAL, TRANSFORMATION_ARC. |
| hook_typeoptional | string | Filter by the opening beat's subtype. |
| limitoptional | integer 1–10 | Max formulas to return.default: 5 |
Responses
200Filters echoed + formulas array. Each formula carries source-ad links so you can verify.{
"filters": { "vertical": "BEAUTY_SKINCARE", "creative_format": "UGC_TESTIMONIAL", "marketing_angle": null, "algo_intent": null, "hook_type": null },
"count": 3,
"formulas": [
{
"id": "formula_abc123",
"name": "PAS-UGC Skincare 30s",
"rationale": "Five winning ads converged on this structure...",
"identity": {
"creative_format": "UGC_TESTIMONIAL",
"video_type": "TALKING_HEAD",
"algo_intent": "PROBLEM_AGITATE_SOLVE",
"behavioural_role": "SELL_PRODUCT_DIRECT",
"marketing_angle": "PROBLEM_SOLUTION",
"psychological_mission": "LOSS_AVERSION",
"offer_state": "PROMOTING_NEW_DROP"
},
"structure": {
"beat_count": 7,
"total_duration": 30,
"beats": [ /* per-beat subtype, what/why, principles, cadence, pov, feature_density */ ]
},
"confidence_level": "HIGH",
"confidence_score": 0.87,
"avg_active_days": 178,
"source_ad_count": 8,
"source_ads": [
{ "id": "decode_001", "url": "https://www.heista.co/decode/...", "brand": "Brand A" }
],
"vertical": "BEAUTY_SKINCARE",
"product_type": "SKINCARE_TREATMENT"
}
]
}Example request
curl "https://www.heista.co/api/v1/intelligence/formulas?vertical=BEAUTY_SKINCARE&limit=5" \
-H "Authorization: Bearer hst_live_your_key"Error responses
All errors follow the standard RFC 9457 Problem Details envelope. See the Errors section above for the full shape.
| Status | When |
|---|---|
| 400 | Validation — bad query params |
| 401 | Unauthorized — missing or invalid API key |
| 429 | Rate limit exceeded |
Decoded Library
Individual decoded ads from the corpus. Sorted by active days descending, deduplicated by brand. Full beat-by-beat structure per ad. Pass `id` into /v1/adscript with source_type "decode".
/v1/intelligence/decoderReturns decoded ads matching your filters. Best-performing first, brand-deduplicated for diversity.
Query parameters
| Field | Type | Description |
|---|---|---|
| verticaloptional | string | Industry vertical filter. |
| creative_formatoptional | string | Creative format filter. |
| marketing_angleoptional | string | Marketing angle filter. |
| hook_typeoptional | string | Filter by opening hook subtype. |
| algo_intentoptional | string | Structural engine filter. |
| brandoptional | string (≤100) | Case-insensitive partial match (e.g. "gymshark" matches "Gymshark Ltd"). |
| limitoptional | integer 1–10 | Max ads to return.default: 5 |
Responses
200Filters echoed + ads array with full structural breakdown.{
"filters": { "vertical": "FITNESS", "creative_format": null, "marketing_angle": null, "hook_type": null, "algo_intent": null, "brand": "gymshark" },
"count": 3,
"ads": [
{
"id": "decode_xyz789",
"decode_url": "https://www.heista.co/decode/gymshark-summer-2026",
"brand": "Gymshark",
"vertical": "FITNESS",
"marketing_angle": "ASPIRATIONAL_IDENTITY",
"active_days": 290,
"duration_seconds": 47,
"classification": {
"creative_format": "UGC_TESTIMONIAL",
"video_type": "TALKING_HEAD",
"algo_intent": "TRANSFORMATION_ARC",
"behavioural_role": "SELL_PRODUCT_DIRECT",
"psychological_mission": "ASPIRATION",
"offer_state": "PROMOTING_NEW_DROP"
},
"beat_count": 7,
"beats": [ /* per-beat details */ ],
"beat_sequence": "Curiosity Spike → Past-Self Open → Tension Build → Mechanism Reveal → Social Proof → Anchor Contrast → Scarcity CTA"
}
]
}Example request
curl "https://www.heista.co/api/v1/intelligence/decoder?vertical=FITNESS&brand=gymshark" \
-H "Authorization: Bearer hst_live_your_key"Error responses
All errors follow the standard RFC 9457 Problem Details envelope. See the Errors section above for the full shape.
| Status | When |
|---|---|
| 400 | Validation — bad query params |
| 401 | Unauthorized — missing or invalid API key |
| 429 | Rate limit exceeded |
Files
Upload PDF, DOCX, DOC, TXT, or MD documents (10MB max, 30-day TTL) to use in PowerSource docs mode. Multipart or JSON+base64. Returns `id` that can be passed in PowerSource `file_ids[]`.
/v1/filesUpload via multipart/form-data or JSON+base64. Returns the created file.
Request body (multipart/form-data)
| Field | Type | Description |
|---|---|---|
| filerequired | binary (multipart) | The file. Required when Content-Type is multipart/form-data. |
| filenamerequired | string (≤256, JSON only) | Filename with extension. Required when Content-Type is application/json. |
| content_base64required | string (JSON only) | Base64-encoded file contents. |
| mime_typeoptional | string (≤128, JSON only) | Optional MIME override. Auto-detected from extension when omitted. |
Responses
201File created.{
"id": "f1l3-a1b2-c3d4-e5f6-7890",
"filename": "brand-brief.pdf",
"mime_type": "application/pdf",
"size_bytes": 184320,
"expires_at": "2026-06-10T11:00:00Z",
"created_at": "2026-05-11T11:00:00Z"
}Example request
curl -X POST https://www.heista.co/api/v1/files \
-H "Authorization: Bearer hst_live_your_key" \
-F "file=@brand-brief.pdf"/v1/filesList files for the authenticated org.
Query parameters
| Field | Type | Description |
|---|---|---|
| limitoptional | integer 1–100 | Max files to return.default: 50 |
Responses
200Array of files plus count.{
"files": [
{ "id": "f1l3-a1b2-c3d4-e5f6-7890", "filename": "brand-brief.pdf", "mime_type": "application/pdf", "size_bytes": 184320, "expires_at": "2026-06-10T11:00:00Z", "created_at": "2026-05-11T11:00:00Z" }
],
"count": 1
}Example request
curl https://www.heista.co/api/v1/files \
-H "Authorization: Bearer hst_live_your_key"/v1/files/{id}Retrieve file metadata. File contents are not exposed via REST — files are referenced by id in downstream tool calls.
Path parameters
| Field | Type | Description |
|---|---|---|
| idrequired | string (UUID) | File ID from upload. |
Responses
200File metadata.{
"id": "f1l3-a1b2-c3d4-e5f6-7890",
"filename": "brand-brief.pdf",
"mime_type": "application/pdf",
"size_bytes": 184320,
"expires_at": "2026-06-10T11:00:00Z",
"created_at": "2026-05-11T11:00:00Z"
}Example request
curl https://www.heista.co/api/v1/files/f1l3-a1b2-c3d4-e5f6-7890 \
-H "Authorization: Bearer hst_live_your_key"/v1/files/urlFetch a public URL and store as a file. Convenience for callers with public storage links (Drive, S3, etc.). Extension allow-list, MIME, and size limits enforced server-side.
Request body (application/json)
| Field | Type | Description |
|---|---|---|
| urlrequired | string (URI, ≤2048) | Public URL to a PDF, DOCX, DOC, TXT, or MD file. Max 10MB. |
Responses
201File created from URL fetch. Includes a `source_url` echo so parallel imports can correlate.{
"id": "f1l3-a1b2-c3d4-e5f6-7890",
"filename": "brand-brief.pdf",
"mime_type": "application/pdf",
"size_bytes": 184320,
"expires_at": "2026-06-10T11:00:00Z",
"created_at": "2026-05-11T11:00:00Z",
"source_url": "https://example.com/files/brand-brief.pdf"
}400Validation or fetch error — non-allowed extension, size cap, or upstream failure.{
"type": "https://api.heista.co/errors/validation-error",
"title": "Validation error",
"status": 400,
"detail": "Failed to fetch URL: upstream returned 404"
}Example request
curl -X POST https://www.heista.co/api/v1/files/url \
-H "Authorization: Bearer hst_live_your_key" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com/files/brand-brief.pdf"}'/v1/files/{id}Delete a file. Removes storage + record. Idempotent — 404 if already gone.
Path parameters
| Field | Type | Description |
|---|---|---|
| idrequired | string (UUID) | File ID to delete. |
Responses
200Deleted.{
"id": "f1l3-a1b2-c3d4-e5f6-7890",
"deleted": true
}Example request
curl -X DELETE https://www.heista.co/api/v1/files/f1l3-a1b2-c3d4-e5f6-7890 \
-H "Authorization: Bearer hst_live_your_key"Error responses
All errors follow the standard RFC 9457 Problem Details envelope. See the Errors section above for the full shape.
| Status | When |
|---|---|
| 400 | Validation — bad query params |
| 401 | Unauthorized — missing or invalid API key |
| 429 | Rate limit exceeded |
| 404 | File not found or not owned by this org |
Account Balance
Read your credit balance, this month's usage broken down by operation, and pricing for every paid tool. Mirrors the MCP `check_balance` tool as JSON.
/v1/account/balanceReturns balance + monthly usage + pricing reference.
Responses
200Balance in cents, lifetime totals, month-to-date counts per tool, pricing reference.{
"balance_cents": 1850,
"total_topped_up_cents": 10000,
"total_spent_cents": 8150,
"month": {
"start": "2026-05-01T00:00:00Z",
"spend_cents": 215,
"counts": { "decode": 12, "powersource": 1, "powersource_docs": 0, "powersource_full": 0, "adscript": 3, "other": 0 }
},
"pricing": {
"decode_short_cents": 15,
"decode_long_cents": 20,
"powersource_url_cents": 100,
"powersource_docs_cents": 100,
"powersource_full_cents": 200,
"adscript_per_script_min_cents": 2,
"adscript_per_script_typical_max_cents": 5,
"intelligence_reads": 0,
"files": 0
},
"top_up_url": "https://heista.co/api-console/billing"
}Example request
curl https://www.heista.co/api/v1/account/balance \
-H "Authorization: Bearer hst_live_your_key"Error responses
All errors follow the standard RFC 9457 Problem Details envelope. See the Errors section above for the full shape.
| Status | When |
|---|---|
| 400 | Validation — bad query params |
| 401 | Unauthorized — missing or invalid API key |
| 429 | Rate limit exceeded |
Job polling
Decode and PowerSource are async. They return a job ID immediately and you poll a GET endpoint until the status changes to completed or failed.
Typical flow:
# 1. Submit (returns 202 with job ID)
POST /v1/decode { url: "..." }
→ { id: "job_abc", status: "queued", poll_url: "/v1/decode/job_abc", estimated_seconds: 60 }
# 2. Poll the poll_url (returns 200 with current status)
GET /v1/decode/job_abc
→ { id: "job_abc", status: "processing", estimated_seconds: 32 }
# 3. Repeat until status changes
GET /v1/decode/job_abc
→ { id: "job_abc", status: "completed", price: 0.15, ...full decode... }Recommended polling cadence:
- First poll after 10 seconds (Decode) or 15 seconds (PowerSource)
- Then every 5 seconds while processing
- Stop polling once status is
completedorfailed
For async endpoints that may stream partial results (PowerSource synthesis), each poll response includes a progress field with partial_data appearing as agents complete.
Webhooks
PowerSource supports webhooks as an alternative to polling. Pass webhook_url in the submit body. We POST a notification to your URL when the scan completes (or fails).
POST /v1/powersource
{
"url": "https://gymshark.com",
"webhook_url": "https://your-api.example.com/heista-webhook"
}Webhook deliveries include three headers:
X-Heista-Signature— HMAC-SHA256 of the request body, hex-encodedX-Heista-Event— the event name (e.g.powersource.completed,powersource.failed)X-Heista-Event-Id— unique event ID, use for idempotency on your side
The body is JSON containing event_id, event, job_id, status, brief_id (when completed), and timestamp.
Delivery retries up to 3 times with backoff (0s, 1s, 5s), 10-second timeout per attempt. For critical workflows, poll the GET endpoint as a safety net — webhook delivery is best-effort.
MCP
The full Heista API surface is also available as an MCP server. Same endpoints, same billing, OAuth-based authentication. Designed for AI agents — Claude, ChatGPT, Codex, Cursor, and 30+ other clients.
Overview
Model Context Protocol is an open standard for connecting AI agents to external tools and data. Heista's MCP server exposes every API endpoint as a tool an agent can call directly — no client code required.
Two ways to think about it:
- REST API — for code you write (Node, Python, curl, n8n nodes, etc.)
- MCP server — for AI agents you talk to (Claude.ai, ChatGPT, Cursor, etc.)
Both surfaces share the same backend, billing, and rate limits. Use whichever fits your workflow.
Endpoint & auth
Pick the path that matches your client — the section under Compatible clients walks each one in detail.
Add the server URL once in your client's connector settings → OAuth → done. One-click install via the Anthropic Connectors Directory and ChatGPT App Directory is coming.
One command: claude mcp add heista https://www.heista.co/api/mcp/mcp. Or install the plugin marketplace for skill-wrapped workflows.
Add the URL to a mcpServers JSON config. Works in Cursor, Windsurf, Codex, n8n, Goose, and any other client that speaks MCP.
A note on inheritance: MCP servers you add to Claude.ai are automatically inherited in Claude Code when you're signed in to the same account — connect once.
Technical reference
https://www.heista.co/api/mcp/mcpTransport: Streamable HTTP (MCP protocol 2025-03-26). Auth: OAuth 2.1 via Supabase, with API key fallback (Authorization: Bearer hst_live_*) for headless or scripted clients.
OAuth discovery follows RFC 9728 Protected Resource Metadata:
https://www.heista.co/.well-known/oauth-protected-resourceOnce authorized, the MCP client gets a token bound to your Heista account. Same credits balance, same rate limits, same logs as the REST API. Tokens are audience-bound — they can't be replayed against other MCP servers.
Available tools
The MCP exposes 11 tools — every paid endpoint plus three free corpus reads and a balance check.
| Tool | REST equivalent | Cost |
|---|---|---|
| decode_ad | POST /v1/decode | 15-20 credits |
| get_decode | GET /v1/decode/:id | Free |
| create_powersource_url | POST /v1/powersource (URL mode) | 100 credits |
| create_powersource_docs | POST /v1/powersource (docs mode) | 100 credits |
| create_powersource_full | POST /v1/powersource (full mode) | 200 credits |
| get_powersource | GET /v1/powersource/:id | Free |
| generate_adscript | POST /v1/adscript | ~2-5 credits/script |
| get_hook_intelligence | GET /v1/intelligence/hooks | Free |
| adformula_intelligence | GET /v1/intelligence/formulas | Free |
| decoder_intelligence | GET /v1/intelligence/decoder | Free |
| check_balance | (MCP only) | Free |
The Files API isn't exposed as a separate tool — file inputs for create_powersource_docs can be uploaded inline as base64 via the documents_inline parameter, or referenced by ID from a prior POST /v1/files call.
Compatible clients
Anything that speaks MCP can connect. The major ones:
Connect from Claude.ai or Cowork
- Settings → Connectors → Add custom connector
- Paste
https://www.heista.co/api/mcp/mcp - Authorize via OAuth (one-time)
- Use Heista tools directly in any conversation
Connect from Claude Code
Add the MCP server with one command:
claude mcp add heista https://www.heista.co/api/mcp/mcpOr install the full Heista plugin (MCP server + skills):
/plugin marketplace add Heista-co/claude-code-skills
/plugin install heista-decode@heista-skillsMCP servers you've added to Claude.ai are inherited automatically in Claude Code when you're signed in to the same account.
Skills that wrap these MCP tools — see heista-skills.
Connect from any other MCP client
Add the MCP server URL to your client's MCP configuration. For most clients this is a JSON config file:
{
"mcpServers": {
"heista": {
"url": "https://www.heista.co/api/mcp/mcp"
}
}
}Auth happens via OAuth on first connection. Or pass an API key in the Authorization header for headless setups.
SDKs
No official SDK yet — every endpoint can be hit with plain HTTP. Examples in three common languages.
curl
Every endpoint can be hit with plain curl. Authentication is a single Bearer header.
# Decode a video
curl -X POST https://api.heista.co/v1/decode \
-H "Authorization: Bearer hst_live_your_key" \
-H "Content-Type: application/json" \
-d '{"url": "https://www.tiktok.com/@brand/video/1234"}'
# Extract brand intelligence from a URL
curl -X POST https://api.heista.co/v1/powersource \
-H "Authorization: Bearer hst_live_your_key" \
-H "Content-Type: application/json" \
-d '{"url": "https://gymshark.com"}'
# Browse the decoded ad corpus (free)
curl "https://api.heista.co/v1/intelligence/decoder?vertical=FITNESS&limit=5" \
-H "Authorization: Bearer hst_live_your_key"JavaScript
Use the global fetch in Node 18+, browsers, or any modern runtime.
const HEISTA_KEY = process.env.HEISTA_API_KEY;
// Decode a video
async function decode(url) {
// 1. Submit
const submit = await fetch('https://api.heista.co/v1/decode', {
method: 'POST',
headers: {
'Authorization': `Bearer ${HEISTA_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ url }),
});
const { id } = await submit.json();
// 2. Poll
while (true) {
await new Promise(r => setTimeout(r, 5000));
const poll = await fetch(`https://api.heista.co/v1/decode/${id}`, {
headers: { 'Authorization': `Bearer ${HEISTA_KEY}` },
});
const data = await poll.json();
if (data.status === 'completed') return data;
if (data.status === 'failed') throw new Error(data.error.message);
}
}
const decode = await decode('https://www.tiktok.com/@brand/video/1234');
console.log(decode.formula, decode.beats, decode.visual_playbook);Python
Use the requests library or any HTTP client.
import os, time, requests
HEISTA_KEY = os.environ['HEISTA_API_KEY']
HEADERS = { 'Authorization': f'Bearer {HEISTA_KEY}' }
def decode(url: str) -> dict:
# 1. Submit
r = requests.post(
'https://api.heista.co/v1/decode',
headers={**HEADERS, 'Content-Type': 'application/json'},
json={'url': url},
)
r.raise_for_status()
job_id = r.json()['id']
# 2. Poll
while True:
time.sleep(5)
r = requests.get(f'https://api.heista.co/v1/decode/{job_id}', headers=HEADERS)
r.raise_for_status()
data = r.json()
if data['status'] == 'completed':
return data
if data['status'] == 'failed':
raise RuntimeError(data['error']['message'])
result = decode('https://www.tiktok.com/@brand/video/1234')
print(result['formula'], result['beats'], result['visual_playbook'])OpenAPI spec
Two ways to use the OpenAPI 3.1 spec — read it interactively or download for code generation, Postman/Insomnia import, or SDK generators.
Support
Questions, issues, or feature requests:
- Email support@heista.co
- Include the
X-Request-Idfrom any failed response — it lets us pinpoint your call in our logs - Status updates and changelogs are posted at heista.co/heista-api