# Automatio API

Three products behind one key: (1) TOOLS — the same web search, data extraction, social, crypto and real-estate tools the Automatio agent uses; (2) AI MODELS — an OpenAI-compatible endpoint billed to your credits, no provider keys needed; (3) AGENT RUNS — hand over a goal and Automatio does the whole job.

## Authentication

Every request needs an API key (create one at https://automatio.ai/integrations?tab=api-keys#automatio-api — Integrations → API Keys → Automatio API):

```
Authorization: Bearer aut_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

Keys can be scoped at creation: a tool allowlist, AI-model access (optionally a specific model set), agent runs (with a per-run credit cap), a lifetime credit budget, and a per-minute rate limit.

## REST endpoint

`POST https://automatio.ai/api/v1/query`

```bash
curl -X POST https://automatio.ai/api/v1/query \
  -H "Authorization: Bearer aut_yourkey" \
  -H "Content-Type: application/json" \
  -d '{"tool":"web_search","params":{"query":"hello world"}}'
```

## What a call costs

Every response that RAN a tool — success or tool-level failure — carries automatio.credits_charged, the credits THAT call billed; so does POST /api/v1/chat/completions and the MCP tools/call result. Summing it is exact and needs no rate card. Add a charge whenever the response HAS an automatio block, never by checking the HTTP status: a REJECTED request (401, 429, 400, or 402 when the key's budget is gone) never ran the tool, billed nothing and carries no automatio block at all, while a tool-level FAILURE (502 upstream, or 402 when credits ran out mid-call) DID run, DID bill, and carries the amount on purpose — `if (res.ok)` drops exactly those. Adding a rejected response blindly turns a running total into NaN. Metered tools (apify_actor_details, apify_run_actor, apify_search_actors, image_analyze, image_generate and video_generate) have no fixed price, so this field is the only real number for them. One exception: agent runs (POST /api/v1/agent) report spend as creditsSpent on the job, NOT as automatio.credits_charged — and it is the CONVERSATION's cumulative total, not that poll's cost. Read it, never add it: summing it across polls multiplies a run's spend by the number of times you polled.

`2778` credits = $1 at the reference tier (also served as `billing.creditsPerUsd` on `GET https://automatio.ai/api/v1/catalog`). Plans buy credits at other rates, so a converted total is indicative and is never an account's actual bill — report credits.

```json
{ "rows": [ ], "totalRows": 12, "automatio": { "credits_charged": 15 } }
```

## MCP endpoint (for agents)

`https://automatio.ai/api/v1/mcp` — a stateless Streamable-HTTP Model Context Protocol server. Point any MCP client (Claude, Cursor, the AI SDK, Cloudflare agents) at it with the same bearer key; `tools/list` returns the tools your key is granted, minus any that cannot finish inside an MCP call (`video_generate` renders for minutes and is REST-only — call it at `POST https://automatio.ai/api/v1/query`).

## Managed LLM (OpenAI-compatible)

`POST https://automatio.ai/api/v1/chat/completions` — call an LLM through Automatio; the cost is billed to your credits (no provider keys to manage). Your key needs the `llm` scope. Wire-compatible with the OpenAI Chat Completions API — point the stock OpenAI SDK at it:

```js
import OpenAI from 'openai';
const client = new OpenAI({ baseURL: 'https://automatio.ai/api/v1', apiKey: 'aut_yourkey' });
const res = await client.chat.completions.create({
  model: 'google/gemini-3-flash',
  messages: [{ role: 'user', content: 'Hello' }],
});
```

84 chat models available. Pricing is in credits per 1M tokens (gateway list price + markup); actual charges use real per-call token usage.

| model | context | vision | credits/1M in | credits/1M out |
|---|---|---|---|---|
| `openai/gpt-5-nano` | 400,000 | yes | 278 | 2,222 |
| `openai/gpt-5-mini` | 400,000 | yes | 1,389 | 11,112 |
| `openai/gpt-5.6-luna` | 1,050,000 | yes | 1,111 | 6,667 |
| `openai/gpt-4.1-mini` | 1,047,576 | yes | 2,222 | 8,890 |
| `openai/gpt-4.1-nano` | 1,047,576 | yes | 556 | 2,222 |
| `google/gemini-2.5-flash` | 1,000,000 | yes | 1,667 | 13,890 |
| `google/gemini-3-flash` | 1,000,000 | yes | 2,778 | 16,668 |
| `google/gemini-3.5-flash` | 1,000,000 | yes | 8,334 | 50,004 |
| `google/gemini-3.6-flash` | 1,000,000 | yes | 4,167 | 20,835 |
| `google/gemini-3.7-flash` | 1,000,000 | yes | 4,167 | 20,835 |
| `minimax/minimax-m3` | 512,000 | yes | 1,667 | 6,667 |
| `google/gemini-3.8-flash` | 1,000,000 | yes | 4,167 | 20,835 |
| `google/gemini-3.5-flash-lite` | 1,000,000 | yes | 1,667 | 13,890 |
| `alibaba/qwen3.7-flash` | 991,000 | yes | 167 | 722 |
| `alibaba/qwen3.8-flash` | 991,000 | yes | 833 | 2,611 |
| `alibaba/qwen3.5-flash` | 1,000,000 | yes | 556 | 2,222 |
| `alibaba/qwen3.5-plus` | 1,000,000 | yes | 2,222 | 13,334 |
| `minimax/minimax-m2` | 205,000 | — | 1,667 | 6,667 |
| `openai/gpt-5` | 400,000 | yes | 6,945 | 55,560 |
| `openai/gpt-5.1-thinking` | 400,000 | yes | 6,945 | 55,560 |
| `openai/gpt-5.6-sol` | 1,050,000 | — | 22,224 | 111,120 |
| `openai/gpt-5.6-terra` | 1,050,000 | — | 11,112 | 66,672 |
| `anthropic/claude-sonnet-4.5` | 1,000,000 | yes | 16,668 | 83,340 |
| `google/gemini-2.5-pro` | 1,048,576 | yes | 6,945 | 55,560 |
| `anthropic/claude-opus-4.5` | 200,000 | yes | 27,780 | 138,900 |
| `openai/gpt-5.2` | 400,000 | yes | 9,723 | 77,784 |
| `openai/gpt-5.2-pro` | 400,000 | yes | 116,676 | 933,408 |
| `openai/gpt-5.3-codex` | 400,000 | yes | 9,723 | 77,784 |
| `openai/gpt-5-pro` | 400,000 | yes | 83,340 | 666,720 |
| `openai/gpt-5.5` | 1,000,000 | yes | 27,780 | 166,680 |
| `openai/gpt-5.5-pro` | 1,000,000 | yes | 166,680 | 1,000,080 |
| `anthropic/claude-sonnet-4` | 1,000,000 | yes | 16,668 | 83,340 |
| `anthropic/claude-sonnet-4.6` | 1,000,000 | yes | 16,668 | 83,340 |
| `anthropic/claude-sonnet-5` | 1,000,000 | yes | 11,112 | 55,560 |
| `anthropic/claude-opus-4.6` | 1,000,000 | yes | 27,780 | 138,900 |
| `openai/gpt-6-astra` | 1,050,000 | yes | 55,560 | 277,800 |
| `spacexai/grok-4.6` | 500,000 | yes | 11,112 | 33,336 |
| `anthropic/claude-fable-5.1` | 1,000,000 | yes | 55,560 | 277,800 |
| `google/gemini-3.1-pro-preview` | 1,000,000 | yes | 11,112 | 66,672 |
| `zai/glm-5` | 202,800 | — | 5,556 | 17,779 |
| `zai/glm-5.1` | 202,800 | — | 7,778 | 24,446 |
| `zai/glm-5.2` | 1,000,000 | — | 4,445 | 14,168 |
| `zai/glm-5.3` | 1,000,000 | — | 7,778 | 24,446 |
| `deepseek/deepseek-v4.1-flash` | 1,048,576 | yes | 1,667 | 6,667 |
| `inception/mercury-2.5` | 260,000 | — | 222 | 833 |
| `zai/glm-5.3-flash` | 1,000,000 | yes | 833 | 2,778 |
| `alibaba/qwen3.7-max` | 991,000 | — | 13,890 | 41,670 |
| `alibaba/qwen3.8-max` | 262,144 | yes | 11,112 | 33,336 |
| `alibaba/qwen3.8-27b` | 1,000,000 | yes | 2,778 | 16,668 |
| `xiaomi/mimo-v2.6-flash` | 1,048,576 | yes | 778 | 1,556 |
| `xiaomi/mimo-v2.6-pro` | 1,048,576 | yes | 2,417 | 4,834 |
| `spacexai/grok-4.7` | 500,000 | yes | 11,112 | 33,336 |
| `stepfun/step-5-preview` | 1,000,000 | yes | 5,556 | 15,001 |
| `zai/glm-5.3-flashx` | 1,000,000 | yes | 2,056 | 6,945 |
| `alibaba/qwen3.8-omni-flash` | 1,000,000 | yes | 833 | 2,611 |
| `openai/gpt-6-luna` | 1,050,000 | yes | 556 | 2,778 |
| `openai/gpt-6-sol` | 1,050,000 | yes | 11,112 | 55,560 |
| `anthropic/claude-sonnet-5.5` | 1,000,000 | yes | 11,112 | 55,560 |
| `anthropic/claude-opus-5.5` | 1,000,000 | yes | 22,224 | 111,120 |
| `anthropic/claude-haiku-4.5` | 200,000 | yes | 5,556 | 27,780 |
| `moonshotai/kimi-k2-thinking` | 216,144 | — | 2,611 | 11,112 |
| `moonshotai/kimi-k2.5` | 256,000 | yes | 3,334 | 16,668 |
| `moonshotai/kimi-k2.6` | 262,000 | yes | 5,278 | 22,224 |
| `moonshotai/kimi-k2.7-code` | 256,000 | yes | 5,278 | 22,224 |
| `moonshotai/kimi-k3` | 1,000,000 | yes | 16,668 | 83,340 |
| `deepseek/deepseek-v3.2` | 128,000 | — | 3,445 | 10,279 |
| `deepseek/deepseek-v3.2-thinking` | 128,000 | — | 3,445 | 10,279 |
| `deepseek/deepseek-v4-flash-0731` | 1,000,000 | — | 444 | 833 |
| `deepseek/deepseek-v4-flash-vision-exp` | 1,048,576 | yes | 1,222 | 3,611 |
| `deepseek/deepseek-v4-pro` | 1,000,000 | — | 3,667 | 11,001 |
| `meta/llama-3.3-70b` | 128,000 | — | 4,000 | 4,000 |
| `meta/muse-spark-1.3` | 1,048,576 | yes | 6,945 | 23,613 |
| `meta/muse-spark-1.3-contributor` | 1,048,576 | yes | 556 | 1,111 |
| `meta/muse-spark-1.2` | 1,048,576 | yes | 6,945 | 23,613 |
| `meta/muse-spark-1.2-contributor` | 1,048,576 | yes | 556 | 1,111 |
| `inclusionai/ling-3.0-flash` | 256,000 | — | 111 | 333 |
| `zai/glm-4.6` | 200,000 | — | 3,334 | 12,223 |
| `zai/glm-4.7` | 200,000 | — | 3,334 | 12,223 |
| `openai/gpt-oss-120b` | 131,072 | — | 556 | 2,778 |
| `openai/gpt-oss-20b` | 131,072 | — | 167 | 778 |
| `google/gemini-3.1-flash-image` | 131,072 | yes | 2,778 | 16,668 |
| `google/gemini-3.1-flash-lite-image` | 65,536 | yes | 1,389 | 8,334 |
| `google/gemini-3-pro-image` | 65,536 | yes | 11,112 | 66,672 |
| `zai/glm-4.5v` | 66,000 | yes | 3,334 | 10,001 |

Models marked `vision` accept image input. Send an image the way the OpenAI API does — a content array with an `image_url` part, either an https URL or a `data:` URI:

```js
const res = await client.chat.completions.create({
  model: 'zai/glm-4.6v-flash',
  messages: [{ role: 'user', content: [
    { type: 'text', text: 'Caption this image.' },
    { type: 'image_url', image_url: { url: 'https://example.com/photo.jpg' } },
  ] }],
});
```

Images are only accepted on `user` messages, and only by a model marked `vision` — sending one to a text-only model returns 400 naming the models that do accept images.

## Agent runs

`POST https://automatio.ai/api/v1/agent` — give Automatio a GOAL and it does the work: routes to the right agents, calls tools, runs multi-step, and returns the result. Requires the `agent` scope.

```bash
curl -X POST https://automatio.ai/api/v1/agent \
  -H "Authorization: Bearer aut_yourkey" \
  -H "Content-Type: application/json" \
  -d '{"goal":"Research X and summarize","idempotencyKey":"req-001"}'
```

`idempotencyKey` is REQUIRED — an agent run takes real actions (sending email, deploying, spending credits), so a retry must never start a second run; the same key always returns the same job.

Returns `202 {jobId, runId, mode, model, runBudgetCredits, capabilities, poll}`. Add `"wait": true` to block until the run finishes and get the result inline.

- `GET https://automatio.ai/api/v1/agent/{jobId}` → `{status, result, creditsSpent, budget, awaitingInput?}`; `budget` is `{capCredits, reached}` — `reached: true` means the run stopped at the conversation's ceiling; status is one of `running | awaiting_input | completed | failed | cancelled`. `creditsSpent` is the CONVERSATION's cumulative spend, not this poll's — read it, never add it up across polls.
- `GET https://automatio.ai/api/v1/agent/jobs` → `{jobs, nextCursor}`. Your jobs, newest first — use it to recover a jobId you no longer have, or to audit what a key has run. `limit` (1-100, default 20) and `cursor` (the previous `nextCursor`) paginate. Scoped to the key OWNER, so jobs started by any of your keys are listed. Each row's `status` is the last PERSISTED disposition: `completed`/`failed` are authoritative, `running` only means not finalized yet — poll the single-job endpoint when you need liveness.
- `POST https://automatio.ai/api/v1/agent/{jobId}/answer` → body `{approved, feedback?, toolCallId?}`. Start the run with `"mode":"interactive"` and it can pause for you; the job reports `awaiting_input` with `awaitingInput.kind`, and answering RESUMES the same run. `question`: answer in `feedback`. `plan`: `approved` true to go ahead, false with `feedback` to revise. `approval`: the agent wants to do something destructive (SQL on the live database, replacing a dataset or a live site, a destructive pod command, destroying a pod), described in `awaitingInput.approval` (`summary`, `command`, `sql`, `reason`, `deploymentId`). `{"approved": true}` approves it — `feedback` is NOT used for an approval, and the response then carries `feedbackIgnored: true`. `{"approved": false, "feedback": "why"}` rejects it and the agent receives your reason.

Each job is capped by the key's credit limit (default 5,000) — counted across EVERY turn of a conversation, not per turn — on top of the account balance. Minimum 100 credits to start. The run enforces the cap itself, on every step, whether or not you poll: at the ceiling its next step runs without tools and writes what was done and what remains, the job ends `completed`, and its status carries `budget.reached: true`.

Multi-turn: the start response returns `conversationId` (the same value as `jobId`). Send it back on a later POST — with a fresh `idempotencyKey` — and that turn runs in the same conversation, so the agent sees the earlier exchange. Omit it for a one-off. Only one run per conversation at a time; a follow-up while one is live returns 409, and polling always reflects the latest turn.

Model: pass `model` with any id from `llm.models` above (agent runs and chat completions share one registry). Omit it and the run uses the key OWNER'S ACCOUNT DEFAULT — a value set in the web app, so two otherwise identical requests can run on different models, at different prices, if that setting changed in between. Pass it explicitly for reproducible cost. A key scoped with `allowedModels` may only pass models inside that scope. Both the start response and every poll report `model`, so you can always see what a job ran on.

A key can be confined to specific capabilities (e.g. `web`, `crypto`, `dataApi`) when it is created. The run then has no other tools at all, and both the start response and every poll report `capabilities` — so a refusal like "I can't access Gmail" can be traced to the key's grant rather than mistaken for a missing integration. `capabilities: null` means unconfined: the run may use every tool the owner has enabled, acting as them.

## Errors

- `401` invalid/missing key · `402` out of credits or key budget exhausted · `403` tool/model/scope not granted · `429` rate limit exceeded (see `Retry-After`) · `400` unknown tool/model or bad body · `409` agent job not awaiting input or already finished · `502` upstream model call failed.

Note: `/api/v1/chat/completions` returns errors in the OpenAI envelope (`{error:{message,type}}`); the other endpoints return `{error: "..."}`.

## Tools

33 tools available. Credits are charged per successful call.

### Apify

#### `apify_search_actors` — billed at cost (provider price + markup, varies per call)

Search the marketplace of ready-made web scrapers / automation tools ("actors") for a task, e.g. "google maps places", "instagram profile scraper", "amazon product reviews". Returns a ranked shortlist with reliability metrics (total runs, users, star rating, 30-day success rate) and pricing so you can PICK the best one. Use this first for any data-extraction / scraping request that the web or browser tools cannot satisfy directly.

| param | type | required | description |
|---|---|---|---|
| `query` | string | yes | Natural-language description of the capability, e.g. "google maps business scraper". |
| `category` | string | no | Optional store category filter (e.g. "ECOMMERCE", "SOCIAL_MEDIA", "AI", "NEWS", "JOBS"). |
| `limit` | integer | no | How many candidates to return (default 8). |

#### `apify_actor_details` — billed at cost (provider price + markup, varies per call)

Read a specific actor's INPUT SCHEMA, example input, and pricing before running it. Always call this after apify_search_actors and before apify_run_actor, so you build a valid input object. Pass the actorId from the search results (e.g. "apify~google-maps-scraper").

| param | type | required | description |
|---|---|---|---|
| `actorId` | string | yes | Actor id from search results, e.g. "apify~google-maps-scraper" or "username/actor-name". |

#### `apify_run_actor` — billed at cost (provider price + markup, varies per call)

Run a marketplace actor (a ready-made web scraper or automation) and return its extracted data. The input object must match the schema that apify_actor_details returns for that actor. Pay-per-use: cost depends on the actor and how much it extracts, and maxItems bounds it. The actorId comes from apify_search_actors results; an id that was not returned there is not supported.

| param | type | required | description |
|---|---|---|---|
| `actorId` | string | yes | Actor id to run, e.g. "apify~google-maps-scraper" (from apify_search_actors). |
| `input` | object | yes | The actor input object, built from apify_actor_details inputFields/exampleInput. |
| `maxItems` | integer | no | Max dataset items to extract & charge for (default 100). Lower = cheaper. Raise this for a large/complete extraction (hundreds–thousands of rows) when the user explicitly wants a lot of data; with maxCostUsd omitted, the spend cap scales with it. |
| `maxCostUsd` | number | no | Hard spend cap in USD for this run, measured in the actor's own price (pricePerResultUsd), before markup; max 25. The run stops once its cost would exceed this. When omitted, the cap is derived from the actor's price × maxItems with headroom, and the user's balance must cover it before the run starts. A large extraction with add-ons may need a higher cap than the derived one. |

### Crypto

#### `crypto_get_prices` — 3 credits/call

Get current prices for multiple cryptocurrencies at once. Returns price, market cap, 24h volume, and 24h price change for specified coins. Perfect for quick price checks and portfolio tracking.

| param | type | required | description |
|---|---|---|---|
| `coinIds` | string[] | yes | Array of coin IDs (e.g., ["bitcoin", "ethereum", "solana"]). Use lowercase IDs. |
| `currencies` | string[] | no | Target currencies (default: ["usd"]). Can include: usd, eur, gbp, btc, etc. |
| `includeMarketCap` | boolean | no | Include market cap data (default: true) |
| `include24hrVol` | boolean | no | Include 24h trading volume (default: true) |
| `include24hrChange` | boolean | no | Include 24h price change percentage (default: true) |

#### `crypto_get_market_data` — 5 credits/call

Get comprehensive market data for cryptocurrencies with rankings, volume, price changes, and more. Returns paginated list of coins sorted by market cap or other criteria. Perfect for market overview and discovering top coins.

| param | type | required | description |
|---|---|---|---|
| `vsCurrency` | string | no | Currency for price data (default: "usd"). Options: usd, eur, gbp, btc, etc. |
| `order` | string | no | Sort order (default: "market_cap_desc"). Options: market_cap_desc, volume_desc, id_asc, etc. |
| `perPage` | number | no | Results per page (default: 50, max: 250) |
| `page` | number | no | Page number (default: 1) |

### Media

#### `image_generate` — billed at cost (provider price + markup, varies per call)

Generate an image from a text prompt and return a permanent public URL. Billed at the provider's real cost plus the standard markup, so the price depends on the model and the image — it is not a flat per-call rate.

| param | type | required | description |
|---|---|---|---|
| `prompt` | string | yes | What to draw. Describe the subject, style and composition; for a transparent background (layers, sprites, logos) say so explicitly. |
| `aspectRatio` | "1:1" | "2:3" | "3:2" | "3:4" | "4:3" | "4:5" | "5:4" | "9:16" | "16:9" | "21:9" | no | Aspect ratio. Defaults to 1:1. The GPT Image family renders from a fixed size list, so a wide ratio snaps to its nearest landscape shape (3:2); the Gemini image engines produce a true 16:9. |
| `model` | "google/gemini-3.1-flash-image" | "google/gemini-3.1-flash-lite-image" | "meta/muse-image-1.0" | "google/gemini-3-pro-image" | "openai/gpt-image-2.5-flare" | "openai/gpt-image-2.5-sunburst" | "openai/gpt-image-2" | "openai/gpt-image-1.5" | no | Image model. Defaults to openai/gpt-image-2. These are the only ids that generate images — the chat models listed under llm.models cannot. Available: google/gemini-3.1-flash-image, google/gemini-3.1-flash-lite-image, meta/muse-image-1.0, google/gemini-3-pro-image, openai/gpt-image-2.5-flare, openai/gpt-image-2.5-sunburst, openai/gpt-image-2, openai/gpt-image-1.5. |

#### `image_analyze` — billed at cost (provider price + markup, varies per call)

Analyze an image and answer a question about it — captioning, OCR, describing a screenshot, reading a chart, identifying what is in a photo. Takes a public image URL or a data: URI and returns text. Billed at the vision model's real cost plus the standard markup, so the price depends on the image and the answer length rather than being a flat per-call rate.

| param | type | required | description |
|---|---|---|---|
| `source` | string | yes | The image: a public http(s) URL, or a `data:` URI with the bytes inline. |
| `question` | string | no | What to find out about the image, in natural language — e.g. 'caption this', 'read the error text', 'what are the dominant hex colours?', 'is there a person in this photo?'. Omit for a general description. |

#### `video_generate` — billed at cost (provider price + markup, varies per call)

Generate a video from a text prompt and return a permanent public URL. Billed at the provider's real cost plus the standard markup, so there is no flat per-call price — a clip is the most expensive call on the platform, and an 8-second clip on the default engine costs about 2,222-8,890 credits (~$0.80-$3.20 at the reference tier) depending on resolution, rising several times over at 4K or with audio. The response reports what the call actually billed as automatio.credits_charged; that number is the real one.

| param | type | required | description |
|---|---|---|---|
| `prompt` | string | yes | What to film. Describe the subject, motion, camera and style; the engines respond to shot direction ("slow dolly in", "handheld"). |
| `aspectRatio` | "1:1" | "2:3" | "3:2" | "3:4" | "4:3" | "4:5" | "5:4" | "9:16" | "16:9" | "21:9" | no | Aspect ratio. Defaults to the engine's own default. |
| `durationSeconds` | integer | no | Clip length in seconds. Clamped into the chosen engine's advertised range (Veo takes 4-8s, Wan 2-30s), because a value outside it is normalised provider-side and then billed for what was rendered rather than what was asked for. |
| `resolution` | "480p" | "768p" | "720p" | "1080p" | "2k" | "4k" | no | Resolution tier. Cost scales steeply with it — 4K can be 4x the 720p rate for the same clip. Dropped if the engine does not advertise it. |
| `model` | "bytedance/seedance-v1.5-pro" | "google/veo-3.1-lite-generate-001" | "alibaba/wan-v3.0-video" | "minimax/minimax-h3" | "minimax/minimax-h3-max" | "google/veo-3.1-generate-001" | "klingai/kling-v3.0-t2v" | "klingai/kling-v3.0-i2v" | "bytedance/seedance-2.0" | no | Video engine. Defaults to alibaba/wan-v3.0-video. Available: bytedance/seedance-v1.5-pro, google/veo-3.1-lite-generate-001, alibaba/wan-v3.0-video, minimax/minimax-h3, minimax/minimax-h3-max, google/veo-3.1-generate-001, klingai/kling-v3.0-t2v, klingai/kling-v3.0-i2v, bytedance/seedance-2.0. |

### Real estate

#### `idealista_suggestions` — 135 credits/call

Autocomplete Idealista location names to get locationId values needed for property searches. Use this first when the user mentions a city, neighborhood, or area in Spain, Portugal, or Italy.

| param | type | required | description |
|---|---|---|---|
| `prefix` | string | yes | Location name to search (e.g. "madrid", "barcelona", "lisbon") |
| `location` | "es" | "pt" | "it" | yes | Country code: Spain (es), Portugal (pt), Italy (it) |
| `propertyType` | "homes" | "offices" | "premises" | "garages" | "bedrooms" | "newDevelopments" | "land" | no | Property type context (default: homes) |
| `operation` | "sale" | "rent" | no | Operation context (default: sale) |

#### `idealista_search` — 135 credits/call

Search Idealista real estate listings for homes, flats, offices, land, rooms, garages, and more in Spain, Portugal, or Italy. Returns results as a single data table. Call this ONCE with the right maxResults — it paginates internally and returns one combined table; never call it repeatedly to page through results manually. Use idealista_suggestions first to get the locationId if you only have a city name.

| param | type | required | description |
|---|---|---|---|
| `locationId` | string | yes | Location ID from idealista_suggestions (e.g. "0-EU-ES-28-07-001-079") |
| `locationName` | string | yes | Human-readable location name, e.g. Madrid. Never include parentheses () — they silently break the search. Use a clean name from idealista_suggestions with no "(City)" / "(Province)" clarifiers. |
| `location` | "es" | "pt" | "it" | yes | Country code: Spain (es), Portugal (pt), Italy (it) |
| `operation` | "sale" | "rent" | yes | Property operation type |
| `propertyType` | "homes" | "newhomes" | "rooms" | "garages" | "storagerooms" | "buildings" | "lands" | "offices" | "commercial" | no | Type of listing to search (default: homes) |
| `maxResults` | integer | no | Maximum number of listings to return (default: 40, max: 480). The tool fetches multiple pages automatically to reach this count. Use this when the user asks for more than 40 results. |
| `startPage` | integer | no | API page to start from (default: 1). Useful for continuing a previous search. |
| `order` | "relevance" | "lowestprice" | "highestprice" | "mostrecent" | "leastrecent" | "highestpricereduction" | "lowestpricem2" | "highestpricem2" | "biggest" | "smallest" | no | Sort order (default: relevance) |
| `locale` | "es" | "it" | "pt" | "en" | "ca" | "de" | "fr" | "nl" | "nb" | no | Response language (default: en) |
| `minPrice` | number | no | Minimum price in local currency |
| `maxPrice` | number | no | Maximum price in local currency |
| `minSize` | number | no | Minimum size in m² |
| `maxSize` | number | no | Maximum size in m² |
| `bedrooms` | integer[] | no | Number of bedrooms to filter by (0, 1, 2, 3, or 4+). Array for multiple: [1, 2] |
| `elevator` | boolean | no |  |
| `garage` | boolean | no |  |
| `swimmingPool` | boolean | no |  |
| `terrace` | boolean | no |  |
| `airConditioning` | boolean | no |  |
| `exterior` | boolean | no |  |
| `garden` | boolean | no |  |
| `furnished` | "furnished" | "furnishedKitchen" | no | Furniture requirement for rentals |
| `newDevelopment` | boolean | no | New development properties only |
| `sinceDate` | "T" | "Y" | "W" | "M" | no | Date filter: T=last 24h (rent only), Y=last 48h (buy only), W=last week, M=last month |
| `bankOffer` | boolean | no | Bank-owned properties only |

#### `idealista_details` — 135 credits/call

Get complete details for a single Idealista property — full description, all photos, floor plans, energy certificate, features, and contact info. Use the propertyCode from idealista_search results.

| param | type | required | description |
|---|---|---|---|
| `propertyId` | string | yes | Property code from search results (e.g. "106387165") |
| `location` | "es" | "pt" | "it" | yes | Country code: Spain (es), Portugal (pt), Italy (it) |
| `language` | "en" | "es" | "it" | "pt" | "ca" | "de" | "fr" | "nl" | "nb" | no | Response language (default: en) |

#### `idealista_locations` — 135 credits/call

Get districts and neighborhoods within a divisible Idealista location. Use after idealista_suggestions when divisible=true to refine searches to specific areas.

| param | type | required | description |
|---|---|---|---|
| `locationId` | string | yes | Parent locationId (divisible=true from suggestions) |
| `location` | "es" | "pt" | "it" | yes | Country code: Spain (es), Portugal (pt), Italy (it) |
| `propertyType` | "homes" | "offices" | "premises" | "garages" | "bedrooms" | "newDevelopments" | "land" | no | Property type context (default: homes) |
| `operation` | "sale" | "rent" | no | Property operation type |

#### `idealista_agency_profile` — 135 credits/call

Get an Idealista real estate agency profile. The micrositeShortName is the slug from idealista.com/pro/{slug}/.

| param | type | required | description |
|---|---|---|---|
| `micrositeShortName` | string | yes | Agency slug from idealista.com/pro/{slug}/ |
| `location` | "es" | "pt" | "it" | yes | Country code: Spain (es), Portugal (pt), Italy (it) |

#### `idealista_agency_locations` — 135 credits/call

Get the locations where a specific Idealista agency has listings, with counts per location.

| param | type | required | description |
|---|---|---|---|
| `micrositeShortName` | string | yes | Agency slug from idealista.com/pro/{slug}/ |
| `location` | "es" | "pt" | "it" | yes | Country code: Spain (es), Portugal (pt), Italy (it) |
| `locationId` | string | yes | Root locationId to scope the search (use a broad location like a province) |
| `operation` | "sale" | "rent" | yes | Property operation type |
| `locale` | "es" | "it" | "pt" | "en" | "ca" | "de" | "fr" | "nl" | "nb" | no | Response language (default: en) |

### Reddit

#### `reddit_search` — 5 credits/call

Search Reddit for posts matching a query. Returns posts with title, score, comments count, and more. Use cases:

| param | type | required | description |
|---|---|---|---|
| `query` | string | yes | Search query (e.g., "AI news", "best programming languages") |
| `subreddit` | string | no | Subreddit to search within (e.g., "programming") |
| `sort` | "relevance" | "hot" | "top" | "new" | "comments" | no | Sort order |
| `time` | "hour" | "day" | "week" | "month" | "year" | "all" | no | Time filter |
| `limit` | number | no | Results per page (default: 25) |
| `after` | string | no | Pagination cursor from previous response |

#### `reddit_post` — 5 credits/call

Get a Reddit post with its comments. Returns post details and top comments. Use cases:

| param | type | required | description |
|---|---|---|---|
| `postId` | string | yes | Post ID (e.g., "abc123") or full Reddit URL |
| `commentLimit` | number | no | Comments to retrieve (default: 30) |
| `commentDepth` | number | no | Max comment nesting depth (default: 3) |
| `commentSort` | "best" | "top" | "new" | "controversial" | "old" | "qa" | no | Comment sort order |

#### `reddit_subreddit` — 5 credits/call

Browse posts from a subreddit. Returns hot, top, new, rising, or controversial posts. Use cases:

| param | type | required | description |
|---|---|---|---|
| `subreddit` | string | yes | Subreddit name (e.g., "programming", "technology") |
| `sort` | "hot" | "top" | "new" | "rising" | "controversial" | no | Sort order |
| `time` | "hour" | "day" | "week" | "month" | "year" | "all" | no | Time filter (for top/controversial) |
| `limit` | number | no | Posts per page (default: 25) |
| `after` | string | no | Pagination cursor from previous response |

#### `reddit_domain_posts` — 5 credits/call

Find all Reddit posts linking to a specific domain. Great for SEO research and brand monitoring. Use cases:

| param | type | required | description |
|---|---|---|---|
| `domain` | string | yes | Domain to search (e.g., "example.com", "github.com") |
| `sort` | "hot" | "top" | "new" | "rising" | "controversial" | no | Sort order |
| `time` | "hour" | "day" | "week" | "month" | "year" | "all" | no | Time filter |
| `limit` | number | no | Posts per page (default: 25) |
| `after` | string | no | Pagination cursor from previous response |

#### `reddit_user` — 5 credits/call

Get a Reddit user's profile information including karma, account age, and verification status. Use cases:

| param | type | required | description |
|---|---|---|---|
| `username` | string | yes | Reddit username (e.g., "spez", "GallowBoob") |

### Twitter

#### `twitter_user` — 15 credits/call

Get detailed information about a Twitter/X user including profile, follower counts, bio, and verification status. Returns essential user data only (~85% smaller than raw API response).

| param | type | required | description |
|---|---|---|---|
| `username` | string | yes | Twitter username (without @, e.g., "elonmusk") |

#### `twitter_user_tweets` — 15 credits/call

Retrieve tweets from a Twitter/X user timeline. User data sent ONCE (not per tweet) for massive token savings (~94% reduction). Returns ~20 tweets per request with cursor for pagination.

| param | type | required | description |
|---|---|---|---|
| `userId` | string | yes | Twitter user ID (get from twitter_user first) |
| `limit` | number | yes | Max tweets to return (default: 20, max: 40) |
| `cursor` | string | no | Pagination cursor from previous response (optional) |

#### `twitter_tweet_comments` — 15 credits/call

Retrieve comments/replies to a Twitter/X tweet. Returns lean comment data optimized for AI consumption (~87% token reduction). Returns ~20 comments per request.

| param | type | required | description |
|---|---|---|---|
| `tweetId` | string | yes | Tweet ID to get comments for |
| `limit` | number | yes | Max comments to return (default: 20, max: 40) |
| `cursor` | string | no | Pagination cursor from previous response (optional) |

#### `twitter_search` — 15 credits/call

Search Twitter/X for tweets matching a query. Returns top results with optimized user data (sent once per unique user) for ~93% token reduction. Use for finding tweets, trends, or topics.

| param | type | required | description |
|---|---|---|---|
| `query` | string | yes | Search query (e.g., "AI news", "@elonmusk", "#bitcoin") |
| `limit` | number | yes | Max results to return (default: 20, max: 40) |
| `searchType` | "Top" | "Latest" | "People" | "Photos" | "Videos" | yes | Type of search results (default: Top) |

#### `twitter_tweet` — 15 credits/call

Read a single Twitter/X tweet by its ID: text, author, engagement counts and timestamp. Use when you have a tweet ID or URL and need the tweet's own content — the comments tool returns only the replies, never the tweet.

| param | type | required | description |
|---|---|---|---|
| `tweetId` | string | yes | Tweet ID — the trailing number in a tweet URL (x.com/user/status/<id>). |

#### `twitter_followers` — 15 credits/call

List a Twitter/X account's followers, one page at a time. Each follower includes their own follower count and verified flag. For counting VERIFIED followers use twitter_verified_followers instead — it is pre-filtered upstream and needs far fewer requests.

| param | type | required | description |
|---|---|---|---|
| `twitterUserId` | string | yes | Numeric Twitter user ID (not the @handle) — get it from twitter_user's userId field. |
| `cursor` | string | no | nextCursor from a previous call, to read the next page. |

#### `twitter_verified_followers` — 15 credits/call

List a Twitter/X account's VERIFIED followers, one page at a time. The upstream list is already filtered to verified accounts, so counting them means paging this rather than scanning every follower. There is no total-count field in the API — to get a total, page until nextCursor is null and sum the counts, and tell the user how many pages that took.

| param | type | required | description |
|---|---|---|---|
| `twitterUserId` | string | yes | Numeric Twitter user ID (not the @handle) — get it from twitter_user's userId field. |
| `cursor` | string | no | nextCursor from a previous call, to read the next page. |

### Web

#### `web_search` — 1 credits/call

Search the web. Returns a ranked list of result titles, URLs and short snippets. The snippets are search previews for judging which results look promising, not the text of the pages: this tool never returns page content, and the `content` field on each result is always empty. The URL-reader tool is what returns a page's actual text, given a URL from these results.

| param | type | required | description |
|---|---|---|---|
| `query` | string | yes | The search query to find URLs and information about (e.g., "artificial intelligence") |
| `country` | string | no | OPTIONAL two-letter ISO country code (e.g. "us", "rs", "de") to localize results. Set ONLY for region-specific queries ("near me", local businesses, a query written in a local language, "in my country"). OMIT for global/general topics — forcing a country hurts global queries. |
| `language` | string | no | OPTIONAL two-letter ISO language code (e.g. "en", "sr", "de") for the results language. Set it to match the query language for local/non-English searches. OMIT for global/general topics. |
| `location` | string | no | OPTIONAL free-text origin at city level (e.g. "Belgrade, Serbia") to simulate a real user's location. Use only for hyper-local "near me" style queries. |

#### `web_read_url` — 1 credits/call

Read and extract content from any URL as clean markdown text. Extracts main content from web pages.

| param | type | required | description |
|---|---|---|---|
| `url` | string | yes | The URL to read and extract content from (e.g., "https://example.com") |

#### `web_screenshot` — 2 credits/call

Take a screenshot of any website URL. Returns a hosted URL to the screenshot image. Captures the visible viewport by default, or the entire scrollable page with fullPage. Use this when you need to capture a visual of a webpage for content creation, documentation, or analysis.

| param | type | required | description |
|---|---|---|---|
| `url` | string | yes | The URL of the website to screenshot (e.g., "https://example.com") |
| `viewport` | object | no | Optional viewport size. Defaults to 1920x1080 (standard widescreen) |
| `device` | "desktop" | "tablet" | "mobile" | no | Device preset: desktop 1920x1080, tablet 820x1180, mobile 390x844 with touch emulation. An explicit viewport takes precedence. |
| `fullPage` | boolean | no | When true, captures the entire scrollable page height instead of just the visible viewport. Defaults to false. |
| `hideSelectors` | string[] | no | CSS selectors for elements hidden before capture, e.g. cookie banners or chat widgets. |
| `darkMode` | boolean | no | When true, renders the page with the dark colour scheme preference. |
| `waitForSelector` | string | no | CSS selector to wait for before capturing. Ignored if the element never appears. |
| `waitUntil` | "visible-content" | "mutation-idle" | "resource-idle" | "media-idle" | "network-idle" | no | How long to let the page settle before capturing. |
| `timeout` | number | no | Seconds to wait for the page before giving up. Defaults to 15, maximum 180. |
| `locale` | string | no | Browser locale for the page, e.g. "de-DE". |
| `userAgent` | string | no | Override the browser User-Agent string. |
| `noCache` | boolean | no | When true, bypasses any cached copy and renders the page fresh. |
| `assertStatusCode` | number | no | When set, the capture fails unless the page returns this HTTP status, e.g. 200. |

### YouTube

#### `youtube_video_info` — 15 credits/call

Get detailed information about a YouTube video including title, description, view count, channel info, and keywords. Accepts video ID or any YouTube URL format.

| param | type | required | description |
|---|---|---|---|
| `videoId` | string | yes | YouTube video ID or URL (e.g., "dQw4w9WgXcQ" or "https://www.youtube.com/watch?v=...") |

#### `youtube_comments` — 15 credits/call

Retrieve comments from a YouTube video. API returns ~20 comments per page. Use continuationToken from response to fetch more pages.

| param | type | required | description |
|---|---|---|---|
| `videoId` | string | yes | YouTube video ID or URL |
| `continuationToken` | string | no | Continuation token from previous response to get next page (optional) |
| `limit` | number | yes | Max comments to return from this page (API limit ~20/page) |

#### `youtube_transcript` — 15 credits/call

Download and extract the full transcript/subtitles from a YouTube video with accurate timestamps. Returns complete text with timestamp links for precise quoting. Useful for analyzing video content without watching.

| param | type | required | description |
|---|---|---|---|
| `videoId` | string | yes | YouTube video ID or URL |

#### `youtube_search` — 15 credits/call

Search for YouTube videos by query with optional filters for time range, language, and country. Returns video information including title, channel, view count, and thumbnails.

| param | type | required | description |
|---|---|---|---|
| `query` | string | yes | Search query (e.g., "how to code in python") |
| `limit` | number | yes | Maximum number of results to return (default: 10, max: 50) |
| `lang` | string | no | Language code (e.g., "en", "es", "fr") - optional |
| `orderBy` | "last_hour" | "today" | "this_week" | "this_month" | "this_year" | no | Filter by upload time - optional (e.g., "this_month" for recent uploads) |
| `country` | string | no | Country code in lowercase (e.g., "us", "uk", "de") - optional |
