> ## Documentation Index
> Fetch the complete documentation index at: https://kardow.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Create Job

> Create a new job posting from your ATS, CRM, or internal workflow

Use this endpoint when an external system needs to publish jobs directly into Kardow.

## Required Fields

<ParamField body="title" type="string" required>
  Job title.
</ParamField>

<ParamField body="description" type="string" required>
  Full job description. HTML is accepted.
</ParamField>

<ParamField body="company_name" type="string" required>
  Hiring company name.
</ParamField>

## Application method

<Warning>
  `how_to_apply` and `how_to_apply_method` only mean something together. If the method is `email`, `website`, `phone` or `form-external`, you **must** send a `how_to_apply` value, otherwise the apply button has nowhere to send candidates. The request is rejected with `422 invalid_apply_destination` rather than saved with a dead button. Only `form` takes no destination.
</Warning>

<ParamField body="how_to_apply" type="string">
  Where candidates apply: an external URL, an email address, or a phone number. Required unless `how_to_apply_method` is `form`.

  This is not the job's own page on your board. That URL is generated automatically and returned in the response.
</ParamField>

<ParamField body="how_to_apply_method" type="string">
  Controls how Kardow presents the apply button. One of:

  | Value           | When to use                                                                   |
  | --------------- | ----------------------------------------------------------------------------- |
  | `form-external` | `how_to_apply` is a link to an external application form or ATS (most common) |
  | `website`       | `how_to_apply` is a general company or job page                               |
  | `email`         | `how_to_apply` is an email address                                            |
  | `phone`         | `how_to_apply` is a phone number                                              |
  | `form`          | Kardow hosts the application form — no `how_to_apply` needed                  |

  If omitted, Kardow detects the method from the `how_to_apply` value, which is usually the safest choice.

  A method that contradicts its value is also rejected: `email` pointing at a URL would render a broken `mailto:` link. Where the intent is unambiguous, Kardow recovers instead of failing: `email` with no destination falls back to `contact_email`, `website` falls back to `company_website`, and a bare domain is upgraded to `https://`.

  The response echoes the resolved destination in `meta.apply` so you can confirm what was stored.
</ParamField>

## Common optional fields

<ParamField body="category" type="string">
  Category name, e.g. `Engineering`. Matched to an existing category (case-insensitive) or created if new. See [Categories](/docs/api-reference/jobs/categories).
</ParamField>

<ParamField body="category_id" type="string">
  Existing category UUID. Takes precedence over `category` if both are sent.
</ParamField>

<ParamField body="job_type" type="string">
  One of `full-time`, `part-time`, `contract`, `internship`, `temporary`, or `casual`.
</ParamField>

<ParamField body="location" type="string">
  The location shown on the job: `Remote`, `Paris`, `San Francisco, CA`, or a full street address.

  Kardow resolves this to a real place and links the job to it, so the job appears on the right location page and in location filters. Street addresses are filed under their city, and the exact coordinates are kept on the job for map pins.
</ParamField>

<ParamField body="location_id" type="string">
  An existing location id from [Locations](/docs/api-reference/jobs/locations). Takes precedence over `location` text and skips resolution entirely.

  Resolve first and pass the id whenever you can: it removes any guesswork about which place the job lands on.
</ParamField>

<ParamField body="location_country" type="string">
  Two-letter country code used when resolving `location` text, e.g. `ca`. Ignored when `location_id` is given.
</ParamField>

<ParamField body="is_remote" type="boolean">
  Set to `true` for remote roles.
</ParamField>

<ParamField body="salary_min" type="number">
  Minimum salary amount.
</ParamField>

<ParamField body="salary_max" type="number">
  Maximum salary amount.
</ParamField>

<ParamField body="salary_currency" type="string">
  Currency code such as `USD`.
</ParamField>

<ParamField body="salary_period" type="string">
  One of `hourly`, `daily`, `weekly`, `monthly`, or `yearly`.
</ParamField>

<ParamField body="contact_email" type="string">
  Recruiter or HR contact email (not the apply destination — use `how_to_apply` for that).
</ParamField>

<ParamField body="status" type="string" default="pending">
  One of `pending`, `active`, `expired`, or `draft`.
</ParamField>

## Examples

### Link to an external application form

The most common case: your ATS or careers page hosts the form.

```bash cURL theme={null}
curl --request POST \
  --url https://api.kardow.com/jobs \
  --header "Content-Type: application/json" \
  --header "x-api-key: your-api-key-here" \
  --data '{
    "title": "Backend Engineer",
    "description": "Build internal and public APIs.",
    "company_name": "Acme",
    "how_to_apply": "https://jobs.acme.com/backend-engineer",
    "how_to_apply_method": "form-external"
  }'
```

### Apply via email

```bash cURL theme={null}
curl --request POST \
  --url https://api.kardow.com/jobs \
  --header "Content-Type: application/json" \
  --header "x-api-key: your-api-key-here" \
  --data '{
    "title": "Office Manager",
    "description": "Run day-to-day office operations.",
    "company_name": "Acme",
    "how_to_apply": "jobs@acme.com",
    "how_to_apply_method": "email"
  }'
```

### Remote role with salary details

```bash cURL theme={null}
curl --request POST \
  --url https://api.kardow.com/jobs \
  --header "Content-Type: application/json" \
  --header "x-api-key: your-api-key-here" \
  --data '{
    "title": "Senior Product Designer",
    "description": "Lead product design across web and mobile.",
    "company_name": "Acme",
    "location": "Remote",
    "is_remote": true,
    "job_type": "full-time",
    "how_to_apply": "https://jobs.acme.com/designer",
    "how_to_apply_method": "form-external",
    "salary_min": 90000,
    "salary_max": 120000,
    "salary_currency": "USD",
    "salary_period": "yearly",
    "contact_email": "jobs@acme.com"
  }'
```

### JavaScript example

```javascript theme={null}
const response = await fetch("https://api.kardow.com/jobs", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "x-api-key": process.env.KARDOW_API_KEY,
  },
  body: JSON.stringify({
    title: "Senior Software Engineer",
    description: "Build product features across the stack.",
    company_name: "Acme",
    location: "Remote",
    is_remote: true,
    job_type: "full-time",
    how_to_apply: "https://jobs.acme.com/senior-swe",
    how_to_apply_method: "form-external",
    contact_email: "jobs@acme.com",
  }),
});

const result = await response.json();
console.log(result.data.id);
```

## Response Shape

<ResponseField name="data.id" type="string" required>
  Newly created job UUID.
</ResponseField>

<ResponseField name="data.slug" type="string" required>
  Job URL path (without the domain).
</ResponseField>

<ResponseField name="data.url" type="string" required>
  The full, shareable public URL to the created job on your live board (uses your active custom domain when you have one).
</ResponseField>

<ResponseField name="data.status" type="string" required>
  Current job status.
</ResponseField>

<ResponseField name="meta.organization_id" type="number" required>
  Organization that owns the job.
</ResponseField>

<ResponseField name="meta.board_url" type="string" required>
  The base public URL of your job board.
</ResponseField>

<ResponseField name="meta.url" type="string" required>
  The full public URL for the created job (same as `data.url`).
</ResponseField>

<ResponseField name="data.location_id" type="string | null">
  The location the job was filed under, or `null` if it could not be resolved.
</ResponseField>

<ResponseField name="meta.apply" type="string">
  Human-readable summary of the apply destination that was actually stored, e.g. `email: jobs@acme.com`. Check it: if it says `Kardow-hosted application form` when you meant an external one, the destination did not arrive.
</ResponseField>

<ResponseField name="meta.location" type="string">
  Human-readable summary of where the job was filed, e.g. `Gatineau, QC (geocoded)`, or `none`.
</ResponseField>

<ResponseField name="meta.warnings" type="string[]">
  Present only when something needs attention, such as a location that could not be resolved.
</ResponseField>

### Example response

```json theme={null}
{
  "data": {
    "id": "4d1b6fe8-688f-467c-b233-b5d6a51ce1cb",
    "title": "Backend Engineer",
    "slug": "/companies/acme/jobs/backend-engineer-4d1b6fe8688f467cb233b5d6a51ce1cb",
    "url": "https://jobs.acme.com/companies/acme/jobs/backend-engineer-4d1b6fe8688f467cb233b5d6a51ce1cb",
    "status": "pending",
    "created_at": "2026-03-10T12:00:00.000Z",
    "company_logo_url": null,
    "how_to_apply": "https://jobs.acme.com/backend-engineer",
    "how_to_apply_method": "form-external",
    "location": "Gatineau, QC",
    "location_id": "7f8a1c2d-0b34-4c8e-9a71-2f5d6e8b0a13",
    "location_display": "Gatineau, QC"
  },
  "meta": {
    "organization_id": 42,
    "board_url": "https://jobs.acme.com",
    "url": "https://jobs.acme.com/companies/acme/jobs/backend-engineer-4d1b6fe8688f467cb233b5d6a51ce1cb",
    "apply": "form-external: https://jobs.acme.com/backend-engineer",
    "location": "Gatineau, QC (geocoded)"
  }
}
```


## OpenAPI

````yaml POST /jobs
openapi: 3.1.0
info:
  title: Kardow Public API
  description: Public API for jobs and member automation on Kardow.
  version: 1.0.0
  contact:
    name: Kardow Support
    email: support@kardow.com
    url: https://kardow.com
servers:
  - url: https://api.kardow.com
    description: Production API
security:
  - ApiKeyAuth: []
paths:
  /jobs:
    post:
      tags:
        - Jobs
      summary: Create Job
      description: Create a job in your organization from an external system.
      operationId: createJob
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateJobRequest'
            examples:
              minimal:
                summary: Minimal job
                value:
                  title: Backend Engineer
                  description: Build internal and public APIs.
                  company_name: Acme
              remote_salary:
                summary: Remote role with salary
                value:
                  title: Senior Product Designer
                  description: Lead product design across web and mobile.
                  company_name: Acme
                  is_remote: true
                  job_type: full-time
                  salary_min: 90000
                  salary_max: 120000
                  salary_currency: USD
                  salary_period: yearly
                  contact_email: jobs@acme.com
              external_apply:
                summary: External apply flow
                value:
                  title: Growth Marketer
                  description: Own paid acquisition and lifecycle campaigns.
                  company_name: Acme
                  location: Remote
                  how_to_apply: https://acme.com/careers/growth-marketer
                  how_to_apply_method: form-external
                  company_website: https://acme.com
                  status: pending
      responses:
        '200':
          description: Job created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateJobResponse'
        '400':
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '429':
          $ref: '#/components/responses/RateLimitError'
        '500':
          $ref: '#/components/responses/InternalError'
      security:
        - ApiKeyAuth: []
components:
  schemas:
    CreateJobRequest:
      type: object
      required:
        - title
        - description
        - company_name
      properties:
        title:
          type: string
        description:
          type: string
        company_name:
          type: string
        apply_url:
          type: string
          description: >-
            Preferred field for the application destination — an external URL,
            email address, or phone number. Alias of how_to_apply (which is
            still accepted).
        apply_type:
          type: string
          enum:
            - form-external
            - website
            - email
            - phone
            - form
          description: >-
            Preferred field for how candidates apply. Alias of
            how_to_apply_method.
        how_to_apply:
          type: string
          description: >-
            Required unless how_to_apply_method is "form". Where candidates
            apply: an external URL, an email address, or a phone number. A
            method of email/website/phone/form-external with no destination is
            rejected with 422 invalid_apply_destination.
        how_to_apply_method:
          type: string
          enum:
            - form-external
            - website
            - email
            - phone
            - form
          description: >-
            What the apply button does: form-external, website, email, phone, or
            form. Must match the how_to_apply value. Only "form" takes no
            destination. Omit to auto-detect from how_to_apply.
        posted_by_email:
          type: string
          format: email
          description: >-
            Email of the employer account posting this job. Must match a job
            board user in the org, else the request returns 422
            employer_not_found.
        category:
          type: string
          description: >-
            Category name (e.g. Engineering). Matched to an existing category or
            created if new.
        is_remote:
          type: boolean
        is_highlighted:
          type: boolean
        is_sticky:
          type: boolean
        location:
          type: string
          description: >-
            Location shown on the job. Resolved to a real place and linked, so
            the job appears on the right location page and filter. Street
            addresses are filed under their city; exact coordinates stay on the
            job.
        location_restricted:
          type: boolean
        category_id:
          type: string
          format: uuid
          description: >-
            Existing category UUID. Takes precedence over category if both are
            sent.
        job_type:
          type: string
          enum:
            - full-time
            - part-time
            - contract
            - internship
            - temporary
            - casual
        company_website:
          type: string
          format: uri
        company_logo_url:
          type: string
          format: uri
        salary_min:
          type: number
        salary_max:
          type: number
        salary_currency:
          type: string
        salary_period:
          type: string
          enum:
            - hourly
            - daily
            - weekly
            - monthly
            - yearly
        contact_email:
          type: string
          format: email
        expires_at:
          type: string
          format: date-time
        status:
          type: string
          enum:
            - pending
            - active
            - expired
            - draft
        location_id:
          type: string
          format: uuid
          description: >-
            Existing location id from POST /locations. Takes precedence over
            `location` text and skips resolution.
        location_country:
          type: string
          minLength: 2
          maxLength: 2
          description: >-
            Two-letter country code used when resolving `location` text, e.g.
            "ca". Ignored when location_id is given.
    CreateJobResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            id:
              type: string
              format: uuid
            title:
              type: string
            slug:
              type: string
              description: URL path only, without the domain.
            url:
              type: string
              description: Full public URL to the created job on the live board.
            status:
              type: string
            created_at:
              type: string
              format: date-time
            company_logo_url:
              type:
                - string
                - 'null'
        meta:
          type: object
          properties:
            organization_id:
              type: integer
            board_url:
              type: string
              description: Base public URL of the job board.
            url:
              type: string
              description: Full public URL for the created job (same as data.url).
    ErrorEnvelope:
      type: object
      properties:
        error:
          type: object
          properties:
            message:
              type: string
            code:
              type: string
            details: {}
            url:
              type: string
          required:
            - message
            - code
      required:
        - error
  responses:
    ValidationError:
      description: Validation error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
    UnauthorizedError:
      description: Unauthorized
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
    RateLimitError:
      description: Rate limit exceeded
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
    InternalError:
      description: Internal server error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: x-api-key
      description: >-
        API key for authentication. Get yours from Settings > API Keys in the
        Kardow dashboard.

````