Create Route

Beta
Add a route: a method + path answered by a resolver. `store_query`, `store_document` and `store_aggregate` serve a store; `store_write` lands the POST body as a document (`write_mode` create / replace / merge; POST only, never on a public API, counted against `daily_write_cap`, deduplicated on `Idempotency-Key`); `run_latest` serves the newest structured output of a schedule trigger's runs; `run` starts a run through a webhook trigger per request (POST only, never on a public API) and waits up to `wait_seconds` before answering 202 with a handle to poll at `/_runs/{run_id}`; `file` serves one published file, or a whole published tree when the path ends in `*` (`/app/*` with `file_root` and `file_index`); `tool` calls one read operation of an `openapi` tool definition, or one tool of an `mcp` tool definition's server, in the API's project whose `approval` is null or `auto` (POST only, never on a public API, counted against `daily_read_cap`): the consumer's JSON body is the arguments and the connector's answer, after the tool's response mapping, is the response. An `mcp` route pins the tool's input schema as `resolver.input_schema` when it is written. Where-clause values and the document id may be `{{query.x}}`, `{{path.x}}` or `{{body.x}}` templates bound from the consumer's request, or `{{user.x}}` claims of the verified caller on a `user_token`, `workspace` or `owner` API; a clause whose template is absent is skipped. On those three modes a written document is stamped `user_identity` as the caller and only they can replace or merge it. Two bindings are refused at write time: `{{user.*}}` on an API that names no caller, and a clause on `user_identity` bound from a request template, which would let any caller read any user's rows. On an API with `mcp_enabled`, every enabled route except a `file` route is also an MCP tool. The tool's name comes from the route's `name`: every run of characters outside letters, digits, `_` and `-` becomes `_`, leading and trailing `_` are dropped, and the result is cut to 64 characters. A route with no name is listed under a name built from its method and path (`post_issues_open`), and a name two routes would share takes `_2`, `_3` in route order, so read the names from the face's `tools/list` rather than deriving them. The route's `description` is what the client's model reads to choose the tool, falling back to the operation's summary on a `tool` route. Name and describe a route for that reader. Dark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).

Authentication

AuthorizationBearer

Enter your API key with the Bearer prefix, e.g. ‘Bearer sk_…’.

Path parameters

api_idstringRequired

Hosted API id (prefixed external id, api_...).

Headers

Speechify-VersionstringOptional
Idempotency-KeystringOptional<=255 characters
A client-generated key (an opaque string, max 255 chars) that makes a side-effect POST safe to retry: the server runs the operation exactly once and replays the first response (its status and body) for 24 hours. Reusing a key with a different request body, or while the first request is still in flight, returns `409 idempotency_conflict`. A replayed response carries the `Idempotent-Replayed: true` header.

Request

This endpoint expects an object.
methodenumRequired
pathstringRequired

Lowercase segments of letters, digits, . _ - or a {param}; /openapi.json, /_runs and /mcp are reserved.

resolverobjectRequired

What answers a route. type selects the fields that apply: store_query (store_id, collection, where, order_by, limit), store_document (store_id, collection, document_id), store_aggregate (store_id, collection, where, group_by, metrics: a summary in one request, from the same implementation as the collection’s aggregate operation), store_write (store_id, collection, write_mode, document_id: the request body lands as a document, the fast path past a run for the one thing a read resolver cannot do; POST only, never on a public API, and on an API that names its caller the document is that person’s), run_latest (trigger_id of a schedule trigger), run (trigger_id of a webhook trigger, wait_seconds), file (file_path of one published file; or, on a route whose path ends in *, file_root and file_index for a whole published tree), tool (tool_id of an openapi or mcp tool definition and the operation on it, an openapi operation’s id or one of the MCP server’s tools by name: the POST body is the arguments, held to their schema, and the connector’s answer after the tool’s response mapping is the response. An openapi vendor’s JSON comes back as it came, or {"text": ...} when it answered text; an MCP tool answers its structured content, its text when that text is JSON, {"text": ...} for plain text, or {"content": [...]} with every block as the server sent it when one is not text. For an mcp tool the route write lists the server’s tools and pins the chosen tool’s input schema on the route as input_schema, so the MCP face and every call use the pin and an upstream change reaches no consumer until the route is written again. An operation whose effective class is read, on a tool whose approval is null or auto, may be served on any API but a public one. An operation that is not a read is served only on a route with allow_write: true, on an API whose auth_mode names a person (owner, workspace or user_token), and still only with an approval of null or auto: a write through a route acts for the member or end user calling, who is sent to the connector as Speechify-User-Identity, a service account is refused, every write counts against daily_write_cap (429 route_write_limit_reached) and claims the caller’s Idempotency-Key so a retry replays the first answer. A route write that breaks any of this is refused with 400 validation_failed on resolver.tool_id, resolver.operation or resolver.allow_write, as is an MCP server that cannot be listed. Because a definition can change after its route is written, every call re-checks it: an operation no longer classified read on a route not switched to writes, or a tool whose approval is no longer null or auto, answers 403 route_tool_not_readable; a tool deleted, moved to another project, or without the operation (an MCP server that no longer lists the tool) answers 409 route_tool_unavailable, which no retry clears until the route or the tool is fixed, and names which of the three happened in error.details.reason (tool_deleted, tool_moved, operation_removed). Both refusals carry what the API’s owner changes to fix the route in error.details.fix. Arguments that do not fit the schema answer 400 validation_failed; the definition’s max_requests_per_minute and the vendor’s own throttle both answer 429 route_upstream_rate_limited with Retry-After; a vendor error answers 502 route_upstream_error with the vendor’s status in error.details.upstream_status, an MCP tool that reports an error answers it with the tool’s own message in error.details.tool_error, and an unreachable vendor or a credential that no longer resolves answers 502 route_upstream_error without either).

namestringOptional

The route’s name, 1-128 letters, digits, spaces, ., _ or -. On an API with mcp_enabled the MCP tool’s name comes from it: each run of other characters (a space included) becomes _, leading and trailing _ are dropped, and the result is cut to 64 characters. A route with no name is listed under a name built from its method and path, and a name two routes would share takes _2, _3 in route order; the face’s tools/list is the authority. Pick a verb-first name a model can choose by.

descriptionstringOptional<=1000 characters

What the route does. On an API with mcp_enabled it is the MCP tool description a client’s model reads to choose the tool, so say what it returns and when to call it; a tool route with none falls back to the operation’s summary.

response_schemamap from strings to anyOptional
cache_ttl_secondsintegerOptional0-3600
enabledbooleanOptional
Enabled when omitted.

Response headers

Speechify-Request-IdstringOptional
Unique identifier for this request, present on every response (2xx and non-2xx alike). If the caller sends a `Speechify-Request-Id` request header the server echoes it back (sanitized and length-capped) so one logical request can be traced end-to-end; otherwise the server generates a fresh value. Log it on every response and quote it in support requests - it is the stable handle that ties your observation to Speechify's server-side logs, and it matches the `request_id` field in the error envelope. The legacy alias `X-Request-ID` carries the same value and is still accepted on requests, until 2027-07-24. Prefer the un-prefixed name (RFC 6648).

Response

The created route.
idstringformat: "^route_[0-9a-hjkmnp-tv-z]{26}$"
api_idstringformat: "^api_[0-9a-hjkmnp-tv-z]{26}$"
methodenum
pathstring

Lowercase segments, one-segment {params}, and optionally a trailing * that serves a published tree (file routes only).

namestring

The route’s name, 1-128 letters, digits, spaces, ., _ or -. On an API with mcp_enabled the MCP tool’s name comes from it: each run of other characters (a space included) becomes _, leading and trailing _ are dropped, and the result is cut to 64 characters. A route with no name is listed under a name built from its method and path, and a name two routes would share takes _2, _3 in route order; the face’s tools/list is the authority. Pick a verb-first name a model can choose by.

descriptionstring

What the route does. On an API with mcp_enabled it is the MCP tool description a client’s model reads to choose the tool, so say what it returns and when to call it; a tool route with none falls back to the operation’s summary.

resolverobject

What answers a route. type selects the fields that apply: store_query (store_id, collection, where, order_by, limit), store_document (store_id, collection, document_id), store_aggregate (store_id, collection, where, group_by, metrics: a summary in one request, from the same implementation as the collection’s aggregate operation), store_write (store_id, collection, write_mode, document_id: the request body lands as a document, the fast path past a run for the one thing a read resolver cannot do; POST only, never on a public API, and on an API that names its caller the document is that person’s), run_latest (trigger_id of a schedule trigger), run (trigger_id of a webhook trigger, wait_seconds), file (file_path of one published file; or, on a route whose path ends in *, file_root and file_index for a whole published tree), tool (tool_id of an openapi or mcp tool definition and the operation on it, an openapi operation’s id or one of the MCP server’s tools by name: the POST body is the arguments, held to their schema, and the connector’s answer after the tool’s response mapping is the response. An openapi vendor’s JSON comes back as it came, or {"text": ...} when it answered text; an MCP tool answers its structured content, its text when that text is JSON, {"text": ...} for plain text, or {"content": [...]} with every block as the server sent it when one is not text. For an mcp tool the route write lists the server’s tools and pins the chosen tool’s input schema on the route as input_schema, so the MCP face and every call use the pin and an upstream change reaches no consumer until the route is written again. An operation whose effective class is read, on a tool whose approval is null or auto, may be served on any API but a public one. An operation that is not a read is served only on a route with allow_write: true, on an API whose auth_mode names a person (owner, workspace or user_token), and still only with an approval of null or auto: a write through a route acts for the member or end user calling, who is sent to the connector as Speechify-User-Identity, a service account is refused, every write counts against daily_write_cap (429 route_write_limit_reached) and claims the caller’s Idempotency-Key so a retry replays the first answer. A route write that breaks any of this is refused with 400 validation_failed on resolver.tool_id, resolver.operation or resolver.allow_write, as is an MCP server that cannot be listed. Because a definition can change after its route is written, every call re-checks it: an operation no longer classified read on a route not switched to writes, or a tool whose approval is no longer null or auto, answers 403 route_tool_not_readable; a tool deleted, moved to another project, or without the operation (an MCP server that no longer lists the tool) answers 409 route_tool_unavailable, which no retry clears until the route or the tool is fixed, and names which of the three happened in error.details.reason (tool_deleted, tool_moved, operation_removed). Both refusals carry what the API’s owner changes to fix the route in error.details.fix. Arguments that do not fit the schema answer 400 validation_failed; the definition’s max_requests_per_minute and the vendor’s own throttle both answer 429 route_upstream_rate_limited with Retry-After; a vendor error answers 502 route_upstream_error with the vendor’s status in error.details.upstream_status, an MCP tool that reports an error answers it with the tool’s own message in error.details.tool_error, and an unreachable vendor or a credential that no longer resolves answers 502 route_upstream_error without either).

cache_ttl_secondsinteger0-3600

Cache-Control max-age on GET responses, and the shared response cache’s lifetime for store and run_latest routes; 0 disables caching, except on a public API, where a GET route with 0 is served with the platform default of 60 seconds so an anonymous crawler never reads storage per request. File routes are never in the response cache; they carry the header for the edge.

enabledboolean
created_atdatetime
updated_atdatetime
response_schemamap from strings to anyOptional
Optional JSON Schema of the response body, rendered into the OpenAPI document.

Errors

400
Bad Request Error
401
Unauthorized Error
402
Payment Required Error
404
Not Found Error
409
Conflict Error
429
Too Many Requests Error
500
Internal Server Error