Addresses
| Address | Returns |
|---|---|
GET /api/v1/jobs | Open jobs, newest first (best match first when searching), a page at a time. |
GET /api/v1/jobs/{id} | One open job, with its full description and requirements. |
GET /api/v1/meta | Every city, field and option: the codes the filters take, with Arabic and English names. |
GET /api/v1/openapi.json | The OpenAPI 3 description of the API, for Postman, code generators and AI agents. |
Every job includes apply: { on, url }: on is jobsly with a job page ending in ?apply=1, or external with the employer’s application link. The API does not submit applications. Unknown API paths return a JSON 404.
Filters for /jobs
All optional, and combined together. Codes and their English or Arabic names come from /api/v1/meta and are accepted in any letter case. Invalid supplied filter values return 400 invalid_parameter with the parameter and allowed codes. A company value that can't be a page address, or a client value that can't be a widget key, is refused (400), so a typo never returns every job.
| Name | Example | Meaning |
|---|---|---|
q | accountant in tripoli above 2000 | Words to search, in Arabic or English. A plain sentence also fills in the city, work mode and minimum salary. |
city | benghazi | City code. |
category | it_software | Field code. |
jobType | full_time | Job type code. |
workMode | remote | On site, remote or hybrid. |
experienceLevel | junior | Experience level code. |
minSalary | 2000 | Lowest monthly salary in LYD (whole number); jobs without a stated salary and salaries in other currencies or periods are excluded. Above 100,000 it is read as 100,000. |
company | al-marjan | Only this organization's jobs (the address of its page on jobs.ly): those it posts, and those a recruitment agency posts for it under its name. Lowercase letters, digits and hyphens. |
client | the-client-key | Only the jobs a recruitment agency posts for one client under its name, by the client's widget key (32 characters, 0-9 and a-f), as the client's widget code gives it. A key whose client is private, archived or handed over to its organization finds no jobs, and meta.client is null. |
partner | lbc | A partner's list: the jobs of organizations with a current membership at that partner (today only lbc, the Libyan Business Council; members only, not organizations merely registered with it). Each job then also has partnerMember. Confidential jobs are never included. Any other value gets 400 invalid_parameter. |
member | 3f2c… (UUID) | With partner: only one member's jobs, by the partner's own id for that organization. A value that isn't a UUID, or member without partner, gets 400 invalid_parameter. |
apply | jobsly | jobsly: only jobs people apply to on jobs.ly (apply.on is "jobsly"), never jobs that send applicants to another website or email. It combines with every other filter (partner, member, company, client…). Any other value gets 400 invalid_parameter. |
page | 2 | Page number, from 1. |
perPage | 20 | Jobs per page, 1 to 50 (default 20). |
A job
{
"data": [
{
"id": "8ce12ab3-5f0e-4c1d-9b7a-2d4e6f8a1b3c",
"url": { "ar": "https://jobs.ly/jobs/8ce12ab3-…-accountant", "en": "https://jobs.ly/en/jobs/8ce12ab3-…-accountant" },
"title": { "ar": "محاسب", "en": "Accountant" },
"company": {
"name": { "ar": "شركة المرجان", "en": "Al Marjan" },
"url": { "ar": "https://jobs.ly/companies/al-marjan", "en": "https://jobs.ly/en/companies/al-marjan" },
"verified": true,
"logo": "https://jobs.ly/api/files/…",
"confidential": false
},
"hiringFor": null,
"city": { "code": "tripoli", "ar": "طرابلس", "en": "Tripoli" },
"countryCode": "LY",
"locations": [{ "countryCode": "LY", "cityCode": "tripoli", "locality": null, "localityAr": null }],
"category": { "code": "accounting_finance", "ar": "المحاسبة والمالية", "en": "Accounting & Finance" },
"occupation": { "isco08": "2411", "ar": "المحاسبون", "en": "Accountants" },
"jobType": { "code": "full_time", "ar": "دوام كامل", "en": "Full-time" },
"workMode": { "code": "onsite", "ar": "في المقر", "en": "On-site" },
"experienceLevel": { "code": "mid", "ar": "خبرة متوسطة", "en": "Mid-level" },
"salary": {
"min": 2500, "max": 3200, "currency": "LYD", "period": "month",
"formatted": { "ar": "2,500 – 3,200 د.ل", "en": "2,500 – 3,200 LYD" }
},
"postedAt": "2026-09-23T09:22:44.000Z",
"expiresAt": "2026-11-07T09:22:44.000Z"
}
],
"meta": { "total": 14, "page": 1, "pageCount": 3, "perPage": 5 }
}| Field | Meaning |
|---|---|
id | The job's id (UUID). |
url.ar, url.en | The job's page. Link every job there so visitors can follow its application instructions, either on jobs.ly or through an external method. |
title | The job title in Arabic and English. |
company | name, url (its page), verified (checked by jobs.ly), logo (or null), confidential. A confidential job (a verified organization keeping its name private) has confidential: true, the name "Undisclosed employer", and url and logo null. General feeds include these jobs anonymously. The company filter, organization-filtered feeds and the organization's widget exclude them. |
hiringFor | When a recruitment agency posts for a client: { "name": …, "nameEn": … } (nameEn is the English name, or the same name when there is none), or { "confidential": true }. Otherwise null. |
city, category, jobType, workMode, experienceLevel | Each as { code, ar, en }. jobType is null when a job copied from another site didn't say its type. |
occupation | The job's occupation in the ILO's classification: { isco08, ar, en } (the 4-digit ISCO-08 group and its name, e.g. 2411 Accountants). null when the job's title isn't one of our standard job titles. |
salary | { min, max, currency, period, formatted: { ar, en } }. Amounts may be decimal or null; currency is an ISO 4217 code. Period: hour, day, week, month, year or project. The whole field is null when not given. |
countryCode, locations | Primary workplace country (ISO 3166-1 alpha-2) and all physical workplaces as { countryCode, cityCode, locality, localityAr }. Remote roles have countryCode null and locations []; these fields do not imply nationality or remote eligibility. |
postedAt, expiresAt | ISO 8601 dates in UTC. postedAt is when the job was first posted: for a job copied from another site, the date of the original post, or null when that date is unknown. |
partnerMember | Only with the partner filter: the partner's own id (UUID) for the organization that posted the job. |
Imported vacancies without onward redistribution rights are excluded from this API, feeds and embedded listings, even when published on jobs.ly.
An organization can also choose not to share its jobs with other job sites (in its settings on jobs.ly). Its jobs are then left out of both feeds and of this API, /jobs/{id} included; GET /api/v1/jobs still returns them when asked for that organization alone (company, client, or a partner's member), which is how its own careers widget shows them on its website. Don't use those filters, or the website's pages, to collect jobs an organization chose not to share. The choice doesn't change jobs.ly's own pages and search, its sitemaps, or the job data search engines read on each job page.
One job
curl "https://jobs.ly/api/v1/jobs/8ce12ab3-5f0e-4c1d-9b7a-2d4e6f8a1b3c"
{
"data": {
"id": "8ce12ab3-…",
"…": "the same fields as in the list, plus:",
"description": { "ar": "…", "en": "…" },
"requirements": { "ar": ["…"], "en": ["…"] }
}
}Options
curl "https://jobs.ly/api/v1/meta"
{
"cities": [{ "code": "tripoli", "ar": "طرابلس", "en": "Tripoli" }, …],
"categories": [{ "code": "it_software", "ar": "…", "en": "…" }, …],
"jobTypes": […], "workModes": […], "experienceLevels": […],
"currency": "LYD"
}Pages
meta.total is the number of jobs found, meta.pageCount the number of pages. Ask for page=2, page=3… until page reaches pageCount. A page past the last one (or past page 500) returns an empty data list, with meta.page still the page you asked for.
With client, meta.client gives that client's name and url (its page), each as { ar, en }, or null when the key names no client with public jobs.
Errors and limits
400 { "error": "invalid_parameter", "message": "company must be an organization's page address (slug): …" }
400 { "error": "invalid_parameter", "message": "client must be a client's widget key: …" }
404 { "error": "not_found", "message": "This job doesn't exist or is closed." }
429 { "error": "too_many_requests", "message": "Up to 120 requests per minute." }
Retry-After: 60- Every error has the same shape:
error, a fixed code for programs, andmessage, in English for people. - Up to 120 requests per minute from one address; after that the answer is 429, and
Retry-Aftersays how many seconds to wait. - Answers may be kept for a minute (
Cache-Control: max-age=60): asking more often doesn't bring newer jobs. - Any website may call the API from the browser (
Access-Control-Allow-Origin: *).
More examples
const res = await fetch("https://jobs.ly/api/v1/jobs?city=tripoli&perPage=10");
const { data, meta } = await res.json();
for (const job of data) {
console.log(job.title.en, "·", job.company.name.en, "·", job.url.en);
}
console.log(`${meta.total} open jobs`);import requests
r = requests.get("https://jobs.ly/api/v1/jobs", params={"category": "it_software", "perPage": 20})
r.raise_for_status()
for job in r.json()["data"]:
print(job["title"]["ar"], job["url"]["ar"])