# AI Agents Source: https://docs.smartlyq.com/ai-agents Every way to drive SmartlyQ from an AI agent or LLM — MCP, llms.txt, the per-page AI menu, and the REST API. SmartlyQ is built to be driven by AI. Whether you're wiring up an autonomous agent, an assistant, or a coding tool, there are four ways to connect — pick the one that fits your client. The hosted **Model Context Protocol** server. Connect Claude, Cursor, ChatGPT, or any MCP client and let an agent post, generate content, and read analytics with natural language. **Start here for most agents.** A machine-readable index of the entire documentation at **`/llms.txt`**. Paste it (or the `Open in ChatGPT/Claude` links at the top of any page) so an assistant can answer questions against the real, current docs. Every capability is a plain HTTPS endpoint with an `sqk_live_` Bearer key. Agents that can't speak MCP can call the API directly — see the [API Reference](/api-reference). Every page has a **Copy page** button and an **Open ▾** menu (ChatGPT, Claude, Perplexity, Grok, Cursor, VS Code). Send any page straight into your assistant to ask questions about it. ## Which one should I use? | You're building… | Use | | --------------------------------------------- | --------------------------------------------------- | | An agent in Claude, Cursor, ChatGPT, Windsurf | **[MCP Server](/mcp-server)** — tools are pre-wired | | A custom backend / your own agent loop | **[REST API](/quickstart)** with an API key | | An assistant that answers docs questions | **[llms.txt](/llms.txt)** + the per-page AI menu | ## Authentication is the same everywhere Whichever path you take, access is governed by a single **API key** and its [scopes](/authentication). The MCP server, the REST API, and any SDK all forward that key — there's no separate agent credential to manage, and the same [billing](/guides/billing-and-credits) and [rate limits](/guides/rate-limiting) apply. New to the API? Start with the **[Quickstart](/quickstart)** to get a key and make your first call, then point your agent at the **[MCP Server](/mcp-server)**. # Billing overview Source: https://docs.smartlyq.com/api-reference/account/billing-overview /openapi.json get /me/billing A read-only superset of GET /me/balance: the wallet balance breakdown, the monthly cycle reset, auto-recharge configuration, and whether a card is on file. Use payment.status to know if the next billable call is at risk. Requires any valid API key (no scope needed). # Get current user profile Source: https://docs.smartlyq.com/api-reference/account/get-current-user-profile /openapi.json get /me Requires any valid API key (no scope needed). # Get usage summary Source: https://docs.smartlyq.com/api-reference/account/get-usage-summary /openapi.json get /me/usage Requires any valid API key (no scope needed). # Get wallet balance Source: https://docs.smartlyq.com/api-reference/account/get-wallet-balance /openapi.json get /me/balance Requires any valid API key (no scope needed). # Account health and rate-limit diagnostics Source: https://docs.smartlyq.com/api-reference/ads/account-health-and-rate-limit-diagnostics /openapi.json get /ads/accounts/diagnostics Per platform, per tracked account: connection status, last sync time/error, and live API rate-limit usage. Requires scope `ads:read`. # Ads performance analytics Source: https://docs.smartlyq.com/api-reference/ads/ads-performance-analytics /openapi.json get /ads/analytics Summary + by-platform breakdown + daily spend chart + period-over-period trends. Date range defaults to the last 30 days. Every block here is scoped to that range, including `by_campaign` - unlike the campaign rows from `GET /ads/campaigns`, which carry a fixed 28-day total. Requires scope `ads:read`. # Archive a campaign Source: https://docs.smartlyq.com/api-reference/ads/archive-a-campaign /openapi.json post /ads/campaigns/{id}/archive Requires scope `ads:write`. # Archive an ad Source: https://docs.smartlyq.com/api-reference/ads/archive-an-ad /openapi.json post /ads/ads/{id}/archive Requires scope `ads:write`. # Archive an ad set Source: https://docs.smartlyq.com/api-reference/ads/archive-an-ad-set /openapi.json post /ads/ad-sets/{id}/archive Requires scope `ads:write`. # Audience-size estimate Source: https://docs.smartlyq.com/api-reference/ads/audience-size-estimate /openapi.json post /ads/estimate Meta only today - returns `available: false` (not an error) for every other platform so callers can branch cleanly rather than treating it as a failure. Uses the same targeting-resolution logic as a real campaign launch, so the estimate reflects exactly what would be sent to Meta. Requires scope `ads:read`. # Bulk pause/resume campaigns Source: https://docs.smartlyq.com/api-reference/ads/bulk-pauseresume-campaigns /openapi.json post /ads/campaigns/bulk-status Up to 50 campaign IDs per call. Never all-or-nothing - each id gets its own result, so a partial failure doesn't block the rest. Returns 200 when every id succeeded and 207 when at least one failed, so a client can branch on the status code without parsing the body. Requires scope `ads:write`. # Create a campaign Source: https://docs.smartlyq.com/api-reference/ads/create-a-campaign /openapi.json post /ads/campaigns For `platform: meta` with a `page_id`, launches a real campaign on Meta (same path the in-app wizard uses). Every other platform/shape creates a local draft - only Meta has live launch wiring today. Requires scope `ads:write`. # Create a creative Source: https://docs.smartlyq.com/api-reference/ads/create-a-creative /openapi.json post /ads/creatives Standalone asset library entry, independent of any ad. Upload the file bytes via `POST /media/upload-direct` first and pass the resulting URL as `file_url` - this endpoint never handles raw file uploads. Requires scope `ads:write`. # Create a lead form Source: https://docs.smartlyq.com/api-reference/ads/create-a-lead-form /openapi.json post /ads/lead-forms Meta only. Standard question fields only (no custom multi-choice/date-picker types). Requires scope `ads:write`. # Create an audience Source: https://docs.smartlyq.com/api-reference/ads/create-an-audience /openapi.json post /ads/audiences Meta (Custom/Lookalike) or Google (remarketing/CRM user list). Requires a connected ad account on the chosen platform. Requires scope `ads:write`. # Delete a campaign Source: https://docs.smartlyq.com/api-reference/ads/delete-a-campaign /openapi.json delete /ads/campaigns/{id} Deletes on the live platform first (falls back to pausing if the platform delete fails, so nothing keeps spending), then removes it locally along with its ad sets and ads. Requires scope `ads:write`. # Delete a creative Source: https://docs.smartlyq.com/api-reference/ads/delete-a-creative /openapi.json delete /ads/creatives/{id} Requires scope `ads:write`. # Delete an ad Source: https://docs.smartlyq.com/api-reference/ads/delete-an-ad /openapi.json delete /ads/ads/{id} Requires scope `ads:write`. # Delete an ad set Source: https://docs.smartlyq.com/api-reference/ads/delete-an-ad-set /openapi.json delete /ads/ad-sets/{id} Requires scope `ads:write`. # Duplicate a campaign Source: https://docs.smartlyq.com/api-reference/ads/duplicate-a-campaign /openapi.json post /ads/campaigns/{id}/duplicate Requires scope `ads:write`. # Duplicate an ad Source: https://docs.smartlyq.com/api-reference/ads/duplicate-an-ad /openapi.json post /ads/ads/{id}/duplicate Requires scope `ads:write`. # Duplicate an ad set Source: https://docs.smartlyq.com/api-reference/ads/duplicate-an-ad-set /openapi.json post /ads/ad-sets/{id}/duplicate Requires scope `ads:write`. # Get a campaign Source: https://docs.smartlyq.com/api-reference/ads/get-a-campaign /openapi.json get /ads/campaigns/{id} Metric fields (`spent`, `impressions`, `clicks`, `conversions`, `leads`, `purchase_value`, `ctr`, `cpa`, `roas`) cover the **last 28 days**, on every platform. That is the window the ad networks give us daily figures for, so it is the longest total we can report without inventing the missing days - it is NOT the campaign's lifetime. For any other date range, or a per-campaign breakdown of one, use `GET /ads/analytics`. Requires scope `ads:read`. # Get an ad Source: https://docs.smartlyq.com/api-reference/ads/get-an-ad /openapi.json get /ads/ads/{id} Requires scope `ads:read`. # Get an ad set Source: https://docs.smartlyq.com/api-reference/ads/get-an-ad-set /openapi.json get /ads/ad-sets/{id} Requires scope `ads:read`. # List a Page's organic posts Source: https://docs.smartlyq.com/api-reference/ads/list-a-pages-organic-posts /openapi.json get /ads/pages/{page_id}/posts Recent organic posts on a connected Meta Page, for picking one to Boost (POST /ads/campaigns with creative.object_story_id). Requires scope `ads:read`. # List ad sets Source: https://docs.smartlyq.com/api-reference/ads/list-ad-sets /openapi.json get /ads/ad-sets Metric fields (`spent`, `impressions`, `clicks`, `conversions`, `leads`, `purchase_value`, `ctr`, `cpa`, `roas`) cover the **last 28 days**, on every platform. That is the window the ad networks give us daily figures for, so it is the longest total we can report without inventing the missing days - it is NOT the campaign's lifetime. For any other date range, or a per-campaign breakdown of one, use `GET /ads/analytics`. Requires scope `ads:read`. # List ads Source: https://docs.smartlyq.com/api-reference/ads/list-ads /openapi.json get /ads/ads Metric fields (`spent`, `impressions`, `clicks`, `conversions`, `leads`, `purchase_value`, `ctr`, `cpa`, `roas`) cover the **last 28 days**, on every platform. That is the window the ad networks give us daily figures for, so it is the longest total we can report without inventing the missing days - it is NOT the campaign's lifetime. For any other date range, or a per-campaign breakdown of one, use `GET /ads/analytics`. Requires scope `ads:read`. # List audiences Source: https://docs.smartlyq.com/api-reference/ads/list-audiences /openapi.json get /ads/audiences Requires scope `ads:read`. # List audit log entries Source: https://docs.smartlyq.com/api-reference/ads/list-audit-log-entries /openapi.json get /ads/audit-log Pass `campaign_id` to scope the log to one campaign and everything under it (its ad sets and ads). Requires scope `ads:read`. # List campaigns Source: https://docs.smartlyq.com/api-reference/ads/list-campaigns /openapi.json get /ads/campaigns Metric fields (`spent`, `impressions`, `clicks`, `conversions`, `leads`, `purchase_value`, `ctr`, `cpa`, `roas`) cover the **last 28 days**, on every platform. That is the window the ad networks give us daily figures for, so it is the longest total we can report without inventing the missing days - it is NOT the campaign's lifetime. For any other date range, or a per-campaign breakdown of one, use `GET /ads/analytics`. Requires scope `ads:read`. # List connected ad accounts Source: https://docs.smartlyq.com/api-reference/ads/list-connected-ad-accounts /openapi.json get /ads/accounts Connected accounts across Meta, Google, TikTok and LinkedIn Ads. Requires scope `ads:read`. # List creatives Source: https://docs.smartlyq.com/api-reference/ads/list-creatives /openapi.json get /ads/creatives Requires scope `ads:read`. # List lead forms Source: https://docs.smartlyq.com/api-reference/ads/list-lead-forms /openapi.json get /ads/lead-forms Meta Lead Ads forms (local mirror, synced from connected Pages). Requires scope `ads:read`. # List pixels / conversion destinations Source: https://docs.smartlyq.com/api-reference/ads/list-pixels-conversion-destinations /openapi.json get /ads/pixels Local mirror of each connected platform's pixels/conversion actions (Meta, Google, TikTok), kept fresh by the account sync. Read-only - pixels are created in each platform's own tools, not through this API. Requires scope `ads:read`. # Pause a campaign Source: https://docs.smartlyq.com/api-reference/ads/pause-a-campaign /openapi.json post /ads/campaigns/{id}/pause Requires scope `ads:write`. # Pause an ad Source: https://docs.smartlyq.com/api-reference/ads/pause-an-ad /openapi.json post /ads/ads/{id}/pause Requires scope `ads:write`. # Pause an ad set Source: https://docs.smartlyq.com/api-reference/ads/pause-an-ad-set /openapi.json post /ads/ad-sets/{id}/pause Requires scope `ads:write`. # Resume a campaign Source: https://docs.smartlyq.com/api-reference/ads/resume-a-campaign /openapi.json post /ads/campaigns/{id}/resume Requires scope `ads:write`. # Resume an ad Source: https://docs.smartlyq.com/api-reference/ads/resume-an-ad /openapi.json post /ads/ads/{id}/resume Requires scope `ads:write`. # Resume an ad set Source: https://docs.smartlyq.com/api-reference/ads/resume-an-ad-set /openapi.json post /ads/ad-sets/{id}/resume Requires scope `ads:write`. # Search Meta interest targeting Source: https://docs.smartlyq.com/api-reference/ads/search-meta-interest-targeting /openapi.json get /ads/targeting-search Type-ahead interest lookup for building a campaign's targeting.interests array. Meta only. Minimum 3 characters. Requires scope `ads:read`. # Trigger an account sync Source: https://docs.smartlyq.com/api-reference/ads/trigger-an-account-sync /openapi.json post /ads/sync Fire-and-forget - queues a full sync of campaigns/ad sets/ads/insights across every connected platform. Returns immediately; poll the list endpoints afterward. Requires scope `ads:write`. # Update a campaign Source: https://docs.smartlyq.com/api-reference/ads/update-a-campaign /openapi.json patch /ads/campaigns/{id} Update name and/or budget. Subject to the same anti-ban edit-cooldown as the in-app wizard - a budget change within Meta's 72h learning-phase window may be blocked (429) or need `confirm_cooldown`. Requires scope `ads:write`. # Update a creative Source: https://docs.smartlyq.com/api-reference/ads/update-a-creative /openapi.json patch /ads/creatives/{id} Requires scope `ads:write`. # Get AI Captain conversation Source: https://docs.smartlyq.com/api-reference/ai-captain/get-ai-captain-conversation /openapi.json get /captain/conversations/{conversation_id} Requires scope `captain:use`. # List AI Captain conversations Source: https://docs.smartlyq.com/api-reference/ai-captain/list-ai-captain-conversations /openapi.json get /captain/conversations Requires scope `captain:use`. # Send AI Captain message Source: https://docs.smartlyq.com/api-reference/ai-captain/send-ai-captain-message /openapi.json post /captain/messages Requires scope `captain:use`. # Best time to post Source: https://docs.smartlyq.com/api-reference/analytics/best-time-to-post /openapi.json get /analytics/best-time When your audience actually engages: a 7x24 heatmap of average engagement per published post by weekday and hour (in your timezone), plus the top 5 slots. Requires scope `analytics:read`. # Content decay Source: https://docs.smartlyq.com/api-reference/analytics/content-decay /openapi.json get /analytics/content-decay How long a post keeps working: the average share of a post's final engagement reached by each age-day (0-13), across posts published in the window. Requires scope `analytics:read`. # Conversation analytics Source: https://docs.smartlyq.com/api-reference/analytics/conversation-analytics /openapi.json get /analytics/inbox/conversations/{conversation_id} One conversation's full stats: message counts and response times (average, median). Requires scope `analytics:read`. # Daily metrics Source: https://docs.smartlyq.com/api-reference/analytics/daily-metrics /openapi.json get /analytics/daily-metrics Per-day aggregates across all accounts (impressions, reach, engagements, follower deltas, posts published) plus a per-platform breakdown. Computed from stored snapshots - fast and unbilled. Requires scope `analytics:read`. # Get account analytics Source: https://docs.smartlyq.com/api-reference/analytics/get-account-analytics /openapi.json get /analytics/accounts/{account_id} Requires scope `analytics:read`. # Get analytics overview Source: https://docs.smartlyq.com/api-reference/analytics/get-analytics-overview /openapi.json get /analytics/overview Requires scope `analytics:read`. # Get post analytics Source: https://docs.smartlyq.com/api-reference/analytics/get-post-analytics /openapi.json get /analytics/posts Requires scope `analytics:read`. # Inbox conversation stats Source: https://docs.smartlyq.com/api-reference/analytics/inbox-conversation-stats /openapi.json get /analytics/inbox/conversations Per-conversation message volume for the window, busiest first. Requires scope `analytics:read`. # Inbox heatmap Source: https://docs.smartlyq.com/api-reference/analytics/inbox-heatmap /openapi.json get /analytics/inbox/heatmap Incoming messages bucketed by weekday and hour (in your timezone) - when your audience actually writes to you. Requires scope `analytics:read`. # Inbox response time Source: https://docs.smartlyq.com/api-reference/analytics/inbox-response-time /openapi.json get /analytics/inbox/response-time How fast you answer: for every incoming message answered by a later reply in the same conversation, the wait - average, median, and a histogram (under_15m, 15m_1h, 1h_4h, 4h_24h, over_24h). Requires scope `analytics:read`. # Inbox source breakdown Source: https://docs.smartlyq.com/api-reference/analytics/inbox-source-breakdown /openapi.json get /analytics/inbox/source-breakdown Active conversations and message volume per platform. Requires scope `analytics:read`. # Inbox top accounts Source: https://docs.smartlyq.com/api-reference/analytics/inbox-top-accounts /openapi.json get /analytics/inbox/top-accounts Your busiest connected accounts by incoming messages. Requires scope `analytics:read`. # Inbox volume Source: https://docs.smartlyq.com/api-reference/analytics/inbox-volume /openapi.json get /analytics/inbox/volume Daily incoming/outgoing message counts plus new conversations started, across the social inbox. Requires scope `analytics:read`. # Post metric timeline Source: https://docs.smartlyq.com/api-reference/analytics/post-metric-timeline /openapi.json get /analytics/posts/{post_id}/timeline Every metric snapshot collected for one post, per platform, oldest first - impressions, reach, engagements, likes, comments, shares, saves, clicks, video views over time. Requires scope `analytics:read`. # Posting frequency vs engagement Source: https://docs.smartlyq.com/api-reference/analytics/posting-frequency-vs-engagement /openapi.json get /analytics/posting-frequency Per ISO week: how many posts you published and what they averaged in engagement - the data behind 'are we posting too much or too little'. Requires scope `analytics:read`. # YouTube audience retention Source: https://docs.smartlyq.com/api-reference/analytics/youtube-audience-retention /openapi.json get /analytics/youtube/video-retention The retention curve for one video: what share of viewers is still watching at each point of the video, plus how that compares with similar videos on YouTube. Rows are ordered by `elapsedVideoTimeRatio`, from 0 (start) to 1 (end). This reads the YouTube Analytics API, which needs its own OAuth permission (`yt-analytics.readonly`). SmartlyQ only began requesting it on 28 July 2026, so channels connected before that answer `403 CONNECTION_SCOPE_MISSING` with `reconnect_required: true` until the account is reconnected. Requires scope `analytics:read`. # YouTube channel insights Source: https://docs.smartlyq.com/api-reference/analytics/youtube-channel-insights /openapi.json get /analytics/youtube/channel-insights Channel totals for a date range: views, watch time, average view duration and percentage, subscribers gained and lost, likes, dislikes, comments and shares. This reads the YouTube Analytics API, which needs its own OAuth permission (`yt-analytics.readonly`). SmartlyQ only began requesting it on 28 July 2026, so channels connected before that answer `403 CONNECTION_SCOPE_MISSING` with `reconnect_required: true` until the account is reconnected. Requires scope `analytics:read`. # YouTube daily views Source: https://docs.smartlyq.com/api-reference/analytics/youtube-daily-views /openapi.json get /analytics/youtube/daily-views Views, watch time, average view duration and subscribers gained broken down by day - the shape a time-series chart needs. This reads the YouTube Analytics API, which needs its own OAuth permission (`yt-analytics.readonly`). SmartlyQ only began requesting it on 28 July 2026, so channels connected before that answer `403 CONNECTION_SCOPE_MISSING` with `reconnect_required: true` until the account is reconnected. Requires scope `analytics:read`. # YouTube viewer demographics Source: https://docs.smartlyq.com/api-reference/analytics/youtube-viewer-demographics /openapi.json get /analytics/youtube/demographics Viewer split by age bracket and gender, as a percentage of views. Pass `video_id` to scope it to one video. YouTube withholds demographics below a privacy threshold - that comes back as an empty `rows` array rather than an error. This reads the YouTube Analytics API, which needs its own OAuth permission (`yt-analytics.readonly`). SmartlyQ only began requesting it on 28 July 2026, so channels connected before that answer `403 CONNECTION_SCOPE_MISSING` with `reconnect_required: true` until the account is reconnected. Requires scope `analytics:read`. # Delete article Source: https://docs.smartlyq.com/api-reference/articles/delete-article /openapi.json delete /articles/{article_id} Requires scope `articles:write`. # Generate article Source: https://docs.smartlyq.com/api-reference/articles/generate-article /openapi.json post /articles/generate Start article generation. Returns 202 with job_id; poll GET /jobs/{job_id} for result. Either `topic` or `keywords` must be provided. Requires scope `articles:write`. # Get article Source: https://docs.smartlyq.com/api-reference/articles/get-article /openapi.json get /articles/{article_id} Requires scope `articles:read`. # List articles Source: https://docs.smartlyq.com/api-reference/articles/list-articles /openapi.json get /articles Requires scope `articles:read`. # Get audio Source: https://docs.smartlyq.com/api-reference/audio/get-audio /openapi.json get /audio/{audio_id} Requires scope `audio:read`. # Speech to text Source: https://docs.smartlyq.com/api-reference/audio/speech-to-text /openapi.json post /audio/speech-to-text Requires scope `audio:write`. # Text to speech Source: https://docs.smartlyq.com/api-reference/audio/text-to-speech /openapi.json post /audio/text-to-speech Requires scope `audio:write`. # Activate automation Source: https://docs.smartlyq.com/api-reference/automations/activate-automation /openapi.json post /automations/{automation_id}/activate Turns the automation on (validates the graph has a trigger and at least one action; webhook triggers get their URL provisioned). Requires scope `contacts:write`. # Duplicate an automation Source: https://docs.smartlyq.com/api-reference/automations/duplicate-an-automation /openapi.json post /automations/{automation_id}/duplicate Copies the automation's graph and trigger config into a new automation, named " (copy)". Always starts as `draft`, regardless of the source's status, so a duplicate never silently begins running triggers. A webhook-triggered source gets a fresh webhook token on the copy, not a shared one. Requires scope `contacts:write`. # Get automation Source: https://docs.smartlyq.com/api-reference/automations/get-automation /openapi.json get /automations/{automation_id} Full automation detail including the node graph. Requires scope `contacts:read`. # Get one automation version Source: https://docs.smartlyq.com/api-reference/automations/get-one-automation-version /openapi.json get /automations/{automation_id}/versions/{version} One snapshot, including its full graph. Requires scope `contacts:read`. # Get run Source: https://docs.smartlyq.com/api-reference/automations/get-run /openapi.json get /automations/{automation_id}/runs/{run_id} One run with its trigger payload and per-step execution logs. Requires scope `contacts:read`. # List automation versions Source: https://docs.smartlyq.com/api-reference/automations/list-automation-versions /openapi.json get /automations/{automation_id}/versions A snapshot of the graph is taken automatically every time it's edited - BEFORE the edit is applied - so version N is what the graph looked like immediately before edit N. Newest first, capped at the most recent 20. Requires scope `contacts:read`. # List automations Source: https://docs.smartlyq.com/api-reference/automations/list-automations /openapi.json get /automations The workspace's automations (built in the visual builder), with run/error counts. Requires scope `contacts:read`. # List runs Source: https://docs.smartlyq.com/api-reference/automations/list-runs /openapi.json get /automations/{automation_id}/runs Run history, newest first. Requires scope `contacts:read`. # Pause automation Source: https://docs.smartlyq.com/api-reference/automations/pause-automation /openapi.json post /automations/{automation_id}/deactivate Pauses the automation and its triggers. Requires scope `contacts:write`. # Restore an automation version Source: https://docs.smartlyq.com/api-reference/automations/restore-an-automation-version /openapi.json post /automations/{automation_id}/versions/{version}/restore Puts that version's graph back onto the live automation. The current graph is snapshotted first (the same hook every edit goes through), so a restore is itself just another edit - always undoable, never destructive. Requires scope `contacts:write`. # Trigger automation Source: https://docs.smartlyq.com/api-reference/automations/trigger-automation /openapi.json post /automations/{automation_id}/trigger Queues a manual run with an optional payload (available to the automation's steps as the trigger payload). The automation must be active. Requires scope `contacts:write`. # Cancel a booking Source: https://docs.smartlyq.com/api-reference/calendar/cancel-a-booking /openapi.json post /calendar/bookings/{id}/cancel Releases the slot, removes the calendar event, tells the attendee and fires webhooks. This is not a bookkeeping change, so only do it when the meeting really is off. Cancelling an already-cancelled booking succeeds rather than erroring. Requires scope `calendar:write`. # List booking pages Source: https://docs.smartlyq.com/api-reference/calendar/list-booking-pages /openapi.json get /calendar/event-types The booking pages this account can take bookings for. Call this first - every other calendar endpoint needs an `event_type_id`. `host_found: false` means there is no calendar account yet, which is different from having no event types. Requires scope `calendar:read`. # List open slots Source: https://docs.smartlyq.com/api-reference/calendar/list-open-slots /openapi.json get /calendar/slots The times that are actually free, grouped by day. Worked out from working hours, buffers, existing bookings and any connected Google or Outlook calendar - never offer a time this did not return. Requires scope `calendar:read`. # Take a booking Source: https://docs.smartlyq.com/api-reference/calendar/take-a-booking /openapi.json post /calendar/bookings Books a specific time. The calendar holds the slot, emails the attendee and the host, and creates the connected calendar event - exactly as if the attendee had booked it themselves. Use a start time from the slots endpoint; if the slot has gone since, the calendar's own message says so and you should offer another. Requires scope `calendar:write`. # Create chatbot Source: https://docs.smartlyq.com/api-reference/chatbot/create-chatbot /openapi.json post /chatbots Requires scope `chatbot:use`. # Delete chatbot Source: https://docs.smartlyq.com/api-reference/chatbot/delete-chatbot /openapi.json delete /chatbots/{id} Requires scope `chatbot:use`. # Get chatbot Source: https://docs.smartlyq.com/api-reference/chatbot/get-chatbot /openapi.json get /chatbots/{id} Requires scope `chatbot:use`. # Get chatbot training status Source: https://docs.smartlyq.com/api-reference/chatbot/get-chatbot-training-status /openapi.json get /chatbots/{id}/train-status Requires scope `chatbot:use`. # Get conversation messages Source: https://docs.smartlyq.com/api-reference/chatbot/get-conversation-messages /openapi.json get /chatbots/{id}/conversations/{conv_id}/messages Requires scope `chatbot:use`. # List chatbot conversations Source: https://docs.smartlyq.com/api-reference/chatbot/list-chatbot-conversations /openapi.json get /chatbots/{id}/conversations Requires scope `chatbot:use`. # List chatbots Source: https://docs.smartlyq.com/api-reference/chatbot/list-chatbots /openapi.json get /chatbots Requires scope `chatbot:use`. # Send chatbot message Source: https://docs.smartlyq.com/api-reference/chatbot/send-chatbot-message /openapi.json post /chatbots/{id}/messages Requires scope `chatbot:use`. # Start chatbot training Source: https://docs.smartlyq.com/api-reference/chatbot/start-chatbot-training /openapi.json post /chatbots/{id}/train Triggers training using the chatbot's existing training data (training_text, training_url, and FAQs). No request body required. Poll GET /chatbots/{id}/train-status for completion. Requires scope `chatbot:use`. # Update chatbot Source: https://docs.smartlyq.com/api-reference/chatbot/update-chatbot /openapi.json patch /chatbots/{id} Requires scope `chatbot:use`. # Approve or reject a comment Source: https://docs.smartlyq.com/api-reference/comments/approve-or-reject-a-comment /openapi.json post /social/comments/{comment_id}/moderate Full moderation status change, optionally banning the author. YouTube only - other platforms only support hide/unhide (POST .../hide) and delete (DELETE .../{comment_id}), which every platform honors. Requires social:write. # Delete a comment Source: https://docs.smartlyq.com/api-reference/comments/delete-a-comment /openapi.json delete /social/comments/{comment_id} Requires scope `social:write`. # Get one post's comments (threaded) Source: https://docs.smartlyq.com/api-reference/comments/get-one-posts-comments-threaded /openapi.json get /social/comments/{post_id} Returns a single post's comments as a nested reply tree, with post metadata. A by-id convenience over GET /social/comments (which already threads replies). Reply nesting is populated for every platform except Pinterest (whose comments are flat). Requires social:read. # Hide or unhide a comment Source: https://docs.smartlyq.com/api-reference/comments/hide-or-unhide-a-comment /openapi.json post /social/comments/{comment_id}/hide Requires scope `social:write`. # Like a comment Source: https://docs.smartlyq.com/api-reference/comments/like-a-comment /openapi.json post /social/comments/{comment_id}/like Like a comment as the connected account. Facebook only - Instagram's Graph API has no comment-like edge. Requires social:write. # List comments Source: https://docs.smartlyq.com/api-reference/comments/list-comments /openapi.json get /social/comments Requires scope `social:read`. # Reply to a comment Source: https://docs.smartlyq.com/api-reference/comments/reply-to-a-comment /openapi.json post /social/comments/{comment_id}/reply Requires scope `social:write`. # Unlike a comment Source: https://docs.smartlyq.com/api-reference/comments/unlike-a-comment /openapi.json delete /social/comments/{comment_id}/like Remove your like from a comment. Facebook only. Requires social:write. # Generate a social caption Source: https://docs.smartlyq.com/api-reference/content/generate-a-social-caption /openapi.json post /content/caption Writes ONE platform-tuned social caption (with hashtags) for a short-form video. Billed per request; refunded on failure. Requires scope `social:write`. # Rewrite content Source: https://docs.smartlyq.com/api-reference/content/rewrite-content /openapi.json post /content/rewrite Requires scope `articles:write`. # Create a company Source: https://docs.smartlyq.com/api-reference/crm-companies/create-a-company /openapi.json post /companies Create a company. Only `name` is required. Requires scope `companies:write`. # Delete a company Source: https://docs.smartlyq.com/api-reference/crm-companies/delete-a-company /openapi.json delete /companies/{id} Deletes the company. Its contacts are **not** deleted - they are unlinked and stay in the CRM. Requires scope `companies:write`. # Get a company Source: https://docs.smartlyq.com/api-reference/crm-companies/get-a-company /openapi.json get /companies/{id} The company with every contact linked to it, its deals, and the open/won value rollup across those deals. The rollup is computed from the deals, not stored. Requires scope `companies:read`. # Link a contact to a company Source: https://docs.smartlyq.com/api-reference/crm-companies/link-a-contact-to-a-company /openapi.json post /companies/{id}/contacts A contact belongs to at most one company, so this replaces any previous link. Requires scopes `companies:write` and `contacts:write`. # List companies Source: https://docs.smartlyq.com/api-reference/crm-companies/list-companies /openapi.json get /companies List companies / organisations with their contact counts. Use this to find the `company_id` that the Contacts API accepts. Requires scope `companies:read`. # Unlink a contact from a company Source: https://docs.smartlyq.com/api-reference/crm-companies/unlink-a-contact-from-a-company /openapi.json delete /companies/{id}/contacts Detaches the contact, leaving the contact itself untouched. Returns 404 if the contact is linked to a different company, rather than silently detaching them from that one. Requires scopes `companies:write` and `contacts:write`. # Update a company Source: https://docs.smartlyq.com/api-reference/crm-companies/update-a-company /openapi.json patch /companies/{id} Only the fields you send change. Requires scope `companies:write`. # Add a note to a contact Source: https://docs.smartlyq.com/api-reference/crm-contacts/add-a-note-to-a-contact /openapi.json post /contacts/{id}/notes Requires scope `contacts:write`. # Add tags to a contact Source: https://docs.smartlyq.com/api-reference/crm-contacts/add-tags-to-a-contact /openapi.json post /contacts/{id}/tags Add one or more tags (idempotent union). Each newly added tag fires the tag_added automation event. Requires scope `contacts:write`. # Clear one custom field Source: https://docs.smartlyq.com/api-reference/crm-contacts/clear-one-custom-field /openapi.json delete /contacts/{id}/fields/{slug} Remove a single custom field from a contact by slug. Succeeds (200) even if the field was not set. Requires contacts:write. # Create or upsert a contact Source: https://docs.smartlyq.com/api-reference/crm-contacts/create-or-upsert-a-contact /openapi.json post /contacts Create a CRM contact. With no `id` and a matching `email`, the existing contact is updated instead of duplicated (upsert). Fires the contact_created / contact_updated automation events. Requires scope `contacts:write`. # Enroll a contact in an automation Source: https://docs.smartlyq.com/api-reference/crm-contacts/enroll-a-contact-in-an-automation /openapi.json post /contacts/{id}/enroll Enrol a contact into an active automation. Queues a run for that automation carrying the contact's context. Requires scope `contacts:write`. # Get a contact Source: https://docs.smartlyq.com/api-reference/crm-contacts/get-a-contact /openapi.json get /contacts/{id} Requires scope `contacts:read`. # List contact notes Source: https://docs.smartlyq.com/api-reference/crm-contacts/list-contact-notes /openapi.json get /contacts/{id}/notes Requires scope `contacts:read`. # List contacts Source: https://docs.smartlyq.com/api-reference/crm-contacts/list-contacts /openapi.json get /contacts List CRM contacts in the workspace. Supports `search`, `tag` and `status` filters plus pagination. Requires scope `contacts:read`. # Log a message on a contact's timeline Source: https://docs.smartlyq.com/api-reference/crm-contacts/log-a-message-on-a-contacts-timeline /openapi.json post /contacts/{id}/messages Record an inbound/outbound message in the contact's conversation timeline (feeds the Inbox). Does NOT deliver email/SMS — channel delivery goes through automations/campaigns, which carry billing + compliance. Requires scope `contacts:write`. # Remove tags from a contact Source: https://docs.smartlyq.com/api-reference/crm-contacts/remove-tags-from-a-contact /openapi.json delete /contacts/{id}/tags Remove one or more tags. Each removed tag fires the tag_removed automation event. Requires scope `contacts:write`. # Set one custom field Source: https://docs.smartlyq.com/api-reference/crm-contacts/set-one-custom-field /openapi.json put /contacts/{id}/fields/{slug} Set a single custom field on a contact by its slug (the field's key). The slug must be a defined custom field in this workspace (create one with POST /custom-fields). Merges into the contact's attributes without touching other fields. Requires contacts:write. # Update a contact Source: https://docs.smartlyq.com/api-reference/crm-contacts/update-a-contact /openapi.json patch /contacts/{id} Update a contact by id. Fires the contact_updated automation event (plus tag_added / tag_removed for tag changes). Requires scope `contacts:write`. # Create a custom field Source: https://docs.smartlyq.com/api-reference/crm-custom-fields/create-a-custom-field /openapi.json post /custom-fields Define a new CRM custom field. Its attr_key is derived from the label and is used to set values via the contact's `attributes` object. Requires scope `contacts:write`. # Delete a custom field Source: https://docs.smartlyq.com/api-reference/crm-custom-fields/delete-a-custom-field /openapi.json delete /custom-fields/{id} Requires scope `contacts:write`. # List custom fields Source: https://docs.smartlyq.com/api-reference/crm-custom-fields/list-custom-fields /openapi.json get /custom-fields List CRM custom field (custom attribute) definitions — the schema behind {{attributes.}}. Requires scope `contacts:read`. # Create a pipeline Source: https://docs.smartlyq.com/api-reference/crm-opportunities/create-a-pipeline /openapi.json post /pipelines Requires scope `opportunities:write`. # Create an opportunity Source: https://docs.smartlyq.com/api-reference/crm-opportunities/create-an-opportunity /openapi.json post /opportunities Create a deal/opportunity. Fires deal_created (and deal_won / deal_lost when created with a terminal status). Falls back to the default pipeline when `pipeline_id` is omitted. Requires scope `opportunities:write`. # Delete an opportunity Source: https://docs.smartlyq.com/api-reference/crm-opportunities/delete-an-opportunity /openapi.json delete /opportunities/{id} Requires scope `opportunities:write`. # Get an opportunity Source: https://docs.smartlyq.com/api-reference/crm-opportunities/get-an-opportunity /openapi.json get /opportunities/{id} Requires scope `opportunities:read`. # List opportunities Source: https://docs.smartlyq.com/api-reference/crm-opportunities/list-opportunities /openapi.json get /opportunities List deals/opportunities. Filter by `pipeline_id`, `status`, `stage` or `assigned_user_id`. Requires scope `opportunities:read`. # List pipelines Source: https://docs.smartlyq.com/api-reference/crm-opportunities/list-pipelines /openapi.json get /pipelines List sales pipelines in the workspace, each with its ordered stages. Requires scope `opportunities:read`. # Update an opportunity Source: https://docs.smartlyq.com/api-reference/crm-opportunities/update-an-opportunity /openapi.json patch /opportunities/{id} Update a deal. Moving `stage` follows won/lost stage semantics into `status` unless `status` is set explicitly. Fires deal_stage_changed / deal_won / deal_lost as applicable. Requires scope `opportunities:write`. # Update opportunity status Source: https://docs.smartlyq.com/api-reference/crm-opportunities/update-opportunity-status /openapi.json post /opportunities/{id}/status Set the opportunity outcome. Transitioning into won/lost fires deal_won / deal_lost. Requires scope `opportunities:write`. # Create a tag Source: https://docs.smartlyq.com/api-reference/crm-tags/create-a-tag /openapi.json post /tags Creates a tag attached to no contacts, so it exists to be picked. Not needed in order to tag someone - `POST /contacts/{id}/tags` creates a tag on the fly. Requires scope `contacts:write`. # Delete a tag Source: https://docs.smartlyq.com/api-reference/crm-tags/delete-a-tag /openapi.json post /tags/delete Removes the tag from every contact carrying it. A body operation rather than `DELETE /tags/{name}` because a tag is free text and may contain a slash, which no path segment can carry. The response reports how many automations and segments still reference the tag by name. Those are **not** edited - stripping the last tag from a tag action would leave it failing at runtime - so fix them yourself, or the tag comes back on the next run. Requires scope `contacts:write`. # List tags Source: https://docs.smartlyq.com/api-reference/crm-tags/list-tags /openapi.json get /tags Every contact tag in the workspace with its contact count. Read this before applying tags: tags are free text, so a near-miss creates a second tag that looks identical to a person but matches nothing in an automation or segment. Requires scope `contacts:read`. # Merge tags Source: https://docs.smartlyq.com/api-reference/crm-tags/merge-tags /openapi.json post /tags/merge Folds several tags into one across every contact. Including the target in `from` is a no-op, not an error. Requires scope `contacts:write`. # Rename a tag Source: https://docs.smartlyq.com/api-reference/crm-tags/rename-a-tag /openapi.json post /tags/rename Renames the tag across every contact carrying it, and follows the rename into the automation steps and segments that reference it by name - otherwise the next automation run puts the old name straight back. Tags applied automatically by the calendar (`calendar:*`) cannot be renamed, only removed. Requires scope `contacts:write`. # Create a task Source: https://docs.smartlyq.com/api-reference/crm-tasks/create-a-task /openapi.json post /tasks Creates a task and fires the `task_created` automation event. Set `linked_contact_id` to show it on a contact's record. Requires scope `tasks:write`. # Delete a task Source: https://docs.smartlyq.com/api-reference/crm-tasks/delete-a-task /openapi.json delete /tasks/{id} Requires scope `tasks:write`. # Get a task Source: https://docs.smartlyq.com/api-reference/crm-tasks/get-a-task /openapi.json get /tasks/{id} Requires scope `tasks:read`. # List tasks Source: https://docs.smartlyq.com/api-reference/crm-tasks/list-tasks /openapi.json get /tasks List CRM tasks and activities. Combine `status=todo` with `due_before=` to get what is overdue. Requires scope `tasks:read`. # Log time on a task Source: https://docs.smartlyq.com/api-reference/crm-tasks/log-time-on-a-task /openapi.json post /tasks/{id}/time Adds to the task's running tracked total rather than replacing it. Requires scope `tasks:write`. # Update a task Source: https://docs.smartlyq.com/api-reference/crm-tasks/update-a-task /openapi.json patch /tasks/{id} Moving a task to `done` for the first time fires the `task_completed` automation event. Mark work done here rather than deleting the task, or that event never fires. Requires scope `tasks:write`. # Bulk import contacts Source: https://docs.smartlyq.com/api-reference/crm/bulk-import-contacts /openapi.json post /contacts/bulk Import up to 100 contacts in one call. Each row goes through the same email/phone upsert as POST /contacts, so re-importing the same file is idempotent. Per-row results report created / updated / failed. Requires scope `contacts:write`. # Contact channels Source: https://docs.smartlyq.com/api-reference/crm/contact-channels /openapi.json get /contacts/{id}/channels The channels this contact has actually communicated on (from the conversation timeline), with per-channel volume and last activity - pick the right channel before reaching out. Requires scope `contacts:read`. # Delete contact Source: https://docs.smartlyq.com/api-reference/crm/delete-contact /openapi.json delete /contacts/{id} Soft-deletes the contact (recoverable from Deleted contacts in the app). Requires scope `contacts:write`. # Update custom field Source: https://docs.smartlyq.com/api-reference/crm/update-custom-field /openapi.json patch /custom-fields/{id} Updates the definition (label, type, options, default, group, sort order). attr_key is immutable - it names the values already stored on contacts. Requires scope `contacts:write`. # Delete a sent message Source: https://docs.smartlyq.com/api-reference/direct-messages/delete-a-sent-message /openapi.json delete /social/conversations/{conversation_id}/messages/{message_id} Delete a message you sent. X and Bluesky only - Messenger, Instagram, and WhatsApp have no unsend API for a business-sent message. On Bluesky this deletes for YOUR side of the conversation only (their chat API has no unsend-for-everyone) - a successful response's `note` says so explicitly when it applies. Requires scope `social:write`. # List DM conversations Source: https://docs.smartlyq.com/api-reference/direct-messages/list-dm-conversations /openapi.json get /social/conversations Requires scope `social:read`. # List messages in a conversation Source: https://docs.smartlyq.com/api-reference/direct-messages/list-messages-in-a-conversation /openapi.json get /social/conversations/{conversation_id}/messages Requires scope `social:read`. # Mark a conversation read Source: https://docs.smartlyq.com/api-reference/direct-messages/mark-a-conversation-read /openapi.json post /social/conversations/{conversation_id}/read Requires scope `social:write`. # React to a message Source: https://docs.smartlyq.com/api-reference/direct-messages/react-to-a-message /openapi.json post /social/conversations/{conversation_id}/messages/{message_id}/reactions React to a message as the connected account. Sending a different emoji replaces the previous reaction. Supported on Messenger, Instagram, Bluesky and WhatsApp. Other platforms return an honest error rather than silently doing nothing. Requires scope `social:write`. # Remove a message reaction Source: https://docs.smartlyq.com/api-reference/direct-messages/remove-a-message-reaction /openapi.json delete /social/conversations/{conversation_id}/messages/{message_id}/reactions Clear the reaction this account previously sent on a message. Supported on Messenger, Instagram, Bluesky and WhatsApp. Other platforms return an honest error rather than silently doing nothing. Requires scope `social:write`. # Send a direct message Source: https://docs.smartlyq.com/api-reference/direct-messages/send-a-direct-message /openapi.json post /social/conversations/{conversation_id}/messages Supports structured payloads: buttons (Facebook) and quick_replies (Facebook + Instagram). Requires scope `social:write`. # Delete image Source: https://docs.smartlyq.com/api-reference/images/delete-image /openapi.json delete /images/{image_id} Requires scope `images:write`. # Generate image Source: https://docs.smartlyq.com/api-reference/images/generate-image /openapi.json post /images/generate Returns 202 with job_id for async generation. Either `prompt` or `description` must be provided. Requires scope `images:write`. # Get image Source: https://docs.smartlyq.com/api-reference/images/get-image /openapi.json get /images/{image_id} Requires scope `images:read`. # List images Source: https://docs.smartlyq.com/api-reference/images/list-images /openapi.json get /images Requires scope `images:read`. # Cancel job Source: https://docs.smartlyq.com/api-reference/jobs/cancel-job /openapi.json post /jobs/{job_id}/cancel Cancels a queued or processing job. The request body may be empty, but `Content-Type: application/json` **must** still be set — omitting it returns a 400 error. Requires scope `jobs:read`. # Get job Source: https://docs.smartlyq.com/api-reference/jobs/get-job /openapi.json get /jobs/{job_id} Requires scope `jobs:read`. # List jobs Source: https://docs.smartlyq.com/api-reference/jobs/list-jobs /openapi.json get /jobs Requires scope `jobs:read`. # List developer logs Source: https://docs.smartlyq.com/api-reference/logs/list-developer-logs /openapi.json get /logs Your workspace's own request history: API calls and webhook delivery attempts merged into one time-ordered feed, newest first, with 90-day retention. `source` tells the two apart and decides which fields are populated - API rows carry method/endpoint/timing/cost, webhook rows carry event/attempt/status. Request and response bodies are omitted unless you pass `include_payloads=true`: they contain whatever was sent, so they are opt-in rather than returned in every page. Requires scope `logs:read`. # Confirm a presigned upload Source: https://docs.smartlyq.com/api-reference/media/confirm-a-presigned-upload /openapi.json post /media/{media_id}/confirm Call this after PUTting your file to the presigned URL from POST /media/upload-url. HEADs the object in storage and backfills the real file_size (and mime_type, if the client's PUT Content-Type disagreed with what was declared at presign time) - the presign flow has no other completion signal. 422 NOT_UPLOADED if nothing has landed at the key yet. Requires scope `media:write`. # Delete media Source: https://docs.smartlyq.com/api-reference/media/delete-media /openapi.json delete /media/{media_id} Requires scope `media:write`. # Get media Source: https://docs.smartlyq.com/api-reference/media/get-media /openapi.json get /media/{media_id} Requires scope `media:read`. # Get presigned upload URL Source: https://docs.smartlyq.com/api-reference/media/get-presigned-upload-url /openapi.json post /media/upload-url Returns a presigned URL - PUT your file's bytes directly there (Content-Type must match what you declared here). The returned media_id has file_size=0 until you call POST /media/{media_id}/confirm afterward; there's no other way this API learns the upload completed. For most callers POST /media/upload-direct is simpler (one request, no separate confirm step) - use presign for large files or when you want the client to upload directly to storage instead of through this API. Requires scope `media:write`. # List media Source: https://docs.smartlyq.com/api-reference/media/list-media /openapi.json get /media Requires scope `media:read`. # Upload a file directly Source: https://docs.smartlyq.com/api-reference/media/upload-a-file-directly /openapi.json post /media/upload-direct Upload a file in one multipart/form-data request - no presign step. The file is stored and recorded in your media library, and the response is the same shape as GET /media/{id}. The file type is detected from the bytes (not the declared Content-Type); max 200MB (upload larger media by URL). Requires `media:write`. # Delete presentation Source: https://docs.smartlyq.com/api-reference/presentations/delete-presentation /openapi.json delete /presentations/{presentation_id} Permanently deletes a presentation and removes it from the media library. Requires the `presentations:write` scope. # Generate presentation Source: https://docs.smartlyq.com/api-reference/presentations/generate-presentation /openapi.json post /presentations/generate Generates a complete AI presentation (slide deck) from a single prompt. Returns `202 Accepted` immediately with a `job_id`; poll `GET /v1/jobs/{job_uid}` for status. Generation typically takes 60-120 seconds. When the job completes, its `result` contains the `presentation_id` and a `url` that opens the finished deck in the presentations editor. **Billing:** a flat charge per generated deck is deducted from your API wallet when the job is accepted. If generation fails the charge is automatically refunded. Requires the `presentations:write` scope. # Get presentation Source: https://docs.smartlyq.com/api-reference/presentations/get-presentation /openapi.json get /presentations/{presentation_id} Returns a presentation's details including its editor URL and, when public, its share URL. Requires the `presentations:read` scope. Read-only - consumes no credits. # List presentations Source: https://docs.smartlyq.com/api-reference/presentations/list-presentations /openapi.json get /presentations Lists the authenticated user's presentations, newest first. Requires the `presentations:read` scope. Read-only - consumes no credits. # Account billing summary Source: https://docs.smartlyq.com/api-reference/profiles/account-billing-summary /openapi.json get /me/account-billing Live count of billable connected accounts across your profiles, the price bands, the estimated next monthly charge, and recent billing history. Accounts are billed per calendar month from your API wallet: \$6 each for accounts 1-10, \$3 for 11-100, \$1 from 101 up, snapshotted on the 1st (UTC). Requires the profiles:read scope. # Create a hosted connect link Source: https://docs.smartlyq.com/api-reference/profiles/create-a-hosted-connect-link /openapi.json post /profiles/{id}/connect-link Mints a signed, single-use hosted page URL you forward to your end-customer. They open it with no login, pick platforms, and approve access; connected accounts land in this profile. The link dies for other browsers once opened. Requires the profiles:write scope. # Create a profile Source: https://docs.smartlyq.com/api-reference/profiles/create-a-profile /openapi.json post /profiles Create a profile - a managed sub-account for one end-customer of your product. Connected accounts inside profiles are billed monthly per account from your API wallet (no plan caps). Requires the profiles:write scope. # Delete a profile Source: https://docs.smartlyq.com/api-reference/profiles/delete-a-profile /openapi.json delete /profiles/{id} Soft-deletes the profile and schedules async teardown of its workspace and connected accounts. Send {"confirm": "DELETE"} in the body. Requires the profiles:write scope. # Get a profile Source: https://docs.smartlyq.com/api-reference/profiles/get-a-profile /openapi.json get /profiles/{id} Requires scope `profiles:read`. # Get a raw connect URL for one platform Source: https://docs.smartlyq.com/api-reference/profiles/get-a-raw-connect-url-for-one-platform /openapi.json post /profiles/{id}/connect/{platform} Per-platform OAuth connect URL targeting this profile, for developers building their own connect UI. Same mechanics as POST /social/connect/{platform}. Requires the profiles:write scope. # List a profile's connected accounts Source: https://docs.smartlyq.com/api-reference/profiles/list-a-profiles-connected-accounts /openapi.json get /profiles/{id}/accounts The social accounts the end-customer has connected to this profile. Requires the profiles:read scope. # List profiles Source: https://docs.smartlyq.com/api-reference/profiles/list-profiles /openapi.json get /profiles List your profiles (newest first). Requires the profiles:read scope. # Pause a profile Source: https://docs.smartlyq.com/api-reference/profiles/pause-a-profile /openapi.json post /profiles/{id}/pause Pause a profile: its workspace is suspended, delegated calls return 409, and its accounts stop counting toward monthly account billing. Requires the profiles:write scope. # Resume a profile Source: https://docs.smartlyq.com/api-reference/profiles/resume-a-profile /openapi.json post /profiles/{id}/resume Resume a paused profile. Requires the profiles:write scope. # Update a profile Source: https://docs.smartlyq.com/api-reference/profiles/update-a-profile /openapi.json patch /profiles/{id} Rename a profile and/or change its external_id. Only the fields you send are modified; renaming also renames the profile's backing workspace so the two never drift. Requires `profiles:write`. # Delete review reply Source: https://docs.smartlyq.com/api-reference/reviews/delete-review-reply /openapi.json delete /reviews/{review_id}/reply Removes the business reply from the review on Google Business and clears it in SmartlyQ (status returns to pending). Requires scope `social:write`. # List reviews Source: https://docs.smartlyq.com/api-reference/reviews/list-reviews /openapi.json get /reviews Google Business reviews synced into SmartlyQ's Reputation Management, newest first. Filter by rating, reply status, or account. Requires scope `social:read`. # Reply to review Source: https://docs.smartlyq.com/api-reference/reviews/reply-to-review /openapi.json post /reviews/{review_id}/reply Publishes a reply to the review on Google Business. Requires scope `social:write`. # Sync reviews Source: https://docs.smartlyq.com/api-reference/reviews/sync-reviews /openapi.json post /reviews/sync Pulls fresh reviews from Google Business on demand - one account (account_id) or every connected GBP account in the workspace. Requires scope `social:write`. # AI Visibility Audit (async) Source: https://docs.smartlyq.com/api-reference/seo/ai-visibility-audit-async /openapi.json post /seo/ai-audit Zero-input GEO audit: give a website URL and the audit auto-generates realistic customer queries in the market's language (or accepts your own), runs every query across ChatGPT, Claude, Gemini and Perplexity, and scores unaided brand presence. The result contains an AI Presence Score (0-100 = share of query x model checks whose answer mentions the brand or domain), per-query mention results, and the competitors AI recommends instead. Long-running (typically 5-15 minutes): returns 202 with a job_id - poll GET /jobs/{job_id}. Billed per (query x model) check. Requires scope `seo:read`. # AI Visibility: brand lookup Source: https://docs.smartlyq.com/api-reference/seo/ai-visibility:-brand-lookup /openapi.json post /seo/brand-lookup AI Visibility brand/domain citation lookup. Async: returns 202 + job_uid - poll GET /jobs/{job_uid} for the result (may take ~1 minute). How often ChatGPT and Google AI Overview cite a brand, domain or keyword: total mentions, AI search volume, estimated impressions (split by platform), the citing prompts, cited source domains, related pages and a 12-month trend. Requires scope `seo:read`. # AI Visibility: prompt explorer Source: https://docs.smartlyq.com/api-reference/seo/ai-visibility:-prompt-explorer /openapi.json post /seo/prompt-explorer Run a prompt across ChatGPT/Claude/Gemini/Perplexity. Async: returns 202 + job_uid - poll GET /jobs/{job_uid} for the result (1-2 minutes). Run one prompt across ChatGPT, Claude, Gemini and Perplexity side-by-side. Returns each model's answer text, citations and fan-out queries; pass `brand` to get a mentions_brand flag per answer. Requires scope `seo:read`. # Backlink anchors Source: https://docs.smartlyq.com/api-reference/seo/backlink-anchors /openapi.json post /seo/backlink-anchors List the top backlink anchor texts for a domain, with backlink and referring-domain counts. Requires scope `seo:read`. # Backlink profile summary Source: https://docs.smartlyq.com/api-reference/seo/backlink-profile-summary /openapi.json post /seo/backlinks-summary Backlink profile summary for a domain: authority rank, totals, referring domains, broken links and link types. Requires scope `seo:read`. # Backlink prospects (link gap) Source: https://docs.smartlyq.com/api-reference/seo/backlink-prospects-link-gap /openapi.json post /seo/backlink-prospects Find link-building prospects: referring domains that link to competitors of the target but not to the target itself. Requires scope `seo:read`. # Backlink spam score Source: https://docs.smartlyq.com/api-reference/seo/backlink-spam-score /openapi.json post /seo/spam-score Get the backlink spam/toxicity score (0-100) for one or more domains. Requires scope `seo:read`. # Deep site audit Source: https://docs.smartlyq.com/api-reference/seo/deep-site-audit /openapi.json post /seo/site-audit Deep crawl-based technical site audit. Async: returns 202 + job_uid - poll GET /jobs/{job_uid}; result_data carries the audit (typically 30-90s). Crawl-based technical SEO audit: overall on-page score, crawl issues, and the worst-scoring pages with their flagged checks. Slower (~10-15s). Requires scope `seo:read`. # Domain rank overview Source: https://docs.smartlyq.com/api-reference/seo/domain-rank-overview /openapi.json post /seo/domain-overview Organic rank overview for a domain: estimated keyword count, traffic value and position buckets. Requires scope `seo:read`. # Historical rank overview Source: https://docs.smartlyq.com/api-reference/seo/historical-rank-overview /openapi.json post /seo/rank-history Historical organic rank/traffic trend by month. Premium lookup. Async: returns 202 + job_uid - poll GET /jobs/{job_uid} for the result. Historical organic keyword count and traffic (ETV) trend by month. Premium lookup (priced ~10x a normal SEO call). Requires scope `seo:read`. # Keyword difficulty Source: https://docs.smartlyq.com/api-reference/seo/keyword-difficulty /openapi.json post /seo/keyword-difficulty Bulk keyword difficulty scores (0-100) for a list of keywords. Requires scope `seo:read`. # Keyword research Source: https://docs.smartlyq.com/api-reference/seo/keyword-research /openapi.json post /seo/keyword-research Keyword suggestions for a seed keyword with real monthly search volume, CPC and competition. Requires scope `seo:read`. # Live SERP lookup Source: https://docs.smartlyq.com/api-reference/seo/live-serp-lookup /openapi.json post /seo/serp Live Google organic SERP for a keyword: ranked results, SERP features and People Also Ask. Requires scope `seo:read`. # On-page SEO audit Source: https://docs.smartlyq.com/api-reference/seo/on-page-seo-audit /openapi.json post /seo/audit Instant on-page SEO audit for a single URL: on-page score and flagged checks. Requires scope `seo:read`. # Organic competitors Source: https://docs.smartlyq.com/api-reference/seo/organic-competitors /openapi.json post /seo/competitors Domains overlapping a target on shared keywords, with common-keyword counts and traffic. Requires scope `seo:read`. # Ranked keywords (rank tracking) Source: https://docs.smartlyq.com/api-reference/seo/ranked-keywords-rank-tracking /openapi.json post /seo/ranked-keywords Every keyword a domain ranks for, with position, search volume and estimated traffic. Requires scope `seo:read`. # Referring domains Source: https://docs.smartlyq.com/api-reference/seo/referring-domains /openapi.json post /seo/referring-domains List the top referring domains for a domain by authority, with backlink counts, first-seen date and dofollow counts. Requires scope `seo:read`. # Generate viral shorts from a long video Source: https://docs.smartlyq.com/api-reference/shorts/generate-viral-shorts-from-a-long-video /openapi.json post /shorts/generate Turns one long video into ranked, ready-to-post vertical (9:16) clips with hooks + virality scores. Provide `video_url` (direct media) OR `source_url` (a YouTube/Vimeo/TikTok link). Returns `202 Accepted` with a `job_uid`; poll `GET /v1/shorts/{uid}`. Billed per REQUESTED clip from your API wallet; a wholly-failed job is refunded automatically. Requires scope `videos:write`. # Get shorts job + clips Source: https://docs.smartlyq.com/api-reference/shorts/get-shorts-job-+-clips /openapi.json get /shorts/{uid} Gets a Magic Shorts job's status and its ranked clips (hooks, scores, clip URLs). Poll after `generateShorts` until `status` is `completed`. Requires scope `videos:read`. # List shorts jobs Source: https://docs.smartlyq.com/api-reference/shorts/list-shorts-jobs /openapi.json get /shorts Lists your workspace's Magic Shorts jobs. Requires scope `videos:read`. # Account health Source: https://docs.smartlyq.com/api-reference/social/account-health /openapi.json get /social/accounts/{account_id}/health Requires scope `social:read`. # Account reconnect URL Source: https://docs.smartlyq.com/api-reference/social/account-reconnect-url /openapi.json get /social/accounts/{account_id}/reconnect-url Requires scope `social:read`. # Add photo Source: https://docs.smartlyq.com/api-reference/social/add-photo /openapi.json post /social/accounts/{account_id}/gmb/media Publishes a photo to the Business Profile from a public URL. Requires a connected Google Business account; bodies/responses are honest passthroughs of Google's resource shapes, so every Google field works. Requires scope `social:write`. # Archive / reopen conversation Source: https://docs.smartlyq.com/api-reference/social/archive-reopen-conversation /openapi.json patch /social/conversations/{conversation_id} Set the conversation status to archived or open. Requires scope `social:write`. # Available attributes Source: https://docs.smartlyq.com/api-reference/social/available-attributes /openapi.json get /social/accounts/{account_id}/gmb/attributes/metadata Which attributes Google allows for THIS location's category, with display names and value types - fetch before updating attributes. Requires a connected Google Business account; bodies/responses are honest passthroughs of Google's resource shapes, so every Google field works. Requires scope `social:read`. # Bulk account health Source: https://docs.smartlyq.com/api-reference/social/bulk-account-health /openapi.json get /social/accounts/health Health of every connected account in one call: healthy / expiring (token within 7 days) / expired / paused tallies plus per-account status. Same fields as the per-account health endpoint. Requires scope `social:read`. # Bulk schedule posts Source: https://docs.smartlyq.com/api-reference/social/bulk-schedule-posts /openapi.json post /social/posts/bulk Schedule up to 50 posts in one call, from a JSON `posts` array or an inline `csv` string. Billed one post unit per row x platform. Any structural problem (missing fields, bad dates, in-batch duplicate rows) rejects the WHOLE batch with 422 before anything is created - and the charge is refunded. After that gate, row-level failures (e.g. a disconnected account, a 24h duplicate) are reported per row in the 201 response. Run the free dry-run first: POST /social/posts/bulk/validate. Requires scope `social:write`. # Connection target options Source: https://docs.smartlyq.com/api-reference/social/connection-target-options /openapi.json get /social/accounts/{account_id}/connect-options Which entity this connection is bound to and, where the stored token allows, the alternatives it could be re-pointed at. Google Business tokens are user-level, so every location under the parent account is selectable; platforms with entity-bound tokens (e.g. Facebook page tokens) report supported: false with the reason - switching those requires reconnecting. Requires scope `social:read`. # Create a Pinterest board Source: https://docs.smartlyq.com/api-reference/social/create-a-pinterest-board /openapi.json post /social/accounts/{account_id}/pinterest/boards Create a board to pin into. Listing the existing boards is the GET on this same path. Requires scope `social:write`. If the account was connected before SmartlyQ requested the permission this call needs, it answers `403 CONNECTION_SCOPE_MISSING` with `reconnect_required: true` - reconnect the account to fix it. # Create account group Source: https://docs.smartlyq.com/api-reference/social/create-account-group /openapi.json post /social/account-groups Create a group (max 50 per workspace, 100 accounts per group). Every account id must be a connected account of this workspace. Requires scope `social:write`. # Create place-action link Source: https://docs.smartlyq.com/api-reference/social/create-place-action-link /openapi.json post /social/accounts/{account_id}/gmb/place-actions Adds an action link (APPOINTMENT, ORDER, RESERVATION, MENU, BOOK_ONLINE...). Requires a connected Google Business account; bodies/responses are honest passthroughs of Google's resource shapes, so every Google field works. Requires scope `social:write`. # Create post (publish immediately) Source: https://docs.smartlyq.com/api-reference/social/create-post-publish-immediately /openapi.json post /social/posts Facebook allows 30 API-published Reels per Page in a moving 24-hour window. A post whose `platform_options.facebook.type` is `reel` is refused when a selected Page is already at that limit (Reels published in the last 24 hours plus Reels already scheduled inside that window): `422 PLATFORM_DAILY_LIMIT_REACHED`, with `details` carrying `account_id`, `limit`, `used` and `next_available_at`. Nothing is created. Check first with `GET /social/accounts/{account_id}/facebook/reel-limit`. Requires scope `social:write`. # Create queue Source: https://docs.smartlyq.com/api-reference/social/create-queue /openapi.json post /social/queues Create a posting queue. Posts join it via `POST /social/posts/schedule` with `queue_id`; each post takes the next open slot (FIFO). Max 20 queues per workspace. Requires scope `social:write`. # Delete account group Source: https://docs.smartlyq.com/api-reference/social/delete-account-group /openapi.json delete /social/account-groups/{group_id} Deletes the group only - the accounts stay connected. Requires scope `social:write`. # Delete ice breakers Source: https://docs.smartlyq.com/api-reference/social/delete-ice-breakers /openapi.json delete /social/accounts/{account_id}/instagram/ice-breakers Requires scope `social:write`. # Delete media Source: https://docs.smartlyq.com/api-reference/social/delete-media /openapi.json delete /social/accounts/{account_id}/gmb/media Removes a media item; name must be a media resource of this location. Requires a connected Google Business account; bodies/responses are honest passthroughs of Google's resource shapes, so every Google field works. Requires scope `social:write`. # Delete Messenger menu Source: https://docs.smartlyq.com/api-reference/social/delete-messenger-menu /openapi.json delete /social/accounts/{account_id}/messenger/menu Requires scope `social:write`. # Delete place-action link Source: https://docs.smartlyq.com/api-reference/social/delete-place-action-link /openapi.json delete /social/accounts/{account_id}/gmb/place-actions Removes an action link; name must belong to this location. Requires a connected Google Business account; bodies/responses are honest passthroughs of Google's resource shapes, so every Google field works. Requires scope `social:write`. # Delete queue Source: https://docs.smartlyq.com/api-reference/social/delete-queue /openapi.json delete /social/queues/{queue_id} Delete a queue. Posts already scheduled through it keep their resolved times. Requires scope `social:write`. # Delete social post Source: https://docs.smartlyq.com/api-reference/social/delete-social-post /openapi.json delete /social/posts/{post_id} Requires scope `social:write`. # Disconnect a social account Source: https://docs.smartlyq.com/api-reference/social/disconnect-a-social-account /openapi.json delete /social/accounts/{account_id} Requires scope `social:write`. # Edit published post Source: https://docs.smartlyq.com/api-reference/social/edit-published-post /openapi.json post /social/posts/{post_id}/edit Edits a post that has ALREADY been published, natively on the platform. Honest per-platform support: Facebook page posts work today; X's edit API is paid-tier-only and Reddit's needs a re-auth scope - those report unsupported per platform instead of failing the call. The SmartlyQ record keeps its original text for multi-platform posts. Requires scope `social:write`. # Facebook page insights Source: https://docs.smartlyq.com/api-reference/social/facebook-page-insights /openapi.json get /social/accounts/{account_id}/facebook/page-insights Metric passthrough to Meta's Page Insights: request any page metric by name (Meta validates, so new metrics work without waiting for us). Defaults to impressions, engagements and fans. Requires scope `analytics:read`. # Facebook post reactions Source: https://docs.smartlyq.com/api-reference/social/facebook-post-reactions /openapi.json get /social/accounts/{account_id}/facebook/post-reactions Reaction-type breakdown (like, love, wow, haha, sad, angry, care) for a published Facebook post. Pass remote_post_id, or post_id of a SmartlyQ post published to this account. Requires scope `social:read`. # Follower stats Source: https://docs.smartlyq.com/api-reference/social/follower-stats /openapi.json get /social/accounts/follower-stats Daily follower history and growth per account, from the analytics snapshots the platform collects. Requires scope `social:read`. # Get account group Source: https://docs.smartlyq.com/api-reference/social/get-account-group /openapi.json get /social/account-groups/{group_id} Requires scope `social:read`. # Get attributes Source: https://docs.smartlyq.com/api-reference/social/get-attributes /openapi.json get /social/accounts/{account_id}/gmb/attributes The attributes currently set on the location (Wi-Fi, outdoor seating, wheelchair access, ...). Requires a connected Google Business account; bodies/responses are honest passthroughs of Google's resource shapes, so every Google field works. Requires scope `social:read`. # Get business info Source: https://docs.smartlyq.com/api-reference/social/get-business-info /openapi.json get /social/accounts/{account_id}/gmb/location The location's full business information: hours, phone numbers, categories, service area, profile description, service items and more. Override fields with ?read_mask=. Requires a connected Google Business account; bodies/responses are honest passthroughs of Google's resource shapes, so every Google field works. Requires scope `social:read`. # Get conversation Source: https://docs.smartlyq.com/api-reference/social/get-conversation /openapi.json get /social/conversations/{conversation_id} Conversation detail plus incoming/outgoing message counts. Requires scope `social:read`. # Get Facebook page details Source: https://docs.smartlyq.com/api-reference/social/get-facebook-page-details /openapi.json get /social/accounts/{account_id}/facebook/page The page's own profile - name, about, description, category, contact details, address, opening hours, follower counts and publish state. These are the fields a management screen shows and edits; the performance numbers live on `/facebook/page-insights`. Requires scope `social:read`. If the account was connected before SmartlyQ requested the permission this call needs, it answers `403 CONNECTION_SCOPE_MISSING` with `reconnect_required: true` - reconnect the account to fix it. # Get Facebook page settings Source: https://docs.smartlyq.com/api-reference/social/get-facebook-page-settings /openapi.json get /social/accounts/{account_id}/facebook/page/settings Read whether people can message the Page. `users_can_message` is `true` when the Page's Message button is on, `false` when messaging is turned off, and `null` when Facebook did not report the setting. With a test key (`sqk_test_...`) the request is validated and a success response is returned without contacting Facebook. Requires scope `social:read`. If the account was connected before SmartlyQ requested the permission this call needs, it answers `403 CONNECTION_SCOPE_MISSING` with `reconnect_required: true` - reconnect the account to fix it. # Get Facebook Reel limit Source: https://docs.smartlyq.com/api-reference/social/get-facebook-reel-limit /openapi.json get /social/accounts/{account_id}/facebook/reel-limit Facebook allows 30 API-published Reels per Page in a moving 24-hour window. This returns how much of that allowance the Page has left. `used` counts Reels published for this Page through the API or the app in the last 24 hours, plus Reels already scheduled for it inside that window. Reels posted to the Page outside this platform are not counted, so Facebook can still refuse a Reel when `remaining` is above 0. `next_available_at` is set only when the Page is at the limit, and is when one more Reel fits. Facebook accounts only - any other account answers `422 VALIDATION_ERROR`. Requires scope `social:read`. # Get food menus Source: https://docs.smartlyq.com/api-reference/social/get-food-menus /openapi.json get /social/accounts/{account_id}/gmb/food-menus The location's structured food menus (sections, items, prices). ONLY available for food-serving business categories (restaurants, cafes, bakeries...) - Google returns a precondition error for other business types, which we pass through as a clear 502. Requires a connected Google Business account; bodies/responses are honest passthroughs of Google's resource shapes, so every Google field works. Requires scope `social:read`. # Get ice breakers Source: https://docs.smartlyq.com/api-reference/social/get-ice-breakers /openapi.json get /social/accounts/{account_id}/instagram/ice-breakers The Instagram account's DM ice breakers (the FAQ chips new visitors see). Requires scope `social:read`. # Get Messenger menu Source: https://docs.smartlyq.com/api-reference/social/get-messenger-menu /openapi.json get /social/accounts/{account_id}/messenger/menu The Facebook page's persistent menu (Meta messenger_profile shape). Requires scope `social:read`. # Get next open slot Source: https://docs.smartlyq.com/api-reference/social/get-next-open-slot /openapi.json get /social/queues/{queue_id}/next-slot The next unoccupied slot in the queue (UTC). For preview only - to actually schedule into the queue, pass `queue_id` to `POST /social/posts/schedule` (that call resolves and occupies the slot atomically). Requires scope `social:read`. # Get queue Source: https://docs.smartlyq.com/api-reference/social/get-queue /openapi.json get /social/queues/{queue_id} Fetch one queue, including its next open slot. Requires scope `social:read`. # Get social post Source: https://docs.smartlyq.com/api-reference/social/get-social-post /openapi.json get /social/posts/{post_id} Requires scope `social:read`. # Google Business performance Source: https://docs.smartlyq.com/api-reference/social/google-business-performance /openapi.json get /social/accounts/{account_id}/gmb/performance Business Profile performance for the window: impressions by surface (Search/Maps, mobile/desktop) with deltas vs the previous window, plus a daily series. Data lags ~1 day (Google). Requires scope `social:read`. # Google Business search keywords Source: https://docs.smartlyq.com/api-reference/social/google-business-search-keywords /openapi.json get /social/accounts/{account_id}/gmb/search-keywords The search terms people used on Google before seeing this Business Profile, with monthly impression counts. Requires scope `social:read`. # Instagram audience demographics Source: https://docs.smartlyq.com/api-reference/social/instagram-audience-demographics /openapi.json get /social/accounts/{account_id}/instagram/audience Follower demographics by city, country, age or gender (IG follower_demographics insights). Instagram requires 100+ followers and hides small buckets - its errors are surfaced verbatim. Requires scope `analytics:read`. # Instagram publishing limit Source: https://docs.smartlyq.com/api-reference/social/instagram-publishing-limit /openapi.json get /social/accounts/{account_id}/instagram/publishing-limit Instagram's daily content-publishing quota for this account: total, remaining, and whether a publish would be allowed right now. Requires scope `social:read`. # Instagram stories Source: https://docs.smartlyq.com/api-reference/social/instagram-stories /openapi.json get /social/accounts/{account_id}/instagram/stories The account's currently live stories (id, media type, URL, permalink, timestamp). Requires scope `social:read`. # Instagram story insights Source: https://docs.smartlyq.com/api-reference/social/instagram-story-insights /openapi.json get /social/accounts/{account_id}/instagram/stories/{story_id}/insights Insights for one story. Defaults to views and replies; pass ?metrics= (comma-separated) to request other metrics - Instagram validates them, so newly released metrics work without waiting for us. Requires scope `analytics:read`. # List account groups Source: https://docs.smartlyq.com/api-reference/social/list-account-groups /openapi.json get /social/account-groups Named sets of connected accounts, so integrations can address a whole client or brand with one id. Requires scope `social:read`. # List Google locations Source: https://docs.smartlyq.com/api-reference/social/list-google-locations /openapi.json get /social/accounts/{account_id}/gmb/locations Every location under the connected profile's parent Google account (multi-location businesses). Override fields with ?read_mask=. Requires a connected Google Business account; bodies/responses are honest passthroughs of Google's resource shapes, so every Google field works. Requires scope `social:read`. # List media Source: https://docs.smartlyq.com/api-reference/social/list-media /openapi.json get /social/accounts/{account_id}/gmb/media Photos/videos on the Business Profile with view counts and Google-hosted URLs. Requires a connected Google Business account; bodies/responses are honest passthroughs of Google's resource shapes, so every Google field works. Requires scope `social:read`. # List mentions Source: https://docs.smartlyq.com/api-reference/social/list-mentions /openapi.json get /social/accounts/{account_id}/mentions Posts where this account was tagged. Facebook pages return posts they were tagged in; Instagram returns media the account was @-mentioned in. X has its own richer endpoint at `/social/accounts/{account_id}/x/mentions`. Requires scope `social:read`. If the account was connected before SmartlyQ requested the permission this call needs, it answers `403 CONNECTION_SCOPE_MISSING` with `reconnect_required: true` - reconnect the account to fix it. # List place-action links Source: https://docs.smartlyq.com/api-reference/social/list-place-action-links /openapi.json get /social/accounts/{account_id}/gmb/place-actions The location's action links shown on Google (order, reserve, appointment, menu...). Requires a connected Google Business account; bodies/responses are honest passthroughs of Google's resource shapes, so every Google field works. Requires scope `social:read`. # List queues Source: https://docs.smartlyq.com/api-reference/social/list-queues /openapi.json get /social/queues List the workspace's posting queues (recurring weekly slot schedules). Requires scope `social:read`. # List social accounts Source: https://docs.smartlyq.com/api-reference/social/list-social-accounts /openapi.json get /social/accounts Returns all connected social media accounts for the authenticated user. Requires scope `social:read`. # List social posts Source: https://docs.smartlyq.com/api-reference/social/list-social-posts /openapi.json get /social/posts Posts imported by external sync carry status 'external' - filter with ?status=external. Requires scope `social:read`. # List subreddit post flairs Source: https://docs.smartlyq.com/api-reference/social/list-subreddit-post-flairs /openapi.json get /social/accounts/{account_id}/reddit/subreddits/{subreddit}/flairs The post flairs a subreddit offers. Many subreddits reject a submission that has no flair, so read this first and pass the id back as `platform_options.reddit.flair_id` when you post. Subreddits that expose no flairs, or restrict them to moderators, return an empty list - that is a normal answer, not a failure. Requires scope `social:read`. # List verifications Source: https://docs.smartlyq.com/api-reference/social/list-verifications /openapi.json get /social/accounts/{account_id}/gmb/verifications Past and pending verification attempts for the location. Requires a connected Google Business account; bodies/responses are honest passthroughs of Google's resource shapes, so every Google field works. Requires scope `social:read`. # Live account insights Source: https://docs.smartlyq.com/api-reference/social/live-account-insights /openapi.json get /social/accounts/{account_id}/insights LIVE account metrics straight from the platform (followers, following, posts, profile views, impressions, reach, engagements, new followers - whatever the platform exposes), via the same per-platform providers the daily analytics sync uses. Works for every connected platform with an analytics provider. Requires scope `analytics:read`. # Move account to profile Source: https://docs.smartlyq.com/api-reference/social/move-account-to-profile /openapi.json post /social/accounts/{account_id}/move Move a connected account between your developer workspace and one of your profiles (or profile to profile). Pass profile_id 0 to move it back to the developer workspace. Must be called WITHOUT X-Profile-Id; requires scopes `social:write` AND `profiles:manage`. Rejected with 409 while the account has scheduled or in-flight posts. # Pause posting to an account Source: https://docs.smartlyq.com/api-reference/social/pause-posting-to-an-account /openapi.json post /social/accounts/{account_id}/pause Requires scope `social:write`. # Pinterest boards Source: https://docs.smartlyq.com/api-reference/social/pinterest-boards /openapi.json get /social/accounts/{account_id}/pinterest/boards The account's Pinterest boards (id, name, privacy) - required to target a board when pinning. Requires scope `social:read`. # Poll headless connection status Source: https://docs.smartlyq.com/api-reference/social/poll-headless-connection-status /openapi.json get /social/connect/{platform} Poll whether a headless connection finished, using the `state_token` embedded in the `connect_url` returned by `POST /social/connect/{platform}`. Useful when your app can't observe the `return_url` redirect (server-to-server, or the user closed the tab). Verifies the signed token and reports `connected` (with the new account), `pending`, or `expired`. Requires scope `social:read`. # Preview upcoming slots Source: https://docs.smartlyq.com/api-reference/social/preview-upcoming-slots /openapi.json get /social/queues/{queue_id}/preview The next N open slots (default 10, max 50), continuing the same walk `next-slot` uses. Requires scope `social:read`. # Private reply (comment-to-DM) Source: https://docs.smartlyq.com/api-reference/social/private-reply-comment-to-dm /openapi.json post /social/comments/{comment_id}/private-reply Sends a DIRECT MESSAGE to the author of a Facebook or Instagram comment via Meta's private-reply mechanism. Meta allows one private reply per comment, within 7 days - violations are surfaced verbatim. Also available as the automation action `action.private_reply` paired with the `comment_created` trigger. Requires scope `social:write`. # Reddit feed Source: https://docs.smartlyq.com/api-reference/social/reddit-feed /openapi.json get /social/accounts/{account_id}/reddit/feed The account's home feed or one subreddit's feed (hot / new / top / rising). Requires scope `social:read`. # Reddit search Source: https://docs.smartlyq.com/api-reference/social/reddit-search /openapi.json get /social/accounts/{account_id}/reddit/search Search Reddit (optionally restricted to one subreddit) with the account's own credentials - find where a topic is being discussed before posting. Requires scope `social:read`. # Rename account Source: https://docs.smartlyq.com/api-reference/social/rename-account /openapi.json patch /social/accounts/{account_id} Rename a connected social account (display name inside SmartlyQ - does not change anything on the platform). Requires scope `social:write`. # Reply to a mention Source: https://docs.smartlyq.com/api-reference/social/reply-to-a-mention /openapi.json post /social/accounts/{account_id}/mentions/{mention_id}/reply Reply publicly to a mention. On Facebook this comments on the tagged post; on Instagram it goes through the mentions edge, which is how Meta expects a business to answer a tag. Requires scope `social:write`. If the account was connected before SmartlyQ requested the permission this call needs, it answers `403 CONNECTION_SCOPE_MISSING` with `reconnect_required: true` - reconnect the account to fix it. # Resume posting to an account Source: https://docs.smartlyq.com/api-reference/social/resume-posting-to-an-account /openapi.json post /social/accounts/{account_id}/resume Requires scope `social:write`. # Retry publishing a post Source: https://docs.smartlyq.com/api-reference/social/retry-publishing-a-post /openapi.json post /social/posts/{post_id}/retry Requires scope `social:write`. # Retweet on X Source: https://docs.smartlyq.com/api-reference/social/retweet-on-x /openapi.json post /social/accounts/{account_id}/x/retweets Retweets a post with the connected X account. Covered by the tweet.write permission your connection already has. Requires scope `social:write`. # Schedule post Source: https://docs.smartlyq.com/api-reference/social/schedule-post /openapi.json post /social/posts/schedule Pass queue_id (instead of scheduled_time) to drop the post into a posting queue's next open slot. Supports evergreen recycling via the optional `recycle` object: each published occurrence automatically schedules the next one until the chain hits its bound. Facebook allows 30 API-published Reels per Page in a moving 24-hour window. A post whose `platform_options.facebook.type` is `reel` is refused when a selected Page would be at that limit at the post's publish time (Reels published plus Reels already scheduled in the 24 hours before it): `422 PLATFORM_DAILY_LIMIT_REACHED`, with `details` carrying `account_id`, `limit`, `used` and `next_available_at`. Nothing is created. If a scheduled Reel still hits the limit when it publishes, that account's result fails with `PLATFORM_DAILY_LIMIT_REACHED` and is not retried automatically. Requires scope `social:write`. # Search conversations Source: https://docs.smartlyq.com/api-reference/social/search-conversations /openapi.json get /social/conversations/search Full-inbox search across participant names and message content; each hit includes the latest matching message. Requires scope `social:read`. # Select connection target Source: https://docs.smartlyq.com/api-reference/social/select-connection-target /openapi.json post /social/accounts/{account_id}/connect-select Re-points a Google Business connection at another location of the same parent account (the assign-location operation). The target is verified against Google's live location list - never trusted from input. Requires scope `social:write`. # Set Facebook page cover photo Source: https://docs.smartlyq.com/api-reference/social/set-facebook-page-cover-photo /openapi.json post /social/accounts/{account_id}/facebook/page/cover Replace the Page's cover photo with the image at `url`. Facebook downloads the image from that URL itself. No feed story is created and followers are not notified. A cover photo can be replaced through the API but not removed - removing it has to be done on Facebook. The image must be JPG, PNG, GIF, BMP or TIFF, 10 MB or smaller, and at least 400 x 150 px. 851 x 315 px is recommended (not enforced). An image that fails a check is refused with `422 VALIDATION_ERROR`, with `details.field` set to `url` and `details.reason` one of `invalid_url`, `unreachable`, `too_large`, `unsupported_type` or `too_small`. Needs the business permission on the connection. Connections without it get `403 CONNECTION_SCOPE_MISSING` with `reconnect_required: true` - reconnect the account to fix it. With a test key (`sqk_test_...`) the request is validated, including the image URL, and a success response is returned without contacting Facebook. Requires scope `social:write`. # Set Facebook page profile picture Source: https://docs.smartlyq.com/api-reference/social/set-facebook-page-profile-picture /openapi.json post /social/accounts/{account_id}/facebook/page/picture Replace the Page's profile picture with the image at `url`. Facebook downloads the image from that URL itself. No feed story is created and followers are not notified. The image must be JPG, PNG, GIF, BMP or TIFF and 10 MB or smaller. Facebook shows the picture as a circle, so a square image of 196 x 196 px or larger is recommended (not enforced). An image that fails a check is refused with `422 VALIDATION_ERROR`, with `details.field` set to `url` and `details.reason` one of `invalid_url`, `unreachable`, `too_large` or `unsupported_type`. With a test key (`sqk_test_...`) the request is validated, including the image URL, and a success response is returned without contacting Facebook. If the account was connected before SmartlyQ requested the permission this call needs, it answers `403 CONNECTION_SCOPE_MISSING` with `reconnect_required: true` - reconnect the account to fix it. Requires scope `social:write`. # Set ice breakers Source: https://docs.smartlyq.com/api-reference/social/set-ice-breakers /openapi.json put /social/accounts/{account_id}/instagram/ice-breakers Replaces the ice breakers (1-4 Meta ice-breaker objects, passthrough). Requires scope `social:write`. # Set Messenger menu Source: https://docs.smartlyq.com/api-reference/social/set-messenger-menu /openapi.json put /social/accounts/{account_id}/messenger/menu Replaces the page's persistent menu (Meta menu objects, passthrough). Requires scope `social:write`. # Start headless account connection Source: https://docs.smartlyq.com/api-reference/social/start-headless-account-connection /openapi.json post /social/connect/{platform} Begin connecting a NEW social account from your own UI (no SmartlyQ dashboard session needed). Returns a one-time `connect_url` (valid 15 minutes) — open it in a browser so the user can authorize. After they approve, the platform's OAuth callback completes the connection and (if provided) redirects to `return_url` with `?connected=&platform=`. Supported on all platforms. Platforms that expose multiple targets (YouTube channels, Google Business locations, Tumblr blogs, LinkedIn pages) connect them all automatically. Poll completion with `GET /social/connect/{state_token}`. Requires scope `social:write`. Some platforms connect with credentials instead of a browser round trip, and return the connected account directly rather than a `connect_url`: Telegram (`bot_token` + `chat`), WhatsApp (`waba_id` + `phone_number_id` + `access_token`) and Bluesky (`identifier` + `app_password`). # Stop recycling Source: https://docs.smartlyq.com/api-reference/social/stop-recycling /openapi.json delete /social/posts/{post_id}/recycle Stops an evergreen recycling chain: clears the recycle config on every post in the chain and cancels any not-yet-published auto-spawned occurrences back to draft. Works on any post in the chain (original or an occurrence). Unbilled. Requires scope `social:write`. # Subreddit info + eligibility Source: https://docs.smartlyq.com/api-reference/social/subreddit-info-+-eligibility /openapi.json get /social/accounts/{account_id}/reddit/subreddits/{subreddit} Subreddit details plus a posting-eligibility check for THIS connected account: does it exist, what submission kinds it accepts, is the account banned, and a computed can_post verdict. Check before submitting. Requires scope `social:read`. # Subreddit rules Source: https://docs.smartlyq.com/api-reference/social/subreddit-rules /openapi.json get /social/accounts/{account_id}/reddit/subreddits/{subreddit}/rules A subreddit's posting rules - check them BEFORE submitting so the post doesn't get removed. Requires scope `social:read`. # Subscribed subreddits Source: https://docs.smartlyq.com/api-reference/social/subscribed-subreddits /openapi.json get /social/accounts/{account_id}/reddit/subreddits Subreddits the connected account subscribes to (name, title, subscriber count). Requires scope `social:read`. # Sync external posts Source: https://docs.smartlyq.com/api-reference/social/sync-external-posts /openapi.json post /social/posts/sync-external On-demand import of posts published natively on the platform (outside SmartlyQ) for one account - facebook, instagram, threads, youtube or tiktok. Imported posts appear in GET /social/posts with status 'external', with analytics snapshots. Discovery is a per-account opt-in: pass enable: true the first time, or the call returns 409. Requires scope `social:write`. # TikTok creator info Source: https://docs.smartlyq.com/api-reference/social/tiktok-creator-info /openapi.json get /social/accounts/{account_id}/tiktok/creator-info Live TikTok creator settings for a connected TikTok account: available privacy levels, whether comments/duet/stitch are disabled, and the account's maximum video duration. TikTok requires surfacing these before showing post options. Requires scope `social:read`. # Typing indicator Source: https://docs.smartlyq.com/api-reference/social/typing-indicator /openapi.json post /social/conversations/{conversation_id}/typing Shows the typing indicator to the other side of a Facebook or Instagram conversation (Meta sender_action). Other platforms return 422. Requires scope `social:write`. # Undo retweet Source: https://docs.smartlyq.com/api-reference/social/undo-retweet /openapi.json delete /social/accounts/{account_id}/x/retweets/{tweet_id} Removes the account's retweet of a post. Requires scope `social:write`. # Unpublish post Source: https://docs.smartlyq.com/api-reference/social/unpublish-post /openapi.json post /social/posts/{post_id}/unpublish Delete a PUBLISHED post from its native platforms while keeping the SmartlyQ record - its status flips to `unpublished`. Optional `platforms` restricts which networks are removed. Supported for deletion: Threads, Facebook, LinkedIn, YouTube, X/Twitter, Pinterest, Tumblr, Bluesky, Google Business (Instagram, TikTok and Snapchat expose no delete API - those targets are reported as skipped). If any attempted native delete fails, nothing changes on our side and per-platform errors are returned so you can retry. Drafts and scheduled posts should use `DELETE /social/posts/{post_id}` instead. Requires scope `social:write`. # Update a YouTube playlist Source: https://docs.smartlyq.com/api-reference/social/update-a-youtube-playlist /openapi.json patch /social/accounts/{account_id}/youtube/playlists/{playlist_id} Rename a playlist, change its description, or change who can see it. Send only what you want to change: YouTube's own update replaces every field you submit, so SmartlyQ reads the playlist first and merges, rather than blanking the fields you left out. Requires scope `social:write`. If the account was connected before SmartlyQ requested the permission this call needs, it answers `403 CONNECTION_SCOPE_MISSING` with `reconnect_required: true` - reconnect the account to fix it. # Update account group Source: https://docs.smartlyq.com/api-reference/social/update-account-group /openapi.json put /social/account-groups/{group_id} Update the name and/or replace the member list. Requires scope `social:write`. # Update attributes Source: https://docs.smartlyq.com/api-reference/social/update-attributes /openapi.json put /social/accounts/{account_id}/gmb/attributes Sets/updates the attributes named in attribute_mask. Discover what this location supports with GET .../attributes/metadata. Requires a connected Google Business account; bodies/responses are honest passthroughs of Google's resource shapes, so every Google field works. Requires scope `social:write`. # Update business info Source: https://docs.smartlyq.com/api-reference/social/update-business-info /openapi.json patch /social/accounts/{account_id}/gmb/location Updates any Google Location fields named in update_mask - hours, phone, website, description, categories, service items. Requires a connected Google Business account; bodies/responses are honest passthroughs of Google's resource shapes, so every Google field works. Requires scope `social:write`. # Update Facebook page details Source: https://docs.smartlyq.com/api-reference/social/update-facebook-page-details /openapi.json patch /social/accounts/{account_id}/facebook/page Edit the page's public details. Send only the fields you want to change. Needs the page-metadata permission on the connection. Pages connected through an older flow that never requested it get `reconnect_required` rather than a silent no-op. Requires scope `social:write`. # Update Facebook page settings Source: https://docs.smartlyq.com/api-reference/social/update-facebook-page-settings /openapi.json patch /social/accounts/{account_id}/facebook/page/settings Turn messaging on the Page on or off. `false` hides the Page's Message button and also blocks replies in chats that already exist. `true` restores both. With a test key (`sqk_test_...`) the request is validated and a success response is returned without contacting Facebook. If the account was connected before SmartlyQ requested the permission this call needs, it answers `403 CONNECTION_SCOPE_MISSING` with `reconnect_required: true` - reconnect the account to fix it. Requires scope `social:write`. # Update food menus Source: https://docs.smartlyq.com/api-reference/social/update-food-menus /openapi.json put /social/accounts/{account_id}/gmb/food-menus Replaces the structured menus with the given Google FoodMenu objects. ONLY available for food-serving business categories - other business types get Google's precondition error passed through. Requires a connected Google Business account; bodies/responses are honest passthroughs of Google's resource shapes, so every Google field works. Requires scope `social:write`. # Update place-action link Source: https://docs.smartlyq.com/api-reference/social/update-place-action-link /openapi.json patch /social/accounts/{account_id}/gmb/place-actions Updates an action link's URI in place. name must belong to this location. Requires scope `social:write`. # Update queue Source: https://docs.smartlyq.com/api-reference/social/update-queue /openapi.json put /social/queues/{queue_id} Update name, slots, timezone, or active state. Only supplied fields change; already-queued posts keep their resolved times. Requires scope `social:write`. # Update social post Source: https://docs.smartlyq.com/api-reference/social/update-social-post /openapi.json patch /social/posts/{post_id} Update a draft or scheduled post. Send only the fields you want to change. If the post will be a scheduled Facebook Reel after the update (`platform_options.facebook.type` set to `reel`), it is checked against Facebook's limit of 30 Reels per Page in 24 hours at its publish time, counting other published and scheduled Reels but not this post itself. A Page that would be over the limit is refused with `422 PLATFORM_DAILY_LIMIT_REACHED` and `details` carrying `account_id`, `limit`, `used` and `next_available_at`; nothing is changed. Requires scope `social:write`. # Update YouTube metadata Source: https://docs.smartlyq.com/api-reference/social/update-youtube-metadata /openapi.json post /social/posts/{post_id}/update-metadata Post-publish updates for the post's YouTube leg: title, description, tags (snippet is fetched and merged so nothing else changes), add to a playlist, and/or set a custom thumbnail from a public URL (max 2MB). Requires scope `social:write`. # Validate a bulk batch Source: https://docs.smartlyq.com/api-reference/social/validate-a-bulk-batch /openapi.json post /social/posts/bulk/validate Free dry-run of the same batch body as POST /social/posts/bulk: per-row structural checks, per-platform caption/media limits, and the 24h duplicate-content check. Nothing is created or billed. Requires scope `social:read`. # Validate media URL Source: https://docs.smartlyq.com/api-reference/social/validate-media-url /openapi.json post /social/validate/media Checks that a media URL is publicly reachable and reports its content type, byte size, and detected kind (image/video). SSRF-guarded: private and internal hosts are rejected. Uses a HEAD request with a 1-byte ranged-GET fallback for CDNs that refuse HEAD. Free and unbilled. Requires scope `social:read`. # Validate post content Source: https://docs.smartlyq.com/api-reference/social/validate-post-content /openapi.json post /social/validate/post Dry-run the same declarative checks the create path uses, WITHOUT creating anything: per-platform caption limits (each limit grounded in the platform handler or the platform's documented maximum), required-media rules (e.g. Instagram), and media extension screening. Free and unbilled. Platforms with no honestly-documented ceiling (Snapchat stories carry no caption; Tumblr documents no body cap) report limit: null. Requires scope `social:read`. # Verification options Source: https://docs.smartlyq.com/api-reference/social/verification-options /openapi.json post /social/accounts/{account_id}/gmb/verifications/options Which verification methods Google offers this location (postcard, phone, email, auto). Requires a connected Google Business account; bodies/responses are honest passthroughs of Google's resource shapes, so every Google field works. Requires scope `social:read`. # X mentions Source: https://docs.smartlyq.com/api-reference/social/x-mentions /openapi.json get /social/accounts/{account_id}/x/mentions Recent posts mentioning the connected X account, with engagement metrics. Requires scope `social:read`. # YouTube playlists Source: https://docs.smartlyq.com/api-reference/social/youtube-playlists /openapi.json get /social/accounts/{account_id}/youtube/playlists The channel's playlists (title, privacy, item count) - target one when uploading. Requires scope `social:read`. # Delete short URL Source: https://docs.smartlyq.com/api-reference/urls/delete-short-url /openapi.json delete /urls/{url_id} Requires scope `urls:write`. # Get short URL Source: https://docs.smartlyq.com/api-reference/urls/get-short-url /openapi.json get /urls/{url_id} Requires scope `urls:read`. # Get short URL stats Source: https://docs.smartlyq.com/api-reference/urls/get-short-url-stats /openapi.json get /urls/{url_id}/stats Requires scope `urls:read`. # List short URLs Source: https://docs.smartlyq.com/api-reference/urls/list-short-urls /openapi.json get /urls Requires scope `urls:read`. # Shorten URL Source: https://docs.smartlyq.com/api-reference/urls/shorten-url /openapi.json post /urls/shorten Requires scope `urls:write`. # Update a short URL Source: https://docs.smartlyq.com/api-reference/urls/update-a-short-url /openapi.json patch /urls/{id} Repoint an existing short link at a new destination, rename it, or deactivate it. The short code never changes, so links already printed, posted or scanned keep working and their click history stays attached. Send only the fields you want to change. Free - the link was already charged for at creation. Requires scope `urls:write`. # Delete video Source: https://docs.smartlyq.com/api-reference/videos/delete-video /openapi.json delete /videos/{video_id} Requires scope `videos:write`. # Generate a viral hook line Source: https://docs.smartlyq.com/api-reference/videos/generate-a-viral-hook-line /openapi.json post /videos/hook Writes ONE scroll-stopping hook line (<=8 words) to overlay on a short's first seconds. Provide `script` or `transcript_words`. Billed per request from your API wallet; refunded on failure. Requires scope `videos:write`. # Generate a viral thumbnail Source: https://docs.smartlyq.com/api-reference/videos/generate-a-viral-thumbnail /openapi.json post /videos/viral-thumbnail Generates a viral, click-optimized thumbnail from a video title (optionally guided by a reference image) and returns a hosted `image_url`. Billed per request; refunded on failure. Requires scope `videos:write`. # Generate video Source: https://docs.smartlyq.com/api-reference/videos/generate-video /openapi.json post /videos/generate Submits a video generation request. Returns `202 Accepted` immediately with a `job_uid`; poll `GET /v1/jobs/{job_uid}` for status and the final video URL. **Before submitting:** call `GET /v1/videos/models` to retrieve the valid parameter values (`duration`, `resolution`, `mode`, `aspect_ratio`, etc.) for your chosen model. Passing an unsupported value returns a `422` validation error. **Billing:** the cost is deducted from your API wallet when the job is accepted. If video generation fails on the provider side the charge is automatically refunded. Requires scope `videos:write`. # Get video Source: https://docs.smartlyq.com/api-reference/videos/get-video /openapi.json get /videos/{video_id} Requires scope `videos:read`. # List available video models Source: https://docs.smartlyq.com/api-reference/videos/list-available-video-models /openapi.json get /videos/models Returns every model currently available for video generation, including valid parameter values for each model and generation type. Call this endpoint before submitting a generation request to discover what options a specific model supports. Empty arrays (`[]`) mean the parameter is not applicable. Boolean `supports_*` flags indicate optional feature availability. Requires scope `videos:read`. # List videos Source: https://docs.smartlyq.com/api-reference/videos/list-videos /openapi.json get /videos Requires scope `videos:read`. # Suggest B-roll moments Source: https://docs.smartlyq.com/api-reference/videos/suggest-b-roll-moments /openapi.json post /videos/broll-suggest Suggests B-roll moments (with timings) for a word-level transcript. Billed per request; refunded on failure. Requires scope `videos:write`. # Suggest on-screen emphasis Source: https://docs.smartlyq.com/api-reference/videos/suggest-on-screen-emphasis /openapi.json post /videos/emphasis Picks on-screen emphasis moments (punchy full-screen text with timings) from a word-level transcript. Billed per request; refunded on failure. Requires scope `videos:write`. # Create webhook Source: https://docs.smartlyq.com/api-reference/webhooks/create-webhook /openapi.json post /webhooks Subscribe a public HTTPS URL to events. The signing `secret` is returned once at creation. Requires scope `webhooks:write`. Max 10 active webhooks per workspace. Events: post.published, post.failed, post.partial, account.connected, account.disconnected, account.token_expired, comment.received, message.received, job.completed, job.failed. # Delete webhook Source: https://docs.smartlyq.com/api-reference/webhooks/delete-webhook /openapi.json delete /webhooks/{id} Delete a webhook subscription. Requires scope `webhooks:write`. # List webhook delivery logs Source: https://docs.smartlyq.com/api-reference/webhooks/list-webhook-delivery-logs /openapi.json get /webhooks/logs Paginated log of webhook delivery attempts across your endpoints, newest first (90-day retention). Each row carries the full delivery envelope, the destination's response code and a response-body excerpt, the attempt count, and the next retry time when pending. Requires scope `webhooks:read`. Not available under X-Profile-Id delegation - webhooks live on your developer workspace and receive profile events automatically (with profile_id attached). # List webhooks Source: https://docs.smartlyq.com/api-reference/webhooks/list-webhooks /openapi.json get /webhooks List the workspace's webhook subscriptions and the available event names. Requires scope `webhooks:read`. # Replay a webhook delivery Source: https://docs.smartlyq.com/api-reference/webhooks/replay-a-webhook-delivery /openapi.json post /webhooks/deliveries/{id}/replay Re-queue a past delivery so it is sent again - the fix-up after your receiver was down or rejecting. The original event and payload are replayed unchanged and signed afresh, and the attempt counter resets so retry backoff starts clean. Find delivery ids with `GET /webhooks/logs`. Requires scope `webhooks:write`. Not available under X-Profile-Id delegation. # Send test webhook Source: https://docs.smartlyq.com/api-reference/webhooks/send-test-webhook /openapi.json post /webhooks/{id}/test Synchronously sends a `webhook.test` delivery to the endpoint through the real signing pipeline (same headers and HMAC signature as production events) and returns the outcome. Works even when the endpoint is paused or failing. Test deliveries are never retried by the worker. Requires scope `webhooks:write`. Not available under X-Profile-Id delegation - webhooks live on your developer workspace and receive profile events automatically (with profile_id attached). # Update webhook Source: https://docs.smartlyq.com/api-reference/webhooks/update-webhook /openapi.json put /webhooks/{id} Update a webhook's `url`, `events`, or `status`. Only supplied fields change. `url` is re-validated (public HTTPS, SSRF-guarded); `events` must all come from the 25-event catalog (unknown names are rejected); setting `status` to `active` also recovers an endpoint auto-paused as `failing`. Requires scope `webhooks:write`. Not available under X-Profile-Id delegation - webhooks live on your developer workspace and receive profile events automatically (with profile_id attached). # Adopt a library template Source: https://docs.smartlyq.com/api-reference/whatsapp/adopt-a-library-template /openapi.json post /whatsapp/templates/from-library Create a template on your WhatsApp Business Account from a library entry. Meta clones the prewritten body under the name you choose; anything the entry leaves open, such as button URLs, is passed through from this request. Requires scope `social:write`. # Block users Source: https://docs.smartlyq.com/api-reference/whatsapp/block-users /openapi.json post /whatsapp/block-users Block one or more WhatsApp users on this number. Blocked users can't message your number or see your online status, and your sends to them fail. Meta only allows blocking a user who has messaged your business within the last 24 hours - other targets come back in `failed` without failing the rest of the batch. Up to 1,000 users per request. Requires social:write. # Bridge an owned number onto WhatsApp Source: https://docs.smartlyq.com/api-reference/whatsapp/bridge-an-owned-number-onto-whatsapp /openapi.json post /whatsapp/numbers/{sender_id}/bridge Adds an already-owned phone number (bought via POST /phone-numbers/purchase) to your connected WhatsApp Business Account. Requires an existing WhatsApp connection (Settings > WhatsApp) - this adds a number to it, it does not create one - and SMS or voice capability on the number to receive Meta's verification code. Next: POST .../bridge/request-code. Requires social:write. # Bridge status Source: https://docs.smartlyq.com/api-reference/whatsapp/bridge-status /openapi.json get /whatsapp/numbers/{sender_id}/bridge Where the bridge is in Meta's add -> request_code -> verify_code -> register sequence. Requires social:read. # Browse the shared template library Source: https://docs.smartlyq.com/api-reference/whatsapp/browse-the-shared-template-library /openapi.json get /whatsapp/template-library Meta's library of prewritten, pre-approved message templates. Adopting one skips the draft-and-wait-for-review cycle, so it is worth browsing before writing a template by hand. Adopt an entry with `POST /whatsapp/templates/from-library`. Requires scope `social:read`. # Create a flow Source: https://docs.smartlyq.com/api-reference/whatsapp/create-a-flow /openapi.json post /whatsapp/flows Create a new WhatsApp Flow in DRAFT status. Optionally clone an existing flow with clone_flow_id. A freshly created flow has no screens - upload a Flow JSON definition next (PUT /whatsapp/flows/{flow_id}/json), then publish it. Requires social:write. # Create a message template Source: https://docs.smartlyq.com/api-reference/whatsapp/create-a-message-template /openapi.json post /whatsapp/templates Submit a message template to Meta for approval. Requires social:write. # Delete a WhatsApp template Source: https://docs.smartlyq.com/api-reference/whatsapp/delete-a-whatsapp-template /openapi.json delete /whatsapp/templates/{name} Delete a template. Without `template_id` this removes every language version of the name; with it, only that one translation. Requires scope `social:write`. # Delete flow Source: https://docs.smartlyq.com/api-reference/whatsapp/delete-flow /openapi.json delete /whatsapp/flows/{flow_id} Delete a DRAFT flow. Irreversible. Published flows can't be deleted, only deprecated. Requires social:write. # Deprecate flow Source: https://docs.smartlyq.com/api-reference/whatsapp/deprecate-flow /openapi.json post /whatsapp/flows/{flow_id}/deprecate Deprecate a published flow, stopping it from being sendable while keeping its submission history and results. Requires social:write. # Get a WhatsApp template Source: https://docs.smartlyq.com/api-reference/whatsapp/get-a-whatsapp-template /openapi.json get /whatsapp/templates/{name} One template by name. A template name can hold several rows - one per language - so this returns every translation under that name. Requires scope `social:read`. # Get business profile Source: https://docs.smartlyq.com/api-reference/whatsapp/get-business-profile /openapi.json get /whatsapp/business-profile Read the number's WhatsApp business profile. Requires social:read. # Get flow Source: https://docs.smartlyq.com/api-reference/whatsapp/get-flow /openapi.json get /whatsapp/flows/{flow_id} Get a flow's details: status, categories, validation errors, and the endpoint URI (for data_exchange flows). Requires social:read. # Get flow JSON asset Source: https://docs.smartlyq.com/api-reference/whatsapp/get-flow-json-asset /openapi.json get /whatsapp/flows/{flow_id}/json Get the uploaded Flow JSON asset's metadata, including a temporary download URL. Requires social:read. # Get flow preview URL Source: https://docs.smartlyq.com/api-reference/whatsapp/get-flow-preview-url /openapi.json get /whatsapp/flows/{flow_id}/preview Get Meta's web-preview URL for the flow (draft or published), embeddable as an iframe. The URL is reused across calls (valid roughly 30 days) unless invalidate=true mints a fresh one. Requires social:read. # Get the WhatsApp display name Source: https://docs.smartlyq.com/api-reference/whatsapp/get-the-whatsapp-display-name /openapi.json get /whatsapp/business-profile/display-name The name customers see in chat, plus where any pending name change stands with Meta review and the number's current quality rating. Requires scope `social:read`. # List blocked users Source: https://docs.smartlyq.com/api-reference/whatsapp/list-blocked-users /openapi.json get /whatsapp/block-users List the WhatsApp users blocked on this number. Cursor-paginated; the blocklist holds up to 64,000 users. Requires social:read. # List flows Source: https://docs.smartlyq.com/api-reference/whatsapp/list-flows /openapi.json get /whatsapp/flows List the WhatsApp Flows on this connection's WhatsApp Business Account. Requires social:read. # List message templates Source: https://docs.smartlyq.com/api-reference/whatsapp/list-message-templates /openapi.json get /whatsapp/templates List the WhatsApp Business Account's message templates. Requires social:read. # List phone numbers Source: https://docs.smartlyq.com/api-reference/whatsapp/list-phone-numbers /openapi.json get /whatsapp/phone-numbers List the phone numbers on this connection's WhatsApp Business Account. Requires social:read. # List your sandbox sessions Source: https://docs.smartlyq.com/api-reference/whatsapp/list-your-sandbox-sessions /openapi.json get /whatsapp/sandbox/sessions Returns your non-expired sandbox sessions (there is at most one, since it's one phone per user) plus the shared sandbox number in E.164. `503 SANDBOX_UNAVAILABLE` if the platform hasn't configured a sandbox number yet. Requires social:read. # Publish flow Source: https://docs.smartlyq.com/api-reference/whatsapp/publish-flow /openapi.json post /whatsapp/flows/{flow_id}/publish Publish a DRAFT flow, making it sendable via POST /whatsapp/messages with type=flow. Irreversible - published flows are immutable; to change one, create a new flow. Requires social:write. # Request a verification code Source: https://docs.smartlyq.com/api-reference/whatsapp/request-a-verification-code /openapi.json post /whatsapp/numbers/{sender_id}/bridge/request-code Meta sends a code to the number ITSELF, by SMS or a voice call - not to you. Check the number's inbound SMS/call log in SmartlyQ to read it. Requires social:write. # Request a WhatsApp display-name change Source: https://docs.smartlyq.com/api-reference/whatsapp/request-a-whatsapp-display-name-change /openapi.json post /whatsapp/business-profile/display-name Submit a new display name. This does not take effect immediately - Meta reviews it, and the old name stays live until approval, so a 202 here means "submitted", not "changed". Poll `GET /whatsapp/business-profile/display-name` and watch `name_status`. Requires scope `social:write`. # Revoke a sandbox session Source: https://docs.smartlyq.com/api-reference/whatsapp/revoke-a-sandbox-session /openapi.json delete /whatsapp/sandbox/sessions/{session_id} Hard-deletes the session. You lose the ability to send to that phone via the sandbox until you activate it again. Requires social:write. # Send a WhatsApp message Source: https://docs.smartlyq.com/api-reference/whatsapp/send-a-whatsapp-message /openapi.json post /whatsapp/messages Send a message from a connected WhatsApp number. `text`, `media`, `interactive_buttons`, `interactive_list`, `location`, `contacts`, and `flow` are SESSION messages - deliverable only inside the 24-hour customer window (i.e. after the user messaged you). Outside it, only `template` (an approved HSM) is deliverable. `context_message_id` (any type except `contacts`) threads the message as a reply under a prior WhatsApp message id, rendering as a quoted reply in the customer's app. `type=flow` requires the flow to be PUBLISHED first (see the WhatsApp Flows endpoints) - a DRAFT flow can only be tested via its preview URL. Requires social:write. **Pricing.** Billed per message from your API wallet, on top of your plan's monthly WhatsApp send limit. The price depends on the destination country and the message category, mirroring how WhatsApp itself prices, so a utility message to one market and a marketing message to another cost different amounts. Free-form replies are billed as `service`, priced at the destination market's utility rate. The response carries a `pricing` block showing which rate was applied, and the `usage` block reports cost and remaining balance. You are charged only for a message accepted for delivery - a rejected send is refunded automatically. # Send the sandbox template Source: https://docs.smartlyq.com/api-reference/whatsapp/send-the-sandbox-template /openapi.json post /whatsapp/sandbox/sessions/{session_id}/send Re-fires the locked sandbox template at an ACTIVE session's phone - the only message the sandbox can send, since the template is the only thing pre-approved on the shared number. 422 if the session is still pending (the phone hasn't replied yet) or the daily limit (50/24h) is reached. Requires social:write. # Set the WhatsApp profile photo Source: https://docs.smartlyq.com/api-reference/whatsapp/set-the-whatsapp-profile-photo /openapi.json post /whatsapp/business-profile/photo Set the profile picture customers see for this number. Meta does not accept an image URL here: upload the file through Meta's resumable upload API first and pass the handle it returns. Requires scope `social:write`. # Start a sandbox activation Source: https://docs.smartlyq.com/api-reference/whatsapp/start-a-sandbox-activation /openapi.json post /whatsapp/sandbox/sessions Creates (or refreshes) a pending sandbox session for `phone` and immediately fires the locked sandbox template from the shared number to it. The session activates when the phone owner replies to that WhatsApp message - the reply itself proves ownership. Once active, use POST .../sessions/{session_id}/send to re-fire the template and see it land. Limits: 50 sends/24h per user, one phone per user at a time (revoke the existing session before activating a different phone). Requires social:write. # Submit the verification code Source: https://docs.smartlyq.com/api-reference/whatsapp/submit-the-verification-code /openapi.json post /whatsapp/numbers/{sender_id}/bridge/verify Submits the code, completes Cloud API registration, and connects the number - after this succeeds it behaves like any other connected WhatsApp number (POST /whatsapp/messages with its account_id, etc). Requires social:write. # Unblock users Source: https://docs.smartlyq.com/api-reference/whatsapp/unblock-users /openapi.json delete /whatsapp/block-users Unblock one or more previously blocked WhatsApp users on this number. Up to 1,000 users per request; per-user failures are reported in `failed` without failing the rest of the batch. Requires social:write. # Update a WhatsApp template Source: https://docs.smartlyq.com/api-reference/whatsapp/update-a-whatsapp-template /openapi.json patch /whatsapp/templates/{name} Replace an existing template's components. Name and language are fixed at creation - changing either means creating a new template. Editing an approved template sends it back through Meta review. If the name has more than one language version you must pass `template_id` to say which one to edit; the endpoint refuses to guess. Requires scope `social:write`. # Update business profile Source: https://docs.smartlyq.com/api-reference/whatsapp/update-business-profile /openapi.json patch /whatsapp/business-profile Update about/address/email/description/vertical/websites on the number's profile. Requires social:write. # Update flow Source: https://docs.smartlyq.com/api-reference/whatsapp/update-flow /openapi.json patch /whatsapp/flows/{flow_id} Update a DRAFT flow's name, categories, or endpoint_uri. Published flows are immutable - create a new flow to change one. Requires social:write. # Upload flow JSON Source: https://docs.smartlyq.com/api-reference/whatsapp/upload-flow-json /openapi.json put /whatsapp/flows/{flow_id}/json Upload or replace a DRAFT flow's Flow JSON - the screens, components, and navigation that define the form. Meta validates on upload; the response's validation_errors comes back either way, so a successful upload doesn't guarantee the flow is publishable yet. Requires social:write. # Bulk sub-account action Source: https://docs.smartlyq.com/api-reference/workspaces/bulk-sub-account-action /openapi.json post /workspaces/bulk Apply one lifecycle action - pause, resume, or disable_saas - to many client sub-accounts in a single call. **Requires an active SaaS plan**: the agency must have a connected, charges-enabled Stripe account able to bill its clients; otherwise the call returns 403 SAAS_PLAN_REQUIRED. High-trust: requires the dedicated workspaces:bulk scope (separate from workspaces:write) because a mass action is high blast-radius. Applies per workspace and returns applied/failed counts plus a per-workspace results array; the id list is capped at 200. disable_saas cascades (cancels each subscription and refunds each sub-wallet to the agency) and is irreversible, so it also requires a typed confirmation. # Create a workspace (sub-account) Source: https://docs.smartlyq.com/api-reference/workspaces/create-a-workspace-sub-account /openapi.json post /workspaces Create a sub-account workspace under the agency the API key belongs to. Requires the workspaces:write scope; plan workspace caps apply. # Delete a workspace (sub-account) Source: https://docs.smartlyq.com/api-reference/workspaces/delete-a-workspace-sub-account /openapi.json delete /workspaces/{id} Permanently delete a client sub-account. The workspace is scheduled for asynchronous teardown and disappears immediately; its data is purged within a few minutes. Blocked (409) while the sub-account is still in SaaS mode - disable SaaS first - and blocked (422) for the agency's own workspaces. Requires the workspaces:write scope and a typed confirmation. # Disable SaaS mode for a workspace Source: https://docs.smartlyq.com/api-reference/workspaces/disable-saas-mode-for-a-workspace /openapi.json post /workspaces/{id}/disable-saas Disable SaaS mode for a sub-account: switch it back to agency billing, turn off rebilling, and refund any remaining sub-wallet balance to the agency wallet. Optionally cancels the client's subscription. Required before a SaaS sub-account can be deleted. Requires the workspaces:write scope. **SaaS-member only**: this is a SaaS-mode action, so your agency must be on an active SaaS plan; otherwise it returns 403 SAAS_PLAN_REQUIRED. # Get a SaaS plan Source: https://docs.smartlyq.com/api-reference/workspaces/get-a-saas-plan /openapi.json get /saas/plans/{id} Get one SaaS plan (client package) by id. Read-only. Requires the workspaces:read scope. # Get a sub-account's subscription Source: https://docs.smartlyq.com/api-reference/workspaces/get-a-sub-accounts-subscription /openapi.json get /workspaces/{id}/subscription Read a sub-account's SaaS subscription + billing snapshot: billing_mode, rebilling state, plan_id, Stripe subscription_status, billing_cycle, and sub-wallet balance. Requires the workspaces:read scope. # Get a sub-account's wallet balance Source: https://docs.smartlyq.com/api-reference/workspaces/get-a-sub-accounts-wallet-balance /openapi.json get /workspaces/{id}/wallet Read a sub-account's wallet balance in SQC. Requires the workspaces:read scope. # Get a workspace (sub-account) Source: https://docs.smartlyq.com/api-reference/workspaces/get-a-workspace-sub-account /openapi.json get /workspaces/{id} Get one sub-account: name, account type, billing mode, rebilling state, and sub-wallet balance. Requires the workspaces:read scope. # List SaaS plans Source: https://docs.smartlyq.com/api-reference/workspaces/list-saas-plans /openapi.json get /saas/plans List the SaaS plans (client packages) configured for the agency the API key belongs to, with price, interval, setup fee, features and limits. Read-only. Requires the workspaces:read scope. # List workspaces (sub-accounts) Source: https://docs.smartlyq.com/api-reference/workspaces/list-workspaces-sub-accounts /openapi.json get /workspaces List the sub-account workspaces under the agency the API key belongs to, each with its billing state and sub-wallet balance. Requires the workspaces:read scope. # Pause (suspend) a workspace Source: https://docs.smartlyq.com/api-reference/workspaces/pause-suspend-a-workspace /openapi.json post /workspaces/{id}/pause Pause a client sub-account: block all access + operations for its users, but keep the data (reversible with resume). Cannot pause the agency's own workspaces. Requires the workspaces:write scope. # Resume a paused workspace Source: https://docs.smartlyq.com/api-reference/workspaces/resume-a-paused-workspace /openapi.json post /workspaces/{id}/resume Reactivate a paused sub-account: restore access + operations. Requires the workspaces:write scope. # Authentication Source: https://docs.smartlyq.com/authentication How to authenticate requests to the SmartlyQ API. All API requests require a **Bearer token** in the `Authorization` header. ## API keys Get your API key from the [Developer Dashboard](https://app.smartlyq.com/next/developer). | Key prefix | Environment | | ------------ | ------------------------------------ | | `sqk_live_*` | Production — real credits, real data | | `sqk_test_*` | Sandbox — no credits charged | ## Making authenticated requests Include your key in the `Authorization` header: ```bash theme={null} curl -X GET https://api.smartlyq.com/v1/me \ -H "Authorization: Bearer sqk_live_xxxxxxxxxxxx" ``` Never expose your API key in client-side code, public repositories, or logs. ## Scopes Each key can be limited to specific scopes. Available scopes: | Scope | Access | | ---------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | | `articles:read` | List and get articles | | `articles:write` | Generate and delete articles | | `images:read` / `images:write` | Image generation and listing | | `videos:read` / `videos:write` | Video generation and listing | | `presentations:read` | List and get presentations | | `presentations:write` | AI presentation generation | | `social:read` / `social:write` | Social accounts, posts, queues, comments, DMs, reviews, validation, platform lookups | | `audio:read` / `audio:write` | Text-to-speech, speech-to-text | | `urls:read` / `urls:write` | URL shortening | | `captain:use` | AI Captain conversations | | `chatbot:use` | Chatbot management and messaging | | `media:read` / `media:write` | Media library | | `analytics:read` | Post, account, derived and inbox analytics | | `seo:read` | Keyword research, SERP, rank tracking, competitors, backlinks, on-page audits | | `contacts:read` / `contacts:write` | CRM contacts, their tags and notes, custom field definitions, the workspace tag vocabulary, automation enrollment | | `companies:read` / `companies:write` | Companies / organisations, and linking contacts to them | | `tasks:read` / `tasks:write` | CRM tasks and activities, and logging time against them | | `calendar:read` / `calendar:write` | Booking pages and open slots; taking and cancelling bookings | | `opportunities:read` / `opportunities:write` | Pipelines and deals/opportunities, stage moves, status | | `workspaces:read` / `workspaces:write` / `workspaces:bulk` | Workspaces, their subscriptions and wallets; bulk workspace actions | | `profiles:read` / `profiles:write` / `profiles:manage` | Connection profiles, their accounts, and connect links | | `webhooks:read` / `webhooks:write` | Webhook subscriptions, delivery logs, test and replay deliveries | | `logs:read` | Your API request and webhook delivery history (`GET /logs`) | | `jobs:read` | Async job status | | `ads:read` / `ads:write` | Ad campaigns, ad sets, ads, audiences, pixels, lead forms, creatives, account diagnostics | ## Error responses If your key is missing or invalid, the API returns: * **401 Unauthorized** — missing or invalid key * **403 Forbidden** — valid key but insufficient scope ```json theme={null} { "success": false, "error": { "code": "UNAUTHORIZED", "message": "Missing or invalid API key." } } ``` # Changelog Source: https://docs.smartlyq.com/changelog New endpoints, improvements, and fixes to the SmartlyQ API Product updates for the SmartlyQ Developer API, MCP server, and Developer Portal. All endpoints live at `https://api.smartlyq.com/v1`. ## Facebook Reels over the daily limit are refused up front, with the reason Facebook allows 30 API-published Reels per Page in a moving 24-hour window, and you can now check that allowance and get a clear refusal instead of a Reel that fails later. * **Check the allowance** - [`GET /social/accounts/{account_id}/facebook/reel-limit`](/api-reference/social/get-facebook-reel-limit) returns `limit` (30), `used`, `remaining` and `next_available_at`. `used` counts Reels published for the Page through the API or the app in the last 24 hours plus Reels already scheduled inside that window. `next_available_at` is set only when the Page is at the limit, and says when one more Reel fits. Reels posted to the Page outside this platform are not counted, so Facebook can still refuse a Reel. Facebook accounts only; any other account gets `422 VALIDATION_ERROR`. Requires scope `social:read`. * **Reels over the limit are refused when you create or schedule them.** A post with `platform_options.facebook.type` set to `reel` is refused when a selected Page would be at the limit at the post's publish time (Reels published plus Reels already scheduled in the 24 hours before it). The response is `422 PLATFORM_DAILY_LIMIT_REACHED` with a message naming the Page and when it can post a Reel again, and `details` carrying `account_id`, `limit`, `used` and `next_available_at`. Nothing is created. * **Editing is checked too.** [`PATCH /social/posts/{post_id}`](/api-reference/social/update-social-post) refuses an update that would make a scheduled Reel go over the limit, with the same `422 PLATFORM_DAILY_LIMIT_REACHED` and `details`. The post being edited is not counted against itself. * **A scheduled Reel that still hits the limit fails with the reason.** That account's result fails with error code `PLATFORM_DAILY_LIMIT_REACHED` and a message starting "Facebook daily Reel limit reached". It is not retried automatically. * **New error code `PLATFORM_DAILY_LIMIT_REACHED`** - HTTP `422`, not retryable. Publish after `details.next_available_at`, or send the post as a different type. See [Errors](/guides/errors). See [Facebook](/platforms/facebook#reel-limit) for the details. ## Manage a Facebook Page's messages, profile picture and cover photo Four new endpoints turn the Page's Message button on or off and change its profile picture and cover photo, without anyone opening Facebook. * **Read the messaging setting** - [`GET /social/accounts/{account_id}/facebook/page/settings`](/api-reference/social/get-facebook-page-settings) returns `users_can_message`, or `null` when Facebook did not report it. * **Turn messaging on or off** - [`PATCH /social/accounts/{account_id}/facebook/page/settings`](/api-reference/social/update-facebook-page-settings) with `users_can_message`. `false` hides the Message button **and blocks replies in chats that already exist**; `true` restores both. * **Set the profile picture** - [`POST /social/accounts/{account_id}/facebook/page/picture`](/api-reference/social/set-facebook-page-profile-picture) with an image `url`. Facebook downloads the image itself, and no feed story or follower notification is created. * **Set the cover photo** - [`POST /social/accounts/{account_id}/facebook/page/cover`](/api-reference/social/set-facebook-page-cover-photo) with `url` and an optional `offset_y` (0-100, default 50). No feed story or notification. It needs the business permission on the connection; connections without it get `403 CONNECTION_SCOPE_MISSING` with `reconnect_required: true`, and reconnecting fixes it. A cover can be replaced through the API but not removed. * **Images are validated.** JPG, PNG, GIF, BMP or TIFF, 10 MB at most, and a cover of at least 400 x 150 px. Anything else is a `422 VALIDATION_ERROR` with `details.field` set to `url` and a `details.reason` saying why. * **`GET /social/accounts/{account_id}/facebook/page` now returns `picture` and `cover`**, and its response fields are documented. `cover` is absent when the Page has no cover photo. Test keys (`sqk_test_...`) validate the request, including the image URL, and return a success response without contacting Facebook. See [Facebook](/platforms/facebook) for the image rules and recommended sizes. ## Every endpoint says which scope it needs, and 30 more are listed Scopes were enforced but never published. The API has always answered `403` when a key lacks the right scope, and the [scope table](/authentication#scopes) has always listed what exists, but nothing told you which scope a given endpoint wanted. You found out from the 403. * **Every endpoint page now states its scope**, in the same "Requires scope `contacts:write`." form that some pages already carried by hand. The three endpoints needing two scopes say both, and `/me`, `/me/usage`, `/me/balance` and `/me/billing` now say plainly that any valid key works. * **The spec carries `x-scopes` on every operation**, so tooling can read the requirement instead of parsing prose. It is a vendor extension, so it changes nothing for existing OpenAPI clients. * **Thirty endpoints that were live but missing from these docs are now listed.** WhatsApp Flows (create, update, publish, deprecate, preview and the flow JSON), the WhatsApp sandbox sessions, the block list, and number bridging; automation versions, version restore and duplicate; `POST /media/{media_id}/confirm`; comment like, unlike and moderate; and deleting a direct message. They worked before, they were simply not documented. Scopes come straight from what the API enforces, so the docs cannot drift from the check any more. ## Facebook Stories `platform_options.facebook.type: "story"` now publishes to the Page's story tray through Meta's Page Stories API. It was refused earlier today, because nothing stood behind it. * **One photo or one video.** Video is MP4, 9:16, up to **60 seconds**; photo is up to 10MB. * **No caption.** Neither story endpoint takes a message, description or overlay, so the post text is **not published** - put the wording in the media itself. The create response returns a `warnings` entry when a story post carries text. * **The media must be new.** Meta refuses a photo or video that has already appeared in a published post, and refuses media served from its own CDN (`fbcdn.net`) - host your own copy. * **`post_url` comes back `null`.** A story has no permanent page and expires after 24 hours; `post_id` is always returned. `"stories"` works as an alias. Your Page token needs `pages_manage_posts` and `pages_read_engagement`. No new endpoint - it is the same `POST /social/posts` you already call. See [Platform options](/guides/platform-options). ## `link` on a post is published instead of dropped `link` has been documented on `POST /social/posts` as long as the endpoint has existed, and it was accepted without complaint. Nothing published it. The post went out carrying only its text - no link, no preview card - and the response was still a `200`, so there was no way to tell from the API that anything had been lost. Putting the URL in `platform_options.facebook.link` did the same thing, because unknown per-platform keys are ignored rather than rejected. * **`link` now reaches the platform.** On a Facebook text post it publishes as a real link post, so Facebook renders the preview card for it. * **On a post that carries media, the URL is appended to the post text.** The attachment is already the card there and Facebook's publishing API has no link parameter for it, so the URL is kept in the copy rather than dropped. It is not added twice if your text already contains it. * **Platforms that cannot carry a link now tell you so.** They come back in `data.warnings` on the create response, keyed by platform, instead of the link quietly disappearing for that leg of the post. * **`platform_options..link` wins over a request-level `link`**, so you can send one default and override it per platform. * **A malformed link is now a `422` rather than a silent drop.** Links must be `http`/`https`, carry a host, and stay under 2,048 characters. If you have been working around this by writing the URL into `content`, that still behaves exactly as before and needs no change. Move it to `link` when you want the preview card. See [Platform options](/guides/platform-options) for the per-platform keys. ## Facebook `type: "feed"` works, and an unknown post type now says so Our [platform options guide](/guides/platform-options) documented `platform_options.facebook.type` as `"feed"`. The publisher only ever accepted `"post"`, so a request following the documentation matched no handler, and the failure came back as a generic `UNKNOWN_PLATFORM_ERROR` with no detail. Text and image posts were unaffected because they carry no `type` at all - only posts that named a video format hit it. * **`"feed"` is now accepted**, along with `"video"`, `"photo"`, `"image"`, `"text"` (all the same as `"post"`) and `"reels"` (the same as `"reel"`). Existing calls that used any of these start working with no change on your side. The same aliases apply to Instagram, plus `"stories"` for `"story"`. * **A post type we cannot publish now returns `422 POST_TYPE_UNSUPPORTED`**, and the message lists the values that platform accepts. It used to be a `500` telling you to retry, which could never help. * **Facebook `type: "story"` is now refused.** It was previously accepted and then published to the timeline instead - there is no Facebook Stories publishing behind it. Instagram stories are unaffected and still publish normally. See [Platform options](/guides/platform-options) for the accepted values per platform. ## Ad metrics now cover one window on every platform The metric fields on `GET /ads/campaigns`, `/ads/ad-sets` and `/ads/ads` - `spent`, `impressions`, `clicks`, `conversions`, `leads`, `purchase_value`, `ctr`, `cpa`, `roas` - meant something different depending on the network. Meta reported its last 28 days, Google reported the campaign's **entire lifetime**, and TikTok and LinkedIn reported nothing at all (a row of zeros). One field, one name, four meanings. * **Every platform now reports the same last 28 days.** If you read these fields for Google campaigns, the values will drop - they were lifetime totals, not a recent window. TikTok and LinkedIn campaigns will show real figures for the first time. * **`GET /ads/analytics` gained a `by_campaign` block** - the same per-campaign figures, scoped to whatever `date_from`/`date_to` you ask for. Use it for any window other than the fixed 28 days, or to compare campaigns over a specific period. Twenty-eight days is what the ad networks give us daily figures for, so it is the longest total we can report without inventing the missing days. ## Bulk campaign status: 50 per call, and a 207 for partial batches Two changes to `POST /ads/campaigns/bulk-status`, both of which can affect existing integrations. * **The per-call limit is now 50 campaigns, down from 100.** Sending more returns `422`. The old limit was one this endpoint could not actually honour: every id costs a live write to the ad platform, and Meta's per-ad-account budget is spent long before 100 - so the tail of a large batch came back `Meta rate limit reached` and looked like your fault. 50 fits inside that budget, so every id you send can actually succeed. Split larger jobs across calls. * **A partly-failed batch now answers `207`, not `200`.** `200` means every id succeeded; `207` means at least one didn't. The body shape is unchanged either way, so you can keep reading per-item results exactly as before - but you no longer have to parse the body to find out whether anything failed. ## `DELETE` requests that send a body must declare `Content-Type` If you send a `DELETE` **with** a JSON body, it now needs `Content-Type: application/json`, the same rule `POST`/`PUT`/`PATCH` have always followed. Without it you get `400 INVALID_REQUEST`. This only affects deletes that carry a body - the confirmation ones like `DELETE /profiles/{id}` and `DELETE /workspaces/{id}`, which take `{"confirm":"DELETE"}`. Every bodyless delete (`DELETE /ads/campaigns/{id}`, `DELETE /ads/creatives/{id}`, and the rest) is untouched and still needs no such header. Worth checking if you call those endpoints with hand-written `curl`: `curl -d` defaults to `application/x-www-form-urlencoded`, not JSON, so add `-H "Content-Type: application/json"` explicitly. Our official SDKs already send it. ## Ads analytics, audience creation, targeting search, Boost Post, and Click-to-Message/Call Five additions to the Ads API shipped earlier today: * **`GET /ads/analytics`** - spend/impressions/clicks/conversions summary, a by-platform breakdown, a daily spend chart, and period-over-period trends. * **`POST /ads/audiences`** - real audience creation on Meta (Custom/Lookalike) or Google (remarketing/CRM user lists), not just the read-only listing from earlier today. * **`GET /ads/targeting-search`** - Meta interest type-ahead, for building a campaign's `targeting.interests`. * **Boost Post** - `POST /ads/campaigns` now accepts `creative.object_story_id` (`"{page_id}_{post_id}"`, from the new `GET /ads/pages/{page_id}/posts`) to turn an existing organic Page post straight into an ad - no new creative to build. * **Click-to-Messenger / Click-to-Call** - `settings.destination_type: "messenger"` with a `MESSAGE_PAGE` CTA opens a Messenger chat instead of a link; a `CALL_NOW` CTA plus `settings.phone_number` (E.164) dials a number instead. All five go through the exact same launch path and guardrails as everything else in the Ads API - nothing here is a separate/parallel flow. ## Ads API - campaigns, ad sets, ads, audiences, pixels, lead forms, and creatives The first ads/campaign endpoints on the public API, spanning Meta, Google, TikTok, and LinkedIn Ads. New `ads:read` / `ads:write` scopes. * **Campaigns, ad sets, ads** - full CRUD plus `pause`, `resume`, `duplicate`, and `archive` on each, and `POST /ads/campaigns/bulk-status` for pausing or resuming up to 100 campaigns in one call (per-item results, never all-or-nothing). * **Creating a campaign** - `POST /ads/campaigns` with `platform: meta` and a `page_id` launches a real campaign on Meta. Every other platform, or Meta without a `page_id`, creates a local draft - only Meta has live launch wiring today. * **Audiences, pixels, lead forms** - `GET /ads/audiences` and `GET /ads/pixels` are read-only mirrors of what's already on the platform. `POST /ads/lead-forms` creates a real Meta Instant Form (standard question fields, requires a privacy policy URL). * **Creatives** - a standalone asset library independent of any specific ad. `POST /ads/creatives` takes an already-hosted `file_url` - upload bytes through `POST /media/upload-direct` first, this endpoint doesn't handle raw uploads. * **Accounts & diagnostics** - `GET /ads/accounts` lists connected ad accounts; `GET /ads/accounts/diagnostics` adds live per-account API rate-limit usage and last-sync health. `GET /ads/audit-log` supports `campaign_id` to scope the log to one campaign and everything under it. * **Estimate** - `POST /ads/estimate` returns a real Meta audience-size range for a targeting spec before you launch. Every other platform returns `available: false` rather than an error, so you can branch on it cleanly - Google's forecasting tools need a keyword seed this endpoint doesn't collect, TikTok has no published estimate API for standard campaigns, and LinkedIn's geo-targeting IDs have no stable public mapping to resolve against yet. Every write goes through the same guardrails the in-app Ad Manager uses - anti-ban edit cooldowns on budget/name changes, per-platform rate limiting, and full audit logging. ## Media presign now works, plus a confirm step `POST /media/upload-url` previously returned a URL that wasn't actually connected to storage - a PUT there silently went nowhere while the API still reported success. It now issues a real presigned URL, so uploads through it actually land. New: `POST /media/{media_id}/confirm` - call this after PUTting to the presigned URL. It checks storage for the object and backfills the real `file_size` (and `mime_type`, if it differs from what you declared at presign time) - the presign flow has no other way to know your upload finished. Returns `422 NOT_UPLOADED` if nothing has landed yet. If you don't need the presign flow's two-step handshake, `POST /media/upload-direct` remains the simpler one-request path and was unaffected by this. ## WhatsApp sandbox and bring-your-own-number bridge * **Sandbox** - test WhatsApp end-to-end without connecting your own number. `POST /whatsapp/sandbox/sessions` activates a phone against a shared SmartlyQ-owned number (they reply to prove ownership), then `POST .../sessions/{session_id}/send` re-fires the locked sandbox template to actually see a message land. 50 sends/24h, one phone per user at a time. * **Number bridge** - already bought a phone number through SmartlyQ? `POST /whatsapp/numbers/{sender_id}/bridge` adds it to your existing WhatsApp Business Account connection, following Meta's real add → request-code → verify → register sequence. Once linked it's a normal connected WhatsApp number, usable anywhere `account_id` is accepted. Sandbox availability depends on the platform having configured a shared number (`503 SANDBOX_UNAVAILABLE` if not) - it's opt-in infrastructure, not guaranteed on every deployment. ## WhatsApp Flows and Block Users * **Flows** - build native in-chat forms (booking, surveys, lead capture) without leaving WhatsApp. `POST /whatsapp/flows` creates a DRAFT flow, `PUT .../flows/{flow_id}/json` uploads its screens, `POST .../flows/{flow_id}/publish` makes it sendable, and `POST /whatsapp/messages` with `type: "flow"` sends the opening CTA. Full CRUD plus a preview URL (`GET .../flows/{flow_id}/preview`) and deprecate. * **Block users** - `GET` / `POST` / `DELETE /whatsapp/block-users` blocks or unblocks WhatsApp users on a connected number, and lists who's currently blocked (up to 64,000, cursor-paginated). Meta only allows blocking someone who messaged your business in the last 24 hours; other targets come back in a `failed` array without failing the rest of the batch. Both run on the same `whatsapp_business_management` / `whatsapp_business_messaging` permissions every other WhatsApp endpoint already uses - no new connection or reconnect required. ## Comment moderation, message delete, and automation version history * **Comment moderation** - `POST /social/comments/{id}/moderate` approves or rejects a comment (with an optional author ban). YouTube only - uses the same underlying endpoint `POST .../hide` already used, just the full status set. * **Comment likes** - `POST` / `DELETE /social/comments/{id}/like`. Facebook only - Instagram's Graph API has no comment-like edge. * **Delete a sent message** - `DELETE /social/conversations/{conversation_id}/messages/{message_id}`. X and Bluesky only (Messenger, Instagram, and WhatsApp have no unsend API for a business-sent message). On Bluesky this removes the message from your side of the conversation only - their chat API has no unsend-for-everyone, and the response says so explicitly via a `note` field. * **Automation duplicate** - `POST /automations/{id}/duplicate`. Copies the graph into a new automation, always starting as `draft`. * **Automation version history** - `GET /automations/{id}/versions` (list), `GET .../versions/{version}` (one, with its graph), `POST .../versions/{version}/restore`. A snapshot is taken automatically before every graph edit, so you can always see - and undo - past changes. Restoring is itself just another edit, so it's never destructive. Each of these follows the same rule as reactions before them: if a platform can't do it, the API says so honestly rather than pretending to succeed. Comment edit (Reddit) and message edit/delete (Telegram) were investigated and are not included in this release - Reddit's granted OAuth scope has no edit permission, and Telegram's inbox integration doesn't exist yet (publish-only today). ## WhatsApp is now priced per destination and message type `POST /whatsapp/messages` no longer bills a single flat rate. The price now depends on the destination country and the message category, mirroring how WhatsApp itself prices: a utility message to the US and a marketing message to Germany are very different amounts, and one flat number could not represent both. Every send response now carries a `pricing` block showing exactly what was applied: ```json theme={null} "pricing": { "category": "utility", "market": "United States", "calling_code": "1", "rate": "0.003600" } ``` Two things worth knowing: * **You are charged only for a message accepted for delivery.** A send rejected by WhatsApp is refunded automatically, and validation failures are never charged at all. * **Free-form replies are billed as `service`**, priced at the destination market's utility rate, so a service reply to the US and one to Germany cost different amounts just like templates do. Template sends take their category from the template. If you already know it, pass `category` on the request and we will skip the lookup. ## Per-platform management: pages, boards, playlists, mentions, flairs The write-side counterparts to the platform lookups. Each one reports a missing permission as `403 CONNECTION_SCOPE_MISSING` with `reconnect_required: true`, so "this account was connected before we asked for that permission" is never a vague failure. * **Facebook page** - `GET` and `PATCH /social/accounts/{account_id}/facebook/page`. Read the page's about, description, category, contact details, address, hours and follower counts; edit the ones you want, sending only what changes. * **Pinterest** - `POST /social/accounts/{account_id}/pinterest/boards` creates a board to pin into. * **YouTube** - `PATCH /social/accounts/{account_id}/youtube/playlists/{playlist_id}` renames a playlist, edits its description, or changes its visibility. YouTube's own update replaces every field you submit, so we read the playlist first and merge rather than blanking what you left out. * **Mentions** - `GET /social/accounts/{account_id}/mentions` lists posts where a Facebook page or Instagram account was tagged, and `POST .../mentions/{mention_id}/reply` answers one. X keeps its own richer endpoint. * **Reddit flairs** - `GET /social/accounts/{account_id}/reddit/subreddits/{subreddit}/flairs`. Many subreddits reject a submission with no flair; read the flairs, then pass the id as `platform_options.reddit.flair_id` when you post. * **WhatsApp template library** - `GET /whatsapp/template-library` browses Meta's prewritten, pre-approved templates, and `POST /whatsapp/templates/from-library` adopts one under your own name. This skips the draft-and-wait-for-review cycle. New Facebook connections now also request the page-metadata permission, which is what page editing needs. Pages connected before today keep working for everything else and can be reconnected when you want to edit them. ## Developer logs, YouTube Analytics, webhook replay **Read your own logs** - `GET /logs` returns the same feed the Developer Logs screen shows: API calls and webhook delivery attempts in one time-ordered stream, 90-day retention, filterable by source, endpoint, status, platform, key and date range. Request and response bodies are opt-in via `include_payloads=true`. Uses a new `logs:read` scope, so a key that reads posts cannot read every request made with your other keys. **Replay a webhook delivery** - `POST /webhooks/deliveries/{id}/replay` re-sends a past delivery with its original payload, freshly signed, and resets retry backoff. This is the fix-up after a receiver was down; find delivery ids with `GET /webhooks/logs`. **YouTube Analytics** - four reports from the data behind YouTube Studio: * `GET /analytics/youtube/channel-insights` - views, watch time, average view duration and percentage, subscribers gained/lost, engagement. * `GET /analytics/youtube/daily-views` - the same headline metrics broken down by day. * `GET /analytics/youtube/video-retention` - the retention curve for one video, plus how it compares with similar videos. * `GET /analytics/youtube/demographics` - viewer split by age bracket and gender. These need a YouTube permission we did not previously request. **Channels connected before today must be reconnected**: until then these endpoints return `403 CONNECTION_SCOPE_MISSING` with `reconnect_required: true`, rather than a vague failure. **Message reactions** - `POST` and `DELETE /social/conversations/{conversation_id}/messages/{message_id}/reactions` react as the connected account on Messenger, Instagram, Bluesky and WhatsApp. **Connect Bluesky without a browser** - `POST /social/connect/bluesky` now accepts `identifier` + `app_password` (an app password from Bluesky settings, never your account password) and connects the account immediately instead of returning a `connect_url`. **Update a short link** - `PATCH /urls/{id}` repoints an existing short URL at a new destination, renames it, or deactivates it. The short code never changes, so links already in the wild keep working and keep their click history. **More WhatsApp management** - per-template `GET`, `PATCH` and `DELETE /whatsapp/templates/{name}`, plus `POST /whatsapp/business-profile/photo` and `GET`/`POST /whatsapp/business-profile/display-name`. ## WhatsApp sends are now billed `POST /whatsapp/messages` now charges your API wallet per message, and the response carries the usual `usage` block with units, cost and remaining balance. Phase 1 shipped unbilled while pricing was settled; your plan's monthly WhatsApp send limit still applies on top. ## WhatsApp Business messaging (Phase 1) SmartlyQ now connects to the **WhatsApp Business Cloud API**: * **Connect** a WhatsApp Business number - `POST /social/connect/whatsapp` with `{ waba_id, phone_number_id, access_token }`. * **Send** - `POST /whatsapp/messages`: text and media (session messages, inside the 24-hour window) and approved templates (any time). * **Receive** - inbound messages and delivery receipts arrive on your webhooks and populate the in-app Inbox. New events: `message.delivered`, `message.read`, `message.failed` (joining `message.received`). * **Manage** - list/create message templates (`/whatsapp/templates`), read/update the business profile (`/whatsapp/business-profile`), and list phone numbers (`/whatsapp/phone-numbers`). See the new [WhatsApp guide](/guides/whatsapp). Flows, calling, and groups are planned for a later phase. ## WhatsApp: interactive buttons, reply threading, reactions, location, contacts `POST /whatsapp/messages` now sends five more message types, and every send can thread as a reply: * **`interactive_buttons`** - up to 3 tappable reply buttons; the customer's tap arrives as a normal inbound message. * **`interactive_list`** - a tappable list picker, up to 10 rows across sections. * **`location`** - share a pin (latitude/longitude, optional name/address). * **`contacts`** - share one or more contact cards. * **`reaction`** - react to (or remove a reaction from) any message. Inbound reactions from the customer now arrive on the `reaction.received` webhook instead of being dropped. * **`context_message_id`** - pass on any send type to thread it as a quoted reply under a prior WhatsApp message id. Voice notes sent via `type: "media"` with `media.kind: "audio"` are now automatically transcoded to WhatsApp's OGG/Opus voice-note format when needed, so a browser-recorded clip renders as a playable voice note instead of a generic attachment. See the updated [WhatsApp guide](/guides/whatsapp#interactive-buttons-and-lists). ## X reply threads with media `first_comments` on X now post as a **chained reply thread** (the first replies to the post, each next one to the previous), and any entry can attach media: pass `{ "text": "...", "media_urls": ["https://..."] }` instead of a plain string to put up to 4 images on that reply. Same in the app composer - first comments on X now offer image **and video** attachment (one video per reply, X's limit). See [Platform options](/guides/platform-options#x-twitter). ## X (Twitter) comment threads, and more * **X comment threading fixed** - replies on X posts now nest correctly. Previously the reply tree never linked (we keyed on the replied-to user instead of the parent tweet), so X threads came back flat. Reply nesting now works for every platform except Pinterest (whose comments are inherently flat). This fix applies to the app's comment view as well as the API. * **`GET /social/comments/{post_id}`** - fetch one post's comments as a nested thread by id, instead of filtering the full comments list. * **`PUT` / `DELETE /contacts/{id}/fields/{slug}`** - set or clear a single contact custom field by its slug, without sending the whole attributes object. * **`GET /me/billing`** - a read-only superset of `/me/balance`: balance breakdown, monthly cycle reset, auto-recharge config, and card-on-file status with an at-a-glance `payment.status`. ## Profile update and direct media upload * **`PATCH /profiles/{id}`** - rename a profile or change its `external_id` without recreating it (renaming keeps the backing workspace name in sync). Fills the last gap in profile management. * **`POST /media/upload-direct`** - upload a file in a single `multipart/form-data` request, no presign round-trip. Stored straight to your media library and returned in the same shape as `GET /media/{id}`; the type is detected from the file's actual bytes, up to 200MB. ## 10 new webhook events The event catalog grows from 25 to 35, covering lifecycle moments you could already act on internally but couldn't subscribe to: * **Posting** - `post.scheduled` (a post was queued for later), `post.cancelled` (a scheduled post was deleted before publishing), `post.recycled` (a recycling chain spawned its next occurrence), and `post.external.created` / `post.external.updated` for posts authored natively on the platform and picked up by external sync. * **Inbox** - `message.sent` (outbound DM delivered), `conversation.started` (a new thread opened), `reaction.received` (an emoji reaction added or removed). * **Reviews** - `review.new` and `review.updated` for Google Business reviews and replies. All fire from both the app and the API where applicable, carry the same signed, deduplicatable envelope as every other event, and are subscribable from the dashboard, the API, and MCP. See the [Webhooks guide](/guides/webhooks). ## Idempotent creates, per-platform stagger, richer platform options * **Idempotency keys** - send `X-Idempotency-Key` (or `X-Request-Id`) on `POST /social/posts` / `/schedule`; a retry with the same key returns the original post (`200`, `idempotent_replay: true`) instead of double-posting. Backed by a unique constraint, so even concurrent retries can't create twice. Works alongside the existing 24-hour duplicate-content guard (`409 DUPLICATE_CONTENT` + `allow_duplicates`). New [Idempotency guide](/guides/idempotency). * **Per-platform stagger** - on the schedule endpoint, any platform can carry its own `scheduled_time` inside `platform_options`: one call, each network publishing at its own moment (response lists the created `staggered[]` legs). * **Platform options, documented and extended** - new [Platform options guide](/guides/platform-options) covering every per-platform key. Newly added: Telegram `parse_mode`/`silent`/`disable_link_preview`/`protect_content`, Reddit `flair_id`/`flair_text`, YouTube `tags`/`category_id`/`contains_synthetic_media` (AI disclosure), Instagram `share_to_feed` for Reels. ## Connect Telegram by API, Discord headlessly Every platform can now be connected without the SmartlyQ dashboard: * **Telegram credential connect** - `POST /social/connect/telegram` with `{ "bot_token", "chat" }` connects the channel immediately (Telegram has no OAuth; this is the same verify-then-save flow as the dashboard modal: bot verified with Telegram, posting rights required, private chats rejected, plan limit enforced, token encrypted at rest). Built for agencies automating workspace onboarding. * **Discord headless connect** - `POST /social/connect/discord` mints the server-webhook authorize URL like any OAuth platform. ## Telegram and Discord publishing Two new `platforms` targets on `POST /social/posts` and `POST /social/posts/schedule`: * **`telegram`** - publishes through your own bot (created with [@BotFather](https://t.me/BotFather)) to its channel or group: text up to 4,096 characters, single media or 2-10-item albums sent by URL. Connected from the dashboard's Social Accounts page. * **`discord`** - publishes through a channel webhook on your server: text up to 2,000 characters, up to 10 images as embeds, posting under your workspace brand's name and logo when one is set. Both platforms respect the same pre-publish validation, auto-transcode, progress states, and webhook events as every other target. Discord also supports [headless connect](/guides/connecting-accounts) (`POST /social/connect/discord`) - its server-webhook authorize flow works from your own UI like any OAuth platform. See the new [Telegram](/platforms/telegram) and [Discord](/platforms/discord) platform pages. ## Auto-transcode, publish progress, and exact failure reasons One `POST /social/posts` call now does more of the hard work invisibly: * **Auto-transcode** - media that a platform would reject for a fixable reason is converted automatically before publishing: video that isn't H.264/AAC MP4 is re-encoded (web-optimized, moov-front), frame rates are brought into each platform's documented range (raised to 30fps below TikTok/Instagram's 23fps floor, capped at 60fps), oversize videos are shrunk under the platform's byte cap, and images in formats a platform rejects are converted (e.g. WEBP to PNG for LinkedIn). What was fixed is reported per platform in the post's `post_urls._transcodes` and in the `transcoded[]` list on `post.published` / `post.partial` / `post.failed` webhook payloads. Changes that would alter your content - trimming duration, cropping aspect ratios - are never made automatically; those still return a precise error telling you what to change. * **Publish progress** - posts now expose `publish_stage` (`validating` → `processing_media` → `publishing`) while a publish is running, so `GET /social/posts/{id}` shows where a slow publish is instead of a bare `processing`. * **Exact failure reasons, end to end** - a failed post always carries its reason now: whole-post failures persist an explanation in `post_urls._errors`, immediate (non-scheduled) posts store per-platform permalinks and errors just like scheduled ones, and webhook failure payloads include `error.detail` with the exact pre-publish reason (e.g. `"Twitter/X caption is 312 characters; the limit is 280. Trim 32 characters and retry."`) whenever the failure came from SmartlyQ's own checks. ## Rich DMs, comment-to-DM automations, connection selection, deep insights * **Structured DMs** - `POST /social/conversations/{id}/messages` now takes `buttons` (Facebook button template, up to 3 links) and `quick_replies` (Facebook + Instagram chips, up to 13), plus a typing indicator at `POST .../typing`. * **Comment-to-DM** - `POST /social/comments/{comment_id}/private-reply` DMs the comment's author (Meta private replies, FB + IG). New `comment_created` automation trigger + `action.private_reply` runner action: build "comment X gets an automatic DM" flows in the automation builder. * **Messaging surfaces** - Messenger persistent menu and Instagram ice breakers, full CRUD under the account. * **Connection selection** - `GET .../connect-options` + `POST .../connect-select` re-point a Google Business connection at any location of its parent account; platforms with entity-bound tokens report why they can't switch without reconnect. * **Deep insights** - `GET .../facebook/page-insights` (any Meta page metric, passthrough) and `GET .../instagram/audience` (follower demographics by city/country/age/gender). ## Subreddit eligibility, X mentions, contact channels * **Subreddit check** - `GET /social/accounts/{account_id}/reddit/subreddits/{subreddit}` returns the community's rules of engagement plus a computed `can_post` verdict for your connected account (banned? restricted? what submission kinds?). * **X mentions** - `GET /social/accounts/{account_id}/x/mentions` lists recent posts mentioning the account, with engagement metrics. * **Contact channels** - `GET /contacts/{id}/channels` shows which channels a contact actually communicates on, with volume and last activity. * **Custom field update** - `PATCH /custom-fields/{id}` edits a field definition (the key stays immutable). * **Place-action update** - `PATCH /social/accounts/{account_id}/gmb/place-actions` changes a link's URI in place. ## Google Business Profile management Sixteen endpoints under `/social/accounts/{account_id}/gmb/*`, riding the connection's existing permissions: list all locations, read and update business info (hours, phone, categories, description) with field masks, manage attributes (with a metadata endpoint that tells you what this location supports), publish and remove photos, edit structured food menus, manage place-action links (order/reserve/appointment), and inspect verification state and options. Bodies are passthroughs of Google's resource shapes - every Google field works without waiting for us. ## Webhooks scopes are now grantable The `/webhooks` endpoints require the `webhooks:read` / `webhooks:write` scopes - but the key-creation surfaces never offered them, so freshly created keys received `403 INSUFFICIENT_SCOPE` on every webhook call. The scope picker in the Developer Dashboard (and the OAuth catalog) now includes both. **Existing keys are unchanged**: edit your key's scopes or create a new key to pick up webhook access. ## Edit published posts, external sync, live insights * **Edit published** - `POST /social/posts/{post_id}/edit` edits the post natively on the platform. Facebook works today; X (paid-tier API) and Reddit (extra scope) report unsupported per platform instead of failing. * **YouTube metadata** - `POST /social/posts/{post_id}/update-metadata` updates title/description/tags, adds to a playlist, and sets a custom thumbnail after publish. * **External post sync** - `POST /social/posts/sync-external` imports posts published natively on Facebook, Instagram, Threads, YouTube or TikTok (per-account opt-in); they appear in `GET /social/posts?status=external` with analytics. * **Live account insights** - `GET /social/accounts/{account_id}/insights` returns platform-fresh follower/impression/reach/engagement metrics for every provider-backed platform. ## Per-platform post options, X retweets, story insights, safe retries * **platform\_options** - create, schedule, and bulk rows now accept the same per-platform options object the web composer uses: TikTok privacy/duet/stitch, per-platform caption overrides, custom video thumbnails, and more. Publish handlers consume it directly. * **X engagement** - `POST /social/accounts/{account_id}/x/retweets` and its DELETE counterpart retweet/un-retweet with the connected account. * **Story insights** - `GET .../instagram/stories/{story_id}/insights` with a passthrough `metrics` parameter. * **Delete review reply** - `DELETE /reviews/{review_id}/reply` removes the reply on Google Business. * **Idempotency, documented** - every write endpoint has always honored `X-Idempotency-Key` (24h replay window, never double-billed); it is now documented in the API intro. ## Reddit research tools, Instagram stories, Facebook reactions * **Reddit** - four research endpoints under `/social/accounts/{account_id}/reddit/`: `search` (optionally restricted to a subreddit), `feed` (hot/new/top/rising), `subreddits` (your subscriptions), and `subreddits/{subreddit}/rules` - check the rules before you submit. * **Instagram stories** - `GET /social/accounts/{account_id}/instagram/stories` lists the account's currently live stories. * **Facebook reactions** - `GET /social/accounts/{account_id}/facebook/post-reactions` returns the per-type breakdown (like, love, wow, haha, sad, angry, care) for any published post. ## Per-platform lookups Five account-level lookups so integrations can compose with real platform data - all `social:read`: * **Pinterest boards** - `GET /social/accounts/{account_id}/pinterest/boards` * **YouTube playlists** - `GET /social/accounts/{account_id}/youtube/playlists` * **Instagram publishing limit** - `GET /social/accounts/{account_id}/instagram/publishing-limit` (daily quota total/remaining) * **Google Business performance** - `GET /social/accounts/{account_id}/gmb/performance?days=28` (impressions by surface, deltas, daily series) * **Google Business search keywords** - `GET /social/accounts/{account_id}/gmb/search-keywords` ## Automations API + contact bulk import * **Operate your automations** - new `/automations` group: list and inspect automations built in the visual builder, activate/pause them, queue a manual run with `POST /automations/{automation_id}/trigger` (optional JSON payload), and read run history down to per-step execution logs. * **Bulk import** - `POST /contacts/bulk` imports up to 100 contacts through the same email/phone upsert as single create - re-importing a file is idempotent, with per-row created/updated/failed results. * **Delete** - `DELETE /contacts/{id}` soft-deletes (recoverable from Deleted contacts in the app). ## Inbox: search, detail, archive * **Search** - `GET /social/conversations/search?q=` scans participant names and message content across the whole inbox and returns the latest matching message per conversation. * **Detail** - `GET /social/conversations/{conversation_id}` adds a single-conversation view with incoming/outgoing counts. * **Archive** - `PATCH /social/conversations/{conversation_id}` with `{ "status": "archived" }` (or `"open"` to restore). * **Drill-down analytics** - `GET /analytics/inbox/conversations` (+ `/{conversation_id}`) closes out the inbox analytics suite with per-conversation volume and response times. ## Reviews API Reputation Management is now scriptable: * **List** - `GET /reviews` with rating / reply-status / account filters. * **Reply** - `POST /reviews/{review_id}/reply` publishes your reply on Google Business. * **Sync** - `POST /reviews/sync` pulls fresh reviews on demand, per account or workspace-wide. ## Inbox analytics Five endpoints under `/analytics/inbox/*` (all `analytics:read`, unbilled): daily volume with new-conversation counts, a weekday x hour incoming-message heatmap in your timezone, per-platform source breakdown, first-response times (average, median, histogram from under 15 minutes to over 24 hours), and your busiest accounts. ## Analytics that answer questions Five derived endpoints, all computed from the snapshots SmartlyQ already collects - fast, unbilled, `analytics:read`: * **Daily metrics** - `GET /analytics/daily-metrics`: per-day aggregates + platform breakdown + posts published. * **Best time to post** - `GET /analytics/best-time?timezone=...`: 7x24 engagement heatmap and your top 5 slots. * **Post timeline** - `GET /analytics/posts/{post_id}/timeline`: every metric snapshot for one post, per platform. * **Content decay** - `GET /analytics/content-decay`: average share of final engagement by post age (day 0-13). * **Posting frequency** - `GET /analytics/posting-frequency`: weekly post count vs average engagement. ## Account groups and profile moves * **Account groups** - full CRUD under `/social/account-groups`: name a set of connected accounts (a client, a brand) and address it with one id. Up to 50 groups per workspace, 100 accounts per group; membership is validated against your workspace on every write. * **Move to profile** - `POST /social/accounts/{account_id}/move` relocates a connected account between your developer workspace and your profiles (`profile_id: 0` moves it back). Requires `social:write` + `profiles:manage`, developer context only, and refuses with 409 while the account has scheduled or in-flight posts. ## Account insights * **Bulk health** - `GET /social/accounts/health` reports every connected account's token health in one call: healthy / expiring / expired / paused tallies plus per-account detail. No more N+1 health polling. * **Follower stats** - `GET /social/accounts/follower-stats?days=30` returns daily follower history, growth, and growth percentage per account from the platform's analytics snapshots. * **Rename** - `PATCH /social/accounts/{account_id}` updates the account's display name. * **TikTok creator info** - `GET /social/accounts/{account_id}/tiktok/creator-info` surfaces live privacy levels, comment/duet/stitch availability, and max video duration - the fields TikTok requires you to show before posting. ## Bulk scheduling and duplicate protection * **Bulk schedule** - `POST /social/posts/bulk` schedules up to 50 posts in one call, from a JSON `posts` array or an inline `csv` string (header: content, platforms, account\_ids, scheduled\_time, media\_urls, link). Billed one post unit per row x platform. A structural problem in any row rejects the whole batch with 422 before anything is created - and the charge is refunded. * **Free batch dry-run** - `POST /social/posts/bulk/validate` returns a per-row report (structure, per-platform caption/media limits, duplicate check) without creating or billing anything. * **24h duplicate-content guard** - `POST /social/posts` and `POST /social/posts/schedule` now reject content identical to a post created in the same workspace within the last 24 hours with `409 DUPLICATE_CONTENT` (charge refunded). Pass `allow_duplicates: true` to override. This catches retry loops and mis-wired crons before they spam your channels. ## Evergreen post recycling Keep your best content in rotation automatically. * **Recycle on schedule** - `POST /social/posts/schedule` now accepts an optional `recycle` object: `{ interval_hours, max_repeats?, until? }`. After each occurrence publishes, the next one is scheduled automatically. A bound is required (`max_repeats` up to 100 and/or `until` up to 2 years out) - no unbounded chains. * **Stop a chain** - `DELETE /social/posts/{post_id}/recycle` clears the config across the whole chain and cancels any not-yet-published occurrences back to draft. Works from any post in the chain. * Occurrences inherit content, media, accounts, UTM tags, and approval status; `GET /social/posts/{post_id}` shows `recycle_count` and `recycle_parent_id` for lineage. ## Pre-flight validation Catch problems before they cost a publish attempt. Both endpoints are free, unbilled, and need only `social:read`. * **Validate a post** - `POST /social/validate/post` dry-runs the exact checks the create path uses and returns a per-platform report: character count vs. the platform's caption limit, required-media rules, and media extension screening. Nothing is created. * **Validate a media URL** - `POST /social/validate/media` confirms a URL is publicly reachable and reports its content type, byte size, and detected kind (image/video). Private and internal hosts are rejected. See the [Validation reference](/api-reference) for the full report shape. ## Posting queues and unpublish Two of the most-requested scheduling powers land in the API. **Posting queues** - define a recurring weekly slot schedule once, then drop posts into it without computing times: * `GET/POST /social/queues`, `GET/PUT/DELETE /social/queues/{queue_id}` - manage queues (slot map `{"Mon": ["09:00"], ...}` + IANA timezone) * `GET /social/queues/{queue_id}/next-slot` and `/preview?count=N` - see upcoming open slots * `POST /social/posts/schedule` now takes `queue_id` instead of `scheduled_time`: the next open slot is resolved server-side and the post occupies it (FIFO), so concurrent calls fill consecutive slots * Bonus: `scheduled_time` now honors an optional `timezone` field (previously UTC-only) **Unpublish** - `POST /social/posts/{post_id}/unpublish` deletes a published post from its native platforms while keeping the record (status becomes `unpublished`). Optional `platforms` array restricts targets; per-platform results are returned, and if any native delete fails nothing changes so you can retry. Supported: Threads, Facebook, LinkedIn, YouTube, X, Pinterest, Tumblr, Bluesky, Google Business. ## Webhooks: update, test, and delivery logs via API Webhook endpoints can now be fully managed and debugged without opening the dashboard. * `PUT /webhooks/{id}` - change the URL or subscribed events, or pause/resume deliveries (`status: active | paused`); re-activating also recovers an endpoint auto-paused after repeated failures * `POST /webhooks/{id}/test` - sends a `webhook.test` event through the real signing pipeline and returns the result synchronously (`delivered`, `response_code`, response excerpt) * `GET /webhooks/logs` - paginated delivery log across your endpoints with the full envelope, response code/body excerpt, attempt count, and next retry time; filter by `webhook_id`, `event`, or `status` * `job.completed` / `job.failed` now actually fire - these events were in the catalog but had no emit point; every async job now emits exactly once on its terminal transition with `{job_id, type, status, error?}` * Stricter validation: `POST /webhooks` and `PUT /webhooks/{id}` reject unknown event names outright instead of silently dropping them * Guardrail: webhook routes reject the `X-Profile-Id` header - webhooks live on your developer workspace and receive profile events via fan-up ## Webhooks: unified 25-event catalog with machine-readable schemas The webhook documentation is now a single source of truth, aligned with what the platform actually delivers. * The [Webhooks guide](/guides/webhooks) now documents the complete **25-event catalog** (posting, accounts, inbox, jobs, billing & keys, CRM) with the `data` fields each event carries * Every event now ships a **JSON payload schema** in the OpenAPI spec's standard `webhooks` object - SDK generators and AI agents can consume delivery shapes directly from [openapi.json](https://docs.smartlyq.com/openapi.json) * `POST /webhooks` now documents the exact allowed `events` values (enum) instead of a free-form string array * Corrected legacy event names that appeared in older docs (`social.posted` → `post.published`, `wallet.low` → `balance.low`); the delivery envelope is documented as `{id, event, created_at, data}` with the `X-SmartlyQ-Event-Id` dedupe header * Documented previously-missing delivery semantics: `Retry-After` honored on 429 (capped 1h), dead-letter after the 5th attempt, auto-pause after 10 consecutive failures, 90-day delivery retention, 10 webhooks per workspace ## Profiles: build your own multi-tenant product Profiles turn the SmartlyQ API into infrastructure for YOUR product: create a profile per end-customer, let them self-connect their social accounts through a single-use hosted link (no SmartlyQ login), and operate the whole API inside their profile with one header. * `POST /profiles`, `GET /profiles`, `GET /profiles/{id}`, `GET /profiles/{id}/accounts`, pause / resume / delete * `POST /profiles/{id}/connect-link` - hosted, single-use connect page for your customer * `POST /profiles/{id}/connect/{platform}` - raw per-platform OAuth URLs for your own UI * `X-Profile-Id` header on existing endpoints - data scopes to the profile; billing, rate limits and logs stay on your account * `GET /me/account-billing` - live billable-account count and charge estimate * Per-account monthly billing from your API wallet: \$6 each for accounts 1-10, \$3 for 11-100, \$1 from 101 up * New webhook events `account_billing.charged` / `account_billing.failed`, plus every in-profile event now fans up to your webhooks with `profile_id` attached * New scopes: `profiles:read`, `profiles:write`, `profiles:manage` See the [Profiles guide](/guides/profiles). ## More platform targets + pre-publish validation `POST /social/posts` and `POST /social/posts/schedule` now accept **10 platform targets**, adding Google Business, Threads, Bluesky, and Tumblr to the existing set. Every post now runs per-platform pre-publish validation (media formats, caption length, platform constraints), so invalid posts fail fast with an actionable `ValidationError` instead of failing at publish time. ## SaaS plan reads + bulk sub-account actions Agency and SaaS builders can now read their billing configuration and act on many sub-accounts at once: * `GET /saas/plans` and `GET /saas/plans/{id}` - list the SaaS plans configured on your account * `GET /workspaces/{id}/subscription` - a workspace's current plan and status * `GET /workspaces/{id}/wallet` - a workspace's credit wallet balance * `POST /workspaces/bulk` - pause, resume, or delete many workspaces in one call (SaaS-plan gated) See the new [Agency sub-accounts & SaaS guide](/guides/agency-saas). ## Workspace lifecycle management Full workspace (sub-account) lifecycle on the public API: * `POST /workspaces` - provision a new sub-account * `GET /workspaces` and `GET /workspaces/{id}` - list and inspect * `POST /workspaces/{id}/pause` and `/resume` - suspend and restore access * `POST /workspaces/{id}/disable-saas` - detach a workspace from your SaaS plan * `DELETE /workspaces/{id}` - guarded delete with async teardown ## CRM API: contacts, opportunities, custom fields The CRM is now fully scriptable: * Contacts: create, list, get, update, tag management, notes, `POST /contacts/{id}/enroll` (enroll in an automation), and `POST /contacts/{id}/messages` (log a message to the timeline) * Custom fields: `GET`/`POST /custom-fields`, `DELETE /custom-fields/{id}` * Pipelines and opportunities: full CRUD plus `POST /opportunities/{id}/status` for stage moves * Outbound CRM events are delivered through your existing [webhooks](/guides/webhooks) All CRM endpoints are also exposed as MCP tools. ## Presentations API Generate full AI slide decks from a single prompt: * `POST /presentations/generate` - builds a complete deck (3 to 12 slides, language and tone options) and returns the presentation id plus edit and share URLs * `GET /presentations`, `GET /presentations/{id}`, `DELETE /presentations/{id}` New scopes: `presentations:write` and `presentations:read`. ## Async SEO lookups Long-running SEO operations (`/seo/site-audit`, `/seo/rank-history`, `/seo/brand-lookup`, `/seo/prompt-explorer`, `/seo/ai-audit`) now return `202 Accepted` with a job id instead of holding the connection open. Poll `GET /jobs/{job_id}` for the result. Hot reads are cached, so repeated lookups on the same target return faster. ## AI visibility endpoints Three new SEO endpoints for the AI-search era: * `POST /seo/ai-audit` - audit how visible a domain is to AI assistants; also delivers a PDF report by email * `POST /seo/brand-lookup` - how a brand appears across search and AI surfaces * `POST /seo/prompt-explorer` - discover the prompts where a domain is (or is not) cited ## Shorts + video editing AI Turn long videos into ranked short clips, and use the editor's AI helpers directly: * `POST /shorts/generate` - split a long video (or a source URL) into ranked viral clips; poll `GET /shorts/{uid}` * `POST /videos/hook` - a scroll-stopping overlay line for a video * `POST /videos/broll-suggest` and `POST /videos/emphasis` - B-roll and emphasis suggestions from a word-level transcript * `POST /videos/viral-thumbnail` - generate a thumbnail from a title * `POST /content/caption` - platform-tuned social captions ## Logs overhaul The Developer Portal's Logs tab is now a unified request feed with endpoint, status, and platform filters, plus CSV export. Sanitized request payloads are captured for easier debugging. ## Billing correctness hardening Wallet charges are now fully idempotent (safe retries with `Idempotency-Key` can never double-charge), refunds are serialized against the wallet row, and priced units fail closed. Billed requests that end in a `4xx`/`5xx` are automatically refunded to the original credit buckets. ## Webhooks: unified event catalog + signed envelope Webhooks are now generally available with a unified 13-event catalog: `post.published` / `post.failed` / `post.partial`, `account.connected` / `account.disconnected` / `account.token_expired`, `comment.received`, `message.received`, `job.completed` / `job.failed`, `balance.low`, `balance.depleted`, `key.revoked` Every delivery carries a canonical envelope `{ id, event, created_at, data }` with headers `X-SmartlyQ-Event` and `X-SmartlyQ-Event-Id` (stable across retries), signed with `X-SmartlyQ-Signature` (HMAC-SHA256, timestamped). Deliveries retry 5 times with exponential backoff before dead-lettering. See the [Webhooks guide](/guides/webhooks). ## SEO API launch 14 SEO endpoints under `/seo/*` with the new `seo:read` scope: keyword research, SERP analysis, keyword difficulty, ranked keywords, domain overview, competitors, backlinks summary, backlink prospects, referring domains, backlink anchors, spam score, rank history, site audit, and on-page audit. ## Playground + automatic refunds * The in-app **API Playground** can now run real requests against any of your keys (scopes, billing, and rate limits all apply; write calls need explicit confirmation) * Charges are taken upfront and **automatically refunded** if the request fails * Wallet ledger rows now carry human-readable descriptions ## Headless connect for all 13 platforms End-to-end account connection without the SmartlyQ UI: * `GET /social/connect/{platform}` - start a headless OAuth connect for any of the 13 platforms and poll connection status * `GET`/`POST /webhooks` and `DELETE /webhooks/{id}` - manage webhook subscriptions from the API, including a deliveries index and dead-letter view * Post results now surface **per-platform publish failure reasons** * Error envelope extended with `retryable`, `fix`, and `docs_url` fields * Platform posting rate limits are enforced at create time with clear errors ## Comments, DMs, and account lifecycle The social engagement surface arrives on `/v1`: * Comments: `GET /social/comments`, `POST .../reply`, `POST .../hide`, `DELETE /social/comments/{id}` * Inbox: `GET /social/conversations`, `GET`/`POST .../messages`, `POST .../read` * Accounts: `GET .../health`, `GET .../reconnect-url`, `POST .../pause`, `POST .../resume`, `DELETE /social/accounts/{id}` * `POST /social/posts/{post_id}/retry` - retry only the platforms that failed All list responses use the standard `data` + `pagination` envelope. # Agency sub-accounts & SaaS Source: https://docs.smartlyq.com/guides/agency-saas Manage agency sub-accounts over the API, and which actions are reserved for SaaS-plan members. If your API key's workspace is an **agency** (a whitelabel tenant), you can manage its client **sub-accounts** over the API: create them, list them, pause and resume, delete, and — for SaaS-plan members — run bulk lifecycle actions. All sub-account endpoints require your key's workspace to be attached to an **agency tenant**. Other keys receive **409 `NOT_ELIGIBLE`**. ## Two tiers of access Basic sub-account management is available to any agency. **SaaS-mode actions are reserved for SaaS-plan members** - the tier that lets an agency bill its own clients. | Capability | Endpoint | Requires | | -------------------- | -------------------------------------------- | ----------------------------------------- | | Create a sub-account | `POST /v1/workspaces` | Agency tenant · `workspaces:write` | | List sub-accounts | `GET /v1/workspaces` | Agency tenant · `workspaces:read` | | Get a sub-account | `GET /v1/workspaces/{id}` | Agency tenant · `workspaces:read` | | Pause / resume | `POST /v1/workspaces/{id}/pause` · `/resume` | Agency tenant · `workspaces:write` | | Delete | `DELETE /v1/workspaces/{id}` | Agency tenant · `workspaces:write` | | **Disable SaaS** | `POST /v1/workspaces/{id}/disable-saas` | **Active SaaS plan** · `workspaces:write` | | **Bulk lifecycle** | `POST /v1/workspaces/bulk` | **Active SaaS plan** · `workspaces:bulk` | ## SaaS-member only: SaaS actions SaaS-mode actions — **disabling SaaS** on a sub-account and all **bulk** lifecycle actions — are **SaaS-member only**. Your agency must be on an active SaaS plan: it must have completed Stripe Connect onboarding to bill clients (the equivalent of a SaaS Pro tier). This is a **stable** entitlement. Once your agency is set up for SaaS, it stays enabled even if your Stripe processor later reports a transient issue (a dispute, a review, a brief disconnect) — so you can always manage and unwind SaaS on your clients. It only turns off if you fully disconnect SaaS billing. If your agency is not on an active SaaS plan, the SaaS endpoints (`POST /v1/workspaces/{id}/disable-saas` and `POST /v1/workspaces/bulk`) return **403 `SAAS_PLAN_REQUIRED`**. Connect a client-billing Stripe account first. `POST /v1/workspaces/bulk` applies one action — `pause`, `resume`, or `disable_saas` — to up to **200** client sub-accounts at once. It requires the dedicated **`workspaces:bulk`** scope (separate from `workspaces:write`), because a mass action is high blast-radius. Because `disable_saas` is irreversible (it cancels each client's subscription and refunds each sub-wallet to the agency), it also requires a typed confirmation. ```bash theme={null} curl -X POST https://api.smartlyq.com/v1/workspaces/bulk \ -H "Authorization: Bearer sqk_live_xxxxxxxxxxxx" \ -H "Content-Type: application/json" \ -d '{ "workspace_ids": [101, 102, 103], "action": "disable_saas", "cancel_subscription": true, "confirm": "BULK" }' ``` The action is applied per workspace, and the response reports each outcome: ```json theme={null} { "ok": true, "action": "disable_saas", "applied": 2, "failed": 1, "results": [ { "workspace_id": 101, "ok": true, "error": null }, { "workspace_id": 102, "ok": true, "error": null }, { "workspace_id": 103, "ok": false, "error": "not_found" } ] } ``` | Field | Meaning | | ----------------- | ---------------------------------------------------- | | `applied` | Workspaces the action succeeded on | | `failed` | Workspaces that were skipped (see `results[].error`) | | `results[].error` | Per-workspace failure reason (`null` on success) | ## Not on a SaaS plan ```json theme={null} { "success": false, "error": { "code": "SAAS_PLAN_REQUIRED", "message": "This endpoint requires an active SaaS plan: connect a Stripe account that can bill your clients before using SaaS/bulk actions." } } ``` Basic sub-account management (create, list, get, pause, resume, delete) stays available to every agency. Only the **SaaS-mode** actions — disable-SaaS and bulk — require an active SaaS plan. Because the gate is a stable entitlement rather than a live payment-processor check, a temporary Stripe issue never blocks you from unwinding SaaS. # Async Jobs Source: https://docs.smartlyq.com/guides/async-jobs How long-running operations work with the job queue. ## Overview Some API operations (article generation, video creation, image generation, presentation generation) are **asynchronous**. Instead of returning the result immediately, the API returns a `job_id` that you poll until the result is ready. ## Job lifecycle ``` POST /articles/generate │ ▼ 202 Accepted → { "job_id": "job_abc123", "status": "pending" } │ ▼ (poll every 3-5 seconds) GET /jobs/job_abc123 │ ├── { "status": "processing" } ← still working ├── { "status": "completed", "result": { ... } } ← done └── { "status": "failed", "error": { ... } } ← error ``` ## Polling for results ```bash theme={null} curl https://api.smartlyq.com/v1/jobs/job_abc123 \ -H "Authorization: Bearer sqk_live_xxxxxxxxxxxx" ``` ### Job statuses | Status | Meaning | | ------------ | -------------------------------------------------- | | `pending` | Queued, not yet started | | `processing` | Actively running | | `completed` | Done — `result` field contains the output | | `failed` | Error — `error` field explains what went wrong | | `cancelled` | Job was cancelled via `POST /jobs/{job_id}/cancel` | ## Webhooks (alternative) Instead of polling, configure a [webhook](/guides/webhooks) to receive a notification when the job completes. # Billing & Credits Source: https://docs.smartlyq.com/guides/billing-and-credits How API credits work, pricing, and wallet management. ## Credit-based billing The SmartlyQ API uses a **prepaid credit** system called **SmartlyQ Credits (SQC)**. Every API call that consumes AI resources deducts credits from your Developer wallet. ## Developer wallet Your wallet is separate from your SmartlyQ subscription. Each request that uses AI resources deducts SQC; read-only calls are free. Top it up from the [Developer Dashboard](https://app.smartlyq.com/next/developer), and enable **auto-recharge** to keep it funded automatically. See [Pricing](/pricing) for the credit packages and what each operation costs. To see exactly what you're spending, check your wallet balance with the endpoint below. ## Checking your balance ```bash theme={null} curl https://api.smartlyq.com/v1/me/balance \ -H "Authorization: Bearer sqk_live_xxxxxxxxxxxx" ``` Response: ```json theme={null} { "success": true, "data": { "balance": "100.0000", "purchased_balance": "50.0000", "monthly_balance": "50.0000", "monthly_reset_at": "2026-04-15T00:00:00Z", "currency": "USD" } } ``` ## Insufficient credits If your balance is too low, the API returns **402 Payment Required**: ```json theme={null} { "success": false, "error": { "code": "INSUFFICIENT_CREDITS", "message": "Not enough credits. Top up your wallet.", "balance_sqc": 0 } } ``` # Connecting Accounts Source: https://docs.smartlyq.com/guides/connecting-accounts Connect social accounts to SmartlyQ and keep them healthy. Before you can publish, a social account has to be connected to a [workspace](/guides/workspaces). There are two ways to connect, and a small set of endpoints to keep accounts healthy afterward. Connecting **someone else's** accounts - an end-customer of your product? Use a [Profile connect link](/guides/profiles): a hosted, single-use page they open with no SmartlyQ login. ## 1. Connect from the dashboard (all platforms) The simplest path — and the only one for most platforms — is the app itself: open [Social Media → Accounts](https://app.smartlyq.com/next/go/social-media/accounts), choose a platform, and complete the OAuth flow. The account immediately becomes available to the API and appears in `GET /v1/social/accounts`. ## 2. Headless connect (your own UI) **Every OAuth platform** can be connected from your own app — Facebook, Instagram, X (Twitter), LinkedIn, YouTube, TikTok, Threads, Bluesky, Pinterest, Reddit, Snapchat, Tumblr, Google Business, and Discord (its server-webhook authorize flow works the same way) — without sending the user through the SmartlyQ dashboard. Telegram is the one non-OAuth platform - it connects with credentials instead of an authorize link. Call the same endpoint with the bot's token and target chat in the body, and the account connects **immediately** (no `connect_url`, nothing for the user to open): ```bash theme={null} curl -X POST https://api.smartlyq.com/v1/social/connect/telegram \ -H "Authorization: Bearer sqk_live_xxxxxxxxxxxx" \ -H "Content-Type: application/json" \ -d '{ "bot_token": "123456:AA...", "chat": "@yourchannel" }' ``` The token is verified with Telegram before saving (the bot must exist and have posting rights in that chat; private chats are rejected), the plan's account limit is enforced, and the token is stored encrypted. A `201` returns the connected `account`; failures return typed errors (`VALIDATION_ERROR`, `PROVIDER_NOT_CONFIGURED`, `LIMIT_REACHED`). Call `POST /v1/social/connect/{platform}` with scope `social:write`. Pass an optional `return_url` to send the user back to your app afterward. ```bash theme={null} curl -X POST https://api.smartlyq.com/v1/social/connect/facebook \ -H "Authorization: Bearer sqk_live_xxxxxxxxxxxx" \ -H "Content-Type: application/json" \ -d '{ "return_url": "https://yourapp.com/connected" }' ``` ```json Response (201) theme={null} { "success": true, "data": { "platform": "facebook", "connect_url": "https://www.facebook.com/v24.0/dialog/oauth?...", "expires_at": "2026-06-06T12:15:00Z" } } ``` Open `connect_url` in a browser (redirect, popup, or new tab). The link is **single-use and expires in 15 minutes**. The user authorizes on the platform. When the user approves, the platform's OAuth callback completes the connection on SmartlyQ's side and — if you supplied `return_url` — redirects the user back to your app with `?connected=&platform=`. The new account now shows up in `GET /v1/social/accounts`. Platforms that expose multiple targets (YouTube channels, Google Business locations, Tumblr blogs, a member's LinkedIn pages) connect them all automatically — there's no in-flow picker. If your app can't observe the `return_url` redirect (e.g. a server-to-server flow, or the user closed the tab), poll with the `state_token` from the `connect_url`: ```bash theme={null} curl https://api.smartlyq.com/v1/social/connect/{state_token} \ -H "Authorization: Bearer sqk_live_xxxxxxxxxxxx" ``` Returns `status` of `pending`, `connected` (with the new `account`), or `expired`. Calling `POST /v1/social/connect/{platform}` with an unrecognized platform returns **422 `UNSUPPORTED_PLATFORM`** (the response lists the supported ones). A platform whose OAuth app isn't configured on your SmartlyQ instance returns **422 `PROVIDER_NOT_CONFIGURED`**. If headless connect is disabled, the endpoint returns **404 `NOT_ENABLED`**. ## Managing connected accounts | Action | Endpoint | | ---------------------- | ------------------------------------------------- | | List accounts | `GET /v1/social/accounts` | | Check token health | `GET /v1/social/accounts/{id}/health` | | Get a re-auth link | `GET /v1/social/accounts/{id}/reconnect-url` | | Pause / resume posting | `POST /v1/social/accounts/{id}/pause` · `/resume` | | Disconnect | `DELETE /v1/social/accounts/{id}` | ### Reconnecting an account Access tokens expire (lifetimes vary by platform — some in hours, some in months). SmartlyQ refreshes them automatically, but if an account's health turns **unhealthy** — or you receive an `account.token_expired` [webhook](/guides/webhooks) — get a reconnect link and send the user through it: ```bash theme={null} curl https://api.smartlyq.com/v1/social/accounts/123/reconnect-url \ -H "Authorization: Bearer sqk_live_xxxxxxxxxxxx" ``` The user re-authorizes and the same account resumes — no new account is created, and scheduled posts keep their target. Pausing an account (`/pause`) skips it during publishing **without** disconnecting it — useful for temporarily taking a brand offline. Disconnecting is irreversible; the user must reconnect to use it again. See [Platforms](/platforms) for which platforms support headless connect, comments, DMs, and analytics. # Errors Source: https://docs.smartlyq.com/guides/errors Error codes, response format, and troubleshooting. ## Error response format All errors follow a consistent JSON structure: ```json theme={null} { "success": false, "error": { "code": "ERROR_CODE", "message": "Human-readable description." } } ``` ## HTTP status codes | Code | Meaning | | ----- | -------------------------------------------- | | `200` | Success | | `201` | Created | | `202` | Accepted (async job queued) | | `204` | No Content (resource deleted) | | `400` | Bad Request — invalid parameters | | `401` | Unauthorized — missing or invalid API key | | `402` | Payment Required — insufficient credits | | `403` | Forbidden — valid key but insufficient scope | | `404` | Not Found — resource does not exist | | `409` | Conflict — duplicate request (idempotency) | | `422` | Unprocessable Entity — validation error | | `429` | Too Many Requests — rate limited | | `500` | Internal Server Error | ## Common error codes | Code | Description | | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `UNAUTHORIZED` | API key is missing or invalid | | `FORBIDDEN` | Key lacks the required scope | | `CONNECTION_SCOPE_MISSING` | The connected social account is missing a permission this call needs, usually because it was connected before SmartlyQ requested that permission. Reconnect the account to fix it; `details.reconnect_required` is `true`. | | `INSUFFICIENT_CREDITS` | Wallet balance too low | | `RATE_LIMITED` | Too many requests | | `VALIDATION_ERROR` | Request body failed validation | | `RESOURCE_NOT_FOUND` | The requested resource does not exist | | `IDEMPOTENCY_CONFLICT` | A request with this idempotency key was already processed | | `MODEL_NOT_AVAILABLE` | The requested AI model is not available on your plan | | `JOB_FAILED` | An async job encountered an error | | `POST_TYPE_UNSUPPORTED` | `platform_options..type` names a post format this platform cannot publish. The message lists the accepted values; see [Platform options](/guides/platform-options). | | `PLATFORM_DAILY_LIMIT_REACHED` | HTTP `422`, not retryable. The platform's daily limit for this post type is used up for a selected account - today, Facebook's 30 Reels per Page in a moving 24-hour window. Nothing is created. Publish after `details.next_available_at`, or send the post as a different type. `details` also carries `account_id`, `limit` and `used`. | ## Idempotency For POST requests, include an `X-Idempotency-Key` header to prevent duplicate operations: ```bash theme={null} curl -X POST https://api.smartlyq.com/v1/articles/generate \ -H "Authorization: Bearer sqk_live_xxxxxxxxxxxx" \ -H "X-Idempotency-Key: unique-request-id-123" \ -H "Content-Type: application/json" \ -d '{ "topic": "My Article", "language": "en" }' ``` Idempotency keys expire after **24 hours**. # Idempotency & Safe Retries Source: https://docs.smartlyq.com/guides/idempotency Retry any create call without ever double-posting. Network timeouts, 5xx responses, and dropped connections leave you not knowing whether a `POST /v1/social/posts` landed. SmartlyQ has two independent layers so a retry is always safe. ## Layer 1: idempotency keys Send an `X-Idempotency-Key` header (`X-Request-Id` is accepted as an alias) with a fresh value per **logical** post - generate a UUID v4 in your code. If the call fails ambiguously (timeout, 5xx, connection reset), retry it with the **same** key: ```bash theme={null} curl -X POST https://api.smartlyq.com/v1/social/posts \ -H "Authorization: Bearer sqk_live_xxxxxxxxxxxx" \ -H "X-Idempotency-Key: YOUR-UNIQUE-KEY-PER-POST" \ -H "Content-Type: application/json" \ -d '{ "content": "Launch day!", "platforms": ["twitter"], "account_ids": [123] }' ``` * **First call** creates the post and returns `201`. * **A retry with the same key** returns `200` with the original post and `idempotent_replay: true` - nothing new is created: ```json Replay response (200) theme={null} { "success": true, "data": { "post_id": 8841, "status": "published", "idempotent_replay": true } } ``` Keys are scoped to your workspace, up to 64 characters (`A-Z a-z 0-9 . _ : -`), and permanently identify their post - there is no expiry window to race against. Deleting the post frees its key. Concurrent retries are safe: a unique database constraint guarantees only one post can ever own a key. Works on `POST /social/posts` and `POST /social/posts/schedule` (including queue scheduling). ## Layer 2: duplicate-content guard Independent of headers, creating **identical content** in the same workspace within **24 hours** returns `409 DUPLICATE_CONTENT` with the existing post's id: ```json 409 response theme={null} { "success": false, "error": { "code": "DUPLICATE_CONTENT", "message": "Identical content was already posted in this workspace within the last 24 hours (post 8841). Pass allow_duplicates: true to post it anyway.", "details": { "existing_post_id": 8841 } } } ``` This catches the classic mis-wired-cron bug even when no idempotency key was sent. Intentional re-posts opt out per request with `"allow_duplicates": true` in the body. Posts that ended `failed` or were unpublished never block a re-post. ## How the layers interact 1. Same idempotency key seen before → **200 replay** of the original post (wins over the duplicate guard - a retry is not a duplicate). 2. No (or new) key, identical content within 24h → **409 DUPLICATE\_CONTENT**. 3. Otherwise → the post is created normally. ## Retry rules of thumb | You got | Do | | ------------------------------------ | ----------------------------------------------------------------------------- | | Timeout / connection reset / 5xx | Retry with the **same** `X-Idempotency-Key` | | `200` with `idempotent_replay: true` | Treat as success - the original post's id is in `post_id` | | `409 DUPLICATE_CONTENT` | You (or another process) already posted this - use `details.existing_post_id` | | `429` | Back off per `Retry-After`, then retry with the same key | | `422` validation errors | Fix the request - do not blind-retry | # Platform Options Source: https://docs.smartlyq.com/guides/platform-options Every per-platform option you can set on a post, per platform. `platform_options` is an object keyed by platform that rides along on `POST /social/posts`, `/social/posts/schedule`, and bulk rows - the same payload the SmartlyQ composer uses, so anything below works identically from the API. Unknown keys are ignored; you never break a post by sending extra fields. ```json theme={null} { "content": "Global caption", "platforms": ["instagram", "youtube", "reddit"], "account_ids": [12, 34, 56], "platform_options": { "instagram": { "share_to_story": true, "first_comments": ["Link in bio!"] }, "youtube": { "title": "My video", "privacy": "unlisted", "tags": ["howto", "smartlyq"] }, "reddit": { "subreddit": "test", "title": "Show & tell", "flair_id": "abc-123" } } } ``` **Universal keys** (every platform): `content` - overrides the global caption for that platform only. **Scheduling stagger** (schedule endpoint): any platform may carry its own `scheduled_time` - see the note on the schedule endpoint. One call, each network at its own moment. ## Per-platform keys ### Instagram | Key | Effect | | -------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `type` | `post`, `reel` or `story`. Omit it for a normal post. `feed`, `video`, `photo`, `image` are aliases of `post`; `reels` of `reel`; `stories` of `story`. Anything else is rejected with `POST_TYPE_UNSUPPORTED`. | | `share_to_story` | Also publish the media as a story | | `share_to_feed` | Reels: whether the reel also appears in the main feed (Instagram's default when omitted) | | `first_comments` | Up to 5 comments posted right after publishing (`first_comment` legacy singular works) | | `_thumbnail.url` / `_thumbnail.timestamp_ms` | Reel cover image, or cover frame at an offset | ### YouTube | Key | Effect | | -------------------------- | ------------------------------------------------------------------- | | `title` | Video title (defaults derived from content) | | `privacy` | `public` · `private` · `unlisted` | | `made_for_kids` | COPPA audience declaration (default false) | | `tags` | Array of video tags/keywords | | `category_id` | Numeric YouTube category (default 22, People & Blogs) | | `contains_synthetic_media` | AI-generated/altered content disclosure - only sent when you set it | | `_thumbnail.url` | Custom video thumbnail | ### TikTok | Key | Effect | | ---------------------------------------------------- | ----------------------------------- | | `visibility` | Privacy level (required by TikTok) | | `allow_comments` / `allow_duet` / `allow_stitch` | Interaction toggles | | `branded_content` / `your_brand` | Commercial-content disclosure flags | | `auto_add_music`, `photo_cover_index`, `photo_title` | Photo-post extras | | `_thumbnail.timestamp_ms` | Video cover frame offset | ### Reddit | Key | Effect | | ------------------------- | --------------------------------------------------------------------------------------------- | | `subreddit` | Target subreddit (overrides the account default) | | `title` | Post title | | `flair_id` / `flair_text` | Assign a link flair from the subreddit's catalog (`flair_text` only for editable-text flairs) | Posting a link instead of a self post is automatic when the post carries media suited to it. Check a community first with `GET /social/accounts/{account_id}/reddit/subreddits/{subreddit}` (rules + `can_post` verdict). ### Facebook | Key | Effect | | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `type` | `post` (timeline, including video), `reel`, or `story`. Omit it for a normal post - text, image and video all publish to the timeline without it. `feed`, `video`, `photo`, `image`, `text` are accepted as aliases of `post`, `reels` of `reel`, and `stories` of `story`. Anything else is rejected with `POST_TYPE_UNSUPPORTED` and a list of the accepted values. | | `first_comments` | Up to 5 comments posted right after publishing (an entry may carry a single image) | | `_thumbnail.url` | Video cover image | | `link` | Destination URL. On a text-only post Facebook renders it as a preview card. When the post carries media the attachment is the card and Graph has no link parameter, so the URL is appended to the post text instead. Setting this here overrides a request-level `link`. | #### Stories `type: "story"` publishes to the Page's story tray via Meta's Page Stories API. * **Exactly one photo or one video.** Not a carousel, not two images. * **No caption.** Neither story endpoint accepts a message, description or text overlay, so the post's text is **not published**. Put any wording into the image or video itself. The create response carries a `warnings` entry saying so when a story post has text. * **Video:** MP4, 9:16, **up to 60 seconds**, 1080x1920 recommended (540x960 minimum), H.264/H.265, 24-60fps. * **Photo:** JPEG, PNG, BMP, GIF or TIFF, up to 10MB. * **The media must not already have been published.** Meta refuses a photo or video that has appeared in a previous post, and refuses media served from its own CDN (`fbcdn.net`) - host your own copy. * **No permanent link.** A story lives for 24 hours and has no `/posts/{id}` page, so `post_url` comes back `null` once it expires. The `post_id` is always returned. * Your Page token needs `pages_manage_posts` and `pages_read_engagement`. ### LinkedIn `title` (video/document posts), `first_comments`, `_thumbnail.url`. Document (PDF) posts publish via the document pipeline automatically. ### X (Twitter) `content` override and `media_urls` per platform. `first_comments` publish as a **reply thread**: the first replies to the post, each subsequent one replies to the previous (a chained thread). Each comment may be a plain string, or an object `{ "text": "...", "media_urls": ["https://..."] }` to attach media to that reply - up to 4 images, or one video (X's per-reply limits). Example: ```json theme={null} "twitter": { "first_comments": [ "More in the thread 👇", { "text": "Here's the demo", "media_urls": ["https://cdn.example.com/demo.png"] } ] } ``` ### Threads `reply_control` - who can reply; `first_comments`. ### Google Business | Key | Effect | | ----------------------------------------------------------------------------------------- | ------------------------------ | | `post_type` | `STANDARD` · `EVENT` · `OFFER` | | `event_title`, `event_start_date`, `event_end_date`, `event_start_time`, `event_end_time` | Event posts | | `offer_coupon`, `offer_redeem_url`, `offer_terms` | Offer posts | | `cta_type`, `cta_url` | Call-to-action button | | `language_code` | Post language | ### Pinterest `title`, `board_id`, `link` (destination), `alt_text`; video cover via a second media item or `_thumbnail.url`. ### Snapchat `surface` (story / spotlight), `save_to_profile`, `title` (saved stories, ≤45 chars), `description` + `locale` (spotlight). ### Telegram | Key | Effect | | ---------------------- | -------------------------------------------------- | | `parse_mode` | `HTML` · `Markdown` · `MarkdownV2` text formatting | | `silent` | Deliver without a notification sound | | `disable_link_preview` | Suppress the URL preview (text posts) | | `protect_content` | Block forwarding/saving of the message | ### Tumblr `tags` (array). ### Bluesky / Discord `content` override only - links, mentions and previews are handled automatically. ## First comments summary `first_comments` (array, max 5) is supported on **Facebook, Instagram, X, LinkedIn, Threads, and Reddit**. Other platforms ignore it. On **X** they post as a chained reply thread and each entry may carry media (`{ text, media_urls }`); on **Facebook** an entry may carry a single image. Elsewhere they are text only. # Profiles Source: https://docs.smartlyq.com/guides/profiles Build your own multi-tenant product on the SmartlyQ API - one profile per end-customer, self-serve account connection, per-account billing Profiles let you build your own SaaS on top of the SmartlyQ API. Each profile is a managed sub-account for one of **your** end-customers: they connect their own social accounts through a hosted link (no SmartlyQ login, no SmartlyQ branding beyond the OAuth consent), and you operate the full API inside their profile with a single header. Profiles are different from [workspaces](/guides/workspaces): workspaces are agency sub-accounts with plan-based limits and optional SaaS billing to the client; profiles are developer infrastructure - no plan caps, usage-billed per connected account, and invisible to the end-customer. ## Quick start **1. Create a profile for your customer** ```bash theme={null} curl -X POST https://api.smartlyq.com/v1/profiles \ -H "Authorization: Bearer $SMARTLYQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name": "Acme Coffee Co", "external_id": "cust_8412"}' ``` **2. Get a connect link and send it to them** ```bash theme={null} curl -X POST https://api.smartlyq.com/v1/profiles/42/connect-link \ -H "Authorization: Bearer $SMARTLYQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{"platforms": ["facebook", "instagram", "linkedin"]}' ``` The response contains a `connect_url`. Your customer opens it in any browser - no account, no login - picks a platform, and approves access. The page updates live as accounts connect. Links are **single-use**: the first browser to open one claims it, and it is dead for anyone else. Default lifetime is 24 hours (`ttl` overridable up to 7 days). Prefer your own UI? `POST /profiles/{id}/connect/{platform}` returns a raw per-platform OAuth URL instead. **3. Operate inside the profile** Add the `X-Profile-Id` header to any regular endpoint and it acts on the profile's data instead of your own workspace: ```bash theme={null} curl -X POST https://api.smartlyq.com/v1/social/posts \ -H "Authorization: Bearer $SMARTLYQ_API_KEY" \ -H "X-Profile-Id: 42" \ -H "Content-Type: application/json" \ -d '{"content": "Fresh roast, first pour.", "platforms": ["instagram"]}' ``` Everything stays yours where it matters: billing, rate limits, and usage logs remain on **your** account and wallet - the profile is only a data boundary. Delegation requires the `profiles:manage` scope on the key; profile lifecycle uses `profiles:read` / `profiles:write`. ## Billing Connected accounts inside profiles are billed monthly from your [API wallet](/guides/billing-and-credits), per account, with marginal bands: | Accounts | Price per account / month | | -------- | ------------------------- | | 1 - 10 | \$6 | | 11 - 100 | \$3 | | 101+ | \$1 | Example: 150 connected accounts = 10 x \$6 + 90 x \$3 + 50 x \$1 = **\$380/month**. * The count is a **snapshot on the 1st of each month (UTC)** across all your active profiles. No proration: an account connected mid-month is first billed the following month; a paused profile's accounts are not billed. * Charges appear in your wallet ledger as `API account billing: YYYY-MM (N accounts)`. * Check anytime with `GET /v1/me/account-billing`: live count, bands, estimated next charge, and history. **If a charge fails** (insufficient balance) you get an `account_billing.failed` webhook and the charge retries daily. After **7 days** unpaid, your profiles are paused (their accounts stop posting and stop billing) and auto-resume as soon as the outstanding charge succeeds after a top-up. Keep `balance.low` webhooks on to see it coming. ## Webhooks Two events cover billing: `account_billing.charged` and `account_billing.failed`. On top of that, **every event that happens inside a profile also reaches your webhooks** - `account.connected`, `post.published`, `comment.received`, `message.received`, and the rest - with `profile_id` and `profile_workspace_id` added to the payload so you can route it to the right customer. See the [Webhooks guide](/guides/webhooks). ## Lifecycle * `POST /profiles/{id}/pause` - suspend a customer (stops posting AND stops their accounts billing). `resume` undoes it. * `DELETE /profiles/{id}` with `{"confirm": "DELETE"}` - schedules async teardown of the profile workspace and its connections. * Profiles have no plan caps on connected accounts - the wallet is the natural limit. Creation is capped at 1000 active profiles per account (contact us to raise it). ## Errors worth knowing | Code | Meaning | | ---------------------------- | ---------------------------------------------- | | `PROFILES_NOT_ENABLED` (404) | Profiles are not enabled for your account yet | | `PROFILE_NOT_FOUND` (404) | The id does not exist or is not yours | | `PROFILE_PAUSED` (409) | Delegated call against a paused profile | | `INSUFFICIENT_SCOPE` (403) | Key lacks `profiles:manage` for `X-Profile-Id` | # Rate Limiting Source: https://docs.smartlyq.com/guides/rate-limiting API rate limits and how to handle them. ## Default limits Every API key is rate-limited to prevent abuse and ensure fair access. | Limit | Default | | ------------------- | -------------------- | | Requests per minute | 60 | | Per-key override | Configurable per key | ## Response headers Every response includes rate-limit headers: | Header | Description | | ----------------------- | ------------------------------------- | | `X-RateLimit-Limit` | Max requests allowed in the window | | `X-RateLimit-Remaining` | Requests remaining | | `X-RateLimit-Reset` | Unix timestamp when the window resets | ## Exceeding the limit When you exceed the limit, the API returns **429 Too Many Requests** with a `Retry-After` header: ```json theme={null} { "success": false, "error": { "code": "RATE_LIMITED", "message": "Too many requests. Retry after 12 seconds." } } ``` ### Handling 429 errors The header value tells you how many seconds to wait. Use exponential backoff: wait the `Retry-After` value, then double on each subsequent retry. Batch operations where possible and cache responses to reduce call volume. # Webhooks Source: https://docs.smartlyq.com/guides/webhooks Receive real-time notifications when events happen. ## Overview Webhooks let your server receive HTTP POST callbacks when events occur in SmartlyQ - a post publishing, a new comment or DM arriving, a contact being created, your wallet running low, and more. There are **38 events** across posting, accounts, inbox, reviews, jobs, billing, and CRM. ## Setting up webhooks Two ways to register an endpoint (both HTTPS-only, public URLs): * **Dashboard**: [Developer Dashboard](https://app.smartlyq.com/next/developer) → **Webhooks** tab → add an endpoint URL and select events. * **API**: `POST /webhooks` with `url` and an `events` array (see the [API reference](/api-reference/webhooks/create-webhook)). The response includes the signing `secret` - shown exactly once. The full management surface via API: list (`GET /webhooks`), create (`POST /webhooks`), update url/events or pause/resume (`PUT /webhooks/{id}`), send a signed test delivery (`POST /webhooks/{id}/test`), inspect the delivery log (`GET /webhooks/logs`), and delete (`DELETE /webhooks/{id}`). Limits and safety: up to **10 active webhooks per workspace**; URLs must be public HTTPS endpoints (internal/private addresses are rejected at save time and re-checked at delivery time); unknown event names are rejected with a validation error. Webhooks are a developer-workspace resource: the `X-Profile-Id` header is not accepted on webhook routes - profile events reach your webhooks automatically via fan-up. `POST /webhooks/{id}/test` sends a `webhook.test` event through the real signing pipeline (same headers, same HMAC) and returns the outcome synchronously - the fastest way to verify your endpoint and signature code. Test deliveries are never retried. ## Delivery envelope Every delivery is JSON with the same envelope: ```json theme={null} { "id": "evt_9f2c1a7d3b4e5f60718293a4", "event": "post.published", "created_at": "2026-07-26T14:30:00Z", "data": { "post_id": 12345, "status": "published", "platforms": [ { "platform": "instagram", "success": true }, { "platform": "twitter", "success": true } ] } } ``` * `id` is a **stable event identifier**: it is shared across fan-out to multiple endpoints and reused on every retry of the same delivery, so you can deduplicate at-least-once delivery. It is also echoed in the `X-SmartlyQ-Event-Id` header so you can dedupe without parsing the body. * `created_at` is UTC, ISO 8601. * `data` is the event-specific payload (catalog below). **Profiles fan-up:** if you use [Profiles](/guides/profiles) (sub-accounts for your end customers), events raised inside a profile's workspace are also delivered to **your** webhooks, with two extra fields added to `data`: `profile_id` and `profile_workspace_id`, so you can attribute the event to the right end customer. ## Headers | Header | Value | | ---------------------- | --------------------------------------------- | | `Content-Type` | `application/json` | | `X-SmartlyQ-Signature` | `t=,v1=` (see below) | | `X-SmartlyQ-Event` | The event name, e.g. `post.published` | | `X-SmartlyQ-Event-Id` | The envelope's stable `id`, for deduplication | | `User-Agent` | `SmartlyQ-Webhook/1.0` | ## Verifying signatures `X-SmartlyQ-Signature` carries `t=,v1=`, where `` is the HMAC-SHA256 of `.` keyed with your webhook secret. The timestamp rotates on every attempt; the body (and `id`) stay the same. ```javascript theme={null} const crypto = require("crypto"); // rawBody = the exact bytes of the request body (do not re-serialize the JSON) function verifySignature(rawBody, signatureHeader, secret) { const parts = Object.fromEntries( signatureHeader.split(",").map((kv) => kv.split("=")) ); const expected = crypto .createHmac("sha256", secret) .update(`${parts.t}.${rawBody}`) .digest("hex"); return crypto.timingSafeEqual( Buffer.from(parts.v1), Buffer.from(expected) ); } ``` Always verify the signature before processing a webhook. Unverified payloads may be spoofed. The signing secret is shown once, when you create the endpoint. ## Retry policy * Non-2xx responses are retried up to **5 attempts** with exponential backoff (≈2, 4, 8, 16, 32 minutes). * If your endpoint answers `429` with a `Retry-After` header, we honor it (capped at 1 hour) instead of the standard backoff. * After the final attempt the delivery is marked **dead-letter** (it is not silently dropped - you can re-run it from the dashboard Logs). * **10 consecutive failures** flip the endpoint to `failing` and pause deliveries until you fix and re-enable it. * Delivery records are retained for **90 days**. ## Event catalog All registerable events. Registering an event name outside this catalog is rejected with a validation error. ### Posting | Event | Fires when | `data` fields | | ----------------------- | ----------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `post.published` | Every platform target published | `post_id`, `status`, `platforms[]` (`platform`, `success`; on failures `error` `{code, message, retryable}` plus, when the failure came from SmartlyQ's own pre-publish checks, `error.detail` with the exact human-readable reason, e.g. `"Twitter/X caption is 312 characters; the limit is 280. Trim 32 characters and retry."`; when media was auto-fixed before publishing, `transcoded[]` lists what was converted, e.g. `"Video was vp9/webm; converted to H.264/AAC MP4."`) | | `post.partial` | Some platforms published, some failed | same as `post.published` | | `post.failed` | Every platform target failed | same as `post.published` | | `post.scheduled` | A post was accepted and queued for a future publish | `post_id`, `status`, `scheduled_at`, `platforms[]` | | `post.cancelled` | A scheduled post was cancelled before publishing | `post_id`, `status` | | `post.recycled` | A recycling chain spawned a new occurrence of a post | `post_id` (the new occurrence), `recycled_from`, `status`, `scheduled_at` | | `post.external.created` | A post authored natively on the platform (not via SmartlyQ) was detected by external sync | `post_id`, `platform`, `remote_id`, `status` (`external`) | | `post.external.updated` | A synced native post's text or media changed on the platform | same as `post.external.created` | ### Accounts | Event | Fires when | `data` fields | | ----------------------- | ------------------------------------------------ | ------------------------------------------------------------ | | `account.connected` | A social account is connected or reactivated | `account_id`, `platform`, `account_name`, `account_username` | | `account.disconnected` | An account's connection was revoked/invalidated | `account_id`, `platform`, `account_name`, `reason` | | `account.token_expired` | An account's token expired (reconnect to resume) | `account_id`, `platform`, `account_name`, `reason` | ### Inbox | Event | Fires when | `data` fields | | ---------------------- | ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `comment.received` | A new comment is ingested on one of your posts | `platform`, `account_id`, `social_post_id` (null for external posts), `remote_post_id`, `remote_comment_id`, `author_name`, `content`, `is_reply`, `commented_at` | | `message.received` | A new incoming DM is ingested | `platform`, `account_name`, `conversation_id`, `message_id`, `remote_message_id`, `sender_name`, `content`, `sent_at` | | `message.sent` | An outbound DM was delivered from the inbox (via app or API) | `platform`, `conversation_id`, `message_id`, `remote_message_id`, `content` | | `conversation.started` | A new inbox conversation was opened with a contact | `platform`, `conversation_id`, `participant_name` | | `reaction.received` | A participant added or removed an emoji reaction on a message | `platform`, `action` (`added`/`removed`), `emoji`, `conversation_id`, `message_id`, `remote_message_id` | | `message.delivered` | An outbound message reached the recipient (WhatsApp) | `platform`, `remote_message_id`, `status` | | `message.read` | The recipient opened an outbound message (WhatsApp) | `platform`, `remote_message_id`, `status` | | `message.failed` | An outbound message failed to deliver (WhatsApp) | `platform`, `remote_message_id`, `status`, `error` `{code, title, message}` | ### Reviews | Event | Fires when | `data` fields | | ---------------- | ---------------------------------------------------------------- | --------------------------------------------------------------------------------------- | | `review.new` | A new review was posted on a connected account (Google Business) | `review_id`, `account_id`, `platform`, `rating`, `author_name`, `content`, `created_at` | | `review.updated` | A review was edited, or you posted/removed a reply | same as `review.new`, plus `reply` when present | ### Jobs | Event | Fires when | `data` fields | | --------------- | ---------------------------------- | ----------------------------------- | | `job.completed` | An async job finished successfully | `job_id`, `type`, `status` | | `job.failed` | An async job failed | `job_id`, `type`, `status`, `error` | ### Billing & keys | Event | Fires when | `data` fields | | --------------------------- | ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | | `balance.low` | Wallet balance crossed below the low threshold | `balance`, `threshold` | | `balance.depleted` | Wallet balance reached zero | `balance` | | `key.revoked` | An API key was revoked | `key_id`, `key_prefix`, `name` | | `wallet.recharge.succeeded` | Auto-recharge charged your card and credited the wallet | `amount`, `currency`, `balance_after` | | `wallet.recharge.failed` | Auto-recharge failed (or grace expired) | `amount`, `currency`, `error`, `balance`, `retry_after`; on grace-expiry pauses instead: `reason`, `profiles_paused`, `grace_days` | | `account_billing.charged` | Monthly per-account billing charged your wallet | `period`, `accounts`, `amount`, `balance_after` | | `account_billing.failed` | Monthly per-account billing could not charge | `period`, `accounts`, `amount_due`, `error`, `grace_days_remaining`; after grace: `profiles_paused` | ### CRM CRM events fire regardless of whether the change came from the app or the API. | Event | Fires when | `data` fields | | --------------------- | ------------------------------- | ---------------------------------------------------------------------------------------------------------- | | `contact.created` | A contact is created | `email`, `contact` `{id, email, name, first_name, last_name, company, phone, tags[], attributes{}}` | | `contact.updated` | A contact is updated | same as `contact.created` | | `contact.tag_added` | A tag is added to a contact | same as `contact.created`, plus `tag` | | `contact.tag_removed` | A tag is removed from a contact | same as `contact.created`, plus `tag` | | `deal.created` | An opportunity is created | `email`, `deal` `{id, name, email, value, stage, status, pipeline_id, assigned_user_id, stage_changed_at}` | | `deal.stage_changed` | An opportunity moved stage | same as `deal.created`, plus `deal.old_stage` | | `deal.won` | An opportunity was marked won | same as `deal.created` | | `deal.lost` | An opportunity was marked lost | same as `deal.created` | Machine-readable payload schemas for every event ship in the [OpenAPI spec](https://docs.smartlyq.com/openapi.json) under the standard OpenAPI 3.1 `webhooks` object, so SDK generators and AI agents can consume them. # WhatsApp Source: https://docs.smartlyq.com/guides/whatsapp Send and receive WhatsApp Business messages through the SmartlyQ API. SmartlyQ connects to the **WhatsApp Business Cloud API** (Meta), so you can send session and template messages, receive inbound messages and delivery receipts via webhooks, and manage templates and the business profile. ## Connect a number There are two ways to connect a WhatsApp number. **In the app (recommended).** In **Settings → WhatsApp**, click **Connect with WhatsApp** and complete Meta's Embedded Signup popup - sign in, pick your WhatsApp Business Account and phone number, and you're done. SmartlyQ exchanges the returned code for a system-user token, subscribes to the number's webhooks, and stores the connection. No tokens to copy. **Over the API (headless).** For server-to-server onboarding, supply your WhatsApp Business Account id, the phone number id, and a Meta system-user access token: ```bash theme={null} curl -X POST https://api.smartlyq.com/v1/social/connect/whatsapp \ -H "Authorization: Bearer sqk_live_xxxxxxxxxxxx" \ -H "Content-Type: application/json" \ -d '{ "waba_id": "1234567890", "phone_number_id": "9876543210", "access_token": "EAAG..." }' ``` Either way, the token is verified against the number and stored encrypted; the connected number appears in `GET /v1/social/accounts` (platform `whatsapp`). Its `id` is the `account_id` you pass to every WhatsApp call below. ## Send a message `POST /v1/whatsapp/messages`. Eight message kinds, and **which you can send depends on the 24-hour window**: * **`text`**, **`media`**, **`interactive_buttons`**, **`interactive_list`**, **`location`**, and **`contacts`** are *session* messages - deliverable only within 24 hours of the customer's last message to you. * **`template`** (an approved HSM) is the only kind deliverable *outside* that window. * **`reaction`** reacts to (or removes a reaction from) a message you already sent or received. ```json Text (session) theme={null} { "account_id": 42, "to": "+14155551234", "type": "text", "text": "Thanks for reaching out!" } ``` ```json Template (any time) theme={null} { "account_id": 42, "to": "+14155551234", "type": "template", "template": { "name": "order_update", "language": "en_US", "components": [{ "type": "body", "parameters": [{ "type": "text", "text": "#A1234" }] }] } } ``` ```json Media (session) theme={null} { "account_id": 42, "to": "+14155551234", "type": "media", "media": { "kind": "image", "link": "https://cdn.example.com/receipt.png", "caption": "Your receipt" } } ``` A `502` means WhatsApp rejected it - most commonly sending a session message outside the 24-hour window (send a template instead). For `media.kind: "audio"`, SmartlyQ automatically transcodes the file to WhatsApp's voice-note format (OGG/Opus) when it isn't already one of `ogg`/`oga`/`aac`/`m4a`/`mp3`/`amr` - so a browser-recorded clip still renders as a playable voice note, not a generic file attachment. This happens transparently; no extra parameter needed. ### Interactive buttons and lists Buttons and lists let the customer respond by tapping instead of typing - their tap arrives as a normal inbound message on your webhook (`message.received`), same as any reply. ```json Interactive buttons (up to 3) theme={null} { "account_id": 42, "to": "+14155551234", "type": "interactive_buttons", "text": "Your order #A1234 is ready. What would you like to do?", "buttons": [ { "id": "confirm", "title": "Confirm" }, { "id": "reschedule", "title": "Reschedule" }, { "id": "cancel", "title": "Cancel" } ] } ``` ```json Interactive list (up to 10 rows) theme={null} { "account_id": 42, "to": "+14155551234", "type": "interactive_list", "text": "Pick a time slot for your appointment.", "button_text": "View slots", "sections": [ { "title": "Tomorrow", "rows": [ { "id": "slot_10", "title": "10:00 AM", "description": "45 min available" }, { "id": "slot_14", "title": "2:00 PM", "description": "45 min available" } ] } ] } ``` Button titles are capped at 20 characters, list row titles at 24, and row descriptions at 72 - Meta's own limits, enforced server-side. ### Location and contact cards ```json Location pin theme={null} { "account_id": 42, "to": "+14155551234", "type": "location", "location": { "latitude": 37.7749, "longitude": -122.4194, "name": "Store pickup counter", "address": "123 Market St, San Francisco, CA" } } ``` ```json Contact card theme={null} { "account_id": 42, "to": "+14155551234", "type": "contacts", "contacts": [{ "name": { "formatted_name": "Support Team", "first_name": "Support" }, "phones": [{ "phone": "+14155550188", "type": "WORK" }] }] } ``` `contacts` takes WhatsApp's raw contact-card object shape directly (name, phones, emails, etc. - see [Meta's WhatsApp Cloud API reference](https://developers.facebook.com/docs/whatsapp/cloud-api/messages/contacts-messages) for every optional field). ### Reply threading and reactions Pass `context_message_id` (a prior WhatsApp message id, `wamid...`) on any send to thread it as a reply - it renders as a quoted message in the customer's app. You get `wamid` values back from every send response's `message_id`, and on every inbound message via the `message.received` webhook. ```json Threaded reply theme={null} { "account_id": 42, "to": "+14155551234", "type": "text", "text": "Yes, that ships tomorrow.", "context_message_id": "wamid.HBgLMTU1NTU1NTU1NTUVAgARGBI5QTM2RUQ4RkQ2QjM4RjQ4QjQA" } ``` ```json React to a message theme={null} { "account_id": 42, "to": "+14155551234", "type": "reaction", "context_message_id": "wamid.HBgLMTU1NTU1NTU1NTUVAgARGBI5QTM2RUQ4RkQ2QjM4RjQ4QjQA", "emoji": "👍" } ``` Send `emoji: ""` to remove a reaction you previously sent. A customer's reaction to your message arrives via the `reaction.received` webhook, not `message.received`. ## Receive messages Inbound messages and delivery receipts arrive on **your webhooks**, not by polling (the Cloud API has no message-history endpoint). Subscribe to: | Event | Fires when | | ------------------- | -------------------------------------------------------- | | `message.received` | A customer sends you a message (lands in your inbox too) | | `message.delivered` | Your outbound message reached the recipient | | `message.read` | The recipient opened your message | | `message.failed` | Your outbound message failed, with the platform error | See the [Webhooks guide](/guides/webhooks). Inbound messages also populate the in-app Inbox automatically. ## Templates * `GET /v1/whatsapp/templates?account_id=42` - list templates and their approval status. * `POST /v1/whatsapp/templates` - submit a new template (`name`, `category` = `MARKETING`/`UTILITY`/`AUTHENTICATION`, `language`, `components`) for Meta approval. ## Business profile * `GET /v1/whatsapp/business-profile?account_id=42` - read about/address/email/websites/vertical. * `PATCH /v1/whatsapp/business-profile` - update any of those fields. ## Phone numbers `GET /v1/whatsapp/phone-numbers?account_id=42` lists the numbers on the connection's WhatsApp Business Account with their display number, verified name, and quality rating. ## Flows Flows are native in-chat forms - booking, surveys, lead capture - that open inside WhatsApp instead of linking out. A flow goes DRAFT → screens uploaded → PUBLISHED → sendable. ```json Create a draft flow theme={null} POST /v1/whatsapp/flows { "account_id": 42, "name": "Book an appointment", "categories": ["APPOINTMENT_BOOKING"] } ``` ```json Upload its screens theme={null} PUT /v1/whatsapp/flows/{flow_id}/json { "account_id": 42, "flow_json": { "version": "6.0", "screens": [ /* ... */ ] } } ``` Check the response's `validation_errors` - a successful upload doesn't guarantee the flow is publishable. Preview a draft with `GET /v1/whatsapp/flows/{flow_id}/preview` (embeddable iframe URL) before publishing. Once `POST /v1/whatsapp/flows/{flow_id}/publish` succeeds, send it: ```json Send the flow theme={null} POST /v1/whatsapp/messages { "account_id": 42, "to": "+14155551234", "type": "flow", "text": "Book your appointment in a few taps.", "flow": { "id": "1234567890123456", "cta": "Book now", "action": "navigate", "action_payload": { "screen": "APPOINTMENT", "data": {} } } } ``` Published flows are immutable - to change one, create a new flow and update your senders to reference the new id. See the full endpoint list on the [API reference](/api-reference). ## Block users `GET` / `POST` / `DELETE /v1/whatsapp/block-users` blocks or unblocks WhatsApp users on a connected number and lists who's currently blocked. Meta only allows blocking someone who has messaged your business within the last 24 hours; other targets come back in the response's `failed` array without failing the rest of the batch. ```json Block a user theme={null} POST /v1/whatsapp/block-users { "account_id": 42, "users": ["+14155551234"] } ``` ## Sandbox (shared test number) Every developer can test against one SmartlyQ-owned WhatsApp number without connecting their own. Activate a phone, reply to prove you own it, then send the locked sandbox template as many times as you like (up to the daily cap) to see it actually land. ```json Start an activation theme={null} POST /v1/whatsapp/sandbox/sessions { "phone": "+14155551234" } ``` Reply to the WhatsApp message that arrives at that phone (any text works) - the reply itself activates the session. Then: ```json Fire the template again once active theme={null} POST /v1/whatsapp/sandbox/sessions/{session_id}/send ``` One phone per user at a time; revoke (`DELETE /v1/whatsapp/sandbox/sessions/{session_id}`) before activating a different one. Limits: 50 sends/24h, pending sessions expire after 24h, active sessions after 7 days. `503 SANDBOX_UNAVAILABLE` means the platform hasn't configured a sandbox number yet - it's opt-in infrastructure, not guaranteed to exist on every deployment. ## Bring your own number (Twilio bridge) If you've bought a phone number through SmartlyQ (`POST /v1/phone-numbers/purchase`), you can bridge it onto WhatsApp instead of connecting a separate number. This adds the number to your **existing** WhatsApp Business Account connection - connect one number first via Settings > WhatsApp, then bridge any additional purchased numbers onto that same account. ```json 1. Start the bridge theme={null} POST /v1/whatsapp/numbers/{sender_id}/bridge ``` ```json 2. Request a verification code theme={null} POST /v1/whatsapp/numbers/{sender_id}/bridge/request-code { "method": "sms" } ``` Meta sends the code to the number itself (SMS or a voice call) - check that number's inbound message log in SmartlyQ to read it, the same way you'd check any other number you own. ```json 3. Submit the code theme={null} POST /v1/whatsapp/numbers/{sender_id}/bridge/verify { "code": "123456" } ``` On success the number is registered with WhatsApp and connected in SmartlyQ - from then on it behaves exactly like any other connected WhatsApp number, including in `POST /whatsapp/messages`. Requires the number to have SMS or voice capability so Meta can actually deliver the code. ## Limits & restrictions WhatsApp messaging is governed by both Meta's platform rules and your SmartlyQ plan. Knowing these avoids surprises: **Meta (platform) rules — enforced by WhatsApp:** * **24-hour customer service window** — free-form `text` and `media` messages are only deliverable within 24 hours of the customer's last message to you. Outside it, you must use an approved `template`. A `502` on a session message almost always means the window has closed. * **Messaging tiers** — a new number can start \~250 unique customers per 24 hours, rising to 1K → 10K → 100K → unlimited as your volume and quality grow. Meta raises the tier automatically. * **Quality rating** — each number carries a green / yellow / red rating. Too many blocks or reports lowers it and can throttle or restrict the number, so keep messaging relevant and opt-in. * **Template categories** — templates are `MARKETING`, `UTILITY`, or `AUTHENTICATION`, and each must be approved by Meta before it can be sent. Marketing templates are subject to per-user frequency caps. * **Throughput** — Meta accepts roughly 80 messages/second per number by default. SmartlyQ paces sends under this automatically so a burst can't trip the throttle. **SmartlyQ (plan) limits:** * **Monthly message cap** — each plan includes a monthly WhatsApp send allowance. When it's reached, `POST /whatsapp/messages` returns `429 LIMIT_REACHED` until the next month or a plan upgrade. This is separate from Meta's tiers. * **Connected numbers** count toward your plan's connected-account limit, like any other channel. # Workspaces Source: https://docs.smartlyq.com/guides/workspaces How workspace scoping works in the SmartlyQ API. Every resource in SmartlyQ belongs to a **workspace**. Workspaces provide tenant isolation — data in one workspace is invisible to another. Building a product where **your own customers** connect accounts and never see SmartlyQ? That is [Profiles](/guides/profiles) - per-end-customer sub-accounts with hosted connect links and per-account billing. Workspaces are for agency sub-accounts you manage yourself. ## Passing workspace\_id Most endpoints accept an optional `workspace_id` parameter. When provided, the API: 1. Verifies your API key's user is a **member** of that workspace 2. Scopes all returned data to that workspace 3. Returns **403 Forbidden** if you don't have access ```bash theme={null} curl -X GET "https://api.smartlyq.com/v1/social/accounts?workspace_id=42" \ -H "Authorization: Bearer sqk_live_xxxxxxxxxxxx" ``` If you omit `workspace_id`, some endpoints may return data across all your workspaces or return empty results. Always pass `workspace_id` for consistent, scoped results. ## Finding your workspace ID Use the `/v1/me` endpoint to list your available workspaces: ```bash theme={null} curl -X GET https://api.smartlyq.com/v1/me \ -H "Authorization: Bearer sqk_live_xxxxxxxxxxxx" ``` The response includes a `workspaces` array with each workspace's `id` and `name`. ## Workspace isolation guarantees * You can **only access workspaces** where your user is a member * All create, read, update, and delete operations are scoped by `workspace_id` * Passing another user's `workspace_id` returns **403 Forbidden** * Resources created without a `workspace_id` default to your primary workspace ## Endpoints that require workspace\_id | Endpoint | workspace\_id | Notes | | ------------------------- | :-----------: | --------------------------------------------- | | `POST /v1/chatbots` | Required | Chatbot is created in the specified workspace | | `GET /v1/social/accounts` | Recommended | Returns only accounts for that workspace | | `POST /v1/social/posts` | Recommended | Post is associated with the workspace | | `GET /v1/urls` | Recommended | Returns only URLs for that workspace | | `GET /v1/articles` | Recommended | Returns only articles for that workspace | | `GET /v1/images` | Recommended | Returns only images for that workspace | | `GET /v1/media` | Recommended | Returns only media for that workspace | ## Error response If you pass a `workspace_id` you don't have access to: ```json theme={null} { "success": false, "error": { "code": "FORBIDDEN", "message": "You do not have access to this workspace." } } ``` # Introduction Source: https://docs.smartlyq.com/introduction Build with the SmartlyQ API — generate content, manage social posts, chatbots, media, and more. ## Welcome to SmartlyQ SmartlyQ is an AI-powered platform for content creation, social media management, chatbots, and analytics. The SmartlyQ API lets you integrate these capabilities into your own applications. Make your first API call in under 5 minutes. Learn how to authenticate your requests. Browse all available endpoints. Get your API key, manage your wallet, and view usage. ## What you can build Articles, rewrites, images, video, audio — powered by leading AI models. Schedule and publish posts across 12+ platforms. Create, train, and deploy AI chatbots with RAG. Pull performance data for posts, accounts, and campaigns. Upload and manage files via presigned URLs. Conversational AI assistant for marketing tasks. Contacts, pipelines, and automation runs — trigger workflows from your stack. List, reply to, and sync Google Business reviews programmatically. Posting queues, evergreen recycling, bulk CSV imports, duplicate protection. ## Base URL All API requests use the following base URL: ``` https://api.smartlyq.com/v1 ``` ## Need help? Reach out to the SmartlyQ team. Manage API keys, credits, and webhooks. # MCP Source: https://docs.smartlyq.com/mcp-server Connect SmartlyQ to Claude, Cursor, ChatGPT, and any MCP client — manage social media and generate content with natural language. SmartlyQ ships a hosted **MCP (Model Context Protocol) server**. MCP is the open standard that lets AI assistants use external tools. With it, an AI agent can post to your socials, generate content, and read analytics on your SmartlyQ account — just by asking. **Server URL** ``` https://mcp.smartlyq.com/mcp ``` ## Prerequisites * A SmartlyQ account on a **Pro plan or higher** (API access requires Pro+). * An MCP-capable client: **Claude.ai, Claude Desktop, Claude Code, Cursor, ChatGPT, Windsurf**, or any MCP client. ## Connect with OAuth (recommended) The server supports **OAuth 2.1 sign-in** (MCP spec authorization) — no API key needed. This is the only way to connect from **claude.ai** custom connectors, and the easiest everywhere else: 1. Add a custom connector / MCP server with the URL `https://mcp.smartlyq.com/mcp`. 2. Your client opens a SmartlyQ sign-in window. Log in, pick the **workspace** to connect, and tick the **permissions** you want to grant. 3. Approve — you're connected. Revoke anytime from **Settings > Developer API** (the connection appears as an API key). ```bash Claude Code (CLI) theme={null} claude mcp add smartlyq --transport http https://mcp.smartlyq.com/mcp # Claude Code walks you through the SmartlyQ sign-in on first use. ``` ## Connect with an API key Alternatively, authenticate with a SmartlyQ API key (`sqk_live_...`) from the [Developer Dashboard](https://app.smartlyq.com/next/developer) — with the scopes you need enabled (see [Scopes](#tool-scopes) below) — passed as a Bearer token. ```jsonc Claude Desktop / Cursor (mcp config) theme={null} { "mcpServers": { "smartlyq": { "type": "http", "url": "https://mcp.smartlyq.com/mcp", "headers": { "Authorization": "Bearer sqk_live_xxxxxxxxxxxx" } } } } ``` ```bash Claude Code (CLI) theme={null} claude mcp add smartlyq \ --transport http https://mcp.smartlyq.com/mcp \ --header "Authorization: Bearer sqk_live_xxxxxxxxxxxx" ``` Once connected, try a prompt like: > "List my connected social accounts, then draft and schedule a launch post across all of them for tomorrow at 9am." ## Managing connections Every OAuth connection is listed in the SmartlyQ dashboard under **Settings → Developer API → Connections** ("Connected AI clients"). Each entry shows the client, the workspace, the permissions you granted, and when it was last used, with a **Disconnect** button that revokes the app's access immediately. Disconnecting is instant and irreversible — the app must sign in again to reconnect. API-key connections are managed separately under the **API Keys** tab. ## Available tools | Tool | What it does | Scope | | ------------------------------------------------ | ------------------------------------------------------ | --------------------- | | `list_social_accounts` | List connected social accounts | `social:read` | | `create_social_post` | Publish a post immediately | `social:write` | | `schedule_social_post` | Schedule a post for later | `social:write` | | `list_social_posts` | List posts (filter by status/platform) | `social:read` | | `retry_social_post` | Re-attempt publishing on given platforms | `social:write` | | `disconnect_social_account` | Disconnect a connected account | `social:write` | | `get_account_health` | Token validity/status of an account | `social:read` | | `get_account_reconnect_url` | URL to re-authenticate an account | `social:read` | | `pause_social_account` / `resume_social_account` | Pause/resume posting to an account | `social:write` | | `list_comments` | List comments on your posts | `social:read` | | `reply_to_comment` | Reply to a comment (posts to platform) | `social:write` | | `hide_comment` / `delete_comment` | Hide or delete a comment | `social:write` | | `list_conversations` | List DM conversations | `social:read` | | `list_messages` | List messages in a conversation | `social:read` | | `send_direct_message` | Send a DM (within platform window) | `social:write` | | `mark_conversation_read` | Mark a conversation read | `social:write` | | `list_webhooks` | List webhook subscriptions + available events | `webhooks:read` | | `create_webhook` | Subscribe an HTTPS URL to events | `webhooks:write` | | `delete_webhook` | Delete a webhook subscription | `webhooks:write` | | `get_analytics_overview` | Likes/impressions/clicks/views overview | `analytics:read` | | `rewrite_content` | Rewrite/rephrase text | `articles:write` | | `generate_article` | Generate a long-form article | `articles:write` | | `generate_image` | Generate an image | `images:write` | | `list_video_models` | List video models + options | `videos:read` | | `generate_video` | Generate a video | `videos:write` | | `text_to_speech` | Convert text to speech | `audio:write` | | `generate_presentation` | Generate a complete AI presentation deck from a prompt | `presentations:write` | | `get_job` | Get an async job's status/result | `jobs:read` | | `list_jobs` | List async jobs | `jobs:read` | ## The full API catalog (operations facade) Beyond the curated tools above, **three facade tools expose every operation in the SmartlyQ API** (100+ operations across Chatbot, Media, Short URLs, AI Captain, Account, and more) without flooding the model's context: | Tool | What it does | | -------------------- | -------------------------------------------------------------------- | | `search_operations` | Find operations by intent ("shorten a url", "train chatbot") | | `describe_operation` | Full parameter/body docs for one operation | | `execute_operation` | Run it — write/delete operations require an explicit `confirm: true` | The agent discovers what it needs (`search_operations` → `describe_operation` → `execute_operation`); scopes, rate limits, and billing apply exactly as for direct API calls. ## Tool scopes Each tool requires the matching scope on your API key. Enable them in the [Developer Dashboard](https://app.smartlyq.com/next/developer). If a key lacks a scope, the tool returns: ``` SmartlyQ API error (HTTP 403) — code: INSUFFICIENT_SCOPE — API key lacks required scope: social:read ``` ## Async generation Content generation tools (`generate_article`, `generate_image`, `generate_video`, `text_to_speech`) run asynchronously and return a **job id**. The agent should poll `get_job` until the job's status is `completed` to retrieve the result. See [Async jobs](/guides/async-jobs). The long SEO lookups (`/seo/site-audit`, `/seo/rank-history`, `/seo/brand-lookup`, `/seo/prompt-explorer`) are also async on the REST API (202 + `job_uid`). Over MCP you don't need to do anything: **the MCP server waits for these jobs internally** and returns the finished result as a normal tool response (30s-2min). If the wait budget is exceeded you receive the `job_uid` to poll with `get_job`. ## Webhooks Subscribe an HTTPS endpoint to events with `create_webhook` (or `POST /webhooks`) so your agent reacts to posts publishing, comments arriving, or jobs finishing. Deliveries are HMAC-signed and retried with backoff — see the full [Webhooks guide](/guides/webhooks) for the event list, signature verification, and retry policy. ## Billing MCP calls are ordinary API calls under the hood, so the same [billing and credits](/guides/billing-and-credits) and [rate limits](/guides/rate-limiting) apply. The MCP layer adds no extra charges and grants no extra access — everything is governed by your API key. ## Security * Treat your API key like a password. If it leaks, rotate it in the Developer Dashboard. * The server forwards your key to the SmartlyQ API per request and stores nothing. # Platforms Source: https://docs.smartlyq.com/platforms Which social platforms SmartlyQ supports, and exactly what each one can do through the API. SmartlyQ publishes to and manages every platform below through a single API. One `POST /v1/social/posts` call fans a post out to any combination of them — you reference connected accounts by `account_ids` and list the target `platforms`. ## At a glance | Platform | Publish | Analytics | Comments | Direct messages | Headless connect | | ------------------------------------------ | :-------------------: | :---------------------: | :---------------------: | :-------------------: | :---------------------: | | **Facebook** | | | | | | | **Instagram** | | | | | | | **X (Twitter)** | | | | | | | **LinkedIn** | | | | | | | **YouTube** | | | | | | | **TikTok** | | | | | | | **Threads** | | | | | | | **Bluesky** | | | | | | | **Pinterest** | | | ¹ | | | | **Reddit** | | | | | | | **Snapchat** | | | | | | | **Tumblr** | | | | | | | **Google Business** | | ² | | | | | **Telegram** | | | | | ³ | | **Discord** | | | | | | supported  ·  not available through the API.
¹ Pinterest comments support fetch + reply, but **not** hide or delete.
² Google Business per-post analytics were deprecated by Google; location-level metrics aren't exposed here.
³ Telegram is a credential connect, not OAuth: `POST /v1/social/connect/telegram` with `bot_token` + `chat` in the body connects immediately (no authorize URL) - or do it once from the dashboard's Social Accounts page.
* **Headless connect** lets you connect a *new* account from your own UI via `POST /v1/social/connect/{platform}` — supported on **every platform above** - 14 via OAuth `connect_url` (Discord included: its server-webhook authorize flow works headlessly), and Telegram via credential connect (`bot_token` + `chat` in the body, connects immediately). Poll `GET /v1/social/connect/{state_token}` to learn when the user finished. You can still connect from the [Developer Dashboard](https://app.smartlyq.com/next/developer). See [Connecting accounts](/guides/connecting-accounts). * **Direct messages** are actively synced for Facebook, Instagram, and X. Meta platforms enforce a **24-hour reply window** — you can only message a user within 24 hours of their last message. ## Platform details Every platform has its own page in the sidebar — pick one for its caption limit, post types, capabilities, connect method, and token notes. ## Media Attach media to a post with `media_urls` (publicly reachable HTTPS URLs), or upload to SmartlyQ storage first via `POST /v1/media/upload-url`. **Formats** — images `jpg` · `jpeg` · `png` · `gif` · `webp`; video `mp4` · `mov` · `m4v` · `webm`. ### Per-platform media rules Each platform enforces its own limits on how many items you can attach, whether images and video can be mixed, and file size / duration. SmartlyQ validates these before publishing where it can, so you get an actionable error up front instead of a downstream failure. | Platform | Images | Videos | Mix image + video | Size / duration | | ------------------------------------- | :-----------------: | :------: | :---------------: | --------------------------------------------------------------- | | X (Twitter) | up to 4 | 1 | No | Image ≤ 5 MB · GIF ≤ 15 MB · video ≤ 100 MB, ≤ 140 s | | Facebook | up to 10 | 1 | No | — | | Instagram | up to 10 (carousel) | 1 | — | Requires media · feed ratio 0.8–1.91 · oversize auto-compressed | | LinkedIn | up to 20 | 1 | — | Single video per post | | TikTok | up to 35¹ | 1 | — | Photo carousels are **JPEG / WebP only** | | Bluesky | up to 4 | 1 | — | Images over \~1 MB auto-recompressed | | Snapchat | 1 item | 1 item | — | Image ≤ 20 MB · video ≤ 500 MB, 5–60 s · 9:16 | | Google Business | 1 | — | — | Single image; **video not supported** | | YouTube | — | 1 | — | Videos ≤ 3 min publish as Shorts | | Telegram | up to 10 (album) | up to 10 | Yes | Image ≤ 5 MB · video ≤ 20 MB (sent by URL) | | Discord | up to 10 (embeds) | 1 | — | Text ≤ 2000 chars · images as embeds | | Threads · Pinterest · Reddit · Tumblr | per post | per post | — | Standard platform limits | ¹ TikTok photo carousels accept up to **35** images and must be JPEG or WebP (PNG/GIF are rejected). A single video post is unaffected. * **Instagram requires media** — a text-only post will be rejected. * Stories / Reels / Shorts and carousels are detected automatically from the media you attach (see each platform above). * **Aspect ratio** is advisory: media well outside a platform's supported range (e.g. a banner on an Instagram feed post) returns a non-blocking `warnings` entry on the create response rather than failing the post. ## Keeping accounts healthy Token lifetimes vary widely — SmartlyQ refreshes them for you. Check any account's status with `GET /v1/social/accounts/{id}/health`, and if one turns unhealthy, send the user through its [reconnect URL](/guides/connecting-accounts#reconnecting-an-account). # Bluesky Source: https://docs.smartlyq.com/platforms/bluesky Bluesky support in the SmartlyQ API: limits, post types, capabilities, and how to connect. | | | | ----------------- | --------------------------------------------------------- | | **Caption limit** | 300 characters | | **Post types** | Text, image, video | | **Capabilities** | Publish · Comments | | **Connect** | Headless (`POST /v1/social/connect/bluesky`) or dashboard | | **Tokens** | Very short-lived (\~15 min), auto-refreshed | See [all platforms](/platforms) for the capability matrix and media rules. # Discord Source: https://docs.smartlyq.com/platforms/discord Discord support in the SmartlyQ API: limits, post types, capabilities, and how to connect. | | | | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Caption limit** | 2,000 characters | | **Post types** | Text, image (up to 10 as embeds) | | **Capabilities** | Publish | | **Connect** | Headless (`POST /v1/social/connect/discord`) or dashboard — authorize SmartlyQ on your server and pick the channel; posting runs through the created channel webhook | | **Tokens** | Webhooks don't expire; posting stops only if the webhook is deleted on the server | Posts publish through a **channel webhook** on your server. Images attach as embeds (up to 10 per message). When your workspace has a default brand, posts appear under the brand's name and logo instead of the webhook's stock identity. See [all platforms](/platforms) for the capability matrix and media rules. # Facebook Source: https://docs.smartlyq.com/platforms/facebook Facebook support in the SmartlyQ API: limits, post types, capabilities, and how to connect. | | | | ----------------- | ---------------------------------------------------------- | | **Caption limit** | 63,206 characters | | **Post types** | Feed posts (text, link, image, video), Reels, Stories | | **Capabilities** | Publish · Analytics · Comments · Direct messages | | **Connect** | Headless (`POST /v1/social/connect/facebook`) or dashboard | | **Tokens** | Page tokens are effectively permanent — no refresh needed | DMs use Meta's **24-hour messaging window**. Comments support full fetch / reply / hide / delete. Reels and Stories are selected with `platform_options.facebook.type` (`reel` / `story`); omit it for a feed post. A **story carries no caption** - Meta's Page Stories API has no text parameter - and lives for 24 hours. See [Platform options](/guides/platform-options) for the per-type media rules. ## Reel limit Facebook allows **30 API-published Reels per Page in a moving 24-hour window**. Creating or scheduling a Reel is refused up front when the Page would be at the limit at the post's publish time (Reels published plus Reels already scheduled in the 24 hours before it): `422 PLATFORM_DAILY_LIMIT_REACHED`, with `details.next_available_at` saying when a Reel fits again. Check the allowance with [Get Facebook Reel limit](/api-reference/social/get-facebook-reel-limit). Reels posted to the Page outside this platform are not counted, so Facebook can still refuse a Reel; a scheduled Reel that hits the limit when it publishes fails with the same error code and is not retried automatically. ## Page settings These calls manage the Facebook Page behind a connected account, where `account_id` is the connected account's id. A test key (`sqk_test_...`) validates the request - including checking the image URL - and returns a success response without contacting Facebook. ### Message button `PATCH /v1/social/accounts/{account_id}/facebook/page/settings` with `{"users_can_message": false}` turns messaging off: the Page's **Message** button is hidden **and replies are blocked in chats that already exist**. Send `true` to restore both. Read the current value with `GET /v1/social/accounts/{account_id}/facebook/page/settings`; `users_can_message` is `null` when Facebook did not report the setting. ### Profile picture `POST /v1/social/accounts/{account_id}/facebook/page/picture` with `{"url": "https://..."}`. Facebook downloads the image from that URL itself. No feed story is created and followers are not notified. Facebook shows the picture as a circle, so a square image of 196 x 196 px or larger works best. ### Cover photo `POST /v1/social/accounts/{account_id}/facebook/page/cover` with `url` and an optional `offset_y` (0-100, default 50). No feed story is created and followers are not notified. The recommended size is 851 x 315 px. This call needs the business permission on the connection. A connection without it gets `403 CONNECTION_SCOPE_MISSING` with `reconnect_required: true` - reconnect the account to fix it. A cover photo can be replaced through the API but not removed; removing it has to be done on Facebook. ### Image rules | | Profile picture | Cover photo | | ---------------------- | ------------------------------ | ------------------------ | | **Formats** | JPG, PNG, GIF, BMP, TIFF | JPG, PNG, GIF, BMP, TIFF | | **Maximum file size** | 10 MB | 10 MB | | **Minimum dimensions** | - | 400 x 150 px | | **Recommended** | Square, 196 x 196 px or larger | 851 x 315 px | Only the format, file size and cover minimum are enforced. An image that breaks one is refused with `422 VALIDATION_ERROR`, with `details.field` set to `"url"` and `details.reason` one of `invalid_url`, `unreachable`, `too_large`, `unsupported_type` or, for a cover photo only, `too_small`. API reference: [Get page settings](/api-reference/social/get-facebook-page-settings) | [Update page settings](/api-reference/social/update-facebook-page-settings) | [Set profile picture](/api-reference/social/set-facebook-page-profile-picture) | [Set cover photo](/api-reference/social/set-facebook-page-cover-photo) See [all platforms](/platforms) for the capability matrix and media rules. # Google Business Source: https://docs.smartlyq.com/platforms/google-business Google Business support in the SmartlyQ API: limits, post types, capabilities, and how to connect. | | | | ----------------- | ----------------------------------------------------- | | **Caption limit** | 1,500 characters | | **Post types** | Location posts (text + single image) | | **Capabilities** | Publish | | **Connect** | Headless (`POST /v1/social/connect/gmb`) or dashboard | | **Tokens** | \~1 hour, auto-refreshed | Publishing only — no comments or DM inbox. Per-post analytics were deprecated by Google. See [all platforms](/platforms) for the capability matrix and media rules. # Instagram Source: https://docs.smartlyq.com/platforms/instagram Instagram support in the SmartlyQ API: limits, post types, capabilities, and how to connect. | | | | ----------------- | ----------------------------------------------------------- | | **Caption limit** | 2,200 characters | | **Post types** | Feed, Carousel, Reels, Stories | | **Capabilities** | Publish · Analytics · Comments · Direct messages | | **Connect** | Headless (`POST /v1/social/connect/instagram`) or dashboard | | **Tokens** | \~60 days, auto-refreshed | **Requires media** — a text-only post is rejected. DMs use Meta's **24-hour messaging window**. See [all platforms](/platforms) for the capability matrix and media rules. # LinkedIn Source: https://docs.smartlyq.com/platforms/linkedin LinkedIn support in the SmartlyQ API: limits, post types, capabilities, and how to connect. | | | | ----------------- | ---------------------------------------------------------- | | **Caption limit** | 3,000 characters | | **Post types** | Text, image, video, carousel, document (PDF), GIF | | **Capabilities** | Publish · Comments | | **Connect** | Headless (`POST /v1/social/connect/linkedin`) or dashboard | | **Tokens** | \~60 days | Both **personal profiles and organization pages** are supported. Some analytics require organization-admin scope; if it's missing the account still connects and posts normally. See [all platforms](/platforms) for the capability matrix and media rules. # Pinterest Source: https://docs.smartlyq.com/platforms/pinterest Pinterest support in the SmartlyQ API: limits, post types, capabilities, and how to connect. | | | | ----------------- | ----------------------------------------------------------- | | **Caption limit** | 800 characters (description) | | **Post types** | Pins, video pins | | **Capabilities** | Publish · Comments (fetch + reply only) | | **Connect** | Headless (`POST /v1/social/connect/pinterest`) or dashboard | | **Tokens** | \~30 days | Pinterest comments **cannot be hidden or deleted** via the API — those operations return `not_supported`. See [all platforms](/platforms) for the capability matrix and media rules. # Reddit Source: https://docs.smartlyq.com/platforms/reddit Reddit support in the SmartlyQ API: limits, post types, capabilities, and how to connect. | | | | ----------------- | -------------------------------------------------------- | | **Caption limit** | 40,000 characters (self-text) | | **Post types** | Text posts, link posts | | **Capabilities** | Publish · Comments · Direct messages | | **Connect** | Headless (`POST /v1/social/connect/reddit`) or dashboard | | **Tokens** | \~1 hour, auto-refreshed | See [all platforms](/platforms) for the capability matrix and media rules. # Snapchat Source: https://docs.smartlyq.com/platforms/snapchat Snapchat support in the SmartlyQ API: limits, post types, capabilities, and how to connect. | | | | ----------------- | ---------------------------------------------------------- | | **Caption limit** | Not length-checked (Stories carry no caption) | | **Post types** | Stories, Spotlight | | **Capabilities** | Publish | | **Connect** | Headless (`POST /v1/social/connect/snapchat`) or dashboard | | **Tokens** | \~1 hour, auto-refreshed | Requires a Snapchat Public Profile. No comments or DM inbox. See [all platforms](/platforms) for the capability matrix and media rules. # Telegram Source: https://docs.smartlyq.com/platforms/telegram Telegram support in the SmartlyQ API: limits, post types, capabilities, and how to connect. | | | | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Caption limit** | 4,096 characters (text posts) · 1,024 as a media caption | | **Post types** | Text, image, video, album (2–10 mixed items) | | **Capabilities** | Publish | | **Connect** | API (`POST /v1/social/connect/telegram` with `bot_token` + `chat`) or dashboard — paste your bot token (from [@BotFather](https://t.me/BotFather)) and pick the target channel/group | | **Tokens** | Bot tokens don't expire; posting stops only if the bot is removed from the chat | Posts publish through **your own bot** to the channel or group it belongs to. Media is sent by URL — Telegram fetches it itself, which caps images at \~5 MB and video at \~20 MB (oversize video is auto-shrunk by SmartlyQ's transcode step where possible). A caption longer than 1,024 characters on a media post is delivered as the media followed by a separate text message. See [all platforms](/platforms) for the capability matrix and media rules. # Threads Source: https://docs.smartlyq.com/platforms/threads Threads support in the SmartlyQ API: limits, post types, capabilities, and how to connect. | | | | ----------------- | --------------------------------------------------------- | | **Caption limit** | 500 characters | | **Post types** | Text, image, video, carousel | | **Capabilities** | Publish · Analytics · Comments | | **Connect** | Headless (`POST /v1/social/connect/threads`) or dashboard | | **Tokens** | \~60 days | See [all platforms](/platforms) for the capability matrix and media rules. # TikTok Source: https://docs.smartlyq.com/platforms/tiktok TikTok support in the SmartlyQ API: limits, post types, capabilities, and how to connect. | | | | ----------------- | -------------------------------------------------------- | | **Caption limit** | 2,200 characters | | **Post types** | Video, photo carousel | | **Capabilities** | Publish · Analytics · Comments | | **Connect** | Headless (`POST /v1/social/connect/tiktok`) or dashboard | | **Tokens** | \~24 hours, auto-refreshed | See [all platforms](/platforms) for the capability matrix and media rules. # Tumblr Source: https://docs.smartlyq.com/platforms/tumblr Tumblr support in the SmartlyQ API: limits, post types, capabilities, and how to connect. | | | | ----------------- | -------------------------------------------------------- | | **Caption limit** | No fixed limit | | **Post types** | Text, image | | **Capabilities** | Publish | | **Connect** | Headless (`POST /v1/social/connect/tumblr`) or dashboard | | **Tokens** | \~42 min, auto-refreshed | See [all platforms](/platforms) for the capability matrix and media rules. # X (Twitter) Source: https://docs.smartlyq.com/platforms/twitter X (Twitter) support in the SmartlyQ API: limits, post types, capabilities, and how to connect. | | | | ----------------- | ------------------------------------------------------------------- | | **Caption limit** | 280 characters | | **Post types** | Text, image, video | | **Capabilities** | Publish · Comments · Direct messages | | **Connect** | Headless (`POST /v1/social/connect/twitter`) or dashboard | | **Tokens** | \~2 hours, auto-refreshed — reconnect if an account turns unhealthy | No analytics via the API. Comments (replies) support full fetch / reply / hide / delete. See [all platforms](/platforms) for the capability matrix and media rules. # YouTube Source: https://docs.smartlyq.com/platforms/youtube YouTube support in the SmartlyQ API: limits, post types, capabilities, and how to connect. | | | | ----------------- | --------------------------------------------------------- | | **Caption limit** | 5,000 characters (description) | | **Post types** | Standard video, Shorts, Community posts | | **Capabilities** | Publish · Analytics · Comments | | **Connect** | Headless (`POST /v1/social/connect/youtube`) or dashboard | | **Tokens** | \~1 hour, auto-refreshed | See [all platforms](/platforms) for the capability matrix and media rules. # Pricing Source: https://docs.smartlyq.com/pricing Pay-as-you-go API pricing with prepaid SmartlyQ Credits — only pay for what you use. The SmartlyQ API is **pay-as-you-go**. There are no API plan tiers — you top up a prepaid wallet of **SmartlyQ Credits (SQC)**, and each request deducts credits for the AI resources it actually uses. Build and test for free in sandbox, then switch to a live key when you ship. Build against the full API with a `sqk_test_` key — **no credits charged**. Live calls deduct SQC based on the model, tokens, and operation type. No subscription for API access. Add credits in packages from the Developer Dashboard, or turn on auto-recharge. ## Credit packages Top up your Developer wallet from the [Developer Dashboard](https://app.smartlyq.com/next/developer): | Package | Price | | --------- | ----- | | 250 SQC | €5 | | 750 SQC | €15 | | 3,000 SQC | €60 | | 7,500 SQC | €150 | Enable **auto-recharge** to keep your wallet funded automatically, so requests never fail for a low balance. ## What a request costs Credit cost per request depends on: 1. **AI model used** — larger, more capable models cost more 2. **Token count** — input + output tokens consumed 3. **Operation type** — text, image, video, audio, or presentations A small minimum charge applies per operation type: | Operation type | Minimum SQC | | ---------------- | ----------- | | Text generation | 1 | | Image generation | 2 | | Audio (TTS/STT) | 1 | | Video generation | varies | | Presentations | 1 | Read-only calls — listing posts, fetching analytics, managing accounts — **don't consume credits**. Generation and publishing actions do. ## Track your spend Check your wallet balance any time: ```bash theme={null} curl https://api.smartlyq.com/v1/me/balance \ -H "Authorization: Bearer sqk_live_xxxxxxxxxxxx" ``` For how the wallet, per-request deductions, and low-balance errors work in detail, see [Billing & Credits](/guides/billing-and-credits). # Quickstart Source: https://docs.smartlyq.com/quickstart Make your first SmartlyQ API call in under 5 minutes. ## Prerequisites * A SmartlyQ account ([sign up](https://app.smartlyq.com/next/signup)) * A **Pro plan or higher** (API access requires Pro+) * An API key from the [Developer Dashboard](https://app.smartlyq.com/next/developer) ## Step 1: Get your API key Go to [app.smartlyq.com/next/developer](https://app.smartlyq.com/next/developer) and create a new API key. Your key looks like `sqk_live_xxxxxxxxxxxx`. Keep it safe — you won't be able to see it again. API usage is prepaid. Top up your Developer wallet from the same dashboard. ## Step 2: Make your first request Verify your key by fetching your profile: ```bash cURL theme={null} curl -X GET https://api.smartlyq.com/v1/me \ -H "Authorization: Bearer sqk_live_xxxxxxxxxxxx" ``` ```javascript Node.js theme={null} const res = await fetch("https://api.smartlyq.com/v1/me", { headers: { Authorization: "Bearer sqk_live_xxxxxxxxxxxx" } }); const data = await res.json(); console.log(data); ``` ```python Python theme={null} import requests res = requests.get( "https://api.smartlyq.com/v1/me", headers={"Authorization": "Bearer sqk_live_xxxxxxxxxxxx"} ) print(res.json()) ``` You should receive a JSON response with your user profile and plan details. ## Step 3: Generate content Try generating an article (returns a job you can poll): ```bash theme={null} curl -X POST https://api.smartlyq.com/v1/articles/generate \ -H "Authorization: Bearer sqk_live_xxxxxxxxxxxx" \ -H "Content-Type: application/json" \ -d '{ "topic": "10 Tips for Social Media Marketing", "language": "en", "tone": "professional" }' ``` The response includes a `job_id`. Poll `GET /jobs/{job_id}` until the job completes. ## Next steps Learn about API keys, scopes, and security. Understand how credits and pricing work. Browse all available endpoints. Learn how to poll for long-running operations. # Chat SDK Adapter Source: https://docs.smartlyq.com/sdks/chat-sdk-adapter Build a multi-platform chatbot on the SmartlyQ unified inbox. **Install:** `npm install @smartlyqofficial/chat-sdk-adapter` · [GitHub](https://github.com/SmartlyQ/smartlyq-chat-sdk-adapter) · [npm](https://www.npmjs.com/package/@smartlyqofficial/chat-sdk-adapter) · Node 18+. SmartlyQ's unified inbox aggregates DMs and comments from every connected social account and fires signed [webhooks](/guides/webhooks). This adapter turns those webhooks into a chatbot pipeline: it **verifies** each delivery, **normalizes** it into one message shape, and hands it to your handler code with **reply helpers** wired to the SmartlyQ API. **Write once - it works on every platform your accounts cover.** ```mermaid theme={null} flowchart LR subgraph P [Platforms] A[Instagram] B[Facebook] C[X] D[TikTok] E[LinkedIn] end P --> I["SmartlyQ unified inbox"] I -- "signed webhook
message.received
comment.received" --> AD["@smartlyqofficial/
chat-sdk-adapter
verify - normalize - route"] AD --> BOT["Your bot
onMessage / onComment"] BOT -- "thread.post() / reply()" --> I ``` Want a chatbot without writing code? SmartlyQ has a built-in [chatbot builder](/api-reference/chatbots/create-chatbot) with an inbox bridge - train it in the dashboard and skip the adapter. This package is for developers who want full code-level control. ## Three steps to a multi-platform chatbot Create an API key in the Developer Dashboard and set it as `SMARTLYQ_API_KEY`. Subscribe it to `message.received` and `comment.received`, pointed at your app. Save the `whsec_...` signing secret it returns - see the [webhooks guide](/guides/webhooks). Install `@smartlyqofficial/chat-sdk-adapter` and wire your handlers: ```typescript theme={null} // app/api/smartlyq/route.ts (Next.js App Router) import { createAdapter, toFetchHandler } from '@smartlyqofficial/chat-sdk-adapter'; const adapter = createAdapter({ webhookSecret: process.env.SMARTLYQ_WEBHOOK_SECRET!, }) .onMessage(async ({ message, thread }) => { // Every DM from every platform lands here, normalized. const history = await thread.fetchMessages(); const answer = await yourModel.respond(message.text, history); await thread.post(answer); }) .onComment(async ({ comment, reply }) => { if (!comment.isReply) { await reply(`Thanks ${comment.authorName ?? 'friend'}!`); } }); export const POST = toFetchHandler(adapter); ``` Using Express? Mount with a **raw** body (signature verification needs the exact bytes): ```typescript theme={null} app.post('/webhooks/smartlyq', express.raw({ type: '*/*' }), toNodeHandler(adapter)); ``` ## What you get in a handler | Context | Fields | | ------------- | ------------------------------------------------------------------------------- | | `message` | `threadId`, `conversationId`, `platform`, `senderName`, `text`, `sentAt`, ids | | `thread` | `post(text)` - reply into the conversation · `fetchMessages()` - recent history | | `comment` | `platform`, `authorName`, `text`, `isReply`, remote ids | | `reply(text)` | Replies publicly to the comment (internal id resolved automatically) | | `raw` | The untouched webhook envelope | ## Security Every delivery is checked against your signing secret (HMAC-SHA256, `X-SmartlyQ-Signature`, 5-minute replay window) before your handlers run; unverified requests are rejected with a 401. Events other than the two chat events are acknowledged and ignored, so your endpoint never causes webhook retries. # CLI Source: https://docs.smartlyq.com/sdks/cli Every SmartlyQ API operation as a shell command. **Install:** `npm install -g @smartlyqofficial/cli` · [GitHub](https://github.com/SmartlyQ/smartlyq-cli) · [npm](https://www.npmjs.com/package/@smartlyqofficial/cli) · Node 18+. The `smartlyq` command mirrors the SDK surface one-to-one: `smartlyq `. Perfect for shell scripts, cron jobs, CI pipelines, and quick one-offs without writing code. ## Login ```bash theme={null} smartlyq login # prompts for your API key, stores it in ~/.smartlyq/config.json ``` Or skip login and use `--api-key` / the `SMARTLYQ_API_KEY` environment variable. Precedence: flag > environment > config file. `smartlyq logout` removes the stored key. ## Usage ```bash theme={null} # Who am I? smartlyq account get-me # Publish a social post smartlyq social create-post --data '{"text":"Hello from the CLI!","account_ids":["acc_123"]}' # Generate an image, then poll the job smartlyq images generate --data '{"prompt":"A minimalist product shot of a smart speaker"}' smartlyq jobs get job_abc123 # List draft articles, page 2 smartlyq articles list --query 'status=draft&page=2' # SEO keyword research smartlyq seo keyword-research --data '{"keyword":"social media scheduling"}' # Body from a file, or from stdin smartlyq social create-post --data @post.json cat post.json | smartlyq social create-post --data - ``` Discover everything from the built-in help: `smartlyq --help` lists resources, `smartlyq social --help` lists its commands, and `smartlyq social create-post --help` shows arguments and the underlying endpoint. ## Flags | Flag | Purpose | | ------------------------- | ---------------------------------------------------------- | | `--data ` | Request body - inline JSON, `@file.json`, or `-` for stdin | | `--query 'k=v&k2=v2'` | Query parameters (also accepts JSON) | | `--profile ` | Act on behalf of a managed [Profile](/guides/profiles) | | `--idempotency-key ` | Safe retries for writes | | `--api-key ` | Override the stored/env API key | | `--output json\|pretty` | Output format (default `pretty`) | | `--timeout ` | Per-request timeout | ## Errors and exit codes API errors print to stderr as `Error : (request )` and exit with code `1`, so failures propagate correctly in scripts and CI. ## Next steps Request and response schemas for every endpoint. How generation jobs work and how to poll them. Issues, changelog, and the full method reference. # Go SDK Source: https://docs.smartlyq.com/sdks/go The official SmartlyQ SDK for Go. **Install:** `go get github.com/SmartlyQ/smartlyq-go` · [GitHub](https://github.com/SmartlyQ/smartlyq-go) · [pkg.go.dev](https://pkg.go.dev/github.com/SmartlyQ/smartlyq-go) · Go 1.22+, standard library only. ## Quickstart ```go theme={null} package main import ( "context" "encoding/json" "fmt" smartlyq "github.com/SmartlyQ/smartlyq-go" ) func main() { client := smartlyq.NewClient("sqk_live_...") // or smartlyq.NewClient("") to read SMARTLYQ_API_KEY ctx := context.Background() // Who am I? me, err := client.Account.GetMe(ctx, nil) if err != nil { panic(err) } fmt.Println(string(me.Data)) // Publish a social post post, err := client.Social.CreatePost(ctx, map[string]any{ "text": "Hello from the SmartlyQ SDK!", "account_ids": []string{"acc_123"}, }, nil) if err != nil { panic(err) } var created struct { ID string `json:"id"` } json.Unmarshal(post.Data, &created) } ``` Every API resource is a field on the client - `client.Social`, `client.Images`, `client.Videos`, `client.Articles`, `client.SEO`, `client.Contacts`, `client.Chatbots`, `client.Captain`, `client.Workspaces`, `client.Profiles`, and the rest. Responses come back as an `Envelope` whose `Data` field is raw JSON you unmarshal into your own types. ## Configuration ```go theme={null} client := smartlyq.NewClient("sqk_live_...", smartlyq.WithTimeout(60*time.Second), smartlyq.WithMaxRetries(2), // automatic retries on 429/5xx ) ``` Per-request options are the last argument of every method: ```go theme={null} client.Social.CreatePost(ctx, body, &smartlyq.RequestOptions{ IdempotencyKey: "my-unique-key", // safe retries for writes ProfileID: "prof_123", // act on behalf of a managed Profile }) ``` ## Async jobs Generation endpoints return a job - poll it until it completes (see [Async jobs](/guides/async-jobs)): ```go theme={null} res, _ := client.Videos.Generate(ctx, map[string]any{"prompt": "A 5s product teaser"}, nil) var gen struct { JobID string `json:"job_id"` } json.Unmarshal(res.Data, &gen) for { job, _ := client.Jobs.Get(ctx, gen.JobID, nil) var j struct { Status string `json:"status"` Result json.RawMessage `json:"result"` } json.Unmarshal(job.Data, &j) if j.Status != "queued" && j.Status != "processing" { fmt.Println(string(j.Result)) break } time.Sleep(3 * time.Second) } ``` ## Error handling ```go theme={null} _, err := client.Articles.Generate(ctx, map[string]any{"topic": "AI trends"}, nil) if err != nil { var apiErr *smartlyq.APIError if errors.As(err, &apiErr) { fmt.Println(apiErr.StatusCode, apiErr.Code, apiErr.Message, apiErr.RequestID) } } ``` The full method reference lives in the [repository README](https://github.com/SmartlyQ/smartlyq-go#api-reference); request and response schemas are in the [API Reference](/api-reference/account/get-me). ## Next steps Request and response schemas for every endpoint. How generation jobs work and how to poll them. Issues, changelog, and the full method reference. # Node.js SDK Source: https://docs.smartlyq.com/sdks/node The official SmartlyQ SDK for Node.js and TypeScript. **Install:** `npm install @smartlyqofficial/node` · [GitHub](https://github.com/SmartlyQ/smartlyq-node) · [npm](https://www.npmjs.com/package/@smartlyqofficial/node) · Node 18+, zero dependencies, full TypeScript types. ## Quickstart ```typescript theme={null} import SmartlyQ from '@smartlyqofficial/node'; const sq = new SmartlyQ({ apiKey: process.env.SMARTLYQ_API_KEY }); // or: new SmartlyQ('sqk_live_...') // Who am I? const me = await sq.account.getMe(); // Publish a social post const post = await sq.social.createPost({ text: 'Hello from the SmartlyQ SDK!', account_ids: ['acc_123'], }); // Generate an image with AI const image = await sq.images.generate({ prompt: 'A minimalist product shot of a smart speaker', }); ``` Every API resource is a property on the client - `sq.social`, `sq.images`, `sq.videos`, `sq.articles`, `sq.seo`, `sq.contacts`, `sq.chatbots`, `sq.captain`, `sq.workspaces`, `sq.profiles`, and the rest - with fully typed request and response shapes generated from the OpenAPI spec. ## Configuration ```typescript theme={null} const sq = new SmartlyQ({ apiKey: 'sqk_live_...', // or set SMARTLYQ_API_KEY timeout: 60_000, // per-request timeout (ms) maxRetries: 2, // automatic retries on 429/5xx }); ``` Per-request options are the last argument of every method: ```typescript theme={null} await sq.social.createPost(body, { idempotencyKey: 'my-unique-key', // safe retries for writes profileId: 'prof_123', // act on behalf of a managed Profile timeout: 10_000, }); ``` ## Async jobs Generation endpoints return a job - poll it until it completes (see [Async jobs](/guides/async-jobs)): ```typescript theme={null} const { data } = await sq.videos.generate({ prompt: 'A 5s product teaser' }); let job = await sq.jobs.get(data.job_id); while (['queued', 'processing'].includes(job.data.status)) { await new Promise((r) => setTimeout(r, 3000)); job = await sq.jobs.get(data.job_id); } console.log(job.data.result); ``` ## Error handling ```typescript theme={null} import { SmartlyQError } from '@smartlyqofficial/node'; try { await sq.articles.generate({ topic: 'AI trends' }); } catch (err) { if (err instanceof SmartlyQError) { console.error(err.status, err.code, err.message, err.requestId); } } ``` The full method reference lives in the [repository README](https://github.com/SmartlyQ/smartlyq-node#api-reference); request and response schemas are in the [API Reference](/api-reference/account/get-me). ## Next steps Request and response schemas for every endpoint. How generation jobs work and how to poll them. Issues, changelog, and the full method reference. # SDKs & CLI Source: https://docs.smartlyq.com/sdks/overview Official SmartlyQ client libraries for Node.js, Python, Go, and PHP - plus a CLI and a chatbot adapter. Official client libraries for the SmartlyQ API. Every SDK covers the **full API surface** - social posting and scheduling, AI content generation (articles, images, video, audio, presentations), SEO research, CRM, chatbots, profiles, workspaces, and more - with the same resource-based method names in every language. All SDKs are generated from the [OpenAPI spec](https://docs.smartlyq.com/openapi.json) and republish automatically when the API gains new endpoints, so they are never behind the API Reference. ## Official SDKs `npm install @smartlyqofficial/node` TypeScript types for every request and response. Zero dependencies, native fetch, Node 18+. `pip install smartlyq` Clean snake\_case client on httpx. Context-manager friendly, Python 3.9+. `go get github.com/SmartlyQ/smartlyq-go` Context-first, standard library only, zero dependencies. Go 1.22+. `composer require smartlyq/sdk` PSR-4, zero runtime dependencies, PHP 8.1+. `npm install -g @smartlyqofficial/cli` Every API operation as a shell command - perfect for scripts, cron jobs, and CI. `npm install @smartlyqofficial/chat-sdk-adapter` Build a multi-platform chatbot on the unified inbox. Write once - works on every connected platform. ## One surface, every language The method names mirror each other across every SDK - learn it once, use it anywhere: ```typescript Node.js theme={null} import SmartlyQ from '@smartlyqofficial/node'; const sq = new SmartlyQ({ apiKey: process.env.SMARTLYQ_API_KEY }); const post = await sq.social.createPost({ text: 'Hello from SmartlyQ!', account_ids: ['acc_123'], }); ``` ```python Python theme={null} from smartlyq import SmartlyQ sq = SmartlyQ() # reads SMARTLYQ_API_KEY post = sq.social.create_post({ "text": "Hello from SmartlyQ!", "account_ids": ["acc_123"], }) ``` ```go Go theme={null} client := smartlyq.NewClient("") // reads SMARTLYQ_API_KEY post, err := client.Social.CreatePost(ctx, map[string]any{ "text": "Hello from SmartlyQ!", "account_ids": []string{"acc_123"}, }, nil) ``` ```php PHP theme={null} $sq = new Smartlyq\SmartlyQ(); // reads SMARTLYQ_API_KEY $post = $sq->social->createPost([ 'text' => 'Hello from SmartlyQ!', 'account_ids' => ['acc_123'], ]); ``` ```bash CLI theme={null} smartlyq social create-post \ --data '{"text":"Hello from SmartlyQ!","account_ids":["acc_123"]}' ``` ## Building an AI agent instead? The hosted **Model Context Protocol** server exposes the same API as tools for Claude, Cursor, ChatGPT, and any MCP client - usually the fastest integration for agents. The SDKs are the right choice when you are writing code. ## Shared behavior Every SDK ships the same batteries: * **Authentication** via `Authorization: Bearer sqk_live_...` - pass the key to the constructor or set the `SMARTLYQ_API_KEY` environment variable. * **Automatic retries** with exponential backoff on `429` and `5xx`, honoring `Retry-After`. * **Idempotency keys** for safely retrying writes. * **Profile scoping** - act on behalf of a managed [Profile](/guides/profiles) by setting the profile option (sent as `X-Profile-Id`). * **Typed errors** carrying the HTTP status, error `code`, message, and `request_id` for support. * **Sandbox keys** (`sqk_test_...`) work everywhere - simulated responses, no charges. # PHP SDK Source: https://docs.smartlyq.com/sdks/php The official SmartlyQ SDK for PHP. **Install:** `composer require smartlyq/sdk` · [GitHub](https://github.com/SmartlyQ/smartlyq-php) · [Packagist](https://packagist.org/packages/smartlyq/sdk) · PHP 8.1+, zero runtime dependencies (ext-curl). ## Quickstart ```php theme={null} account->getMe(); // Publish a social post $post = $sq->social->createPost([ 'text' => 'Hello from the SmartlyQ SDK!', 'account_ids' => ['acc_123'], ]); // Generate an image with AI $image = $sq->images->generate(['prompt' => 'A minimalist product shot of a smart speaker']); ``` Every API resource is a property on the client - `$sq->social`, `$sq->images`, `$sq->videos`, `$sq->articles`, `$sq->seo`, `$sq->contacts`, `$sq->chatbots`, `$sq->captain`, `$sq->workspaces`, `$sq->profiles`, and the rest. Methods return decoded associative arrays. ## Configuration ```php theme={null} $sq = new SmartlyQ('sqk_live_...', [ 'timeout' => 60, // per-request timeout (seconds) 'max_retries' => 2, // automatic retries on 429/5xx ]); ``` Per-request options are the last argument of every method: ```php theme={null} $sq->social->createPost($body, [ 'idempotency_key' => 'my-unique-key', // safe retries for writes 'profile_id' => 'prof_123', // act on behalf of a managed Profile ]); ``` ## Async jobs Generation endpoints return a job - poll it until it completes (see [Async jobs](/guides/async-jobs)): ```php theme={null} $result = $sq->videos->generate(['prompt' => 'A 5s product teaser']); $jobId = $result['data']['job_id']; $job = $sq->jobs->get($jobId); while (in_array($job['data']['status'], ['queued', 'processing'])) { sleep(3); $job = $sq->jobs->get($jobId); } print_r($job['data']['result']); ``` ## Error handling ```php theme={null} use Smartlyq\SmartlyQError; try { $sq->articles->generate(['topic' => 'AI trends']); } catch (SmartlyQError $e) { echo $e->getStatusCode() . ' ' . $e->getErrorCode() . ' ' . $e->getMessage() . ' ' . $e->getRequestId(); } ``` The full method reference lives in the [repository README](https://github.com/SmartlyQ/smartlyq-php#api-reference); request and response schemas are in the [API Reference](/api-reference/account/get-me). ## Next steps Request and response schemas for every endpoint. How generation jobs work and how to poll them. Issues, changelog, and the full method reference. # Python SDK Source: https://docs.smartlyq.com/sdks/python The official SmartlyQ SDK for Python. **Install:** `pip install smartlyq` · [GitHub](https://github.com/SmartlyQ/smartlyq-python) · [PyPI](https://pypi.org/project/smartlyq/) · Python 3.9+, built on httpx. ## Quickstart ```python theme={null} from smartlyq import SmartlyQ sq = SmartlyQ() # reads SMARTLYQ_API_KEY, or pass api_key="sqk_live_..." # Who am I? me = sq.account.get_me() # Publish a social post post = sq.social.create_post({ "text": "Hello from the SmartlyQ SDK!", "account_ids": ["acc_123"], }) # Generate an image with AI image = sq.images.generate({"prompt": "A minimalist product shot of a smart speaker"}) ``` Every API resource is an attribute on the client - `sq.social`, `sq.images`, `sq.videos`, `sq.articles`, `sq.seo`, `sq.contacts`, `sq.chatbots`, `sq.captain`, `sq.workspaces`, `sq.profiles`, and the rest - with snake\_case method names mirroring the other SDKs. ## Configuration ```python theme={null} sq = SmartlyQ( api_key="sqk_live_...", # or set SMARTLYQ_API_KEY timeout=60.0, # per-request timeout (seconds) max_retries=2, # automatic retries on 429/5xx ) ``` Per-request options are keyword arguments on every method: ```python theme={null} sq.social.create_post( body, idempotency_key="my-unique-key", # safe retries for writes profile_id="prof_123", # act on behalf of a managed Profile timeout=10.0, ) ``` The client is a context manager if you want deterministic cleanup: ```python theme={null} with SmartlyQ() as sq: sq.account.get_me() ``` ## Async jobs Generation endpoints return a job - poll it until it completes (see [Async jobs](/guides/async-jobs)): ```python theme={null} import time result = sq.videos.generate({"prompt": "A 5s product teaser"}) job_id = result["data"]["job_id"] job = sq.jobs.get(job_id) while job["data"]["status"] in ("queued", "processing"): time.sleep(3) job = sq.jobs.get(job_id) print(job["data"]["result"]) ``` ## Error handling ```python theme={null} from smartlyq import SmartlyQ, SmartlyQError try: sq.articles.generate({"topic": "AI trends"}) except SmartlyQError as err: print(err.status_code, err.code, err, err.request_id) ``` The full method reference lives in the [repository README](https://github.com/SmartlyQ/smartlyq-python#api-reference); request and response schemas are in the [API Reference](/api-reference/account/get-me). ## Next steps Request and response schemas for every endpoint. How generation jobs work and how to poll them. Issues, changelog, and the full method reference.