{"openapi":"3.1.0","info":{"title":"Scheduloid API","version":"1.0.0","summary":"Read and run a Scheduloid workspace — users, activity, health, posts, content studios, billing and webhooks.","description":"Every endpoint serves **Scheduloid users only**: Cliqr venues and ClipBuzz creators share the\nunderlying user table and are excluded everywhere. An out-of-scope id answers 404, never 403.\n\n**Keys** are `sk_live_…` bearer tokens with scopes. A normal key sees its own workspace (owner +\nactive team members); an admin key reads every Scheduloid workspace but can never write on a\ncustomer's behalf. Writes and paid actions need the workspace **owner's** key.\n\n**Errors** always carry a machine-readable `error.type`. **Pagination** is keyset: pass\n`next_cursor` back as `cursor` until it is null. **Paid or irreversible actions** honour\n`Idempotency-Key` (replayed for 24h, scoped per endpoint and key).\n\nRate limits: 600 req/min per key, 1,200/min per workspace, 60 posts/hour and 30 generations/hour\nper workspace. Every response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`,\n`X-RateLimit-Reset`.\n\nData caveats returned in payloads, not hidden: post metrics are `values: null` unless the sync\nsucceeded (failed syncs store zeros); activity history coverage is stated per event type; there is\nno cancellation signal in the product.\n","contact":{"name":"Scheduloid","url":"https://app.scheduloid.com"}},"servers":[{"url":"https://app.scheduloid.com/api/v1"}],"security":[{"bearer":[]}],"tags":[{"name":"Key","description":"The calling key and the API's own health."},{"name":"Users","description":"Users, workspaces and health scores."},{"name":"Activity"},{"name":"Posts","description":"Read, publish, schedule, reschedule, cancel."},{"name":"Accounts"},{"name":"Billing"},{"name":"Content Studio"},{"name":"Repurpose Studio"},{"name":"Weekly Planner","description":"A researched week of posts about the customer's OWN topics. Nothing is made or scheduled without an approval step."},{"name":"Webhooks"},{"name":"CRM","description":"Operator keys only (crm:read / crm:write). The AI team's shared memory: tables and columns are data, so agents can add both. Send X-Agent: <teammate> on every request."},{"name":"Outreach","description":"Operator keys only. The gate every automated outreach send goes through: reserve (Idempotency-Key required), send exactly what was reserved, report the outcome. Social actions run under the owner's per-account standing permission plus a fresh Reviewer clearance; every email needs the owner's approval of that exact message. Nothing here grants or releases."},{"name":"Site","description":"Operator keys only (site:read / site:write). Scheduloid's own blog, page SEO and llms.txt. HTML is sanitised, a published slug never changes, and every change is a revision that can be restored. Send X-Agent: <teammate>."},{"name":"Messaging","description":"Operator keys only: email one user, with consent, unsubscribe, caps and a link allowlist enforced server-side."}],"paths":{"/health":{"get":{"tags":["Key"],"summary":"Liveness (no key needed)","security":[],"responses":{"200":{"description":"Healthy"},"503":{"description":"Database unreachable"}}}},"/openapi.json":{"get":{"tags":["Key"],"summary":"This document as JSON (no key needed)","security":[],"responses":{"200":{"description":"OpenAPI 3.1"}}}},"/openapi.yaml":{"get":{"tags":["Key"],"summary":"This document as YAML (no key needed)","security":[],"responses":{"200":{"description":"OpenAPI 3.1"}}}},"/me":{"get":{"tags":["Key"],"summary":"What this key is, may do, and what this workspace can post","responses":{"200":{"$ref":"#/components/responses/Ok"},"401":{"$ref":"#/components/responses/Unauthenticated"}}}},"/users":{"get":{"tags":["Users"],"summary":"Users the key can see, newest first","x-scopes":["users:read"],"parameters":[{"$ref":"#/components/parameters/cursor"},{"$ref":"#/components/parameters/limit"},{"name":"q","in":"query","schema":{"type":"string"},"description":"Email / name / username contains (% and _ are literal)."},{"name":"status","in":"query","schema":{"enum":["active","inactive"]}},{"name":"subscription","in":"query","schema":{"enum":["running","trial","expired","none"]}},{"name":"consent","in":"query","schema":{"enum":["granted","declined","undecided","contactable"]}},{"name":"created_after","in":"query","schema":{"type":"string","format":"date-time"}},{"name":"created_before","in":"query","schema":{"type":"string","format":"date-time"}},{"name":"inactive_days","in":"query","schema":{"type":"integer","minimum":1,"maximum":730},"description":"No activity in N days (includes never-active users)."},{"name":"active_days","in":"query","schema":{"type":"integer","minimum":1,"maximum":730}},{"name":"has_connected_accounts","in":"query","schema":{"enum":["true","false"]}}],"responses":{"200":{"$ref":"#/components/responses/Page"},"422":{"$ref":"#/components/responses/Invalid"}}}},"/users/at-risk":{"get":{"tags":["Users"],"summary":"Users ranked by who to talk to first, with suggested actions","x-scopes":["activity:read"],"parameters":[{"$ref":"#/components/parameters/cursor"},{"$ref":"#/components/parameters/limit"},{"name":"stage","in":"query","schema":{"type":"string"},"description":"Comma list of at_risk, watch, churned, never_activated, healthy. Default at_risk,watch,churned."},{"name":"max_score","in":"query","schema":{"type":"integer","minimum":0,"maximum":100}},{"name":"action","in":"query","schema":{"type":"string"},"description":"Only users with one of these suggested actions (comma list)."},{"name":"contactable","in":"query","schema":{"enum":["true","false"]},"description":"true: only users an assistant may email under the consent rule."}],"responses":{"200":{"$ref":"#/components/responses/Page"}}}},"/users/{id}":{"get":{"tags":["Users"],"summary":"One user — accounts, post outcomes, attention flags (billing needs billing:read)","x-scopes":["users:read"],"parameters":[{"$ref":"#/components/parameters/id"}],"responses":{"200":{"$ref":"#/components/responses/Ok"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/users/{id}/health":{"get":{"tags":["Users"],"summary":"Health score, stage, factors and suggested actions (each with actionable_by)","x-scopes":["activity:read"],"parameters":[{"$ref":"#/components/parameters/id"}],"responses":{"200":{"$ref":"#/components/responses/Ok"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/users/{id}/activity":{"get":{"tags":["Activity"],"summary":"One user's event stream, newest first, with coverage notes","x-scopes":["activity:read"],"parameters":[{"$ref":"#/components/parameters/id"},{"$ref":"#/components/parameters/cursor"},{"$ref":"#/components/parameters/limit"},{"$ref":"#/components/parameters/eventType"},{"$ref":"#/components/parameters/since"},{"$ref":"#/components/parameters/until"}],"responses":{"200":{"$ref":"#/components/responses/Page"}}}},"/users/{id}/messages":{"post":{"tags":["Messaging"],"summary":"Email one user (operator key; consent, suppression, caps and link allowlist enforced)","x-scopes":["messages:write"],"parameters":[{"$ref":"#/components/parameters/id"},{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","maxLength":200},"description":"Required unless dry_run. A retry with the same key replays the first response instead of emailing twice."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageCreate"}}}},"responses":{"200":{"description":"dry_run: the email exactly as it would be sent, and the eligibility verdict; nothing sent or recorded"},"201":{"description":"Sent"},"202":{"description":"Shadow mode: recorded, every gate passed, nothing emailed"},"403":{"description":"not_contactable | suppressed | account_inactive | no_email | operator_only | ip_not_allowed"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Invalid"},"429":{"description":"too_soon | monthly_cap | daily_cap | key_hourly_cap; Retry-After and error.eligibility.next_allowed_at say when"},"502":{"description":"delivery_failed: the mail server refused; recorded as failed, does not count toward caps"},"503":{"description":"messaging_disabled (kill switch) or not_ready (table not migrated)"}}},"get":{"tags":["Messaging"],"summary":"Messages sent to this user, newest first; meta.eligibility says whether one may be sent now","x-scopes":["messages:write"],"parameters":[{"$ref":"#/components/parameters/id"},{"$ref":"#/components/parameters/cursor"},{"$ref":"#/components/parameters/limit"}],"responses":{"200":{"$ref":"#/components/responses/Page"}}}},"/users/{id}/do-not-contact":{"post":{"tags":["Messaging"],"summary":"Stop emailing this user (they asked). Every later send is refused with not_contactable.","x-scopes":["messages:write"],"parameters":[{"$ref":"#/components/parameters/id"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["reason"],"properties":{"reason":{"type":"string","minLength":3,"maxLength":500}}}}}},"responses":{"200":{"$ref":"#/components/responses/Ok"},"422":{"$ref":"#/components/responses/Invalid"}}},"delete":{"tags":["Messaging"],"summary":"Undo do-not-contact","x-scopes":["messages:write"],"parameters":[{"$ref":"#/components/parameters/id"}],"responses":{"200":{"$ref":"#/components/responses/Ok"}}}},"/crm/stats":{"get":{"tags":["CRM"],"summary":"Rows per table, people by stage, approvals by status, open tasks by agent, touches in the last 7 days","x-scopes":["crm:read","crm:write"],"responses":{"200":{"$ref":"#/components/responses/Ok"}}}},"/crm/collections":{"get":{"tags":["CRM"],"summary":"Every table with its columns, plus the column types you can use","x-scopes":["crm:read","crm:write"],"parameters":[{"name":"include_archived","in":"query","schema":{"type":"boolean"}}],"responses":{"200":{"$ref":"#/components/responses/Ok"}}},"post":{"tags":["CRM"],"summary":"Create a table, optionally with its columns","x-scopes":["crm:write"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["key"],"properties":{"key":{"type":"string","pattern":"^[a-z][a-z0-9_]{1,39}$"},"label":{"type":"string"},"description":{"type":"string"},"fields":{"type":"array","items":{"$ref":"#/components/schemas/CrmField"}}}}}}},"responses":{"201":{"$ref":"#/components/responses/Ok"},"422":{"$ref":"#/components/responses/Invalid"}}}},"/crm/collections/{collection}":{"parameters":[{"$ref":"#/components/parameters/crmCollection"}],"patch":{"tags":["CRM"],"summary":"Rename or re-describe a table","x-scopes":["crm:write"],"responses":{"200":{"$ref":"#/components/responses/Ok"}}},"delete":{"tags":["CRM"],"summary":"Archive a table (rows kept; system tables refuse)","x-scopes":["crm:write"],"responses":{"200":{"$ref":"#/components/responses/Ok"},"409":{"$ref":"#/components/responses/Refused"}}}},"/crm/collections/{collection}/fields":{"parameters":[{"$ref":"#/components/parameters/crmCollection"}],"post":{"tags":["CRM"],"summary":"Add a column","x-scopes":["crm:write"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CrmField"}}}},"responses":{"201":{"$ref":"#/components/responses/Ok"},"422":{"$ref":"#/components/responses/Invalid"}}}},"/crm/collections/{collection}/fields/{field}":{"parameters":[{"$ref":"#/components/parameters/crmCollection"},{"name":"field","in":"path","required":true,"schema":{"type":"string"}}],"patch":{"tags":["CRM"],"summary":"Change a column: label, options, required, position, or type (only if every existing value fits)","x-scopes":["crm:write"],"responses":{"200":{"$ref":"#/components/responses/Ok"},"422":{"$ref":"#/components/responses/Invalid"}}},"delete":{"tags":["CRM"],"summary":"Archive a column (values kept; system columns refuse)","x-scopes":["crm:write"],"responses":{"200":{"$ref":"#/components/responses/Ok"},"409":{"$ref":"#/components/responses/Refused"}}}},"/crm/people/lookup":{"get":{"tags":["CRM"],"summary":"Find people by email, X handle/URL or LinkedIn URL, with a contact check","x-scopes":["crm:read","crm:write"],"parameters":[{"name":"email","in":"query","schema":{"type":"string"}},{"name":"x","in":"query","schema":{"type":"string"}},{"name":"linkedin","in":"query","schema":{"type":"string"}}],"responses":{"200":{"$ref":"#/components/responses/Ok"}}}},"/crm/people/{id}/do-not-contact":{"post":{"tags":["CRM"],"summary":"Stop contacting this person everywhere: CRM, outreach suppression list, and Scheduloid consent if they are a user","x-scopes":["crm:write"],"parameters":[{"$ref":"#/components/parameters/id"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["reason"],"properties":{"reason":{"type":"string"}}}}}},"responses":{"200":{"$ref":"#/components/responses/Ok"}}}},"/crm/people/{id}/contact-check":{"get":{"tags":["CRM"],"summary":"May we contact this person on this channel today? (do-not-contact, suppression, one channel per day)","x-scopes":["crm:read","crm:write"],"parameters":[{"$ref":"#/components/parameters/id"},{"name":"channel","in":"query","schema":{"type":"string"}}],"responses":{"200":{"$ref":"#/components/responses/Ok"}}}},"/crm/{collection}":{"parameters":[{"$ref":"#/components/parameters/crmCollection"}],"get":{"tags":["CRM"],"summary":"Rows, filtered and sorted","x-scopes":["crm:read","crm:write"],"parameters":[{"$ref":"#/components/parameters/cursor"},{"$ref":"#/components/parameters/limit"},{"name":"q","in":"query","schema":{"type":"string"},"description":"Case-insensitive search across every column."},{"name":"where[column]","in":"query","schema":{"type":"string"},"description":"Exact match on a column (tags: contains; 'null' matches empty)."},{"name":"where_gte[column]","in":"query","schema":{"type":"string"},"description":"integer/number/date/datetime columns."},{"name":"where_lte[column]","in":"query","schema":{"type":"string"}},{"name":"person_id","in":"query","schema":{"type":"integer"}},{"name":"updated_since","in":"query","schema":{"type":"string","format":"date-time"}},{"name":"sort","in":"query","schema":{"type":"string"},"description":"id, created_at, updated_at or any column."},{"name":"order","in":"query","schema":{"enum":["asc","desc"]}}],"responses":{"200":{"$ref":"#/components/responses/Page"}}},"post":{"tags":["CRM"],"summary":"Add a row (people: merges into an existing person with the same email / X / LinkedIn and answers 200 with meta.merged)","x-scopes":["crm:write"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","description":"Column values, flat or under \"data\". Unknown columns are refused: add them first."}}}},"responses":{"200":{"description":"Merged into an existing person"},"201":{"description":"Created"},"422":{"$ref":"#/components/responses/Invalid"}}}},"/crm/{collection}/batch":{"parameters":[{"$ref":"#/components/parameters/crmCollection"}],"post":{"tags":["CRM"],"summary":"Add up to 100 rows; each result reported separately","x-scopes":["crm:write"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["rows"],"properties":{"rows":{"type":"array","maxItems":100,"items":{"type":"object"}}}}}}},"responses":{"200":{"$ref":"#/components/responses/Ok"}}}},"/crm/{collection}/{id}":{"parameters":[{"$ref":"#/components/parameters/crmCollection"},{"$ref":"#/components/parameters/id"}],"get":{"tags":["CRM"],"summary":"One row (people also get contact_check, recent_touches, open_tasks)","x-scopes":["crm:read","crm:write"],"responses":{"200":{"$ref":"#/components/responses/Ok"},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"tags":["CRM"],"summary":"Change some columns of a row","x-scopes":["crm:write"],"responses":{"200":{"$ref":"#/components/responses/Ok"},"422":{"$ref":"#/components/responses/Invalid"}}},"delete":{"tags":["CRM"],"summary":"Delete a row (soft; an admin can restore)","x-scopes":["crm:write"],"responses":{"200":{"$ref":"#/components/responses/Ok"}}}},"/crm/{collection}/{id}/history":{"get":{"tags":["CRM"],"summary":"Every change to a row: action, changes, actor, time","x-scopes":["crm:read","crm:write"],"parameters":[{"$ref":"#/components/parameters/crmCollection"},{"$ref":"#/components/parameters/id"}],"responses":{"200":{"$ref":"#/components/responses/Ok"}}}},"/outreach/policy":{"get":{"tags":["Outreach"],"summary":"Standing permissions, sending accounts (connection, holds, warm-up readiness, today's usage) and blocked content","x-scopes":["crm:read","crm:write"],"responses":{"200":{"$ref":"#/components/responses/Ok"}}}},"/outreach/attempts":{"get":{"tags":["Outreach"],"summary":"Attempts, newest first (status=unknown is the reconciliation queue)","x-scopes":["crm:read","crm:write"],"parameters":[{"name":"status","in":"query","schema":{"enum":["reserved","sent","not_sent","unknown"]}},{"name":"person_id","in":"query","schema":{"type":"integer"}}],"responses":{"200":{"$ref":"#/components/responses/Ok"}}},"post":{"tags":["Outreach"],"summary":"Reserve one send. Every gate runs; on success the person (all matched identities) and the account are claimed. Requires Idempotency-Key.","x-scopes":["crm:write"],"parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","maxLength":200}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["person_id","account_id","action","content"],"properties":{"person_id":{"type":"integer"},"account_id":{"type":"integer"},"action":{"type":"string"},"content":{"type":"string"},"target_url":{"type":"string"},"clearance_id":{"type":"integer"},"approval_id":{"type":"integer"},"recipient":{"type":"string"}}}}}},"responses":{"200":{"$ref":"#/components/responses/Ok"}}}},"/outreach/attempts/{id}/result":{"post":{"tags":["Outreach"],"summary":"Report the outcome: sent (receipt required), not_sent (submitted false or platform_refused), or unknown (goes to reconciliation, never retried)","x-scopes":["crm:write"],"parameters":[{"$ref":"#/components/parameters/id"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"$ref":"#/components/responses/Ok"}}}},"/outreach/attempts/{id}/reconcile":{"post":{"tags":["Outreach"],"summary":"Settle an unknown outcome as sent, with the receipt found. Only the owner can confirm not_sent.","x-scopes":["crm:write"],"parameters":[{"$ref":"#/components/parameters/id"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"$ref":"#/components/responses/Ok"}}}},"/outreach/clearances":{"post":{"tags":["Outreach"],"summary":"Reviewer clearance of one exact content to one target (Reviewer key only; single use; expires)","x-scopes":["crm:write"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"$ref":"#/components/responses/Ok"}}}},"/outreach/signals":{"post":{"tags":["Outreach"],"summary":"reply (stops every channel, hands over), opt_out / negative / complaint / dnc (suppress everywhere), engaged / invited_dm / connection_accepted (evidence required)","x-scopes":["crm:write"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"$ref":"#/components/responses/Ok"}}}},"/outreach/identities":{"post":{"tags":["Outreach"],"summary":"Record a verified cross-channel identity (a similar name is refused)","x-scopes":["crm:write"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"$ref":"#/components/responses/Ok"}}}},"/outreach/preferences":{"post":{"tags":["Outreach"],"summary":"Record what the person asked for (no_dm, no_email, no_linkedin, no_contact, …); applied across all matched identities","x-scopes":["crm:write"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"$ref":"#/components/responses/Ok"}}}},"/outreach/people/{id}":{"get":{"tags":["Outreach"],"summary":"The person's tracker row: identities, history, attempts, receipts, reply state, next step, approval needed, stop reasons","x-scopes":["crm:read","crm:write"],"parameters":[{"$ref":"#/components/parameters/id"}],"responses":{"200":{"$ref":"#/components/responses/Ok"}}}},"/outreach/people/{id}/plan":{"post":{"tags":["Outreach"],"summary":"Recompute the next step and record any skipped stages with their reasons","x-scopes":["crm:write"],"parameters":[{"$ref":"#/components/parameters/id"}],"responses":{"200":{"$ref":"#/components/responses/Ok"}}}},"/outreach/tracker":{"get":{"tags":["Outreach"],"summary":"Every tracked person","x-scopes":["crm:read","crm:write"],"parameters":[{"name":"status","in":"query","schema":{"type":"string"}},{"name":"q","in":"query","schema":{"type":"string"}}],"responses":{"200":{"$ref":"#/components/responses/Ok"}}}},"/outreach/tracker.csv":{"get":{"tags":["Outreach"],"summary":"The tracker as CSV","x-scopes":["crm:read","crm:write"],"responses":{"200":{"$ref":"#/components/responses/Ok"}}}},"/outreach/accounts/{id}/connection":{"post":{"tags":["Outreach"],"summary":"Record a connection check (connector readback, browser session, Gmail profile) with evidence","x-scopes":["crm:write"],"parameters":[{"$ref":"#/components/parameters/id"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"$ref":"#/components/responses/Ok"}}}},"/outreach/accounts/{id}/hold":{"post":{"tags":["Outreach"],"summary":"Hold an account after a platform warning, restriction or removal. Observed facts and inferences are stored apart.","x-scopes":["crm:write"],"parameters":[{"$ref":"#/components/parameters/id"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"$ref":"#/components/responses/Ok"}}}},"/outreach/accounts/{id}/release":{"post":{"tags":["Outreach"],"summary":"Autopilot only: lift a hold a teammate set (held_by api:...), with a note on what was checked. Owner and system holds stay the owner's.","x-scopes":["crm:write"],"parameters":[{"$ref":"#/components/parameters/id"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["note"],"properties":{"note":{"type":"string","minLength":10}}}}}},"responses":{"200":{"$ref":"#/components/responses/Ok"}}}},"/outreach/accounts/{id}/activity":{"post":{"tags":["Outreach"],"summary":"Add warm-up activity to the account's ledger (receipt required)","x-scopes":["crm:write"],"parameters":[{"$ref":"#/components/parameters/id"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"$ref":"#/components/responses/Ok"}}}},"/outreach/permissions/{id}/revoke":{"post":{"tags":["Outreach"],"summary":"Revoke a standing permission (anyone may; only the owner grants)","x-scopes":["crm:write"],"parameters":[{"$ref":"#/components/parameters/id"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"$ref":"#/components/responses/Ok"}}}},"/site/blogs":{"get":{"tags":["Site"],"summary":"Blog posts, newest first, drafts included","x-scopes":["site:read","site:write"],"parameters":[{"name":"status","in":"query","schema":{"type":"string","enum":["published","draft"]}},{"$ref":"#/components/parameters/cursor"},{"$ref":"#/components/parameters/limit"}],"responses":{"200":{"$ref":"#/components/responses/Ok"}}},"post":{"tags":["Site"],"summary":"Create a post (a draft unless publish: true). Fields: title, slug, body_html, meta_title, meta_description, meta_keywords, faq, is_featured, image_url, publish","x-scopes":["site:write"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","description":"title (10-160), body_html (sanitised; h2-h4, p, lists, https links/images, tables), slug, meta_title (<=70), meta_description (<=200), meta_keywords (<=15), faq [{question, answer}] (<=12, rendered as FAQPage JSON-LD), is_featured, image_url (https JPEG/PNG/WebP), publish"}}}},"responses":{"200":{"$ref":"#/components/responses/Ok"},"422":{"$ref":"#/components/responses/Invalid"}}}},"/site/blogs/{id}":{"get":{"tags":["Site"],"summary":"One post with body_html, faq and its SEO checks","x-scopes":["site:read","site:write"],"parameters":[{"$ref":"#/components/parameters/id"}],"responses":{"200":{"$ref":"#/components/responses/Ok"},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"tags":["Site"],"summary":"Change some fields of a post. A published post's slug cannot change","x-scopes":["site:write"],"parameters":[{"$ref":"#/components/parameters/id"}],"responses":{"200":{"$ref":"#/components/responses/Ok"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Invalid"}}}},"/site/blogs/{id}/publish":{"post":{"tags":["Site"],"summary":"Publish a post (regenerates the sitemap)","x-scopes":["site:write"],"parameters":[{"$ref":"#/components/parameters/id"}],"responses":{"200":{"$ref":"#/components/responses/Ok"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/site/blogs/{id}/unpublish":{"post":{"tags":["Site"],"summary":"Take a post off the site (nothing is ever deleted)","x-scopes":["site:write"],"parameters":[{"$ref":"#/components/parameters/id"}],"responses":{"200":{"$ref":"#/components/responses/Ok"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/site/blogs/{id}/revisions":{"get":{"tags":["Site"],"summary":"Every change to a post: action, actor, time","x-scopes":["site:read","site:write"],"parameters":[{"$ref":"#/components/parameters/id"}],"responses":{"200":{"$ref":"#/components/responses/Ok"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/site/media":{"post":{"tags":["Site"],"summary":"Upload an image (JPEG/PNG/WebP, max 5 MB) or MP4 (max 120 MB) the team made locally; returns a public https URL","description":"multipart/form-data with field `file` and optional `alt`. The URL works as a post's image_url, in <img src> inside body_html, and in POST /posts media_urls. WebP is stored as PNG. The same bytes always give the same URL. Honours Idempotency-Key. Refusals are 422 with the reason under error.fields.file.","x-scopes":["site:write"],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"string","format":"binary"},"alt":{"type":"string","maxLength":300}}}}}},"responses":{"201":{"description":"Stored. data: id, url, mime, width, height, bytes, duration_s (video), alt"},"422":{"$ref":"#/components/responses/Invalid"}}}},"/site/seo":{"get":{"tags":["Site"],"summary":"Site description and keywords, page meta (blogs, plans, contact), llms files, robots.txt","x-scopes":["site:read","site:write"],"responses":{"200":{"$ref":"#/components/responses/Ok"}}},"patch":{"tags":["Site"],"summary":"Edit site_description, site_meta_keywords, or pages.{blogs|plans|contact}.{meta_title, meta_description, meta_keywords}","x-scopes":["site:write"],"responses":{"200":{"$ref":"#/components/responses/Ok"},"422":{"$ref":"#/components/responses/Invalid"}}}},"/site/seo/audit":{"get":{"tags":["Site"],"summary":"Measured SEO/AEO checks for every post and page, worst first. Rankings are unknown (no Search Console)","x-scopes":["site:read","site:write"],"responses":{"200":{"$ref":"#/components/responses/Ok"}}}},"/site/llms/{file}":{"get":{"tags":["Site"],"summary":"Read llms.txt or llms-full.txt","x-scopes":["site:read","site:write"],"parameters":[{"name":"file","in":"path","required":true,"schema":{"type":"string","enum":["llms.txt","llms-full.txt"]}}],"responses":{"200":{"$ref":"#/components/responses/Ok"},"404":{"$ref":"#/components/responses/NotFound"}}},"put":{"tags":["Site"],"summary":"Replace llms.txt or llms-full.txt (Markdown, starts with '# ', 200 B to 100 KB)","x-scopes":["site:write"],"parameters":[{"name":"file","in":"path","required":true,"schema":{"type":"string","enum":["llms.txt","llms-full.txt"]}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","description":"content: the whole file as Markdown"}}}},"responses":{"200":{"$ref":"#/components/responses/Ok"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Invalid"}}}},"/site/revisions":{"get":{"tags":["Site"],"summary":"Recent changes across the site","x-scopes":["site:read","site:write"],"parameters":[{"name":"subject","in":"query","schema":{"type":"string","enum":["blog","page","setting","file"]}},{"$ref":"#/components/parameters/limit"}],"responses":{"200":{"$ref":"#/components/responses/Ok"}}}},"/site/revisions/{id}":{"get":{"tags":["Site"],"summary":"One revision with the state before and after","x-scopes":["site:read","site:write"],"parameters":[{"$ref":"#/components/parameters/id"}],"responses":{"200":{"$ref":"#/components/responses/Ok"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/site/revisions/{id}/restore":{"post":{"tags":["Site"],"summary":"Put the subject back as it was before this revision (undoing a create unpublishes)","x-scopes":["site:write"],"parameters":[{"$ref":"#/components/parameters/id"}],"responses":{"200":{"$ref":"#/components/responses/Ok"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Refused"}}}},"/messages":{"get":{"tags":["Messaging"],"summary":"The outreach ledger across every user, with the current mode and limits","x-scopes":["messages:write"],"parameters":[{"$ref":"#/components/parameters/cursor"},{"$ref":"#/components/parameters/limit"},{"name":"status","in":"query","schema":{"type":"string"},"description":"Comma list: sent, failed, shadow."},{"name":"category","in":"query","schema":{"type":"string"}},{"name":"user_id","in":"query","schema":{"type":"integer"}},{"$ref":"#/components/parameters/since"},{"$ref":"#/components/parameters/until"}],"responses":{"200":{"$ref":"#/components/responses/Page"}}}},"/workspaces/{id}":{"get":{"tags":["Users"],"summary":"A workspace (its owner's id) — owner, active members, counts","x-scopes":["users:read"],"parameters":[{"$ref":"#/components/parameters/id"}],"responses":{"200":{"$ref":"#/components/responses/Ok"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/activity":{"get":{"tags":["Activity"],"summary":"Events across every user the key can see","x-scopes":["activity:read"],"parameters":[{"$ref":"#/components/parameters/cursor"},{"$ref":"#/components/parameters/limit"},{"$ref":"#/components/parameters/eventType"},{"$ref":"#/components/parameters/since"},{"$ref":"#/components/parameters/until"},{"name":"user_id","in":"query","schema":{"type":"integer"}}],"responses":{"200":{"$ref":"#/components/responses/Page"}}}},"/posts":{"get":{"tags":["Posts"],"summary":"Posts; failures carry failure.category and failure.actionable_by","x-scopes":["posts:read"],"parameters":[{"$ref":"#/components/parameters/cursor"},{"$ref":"#/components/parameters/limit"},{"name":"status","in":"query","schema":{"type":"string"},"description":"Comma list: draft, pending, scheduled, published, failed, awaiting_approval, rejected."},{"name":"platform","in":"query","schema":{"type":"string"},"description":"Comma list of platform slugs."},{"name":"user_id","in":"query","schema":{"type":"integer"}},{"$ref":"#/components/parameters/since"},{"$ref":"#/components/parameters/until"}],"responses":{"200":{"$ref":"#/components/responses/Page"}}},"post":{"tags":["Posts"],"summary":"Publish now, or schedule (owner key; uses post credits)","x-scopes":["posts:write"],"parameters":[{"$ref":"#/components/parameters/idempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostCreate"}}}},"responses":{"201":{"$ref":"#/components/responses/Ok"},"402":{"$ref":"#/components/responses/Refused"},"409":{"$ref":"#/components/responses/Refused"},"422":{"$ref":"#/components/responses/Invalid"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/posts/{id}":{"get":{"tags":["Posts"],"summary":"One post","x-scopes":["posts:read","posts:write"],"parameters":[{"$ref":"#/components/parameters/id"}],"responses":{"200":{"$ref":"#/components/responses/Ok"},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"tags":["Posts"],"summary":"Reschedule or edit a SCHEDULED post","x-scopes":["posts:write"],"parameters":[{"$ref":"#/components/parameters/id"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"schedule_at":{"type":"string","description":"ISO-8601 with an offset, or pair with timezone."},"timezone":{"type":"string"},"text":{"type":"string","maxLength":8000}}}}}},"responses":{"200":{"$ref":"#/components/responses/Ok"},"409":{"$ref":"#/components/responses/Refused"}}},"delete":{"tags":["Posts"],"summary":"Cancel a post that has not gone out (credit not refunded, as on the website)","x-scopes":["posts:write"],"parameters":[{"$ref":"#/components/parameters/id"}],"responses":{"200":{"$ref":"#/components/responses/Ok"},"409":{"$ref":"#/components/responses/Refused"}}}},"/posts/{id}/metrics":{"get":{"tags":["Posts"],"summary":"Engagement — values are null unless the sync succeeded (never fake zeros)","x-scopes":["posts:read","activity:read"],"parameters":[{"$ref":"#/components/parameters/id"}],"responses":{"200":{"$ref":"#/components/responses/Ok"}}}},"/accounts":{"get":{"tags":["Accounts"],"summary":"Connected accounts with postable, blocked_reason and reconnect_url","x-scopes":["posts:read","posts:write","users:read"],"parameters":[{"name":"user_id","in":"query","schema":{"type":"integer"},"description":"Required for admin keys."},{"name":"updated_since","in":"query","schema":{"type":"string","format":"date-time"},"description":"Only accounts that changed at or after this time (each account carries updated_at). The way to notice a disconnect or reconnect without comparing the whole list; a removed account no longer appears, use the account.disconnected event for that."}],"responses":{"200":{"$ref":"#/components/responses/Ok"}}}},"/subscriptions":{"get":{"tags":["Billing"],"summary":"Subscription facts (no paying/comped verdict — see docs)","x-scopes":["billing:read"],"parameters":[{"$ref":"#/components/parameters/cursor"},{"$ref":"#/components/parameters/limit"},{"name":"status","in":"query","schema":{"enum":["running","inactive","expired"]}},{"name":"user_id","in":"query","schema":{"type":"integer"}},{"name":"trial","in":"query","schema":{"enum":["true","false"]}},{"name":"free_plan","in":"query","schema":{"enum":["true","false"]}},{"name":"plan_id","in":"query","schema":{"type":"string"}},{"name":"expires_after","in":"query","schema":{"type":"string","format":"date-time"}},{"name":"expires_before","in":"query","schema":{"type":"string","format":"date-time"}}],"responses":{"200":{"$ref":"#/components/responses/Page"}}}},"/subscriptions/{user_id}":{"get":{"tags":["Billing"],"summary":"One user's current plan and history","x-scopes":["billing:read"],"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"$ref":"#/components/responses/Ok"}}}},"/content/generate":{"post":{"tags":["Content Studio"],"summary":"Generate an image, video or text with Content Studio (synchronous, up to ~3 min; AI credits)","x-scopes":["content:write"],"parameters":[{"$ref":"#/components/parameters/idempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"prompt":{"type":"string","maxLength":8000},"thread_id":{"type":"integer","description":"Continue a conversation."},"clarify_reply":{"type":"object","description":"Answers to a clarify result, keyed by question id."},"media_urls":{"type":"array","maxItems":4,"items":{"type":"string","format":"uri"}},"aspect_ratio":{"enum":["1:1","9:16","16:9","4:5"]}}}}}},"responses":{"200":{"description":"result.type is image | video | text | clarify | story_plan | multi_platform"},"402":{"$ref":"#/components/responses/Refused"},"429":{"$ref":"#/components/responses/RateLimited"},"502":{"description":"Generation failed (provider errors are never passed through)"}}}},"/content/threads":{"get":{"tags":["Content Studio"],"summary":"Content Studio conversations","x-scopes":["content:read","content:write"],"parameters":[{"$ref":"#/components/parameters/cursor"},{"$ref":"#/components/parameters/limit"}],"responses":{"200":{"$ref":"#/components/responses/Page"}}}},"/content/threads/{id}":{"get":{"tags":["Content Studio"],"summary":"Every message and result in a thread","x-scopes":["content:read","content:write"],"parameters":[{"$ref":"#/components/parameters/id"}],"responses":{"200":{"$ref":"#/components/responses/Ok"}}}},"/content/threads/{id}/stories/{messageId}/approve":{"post":{"tags":["Content Studio"],"summary":"Approve and render a multi-scene story (spends credits per scene; async)","x-scopes":["content:write"],"parameters":[{"$ref":"#/components/parameters/id"},{"name":"messageId","in":"path","required":true,"schema":{"type":"integer"}},{"$ref":"#/components/parameters/idempotencyKey"}],"responses":{"202":{"description":"Queued"},"402":{"$ref":"#/components/responses/Refused"},"409":{"$ref":"#/components/responses/Refused"},"422":{"$ref":"#/components/responses/Invalid"}}}},"/repurpose/platforms":{"get":{"tags":["Repurpose Studio"],"summary":"Platforms this plan can write for","x-scopes":["content:read","content:write"],"responses":{"200":{"$ref":"#/components/responses/Ok"}}}},"/repurpose/text":{"post":{"tags":["Repurpose Studio"],"summary":"One idea or piece of content → one ready post per platform (drafts; AI credits)","x-scopes":["content:write"],"parameters":[{"$ref":"#/components/parameters/idempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["platforms"],"properties":{"content":{"type":"string","maxLength":20000},"idea":{"type":"string","maxLength":2000},"platforms":{"type":"array","minItems":1,"maxItems":10,"items":{"type":"string"}},"tone":{"type":"string"},"language":{"type":"string"}}}}}},"responses":{"200":{"$ref":"#/components/responses/Ok"},"402":{"$ref":"#/components/responses/Refused"},"502":{"description":"Generation failed"}}}},"/repurpose/videos":{"get":{"tags":["Repurpose Studio"],"summary":"Library videos (ids like cnt_1301)","x-scopes":["content:read","content:write"],"parameters":[{"$ref":"#/components/parameters/cursor"},{"$ref":"#/components/parameters/limit"}],"responses":{"200":{"$ref":"#/components/responses/Page"}}}},"/repurpose/video/sessions":{"post":{"tags":["Repurpose Studio"],"summary":"Open a library video in Video Studio","x-scopes":["content:write"],"parameters":[{"$ref":"#/components/parameters/idempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["video_id"],"properties":{"video_id":{"type":"string","pattern":"^cnt_\\d+$"}}}}}},"responses":{"201":{"$ref":"#/components/responses/Ok"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/repurpose/video/sessions/{id}":{"get":{"tags":["Repurpose Studio"],"summary":"A Video Studio session and its jobs","x-scopes":["content:read","content:write"],"parameters":[{"$ref":"#/components/parameters/id"}],"responses":{"200":{"$ref":"#/components/responses/Ok"}}}},"/repurpose/video/sessions/{id}/actions":{"post":{"tags":["Repurpose Studio"],"summary":"Queue an action (one at a time per session; poll the job)","x-scopes":["content:write"],"parameters":[{"$ref":"#/components/parameters/id"},{"$ref":"#/components/parameters/idempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["action"],"properties":{"action":{"enum":["transcribe","cut_suggestions","subtitles_export","color_grade","render_preview","render_final"]},"options":{"type":"object"}}}}}},"responses":{"202":{"description":"Queued"},"200":{"description":"An action was already running; that job is returned"}}}},"/repurpose/video/jobs/{id}":{"get":{"tags":["Repurpose Studio"],"summary":"Poll a Video Studio job (result_url when completed)","x-scopes":["content:read","content:write"],"parameters":[{"$ref":"#/components/parameters/id"}],"responses":{"200":{"$ref":"#/components/responses/Ok"}}}},"/planner/plans":{"get":{"tags":["Weekly Planner"],"summary":"The workspace's weekly plans, newest first","x-scopes":["content:read","content:write"],"parameters":[{"$ref":"#/components/parameters/cursor"},{"$ref":"#/components/parameters/limit"}],"responses":{"200":{"$ref":"#/components/responses/Page"}}},"post":{"tags":["Weekly Planner"],"summary":"Research a website and start a week (synchronous, ~15-60s; free planning caps apply)","description":"Returns the plan with status \"researched\" and the questions to ask the customer. campaign.topics are the customer's own topics: the week is written about ONLY these (unless only_topics is false) and the validator rejects posts that drift.","x-scopes":["content:write"],"parameters":[{"$ref":"#/components/parameters/idempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url"],"properties":{"url":{"type":"string","maxLength":500},"campaign":{"$ref":"#/components/schemas/PlannerCampaign"},"corrections":{"$ref":"#/components/schemas/PlannerCorrections"}}}}}},"responses":{"201":{"description":"The plan, status researched"},"402":{"$ref":"#/components/responses/Refused"},"422":{"$ref":"#/components/responses/Invalid"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/planner/plans/{plan}":{"get":{"tags":["Weekly Planner"],"summary":"One plan with its week, violations, cost estimate and a \"next\" hint (poll this)","x-scopes":["content:read","content:write"],"parameters":[{"name":"plan","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"$ref":"#/components/responses/Ok"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/planner/plans/{plan}/campaign":{"put":{"tags":["Weekly Planner"],"summary":"Change the topics, facts or never-say list; replan=true writes the week again","x-scopes":["content:write"],"parameters":[{"name":"plan","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/PlannerCampaign"},{"type":"object","properties":{"replan":{"type":"boolean","description":"Only for a week not yet approved (status strategy_ready)."},"corrections":{"$ref":"#/components/schemas/PlannerCorrections"}}}]}}}},"responses":{"200":{"description":"The plan (status planning when replan was sent)"},"409":{"$ref":"#/components/responses/Refused"},"422":{"$ref":"#/components/responses/Invalid"}}}},"/planner/plans/{plan}/answers":{"post":{"tags":["Weekly Planner"],"summary":"Answer the questions; the week is written in the background (~1 min)","x-scopes":["content:write"],"parameters":[{"name":"plan","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"answers":{"type":"object","description":"Keyed by question id: the option picked, a list of options, or the customer's own words."},"campaign":{"$ref":"#/components/schemas/PlannerCampaign"},"goals":{"type":"array","items":{"enum":["sales","leads","bookings","signups"]},"description":"Default [leads]; the first is the main goal."},"posts_per_week":{"enum":[3,5,7],"default":5},"week_start":{"type":"string","format":"date","description":"Default today."},"timezone":{"type":"string","default":"UTC"},"mode":{"enum":["per_day","per_platform"],"default":"per_day"},"account_ids":{"type":"array","items":{"type":"integer"},"description":"Default: every connected image account the key may use."},"channels":{"type":"array","items":{"enum":["instagram","facebook","linkedin","twitter","threads","pinterest"]},"description":"Only when no account is connected."},"offer":{"type":"string"},"cta_url":{"type":"string","format":"uri"},"audience":{"type":"string"},"tone":{"enum":["Friendly","Professional","Bold","Playful","Premium"]},"post_time":{"type":"string","pattern":"^\\d{2}:\\d{2}$"}}}}}},"responses":{"202":{"description":"The plan, status planning"},"402":{"$ref":"#/components/responses/Refused"},"403":{"$ref":"#/components/responses/Refused"},"409":{"$ref":"#/components/responses/Refused"},"422":{"$ref":"#/components/responses/Invalid"}}}},"/planner/plans/{plan}/items/decision":{"post":{"tags":["Weekly Planner"],"summary":"Approve or reject finished posts (only approved posts are scheduled)","x-scopes":["content:write"],"parameters":[{"name":"plan","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["item_ids","decision"],"properties":{"item_ids":{"type":"array","items":{"type":"integer"},"maxItems":50},"decision":{"enum":["approve","reject","clear"]}}}}}},"responses":{"200":{"$ref":"#/components/responses/Ok"},"422":{"$ref":"#/components/responses/Invalid"}}}},"/planner/plans/{plan}/items/{item}/rewrite":{"post":{"tags":["Weekly Planner"],"summary":"Rewrite one planned post with a note (async; it keeps its topic)","x-scopes":["content:write"],"parameters":[{"name":"plan","in":"path","required":true,"schema":{"type":"integer"}},{"name":"item","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["note"],"properties":{"note":{"type":"string","maxLength":500}}}}}},"responses":{"202":{"description":"The plan; the post shows status generating until rewritten"},"409":{"$ref":"#/components/responses/Refused"}}}},"/planner/plans/{plan}/approve":{"post":{"tags":["Weekly Planner"],"summary":"Approve the written week (needs a paid plan; spends nothing)","x-scopes":["content:write"],"parameters":[{"name":"plan","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"The plan, status style_review"},"402":{"description":"plan_required: the workspace has no paid plan (details.plans_url)"},"422":{"description":"plan_has_violations: rewrite the posts named in details.violations first"}}}},"/planner/plans/{plan}/generate":{"post":{"tags":["Weekly Planner"],"summary":"Make every post (spends AI credits; async). Refused if the cost exceeds max_credits","x-scopes":["content:write"],"parameters":[{"name":"plan","in":"path","required":true,"schema":{"type":"integer"}},{"$ref":"#/components/parameters/idempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["max_credits"],"properties":{"max_credits":{"type":"integer","description":"The most the customer agreed to spend; compare estimate.image_credits."}}}}}},"responses":{"202":{"description":"The plan, status generating"},"402":{"description":"credits_exhausted (needed, remaining, packs, upgrade) or plan_required"},"409":{"$ref":"#/components/responses/Refused"},"422":{"description":"cost_exceeds_max, or invalid"}}}},"/planner/plans/{plan}/schedule":{"post":{"tags":["Weekly Planner"],"summary":"Schedule every approved, finished post to its accounts","x-scopes":["content:write","posts:write"],"description":"Needs BOTH content:write and posts:write, and the key must be allowed on every account in the plan.","parameters":[{"name":"plan","in":"path","required":true,"schema":{"type":"integer"}},{"$ref":"#/components/parameters/idempotencyKey"}],"responses":{"200":{"description":"The plan; meta.schedule_results per post"},"403":{"$ref":"#/components/responses/Refused"},"409":{"$ref":"#/components/responses/Refused"},"422":{"$ref":"#/components/responses/Refused"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/webhooks/events":{"get":{"tags":["Webhooks"],"summary":"The event catalogue — what is emitted, from where, and what is not","x-scopes":["webhooks:manage"],"responses":{"200":{"$ref":"#/components/responses/Ok"}}}},"/webhooks":{"get":{"tags":["Webhooks"],"summary":"Registered endpoints","x-scopes":["webhooks:manage"],"responses":{"200":{"$ref":"#/components/responses/Ok"},"503":{"description":"Webhook tables not migrated yet"}}},"post":{"tags":["Webhooks"],"summary":"Register an endpoint (returns the signing secret ONCE)","x-scopes":["webhooks:manage"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url","events"],"properties":{"url":{"type":"string","format":"uri","description":"https, port 443, public address only."},"events":{"type":"array","items":{"type":"string"}},"description":{"type":"string","maxLength":200}}}}}},"responses":{"201":{"$ref":"#/components/responses/Ok"},"422":{"$ref":"#/components/responses/Invalid"}},"callbacks":{"event":{"{$request.body#/url}":{"post":{"summary":"An event delivery","parameters":[{"name":"X-Scheduloid-Signature","in":"header","required":true,"schema":{"type":"string"},"description":"t=<unix>,v1=<hex HMAC-SHA256(secret, \"<t>.<raw body>\")>"},{"name":"X-Scheduloid-Event","in":"header","required":true,"schema":{"type":"string"}},{"name":"X-Scheduloid-Event-Id","in":"header","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"2XX":{"description":"Acknowledged within 10 seconds. Anything else is retried with backoff for 24h."}}}}}}}},"/webhooks/{id}":{"delete":{"tags":["Webhooks"],"summary":"Remove an endpoint (pending retries are abandoned)","x-scopes":["webhooks:manage"],"parameters":[{"$ref":"#/components/parameters/id"}],"responses":{"200":{"$ref":"#/components/responses/Ok"}}}},"/webhooks/{id}/test":{"post":{"tags":["Webhooks"],"summary":"Send a signed ping now and report what the receiver answered","x-scopes":["webhooks:manage"],"parameters":[{"$ref":"#/components/parameters/id"}],"responses":{"200":{"$ref":"#/components/responses/Ok"}}}},"/webhooks/{id}/deliveries":{"get":{"tags":["Webhooks"],"summary":"The last 100 deliveries for an endpoint","x-scopes":["webhooks:manage"],"parameters":[{"$ref":"#/components/parameters/id"}],"responses":{"200":{"$ref":"#/components/responses/Ok"}}}}},"components":{"securitySchemes":{"bearer":{"type":"http","scheme":"bearer","description":"A Scheduloid API key: Authorization: Bearer sk_live_…  (X-Api-Key is also accepted)."}},"parameters":{"id":{"name":"id","in":"path","required":true,"schema":{"type":"integer"}},"cursor":{"name":"cursor","in":"query","schema":{"type":"string"},"description":"next_cursor from the previous page."},"limit":{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":25}},"since":{"name":"since","in":"query","schema":{"type":"string","format":"date-time"}},"until":{"name":"until","in":"query","schema":{"type":"string","format":"date-time"}},"eventType":{"name":"type","in":"query","schema":{"type":"string"},"description":"Comma list of product event types."},"crmCollection":{"name":"collection","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z][a-z0-9_]{1,39}$"},"description":"Table key: people, touches, tasks, approvals, content, reports, or one you created."},"idempotencyKey":{"name":"Idempotency-Key","in":"header","schema":{"type":"string","maxLength":200},"description":"Replays the first response for 24h instead of acting twice."}},"schemas":{"PlannerCampaign":{"type":"object","description":"The customer's own brief. Topics come from the customer, never from Scheduloid.","properties":{"topics":{"type":"array","maxItems":20,"items":{"type":"string","maxLength":200},"description":"What the week is about. With only_topics (default) every post must be about one of these, and each topic gets a post where the week has room."},"only_topics":{"type":"boolean","default":true},"facts":{"type":"array","maxItems":30,"items":{"type":"string","maxLength":300},"description":"Claims the posts may state although the website does not (the customer vouches for them)."},"never_say":{"type":"array","maxItems":30,"items":{"type":"string","maxLength":200}}}},"PlannerCorrections":{"type":"object","description":"Plain-text fixes to what the research read off the website (not regex).","properties":{"replace":{"type":"array","maxItems":20,"items":{"type":"object","required":["from","to"],"properties":{"from":{"type":"string","maxLength":200},"to":{"type":"string","maxLength":300}}}},"drop":{"type":"array","maxItems":20,"items":{"type":"string","maxLength":200},"description":"Remove every research line containing one of these."}}},"Error":{"type":"object","properties":{"error":{"type":"object","required":["type","message","request_id"],"properties":{"type":{"type":"string","examples":["unauthenticated","revoked","expired","forbidden_scope","rate_limited","not_found","invalid","generation_failed","credits_exhausted"]},"message":{"type":"string"},"request_id":{"type":"string"}}}}},"PostCreate":{"type":"object","required":["account_ids"],"properties":{"account_ids":{"type":"array","minItems":1,"maxItems":20,"items":{"type":"integer"}},"text":{"type":"string","maxLength":8000},"text_by_account":{"type":"object","additionalProperties":{"type":"string"}},"link":{"type":"string","format":"uri"},"media_urls":{"type":"array","maxItems":10,"items":{"type":"string","format":"uri"},"description":"https only; fetched through an SSRF guard; max 128 MB each."},"schedule_at":{"type":"string","description":"Omit to publish now. ISO-8601 with an offset, or pair with timezone."},"timezone":{"type":"string"},"post_type":{"enum":["feed","reel","story","short"]},"tiktok":{"type":"object","properties":{"privacy_level":{"enum":["PUBLIC_TO_EVERYONE","MUTUAL_FOLLOW_FRIENDS","FOLLOWER_OF_CREATOR","SELF_ONLY"]},"music_consent":{"const":true}}}}},"CrmField":{"type":"object","required":["key","type"],"properties":{"key":{"type":"string","pattern":"^[a-z][a-z0-9_]{1,39}$"},"label":{"type":"string"},"type":{"enum":["text","longtext","integer","number","bool","date","datetime","url","email","select","tags","json","person"]},"options":{"type":"array","items":{"type":"string"},"description":"Required for select."},"required":{"type":"boolean"}}},"MessageCreate":{"type":"object","required":["subject","body","category","reason"],"properties":{"subject":{"type":"string","minLength":3,"maxLength":120,"description":"One line."},"body":{"type":"string","minLength":20,"maxLength":4000,"description":"Sent as plain text exactly as written: no template, no footer. Put links in as full https URLs. No HTML, no placeholders, no mention of passwords/codes/card details. Every link, bare domain and email address must be on a Scheduloid host."},"category":{"enum":["onboarding","activation","reconnect_account","failing_posts","reactivation","plan","credits","feature_tip","check_in"]},"reason":{"type":"string","minLength":5,"maxLength":500,"description":"Why this user, why now. Audit log only; never shown to the user."},"dry_run":{"type":"boolean","description":"Render and judge without sending or recording."}}},"WebhookEvent":{"type":"object","required":["id","type","occurred_at","data"],"properties":{"id":{"type":"string","format":"uuid"},"type":{"enum":["user.signed_up","user.inactive_7d","account.token_expired","account.disconnected","post.failed","credits.low","subscription.trial_ending","ping"]},"occurred_at":{"type":"string","format":"date-time"},"data":{"type":"object","description":"Always includes data.user with contactable and ai_outreach_consent — respect them."}}}},"responses":{"Ok":{"description":"{ data: … }"},"Page":{"description":"{ data: [...], next_cursor: string|null, has_more: bool, meta?: {...} }"},"Unauthenticated":{"description":"Missing, unknown, revoked or expired key — error.type says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Not found — also the answer for anything outside the key's audience.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Invalid":{"description":"Every invalid field at once, under error.fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Refused":{"description":"A business refusal with a specific error.type (and details such as reconnect_url).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Limit reached; Retry-After says when to retry. error.limit_scope names the limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}