ceruleanjobs-com MCP API
The Model Context Protocol server over the same index — the same engine as Search, shaped for an AI agent rather than for a hand-written client.
The Model Context Protocol server over the same index — the same engine as Search, shaped for an AI agent rather than for a hand-written client.
Every API here is available over the APIs.io API and to AI agents over MCP.
One button, every client — Claude, Cursor, VS Code and the rest.
https://apis.io/mcp
find_apisBrowse and filter every API in the catalog.get_api_artifactsOne API's artifacts, grouped by type.get_openapiThe primary OpenAPI for this API.find_similar_apisAPIs that look like this one.apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.resolveTurn a domain, URL or GitHub org into the provider it belongs to.find_cohortsEvery scored population of providers in the catalog.curl "https://apis.io/api/v1/apis/ceruleanjobs-com:ceruleanjobs-com-mcp-api"
curl "https://apis.io/api/v1/apis?limit=25"
Calls without X-API-Key need no key. Calls with it need a paid plan — get a key.
Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.
A second provider on the same verified email joins the account you already have.
openapi: 3.2.0
info:
title: Cerulean Search MCP API
version: 2.2.0
summary: Public, unauthenticated search over the Cerulean luxury job board.
description: '`GET /api/search` is the public search endpoint behind
ceruleanjobs.com/search/. It is the same
endpoint the site''s own search page calls, and it is open to anyone — there
is no API key, no authentication, and no rate limit. If you are pointing an AI agent at Cerulean rather than writing a client by
hand, `POST /api/mcp` is the same engine as a Model Context Protocol server. See **The MCP server** below. Requests are answered from an in-memory index held by regional Lambdas in
Singapore, Virginia, Oregon and Paris. Route 53 latency routing sends each
caller to the nearest one, and there is no database in the query path. Median server time is a few milliseconds. ### The MCP server
`POST /api/mcp` is a Model Context Protocol
server over the same index, `cerulean-jobs` version 3.0.0. It exists so a
model can search the board without being handed this document and asked to
write a client: three read-only tools — `search`, `fetch` and
`find_similar_jobs` — carry their own JSON Schemas, including the complete
list of valid values for the four taxonomy filters. It speaks protocol revision `2026-07-28`, which is stateless: there is no
handshake to complete, no session ID to keep, and no connection to hold
open, so one HTTP POST is one complete tool call. Clients on the earlier
revisions, `2024-11-05` to `2025-11-25`, open with `initialize` as they
always have and are answered request by request too. Point a client at
`https://ceruleanjobs.com/api/mcp` and it works, with the same absence of
keys and sign-up as the REST endpoint. The tools return shapes of their own, published as output schemas. `search`
and `fetch` follow OpenAI''s search/fetch standard — results carry `id`,
`title` and `url`, and `fetch` returns `{id, title, text, url, metadata}`,
the whole listing — and beside those carry a listing''s brand, place, date
and taxonomy slugs as `SearchResult` does. Their `warnings` use this
document''s codes, plus `detail_unavailable` when `fetch` can serve only a
listing''s summary, and `indexBuiltAt` means what it means here. Every link
they return carries `utm_medium=mcp`. The one place they behave differently
from this endpoint is invalid filter values: the REST endpoint answers with
`200`, `total: 0` and an `unknown_enum_value` warning, while the tool schema
rejects the value and names the ones that would have worked. That is a
better answer for a model, which can correct itself and call again. ### What changed in 2.2.0 — 2026-09-24
Additive only. `brand`, `location` and `country` take names as people write
them as well as slugs, and a value that names nothing on file raises the new
`unknown_value` warning, with close spellings in the new, optional
`Warning.suggestions`. The MCP server moved to version 3.0.0: its tools are
now `search`, `fetch` and `find_similar_jobs`, described under `/api/mcp`,
and `GET` and `DELETE` on that path answer `405`. `X-Cerulean-Api-Version`
still reads `2`. ### What changed in 2.1.0 — 2026-08-21
Additive only; nothing in the `GET /api/search` contract changed. The MCP
server described above was added at `POST /api/mcp`, and this document gained
the `/api/mcp` path below. `X-Cerulean-Api-Version` still reads `2`. ### What changed in 2.0.0 — 2026-08-20
Two breaking changes, both in the response body. A client that reads `url`
or `brand.hasLogo` must be updated; every example below shows the new form. * **`url` is now an absolute URL** — `https://ceruleanjobs.com/jobs/{slug}/`
rather than the bare path `/jobs/{slug}/`. Nothing has to be prepended any
more. `slug` is unchanged and is still the stable identifier to pass back
to `similar` and to the new `slug` parameter. * **`brand.hasLogo` is replaced by `brand.logoUrl`** — emitted only when a
logo exists, so its presence carries the same signal the boolean did, and
the URL comes with it.'
contact:
name: Cerulean
url: https://ceruleanjobs.com/about/
license:
name: Job data may be used for personal, editorial and commercial purposes, crediting Cerulean and linking each listing to its url.
url: https://ceruleanjobs.com/about/developers/#terms-and-attribution
servers:
- url: https://ceruleanjobs.com
description: Primary site (English) — `site=en`
- url: https://cerulean.fr
description: France — `site=fr-fr`
- url: https://ceruleanjobs.ch
description: Switzerland — `site=fr-ch`, and `site=de-ch` under /de/
- url: https://ceruleanjobs.ca
description: Canada — `site=en-ca`, and `site=fr-ca` under /fr/
- url: https://ceruleanjobs.de
description: Germany — `site=de-de`
- url: https://ceruleanjobs.co.uk
description: United Kingdom — `site=en-gb`
- url: https://cerulean.es
description: Spain — `site=es-es`
- url: https://ceruleanjobs.it
description: Italy — `site=it-it`
- url: https://ceruleancareers.com
description: United States — `site=en-us`
security: []
tags:
- name: MCP
description: The Model Context Protocol server over the same index — the same engine as Search, shaped for an AI agent rather than for a hand-written client.
paths:
/api/mcp:
post:
tags:
- MCP
summary: Model Context Protocol endpoint
operationId: mcpEndpoint
description: 'A Model Context Protocol server over
the same index as `GET /api/search` — `cerulean-jobs` version 3.0.0 —
using the streamable HTTP transport in stateless mode. The body is a single JSON-RPC 2.0
message, so this operation is described here rather than typed: the
method names and message shapes are the MCP specification''s, and the
tools'' input and output schemas are in `tools/list`. What is written
down below is the HTTP envelope, which is the part that trips people up. ### One POST is one tool call
Revision `2026-07-28` removed protocol-level sessions — there is no
`initialize` handshake, no session identifier, and nothing held open
between calls. A client sends one POST and gets one complete answer. Clients on the earlier revisions — `2024-11-05`, `2025-03-26`,
`2025-06-18` and `2025-11-25` — still open with `initialize`, and the
server answers each of their requests on its own too; `server/discover`
lists all five. `GET` and `DELETE` answer `405`: there is no stream to
open and no session to end. On `2026-07-28`, what replaced the handshake is redundancy: every
request describes itself, in headers *and* in `params._meta`, and the
server rejects a request where the two disagree. The duplication is
deliberate — it lets an intermediary route a call without parsing the
body. ```
POST /api/mcp HTTP/1.1
Content-Type: application/json
Accept: application/json, text/event-stream
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: search
{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{
"_meta":{
"io.modelcontextprotocol/protocolVersion":"2026-07-28",
"io.modelcontextprotocol/clientInfo":{"name":"my-agent","version":"1.0"},
"io.modelcontextprotocol/clientCapabilities":{}},
"name":"search",
"arguments":{"query":"atelier","location":"paris","limit":20}}}
```
Almost nobody writes this by hand. Any MCP client library speaks it;
the transcript is here so that a failure is diagnosable. ### The tools
All three are read-only, and annotated so: `readOnlyHint` and
`idempotentHint` true, `destructiveHint` and `openWorldHint` false. | Tool | Arguments |
|---|---|
| `search` | `query`, `brand`, `location`, `country`, `employment_type`, `seniority_level`, `industry`, `department`, `locale`, `posted_within_days`, `limit`, `offset` — all optional |
| `fetch` | `id` (required), `locale` |
| `find_similar_jobs` | `id` (required), `location`, `country`, `locale`, `limit`, `offset` |
`search` and `fetch` follow OpenAI''s search/fetch standard: results
carry `id`, `title` and `url`, and `fetch` returns
`{id, title, text, url, metadata}` — the whole listing as text, and its
structured detail. `id` is a listing''s `slug`, and `fetch` and
`find_similar_jobs` take a Cerulean job URL as well. `query` may be a
plain sentence ("senior client advisor, Bulgari, Geneva"): a brand,
place, country or employment type it names becomes that filter, and the
result says so in `interpreted`. `brand`, `location` and `country` take
names, as the query parameters above do. `locale` is the user''s language and region as a BCP 47 tag (`fr-CH`),
negotiated as `Accept-Language` is: a tag naming one of the ten sites
takes it, any other takes its language''s site, and another language
takes `en`. It sets the language of the text and adds `localizedUrl`,
and never filters; the tools take no `site` or `lang`. `posted_within_days` keeps the listings first seen in the last N days,
up to 365, and `limit` is at most 20 here. `find_similar_jobs` maps `id` to the `similar` parameter, not `slug`. It
offers no `brand` argument because similarity mode ignores that filter,
and no `query` because the text weight there is zero. Where
`GET /api/search` falls back to a plain search for a listing with no
embedding, the tool answers with an error. Every link the tools return carries `utm_medium=mcp`. ### The one behavioural difference from `GET /api/search`
Invalid filter values are **rejected** rather than warned about.'
requestBody:
required: true
description: A JSON-RPC 2.0 request object, as defined by the MCP specification.
content:
application/json:
schema:
type: object
description: A JSON-RPC 2.0 request. See the MCP specification for the message shapes.
parameters:
- name: MCP-Protocol-Version
in: header
required: false
description: The protocol revision. Required on `2026-07-28`, where it must match `params._meta["io.modelcontextprotocol/protocolVersion"]`. A client on an earlier revision sends it after `initialize` from `2025-06-18` on, and not at all before; a request that names no version anywhere is served as one of theirs.
schema:
type: string
enum:
- '2026-07-28'
- '2025-11-25'
- '2025-06-18'
- '2025-03-26'
- '2024-11-05'
- name: Mcp-Method
in: header
required: false
description: The JSON-RPC method, repeated from the body so an intermediary can route without parsing it. Required on `2026-07-28`, where it must match `method`.
schema:
type: string
examples:
- tools/list
- tools/call
- resources/read
- name: Mcp-Name
in: header
required: false
description: The tool, resource or prompt being addressed, repeated from the body. Required on `2026-07-28` for `tools/call`, `resources/read` and `prompts/get`, and omitted otherwise. Must match the name in `params`.
schema:
type: string
examples:
- search
- fetch
- find_similar_jobs
- name: Accept
in: header
required: false
description: The streamable HTTP transport asks for both `application/json` and `text/event-stream`. Every answer here is JSON, so `application/json` alone, `*/*`, or no `Accept` at all is served too.
schema:
type: string
examples:
- application/json, text/event-stream
responses:
'200':
description: A JSON-RPC 2.0 response. A tool that rejects its arguments still answers `200`, with `isError` set in the result.
headers:
Cache-Control:
description: Always `no-store`. A POST is not cached at the edge, so every call reaches the origin.
schema:
type: string
examples:
- no-store
X-Cerulean-Api-Version:
description: The same contract version the search endpoint reports.
schema:
type: string
examples:
- '2'
content:
application/json:
schema:
type: object
description: A JSON-RPC 2.0 response. See the MCP specification for the result shapes.
'202':
description: Accepted, with no body — the request carried only notifications.
'400':
description: A malformed JSON-RPC message, an `Accept` that admits no JSON, an unsupported `MCP-Protocol-Version`, a `2026-07-28` request missing a header or a `params._meta` field, or a header that disagrees with the body.
'413':
description: The request body exceeded 64 KiB.
'415':
description: The `Content-Type` was not `application/json`.
get:
tags:
- MCP
summary: Not served — there is no stream to open
operationId: mcpGet
description: The server is stateless, so it has no server-sent event stream for a `GET` to open. Clients probe for one, and are answered at once.
responses:
'405':
$ref: '#/components/responses/McpMethodNotAllowed'
delete:
tags:
- MCP
summary: Not served — there is no session to end
operationId: mcpDelete
description: The server keeps no session, so there is none for a `DELETE` to end.
responses:
'405':
$ref: '#/components/responses/McpMethodNotAllowed'
components:
responses:
McpMethodNotAllowed:
description: Only `POST` is served on `/api/mcp`.
headers:
Allow:
description: Always `POST`.
schema:
type: string
examples:
- POST
Cache-Control:
description: Always `no-store`.
schema:
type: string
examples:
- no-store
content:
text/plain:
schema:
type: string
examples:
- 'Method not allowed: /api/mcp takes POST.'