Public Jobs API
Read-only access to a company's open job postings in The Talent App, for job boards, company websites, aggregators and partner systems.
- Read-only. Lists open jobs and returns one job's details. It cannot create, change or apply to jobs.
- Public data only. You get what the careers page shows. Salary, number of openings, internal notes, assessment settings and anything about candidates are never returned.
- Machine-readable spec.
GET <base URL>/openapi.jsonreturns an OpenAPI 3.1 document you can load into Postman, Swagger UI or a client generator.
Base URL
https://<your-supabase-host>/functions/v1/public-jobs
The hiring team you're integrating with gives you <your-supabase-host>. It is
the same host their careers site talks to, not the careers site itself.
Authentication
None is required by the API. Some deployments put a gateway in front of all functions that expects
the public (anon) key. If your first request returns 401, ask the hiring team
for their public key and send it on every request:
apikey: <public-key> Authorization: Bearer <public-key>
That key is designed to be public. Never ask for, or accept, a service-role key.
List open jobs
GET /public-jobs
Newest first. All query parameters are optional.
| Parameter | Type | Description |
|---|---|---|
limit | integer, 1–100 | Page size. Default 50. |
offset | integer, 0–10000 | Number of jobs to skip. Default 0. |
department | string | Exact match, e.g. Frontend. |
domain | string | Exact match, e.g. Engineering. |
location | string | Case-insensitive "contains" match, e.g. noida. |
employment_type | enum | full_time, part_time, contract, internship, freelance. |
experience_level | enum | entry, mid, senior, lead, executive. |
updated_since | ISO 8601 date or date-time | Only jobs updated at or after this time. Use it for incremental sync. |
Response 200:
{
"api_version": "v1",
"data": [
{
"id": "3f2b1c9e-8a7d-4e6f-9b0a-1c2d3e4f5a6b",
"title": "Senior React Developer",
"description": "You will build the hiring UI...",
"domain": "Engineering",
"department": "Frontend",
"location": "Noida, India",
"employment_type": "full_time",
"experience_level": "senior",
"experience_years": "5-8",
"skills": ["React", "TypeScript"],
"application_deadline": "2026-12-31",
"posted_at": "2026-10-01T10:00:00Z",
"updated_at": "2026-10-20T10:00:00Z",
"url": "https://careers.example.com/careers/3f2b1c9e-8a7d-4e6f-9b0a-1c2d3e4f5a6b",
"apply_url": "https://careers.example.com/careers/3f2b1c9e-8a7d-4e6f-9b0a-1c2d3e4f5a6b"
}
],
"pagination": { "limit": 50, "offset": 0, "total": 1, "has_more": false }
} Get one job
GET /public-jobs/{id} id is the job's UUID from the list. Returns
{ "api_version": "v1", "data": { ...job } }.
A job that is closed, paused, filled or deleted returns 404. Treat that as
"remove this listing".
Job fields
| Field | Type | Notes |
|---|---|---|
id | UUID | Stable for the life of the job. Use it as your external reference. |
title | string | |
description | string or null | Plain text; may contain line breaks. Escape it before inserting into HTML. |
domain | string or null | Broad area, e.g. Engineering. |
department | string or null | Team within the domain. |
location | string or null | Free text, e.g. "Noida, India" or "Remote". |
employment_type | enum or null | See the list parameters above. |
experience_level | enum or null | See the list parameters above. |
experience_years | string or null | Free text range, e.g. "3-5". |
skills | string[] | May be empty. |
application_deadline | date or null | YYYY-MM-DD. |
posted_at | date-time | When the job was created. |
updated_at | date-time | Last change to the posting. |
url | URL or null | Public job page on the careers site. null only if the deployment has no public app URL configured. |
apply_url | URL or null | Send candidates here to apply. Today the same as url. |
New fields may be added within v1, so ignore fields you don't recognise.
Removing or renaming a field means a new version.
Errors
Errors are JSON with an error message:
{ "error": "limit must be between 1 and 100" } | Status | Meaning |
|---|---|
400 | A parameter or job id is invalid. The message says which. |
401 | The deployment's gateway wants the public key (see Authentication). |
404 | Job not found or no longer open, or unknown path. |
405 | Only GET is supported. |
500 | Server error. The response includes a request_id to quote when reporting it. |
Caching, sync and fair use
- Responses can be cached for 5 minutes (
Cache-Control: public, max-age=300). Polling more often than that gains nothing. - Recommended sync: one full read (page through with
offsetuntilhas_moreisfalse), then poll every 15–60 minutes withupdated_sinceset to the time of your previous sync. Separately re-check jobs you hold, since a job that closes disappears from the list instead of appearing in it. - Keep requests modest: a full sync of a few pages per poll is fine; please avoid tight loops.
- The API sends
Access-Control-Allow-Origin: *, so a website can call it directly from the browser.
Examples
curl
curl "https://<your-supabase-host>/functions/v1/public-jobs?employment_type=full_time&limit=20"
JavaScript (browser or Node 18+)
const BASE = "https://<your-supabase-host>/functions/v1/public-jobs";
async function fetchAllOpenJobs() {
const jobs = [];
for (let offset = 0; ; offset += 100) {
const res = await fetch(`${BASE}?limit=100&offset=${offset}`);
if (!res.ok) throw new Error((await res.json()).error);
const page = await res.json();
jobs.push(...page.data);
if (!page.pagination.has_more) return jobs;
}
} Python
import requests
BASE = "https://<your-supabase-host>/functions/v1/public-jobs"
job = requests.get(f"{BASE}/3f2b1c9e-8a7d-4e6f-9b0a-1c2d3e4f5a6b", timeout=10)
if job.status_code == 404:
print("Job closed: remove the listing")
else:
job.raise_for_status()
print(job.json()["data"]["title"]) Google for Jobs
Each job page on the careers site (url) already includes
JobPosting structured data, so Google can list the jobs without using this API.
For deployment owners
If you self-host The Talent App, the API ships as the
public-jobs edge function.
- Deploy the function. Hosted Supabase:
supabase functions deploy public-jobs --no-verify-jwt. Self-hosted: copysupabase/functions/public-jobsandsupabase/functions/_sharedinto the functions volume and restart the functions service. - Set the public app URL so
urlandapply_urlare filled in:PUBLIC_APP_URLon the functions service, or the public URL in the business branding settings. - Gateway JWT checks.
supabase/config.tomldisables JWT verification for this function. On self-hosted stacks where the functions gateway verifies JWTs for every function, partners must send the public key (see Authentication). - Check it:
curl https://<your-supabase-host>/functions/v1/public-jobs?limit=1should return"api_version": "v1".
Questions
Does the Public Jobs API need an API key?
No. The API itself needs no authentication. Some self-hosted deployments put a gateway in front of all functions that expects the public (anon) key; in that case send it as the apikey and Authorization headers. Never use a service-role key.
Does the API expose salary or candidate data?
No. It returns only what the public careers page shows. Salary, number of openings, internal notes, assessment settings and anything about candidates are never returned.
How should a job board keep listings in sync?
Do one full read by paging with offset until has_more is false, then poll every 15–60 minutes with updated_since set to the time of the previous sync. Re-check jobs you already hold, because a job that closes disappears from the list and returns 404 on its detail endpoint.