Job publishing API

Publish and manage your jobs from your own system. You can send our simple JSON format, or the data.jobDetails JSON you already send to other job boards — we convert it for you.

1. Get a token

  1. Sign in as a company and complete verification (needed to submit jobs for approval).
  2. Open API in the menu (/company/api) and create a token. Copy it — it is shown once.
  3. Send it with every request: Authorization: Bearer <token> and Accept: application/json.

Download the Postman collection — import it in Postman, set the token variable, and every request below is ready to send.

Base URL: https://jorajobs.com/api/v1 · Rate limit: 60 requests per minute per company.

2. Endpoints

MethodPathPermissionWhat it does
GET /jobs jobs:read Your jobs, newest first. Filters: status, external_id, per_page (max 100).
GET /jobs/{id} jobs:read One job, with status and rejection reason.
POST /jobs jobs:write Create a job (201), or update the job with the same external_id (200).
PUT / PATCH /jobs/{id} jobs:write Update a job. Send only the fields that change.
POST /jobs/{id}/close jobs:write Stop taking applications for a live job.
DELETE /jobs/{id} jobs:write Delete a draft / pending / rejected job without applicants.
GET /categories any Categories and sub-categories (id, slug, name).
GET /options any Allowed values for employment_type, work_arrangement, salary_period, …

3. How a job moves

  • New jobs are submitted for approval (status pending). Send "submit": false to keep a draft.
  • An administrator approves (live) or rejects (rejected, with rejection_reason). Poll GET /jobs/{id} to follow it.
  • Updating a live job keeps it live. Updating a rejected job and sending "submit": true sends it back for approval.
  • Unverified companies can only create drafts; submitting returns 403 company_not_verified.

4. Native format

Required: title, category (id, slug or name), description, country (name or ISO code, see GET /options), city, deadline_at (within a year). New regions (state) and cities are added automatically. salary_currency and salary_period are required when you send an amount. questions takes up to 10 strings or objects (type: text, yes_no, number).

curl -X POST https://jorajobs.com/api/v1/jobs \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d @job.json
{
    "external_id": "ATS-2026-0042",
    "title": "Senior Laravel Developer",
    "category": "information-communication-technology",
    "sub_category": "Software Development",
    "employment_type": "full_time",
    "work_arrangement": "hybrid",
    "job_level": "senior",
    "vacancies": 2,
    "experience_min": 4,
    "experience_max": 8,
    "education_requirement": "BSc in CSE or related",
    "skills": [
        "Laravel",
        "MySQL",
        "REST"
    ],
    "abstract": "Build and scale our hiring platform.",
    "description": "<p>You will design APIs…</p><ul><li>4+ years with Laravel</li></ul>",
    "salary_min": 120000,
    "salary_max": 160000,
    "salary_currency": "BDT",
    "salary_period": "month",
    "salary_negotiable": true,
    "country": "Bangladesh",
    "country_code": "BD",
    "city": "Dhaka",
    "area": "Gulshan",
    "deadline_at": "2026-12-05",
    "questions": [
        "What is your notice period?",
        {
            "question": "Have you used Laravel queues in production?",
            "type": "yes_no",
            "required": true
        }
    ],
    "submit": true
}

5. Job board (data.jobDetails) format

Send the same JSON you send to other job boards. Fields about the other platform (tracking, insights, SEO, reviews, branding, advertiser and company profile) are ignored — your company always comes from the token. Any native field sent next to data overrides the converted value, for example {"data": {…}, "category": "sales", "submit": false}.

{
    "data": {
        "jobDetails": {
            "job": {
                "id": "94933960",
                "title": "DELIVERY MANAGER & RELEASE MANAGER",
                "abstract": "DELIVERY MANAGER & RELEASE MANAGER",
                "content2": "<p><strong>WE’RE HIRING</strong> …</p>",
                "isExpired": false,
                "isLinkOut": false,
                "expiresAt": {
                    "dateTimeUtc": "2026-11-19T14:17:36.612Z"
                },
                "salary": {
                    "currencyLabel": "PHP",
                    "label": "₱120,000 – ₱130,000 per month"
                },
                "workTypes": {
                    "label": "Full time"
                },
                "tracking": {
                    "classificationInfo": {
                        "classification": "Information & Communication Technology",
                        "subClassification": "Programme & Project Management"
                    },
                    "locationInfo": {
                        "location": "Taguig City"
                    }
                },
                "products": {
                    "questionnaire": {
                        "questions": [
                            "What's your expected monthly basic salary?"
                        ]
                    }
                }
            },
            "gfjInfo": {
                "location": {
                    "countryCode": "PH",
                    "country": "Philippines",
                    "suburb": "Bonifacio Global City",
                    "state": "Metro Manila",
                    "postcode": null
                },
                "workTypes": {
                    "label": [
                        "FULL_TIME"
                    ]
                }
            },
            "workArrangements": {
                "arrangements": [
                    {
                        "type": "ONSITE",
                        "label": "On-site"
                    }
                ]
            }
        }
    }
}
Your fieldBecomesNotes
job.idexternal_idRe-sending the same id updates the job instead of creating a new one.
job.titletitle
job.abstractabstractIgnored when it just repeats the title.
job.content2 (or job.content)descriptionHTML is cleaned to basic formatting.
job.expiresAt.dateTimeUtcdeadline_atMust be in the future; jobs with job.isExpired = true are refused.
job.salary.label + currencyLabelsalary_min, salary_max, salary_currency, salary_period, salary_labelAmounts and period are read from the label, e.g. "₱120,000 – ₱130,000 per month".
gfjInfo.workTypes.label / job.workTypes.labelemployment_typeFULL_TIME → full_time, CONTRACTOR → contract, …
workArrangements / badgeswork_arrangementONSITE, REMOTE, HYBRID
tracking.classificationInfocategory, sub_categoryMatched by name. Send "category" next to "data" to override.
gfjInfo.location + tracking.locationInfo.locationcountry, country_code, state, city, area, postcode
products.questionnaire.questionsquestionsAdded as optional text questions.
job.isLinkOut + job.shareLinkapply_method, external_apply_urlLink-out jobs send candidates to your URL.

6. Responses and errors

Jobs are returned as {"data": {…}} with id, external_id, status, rejection_reason, public_url (once approved), salary, location, questions and dates. POST /jobs also returns meta.created.

  • 401 missing or invalid token · 403 missing permission, not_a_company, account_blocked or company_not_verified
  • 404 the job does not exist or belongs to another company
  • 409 invalid_status, cannot_delete or duplicate_external_id
  • 422 validation failed — errors lists the problems per field
  • 429 rate limit reached — wait for the Retry-After seconds