Stow./Docs

AI Tool Builder Guide

This page is the instruction set for generating a full Custom API spec for the Stow platform. If you were sent here by a prompt, read the full page before generating anything.

AI NOTICE (must follow): If you are an AI assistant reading this page, you MUST read every word on this page and understand it before you output any JSON. Do not skim. Do not partially follow the instructions. A spec that “almost” works creates confusing UI states and failed connection tests.


Output Format (Full Spec JSON)

When ready to output, produce a strict JSON object — no markdown fences, no commentary, nothing before or after the object.

When you output, you MUST produce a single strict JSON object with this top-level shape (no markdown fences, no commentary, nothing before or after the object):

{
  "version": 1,
  "service": {
    "name": "string",
    "docs_url": "https://...",
    "base_url": "https://api.example.com",
    "auth": {
      "type": "none|bearer_token|api_key_header|api_key_query|basic_auth",
      "header_name": "X-API-Key (only for api_key_header)",
      "query_param_name": "api_key (only for api_key_query)",
      "notes": "optional string"
    },
    "setup_instructions": {
      "summary": "short explanation of how to obtain credentials / how auth works",
      "steps": [
        { "title": "optional short title", "text": "required step text", "url": "optional https://..." }
      ]
    }
  },
  "tools": [
    {
      "name": "string",
      "description": "string",
      "group": "optional group name, e.g. Users, Tickets, Comments",
      "method": "GET|POST|PUT|PATCH|DELETE",
      "endpoint": "/path/with/{params}",
      "parameters": [
        { "name": "param", "in": "path|query|header|body", "required": true, "type": "string|number|integer|boolean", "description": "..." }
      ],
      "response_description": "string",
      "status": "active|disabled"
    }
  ]
}

Full spec requirements

  • You MUST populate service.base_url with the API's correct base URL (prefer production over sandbox unless the docs clearly indicate otherwise).
  • You MUST infer service.auth.type from the docs.
  • If service.auth.type is:
    • api_key_header: include service.auth.header_name (use the header name the docs specify; if unknown, use X-API-Key and mention uncertainty in notes)
    • api_key_query: include service.auth.query_param_name (use the parameter name the docs specify; if unknown, use api_key and mention uncertainty in notes)
  • You MUST ensure docs_url and base_url are valid http(s) URLs. Do not wrap URLs in brackets, quotes, or markdown. Examples of invalid values:
    • "[https://api.example.com]", `https://api.example.com`, "<https://api.example.com>", "https://api.example.com", "auth": {...} (extra JSON text)
  • You MUST generate service.setup_instructions as a human-followable checklist explaining where the user gets the credential and any critical permissions/scopes needed (when the docs make this possible).
  • Tools MUST be in the tools array; do not output tools as a top-level JSON array.
  • Output must be valid JSON (double quotes, no trailing commas).

Naming Rules

Every tool name must be namespaced to the specific API being integrated.

  • Use the API's brand or product name as the namespace prefix — not a generic word like custom, api, or integration
  • Derive the namespace from the docs URL domain if the API name is obvious from it
  • Format: <api_namespace>-<action> where <action> is kebab-case or snake_case

| | Example | |---|---| | Good | slack-post-message, github-list-repos, opentdb-get-questions | | Bad | list-repos (no namespace), custom-api-list-repos (generic namespace), get repos (spaces) |


Tool Grouping

Every tool should have a group field that places it in a logical category. Groups appear in the Stow UI as collapsible sections with a single enable/disable toggle for the whole group — exactly like standard service capabilities.

  • Choose a short, human-readable group name based on the resource the tools operate on: Users, Tickets, Projects, Comments, Files, Webhooks, etc.
  • All tools that operate on the same resource should share a group name.
  • Omit group only when a tool genuinely doesn't fit any resource category.

| Tool names | Group | |---|---| | asana-list-tasks, asana-get-task, asana-create-task | Tasks | | asana-list-projects, asana-get-project | Projects | | asana-list-users, asana-get-user | Users |

Tools without a group are shown in an Ungrouped section at the bottom of the Tools tab.


Parameter Location Rules

The in field tells the platform where to route each parameter when making the HTTP request.

| Value | When to use | |---|---| | path | The parameter appears in the URL path as {param} | | query | Appended to the URL as ?key=value | | body | Sent in the JSON request body (POST, PUT, PATCH) | | header | Sent as an HTTP request header |

Critical rule: If the endpoint contains {param}, there must be a matching parameter with "in": "path" and the same name. Forgetting this will cause a broken tool.

Parameter name: allowed characters and wire format

Parameter name is a Stow identifier, not necessarily the same string the vendor sends on the wire. It must match Stow’s rules (for example: no . in the name, plus length limits). Invalid names produce save-time errors such as “invalid characters or is too long.”

If the vendor’s real query key or header name cannot be used as name, document the gap and either:

  • support a wire alias if the product has or will add query_key / header_name separate from name, or
  • omit that filter from the tool and describe in the tool description how to achieve the same result with other tools or manual calls.

Common case: Dotted query parameters (for example assignee.any, projects.any) are often impossible to use as-is for parameters[].name because the HTTP key is not a valid Stow parameter name.

Vendor APIs with odd query keys
Many APIs use param.subparam, bracket notation, or repeated keys. If Stow only maps name → query key 1:1 and does not allow dots in name, those filters cannot be modeled until alias support exists.
Recommended spec pattern: keep the tool valid with only Stow-legal names; in description, list the exact vendor query parameters from the official API reference and state that they are unavailable in this connector until Stow supports them.

Request body shape

in: body sends parameters as a merged JSON object. If the API requires a fixed wrapper (for example Asana’s {"data": { ... }}), flat body parameters merged into a single object may not match what the vendor expects unless the platform explicitly builds that envelope.

Guidance for spec authors: either mark such operations disabled with a one-line reason, or document that the operation requires raw / templated body when the product supports it. Do not imply a flat field list will work when the vendor always nests under data (or another wrapper).


Setup Instructions (Critical)

service.setup_instructions is shown directly in the UI. It must be readable by humans and must not contain broken/encoded content.

What good setup instructions do

  • Tell the user where to get the credential (link to the exact portal page if docs provide it).
  • Tell the user what to copy (API key vs token vs client id/secret).
  • Tell the user where to paste it in Stow (e.g. “Auth Type: API Key (header)”, “Header Name: x-api-key”, “Secret: your API key”).
  • Call out any required non-secret headers (e.g. host/version headers) and provide the exact key/value.
  • Mention required plan/subscription, scopes, or permissions if the docs say so.

What setup instructions must NOT do

  • Do not include URL-encoded blobs, pasted JSON, or other raw machine output (e.g. strings containing %22, %7B, ](, or huge fragments of a URL).
  • Do not describe an auth scheme that conflicts with service.auth (e.g. saying “Bearer token” while auth.type is api_key_header).
  • Do not include the user’s actual secret or token.

Template to follow (recommended)

Write a short summary and 3–6 concrete steps:

  • Summary: 1–2 sentences describing the credential + where it is used.
  • Steps: numbered actions a human can complete in a minute.

Example:

{
  "setup_instructions": {
    "summary": "This API uses an API key passed in the x-rapidapi-key header. Some requests also require x-rapidapi-host.",
    "steps": [
      { "title": "Create an API key", "text": "Open the provider dashboard and create an API key for this API.", "url": "https://example.com/dashboard/api-keys" },
      { "title": "Configure Stow", "text": "In Stow, set Auth Type to “API Key (header)” and set Header Name to “x-rapidapi-key”." },
      { "title": "Save the secret", "text": "Paste your API key into the Secret field and click “Save & Test”." },
      { "title": "Add required headers", "text": "If the docs require x-rapidapi-host, add it under Additional headers: x-rapidapi-host = example-api.p.rapidapi.com." }
    ]
  }
}

Cross-check rules (must pass)

Before outputting JSON, verify:

  • Setup instructions match the service.auth configuration exactly.
  • Any mandatory headers listed in docs are explicitly called out in steps (as non-secret headers).
  • There is no encoded/garbage text; everything reads like normal English sentences.

Authorization & Required Headers (Critical)

Your goal is for the generated connector spec to produce a request that the API will accept without any manual fixes. Do not guess. Read the API docs and capture the required auth and headers precisely.

Step 1: Determine the auth mechanism

Infer service.auth.type from the docs:

  • none: docs explicitly say "no auth", "public", or examples show no API key/token at all.
  • bearer_token: examples show Authorization: Bearer <token> (or "access token"). Put only the raw token in the secret vault; the platform adds Bearer automatically.
  • api_key_header: docs say "API key in header" (examples like x-api-key: <key>, x-rapidapi-key: <key>, X-API-Key: <key>). Set:
    • service.auth.type = "api_key_header"
    • service.auth.header_name = "<exact header name from docs>"
  • api_key_query: docs show the key in the URL query string (e.g. ?api_key=..., ?key=..., ?token=...). Set:
    • service.auth.type = "api_key_query"
    • service.auth.query_param_name = "<exact param name from docs>"
  • basic_auth: docs show HTTP Basic auth or examples like Authorization: Basic ... or a username/password pair. Store as a JSON secret like {"username":"...","password":"..."}.

Step 2: Capture mandatory non-secret headers

Many APIs require additional headers beyond the credential itself (for example: host headers, version headers, tenant identifiers, client identifiers, or vendor-specific routing headers). The platform supports this via static headers stored with the connection.

When docs say "requests are valid only if the following headers are present", you MUST:

  • Keep the credential as the vault secret (API key / token).
  • Add all non-secret mandatory headers into service.auth.static_headers (see below).

Important: Do not put secrets in static headers. Only put fixed values that are safe to store as plain metadata (hostnames, version strings, etc.).

RapidAPI / API Hub pattern (common)

If the docs reference RapidAPI (or an "API Hub") and show headers like:

  • x-rapidapi-key: <your key>
  • x-rapidapi-host: <api-hostname>

Then:

  • Use api_key_header with header_name = "x-rapidapi-key"
  • Put x-rapidapi-host in static_headers with the exact hostname from docs
  • Do not use bearer tokens unless docs explicitly say to.

Full auth object (with static headers)

If you need extra headers, include them like this (still strict JSON):

{
  "auth": {
    "type": "api_key_header",
    "header_name": "x-rapidapi-key",
    "static_headers": [
      { "name": "x-rapidapi-host", "value": "example-api.p.rapidapi.com" },
      { "name": "x-api-version", "value": "2026-01-01" }
    ],
    "notes": "If the docs require additional headers, include them in static_headers."
  }
}

Checklist before outputting JSON

  • Does the docs show exactly where the credential goes (header/query/bearer/basic)?
  • Do examples include additional mandatory headers? If yes, include them as static_headers.
  • Is the base_url actually the API host (not a docs site), and is it a valid https://... URL?
  • Are you using the correct header name casing/spelling from docs (x-rapidapi-key vs x-rapidapi-Key)?

What Makes a Valid Tool

The platform validates tool definitions on save. A tool will be flagged as broken if:

  • name is missing
  • method is missing
  • endpoint is missing
  • The endpoint contains {param} but no parameter with "in": "path" and that name exists
  • Any parameter is missing its name field

Warnings (non-blocking) are shown for:

  • No description on the tool
  • A parameter is defined as a path variable but not marked "in": "path"

When not to model a tool

  • Endpoints that require multipart/form-data (file uploads) or non-JSON bodies should not be represented as ordinary JSON body tools unless the platform documents first-class support for that shape. Prefer status: disabled with a short explanation, or a separate integration path.

MCP / agent integration

Some Stow tools require agent identity (for example agent_type, agent_role) on each call even when the JSON schema shows an empty arguments object. AI agents should be instructed to register identity first and to include those fields when the server returns agent_identity_required. When your product publishes MCP-focused docs, prefer linking agents to that guidance.

Validation checklist

Before outputting a spec, also verify:

  • Every parameters[].name uses only Stow-allowed characters; do not rely on vendor dotted names unless aliases exist.
  • Query/header wire names that differ from name are documented or modeled via supported fields.
  • POST/PUT/PATCH tools match the vendor’s actual JSON shape (including wrappers), not an assumed flat merge.

Full Example

A well-formed full spec for the Open Trivia Database API:

{
  "version": 1,
  "service": {
    "name": "Open Trivia DB",
    "docs_url": "https://opentdb.com/api_config.php",
    "base_url": "https://opentdb.com",
    "auth": { "type": "none", "header_name": null, "query_param_name": null },
    "setup_instructions": {
      "summary": "This API is public and does not require authentication.",
      "steps": [
        { "text": "No API key is required. You can call the endpoints directly." }
      ]
    }
  },
  "tools": [
    {
      "name": "opentdb-get-questions",
      "description": "Fetch a set of trivia questions from the Open Trivia Database.",
      "group": "Questions",
      "method": "GET",
      "endpoint": "/api.php",
      "parameters": [
        { "name": "amount", "in": "query", "required": true, "type": "integer", "description": "Number of questions to return (max 50)." },
        { "name": "category", "in": "query", "required": false, "type": "integer", "description": "Category ID to filter questions." },
        { "name": "difficulty", "in": "query", "required": false, "type": "string", "description": "Difficulty level: easy, medium, or hard." },
        { "name": "type", "in": "query", "required": false, "type": "string", "description": "Question type: multiple or boolean." }
      ],
      "response_description": "Returns an array of trivia question objects with correct and incorrect answers.",
      "status": "active"
    },
    {
      "name": "opentdb-get-categories",
      "description": "Retrieve the list of all available trivia categories and their IDs.",
      "group": "Questions",
      "method": "GET",
      "endpoint": "/api_category.php",
      "parameters": [],
      "response_description": "Returns an array of category objects, each with an id and name.",
      "status": "active"
    }
  ]
}