> For clean Markdown of any page, append .md to the page URL. > For a complete documentation index, see https://docs.merge.dev/merge-gateway/api-overview/llms.txt. > For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.merge.dev/_mcp/server. # API details > Base URL, authentication, pagination, and key endpoint shapes for the Models, Vendors, Responses, and Management APIs. ## Base API URL All API endpoints in the reference documentation are relative to the following base URL: ``` https://api-gateway.merge.dev/v1 ``` ## Authentication For any request you make when communicating with Merge Gateway, you will need an [API key](https://gateway.merge.dev/api-keys) to authenticate yourself as an authorized user. Add your API Key with a "Bearer " prefix as a header called Authorization to authorize your Merge API requests. This header must be included in every request in this format: ``` Authorization: Bearer ``` You do not have to use the dashboard to get a key. The [Management API](#management-api) mints, rotates, and revokes API keys programmatically, which is what you want if keys are provisioned per customer or checked into infrastructure-as-code. ## Key endpoints Gateway's model-calling surface is centered around these endpoint groups: * `GET /models` * list models * filter by `provider` or `vendor` * fetch a single model by query string with `?model=` * `GET /vendors` * list execution vendors and the models they currently serve * fetch a single vendor with `GET /vendors/{vendor_id}` * `POST /responses` * create an LLM response * the response includes the `vendor` that ultimately served the request and the `service_tier` that actually served it * `POST /embeddings` * create embeddings, priced on input tokens Gateway also exposes SDK-compatible surfaces under `/v1/openai`, `/v1/anthropic`, `/v1/ai-sdk`, and `/v1/langchain`, so an existing client reaches the same models with a base URL change. See [Get started](/merge-gateway/get-started). Administering the org, rather than calling models, is a separate surface: see [Management API](#management-api) below. ## Management API API keys, projects, routing policies, and usage are administered programmatically through the Management API, on the same host. It is authenticated by a management key (`Authorization: Bearer mgmt_`) rather than a gateway API key, so the credential that provisions keys is never one that can make model calls. Create a management key in the dashboard under [API keys → Management keys](https://gateway.merge.dev/api-keys). | Endpoint group | What it covers | | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------ | | `/v1/keys` | Create, list, update, and revoke API keys, each with an optional spend limit and a `limit_reset` window of `daily`, `weekly`, or `monthly` | | `/v1/projects` | Create and manage projects, their routing policy, budgets, usage, and per-project prompt injection and DLP settings | | `/v1/routing-policies` | Create and update routing policies and set the org default | | `/v1/organization/usage` | Org-wide and per-project spend rollups with per-model breakdowns | Full reference and scopes: [Management API](/merge-gateway/management-api). Walkthroughs: [API keys](/merge-gateway/automation/api-keys) and [Projects API](/merge-gateway/automation/projects-api). ## Models API shape `GET /models` returns canonical model identity at the top level and vendor-specific execution metadata under `vendors`. Example: ```json { "model": "anthropic/claude-opus-4-6", "provider": "anthropic", "display_name": "Claude Opus 4.6", "vendors": { "anthropic": { "launch_date": "2025-05-14", "context_window": 1000000, "max_output_tokens": 32768, "availability_status": "available", "capabilities": { "input": ["text", "image"], "output": ["text", "tool_use"], "supports_tool_calling": true, "supports_tool_choice": true, "supports_structured_outputs": true, "streaming": true }, "service_tiers": ["standard", "flex"], "pricing": { "input_per_million": 2.5, "output_per_million": 10, "currency": "USD", "flex": { "input_per_million": 1.25, "output_per_million": 7.5 } } } }, "availability_status": "available", "created_at": "2025-05-14T00:00:00Z", "updated_at": "2026-03-01T00:00:00Z" } ``` Use `GET /models?model=` when you want one specific model object but prefer a query parameter over a path parameter. The slash is fine there because it is part of the query parameter value. ## Vendors API shape `GET /vendors` returns execution hosts, not canonical model owners. Example: ```json { "vendor": "bedrock", "name": "AWS Bedrock", "models": [ "anthropic/claude-opus-4-6", "google/gemma-3-27b-it" ], "supports_zdr": true, "supports_byok": true, "availability_status": "active" } ``` ## Pagination `GET /models` and `GET /vendors` return a paged list envelope: ```json { "object": "list", "data": [ ... ], "has_more": true, "next_cursor": "mistral/magistral-medium-2509-thinking" } ``` `limit` sets the page size. It defaults to 50 and **caps at 500**. A higher value is rejected before the request reaches the catalog: ```json HTTP 422 { "detail": [ { "type": "less_than_equal", "loc": ["query", "limit"], "msg": "Input should be less than or equal to 500", "input": "1000", "ctx": { "le": 500 } } ] } ``` Do not size a single request to the catalog. The catalog grows as models are added, so a `limit` that fits today silently starts truncating later. Page instead: send the previous response's `next_cursor` back as `cursor` and keep going while `has_more` is `true`. The next page starts after the cursor entry, so pages never overlap, and `next_cursor` is `null` on the last page. Treat the cursor as opaque, a value to pass back rather than one to construct. ```python title="Python" import requests BASE = "https://api-gateway.merge.dev/v1" headers = {"Authorization": "Bearer YOUR_API_KEY"} models, cursor = [], None while True: params = {"limit": 500} if cursor: params["cursor"] = cursor page = requests.get(f"{BASE}/models", headers=headers, params=params).json() models.extend(page["data"]) if not page["has_more"]: break cursor = page["next_cursor"] print(len(models)) ``` ```bash title="cURL" # First page curl "https://api-gateway.merge.dev/v1/models?limit=500" \ -H "Authorization: Bearer YOUR_API_KEY" # Next page: pass the previous response's next_cursor curl "https://api-gateway.merge.dev/v1/models?limit=500&cursor=mistral/magistral-medium-2509-thinking" \ -H "Authorization: Bearer YOUR_API_KEY" ``` Filters do not carry across pages on their own, so repeat `provider` or `vendor` on every request in the loop alongside `cursor`. Fetching one model with `?model=` returns a bare model object rather than a list envelope, so it has no `has_more` or `next_cursor`. The Management API paginates separately and caps its list endpoints at 100 per page: `GET /v1/keys` takes `offset` and `limit` and reports `has_more`, and `GET /v1/projects` takes `cursor` and `limit`. ## Responses `POST /responses` returns the canonical `model` that served the request and a top-level `vendor` field for the execution host that actually handled it. ### Request parameters Beyond the message `input`, the request accepts these optional fields: | Field | Type | Description | | ----------------------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `service_tier` | `"standard"` \| `"flex"` \| `"priority"` | Processing tier. Omit for `standard`. Only allowed on routes priced for the tier, otherwise the request fails closed with `400`. `priority` is accepted but not currently priced on any route, so it always fails closed today. | | `service_tier_fallback` | `boolean` | If the provider throttles the requested tier (`429` / `503`), retry once at `standard` instead of erroring. Defaults to `false`. | ### Served tier and billing The response echoes a top-level `service_tier`: the tier that actually served the request and the rate you were billed at. On a throttle fallback this differs from the tier you sent (e.g. you requested `flex` but were served and billed at `standard`). ```json { "model": "openai/gpt-5.4", "vendor": "openai", "service_tier": "flex", "usage": { "input_tokens": 812, "output_tokens": 180, "total_tokens": 992, "cost": 0.00219 } } ``` See [Service tiers](/merge-gateway/capabilities/service-tiers) for the full flow, supported vendor routes, and fallback behavior. ### Per-call cost Every response carries `usage.cost`: the provider cost in USD that Gateway computed for that call, from the tokens above and the pricing of the route that actually served the request. No request parameter is needed. ```json "usage": { "input_tokens": 812, "output_tokens": 180, "total_tokens": 992, "cost": 0.00219 } ``` `usage.cost` is returned on: | Endpoint | Notes | | ------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------- | | `POST /responses` | Streaming responses carry usage only on the final `response.done` chunk, so that is where `cost` appears | | `POST /embeddings` | Priced on input tokens | | `POST /openai/responses`, `POST /openai/chat/completions`, `POST /openai/embeddings` | Same value on the OpenAI-compatible surfaces, including the final streaming chunk | Three details worth knowing: * **`cost` is `null`, never `0`, when the served route has no pricing on file.** A false zero would be recorded as real spend by anything summing costs, so an unknown price is reported as unknown. Cost-tracking tools that read this field, such as Langfuse, skip a null instead of logging \$0. * **It is the same figure as `routing.cost_usd`**, which is only returned when you set `include_routing_metadata: true`. Both come from the same calculation, so a request that asks for both sees them agree. Use `usage.cost` for everyday cost tracking and routing metadata when you also need the policy and vendor decisions behind the call. * **It is provider cost, not your invoiced amount.** The two can differ, and the invoice is the authority. Billing starts from this figure and then applies your organization's plan rate, prices BYOK requests against the model's list price rather than what your own provider account charged, and adds server-tool charges such as web search, which never appear in `usage.cost`. Cost reflects the tier that served the request, so a `flex` request that fell back to `standard` is priced at `standard`. For invoiced spend, budgets, and dashboards, see [Cost governance and savings](/merge-gateway/cost/cost-governance-and-savings). > Build once. Connect to every API, tool, and LLM. Ship products and agents to production without owning the integration stack. ## API Docs - API Overview > Models [List public models or fetch a single model](https://docs.merge.dev/merge-gateway/api-overview/models/list.md) - API Overview > Vendors [List vendors](https://docs.merge.dev/merge-gateway/api-overview/vendors/list.md) - API Overview > Vendors [Fetch a single vendor by ID](https://docs.merge.dev/merge-gateway/api-overview/vendors/retrieve.md) - API Overview > Responses [Create Response](https://docs.merge.dev/merge-gateway/api-overview/responses/create.md) - API Overview > Embeddings [Create Embedding](https://docs.merge.dev/merge-gateway/api-overview/embeddings/create.md) - API Overview > Routing [List Routing Policies](https://docs.merge.dev/merge-gateway/api-overview/routing/list-policies.md) - API Overview > Routing [List Routing Strategies](https://docs.merge.dev/merge-gateway/api-overview/routing/list-strategies.md) - API Overview > Tags [List Tags](https://docs.merge.dev/merge-gateway/api-overview/tags/list.md) ## OpenAPI Specification The raw OpenAPI 3.1 specification for this API is available at: - [OpenAPI JSON](https://docs.merge.dev/openapi.json) - [OpenAPI YAML](https://docs.merge.dev/openapi.yaml)