Developers

Jobs

GET /api/v1/jobs returns the jobs in your workspace a page at a time, and GET /api/v1/jobs/{id} returns one, with every parameter and field.

Overview

GET /api/v1/jobs lists the jobs in your workspace, and GET /api/v1/jobs/{id} returns one of them. You get the jobs you can see in the app, minus any team your key's team access leaves out. Both need the Read permission.

Authentication, Errors and Rate limits cover what every operation shares. This page covers the parameters and the fields.

Request

curl "https://attia.app/api/v1/jobs?limit=50" \  -H "Authorization: Bearer $ATTIA_API_KEY"

All four query parameters are optional.

ParameterTypeDefaultMeaning
limitinteger, 1 to 10025How many jobs to return
cursorstringnoneThe next_cursor of the previous page
updated_sinceRFC 3339 timestampnoneOnly jobs updated at or after this time
include_archivedtrue or falsefalseAlso return archived jobs

A parameter outside this table gets a 400 with the code invalid-request. So does a parameter sent twice.

Response

The answer holds the jobs in data, sorted by updated_at and then id, oldest change first. next_cursor is a string when more jobs exist and null on the last page.

{  "data": [    {      "id": "0192f3a1-6b02-7d4c-8e11-5a9c0b7d3e01",      "title": "Backend developer",      "slug": "backend-developer",      "status": "live",      "publish_status": "published",      "team_id": "0192f39f-11aa-7c3e-b2d4-9e8f7a6b5c4d",      "employment_type": "full_time",      "contract_type": "permanent",      "workplace_type": "hybrid",      "locations": ["Eksempelvik"],      "positions_count": 2,      "application_deadline": "2026-11-15",      "published_at": "2026-09-28T07:30:00.000000Z",      "closed_at": null,      "archived_at": null,      "created_at": "2026-09-25T12:04:51.220417Z",      "updated_at": "2026-10-01T09:42:10.118305Z"    }  ],  "next_cursor": null}
FieldTypeMeaning
idUUIDThe job's id. It never changes
titlestringThe job title
slugstringThe job's name in its URL, unique in the workspace
statusenumdraft, live, paused or closed. Whether candidates can apply, which the app calls Publish. See Job status
publish_statusenumunpublished, scheduled or published. The job's status in the app. A scheduled job goes out at its publish time, and an unpublished job with a closed_at was published before
team_idUUIDThe team that owns the job
employment_typeenumfull_time or part_time
contract_typeenumpermanent, temporary, fixed_contract, seasonal, project, trainee, apprenticeship, self_employed or agency
workplace_typeenumremote, hybrid or onsite
locationslist of stringsThe places the job names. Empty when it names none
positions_countintegerHow many people the job hires, at least 1
application_deadlinedate or nullThe last day to apply, YYYY-MM-DD
published_attimestamp or nullWhen the job went live
closed_attimestamp or nullWhen the job was last unpublished. null while it is scheduled or published
archived_attimestamp or nullWhen the job was archived
created_attimestampWhen the job was created
updated_attimestampWhen the job last changed

An enum can gain a value in version 1, so handle one you do not know. Timestamps, nulls and enums describes each format.

Page through every job

Ask for a page, then send next_cursor back as cursor until it is null.

curl "https://attia.app/api/v1/jobs?limit=100&cursor=eyJ1IjoiMjAyNi0xMC0wMlQwODoxNTozMC40ODI5MTNaIiwiaSI6IjAxOTJmM2ExLTdjNDQtN2IxZS05YTUwLTNmMWQyYzZlOGEwMiJ9" \  -H "Authorization: Bearer $ATTIA_API_KEY"

The cursor stays stable while people edit jobs. A job edited during your read moves behind the cursor, and you may see it again on a later page. Keep the latest copy per id. Pagination explains the sort order.

Poll for changes

updated_since is inclusive. Send a time five minutes before the largest updated_at you have stored. A change gets its updated_at when the save starts, so it can turn up with a time older than one you already read. The answer repeats jobs you already have, so drop the repeats by id. With 2026-10-01T09:42:10.118305Z as the largest, the call is this.

curl "https://attia.app/api/v1/jobs?updated_since=2026-10-01T09:37:10.118305Z" \  -H "Authorization: Bearer $ATTIA_API_KEY"

You can combine updated_since with cursor and limit when one poll returns more than a page.

Archived jobs

The list leaves archived jobs out unless you ask for them. That matters when you keep a copy. A job that someone archives stops appearing in the default list, and your copy never learns why. Send include_archived=true when you poll, and an archived job comes back with a time in archived_at.

curl "https://attia.app/api/v1/jobs?include_archived=true&updated_since=2026-10-01T00:00:00Z" \  -H "Authorization: Bearer $ATTIA_API_KEY"

Get one job

GET /api/v1/jobs/{id} returns the job with that id. It takes no query parameters, so one gets a 400 with the code invalid-request.

curl "https://attia.app/api/v1/jobs/0192f3a1-6b02-7d4c-8e11-5a9c0b7d3e01" \  -H "Authorization: Bearer $ATTIA_API_KEY"

The answer holds the job in data, with the same fields as a job in the list. An archived job comes back too, with a time in archived_at.

{  "data": {    "id": "0192f3a1-6b02-7d4c-8e11-5a9c0b7d3e01",    "title": "Backend developer",    "slug": "backend-developer",    "status": "live",    "publish_status": "published",    "team_id": "0192f39f-11aa-7c3e-b2d4-9e8f7a6b5c4d",    "employment_type": "full_time",    "contract_type": "permanent",    "workplace_type": "hybrid",    "locations": ["Eksempelvik"],    "positions_count": 2,    "application_deadline": "2026-11-15",    "published_at": "2026-09-28T07:30:00.000000Z",    "closed_at": null,    "archived_at": null,    "created_at": "2026-09-25T12:04:51.220417Z",    "updated_at": "2026-10-01T09:42:10.118305Z"  }}

A 404 with the code not-found means no job your key can see has that id. The job may not exist, may be deleted, may belong to another workspace, or may sit in a team the list leaves out for you. Every one of these gets the same answer, so a 404 never tells you whether the job exists. An id that is not a UUID gets the same 404.