Documentation
Everything you need to integrate with the Open Job Board API.
The Open Job Board API is a free, open REST API for job listings. You can query thousands of jobs, perform full-text searches, and submit new listings. No sign-up is required for reading data.
Want to explore the API interactively? Try the API Explorer with built-in request testing.
Base URL
All endpoints share a single base URL. When self-hosting, replace with your own domain:
https://your-server.example.com
Interactive API documentation is available at /swagger-ui/.
Quick Start
Get up and running in under a minute. All read endpoints are public — no authentication required.
1. Search for jobs
Use full-text search to find relevant jobs:
curl -X POST "https://your-server.example.com/api/jobs/search" \
-H "Content-Type: application/json" \
-d '{
"query": "senior backend engineer",
"country": "Germany",
"remote": true,
"page_num": 1,
"page_size": 20
}'
2. List jobs
Paginated list of active jobs:
curl "https://your-server.example.com/api/jobs?page=1&page_size=20"
3. Submit a job
Post a new job listing (no auth required, but rate limited):
curl -X POST "https://your-server.example.com/api/jobs" \
-H "Content-Type: application/json" \
-d '{
"origin": { "source": "my-scraper", "reference": "job-123" },
"title": "Senior Backend Engineer",
"description": "We are looking for a backend engineer to join our platform team.",
"company": { "name": "Acme Corp", "sector": "Technology" },
"location": { "city": "Berlin", "country": "Germany", "remote": { "full": true } },
"employment_type": "full-time",
"salary": { "currency": "EUR", "min": 80000, "max": 120000, "period": "yearly" }
}'
Authentication
Read Endpoints (no auth)
All read endpoints (list, search, detail) are public and require no authentication.
API Key for Writing (optional)
Job submissions work without authentication but are rate limited to 10 requests per minute per IP address.
For higher rate limits (100 req/min by default), include an API key in the X-API-Key header:
curl -X POST "https://your-server.example.com/api/jobs" \
-H "Content-Type: application/json" \
-H "X-API-Key: ojb_your_api_key_here" \
-d '{ ... }'
You can create an API key instantly below.
Create API Key
Generate an API key for higher rate limits on job submissions. Your key is hashed server-side and never stored in plaintext. Save it immediately — it will only be shown once.
List Jobs
GET /api/jobs
Returns a paginated list of active job listings, ordered by posted date (newest first).
Example
curl "https://your-server.example.com/api/jobs?page=1&page_size=20"
Parameters
| Parameter | Type | Description |
|---|---|---|
page | integer | Page number (default 1) |
page_size | integer | Results per page (default 20, max 100) |
Response
{
"data": [
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"title": "Senior Backend Engineer",
"company_name": "Acme Corp",
"location_country": "Germany",
"location_city": "Berlin",
"remote_full": true,
"employment_type": "full-time",
"salary_currency": "EUR",
"salary_min": 80000,
"salary_max": 120000,
"salary_period": "yearly",
"posted_at": "2026-02-20T10:00:00Z"
}
],
"page": 1,
"page_size": 20,
"total_count": 150
}
Search Jobs
POST /api/jobs/search
Full-text search with relevance ranking. Results are scored by how well they match the query. The search is weighted: job title has the highest weight, followed by company name, description, and location.
Request Body
| Field | Type | Description |
|---|---|---|
query | string | Search query (natural language, quoted phrases) |
country | string | Filter by country |
city | string | Filter by city |
remote | boolean | Only fully remote jobs |
employment | string | Employment type (case-insensitive) |
salary_min_val | number | Minimum salary filter |
salary_max_val | number | Maximum salary filter |
source_filter | string | Filter by source name |
page_num | integer | Page number (default 1) |
page_size | integer | Results per page (default 20, max 100) |
Example
curl -X POST "https://your-server.example.com/api/jobs/search" \
-H "Content-Type: application/json" \
-d '{
"query": "senior backend engineer",
"country": "Germany",
"remote": true,
"salary_min_val": 80000,
"page_num": 1,
"page_size": 20
}'
Response
Returns a paginated envelope with job objects and a rank relevance score:
{
"data": [
{
"rank": 0.42,
"id": "a1b2c3d4-...",
"title": "Senior Backend Engineer",
"company_name": "Acme Corp",
...
}
],
"page": 1,
"page_size": 20,
"total_count": 42
}
Get Job Detail
GET /api/jobs/{id}
Returns the complete job record including full description, responsibilities, requirements, benefits, and all nested fields.
Example
curl "https://your-server.example.com/api/jobs/a1b2c3d4-e5f6-7890-abcd-ef1234567890"
Response
Returns the full job object. Includes all fields from the Job schema plus:
| Field | Type | Description |
|---|---|---|
description | string | Full job description |
responsibilities | string[] | List of responsibilities |
benefits | string[] | List of benefits |
requirements | object | Qualifications, hard/soft skills |
contact | object | Contact person (name, email, phone) |
company_anecdote | string | Brief company description |
company_locations | string[] | Physical office locations |
Submit a Job
POST /api/jobs
Submit a new job listing. Only origin.source, title, and description are required. All other fields are optional.
Deduplication: If a job with the same source + reference already exists, the API returns HTTP 409. Scrapers can safely re-submit without creating duplicates.
Required Fields
| Field | Type | Description |
|---|---|---|
origin.source | string | Unique name for the submitter (1-200 chars) |
title | string | Job title (2-500 chars) |
description | string | Job description (10-100,000 chars) |
Optional Fields
| Field | Type | Description |
|---|---|---|
origin.reference | string | Original job ID from source (for deduplication) |
origin.contact | object | Contact person: name, email, phone |
company | object | name, website, sector, anecdote, location[] |
employment_type | string | e.g. "full-time", "part-time", "contract" |
location | object | city, country, remote: {full, days} |
requirements | object | qualifications[], hard_skills[], soft_skills[], others[] |
salary | object | currency, min, max, period (hourly/daily/weekly/monthly/yearly) |
benefits | string[] | List of benefits |
responsibilities | string[] | List of responsibilities |
posted_at | datetime | When originally posted (ISO 8601) |
parsed_at | datetime | When parsed by scraper (ISO 8601) |
Full Example
curl -X POST "https://your-server.example.com/api/jobs" \
-H "Content-Type: application/json" \
-H "X-API-Key: ojb_your_key_here" \
-d '{
"origin": {
"source": "my-scraper",
"reference": "job-99999",
"contact": { "name": "Jane", "email": "jobs@acme.com" }
},
"title": "Senior Backend Engineer",
"description": "We are looking for a senior backend engineer to join our platform team and help us scale to millions of users.",
"responsibilities": [
"Design and build scalable REST APIs",
"Own and improve database performance"
],
"company": {
"name": "Acme Corp",
"website": "https://acme.com",
"sector": "Technology"
},
"employment_type": "full-time",
"location": {
"city": "Berlin",
"country": "Germany",
"remote": { "full": true }
},
"requirements": {
"hard_skills": ["Go", "PostgreSQL", "Kubernetes"],
"qualifications": ["5+ years backend experience"]
},
"salary": {
"currency": "EUR",
"min": 90000,
"max": 130000,
"period": "yearly"
},
"benefits": ["30 days vacation", "Health insurance"]
}'
Success Response (201)
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"created_at": "2026-02-27T12:00:00Z"
}
Filtering & Sorting
Use the POST /api/jobs/search endpoint for filtered and sorted queries. All filters are passed as JSON in the request body:
| Field | Type | Description |
|---|---|---|
query | string | Full-text search query |
country | string | Filter by country (case-insensitive) |
city | string | Filter by city (case-insensitive) |
remote | boolean | Only fully remote jobs |
employment | string | Employment type (case-insensitive) |
salary_min_val | number | Minimum salary filter |
salary_max_val | number | Maximum salary filter |
location | string | Location to geocode for proximity search |
latitude | number | Explicit latitude for proximity search |
longitude | number | Explicit longitude for proximity search |
max_distance_km | number | Radius in km for proximity search (default 50) |
Sorting
Results are sorted by relevance (when a query is provided), then by proximity (when using geo search), then by posted date (newest first).
Proximity Search
Provide coordinates or a location string to find jobs near a specific area:
{
"query": "engineer",
"location": "Berlin, Germany",
"max_distance_km": 100
}
Rate Limits
| Tier | Limit | Scope |
|---|---|---|
| Anonymous (no API key) | 10 requests/minute | Per IP address |
| With API key | 100 requests/minute | Per API key |
Rate limits apply to the submit-job endpoint only. Read endpoints (list, search, detail) are not rate limited.
When the limit is exceeded, the API returns 429 Too Many Requests with a Retry-After: 60 header.
If you provide an invalid API key, the request falls back to IP-based rate limiting silently.
Error Codes
| Code | Meaning | Details |
|---|---|---|
200 | Success | Request completed successfully |
201 | Created | Job submitted successfully |
400 | Bad Request | Invalid JSON body |
405 | Method Not Allowed | Wrong HTTP method |
409 | Conflict | Duplicate job (same source + reference) |
422 | Validation Error | Request body failed schema validation |
429 | Rate Limited | Too many requests, retry after 60s |
500 | Server Error | Internal server error |
Error Response Format
{
"error": "Validation failed",
"details": {
"fieldErrors": {
"title": ["String must contain at least 2 character(s)"]
},
"formErrors": []
}
}
Job Schema
Fields returned by the list and search endpoints:
| Field | Type | Description |
|---|---|---|
id | uuid | Unique job identifier |
title | string | Job title |
company_name | string? | Company name |
company_sector | string? | Company industry |
company_website | string? | Company website URL |
location_city | string? | Job city |
location_country | string? | Job country |
remote_full | boolean? | True if fully remote |
remote_days | integer? | Remote days per week (hybrid) |
employment_type | string? | e.g. full-time, part-time, contract |
salary_currency | string? | e.g. EUR, USD, GBP |
salary_min | number? | Minimum salary |
salary_max | number? | Maximum salary |
salary_period | string? | hourly, daily, weekly, monthly, yearly |
source | string | Name of the submitter |
reference | string? | Original job ID from source |
posted_at | datetime? | When originally posted |
created_at | datetime | When added to the database |
is_active | boolean | Whether the job is active |