Skip to content

Account

Store-wide settings shared by preorders and back in stock: setup, sending, integrations, languages, markets, locations and plan usage.

BetaAPI v2 is in public beta: endpoints, fields and behaviour can change before general availability. See the changelog

Last updated

Account settings are shop-level singletons: no :id in any path. Each write is what a dashboard settings page saves, through the same code, so the API and the dashboard never disagree. Writes are deep partial: absent fields are unchanged. Validation errors are keyed "<section>.<path>", for example "markets.preorder_location_filter.shopify_location_ids".

Shopify ids are shopify_<thing>_id and always a gid (gid://shopify/Location/111). Writes also accept the numeric tail.

Path prefix: /account

Method Path What it is
GET /account/setup Whether STOQ’s app embed is on, with the theme editor link when it is off
GET /account/setup_guide The Home setup guide’s steps for back in stock and preorders
POST /account/setup_guide/skip Skip the setup guide, as its Skip button does
GET / PATCH /account/sending Who sends each notification, the email and SMS providers, sender, reply-to
POST /account/sending/verify_domain Check the custom sending domain with Amazon SES
GET / PATCH /account/templates/email_style Font, header and description text sizes, logo size and logo of every STOQ email
POST / DELETE /account/templates/email_style/logo Upload or remove the email logo
GET /account/integrations Every integration’s status, setup steps and links (?category=, ?status=)
GET /account/integrations/{id} One integration’s status
PATCH /account/integrations Customer sync, Omnisend / Mailchimp keys, Meta pixel, Flow triggers, Postscript keyword
GET /account/integrations/mailchimp/audiences Audiences for mailchimp.list_id
GET /account/integrations/postscript/keywords Keywords for postscript.keyword
POST /account/integrations/{id}/connect Connect: the dashboard link for OAuth, or an API key (api_key, list_id, reconnect)
POST /account/integrations/{provider}/disconnect Disconnect any integration: revoke OAuth, remove a key, or turn a switch off
GET /account/integrations/{id}/events Connects, disconnects, key and settings changes (limit, before)
POST /account/integrations/{channel}/flows/{verify,send_test_event,auto_setup,confirm,reset} Klaviyo / Postscript flow setup
GET / PATCH /account/languages Dashboard language, multi-language, translated product titles
POST /account/languages/reset_locale Reset one language’s copy to STOQ’s default text
GET / PATCH /account/markets Market setup, market-to-location links, market URLs, preorder location filter
GET /account/locations Shopify locations
GET /account/plan_usage Plan, the limits it includes, and a month’s usage

sms_provider is stoq (STOQ sends), klaviyo or postscript; email_provider is stoq, klaviyo or flow.

STOQ sets three read-only flags, and a PATCH carrying any of them is refused: email_allowed (false when STOQ has stopped the shop’s email), sms_allowed (SMS is approved) and sms_restricted_content_verified (STOQ has reviewed the shop’s SMS content).

custom_address_status says where sending from the merchant’s own address stands:

  • not_set: use_custom_email_address is off, or email_reply_to_address is empty or STOQ’s own address.
  • pending: the toggle is on with the merchant’s address, but DKIM for its domain is not verified yet (email_dkim_verified is false). Mail goes from STOQ’s address (restock@<email_subdomain>.stoqapp.com, or preorders@ for preorder emails) until it is.
  • verified: DKIM is verified; mail goes from the merchant’s address (email_from_address).

It is read from the stored email_dkim_verified, which STOQ refreshes from Amazon SES when the address changes and on a regular check, so a DNS change can take a while to show. The DNS records to add while pending are not on this read: POST /account/sending/verify_domain asks SES for them and returns them as dns_records (name, type, value), the same records the dashboard’s Address drawer gives with Download records. It also refreshes email_dkim_verified. email_subdomain is read only.

notifications nests each channel under its notification: back_in_stock has signup_thank_you (email, SMS), restock_alert and reminder (email, SMS); preorders has confirmation (email, SMS), shipping_update and payment_failed (email). Each channel is worked out by the same code that sends the notification:

  • sender: who sends it right now (stoq, klaviyo, postscript or flow). null means it is not sent and the customer gets nothing: SMS only falls back to STOQ when the shop is verified for restricted content, and a back-in-stock alert on Shopify Flow without its trigger is not sent.
  • integration: { id, href } of the chosen provider’s integration (Klaviyo, Postscript or Shopify Flow), null for STOQ’s own. Read it for the step that fixes a fallback.
  • fallback_reason: why the sender is not the chosen email_provider or sms_provider, else null. credentials_missing (Klaviyo or Postscript not connected), flow_unverified (that notification’s Klaviyo or Postscript flow is not verified), flow_trigger_disabled (the Shopify Flow trigger is off), flow_not_supported (Shopify Flow has no trigger for reminders or preorder emails), custom_html_template (a custom HTML email always goes through STOQ).

Shop with Klaviyo email, the restock alert flow verified and the reminder flow not (trimmed):

{
"email_provider": "klaviyo",
"sms_provider": "stoq",
"notifications": {
"back_in_stock": {
"restock_alert": {
"email": { "sender": "klaviyo", "fallback_reason": null },
"sms": { "sender": "stoq", "fallback_reason": null }
},
"reminder": {
"email": { "sender": "stoq", "fallback_reason": "flow_unverified" }
}
},
"preorders": {
"confirmation": { "email": { "sender": "stoq", "fallback_reason": "flow_unverified" } }
}
}
}

GET /account/integrations returns integrations, one entry per integration: klaviyo, postscript, shopify_flow, judgeme, smile, gorgias, omnisend, mailchimp, meta_pixel, split_order, shopify_customers. Filter with category (marketing, shopify or other, the sections of the Integrations page) or status.

  • status: not_connected (nothing set up), off (set up but not in use), setup_incomplete (a required step is left), needs_attention (something that worked is broken, or it is the chosen provider but does not send), connected. Every status but connected has a reason { code, message }.
  • connection: method (oauth, api_key or switch), connected, legacy (a Klaviyo private key) and key_hint (the last 4 characters of a saved key). Keys and tokens are never returned.
  • selected_for: the channels it sends on, from email_provider and sms_provider on /account/sending.
  • steps: what setup needs, each with a state (done, todo, optional, blocked) and, where there is one, the action that runs it.
  • settings, plan (required: free or paid, available), actions, and links to the dashboard panel and the help article.

Klaviyo, Postscript and Shopify Flow are needs_attention when they are the chosen email_provider or sms_provider but a notification does not send through them: it goes out from STOQ instead (reason.code is the fallback_reason from /account/sending), it is not sent at all (notifications_blocked), or the integration isn’t connected (provider_not_connected). The Postscript keyword is optional. Klaviyo settings.flows is how the merchant chose to set up each campaign type’s flows, per channel: automatic (STOQ built them), manual (the merchant did), or null before they chose; set it with /account/integrations/{channel}/flows/auto_setup, confirm and reset.

{ "id": "klaviyo", "name": "Klaviyo", "category": "marketing", "status": "needs_attention",
"reason": { "code": "flow_unverified", "message": "Reminder email goes out from STOQ, not Klaviyo: the flow is not verified." },
"connection": { "method": "oauth", "connected": true, "legacy": false, "key_hint": null },
"selected_for": ["email"], "plan": { "required": "paid", "available": true },
"steps": [ { "id": "connect", "state": "done", "required": true },
{ "id": "email.verify_flow", "state": "todo", "required": true, "fallback": "stoq",
"scope": { "channel": "email", "notifications": ["reminder"] },
"action": { "verb": "POST", "path": "/account/integrations/klaviyo_email/flows/verify" } } ],
"settings": { "sync_customers": true, "profile_tag": "restock-rocket",
"flows": { "restock": { "email": "automatic", "sms": null }, "preorder": { "email": "manual", "sms": null },
"wishlist_reminder": { "email": null, "sms": null }, "signup_thank_you": { "email": null, "sms": null } } },
"actions": ["disconnect", "update", "run_step"],
"links": { "dashboard_url": "https://admin.shopify.com/store/acme/apps/restockrocket-1/integrations?platform=klaviyo",
"help_url": "https://help.stoqapp.com/back-in-stock/connect-klaviyo-for-back-in-stock/" } }

Each email and sms block of the back-in-stock and preorder template reads adds integration, the { id, href } of the chosen provider’s integration.

POST /account/integrations/{id}/connect never connects an OAuth integration (Klaviyo, Judge.me, Smile.io, Gorgias) by itself: it returns 201 with connect_url, expires_at and single_use: true. The link opens the integration’s panel in the STOQ dashboard, where the merchant presses Connect and approves in the provider. Shopify asks for a staff login for that store first, so a leaked link can’t connect another store. Show the link to the merchant; don’t open or fetch it.

A link works once, for 15 minutes. A new one replaces the last, and each integration gets 5 an hour (then 429 rate_limited). For Gorgias, send account (the <account>.gorgias.com subdomain) to fill it in. An expired link still opens the panel, and Connect then works as it does in the dashboard. Read the result from connect on GET /account/integrations/{id}: pending, expires_at, last_result (connected, denied, failed, expired) and last_result_at. After a failed connect, reason.code is provider_error.

API-key integrations (Postscript, Omnisend, Mailchimp) take api_key. STOQ checks Postscript keys and Mailchimp keys with the provider before saving; a key the provider refuses is 422 with code: key_rejected and nothing is saved. An integration that is already connected is 409 already_connected unless you send reconnect: true. Switches (Shopify Flow, Meta Pixel, SplitOrder, Shopify customers) are 422 not_applicable: turn them on with PATCH /account/integrations. Paid integrations on the free plan are 403 plan_required.

Through the MCP server (ChatGPT, Claude) API keys are never accepted, so that no key passes through an assistant. The REST API takes them. Connect returns 201 with key_url, the dashboard panel where the merchant pastes the key. An api_key sent anyway, to connect, PATCH /account/integrations or PATCH /account/sending, is refused with code: use_key_link, path and key_url, and nothing is saved.

GET /account/integrations/{id}/events lists what happened, newest first: connect_link_created, connected, connect_denied, connect_failed, key_link_issued, key_set, key_rejected, key_cleared, settings_changed (field names only), disconnected and auto_disconnected (the provider revoked STOQ’s access). Each has actor_surface (dashboard, quinn, mcp, api, system); changes saved in the dashboard are recorded as well. Page with before: next_cursor. Keys are never recorded.

/account/templates/email_style is the look every STOQ email shares, back-in-stock and preorder alike, as the template editor sets it: font_family (one of the editor’s fonts), header_font_size, description_font_size and logo_size (whole pixels above 0). One change restyles every template.

logo_url is the uploaded logo (null when there is none). Upload it with POST /account/templates/email_style/logo, sending the image as a multipart file field, as the template editor does. It must be a PNG, JPEG, GIF or WebP file of 2 MB or less, judged from the file’s bytes rather than the type it is sent as; anything else is a 422 and nothing is stored; DELETE removes it. Back-in-stock and preorder emails share the one logo. The MCP server can’t send files, so for them the logo is read-only.

Terminal window
curl -X POST https://app.stoqapp.com/api/v2/account/templates/email_style/logo \
-H "Authorization: Bearer $TOKEN" -F "file=@logo.png"

GET /account/setup_guide returns the Home setup guide for each product, back_in_stock and preorders:

{
"back_in_stock": {
"chosen": true,
"completed_at": null,
"steps": [
{ "step": "app_embed", "done": true },
{ "step": "design_widget", "done": false },
{ "step": "first_signup", "done": false },
{ "step": "first_alert_sent", "done": false }
],
"steps_done": 1,
"next_step": "design_widget",
"first_signup_id": null
},
"preorders": {
"chosen": false,
"completed_at": null,
"steps": [
{ "step": "app_embed", "done": true },
{ "step": "first_offer", "done": false },
{ "step": "first_product", "done": false },
{ "step": "first_order", "done": false }
],
"steps_done": 1,
"next_step": "first_offer"
}
}
  • chosen is the product picked on the welcome screen. Preorder steps are app_embed, first_offer, first_product, first_order.
  • A guide is complete once every step is done; completed_at is set then, or when it is skipped. A skipped guide can’t be told apart from a finished one, as on Home.
  • Whether the app embed is on, and the link to turn it on, is GET /account/setup.
  • POST /account/setup_guide/skip skips both guides, or one with flow (back_in_stock or preorders). It can’t be undone. Hiding the guide on Home (“Dismiss”) is kept in the browser only, so it isn’t in the API.
{
"market_setup_type": "multiple_markets",
"market_locations_enabled": true,
"market_urls_enabled": false,
"preorder_location_filter": { "enabled": false, "shopify_location_ids": [] },
"links": [
{ "shopify_market_id": "gid://shopify/Market/12345", "shopify_location_id": "gid://shopify/Location/111" }
],
"markets": [
{ "shopify_market_id": "gid://shopify/Market/12345", "name": "United States", "status": "active",
"countries": ["US"], "country_names": ["United States"] }
]
}
  • markets lists active markets; add ?include_drafts=true for draft_markets.
  • links count only while market_setup_type is multiple_markets and market_locations_enabled is true.
  • PATCH with links or preorder_location_filter.shopify_location_ids replaces the stored list. Ids are checked against Shopify; if Shopify cannot be read the call returns 503 and saves nothing.
  • Switching market_setup_type turns the other setup’s switch off unless you send it. Switching to multiple_markets also turns the back-in-stock location filter off, as the dashboard does. The response lists each in also_changed.

reset_locale takes an optional default_locale (first make it the language alerts and the widget use) and an optional locale (the language whose copy is reset). If locale is omitted, it is default_locale when that is sent, otherwise the shop’s default language. It overwrites the merchant’s copy in that language.

Read-only. plan is the plan’s name and friendly_name; limits is what it includes (usage_type per_month pools email and SMS under notification_limit, per_channel uses email_limit and sms_limit). usage is the numbers the dashboard’s usage card shows; usage.alerts_over_limit is the alerts over the plan limit. Unlimited plans have none. ?period=YYYY-MM reads another month, metered against the current plan.

Dev mode, custom CSS and custom JS are on /back_in_stock/settings/widget and /preorders/settings/developer.

Was this page helpful?

Still stuck? Ask Quinn or chat with support