---
name: jooble-ats-integration
description: Jooble ATS Integration API reference — endpoints, request/response schemas, authentication, webhooks, enums, and error handling. Use when building or modifying an integration that publishes jobs to Jooble or retrieves candidates.
---

# Jooble ATS Integration API Skill

Reference documentation for the Jooble ATS Integration API
(https://jooble.org). Use this skill when writing or reviewing code that
publishes/manages job listings on Jooble or retrieves candidates.

When you write code: use the language/framework the user specifies; otherwise
default to clean, production-ready examples with error handling. Never invent
endpoints, fields, or enum values that are not listed here.

## Overview

A REST API for applicant tracking systems to publish and manage job listings on
Jooble and to retrieve candidates. New applications can also be delivered to your
own endpoint in real time via webhooks.

## Minimum Required Flow

1. **Authenticate** — `POST /AtsJob/login` → get a JWT `access_token` (valid 24h).
2. **Create a job** — `POST /AtsJob/create` with the token → get the job `id`.
3. **Retrieve candidates** — `GET /AtsJob/appliesByJob/{jobId}`.
4. **(Optional) Receive applications in real time** — configure a webhook.

## Base URL

```
https://{countryCode}.jooble.org/employer/api/v2
```

Replace `{countryCode}` with the country subdomain (e.g. `ua`, `hu`, `ro`).

## Authentication

All requests except login require a JWT in the `Authorization` header.

- `POST /AtsJob/login` — Body `{ "email": string, "password": string }` (both required).
  Response `200`: `{ "access_token": string }` (JWT, expires after 24 hours).
- Send on every authenticated request:
  - `Authorization: Bearer {access_token}`
  - `Content-Type: application/json`
- `401 Unauthorized` means the token is missing or invalid → re-authenticate.

## Endpoints

### POST /AtsJob/create — create a job listing
Body fields:
- `title` (string, **required**, max 20 chars)
- `region` (string, **required**) — city/region
- `description` (string, **required**, min 100 chars; excess HTML tags auto-removed)
- `address` (string, optional)
- `salaryValue1` (integer ≥ 0, optional) — salary "from"
- `salaryValue2` (integer ≥ 0, optional) — salary "to", must be ≥ salaryValue1
- `salaryRateId` (sbyte, optional) — see Enums; **required if** salaryValue1 is set
- `jobType1` (int, optional) — see Enums
- `jobType2` (int, optional) — see Enums

Validation:
- salaryValue2 cannot be set without salaryValue1.
- If both present, salaryValue2 ≥ salaryValue1.
- salaryRateId required when salaryValue1 is set.
- jobType2 cannot be set without jobType1.

Response `200`: `{ "id": long, "dateCreated": datetime }`. The contact email is
taken from the authenticated account. The job is published immediately
(`Activated`); if the active job limit is exceeded it becomes `Deactivated`.
Errors: `400` (validation / subscription on approving), `403` (account not employer).

### PUT /AtsJob/update — update a job
Same fields as create plus `jobId` (long, **required**, > 0). Validation identical
to create. Response: `204 No Content`. Errors: `400`, `403` (job not owned).

### POST /AtsJob/changeStatus — change job status
Body: `{ "jobId": long (required, > 0), "jobStatus": int (required) }`.
`jobStatus`: `0` = Activated, `1` = Stopped, `2` = Deleted.
Response `200`: `{ "status": "Activated" | "Stopped" | "Deleted" }`. When activating,
the active job limit is checked. Errors: `400`, `403`, `404`.

### GET /AtsJob/{id} — get one job
Response `200` (job object): `id` (long), `status` (string), `title`, `region`,
`address`, `jobType1` (int?), `jobType2` (int?), `salaryValue1` (decimal?, null if 0),
`salaryValue2` (decimal?, null if 0), `salaryRateId` (sbyte?),
`description` (HTML string), `appliesCount` (int), `viewsCount` (int), `email` (string).
Errors: `403`, `404`.

### GET /AtsJob/allJobs — get all jobs for the employer
Optional query param `jobStatus` (string) — filter by status; if omitted returns all
non-deleted jobs. Response `200`: JSON array of job objects (same shape as Get Job).
Errors: `403`.

### GET /AtsJob/appliesByJob/{jobId} — get candidates for a job
Response `200`: JSON array of candidate objects. Each candidate:
`id` (long, "apply id"), `jobId` (long), `date` (datetime), `dateStatusChanged` (datetime),
`status` (string, see Candidate Status), `isNew`, `isViewed`, `isSuggested`, `isPremium`,
`withoutCv`, `hasProfileCV`, `hasProfile` (bool), `fileName` (string?), `hasMessages` (bool),
`messagesCount` (int), `newMessagesCount` (int), `lastMessageDate` (datetime?),
`appliesCount` (int), `cvFile` (object? — only if ATS integration enabled, else null).
- `applicant`: `name` (string), `age` (int?), `salary` (decimal?), `currency` (string?),
  `region` (string?), `gender` (string?, see Enums), `photoUrl` (string?), `isReadyForRelocate` (bool?).
- `applicant.contacts`: `phone` (string?), `email` (string?, may be masked), `isOpened` (bool),
  `openDate` (datetime?).
- `additionalQuestions`: `total` (int), `passed` (int).
- `cvFile`: `name` (string), `data` (string, base64), `type` (always "CV"),
  `contentType` (string, e.g. "application/pdf", "application/msword").
Errors: `403`.

### POST /AtsJob/{applyId}/markViewed — mark a candidate (apply) as viewed
`applyId` = the `id` field from appliesByJob. Response `200`, empty body. Idempotent:
repeated calls are safe. The `JobApplyViewed` event fires only on the first call.
Errors: `401`, `403`.

## Webhooks (inbound — Jooble calls YOUR endpoint)

On each new application Jooble sends a request to your configured URL/method
(`GET`, `POST`, or `PUT`). Headers:
- `Content-Type: application/json`
- `X-Webhook-Signature`: HMAC-SHA256 as `t={timestamp},v1={signature}` — verify it.

Body:
```json
{
  "candidate": {
    "first_name": "string (required)",
    "last_name": "string (required)",
    "email_address": "string? (defaults to user_without_email@noemail.com)",
    "phone_number": "string? (\"\" if unavailable)",
    "gender": "MALE | FEMALE | null",
    "location": { "city": "string?", "country": "string (required, ISO code uppercase)" }
  },
  "application": { "job_id": "string (required)", "source": "string? (omitted if null)" },
  "attachments": [
    { "name": "string", "data": "string (base64)", "type": "CV", "content_type": "string" }
  ]
}
```

## Enums

- **Job Status** (string): `Activated`, `Stopped`, `Deactivated`, `Deleted`, `PreActivated`.
- **Job Status** (int, for changeStatus): `0`=Activated, `1`=Stopped, `2`=Deleted.
- **Job Type** (`jobType1`/`jobType2`): `0`=Full-time, `1`=Part-time, `2`=Contract, `3`=Temporary, `4`=Remote, `5`=Internship.
- **Salary Rate** (`salaryRateId`): `0`=Per hour, `1`=Per day, `2`=Per week, `3`=Per month, `5`=Per year (value 4 is not used).
- **Gender**: `MALE`, `FEMALE`, or `null`.
- **Candidate Status**: `Received`, `Processing`, `Invited`, `Offer`, `Rejected`, `Deleted`.

## HTTP Status Codes

`200` OK, `204` No Content, `400` Bad Request, `401` Unauthorized, `403` Forbidden,
`404` Not Found, `500` Internal Server Error.

## Error Response Format

```json
{ "code": "string", "message": "string" }
```
Business error codes (in `400` responses):
- `JobsOverLimit` — active job limit exceeded for the account.
- `SubscriptionOnApproving` — subscription is being approved; publishing is blocked.

## Best Practices

- Cache the JWT and refresh it before the 24h expiry; handle `401` by re-logging in.
- Validate salary and job-type rules client-side before calling create/update.
- Always verify the webhook HMAC signature before trusting the payload.
- Treat `markViewed` as idempotent.
- Never log the `access_token` or candidate personal data.
