Getting a key
On your board's dashboard, under Set up, open API keys. The board owner names a key, chooses whether it reads the board or reads and changes it, and is shown the key once. Copy it then; it is kept only as a digest and cannot be shown again. A key opens that one board and nothing else, and can be revoked on the same screen at any time, which stops it straight away.
A board has at most five keys. Keys are on the Growth plan; the public listing below needs none.
Authentication
Send the key in the Authorization header of every call:
curl https://jobvito.com/api/v1/board \
-H "Authorization: Bearer jv_live_…"A key that is wrong, revoked or missing is answered 401. A good key that cannot do what was asked, because it only reads, because the board is off the Growth plan, or because the board is not live, is answered 403 and told which.
Keep the key out of browsers and out of anything you publish. The keyed routes send no CORS headers on purpose.
Requests and answers
Every route is under https://jobvito.com/api/v1 and speaks JSON: send application/json, at most 64 KB, and read application/json back. An answer carries the thing asked for under data. A refusal is an application/problem+json body with a status, a short title, the detail the database gave, a code from the list below, and a request_id that identifies the call if you need to write to us.
Every answer carries X-Request-Id. Send your own X-Request-Id and it is echoed back.
Rate limits
Each key can make 120 calls a minute. Every answer says where the key stands in RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset (seconds until the minute turns). Over the cap, a call is answered 429 with Retry-After, and is not counted.
The cap is per key, so a board's keys do not share one. Read calls and write calls count the same.
Paging
Lists come newest first, 50 at a time unless limit says otherwise (up to 100). When there is more, the answer carries next_cursor and a Link header with rel="next"; pass the cursor back as ?cursor= for the next page. A cursor is opaque: keep it as given.
The public listing pages by number instead (?page= and ?limit=, up to 50), with the total in meta.
Jobs
A job is made with POST /jobs and a title, and anything else you have: its description, where it is, what it pays, how to apply, its category by name, and the company by id or by name (company_name finds the company on the board if it is there, and makes it if not). It arrives as a draft unless status says pending or published. Change its fields with PATCH; only the fields sent change, and a null clears one.
A job's status is changed by an action, not a patch: POST /jobs/{id}/publish, /draft, /archive or /pending. Publishing stamps published_at the first time and clears the review flag, just as the owner's Publish does. A job created or set to pending goes to the board's review queue and tells the owner, so a feed of your own usually creates as draft or published.
Give each job your own external_id. The board refuses a second job with the same one (409), so a create you retry after a timeout is safe.
What the API shows of a job is what the board shows: never who submitted it, the owner's notes, what the model thought of it, or an employer's attached files. A job's applications are not in this version of the API.
Companies
A company is an employer on the board: a name, and if you have them a description, a website and a location. Its logo is set on the board. A company's contact name and address are the owner's business, and are neither shown nor settable here.
Categories
The board's categories are a list of names, in the order the board shows them. Add one with POST /categories, rename one with PATCH /categories/{name}, remove one with DELETE /categories/{name} (with ?move_to= to carry its jobs into another first), and put them in a new order with PUT /categories. A rename or a merge reaches every job, subscription and page that held the old name, and the old address redirects.
The public listing
GET /boards/{slug}/jobs lists a board's published jobs for anyone, with no key, from any origin, cached for five minutes. It shows what the board's own listing shows: the title, category, type, location, pay, dates, where to apply, the job's address on the board, its description as plain text, and the company's name and logo. Filter with ?q=, ?location=, ?category=, ?type= and ?location_type=, sort with ?sort=newest or salary, and page with ?page= and ?limit=.
A board that is not live, or has opted out of search engines, answers 404.
Coming from the Only Chefs API
The fields are named as Jobvito names them. Where the old API said type, this says job_type; salary_interval is salary_period, and its values are year, month, week, day and hour; slug is gone, and url is the job's address. The page shape is the same: data and meta.
Changes
v1, October 2026: keys; the board; jobs, companies and categories; the public listing. Webhooks are planned.
Routes
Every path is under https://jobvito.com/api/v1.
GET /boards/{slug}/jobs
A board's published jobs. No key. No key. Answers on jobvito.com and on the board's own domain, with Access-Control-Allow-Origin: * and a five-minute cache. A board that is not live, or has opted out of search engines, answers 404.
slug(path, required): The board's slug, as in jobvito.com/b/{slug}.q(query): A word in the title.location(query): A word in the location.category(query): One or more names, comma-separated.type(query): One or more of full_time, part_time, contract, temporary, internship, fixed_term, apprenticeship, comma-separated.location_type(query): One or more of onsite, hybrid, remote, comma-separated.sort(query): one of newest, salarypage(query): From 1.limit(query): 1 to 50; 20 unless said.
Answers 200 with {data: [PublicJob], meta: {total, page, limit, total_pages}}.
curl https://jobvito.com/api/v1/boards/your-board/jobsGET /board
The board the key opens. Needs a key that can read.
Answers 200 with {data: Board}.
curl https://jobvito.com/api/v1/board \
-H "Authorization: Bearer jv_live_…"GET /jobs
The board's jobs, in any state. Needs a key that can read.
status(query): one of draft, pending, published, expired, archived, rejectedq(query): A word in the title.category(query)company_id(query)updated_since(query)cursor(query): From the previous answer's next_cursor.limit(query): 1 to 100; 50 unless said.
Answers 200 with {data: [Job], next_cursor}.
curl https://jobvito.com/api/v1/jobs \
-H "Authorization: Bearer jv_live_…"POST /jobs
Make a job. Needs a key that can write. A draft unless status says pending or published. Title is the one field a job needs.
Answers 201 with {data: Job}.
curl https://jobvito.com/api/v1/jobs \
-X POST \
-H "Authorization: Bearer jv_live_…" \
-H "Content-Type: application/json" \
-d '{"title": "Staff Nurse", "category": "Nursing", "company_name": "Leeds Teaching Hospitals", "salary_min": 30000, "salary_period": "year", "apply_url": "https://example.org/apply"}'GET /jobs/{id}
One job. Needs a key that can read.
id(path, required)
Answers 200 with {data: Job}.
curl https://jobvito.com/api/v1/jobs/{id} \
-H "Authorization: Bearer jv_live_…"PATCH /jobs/{id}
Change a job's fields. Needs a key that can write. Only the fields sent change. A null clears a field. Status is not a field here: use the actions.
id(path, required)
Answers 200 with {data: Job}.
curl https://jobvito.com/api/v1/jobs/{id} \
-X PATCH \
-H "Authorization: Bearer jv_live_…" \
-H "Content-Type: application/json" \
-d '{"title": "Staff Nurse", "category": "Nursing", "company_name": "Leeds Teaching Hospitals", "salary_min": 30000, "salary_period": "year", "apply_url": "https://example.org/apply"}'DELETE /jobs/{id}
Delete a job. Needs a key that can write. Its applications go with it, as when the owner deletes it.
id(path, required)
Answers 200 with {data: {id, deleted: true}}.
curl https://jobvito.com/api/v1/jobs/{id} \
-X DELETE \
-H "Authorization: Bearer jv_live_…"POST /jobs/{id}/{action}
Publish, archive, or set a job back to draft or pending. Needs a key that can write. Publishing stamps published_at the first time and clears the review flag, as the owner's Publish does. The same status again changes nothing.
id(path, required)action(path, required): one of publish, draft, archive, pending
Answers 200 with {data: Job}.
curl https://jobvito.com/api/v1/jobs/{id}/publish \
-X POST \
-H "Authorization: Bearer jv_live_…"GET /companies
The board's companies. Needs a key that can read.
q(query): A word in the name.cursor(query): From the previous answer's next_cursor.limit(query): 1 to 100; 50 unless said.
Answers 200 with {data: [Company], next_cursor}.
curl https://jobvito.com/api/v1/companies \
-H "Authorization: Bearer jv_live_…"POST /companies
Make a company. Needs a key that can write.
Answers 201 with {data: Company}.
curl https://jobvito.com/api/v1/companies \
-X POST \
-H "Authorization: Bearer jv_live_…" \
-H "Content-Type: application/json" \
-d '{"name": "Bakehouse Nine", "website": "https://bakehousenine.example"}'GET /companies/{id}
One company. Needs a key that can read.
id(path, required)
Answers 200 with {data: Company}.
curl https://jobvito.com/api/v1/companies/{id} \
-H "Authorization: Bearer jv_live_…"PATCH /companies/{id}
Change a company's fields. Needs a key that can write.
id(path, required)
Answers 200 with {data: Company}.
curl https://jobvito.com/api/v1/companies/{id} \
-X PATCH \
-H "Authorization: Bearer jv_live_…" \
-H "Content-Type: application/json" \
-d '{"name": "Bakehouse Nine", "website": "https://bakehousenine.example"}'GET /categories
The board's categories, in order. Needs a key that can read.
Answers 200 with {data: {categories: [string]}}.
curl https://jobvito.com/api/v1/categories \
-H "Authorization: Bearer jv_live_…"POST /categories
Add a category. Needs a key that can write.
Answers 201 with {data: {categories: [string]}}.
curl https://jobvito.com/api/v1/categories \
-X POST \
-H "Authorization: Bearer jv_live_…" \
-H "Content-Type: application/json" \
-d '{"name": "Pharmacy"}'PUT /categories
Put the categories in a new order. Needs a key that can write. The same names, each once.
Answers 200 with {data: {categories: [string]}}.
curl https://jobvito.com/api/v1/categories \
-X PUT \
-H "Authorization: Bearer jv_live_…" \
-H "Content-Type: application/json" \
-d '{"categories": ["Nursing", "Midwifery", "Pharmacy"]}'PATCH /categories/{name}
Rename a category. Needs a key that can write. Every job, subscription and page that held the old name takes the new one, and the old address redirects.
name(path, required)
Answers 200 with {data: {categories: [string]}}.
curl https://jobvito.com/api/v1/categories/Nursing \
-X PATCH \
-H "Authorization: Bearer jv_live_…" \
-H "Content-Type: application/json" \
-d '{"name": "Pharmacy"}'DELETE /categories/{name}
Remove a category. Needs a key that can write.
name(path, required)move_to(query): Another of the board's categories to move its jobs into first. Without it the jobs are left with none.
Answers 200 with {data: {categories: [string]}}.
curl https://jobvito.com/api/v1/categories/Nursing \
-X DELETE \
-H "Authorization: Bearer jv_live_…"Fields
Job
| Field | Type | Set by you | Notes |
|---|---|---|---|
id | string (uuid) | ||
title | string | Required | Up to 200 characters. |
status | string | One of draft, pending, published, expired, archived, rejected. On create only: draft (the default), pending or published. Changed afterwards with the actions. | |
description | string, or null | Yes | Up to 20,000 characters. Plain text or simple HTML (paragraphs, lists, headings, links). |
location | string, or null | Yes | Up to 200 characters. |
location_type | string, or null | Yes | One of onsite, hybrid, remote. |
job_type | string, or null | Yes | One of full_time, part_time, contract, temporary, internship, fixed_term, apprenticeship. |
category | string, or null | Yes | One of the board's categories, by name. |
salary_min | integer, or null | Yes | |
salary_max | integer, or null | Yes | Not below salary_min. |
salary_period | string, or null | Yes | One of year, month, week, day, hour. |
salary_currency | string | The board's currency: GBP, EUR or USD. | |
apply_url | string (uri), or null | Yes | Where applying takes the candidate. A job has apply_url or apply_email, not both; without either the board takes applications itself. |
apply_email | string (email), or null | Yes | A business address shown on the advert, never a person's. |
expires_at | string (date-time), or null | Yes | |
experience | string, or null | Yes | One of none, 0-1, 1-2, 2-3, 3-5, 5-7, 7-10, 10+. |
filters | object | Yes | The board's custom filters, as {key: [option, ...]}. Options the board does not know are dropped. |
featured | boolean | Yes | |
external_id | string, or null | Yes | Up to 200 characters. Your own id for the job. The board refuses a second job with the same one, so a retried create is safe. |
source | string | api for a job made here; manual, careers_page or import otherwise. | |
source_url | string (uri), or null | Yes | |
needs_review | boolean | ||
review_reason | string, or null | ||
published_at | string (date-time), or null | ||
created_at | string (date-time) | ||
updated_at | string (date-time) | ||
url | string (uri) | The job's page on the board. | |
company | object, or null | {id, name, slug, logo_url}. | |
company_id | string (uuid), or null | Yes | One of the board's companies. |
company_name | string | Yes | The company by name: found on the board if it is there, made if not. |
Company
| Field | Type | Set by you | Notes |
|---|---|---|---|
id | string (uuid) | ||
name | string | Required | Up to 200 characters. |
slug | string | Its address on the board, from its name. | |
description | string, or null | Yes | Up to 20,000 characters. |
website | string (uri), or null | Yes | |
location | string, or null | Yes | Up to 200 characters. |
logo_url | string (uri), or null | ||
verified | boolean | ||
created_at | string (date-time) | ||
updated_at | string (date-time) |
Errors
A refusal is application/problem+json: {type, title, status, detail, code, request_id}.
| Status | Code | When |
|---|---|---|
| 400 | invalid_parameter | A field is missing, unknown, the wrong shape, or the request is not JSON. |
| 401 | unauthorised | No key, a key nobody minted, or a revoked key. |
| 403 | forbidden | A good key that cannot do this: a read key on a write, a board off the Growth plan, or a board that is not live. |
| 404 | not_found | No job, company or category with that id or name on this board. |
| 405 | method_not_allowed | The method is not one this path answers. The routes below say which each takes. |
| 409 | conflict | A second job with the same external_id, or a second company or category with the same name. |
| 413 | too_large | The body is over 64 KB. |
| 415 | unsupported_media_type | The body is not application/json. |
| 422 | check_failed | A value the board cannot hold: a job type it has no word for, a top salary below the bottom. |
| 429 | rate_limited | More than the key's calls in a minute (120 unless the platform says otherwise). The RateLimit-Reset header says how many seconds until the next minute. |
| 500 | server_error | Something on our side. The request id in the body identifies the call. |
