Dispatch & Discovery
v2 is built around two ideas: a single catch-all dispatcher, and a discovery surface that lets any consumer (human or AI) find every action without external documentation.
Catch-all dispatch
Every path under /api/v2/external/* is handled by one dispatcher. It walks ApiV2::Registry — a lookup table populated by classes that include the api_v2 do ... end DSL — and routes the verb+path to the matching action.
All action paths and /help require X-Auth-Token and consume rate-limit points. skill.md and /llms.txt are public.
A request that doesn't match any registered action returns 404:
{ "errors": ["No v2 action registered for POST /preorders/offers/nope"] }/help — the canonical manifest
curl https://app.stoqapp.com/api/v2/external/help \
-H "X-Auth-Token: $STOQ_API_KEY"Returns one entry per registered action, wrapped in a small envelope:
{
"version": "v2",
"base_path": "/api/v2/external",
"actions": [
{
"name": "set_button_text",
"method": "POST",
"path": "/preorders/offers/:id/widget/set_button_text",
"description": "Set the preorder button's call-to-action label.",
"aliases": ["change button text", "rename button", "set button label", "change preorder button label", "update button text"],
"path_params": ["id"],
"request_schema": {
"type": "object",
"properties": { "text": { "not": { "type": "null" } } },
"required": ["text"]
}
}
]
}request_schemais a real JSON Schema, generated from the same contract that validates the request at runtime — includingrequired,enumvalues, and numeric bounds. Dispatcher-injected keys and URL placeholders are stripped; they're not part of the request body.path_paramslists the:placeholdersin the path (present only when the path has them).notescarries free-text caveats — including, for every capabilityPATCH, the list of toggleable field paths.
Scoped help
Two equivalent ways to load a focused subset into an agent's context:
# 1. The prefix filter on /help
curl 'https://app.stoqapp.com/api/v2/external/help?prefix=/preorders/orders' \
-H "X-Auth-Token: $STOQ_API_KEY"
# 2. GET <any scope>/help — works at every URL level
curl https://app.stoqapp.com/api/v2/external/preorders/offers/help \
-H "X-Auth-Token: $STOQ_API_KEY"
curl https://app.stoqapp.com/api/v2/external/preorders/offers/5b2e.../widget/help \
-H "X-Auth-Token: $STOQ_API_KEY"The scope may contain concrete IDs where the registered patterns have :placeholders — /preorders/offers/5b2e.../widget/help returns the widget actions. A scope with no registered actions returns 404 ("No v2 actions registered under ...").
The scoped response carries a scope key alongside actions so you can tell what you asked for.
skill.md — per-domain AI guide
GET https://app.stoqapp.com/api/v2/external/preorders/skill.mdUnauthenticated, plain markdown, generated from the live registry. Designed to be passed verbatim into an AI agent's prompt — explains the preorder domain, the action conventions (named POSTs for side effects, capability PATCH for settings and toggles), shows representative request shapes, and embeds the full action table.
If you're building an integration that lets your users say "increase the deposit by 10%" or "release all orders that paid the balance", point your agent at this URL.
MCP — every action as a tool
POST https://app.stoqapp.com/api/v2/external/mcpThe same registry also backs an MCP server: every registered action is exposed as an MCP tool (name, description, input schema — generated from the manifest entries above). Auth is the same API key, via X-Auth-Token or Authorization: Bearer. New actions become MCP tools automatically.
See MCP for connection details and tool naming.
llms.txt — root index
GET https://app.stoqapp.com/llms.txtA top-level index that points agents at the per-domain skill documents. As STOQ exposes more APIs, they show up here.
Why this matters for AI agents
The combination of /help + skill.md lets an agent operate without baked-in knowledge of STOQ:
- The agent fetches
skill.mdonce to understand the domain. - It calls
/help?prefix=...(orGET <scope>/help) to discover the exact actions and request schemas. - It maps the user's natural-language intent to a single
aliasesentry → canonical action. - It executes the call, reading
notesfor any gotchas (e.g., which field paths a capabilityPATCHtoggles, or whenforce: trueis needed).
No SDK, no schema registry, no rebuild when STOQ adds new actions. Prefer tool-calling over raw HTTP? Use the MCP server — same registry, same actions.
DSL recap (for reference)
Every action class declares itself like this:
class Preorders::Offer::Widget::SetButtonText < ApiV2Interactor
api_v2 do
description "Set the preorder button's call-to-action label."
aliases "change button text", "rename button", "set button label",
"change preorder button label", "update button text"
path "/preorders/offers/:id/widget/set_button_text"
verb :post
end
expects do
required(:shop).filled
required(:id).filled
required(:text).filled
end
def call
# ...
end
endThe class is the manifest: description / aliases / notes / path / verb come from the api_v2 block, the request_schema is derived from the same expects contract that validates requests at runtime, and path_params come from the :placeholders in the path. /help, scoped help, skill.md, and the MCP tool list are all generated by iterating ApiV2::Registry — there's no separate registry file to keep in sync, so none of the discovery surfaces can drift from runtime behavior.
