2hr API
A fast, reliable API for publishing and managing job listings. Built for developers who value clarity, speed, and predictable behaviour.
Quick Start
Get your first API call running in under 2 minutes.
Fill in the registration form. After manual verification (1-2 business days) your Bearer token will be emailed to you. Tokens are account-scoped and never expire unless revoked.
curl -X POST https://api.2hr.pl/api/v1/companies \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"name":"Example Company Sp. z o.o.","website":"https://example-company.com"}'
curl -X POST https://api.2hr.pl/api/v1/jobs \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "title": "Senior PHP Developer", "location": "Warszawa", "snippet": "We are looking for...", "link": "https://example-company.com/jobs/php", "company_uuid": "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", "salary": { "type": "monthly", "min": 12000, "max": 18000, "currency": "PLN" } }'
Get API Token
Tokens are issued after manual verification. Fill in the registration form - we review your details and send the token by email within 1-2 business days.
What to fill in
The form asks for basic contact information. Select your account type -the required fields differ:
| Field | Individual | Company | Notes |
|---|---|---|---|
| Name | ✓ required | ✓ required | Full name or company name, max 255 characters. |
| ✓ required | ✓ required | Token is delivered to this address. | |
| NIP / Tax Number | - | ✓ required | Tax identifier in any country format. Accepted characters: letters, digits, spaces, hyphens, dots and slashes -separators are stripped before storage. 2-30 characters after stripping. Examples: 5260250274 (PL), GB 123 456 789 (UK), B-58378431 (ES), FR 12 345 678 901 (FR), DE123456789 (DE). |
| Street address | - | ✓ required | Street and building / apartment number. |
| Postal code | - | ✓ required | |
| City | - | ✓ required | |
| Country | - | ✓ required |
How the token is delivered
After your account is verified, a 64-character hex token (256-bit entropy) is generated and sent to your email address. Only the SHA-256 hash is stored on the server - the raw token is never saved and cannot be recovered. If you lose it, contact us to issue a new one.
Using the token
Authorization: Bearer <your_token>
Re-registration & rate limiting
Submitting the form with an existing email updates your profile data - it does not generate a new token. To get a new token contact us directly. The registration endpoint is rate-limited per IP to prevent abuse.
Base URL
All requests and responses use application/json. Always set the Content-Type header on POST and PUT requests.
The API is only available over HTTPS. HTTP requests will be refused. All data in transit is encrypted.
Authentication
Every API request must include a valid Bearer token in the Authorization header.
Bearer Token
Authorization: Bearer <your_token>
Authentication Errors
| Scenario | HTTP Status | Response |
|---|---|---|
| No Authorization header | 401 | {"error":"Unauthorized","message":"Bearer token required."} |
| Token not recognised | 401 | {"error":"Unauthorized","message":"Invalid token."} |
Rate Limiting
Each token is limited to 10 requests per minute using a fixed 60-second window.
10 requests per token per 60-second window.
Fixed window -60 seconds. The counter resets at the start of each window.
429 Response
{ "error": "Too Many Requests", "retry_after": 42 // seconds until the window resets }
Endpoints
All endpoints are under /api/v1/ and require a valid Bearer token.
Companies
Companies represent the hiring organisations behind job listings. Create a company first, then attach it to jobs via company_uuid.
Returns all companies owned by your token's account, ordered by creation date descending.
{ "data": [ { "uuid": "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", "name": "Example Company Sp. z o.o.", "description": "Tech company based in Warsaw.", "website": "https://example-company.com", "logo_url": "https://example-company.com/logo.png", "created_at": "2026-02-28T10:00:00+00:00", "updated_at": "2026-02-28T10:00:00+00:00" } ], "meta": { "total": 1 } }
curl https://api.2hr.pl/api/v1/companies \ -H "Authorization: Bearer YOUR_TOKEN"
Request Body
| Field | Type | Description |
|---|---|---|
| name required | string | Company name. Max 255 characters. |
| description optional | string | Short company description. |
| website optional | string (URL) | Company website URL. Max 500 characters. |
| logo_url optional | string (URL) | Logo image URL. Max 500 characters. |
{ "name": "Example Company Sp. z o.o.", "description": "Tech company based in Warsaw.", "website": "https://example-company.com", "logo_url": "https://example-company.com/logo.png" }
{ "data": { "uuid": "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", "name": "Example Company Sp. z o.o.", "description": "Tech company based in Warsaw.", "website": "https://example-company.com", "logo_url": "https://example-company.com/logo.png", "created_at": "2026-02-28T10:00:00+00:00", "updated_at": "2026-02-28T10:00:00+00:00" } }
curl -X POST https://api.2hr.pl/api/v1/companies \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"name":"Example Company Sp. z o.o.","website":"https://example-company.com"}'
Updates the company details. All fields are sent -omitted optional fields are set to null.
{ "name": "Example Company S.A.", "website": "https://example-company.com" }
{ "data": { "uuid": "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", "name": "Example Company S.A.", "description": null, "website": "https://example-company.com", "logo_url": null, "created_at": "2026-02-28T10:00:00+00:00", "updated_at": "2026-02-28T12:00:00+00:00" } }
curl -X PUT https://api.2hr.pl/api/v1/companies/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11 \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"name":"Example Company S.A.","website":"https://example-company.com"}'
Jobs
Returns all published jobs owned by your account, ordered by updated_at descending. Jobs pending moderation (requires_review) or rejected are not included.
{ "data": [ { "uuid": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "company": { "uuid": "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", "name": "Example Company Sp. z o.o.", "description": "Tech company based in Warsaw.", "website": "https://example-company.com", "logo_url": "https://example-company.com/logo.png", "created_at": "2026-02-28T10:00:00+00:00", "updated_at": "2026-02-28T10:00:00+00:00" }, "title": "Senior PHP Developer", "location": "Warszawa", "snippet": "We are looking for...", "salary": { "type": "monthly", "min": 12000, "max": 18000, "currency": "PLN" }, "link": "https://example-company.com/jobs/php", "status": "published", "created_at": "2026-02-28T10:00:00+00:00", "updated_at": "2026-02-28T10:05:00+00:00" } ], "meta": { "total": 1 } }
curl https://api.2hr.pl/api/v1/jobs \ -H "Authorization: Bearer YOUR_TOKEN"
Creates a new job listing. The request is accepted immediately and processed asynchronously -the job will be visible once processing completes (typically within seconds).
403 Forbidden. Contact us to get your account verified.Request Body
| Field | Type | Description |
|---|---|---|
| title required | string | Job title. Max 255 characters. |
| location required | string | Job location (city or remote). Max 255 characters. |
| snippet required | string | Short job description / teaser text. |
| link required | string (URL) | URL of the recruitment/application form. Max 500 characters. |
| salary required | object | Salary details. See Salary Object. |
| company_uuid optional | string (UUID v4) | UUID of the company to associate with this job. |
| source optional | string | Traffic source label (e.g. "linkedin"). |
| type optional | string | Employment type (e.g. "full-time", "contract"). |
{ "title": "Senior PHP Developer", "location": "Warszawa", "snippet": "We are looking for an experienced PHP developer...", "link": "https://example-company.com/jobs/php", "company_uuid": "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", "salary": { "type": "monthly", "min": 12000, "max": 18000, "currency": "PLN" }, "type": "full-time" }
{ "uuid": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "status": "pending" // becomes "published" once processed }
{ "error": "Field \"salary.min\" cannot be greater than \"salary.max\"." }
curl -X POST https://api.2hr.pl/api/v1/jobs \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "title": "Senior PHP Developer", "location": "Warszawa", "snippet": "We are looking for...", "link": "https://example-company.com/jobs/php", "company_uuid": "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", "salary": {"type":"monthly","min":12000,"max":18000,"currency":"PLN"} }'
Updates the content of an existing job. Accepts the same fields as POST. Only jobs owned by your account can be updated.
{ "title": "Lead PHP Developer", "location": "Warszawa / Remote", "snippet": "Updated description...", "link": "https://example-company.com/jobs/php", "salary": { "type": "monthly", "min": 15000, "max": 22000, "currency": "PLN" } }
{ "data": { "uuid": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "title": "Lead PHP Developer", "salary": { "type": "monthly", "min": 15000, "max": 22000, "currency": "PLN" }, "status": "published" } }
curl -X PUT https://api.2hr.pl/api/v1/jobs/f47ac10b-58cc-4372-a567-0e02b2c3d479 \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"title":"Lead PHP Developer","location":"Warszawa","snippet":"...","link":"https://example-company.com/jobs/php","salary":{"type":"monthly","min":15000,"max":22000,"currency":"PLN"}}'
Transitions a job to a new status. Only valid transitions are allowed -see Job Status.
Request Body
| Field | Type | Description |
|---|---|---|
| status required | string (enum) | Target status. One of: published, ended. |
{ "status": "ended" }
{ "data": { "uuid": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "status": "ended", "updated_at": "2026-02-28T12:00:00+00:00" } }
curl -X PUT https://api.2hr.pl/api/v1/jobs/f47ac10b-58cc-4372-a567-0e02b2c3d479/status \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"status":"ended"}'
Company Object
| Field | Type | Description |
|---|---|---|
| uuid | string (UUID v4) | Public unique identifier. Use this value in PUT requests. |
| name | string | Company name. Max 255 characters. |
| description | string | null | Optional short description. |
| website | string | null | Company website URL. |
| logo_url | string | null | Logo image URL. |
| created_at | string (ISO 8601) | Creation timestamp. |
| updated_at | string (ISO 8601) | Last update timestamp. |
Job Object
| Field | Type | Description |
|---|---|---|
| uuid | string (UUID v4) | Public unique identifier. Use this value in PUT requests. |
| company | object | null | Embedded company data (see Company Object), or null if no company is linked. |
| title | string | Job title. |
| location | string | Job location. |
| snippet | string | Short job description. |
| salary | object | Salary details. See Salary Object. |
| link | string | URL of the recruitment/application form. |
| source | string | null | Traffic source label. |
| type | string | null | Employment type. |
| status | string (enum) | Current status: pending, published, ended, failed, requires_review, rejected. |
| created_at | string (ISO 8601) | Creation timestamp. |
| updated_at | string (ISO 8601) | Last update timestamp. |
Salary Object
The salary field is required on all job create and update requests. It is an object with the following fields:
| Field | Type | Description |
|---|---|---|
| type | string (enum) | Pay period. One of: monthly, hourly. |
| min | integer | Minimum salary. Must be ≥ 0 and ≤ max. |
| max | integer | Maximum salary. Must be ≥ min. |
| currency | string | ISO 4217 currency code, 3 uppercase letters (e.g. PLN, EUR, USD). |
Examples
{ "type": "monthly", "min": 12000, "max": 18000, "currency": "PLN" }
{ "type": "hourly", "min": 100, "max": 160, "currency": "PLN" }
min cannot be greater than max. Currency must be exactly 3 uppercase letters (ISO 4217). Lowercase or mixed-case (e.g. pln) will be rejected with 422.Job Status & Lifecycle
Jobs follow a strict state machine. Invalid transitions are rejected with HTTP 422.
Error Codes
All errors return a consistent JSON body. The error field is always present.
{ "error": "Descriptive error", // always present "message": "More detail" // auth errors }
| HTTP Status | Meaning | When it happens |
|---|---|---|
| 200 OK | Success | GET and synchronous PUT requests completed successfully. |
| 201 Created | Created | Company created successfully. |
| 202 Accepted | Accepted | Job creation accepted for async processing. |
| 400 Bad Request | Malformed request | Request body is not valid JSON or missing Content-Type header. |
| 401 Unauthorized | Authentication failed | Missing, malformed, or invalid Bearer token. |
| 403 Forbidden | Access denied | Resource exists but belongs to a different account. |
| 404 Not Found | Resource not found | Job or company does not exist. |
| 422 Unprocessable | Validation failed | Missing field, invalid URL, invalid salary (min > max, bad currency), illegal status transition. |
| 429 Too Many Requests | Rate limit exceeded | More than 10 requests in the current 60-second window. |
| 500 Internal Error | Server error | Unexpected server-side failure. Retry after a short delay. |
Async Processing
Job creation (POST /api/v1/jobs) uses asynchronous processing. Your request returns immediately with HTTP 202 while the job is processed in the background.
You get the job UUID back with status pending. Poll GET /api/v1/jobs to check when it becomes published.
Within seconds. Status transitions from pending → published automatically once processing completes.
OpenAPI Specification
The full API is described in an OpenAPI 3.1 spec compatible with Postman, Insomnia, and Swagger Editor.
Open Postman → Import → Link → paste the URL below.
https://api.2hr.pl/docs/openapi.yaml
Open Insomnia → Import → From URL → paste the URL below.
https://api.2hr.pl/docs/openapi.yaml
Swagger Editor
Swagger Editor cannot load the spec via URL due to browser CORS restrictions. Download the file first and paste the contents into the editor.
https://api.2hr.pl/docs/openapi.yaml
Press Ctrl+A then Ctrl+C.
Go to editor.swagger.io and paste (Ctrl+V) into the left panel.