https://api.smartlyq.com/v1.
APIImprovement
Platform refusals now return 422 with the reason, instead of an empty 502
When a connected platform refused a request - an expired token, a value it does not accept, a WhatsApp message outside the 24-hour window - the API answered502. Our edge network replaces the body of 502 responses, so you received error code: 502 and none of the explanation.- Refusals now return
422with the platform’s reason inerror.message. This covers the social account, published post, WhatsApp, Google Business, Reddit, reviews, ads, SEO, avatar and video AI endpoints. The errorcodevalues are unchanged (PLATFORM_ERROR,PLATFORM_UPSTREAM_ERROR,PROVIDER_ERROR,CREATE_FAILED,SEO_PROVIDER_ERROR,PLATFORM_DELETE_FAILED). 502now only means the platform could not be reached at all. It still arrives without a body; retry after a short delay.- AI generation failures (hooks, captions, B-roll, emphasis, thumbnails) return
422withretryable: true, because a second attempt can succeed. PATCH /social/accounts/{account_id}/facebook/pagesaves each field separately. Facebook could store some fields and refuse a later one while the request reported a failure. A refusal now stops at that field and returnsdetails.fieldanddetails.saved_fields, and the success response lists the fields saved.- If your integration branches on
502, treat422with one of the codes above as a refusal to fix, not an outage. See Errors.
APIBilling
Connected accounts are billed by the day, and the rates are now documented
Per-account charges were never described in these docs. They are now, and accounts are counted daily rather than once a month.- What you pay - each connected social account is billed from your Developer wallet: 3 for accounts 11 to 100, 75. See Billing & Credits.
- Accounts are counted by the day. An account is billed for each day it stays connected, so a part month costs a part price: 15 accounts connected for half a month bills 7.5 account-months, $45. Connect on the 20th and you pay for eleven days. A full month costs the same as it always did.
- Disconnecting stops the charge; pausing does not. Pausing a profile stops it publishing, but its accounts stay connected and stay billable. If you no longer need an account, disconnect it.
- The Profiles tab and the Wallet tab in the Developer Dashboard now show your current rates, so what you pay is visible next to the accounts and next to the balance it is charged against.
SocialImprovement
Posts are screened against the Content Policy before they are created
Content checks previously ran only inside AI Captain and the website builder. They now run on every post, whichever way it is created: the API, the app, WordPress or Captain.- Prohibited content is refused at creation with
422 CONTENT_POLICY_VIOLATION, before anything is scheduled or published. The response links the Content Policy and the error is not retryable: edit the post, or contact support if you think the decision is wrong. - Links to adult subscription platforms are refused. This is a check on the links in your caption and
linkfield, so ordinary posts that merely mention such a service in passing are unaffected. - Duplicate detection now looks across your profiles, not just inside one. The existing 24-hour duplicate guard only ever compared posts within a single workspace, which meant the same content sent to many profiles was invisible to it. Publishing identical content to several of your profiles in quick succession is still allowed and still publishes; it is now recorded for review.
- New error code
CONTENT_POLICY_VIOLATION- HTTP422, not retryable. See Errors.
APIImprovement
Request limits now scale with your connected accounts, and posting is capped per account
Your request allowance used to be a flat 60 per minute regardless of size. It now scales with how many social accounts you have connected, because that is what determines how much work your integration actually has to do.- Requests per minute by connected accounts - 0 to 2 accounts get 60, 3 to 2,000 get 600, and 2,001 or more get 1,200. The limit moves as soon as you connect or disconnect an account. There is still no separate request limit per platform. A custom limit configured on your key continues to override the table.
- Analytics reads get a per-second ceiling - your per-minute allowance divided by 60, never below 6. It applies to every endpoint requiring the
analytics:readscope, so a dashboard refresh cannot spend a whole minute’s budget in one burst and then stall. Exceeding it returns429withRetry-After: 1, and the rejected request does not count against your per-minute allowance. - Each connected account now has a daily posting cap - Threads 250, Instagram 100, Facebook 100, X 50, Pinterest 25, TikTok 15 video plus 15 photo as separate allowances, and 50 for every other platform. Caps are per account, not a shared pool, so connecting more accounts raises your total throughput.
- Every account is also limited to 25 posts per hour across all platforms, so a day’s allowance cannot be published in a single burst.
- Capped accounts are held back, not the whole request. When you publish to several accounts at once, the accounts still under their caps go through and only the capped ones are refused, before the platform is called. The response names the limit that was reached and when to retry.
- Two error codes are now listed in Errors that were missing or wrong.
RATE_LIMIT_EXCEEDEDmeans you exceeded your own request limit.PLATFORM_RATE_LIMITEDmeans a connected platform is temporarily limiting publishing for that account, is retryable after its cooldown, and had never been documented despite being returned by the publishing endpoints. The429example in the OpenAPI spec showed a code the API does not return and now showsRATE_LIMIT_EXCEEDED.
SocialNew
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-limitreturnslimit(30),used,remainingandnext_available_at.usedcounts 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_atis 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 gets422 VALIDATION_ERROR. Requires scopesocial:read. - Reels over the limit are refused when you create or schedule them. A post with
platform_options.facebook.typeset toreelis 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 is422 PLATFORM_DAILY_LIMIT_REACHEDwith a message naming the Page and when it can post a Reel again, anddetailscarryingaccount_id,limit,usedandnext_available_at. Nothing is created. - Editing is checked too.
PATCH /social/posts/{post_id}refuses an update that would make a scheduled Reel go over the limit, with the same422 PLATFORM_DAILY_LIMIT_REACHEDanddetails. 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_REACHEDand a message starting “Facebook daily Reel limit reached”. It is not retried automatically. - New error code
PLATFORM_DAILY_LIMIT_REACHED- HTTP422, not retryable. Publish afterdetails.next_available_at, or send the post as a different type. See Errors.
SocialNew
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/settingsreturnsusers_can_message, ornullwhen Facebook did not report it. - Turn messaging on or off -
PATCH /social/accounts/{account_id}/facebook/page/settingswithusers_can_message.falsehides the Message button and blocks replies in chats that already exist;truerestores both. - Set the profile picture -
POST /social/accounts/{account_id}/facebook/page/picturewith an imageurl. 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/coverwithurland an optionaloffset_y(0-100, default 50). No feed story or notification. It needs the business permission on the connection; connections without it get403 CONNECTION_SCOPE_MISSINGwithreconnect_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_ERRORwithdetails.fieldset tourland adetails.reasonsaying why. GET /social/accounts/{account_id}/facebook/pagenow returnspictureandcover, and its response fields are documented.coveris absent when the Page has no cover photo.
sqk_test_...) validate the request, including the image URL, and return a success response without contacting Facebook. See Facebook for the image rules and recommended sizes.APIDocs
Every endpoint says which scope it needs, and 30 more are listed
Scopes were enforced but never published. The API has always answered403 when a key lacks the right scope, and the scope table 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/balanceand/me/billingnow say plainly that any valid key works. - The spec carries
x-scopeson 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.
SocialNew
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
warningsentry 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_urlcomes backnull. A story has no permanent page and expires after 24 hours;post_idis 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.SocialFixed
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.linknow 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.warningson the create response, keyed by platform, instead of the link quietly disappearing for that leg of the post. platform_options.<platform>.linkwins over a request-levellink, so you can send one default and override it per platform.- A malformed link is now a
422rather than a silent drop. Links must behttp/https, carry a host, and stay under 2,048 characters.
content, that still behaves exactly as before and needs no change. Move it to link when you want the preview card.See Platform options for the per-platform keys.SocialFixed
Facebook type: "feed" works, and an unknown post type now says so
Our platform options guide 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 a500telling 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.
AdsChanged
Ad metrics now cover one window on every platform
The metric fields onGET /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/analyticsgained aby_campaignblock - the same per-campaign figures, scoped to whateverdate_from/date_toyou ask for. Use it for any window other than the fixed 28 days, or to compare campaigns over a specific period.
AdsChanged
Bulk campaign status: 50 per call, and a 207 for partial batches
Two changes toPOST /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 backMeta rate limit reachedand 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, not200.200means every id succeeded;207means 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.
APIChanged
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.AdsNew
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’stargeting.interests.- Boost Post -
POST /ads/campaignsnow acceptscreative.object_story_id("{page_id}_{post_id}", from the newGET /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 aMESSAGE_PAGECTA opens a Messenger chat instead of a link; aCALL_NOWCTA plussettings.phone_number(E.164) dials a number instead.
AdsNew
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. Newads:read / ads:write scopes.- Campaigns, ad sets, ads - full CRUD plus
pause,resume,duplicate, andarchiveon each, andPOST /ads/campaigns/bulk-statusfor pausing or resuming up to 100 campaigns in one call (per-item results, never all-or-nothing). - Creating a campaign -
POST /ads/campaignswithplatform: metaand apage_idlaunches a real campaign on Meta. Every other platform, or Meta without apage_id, creates a local draft - only Meta has live launch wiring today. - Audiences, pixels, lead forms -
GET /ads/audiencesandGET /ads/pixelsare read-only mirrors of what’s already on the platform.POST /ads/lead-formscreates 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/creativestakes an already-hostedfile_url- upload bytes throughPOST /media/upload-directfirst, this endpoint doesn’t handle raw uploads. - Accounts & diagnostics -
GET /ads/accountslists connected ad accounts;GET /ads/accounts/diagnosticsadds live per-account API rate-limit usage and last-sync health.GET /ads/audit-logsupportscampaign_idto scope the log to one campaign and everything under it. - Estimate -
POST /ads/estimatereturns a real Meta audience-size range for a targeting spec before you launch. Every other platform returnsavailable: falserather 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.
MediaFix
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.WhatsAppNew
WhatsApp sandbox and bring-your-own-number bridge
- Sandbox - test WhatsApp end-to-end without connecting your own number.
POST /whatsapp/sandbox/sessionsactivates a phone against a shared SmartlyQ-owned number (they reply to prove ownership), thenPOST .../sessions/{session_id}/sendre-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}/bridgeadds 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 anywhereaccount_idis accepted.
503 SANDBOX_UNAVAILABLE if not) - it’s opt-in infrastructure, not guaranteed on every deployment.WhatsAppNew
WhatsApp Flows and Block Users
- Flows - build native in-chat forms (booking, surveys, lead capture) without leaving WhatsApp.
POST /whatsapp/flowscreates a DRAFT flow,PUT .../flows/{flow_id}/jsonuploads its screens,POST .../flows/{flow_id}/publishmakes it sendable, andPOST /whatsapp/messageswithtype: "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-usersblocks 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 afailedarray without failing the rest of the batch.
whatsapp_business_management / whatsapp_business_messaging permissions every other WhatsApp endpoint already uses - no new connection or reconnect required.APINew
Comment moderation, message delete, and automation version history
- Comment moderation -
POST /social/comments/{id}/moderateapproves or rejects a comment (with an optional author ban). YouTube only - uses the same underlying endpointPOST .../hidealready 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 anotefield. - Automation duplicate -
POST /automations/{id}/duplicate. Copies the graph into a new automation, always starting asdraft. - 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.
WhatsAppBilling
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:- 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.
category on the request and we will skip the lookup.NewSocialWhatsApp
Per-platform management: pages, boards, playlists, mentions, flairs
The write-side counterparts to the platform lookups. Each one reports a missing permission as403 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 -
GETandPATCH /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/boardscreates 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}/mentionslists posts where a Facebook page or Instagram account was tagged, andPOST .../mentions/{mention_id}/replyanswers 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 asplatform_options.reddit.flair_idwhen you post. - WhatsApp template library -
GET /whatsapp/template-librarybrowses Meta’s prewritten, pre-approved templates, andPOST /whatsapp/templates/from-libraryadopts one under your own name. This skips the draft-and-wait-for-review cycle.
NewLogsAnalytics
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.
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.WhatsAppBilling
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.WhatsAppNew
WhatsApp Business messaging (Phase 1)
SmartlyQ now connects to the WhatsApp Business Cloud API:- Connect a WhatsApp Business number -
POST /social/connect/whatsappwith{ 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(joiningmessage.received). - Manage - list/create message templates (
/whatsapp/templates), read/update the business profile (/whatsapp/business-profile), and list phone numbers (/whatsapp/phone-numbers).
WhatsAppNew
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 thereaction.receivedwebhook 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.
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.SocialNew
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.SocialFixed
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-glancepayment.status.
APINew
Profile update and direct media upload
PATCH /profiles/{id}- rename a profile or change itsexternal_idwithout 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 singlemultipart/form-datarequest, no presign round-trip. Stored straight to your media library and returned in the same shape asGET /media/{id}; the type is detected from the file’s actual bytes, up to 200MB.
WebhooksNew
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), andpost.external.created/post.external.updatedfor 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.newandreview.updatedfor Google Business reviews and replies.
SocialNew
Idempotent creates, per-platform stagger, richer platform options
- Idempotency keys - send
X-Idempotency-Key(orX-Request-Id) onPOST /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. - Per-platform stagger - on the schedule endpoint, any platform can carry its own
scheduled_timeinsideplatform_options: one call, each network publishing at its own moment (response lists the createdstaggered[]legs). - Platform options, documented and extended - new Platform options guide covering every per-platform key. Newly added: Telegram
parse_mode/silent/disable_link_preview/protect_content, Redditflair_id/flair_text, YouTubetags/category_id/contains_synthetic_media(AI disclosure), Instagramshare_to_feedfor Reels.
SocialNew
Connect Telegram by API, Discord headlessly
Every platform can now be connected without the SmartlyQ dashboard:- Telegram credential connect -
POST /social/connect/telegramwith{ "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/discordmints the server-webhook authorize URL like any OAuth platform.
SocialNew
Telegram and Discord publishing
Two newplatforms targets on POST /social/posts and POST /social/posts/schedule:telegram- publishes through your own bot (created with @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.
POST /social/connect/discord) - its server-webhook authorize flow works from your own UI like any OAuth platform. See the new Telegram and Discord platform pages.SocialNew
Auto-transcode, publish progress, and exact failure reasons
OnePOST /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._transcodesand in thetranscoded[]list onpost.published/post.partial/post.failedwebhook 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, soGET /social/posts/{id}shows where a slow publish is instead of a bareprocessing. - 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 includeerror.detailwith 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.
SocialNew
Rich DMs, comment-to-DM automations, connection selection, deep insights
- Structured DMs -
POST /social/conversations/{id}/messagesnow takesbuttons(Facebook button template, up to 3 links) andquick_replies(Facebook + Instagram chips, up to 13), plus a typing indicator atPOST .../typing. - Comment-to-DM -
POST /social/comments/{comment_id}/private-replyDMs the comment’s author (Meta private replies, FB + IG). Newcomment_createdautomation trigger +action.private_replyrunner 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-selectre-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) andGET .../instagram/audience(follower demographics by city/country/age/gender).
SocialNew
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 computedcan_postverdict for your connected account (banned? restricted? what submission kinds?). - X mentions -
GET /social/accounts/{account_id}/x/mentionslists recent posts mentioning the account, with engagement metrics. - Contact channels -
GET /contacts/{id}/channelsshows 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-actionschanges a link’s URI in place.
SocialNew
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.WebhooksFixed
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.SocialNew
Edit published posts, external sync, live insights
- Edit published -
POST /social/posts/{post_id}/editedits 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-metadataupdates title/description/tags, adds to a playlist, and sets a custom thumbnail after publish. - External post sync -
POST /social/posts/sync-externalimports posts published natively on Facebook, Instagram, Threads, YouTube or TikTok (per-account opt-in); they appear inGET /social/posts?status=externalwith analytics. - Live account insights -
GET /social/accounts/{account_id}/insightsreturns platform-fresh follower/impression/reach/engagement metrics for every provider-backed platform.
SocialNew
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/retweetsand its DELETE counterpart retweet/un-retweet with the connected account. - Story insights -
GET .../instagram/stories/{story_id}/insightswith a passthroughmetricsparameter. - Delete review reply -
DELETE /reviews/{review_id}/replyremoves 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.
SocialNew
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), andsubreddits/{subreddit}/rules- check the rules before you submit. - Instagram stories -
GET /social/accounts/{account_id}/instagram/storieslists the account’s currently live stories. - Facebook reactions -
GET /social/accounts/{account_id}/facebook/post-reactionsreturns the per-type breakdown (like, love, wow, haha, sad, angry, care) for any published post.
SocialNew
Per-platform lookups
Five account-level lookups so integrations can compose with real platform data - allsocial: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
CRMNew
Automations API + contact bulk import
- Operate your automations - new
/automationsgroup: list and inspect automations built in the visual builder, activate/pause them, queue a manual run withPOST /automations/{automation_id}/trigger(optional JSON payload), and read run history down to per-step execution logs. - Bulk import -
POST /contacts/bulkimports 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).
SocialNew
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.
ReviewsNew
Reviews API
Reputation Management is now scriptable:- List -
GET /reviewswith rating / reply-status / account filters. - Reply -
POST /reviews/{review_id}/replypublishes your reply on Google Business. - Sync -
POST /reviews/syncpulls fresh reviews on demand, per account or workspace-wide.
AnalyticsNew
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.AnalyticsNew
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.
SocialNew
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}/moverelocates a connected account between your developer workspace and your profiles (profile_id: 0moves it back). Requiressocial:write+profiles:manage, developer context only, and refuses with 409 while the account has scheduled or in-flight posts.
SocialNew
Account insights
- Bulk health -
GET /social/accounts/healthreports 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=30returns 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-infosurfaces live privacy levels, comment/duet/stitch availability, and max video duration - the fields TikTok requires you to show before posting.
SocialNew
Bulk scheduling and duplicate protection
- Bulk schedule -
POST /social/posts/bulkschedules up to 50 posts in one call, from a JSONpostsarray or an inlinecsvstring (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/validatereturns a per-row report (structure, per-platform caption/media limits, duplicate check) without creating or billing anything. - 24h duplicate-content guard -
POST /social/postsandPOST /social/posts/schedulenow reject content identical to a post created in the same workspace within the last 24 hours with409 DUPLICATE_CONTENT(charge refunded). Passallow_duplicates: trueto override. This catches retry loops and mis-wired crons before they spam your channels.
SocialNew
Evergreen post recycling
Keep your best content in rotation automatically.- Recycle on schedule -
POST /social/posts/schedulenow accepts an optionalrecycleobject:{ interval_hours, max_repeats?, until? }. After each occurrence publishes, the next one is scheduled automatically. A bound is required (max_repeatsup to 100 and/oruntilup to 2 years out) - no unbounded chains. - Stop a chain -
DELETE /social/posts/{post_id}/recycleclears 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}showsrecycle_countandrecycle_parent_idfor lineage.
SocialNew
Pre-flight validation
Catch problems before they cost a publish attempt. Both endpoints are free, unbilled, and need onlysocial:read.- Validate a post -
POST /social/validate/postdry-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/mediaconfirms a URL is publicly reachable and reports its content type, byte size, and detected kind (image/video). Private and internal hosts are rejected.
SocialNew
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-slotand/preview?count=N- see upcoming open slotsPOST /social/posts/schedulenow takesqueue_idinstead ofscheduled_time: the next open slot is resolved server-side and the post occupies it (FIFO), so concurrent calls fill consecutive slots- Bonus:
scheduled_timenow honors an optionaltimezonefield (previously UTC-only)
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.WebhooksNew
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 failuresPOST /webhooks/{id}/test- sends awebhook.testevent 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 bywebhook_id,event, orstatusjob.completed/job.failednow 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 /webhooksandPUT /webhooks/{id}reject unknown event names outright instead of silently dropping them - Guardrail: webhook routes reject the
X-Profile-Idheader - webhooks live on your developer workspace and receive profile events via fan-up
WebhooksDocs
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 now documents the complete 25-event catalog (posting, accounts, inbox, jobs, billing & keys, CRM) with the
datafields each event carries - Every event now ships a JSON payload schema in the OpenAPI spec’s standard
webhooksobject - SDK generators and AI agents can consume delivery shapes directly from openapi.json POST /webhooksnow documents the exact allowedeventsvalues (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 theX-SmartlyQ-Event-Iddedupe header - Documented previously-missing delivery semantics:
Retry-Afterhonored on 429 (capped 1h), dead-letter after the 5th attempt, auto-pause after 10 consecutive failures, 90-day delivery retention, 10 webhooks per workspace
ProfilesNew
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 / deletePOST /profiles/{id}/connect-link- hosted, single-use connect page for your customerPOST /profiles/{id}/connect/{platform}- raw per-platform OAuth URLs for your own UIX-Profile-Idheader on existing endpoints - data scopes to the profile; billing, rate limits and logs stay on your accountGET /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 withprofile_idattached - New scopes:
profiles:read,profiles:write,profiles:manage
Social
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.WorkspacesSaaS
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/plansandGET /saas/plans/{id}- list the SaaS plans configured on your accountGET /workspaces/{id}/subscription- a workspace’s current plan and statusGET /workspaces/{id}/wallet- a workspace’s credit wallet balancePOST /workspaces/bulk- pause, resume, or delete many workspaces in one call (SaaS-plan gated)
Workspaces
Workspace lifecycle management
Full workspace (sub-account) lifecycle on the public API:POST /workspaces- provision a new sub-accountGET /workspacesandGET /workspaces/{id}- list and inspectPOST /workspaces/{id}/pauseand/resume- suspend and restore accessPOST /workspaces/{id}/disable-saas- detach a workspace from your SaaS planDELETE /workspaces/{id}- guarded delete with async teardown
CRM
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), andPOST /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}/statusfor stage moves - Outbound CRM events are delivered through your existing webhooks
Presentations
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 URLsGET /presentations,GET /presentations/{id},DELETE /presentations/{id}
presentations:write and presentations:read.SEO
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.SEONew
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 emailPOST /seo/brand-lookup- how a brand appears across search and AI surfacesPOST /seo/prompt-explorer- discover the prompts where a domain is (or is not) cited
VideosShorts
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; pollGET /shorts/{uid}POST /videos/hook- a scroll-stopping overlay line for a videoPOST /videos/broll-suggestandPOST /videos/emphasis- B-roll and emphasis suggestions from a word-level transcriptPOST /videos/viral-thumbnail- generate a thumbnail from a titlePOST /content/caption- platform-tuned social captions
Developer Portal
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
Billing correctness hardening
Wallet charges are now fully idempotent (safe retries withIdempotency-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
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.revokedEvery 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.SEONew
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.Developer PortalBilling
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
SocialWebhooksNew
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 statusGET/POST /webhooksandDELETE /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, anddocs_urlfields - Platform posting rate limits are enforced at create time with clear errors
SocialNew
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
data + pagination envelope.
