Developers
The search behind Cerulean is open to everyone. No key, no token, no application form.
The Search API
/api/search is the endpoint our own search page calls. It is public, it needs
no authentication, and it is not rate limited. If you are building a job
aggregator, a research project, or a browser extension that wants to know what
the luxury houses are hiring for, this is the front door.
There is no database behind it. The entire catalogue lives in memory on regional servers in Singapore, Virginia, Oregon and Paris, and each request is answered by whichever is nearest. A typical query is served in a few milliseconds.
GET https://ceruleanjobs.com/api/search?query=atelier&location=paris
The machine-readable definition of everything below is published as an OpenAPI 3.1 document. Point your client generator at it rather than transcribing this page by hand.
If you are pointing an AI agent at Cerulean rather than writing a client by
hand, there are two shorter routes. /llms.txt
is a Markdown index of the site, in the llms.txt convention, that opens with
this endpoint and the OpenAPI document above. And /api/mcp is the same search
engine as a Model Context Protocol server, which most agents can connect to
without being told anything else — see The MCP server.
A first request
With no parameters at all, you get the most recent listings — every parameter below is optional.
curl "https://ceruleanjobs.com/api/search?query=atelier&location=paris&limit=2"
Parameters
Every parameter is optional, including limit and offset. There is no
required parameter and no minimum query — omitting a filter simply means no
constraint on that field, and omitting limit returns 12 results.
Every example below is a live link — click it to see the JSON in your browser.
Searching
| Parameter | Description | Example |
|---|---|---|
query | Free-text relevance search across title, description, qualifications, responsibilities, skills, experience, culture and benefits. Titles are weighted most heavily. | atelier+manager |
slug | The slug of a single listing. Returns that one listing, so a url you followed leads back to structured data. It is a filter like any other, not a bypass — combine it with a brand that does not match and you get nothing. | chanel-tailoring-atelier-manager-dubai-2026-03-16 |
similar | The slug of a listing. Switches to similarity mode — see below. | chanel-tailoring-atelier-manager-dubai-2026-03-16 |
site | The localised site to answer as: en, fr-fr, fr-ch, de-ch, en-ca, fr-ca, de-de, en-gb, es-es, it-it. Selects the response language and the site localizedUrl points at. It is not a filter — it does not restrict results to that country. An unrecognised value behaves as an absent one. | fr-fr |
lang | Deprecated — use site. A translation language alone: fr, de, es or it returns that language’s titles and descriptions where a translation exists. Anything else is English. It still wins over site for the text, but reaches localizedUrl only for a language naming exactly one site — es (es-es) and it (it-it) today, never de (de-ch and de-de) or fr (fr-fr, fr-ch and fr-ca). Send site for a localised link. | fr |
Filtering
location, country and brand take names as people write them. The other
four take a fixed set of values, listed below.
| Parameter | Description | Example |
|---|---|---|
location | Matches a city, region or country — whichever the value names, in any common spelling: Genève or geneva, NYC, UAE. | paris — a citylombardy — a regionfrance — a country |
country | Country only, by name or ISO code — italy, US, Deutschland. Narrower than location, and combinable with it. | italy |
brand | A brand, by slug or by name — chanel, Tiffany & Co., LV. See all brands. | chanel |
employment-type | One of nine values — see below. | full-time |
seniority-level | One of ten values — see below. | senior |
industry | One of seventeen values — see below. | high-jewelry |
department | One of twenty-six values — see below. | creative-design |
Paging
| Parameter | Default | Maximum | Example |
|---|---|---|---|
limit | 12 | 48 | 48 |
offset | 0 | 10,000 | 24 |
Omit limit and you get 12 results, the same page size the site’s own search
uses; omit offset and you start at the first result.
Paging is native to the index, so a request at offset 9,000 costs no more than
one at offset 0. A large limit is cheaper than several small ones — please
prefer it.
Filter values
employment-type, seniority-level, industry and department each accept
a fixed set of values. Anything outside these sets is not an error — it simply
matches nothing. Every value below is a live link.
employment-type
| Value |
|---|
full-time |
part-time |
contract |
temporary |
seasonal |
internship |
volunteer † |
per-diem |
other |
seniority-level
| Value |
|---|
intern |
entry-level |
junior |
mid-level |
senior |
lead |
supervisor |
manager |
director |
executive |
industry
| Value | |
|---|---|
fashion-apparel-leather | Fashion, Apparel & Leather Goods |
watches-horology | Fine Watches & Horology |
high-jewelry | High Jewelry |
perfumes-cosmetics-beauty | Perfumes, Cosmetics & Prestige Beauty |
eyewear-vision-care | Eyewear & Vision Care |
wines-spirits-gastronomy | Wines, Spirits & Gastronomy |
luxury-hospitality-travel | Luxury Hospitality & Experiential Travel |
selective-retailing | Selective Retailing & Department Stores |
luxury-real-estate | Luxury Real Estate, Property & Development |
yachting-aviation-mobility | Yachting, Aviation & Luxury Mobility |
pre-owned-vintage-luxury | Pre-Owned, Vintage & Circular Luxury |
media-art-culture | Media, Art & Cultural Institutions |
luxury-automotive | Luxury Automotive |
luxury-furniture-homewares | Luxury Furniture & Homewares |
high-end-audio-electronics | High-End Audio & Consumer Electronics |
premium-sporting-equipment | Premium Sporting & Outdoor Equipment |
photography-optics | Photography & Precision Optics † |
department
| Value | |
|---|---|
retail-operations | Retail & Boutique Operations |
wholesale-b2b | Wholesale, Commercial & B2B Sales |
merchandising-buying | Merchandising, Buying & Planning |
ecommerce-digital | E-Commerce, Digital & Data Analytics |
clienteling-crm | Clienteling, CRM & VIP Relations |
creative-design | Creative, Design & Styling |
research-innovation | Research, Innovation & Product Development |
visual-merchandising-architecture | Visual Merchandising, Store Design & Architecture |
heritage-archives | Heritage, Patrimony & Archives |
manufacturing-artisanal | Manufacturing, Artisanal & Industrial Operations |
supply-chain-logistics | Supply Chain, Logistics & Inventory |
culinary-food-production | Culinary & Food Production |
food-beverage-service | Food & Beverage Service |
front-office-guest-experience | Front Office, Concierge & Guest Experience |
housekeeping-cleaning | Housekeeping, Cleaning & Sanitation |
spa-wellness | Spa, Wellness & Recreation |
engineering-facilities | Engineering, Facilities & Maintenance |
real-estate-asset-management | Real Estate, Development & Asset Management |
corporate-affairs-csr | Corporate Affairs, Sustainability & CSR |
human-resources | Human Resources, People & Culture |
finance-accounting | Finance, Accounting & Revenue Management |
it-technology | IT & Technology Systems |
medical-clinical | Medical, Clinical & Specialized Technical |
legal-compliance | Legal & Compliance |
executive-management | Executive & General Management |
other | Other |
† Valid values that currently have no open listings, so they return
total: 0.
brand, location and country are open-ended rather than fixed —
all brands and all locations show what is on file. A
value that names nothing on file matches nothing and says so, with an
unknown_value warning and up to five close spellings in suggestions.
How values are encoded
Use + or %20 for spaces: location=new+york and location=New%20York are
the same request. Case does not matter, and in brand, location and country
neither do accents or punctuation. Values are trimmed and truncated to 200
characters.
The CDN canonicalises the query string before the request reaches the service, so that equivalent requests share one cache entry: it drops any parameter not in the tables above, drops parameters with an empty value, collapses a repeated parameter to its first value, and lowercases the values it keeps. A parameter the endpoint does not read is therefore normally removed before it arrives, and produces no warning in production.
Nothing returns an error
The endpoint always answers 200. A filter that matches nothing gives you
total: 0, not a 404. A limit of banana falls back to 12. An offset of
five million is clamped to 10,000. This is deliberate — a search that finds
nothing is a valid answer to a valid question, and clients should not need
error handling for a typo in a query string.
But the endpoint tells you what it did. Anything ignored, clamped, cut short or
not recognised comes back in a warnings array beside the results, so you never
have to guess whether total: 0 is a typo of yours or an empty shelf of ours.
curl "https://ceruleanjobs.com/api/search?industry=fashion&limit=1"
{
"results": [], "total": 0, "hasMore": false, "limit": 1, "offset": 0,
"indexBuiltAt": "2026-08-20T04:12:37Z",
"warnings": [
{ "code": "unknown_enum_value", "parameter": "industry", "message": "\"fashion\" is not one of the 17 values industry accepts, so it matched nothing." }
]
}
industry=fashion is the classic one. It is close enough to look right and it
matches nothing, because the value the endpoint wants is
fashion-apparel-leather.
| Code | Raised when |
|---|---|
clamped | limit or offset was out of range, or was not a number at all. The value actually used is in the response’s own limit and offset fields. |
truncated | A parameter value was longer than 200 characters and was cut before searching. |
unknown_enum_value | A value outside the fixed set for employment-type, seniority-level, industry or department. It matched nothing. |
unknown_value | A brand, location or country that names nothing Cerulean has jobs under. It matched nothing. suggestions lists up to five values on file spelled close to it, where there are any — brand=chanell suggests chanel. |
unknown_parameter | A parameter this endpoint does not read. It was ignored. Up to ten are reported. Rarely seen in production: the CDN normally strips an unrecognised parameter before the request arrives. |
similar_not_found | The similar slug has no embedding, so results were not ranked by similarity. If you also sent brand, it was applied rather than ignored — the one case where brand is not suppressed in similarity mode. |
Each warning carries a code you can branch on, the parameter it concerns
where there is one, a message written for a human reading a log, and — on an
unknown_value with close spellings to offer — suggestions. The
array is absent entirely when there is nothing to report, which is the ordinary
case, and its order is fixed — the same request always produces the same bytes.
A warning is never an error. The status is still 200 and the results, if there
are any, are still there.
The response
This is a real response, from
?query=atelier&limit=1.
{
"results": [
{
"title": "Tailoring Atelier Manager",
"slug": "chanel-tailoring-atelier-manager-dubai-2026-03-16",
"url": "https://ceruleanjobs.com/jobs/chanel-tailoring-atelier-manager-dubai-2026-03-16/",
"description": "Chanel seeks a Tailoring Atelier Manager in Dubai to oversee couture tailoring operations, lead a team of artisans and ensure the highest standards of construction and client fit for bespoke pieces. The role combines technical leadership, atelier administration and cross-functional collaboration wit",
"city": "Dubai",
"country": "UAE",
"datePosted": "2026-03-16T12:00:29Z",
"paintingUrl": "https://ceruleanjobs.com/images/paintings/chanel-7.webp",
"brand": {
"slug": "chanel",
"name": "Chanel",
"url": "https://ceruleanjobs.com/careers/chanel/",
"logoUrl": "https://ceruleanjobs.com/images/logos/chanel.webp"
},
"region": "Dubai",
"employmentType": "other",
"seniorityLevel": "manager",
"industry": "",
"department": ""
}
],
"total": 451,
"hasMore": true,
"limit": 1,
"offset": 0,
"indexBuiltAt": "2026-08-20T04:12:37Z"
}
total is the number of listings matching your query across every page, not
the length of results.
url is absolute and stable. It is the English ceruleanjobs.com page for
the listing, it always resolves, and it is the link to put in your database and
show to a reader. There is nothing to prepend and no host to know. It is usually
where the page’s own canonical tag points too; for a listing in a country with
an English-language Cerulean site — Canada, the UK — that tag names the country
site’s copy, which site=en-ca or site=en-gb returns as localizedUrl.
localizedUrl sits beside it, and only appears when a localised page for that
listing genuinely exists: the site you asked for is not en, and the listing
is in the country that site serves. A French listing asked for with
site=fr-fr comes back with its cerulean.fr/emplois/ address as well as its
ceruleanjobs.com one; a Swiss one asked for with site=de-ch comes back with
ceruleanjobs.ch/de/stellen/. Where there is no localised page there is no key,
so localizedUrl || url is always the right link to follow.
slug is the stable identifier. It is the listing’s primary key, it is what you
pass back to slug and similar, and it survives changes to the URL scheme —
which is why it is a field of its own rather than something you cut out of a
url with a regular expression.
description is a plain-text excerpt truncated to 300 characters, and is absent
when a listing has none. Fields we could not classify come back as empty strings
rather than being omitted — industry and department above are unclassified,
not missing.
Only seven keys can be absent altogether: description, localizedUrl,
datePosted, paintingUrl, brand.logoUrl, indexBuiltAt and warnings.
Everything else is always present, and results is always an array, even when
it is empty.
Filter values round-trip
employmentType, seniorityLevel, industry and department come back as the
same slugs you filter with — full-time, manager, fashion-apparel-leather,
ecommerce-digital. They are not display labels, and this page and the OpenAPI
document used to say otherwise. They were wrong; the API was always right.
The practical consequence is that any value in a result can be sent straight back in as a parameter.
curl "https://ceruleanjobs.com/api/search?industry=fashion-apparel-leather&department=ecommerce-digital"
brand.slug round-trips into brand, and slug into slug or similar, on
the same principle. So “more like this one, but only the internships” needs no
lookup table at your end — read the fields off a result and resend them.
city, region and country are the one presentational exception: they come
back title-cased for reading. location and country fold case before
matching, so those round-trip anyway.
Similar jobs
Pass a slug as similar and the ranking changes entirely. Instead of
matching words, it ranks by meaning and geography — semantic similarity against
a 512-dimension embedding, how close the location is, and how recent the
listing is.
curl "https://ceruleanjobs.com/api/search?similar=chanel-tailoring-atelier-manager-dubai-2026-03-16"
The subject job is excluded from its own results, so total needs no
adjustment. Any brand filter is ignored, because the point is to cross
between houses. If the slug is unknown, or too new to have an embedding, the
request quietly falls back to an ordinary recency-ranked search rather than
failing — and in that one case the brand filter is applied after all, with a
similar_not_found warning to tell you so.
The MCP server
Everything above describes a REST endpoint you call from code you write. If what
you are building is an agent, or you would rather ask an assistant than write a
client, there is a second door onto the same index: /api/mcp, a
Model Context Protocol
server. No key, no token, no sign-up — the same as the REST endpoint, and for the
same reason.
https://ceruleanjobs.com/api/mcp
Connecting
The steps for Claude and ChatGPT are also written for job seekers, on Use Cerulean in ChatGPT and Claude.
- Claude — Customize → Connectors → + → Add custom connector, on the web or in the desktop app. Name it Cerulean, paste the URL and press Add; there is nothing to sign in to, and the connector follows you to the mobile apps. On a Team or Enterprise plan an owner adds it under Organization settings → Connectors, and a Free plan allows one custom connector. In a chat, switch it on under + → Connectors.
- Claude Code —
claude mcp add --transport http cerulean https://ceruleanjobs.com/api/mcp - ChatGPT — on the web, on a paid plan (OpenAI’s pages differ on which). Turn on Developer mode, under Settings → Security and login or Settings → Apps → Advanced settings, then at chatgpt.com/plugins press +, give the URL with No Authentication, and create it. It is private to you. In a chat, pick it under + → Developer mode, or @-mention it.
- The ChatGPT desktop app and Codex — Settings → MCP servers → Add server,
Streamable HTTP, the URL; or
codex mcp add cerulean --url https://ceruleanjobs.com/api/mcp. - Cursor — Add to Cursor,
or
"cerulean": { "url": "https://ceruleanjobs.com/api/mcp" }undermcpServersin~/.cursor/mcp.json. - VS Code — Install in VS Code.
- Anything else — most clients take an entry like this:
{
"mcpServers": {
"cerulean": {
"type": "http",
"url": "https://ceruleanjobs.com/api/mcp"
}
}
}
The tools
Three, all read-only — readOnlyHint and idempotentHint true,
destructiveHint and openWorldHint false — with their input and output
schemas in tools/list.
| Tool | Arguments | Returns |
|---|---|---|
search | query, brand, location, country, employment_type, seniority_level, industry, department, locale, posted_within_days, limit, offset — all optional | A page of listings, each with an id, title, url and short text, and total, the number of matches |
fetch | id (required) — a listing’s id, or its URL on any Cerulean site — and locale | The whole listing: responsibilities, qualifications, skills, experience, education, pay where the employer states it, benefits and languages |
find_similar_jobs | id (required), location, country, locale, limit, offset | The open roles closest to that listing, by what the job is and where it is |
querytakes keywords or the request as a sentence: “senior client advisor, Cartier, Geneva”. A brand, place, country or employment type it names is applied as that filter, unless the filter is set, andinterpretedsays how it was read. Leave it out to browse by filters alone.brand,locationandcountrytake names as people write them, as on/api/search, with the sameunknown_valuewarning.localeis the user’s language and region as a BCP 47 tag —fr-CH,en-US. It sets the language of titles, descriptions andfetch’s text, and addslocalizedUrlwhere that country’s Cerulean site carries the listing; it never filters. It is negotiated likeAccept-Language: a tag naming one of the ten sites takes it, any other takes its language’s site (fr-BEreads asfr-FR), and another language reads as English. The tools have nositeorlang.posted_within_dayskeeps the listings Cerulean first saw in the last N days: 1 for today’s, up to 365.limitis 12 by default and 20 at most; more is served as 20, with aclampedwarning.- The four taxonomy filters are enumerations in the schema, the one place the
tools behave differently from
/api/search: a value outside its vocabulary is refused, with the valid values named, where the REST endpoint answerstotal: 0and anunknown_enum_valuewarning. A model can correct itself from a refusal; an empty shelf it has to interpret.limitandoffsetare still clamped rather than refused. - An unknown
idis an error.find_similar_jobsnever substitutes: a listing with no similarity data is an error too, where/api/searchfalls back to a plain search. searchandfetchfollow OpenAI’s search/fetch standard — results carryid,titleandurl, andfetchreturns{id, title, text, url, metadata}— so ChatGPT deep research can use them.
Every link the tools return — url, localizedUrl, and the one that ends
fetch’s text — carries utm_medium=mcp, which is how we tell a visit through
the tools from an assistant citing the page on its own; it is the same page. url
is the listing’s page on Cerulean, where candidates read it in full and apply.
The tools never hand out the employer’s own application link.
Cards
In Claude and ChatGPT the results also appear as cards beside the answer:
numbered listings for search and find_similar_jobs, and the whole listing for
fetch, each linking to its page. They are an MCP Apps page,
ui://cerulean/jobs.html, which every tool names. A client that does not render
MCP Apps ignores it, and the model reads the same text either way.
Things to ask
- “Find senior client advisor roles at Cartier in Geneva.”
- “Which watch houses are hiring in Switzerland this week?”
- “Tell me about the second one: what does it ask for, and is the pay stated?”
- “Show me roles like that one in Paris.”
- “Trouve-moi des postes en marketing chez Hermès à Paris.”
The protocol
The server is cerulean-jobs, version 3.0.0, versioned apart from the REST API.
It speaks MCP revision 2026-07-28, which removed protocol-level sessions: no
handshake before the first call, no session identifier, no connection to keep
open. A tool call is one HTTP POST and one complete answer, and the server keeps
no session between calls, so nothing can expire under you. Clients on the
earlier revisions — 2024-11-05, 2025-03-26, 2025-06-18 and 2025-11-25 —
open with initialize as they always have, and the server answers each of their
requests on its own too. GET and DELETE answer 405 with Allow: POST:
there is no stream to open and no session to end.
On 2026-07-28 every request describes itself, and the server rejects one where
the copies disagree:
- the headers
MCP-Protocol-Version,Mcp-Method— andMcp-Namefortools/call— alongsideContent-Type: application/json; protocolVersion,clientInfoandclientCapabilitiesinsideparams._meta.
A client library handles all of it. If you are debugging rather than building,
the exact shape is in
the OpenAPI document under
/api/mcp, and the server also publishes that document to connected clients as
a resource.
How results are ranked
An ordinary search blends three signals: text relevance, how recently the listing appeared weighted by the standing of the house, and how close the listing sits to your query in meaning. Similarity mode drops text relevance and leans on meaning and location instead.
The practical consequence is that recency matters. A perfect keyword match from three months ago will sit below a good match from this morning.
Freshness and caching
The index is rebuilt roughly every four hours, so results can trail the site by up to one cycle. Responses are cached at the edge for an hour.
You do not have to guess which cycle you got. Every response carries
indexBuiltAt, an RFC 3339 timestamp of the index build that answered it.
"indexBuiltAt": "2026-08-20T04:12:37Z"
If you are diffing our data against your own, that is the number to compare —
not the time you made the request, which the edge cache can put an hour ahead of
it. Two identical requests inside the same cached hour report the same
indexBuiltAt, and a change in that value is the honest signal that there is
something new to fetch.
Terms and attribution
The listings belong to the houses that are hiring. We gather them, translate and classify them, and keep them current. We do not own them.
What the Search API and the MCP server return may be used for personal,
editorial and commercial purposes alike. Search it, cache it, quote it, and
build a study, a tool, an agent or an app on top of it — on two conditions.
Credit Cerulean wherever the data is shown. And link each listing to its url,
its page on Cerulean, where a reader can apply. That link is the reason the
endpoint is open at all.
One caution, and it matters more than the rest. Results are a snapshot of an index rebuilt every four hours, and listings go stale quickly. A closed listing keeps its page for ninety days and is then gone, so a copy you refresh monthly will be showing jobs nobody can apply for. Re-query rather than republish an old snapshot.
Being a good citizen
Crawlers are welcome here — we have deliberately left the endpoint open. Two requests in return.
Send a User-Agent that identifies your project and gives us a way to reach
you, so we can get in touch if something goes wrong at your end or ours. And
ask for large pages rather than many small ones; limit=48 costs us no more
than limit=1.
Two more things, each of which will save you requests.
paintingUrl returns the full-size painting, 1024 square. Insert a size segment
for a smaller one — 256, 512 or 768:
https://ceruleanjobs.com/images/paintings/chanel-7.webp
https://ceruleanjobs.com/images/paintings/256/chanel-7.webp
And sitemap-index.xml is the
route to the whole corpus if you truly want every page. If what you want is the
data, though, limit=48 and offset will get you there in a small fraction of
the requests.
If you are building something substantial, we would genuinely like to hear about it. Write to us via the about page.
Changelog
Every response carries X-Cerulean-Api-Version, which reads 2 today. There is
no key and no sign-up, so there is no list of you for us to email — this section
is the notification. If you depend on this endpoint, watch this page.
Additive changes ship whenever they are ready: a new field, a new parameter, a new warning code, a new filter value. Ignore what you do not recognise and nothing of yours breaks.
Breaking changes get a version bump and an entry here. Removing a field, renaming one, or changing what one means.
2.2.0 — 24 September 2026
brand,locationandcountrytake names as people write them —Tiffany & Co.,LV,Genève,NYC,US— as well as slugs, and a place is found under every spelling it is filed under.unknown_valueis a new warning code, for abrand,locationorcountrythat names nothing on file. It carries a new, optional field:suggestions.- The MCP server is version 3.0.0. Its tools are
search,fetchandfind_similar_jobs:search_jobsbecamesearch, andget_jobbecamefetch, which returns the whole listing. They takeidandlocalewhere they tookslugandlang, and return shapes of their own rather than the search response.GETandDELETEon/api/mcpanswer405. See The MCP server. X-Cerulean-Api-Versionstill reads2: nothing in/api/searchwas removed or renamed.
2.1.0 — 21 August 2026
/api/mcpis new: a Model Context Protocol server over the same index, described in The MCP server. Nothing about/api/searchchanged, andX-Cerulean-Api-Versionstill reads2.- The OpenAPI document gains an
/api/mcppath covering the HTTP envelope the protocol requires.
2.0.0 — 20 August 2026
urlandpaintingUrlare now absolutehttps://ceruleanjobs.com/…links. They were paths, and every client had to know the host to use them.brand.hasLogo, a boolean, is gone.brand.logoUrlreplaces it and carries the same present-or-absent fact along with the address.brand.urlis new: the house’s careers page on Cerulean.localizedUrlis new, and appears when the listing has a page on a localised site — thencerulean.fralone, selected withlang=fr. Usesite=for it now:lang=frno longer names a single site.indexBuiltAtis new: when the index that answered you was built.warningsis new: what the endpoint clamped, cut, ignored or did not recognise.slugis new as a query parameter — the exact lookup of a single listing.datePostedis omitted when we do not know it. It used to read1970-01-01T00:00:00Z.resultsis now always an array. A degraded response used to sendnull, which broke clients that called.lengthon it.- Two new response headers:
X-Cerulean-Api-Version, and aLinkheader pointing at the OpenAPI document. - The documented values of
employmentType,seniorityLevel,industryanddepartmentare corrected. This page and the OpenAPI document showed display labels such asFashion; the API has always returned filter slugs such asfashion-apparel-leather. Only the documentation changed — see filter values round-trip.