{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://artifacts.apis.io/cavuno-com/json-schema/cavuno-com-create-job-body-schema.json",
  "title": "CreateJobBody",
  "x-generated": "2026-11-04",
  "x-method": "derived",
  "x-generator": "derive-json-schema.py",
  "x-source": "openapi/cavuno-com-openapi.json#/components/schemas/CreateJobBody",
  "type": "object",
  "properties": {
    "companyId": {
      "type": "string",
      "minLength": 1,
      "description": "The ID of an existing company. **Required unless you supply `company`** — exactly one of the two must be present, and supplying both is an error."
    },
    "description": {
      "type": "string",
      "minLength": 1,
      "maxLength": 25000,
      "description": "Long-form description of the role, as HTML. Up to 25,000 characters. The HTML is sanitized on write: tags and attributes outside the supported formatting set (paragraphs, headings, lists, links, inline emphasis) are removed. Plain text without HTML tags is converted to paragraphs."
    },
    "slug": {
      "type": "string",
      "description": "URL-friendly slug for the job. Auto-generated from `title` when omitted."
    },
    "employmentType": {
      "type": "string",
      "enum": [
        "full_time",
        "part_time",
        "contract",
        "internship",
        "temporary",
        "volunteer",
        "other"
      ],
      "description": "Employment type of the role. When `customEmploymentType` is also sent, this must be the Google equivalent of that custom type (or be omitted: the server sets it). Sending a built-in type without `customEmploymentType` clears any custom type on the job."
    },
    "customEmploymentType": {
      "type": [
        "string",
        "null"
      ],
      "minLength": 1,
      "maxLength": 100,
      "description": "Key of one of the board's custom employment types (`GET /v1/settings/job-form` → `employmentType.customTypes`). The job is saved with the Google equivalent of that type as its built-in `employmentType`, which is what Google for Jobs and feeds see. An unknown key, a type the board no longer offers (`offered: false`), or an `employmentType` that differs from its Google equivalent returns `400 jobs_constraint_violation`. Pass `null` to clear the custom type and keep the built-in; omitted means unchanged."
    },
    "remoteOption": {
      "type": "string",
      "enum": [
        "on_site",
        "hybrid",
        "remote"
      ],
      "description": "**Required.** Whether the role is on-site, hybrid, or fully remote: one of `on_site`, `hybrid`, `remote`. Omitting it or sending `null` returns `400`. It is not valid on its own either: `on_site` and `hybrid` require at least one `officeLocations` entry, and `remote` requires `remotePermits` (use `[{\"type\":\"worldwide\",\"value\":\"worldwide\"}]` for anywhere; `remoteTimezones` then auto-derives on POST)."
    },
    "remotePermits": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "worldwide",
              "world_region",
              "continent",
              "region",
              "subregion",
              "subdivision",
              "country",
              "custom"
            ]
          },
          "value": {
            "type": "string",
            "minLength": 1
          },
          "code": {
            "type": "string",
            "description": "Ignored on input. Job reads add it to some entries; send it back or leave it out."
          }
        },
        "required": [
          "type",
          "value"
        ],
        "additionalProperties": false
      },
      "description": "Where remote candidates must hold work authorization. Each entry is the smallest relevant scope: `worldwide`, a `world_region` (EMEA / LATAM / NA / APAC), a `continent`, a `region`, a `subregion`, a `custom` group (e.g. `EU`), a `country` (ISO 3166-1 alpha-2), or a `subdivision` (ISO 3166-2). Subdivisions auto-imply their parent country in the derived `remoteWorkPermitCountryCodes` output. Worldwide is mutually exclusive with all other entries. The canonical `{type, value}` set is published at `GET /v1/taxonomies/remote-permits`. Pass `[]` to clear an existing constraint."
    },
    "remoteTimezones": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "all",
              "world_region",
              "continent",
              "region",
              "subregion",
              "country",
              "timezone"
            ]
          },
          "value": {
            "type": "string",
            "minLength": 1
          },
          "plusMinus": {
            "type": "number",
            "minimum": 0,
            "maximum": 12
          }
        },
        "required": [
          "type",
          "value"
        ],
        "additionalProperties": false
      },
      "description": "Where remote candidates must be timezone-compatible. Each entry mirrors the `remotePermits` shape (`world_region`, `continent`, `region`, `subregion`, `country`) plus `timezone` (specific IANA name with optional `plusMinus` ±N hours expansion) and `all` (every timezone: equivalent of `worldwide` for permits). The canonical `{type, value}` set is published at `GET /v1/taxonomies/remote-timezones`. **When omitted on POST**, the server auto-derives this from `remotePermits` (or `[{all,all}]` if neither was provided). **PATCH never auto-re-derives**: once set, only an explicit replacement updates the stored value, even when `remotePermits` changes. Pass `[]` to clear an existing constraint."
    },
    "remoteSponsorship": {
      "type": "string",
      "enum": [
        "yes",
        "no",
        "unknown"
      ],
      "description": "Whether the employer sponsors visas for remote candidates. One of `yes`, `no`, or `unknown`."
    },
    "seniority": {
      "type": "string",
      "enum": [
        "entry_level",
        "associate",
        "mid_level",
        "senior",
        "lead",
        "principal",
        "director",
        "executive"
      ],
      "description": "Seniority level of the role."
    },
    "applicationUrl": {
      "type": "string",
      "minLength": 1,
      "maxLength": 2048,
      "description": "Where candidates apply. Accepts an HTTPS URL, a `mailto:` URI, or a bare email address (which is normalized to `mailto:` form)."
    },
    "salaryMin": {
      "type": [
        "number",
        "null"
      ],
      "minimum": 0,
      "description": "Minimum salary, in `salaryCurrency` units. `null` means not set. When `salaryMin` or `salaryMax` is set, `salaryCurrency` and `salaryTimeframe` are both required; otherwise `400`."
    },
    "salaryMax": {
      "type": [
        "number",
        "null"
      ],
      "minimum": 0,
      "description": "Maximum salary, in `salaryCurrency` units. `null` means not set. When `salaryMin` or `salaryMax` is set, `salaryCurrency` and `salaryTimeframe` are both required; otherwise `400`."
    },
    "salaryCurrency": {
      "type": [
        "string",
        "null"
      ],
      "description": "Three-letter ISO 4217 currency code for `salaryMin` and `salaryMax`. `null` means not set. Required when `salaryMin` or `salaryMax` is set; optional otherwise."
    },
    "salaryTimeframe": {
      "type": [
        "string",
        "null"
      ],
      "enum": [
        "per_year",
        "per_month",
        "per_week",
        "per_day",
        "per_hour"
      ],
      "description": "Period the `salaryMin` and `salaryMax` figures are quoted against. `null` means not set. Required when `salaryMin` or `salaryMax` is set; optional otherwise."
    },
    "skills": {
      "type": "array",
      "items": {
        "type": "string",
        "minLength": 1
      },
      "maxItems": 100,
      "description": "Up to 100 canonical skill slugs from `GET /v1/taxonomies/skills`. Every slug must exist in this account."
    },
    "categories": {
      "type": "array",
      "items": {
        "type": "string",
        "minLength": 1
      },
      "maxItems": 100,
      "description": "Up to 100 canonical category slugs from `GET /v1/taxonomies/categories`. Every slug must exist in this account."
    },
    "isFeatured": {
      "type": "boolean",
      "description": "Whether the job appears in featured slots on the public board."
    },
    "expiresAt": {
      "anyOf": [
        {
          "type": "integer",
          "minimum": 0
        },
        {
          "nullable": true
        }
      ],
      "description": "Job expiry as a Unix epoch in milliseconds. On create, omitted or `null` defaults to 30 days from creation. On PATCH, `null` clears the explicit expiry; an ordinary published job then receives the board's `defaultJobDurationDays` from its publication time (30 days when unset). Draft and sponsored jobs may have no expiry. Past timestamps remove the job from the public board."
    },
    "publishedAt": {
      "type": "integer",
      "minimum": 0,
      "description": "Time at which the job was first published, as a Unix epoch in milliseconds. When omitted on create with `status: \"published\"`, the server stamps the current time. Useful for bulk-importing historical jobs while preserving original publication dates. PATCH may overwrite an existing value but cannot clear it."
    },
    "educationRequirements": {
      "type": "array",
      "items": {
        "type": "string",
        "enum": [
          "high_school",
          "associate_degree",
          "bachelor_degree",
          "professional_certificate",
          "postgraduate_degree",
          "no_requirements"
        ]
      },
      "description": "Required education credentials. Each value is one of `high_school`, `associate_degree`, `bachelor_degree`, `professional_certificate`, `postgraduate_degree`, or `no_requirements`."
    },
    "experienceMonths": {
      "type": "integer",
      "minimum": 0,
      "description": "Minimum required experience, expressed in months."
    },
    "experienceInPlaceOfEducation": {
      "type": "boolean",
      "description": "If `true`, equivalent experience may substitute for the listed education requirements."
    },
    "inOfficePeriod": {
      "type": "string",
      "enum": [
        "per_week",
        "per_month",
        "per_year"
      ],
      "description": "Period denominator for `inOfficeFrequency`."
    },
    "inOfficeFrequency": {
      "type": "number",
      "minimum": 0,
      "description": "How often the candidate must be in-office over `inOfficePeriod`."
    },
    "officeLocations": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/JobOfficeLocationInput"
      },
      "description": "Physical office locations associated with the job. Prefer `{query: \"City, Country\"}` for free-form input; `{city, country, region?, locality?}` is also accepted when you already have structured fields. Each entry is resolved server-side; a country mismatch returns `400 jobs_unresolvable_location`."
    },
    "externalId": {
      "type": [
        "string",
        "null"
      ],
      "minLength": 1,
      "maxLength": 255,
      "description": "An external identifier for the job from your own system, such as an ATS requisition ID. Use this value to look up the job later via `GET /v1/jobs?externalId=...` for deduplication. Always set it on create (the source job URL or ATS requisition ID) so reposts of the same job are detected reliably, even when several jobs share one `applicationUrl`. Scoped per-account: two different accounts may reuse the same `externalId` without collision. Up to 255 characters. `null` means not set."
    },
    "customFieldValues": {
      "type": "object",
      "additionalProperties": {
        "anyOf": [
          {
            "type": "string"
          },
          {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          {
            "type": "boolean"
          },
          {
            "type": "number"
          },
          {
            "nullable": true
          }
        ]
      },
      "description": "Board-defined custom-field values, keyed by the field `key` (definitions, including type and option keys, are published at `GET /v1/settings/job-form`). Writes are **additive**: on `PATCH` a key you send is set/overwritten and a key you omit is preserved (unsent keys are never cleared); on `POST` this initializes the bag. Send a key with an intentional-empty value (`null`, `\"\"`, or `[]`) to **clear** it (`\"\"`/`null` clear any type; `[]` clears a `multi_select`); `false` and `0` are kept as real values. Values must match the field type and `single_select`/`multi_select` must use defined option **keys** (not labels); a wrong-typed value is rejected (`custom_field_wrong_type`), never silently cleared. Unknown keys are ignored. The stored bag never contains `null`/empty values."
    },
    "collectionValues": {
      "type": "object",
      "additionalProperties": {
        "anyOf": [
          {
            "type": "array",
            "items": {
              "type": "string"
            },
            "maxItems": 100
          },
          {
            "nullable": true
          }
        ]
      },
      "description": "Collection-reference selections keyed by the configured job collection field key. Values are arrays of record IDs, including for single-select fields. On PATCH omitted keys are preserved; send null or an empty array to clear a field. Keys for fields whose collection is archived are ignored, and their stored selections are kept."
    },
    "collectionOverrides": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/JobCollectionOverride"
      },
      "maxItems": 500,
      "description": "Per-job wording for selected collection entries, only on fields whose definition has `allowOverrides`. At most one item per selected entry; an item for an entry that is not selected on the job is rejected with `400 jobs_constraint_violation`. The list replaces the job's stored overrides: on PATCH omit it to keep them and send `[]` to clear them all. Deselecting an entry through `collectionValues` drops its override."
    },
    "title": {
      "type": "string",
      "minLength": 1,
      "maxLength": 200,
      "description": "The job title."
    },
    "company": {
      "$ref": "#/$defs/InlineCompanyInput"
    },
    "status": {
      "type": "string",
      "enum": [
        "draft",
        "published"
      ],
      "description": "Initial status of the job. Defaults to `draft`. Only `draft` and `published` are writable here. The system-set values `expired`, `archived`, and `pending_approval` are not. Use the dedicated transitions (`POST /v1/jobs/:id/publish`, `/pause`, `/expire`) for status changes after create."
    },
    "allowSharedApplicationUrl": {
      "type": "boolean",
      "description": "Set to `true` to create this job even though another job on your account already uses the same `applicationUrl` (for example, several roles that share one ATS page or one application inbox). Only skips the application-URL duplicate check; a matching `externalId` or an identical job (same company, title, location and description) still returns 409 `jobs_already_exists`. Defaults to `false`."
    }
  },
  "required": [
    "description",
    "remoteOption",
    "applicationUrl",
    "title"
  ],
  "additionalProperties": false,
  "$defs": {
    "InlineCompanyInput": {
      "type": "object",
      "properties": {
        "name": {
          "type": "string",
          "minLength": 1,
          "maxLength": 120,
          "description": "The company's display name."
        },
        "website": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 200,
          "description": "Public company website URL. Used to resolve to an existing company by domain before falling back to creation. `null` or `\"\"` means not set."
        },
        "summary": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 280,
          "description": "One-line summary of the company. Up to 280 characters. `null` or `\"\"` means not set."
        },
        "matchByName": {
          "type": "boolean",
          "description": "If `true` (default), falls back to matching by `name` when no website match is found. Set to `false` to match by website domain only."
        },
        "createIfMissing": {
          "type": "boolean",
          "description": "If `true` (default), creates a new company when no match is found. Set to `false` to receive a `404 companies_not_found` response instead."
        }
      },
      "required": [
        "name"
      ],
      "additionalProperties": false,
      "description": "An inline company payload, resolved via `find-or-create` before the job is created. **Required unless you supply `companyId`** — exactly one of the two must be present, and supplying both is an error."
    },
    "JobCollectionOverride": {
      "type": "object",
      "properties": {
        "fieldKey": {
          "type": "string",
          "minLength": 1,
          "description": "The collection field key used in `collectionValues`."
        },
        "recordId": {
          "type": "string",
          "minLength": 1,
          "description": "A record ID selected for that field on this job."
        },
        "title": {
          "type": "string",
          "maxLength": 160,
          "description": "The entry's title on this job, up to 160 characters. Omit or send a blank string to use the entry name."
        },
        "description": {
          "type": "string",
          "maxLength": 2000,
          "description": "The entry's description on this job, up to 2000 characters of plain text. When the default description field is rich text, the entry's `description` returns it escaped as HTML. Omit or send a blank string to use the default description."
        }
      },
      "required": [
        "fieldKey",
        "recordId"
      ]
    },
    "JobOfficeLocationInput": {
      "anyOf": [
        {
          "type": "object",
          "properties": {
            "query": {
              "type": "string",
              "minLength": 1,
              "maxLength": 500,
              "description": "Free-form location string (e.g. `\"Berlin, Germany\"`, `\"Utrecht, Netherlands\"`, `\"Mountain View, California, USA\"`). Resolved server-side; rejected when no high-confidence match is found."
            }
          },
          "required": [
            "query"
          ],
          "additionalProperties": false
        },
        {
          "type": "object",
          "properties": {
            "locality": {
              "type": "string",
              "minLength": 1,
              "description": "Neighborhood or sub-locality."
            },
            "city": {
              "type": "string",
              "minLength": 1,
              "description": "City."
            },
            "region": {
              "type": "string",
              "minLength": 1,
              "description": "Region, state, or province."
            },
            "country": {
              "type": "string",
              "minLength": 1,
              "description": "ISO 3166-1 alpha-2 country code OR recognized country name/alias. Aliases are normalized to canonical alpha-2 server-side (e.g. `US`, `USA`, `United States` → `US`; `UK`, `GB`, `United Kingdom` → `GB`)."
            }
          },
          "required": [
            "city",
            "country"
          ],
          "additionalProperties": false
        }
      ]
    }
  }
}