> ## Documentation Index
> Fetch the complete documentation index at: https://docs.connie.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Tool API

> Run Connie tools from your own code with an API key: find a tool, read its inputs and price, run it, and get the result in the response, by polling or by webhook.

The Tool API runs the tools in your Connie workspace (scrapers, enrichment, search, connected apps, media generation and more) from your own server, script or app. Each run uses the workspace's credits, exactly as in the app, and returns a billing receipt.

* **Base URL:** `https://conigma-tool-server-production.up.railway.app`
* **Sign-in:** a workspace [API key](/developers/api-keys), sent as `Authorization: Bearer sk_…`. Your **Personal key** works as is.
* **Reference:** every endpoint and field is in the [Tool API reference](/api-reference/tool-api/search-tools).

<Note>
  Use this API from a server. Never put an API key in a browser app or a mobile app: anyone who reads the key can spend the workspace's credits.
</Note>

## How it works

<Steps>
  <Step title="Find the tool">
    `GET /api/tools?query=instagram profile` returns matching tools, best first. Searching is free.
  </Step>

  <Step title="Read its inputs and price">
    `GET /api/tool/{toolId}` returns the `inputSchema` (JSON Schema), whether your key can run it (`availability`), whether it needs a connected account (`connection`) and what it costs (`pricing`). This is free and runs nothing.
  </Step>

  <Step title="Run it">
    `POST /api/tool/{toolId}` with `{"input": {…}}` and an `Idempotency-Key` header. Successful calls that end within 25 seconds answer `200` with the result. Failures return the error codes below. Longer calls answer `202` and keep running.
  </Step>

  <Step title="Get the outcome of a long call">
    Poll `GET /api/tool-invocations/{invocationId}` every few seconds (it answers `202` until the call ends), or pass a `webhook` URL when you run the tool and Connie POSTs the outcome to it. See [Webhooks](/developers/tool-api-webhooks).
  </Step>
</Steps>

## Quickstart

<Tabs>
  <Tab title="curl">
    ```bash theme={null}
    # 1. Find a tool
    curl -s "https://conigma-tool-server-production.up.railway.app/api/tools?query=instagram%20profile&limit=3" \
      -H "Authorization: Bearer $CONNIE_API_KEY"

    # 2. Run it. Keep the key: send it again on a retry.
    curl -s -X POST "https://conigma-tool-server-production.up.railway.app/api/tool/instagram-profiles-collect-by-url" \
      -H "Authorization: Bearer $CONNIE_API_KEY" \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: 3f6c2a40-9b1e-4f55-8a5e-1c2d3e4f5a6b" \
      -d '{"input": {"url": "https://www.instagram.com/nasa"}}'

    # 3. If the answer was 202, poll the operation until it isn't
    curl -s "https://conigma-tool-server-production.up.railway.app/api/tool-invocations/INVOCATION_ID" \
      -H "Authorization: Bearer $CONNIE_API_KEY"
    ```
  </Tab>

  <Tab title="Node.js">
    ```javascript theme={null}
    import { randomUUID } from 'node:crypto';

    const BASE = 'https://conigma-tool-server-production.up.railway.app';
    const headers = { Authorization: `Bearer ${process.env.CONNIE_API_KEY}`, 'Content-Type': 'application/json' };

    /** Run a tool and wait for its outcome. Reuse `key` if you retry after a lost response. */
    async function runTool(toolId, input, key = randomUUID()) {
      let response = await fetch(`${BASE}/api/tool/${encodeURIComponent(toolId)}`, {
        method: 'POST',
        headers: { ...headers, 'Idempotency-Key': key },
        body: JSON.stringify({ input }),
      });
      let operation = await response.json();
      while (response.status === 202) {
        await new Promise((resolve) => setTimeout(resolve, 2000));
        response = await fetch(`${BASE}/api/tool-invocations/${operation.invocationId}`, { headers });
        operation = await response.json();
      }
      if (operation.ok && operation.resultRef) {
        // Results over 1 MiB are fetched separately.
        response = await fetch(`${BASE}/api/tool-invocations/${operation.invocationId}?result=full`, { headers });
        operation = await response.json();
      }
      return operation; // { ok, status, result | error, billing: { chargedCredits, items } }
    }

    const outcome = await runTool('instagram-profiles-collect-by-url', { url: 'https://www.instagram.com/nasa' });
    console.log(outcome.ok ? outcome.result : outcome.error.message, `(${outcome.billing?.chargedCredits ?? 0} credits)`);
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    import os, time, uuid, requests

    BASE = "https://conigma-tool-server-production.up.railway.app"
    HEADERS = {"Authorization": f"Bearer {os.environ['CONNIE_API_KEY']}"}

    def run_tool(tool_id, tool_input, key=None):
        """Run a tool and wait for its outcome. Reuse `key` if you retry after a lost response."""
        key = key or str(uuid.uuid4())
        r = requests.post(f"{BASE}/api/tool/{requests.utils.quote(tool_id, safe='')}",
                          headers={**HEADERS, "Idempotency-Key": key}, json={"input": tool_input}, timeout=60)
        operation = r.json()
        while r.status_code == 202:
            time.sleep(2)
            r = requests.get(f"{BASE}/api/tool-invocations/{operation['invocationId']}", headers=HEADERS, timeout=30)
            operation = r.json()
        if operation.get("ok") and operation.get("resultRef"):
            r = requests.get(f"{BASE}/api/tool-invocations/{operation['invocationId']}?result=full", headers=HEADERS, timeout=60)
            operation = r.json()
        return operation

    outcome = run_tool("instagram-profiles-collect-by-url", {"url": "https://www.instagram.com/nasa"})
    print(outcome["result"] if outcome["ok"] else outcome["error"]["message"])
    ```
  </Tab>
</Tabs>

## Retry safely with idempotency keys

Every run needs an `Idempotency-Key` header: 8 to 200 characters (letters, digits and `. _ : ~ + / = -`), one per logical operation. A UUID you generate and store with your job works well.

* **Same key, same request:** Connie returns the original operation, marked `"replayed": true`, and runs and charges nothing again (`"chargedThisRequest": 0`). Use this when a response was lost or timed out. If nothing ran and `error.retryable` is `true`, the same key lets Connie try the operation again.
* **Same key, different input or webhook:** `409 idempotency_conflict`. Nothing runs.
* **New key:** a new run, charged again.
* Keys and outcomes are kept for 24 hours. After that, the same key answers `409 replay_expired` and never runs again.

## Outcomes

Running or polling a tool returns an operation: the outcome, or an error with the same shape. Search, tool details and webhook-secret endpoints return their own response shapes. On search, authentication errors can instead use `{"error":"message","code":"api_key_required"}`; check the HTTP status before reading the body.

| `status` | HTTP | Meaning | What to do |
| - | - | - | - |
| `completed`, `ok: true` | `200` | The tool ran and succeeded. `result` holds its output (or `resultRef` when it's over 1 MiB). | Use the result. |
| `completed`, `ok: false` | `400` | The tool ran and failed: `tool_failed`, or `source_input_unreachable` (the link was wrong, private or removed). | Read `error.message`. Change the input before trying again. |
| `in_progress` | `202` | Still running. | Poll every few seconds, or wait for the webhook. |
| `not_executed` | `4xx`/`5xx` | Nothing ran and nothing was charged: invalid input, too few credits, a limit, or a temporary problem. | If `error.retryable` is `true`, send the same request with the same key again. |
| `unknown_outcome` | `409` | The tool may have run, but Connie couldn't confirm the outcome. | Don't run it again automatically. Media generations carry a `recovery` you can run with `get-generation`. |

`billing.chargedCredits` is what the call cost; `billing.items` has a line per tool it ran. The price from `GET /api/tool/{toolId}` is an estimate: the receipt is the charge.

<Note>
  Connie keeps running a call through its own server restarts. A call already running when Connie deploys finishes on the old server. Calls Connie knows only read from the web or a data provider (scrapes, dataset collections, searches, lookups) and are interrupted by a crash run once more automatically, and are charged once. A call that writes or sends, or one Connie can't confirm only reads, is never run twice: if its outcome is lost it reads `unknown_outcome`.
</Note>

## Errors you can retry

For a `not_executed` response with `error.retryable: true`, wait for the `Retry-After` header, then send the same request with the same `Idempotency-Key`. Examples include:

| HTTP | `error.code` | Message starts with |
| - | - | - |
| `429` | `rate_limited` | "Too many requests to Connie in a short time…" or "This workspace already has 20 operations running…" |
| `503` | `server_restarting` | "This Connie server is restarting. Nothing was run…" |
| `503` | `admission_unavailable`, `service_unavailable` | "The invocation store is unavailable…", "Tool execution is temporarily unavailable…" |
| `503` | `not_started` | "Connie could not start this operation in time. Nothing was run…" |
| `503` | `webhooks_unavailable` | "Webhooks are temporarily unavailable. Nothing was run…" or "Connie couldn't look up the webhook URL's host just now. Nothing was run…" |
| `503` | `schema_unavailable` | "The current workspace schema is unavailable…" |

Do not retry based only on HTTP `503` or the presence of `Retry-After`. `feature_unavailable` means new tool operations are disabled for the workspace; `invalid_schema` means the tool's editor must fix its schema. Both have `error.retryable: false`. Contact support for `feature_unavailable`, or the tool's editor for `invalid_schema`, before trying again.

The full list of codes and limits is in [Errors and limits](/developers/errors-and-limits#tool-api).

## Limits

| What | Without a paid plan | On a paid plan (including a trial) |
| - | - | - |
| Operations running at once, per workspace | 20 | 500 |
| Operations started per minute, per workspace | 120 | 3,000 |
| Requests per API key (polling included) | 1,200 a minute, 20,000 an hour | 6,000 a minute, 100,000 an hour |
| Request body | 256 KiB | 256 KiB |
| Result inline in the answer | 1 MiB (larger results: `?result=full`) | 1 MiB |

Over a limit you get `429` with `Retry-After`, and nothing runs. See [Plans](https://connie.ai/pricing).

## Good to know

* **Permissions:** a key acts as one workspace member and can run what that member can, with their connected accounts. Tools that need a connected account say so in `connection`; connect the account in Connie first. See [API keys](/developers/api-keys).
* **Tool ids** are case-sensitive. URL-encode them once: `/` becomes `%2F`.
* **Credits:** API runs show in the credit usage log as coming from an API key. See [Credits](/billing/credits).
* **Browsers:** the API answers cross-origin requests, but don't call it from a browser with a real key. Run it from your server.

## Related

* [Webhooks for tool calls](/developers/tool-api-webhooks)
* [Tool API reference](/api-reference/tool-api/search-tools)
* [API keys](/developers/api-keys)
* [Errors and limits](/developers/errors-and-limits)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.