{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://artifacts.apis.io/100hires-com/json-schema/100hires-com-job-create-request-schema.json",
  "title": "JobCreateRequest",
  "x-generated": "2026-10-31",
  "x-method": "derived",
  "x-generator": "derive-json-schema.py",
  "x-source": "openapi/100hires-com-openapi.json#/components/schemas/JobCreateRequest",
  "type": "object",
  "required": [
    "status",
    "title",
    "description",
    "location_country"
  ],
  "properties": {
    "company_id": {
      "type": "integer",
      "description": "Target company ID. Required only when the API key has access to multiple companies."
    },
    "form_id": {
      "type": "integer",
      "description": "Application form ID. If omitted, a new form named after the job title is created with default questions (First name, Last name, Email, Phone)."
    },
    "status": {
      "type": "string",
      "description": "Job status (e.g. Draft, Public). See GET /taxonomy/statuses."
    },
    "title": {
      "type": "string",
      "description": "Public job title."
    },
    "internal_title": {
      "type": "string",
      "description": "Internal-only title visible to the hiring team."
    },
    "internal_job_id": {
      "type": "string",
      "description": "External reference ID from your ATS or HR system."
    },
    "description": {
      "type": "string",
      "description": "Job description (HTML allowed)."
    },
    "language": {
      "type": [
        "string",
        "null"
      ],
      "minLength": 1,
      "maxLength": 35,
      "description": "Locale the job page renders in, overriding the career site language (e.g. de-DE; underscore aliases like de_DE are normalized). Omit it or send null to follow the career site language. Unknown, disabled, empty and longer-than-35-character values are rejected with a 400 validation error on `language`. Available locales come from GET /languages."
    },
    "resume_field_status": {
      "type": "string",
      "description": "Resume field behavior on the application form.",
      "enum": [
        "required",
        "optional",
        "hidden"
      ]
    },
    "location_city": {
      "type": "string",
      "description": "Job city. Required unless is_remote is true; leave city, state and postal code empty for a nationwide remote posting on Indeed. Street, city, state, postal code and country are validated together on save, the same way the back-office form checks them: with Google Address Validation in the countries it covers, with a geocoding provider elsewhere. A confirmed location is stored as validated, so a misspelt city is corrected; a location that is unknown, suspicious (for example a city in the wrong state) or would need a part replaced is stored as sent, and the job boards tab of the job reports it until it is fixed."
    },
    "location_country": {
      "type": "string",
      "description": "Job country: a country name or an ISO 3166-1 alpha-2 code from the country list. Anything else is rejected with a validation error."
    },
    "location_state": {
      "type": "string",
      "description": "Job state or region."
    },
    "location_street_address": {
      "type": "string",
      "description": "Street address."
    },
    "location_full_address": {
      "type": "string",
      "description": "Accepted and ignored: the job address is assembled from street, city, state, postal code and country. Kept for compatibility."
    },
    "location_postal_code": {
      "type": "string",
      "description": "Postal or ZIP code."
    },
    "salary_min": {
      "type": "number",
      "description": "Minimum salary."
    },
    "salary_max": {
      "type": "number",
      "description": "Maximum salary."
    },
    "salary_currency": {
      "type": "string",
      "description": "Salary currency code (e.g. USD, EUR)."
    },
    "salary_period": {
      "type": "string",
      "description": "Salary period. A job created without it is \"annually\". An \"annually\" salary with an amount below 1000 is rejected as an hourly rate typed against the yearly period: send \"hourly\" instead.",
      "enum": [
        "annually",
        "monthly",
        "daily",
        "hourly"
      ]
    },
    "is_remote": {
      "type": "boolean",
      "description": "Whether this is a remote position. With an empty city, state and postal code Indeed lists it nationwide."
    },
    "is_hybrid": {
      "type": "boolean",
      "description": "Hybrid position: office days plus remote days at the job address, so location_city stays required. Requires is_remote false; sending both as true is rejected, and is_remote true on a hybrid job clears is_hybrid. Indeed lists it as Hybrid Remote, LinkedIn as Hybrid, other boards as an on-site job at that address."
    },
    "job_board_remote_scope": {
      "type": [
        "string",
        "null"
      ],
      "enum": [
        "city",
        "statewide",
        "nationwide",
        null
      ],
      "default": "city",
      "description": "How a remote job is listed on Indeed: city (default) keeps the address, statewide sends the state alone, nationwide the country alone. Other boards always get the full address. Non-city requires is_remote true, statewide also location_state; null resets to city."
    },
    "employment_type_id": {
      "type": "integer",
      "description": "Employment type ID from GET /taxonomy/employment-types."
    },
    "department_id": {
      "type": "integer",
      "description": "Department ID from GET /taxonomy/departments."
    },
    "category_id": {
      "type": "integer",
      "description": "Job category ID from GET /taxonomy/categories."
    },
    "education_level_id": {
      "type": "integer",
      "description": "Education level ID from GET /taxonomy/education-levels."
    },
    "experience_level_id": {
      "type": "integer",
      "description": "Experience level ID from GET /taxonomy/experience-levels."
    },
    "workflow_id": {
      "type": "integer",
      "description": "Workflow ID. If omitted, a new workflow named after the job title is created with default stages."
    },
    "parent_job_id": {
      "type": "integer",
      "description": "Canonical parent job ID. If provided, the created job becomes a satellite job."
    },
    "knockout_questions": {
      "type": "array",
      "description": "Boolean knockout questions added to the application form. Each question with `disqualify_on_wrong_answer=true` creates an auto-disqualify rule on the Applied stage.",
      "items": {
        "type": "object",
        "required": [
          "text",
          "expected_answer",
          "disqualify_on_wrong_answer"
        ],
        "properties": {
          "text": {
            "type": "string",
            "description": "Question text shown to the applicant."
          },
          "expected_answer": {
            "type": "string",
            "enum": [
              "Yes",
              "No"
            ],
            "description": "The correct/desired answer."
          },
          "disqualify_on_wrong_answer": {
            "type": "boolean",
            "description": "If true, applicants who answer incorrectly are automatically disqualified."
          }
        }
      }
    },
    "ai_scoring_criteria": {
      "type": "array",
      "description": "AI scoring criteria for evaluating candidates against this job.\nProviding this field is a full replace: existing criteria are detached\n(preserved if used in past evaluations, soft-deleted otherwise) and the\nsupplied list becomes the current set. Omit the field to leave defaults\nuntouched. Pass an empty array to detach all criteria.\n",
      "items": {
        "$ref": "#/$defs/AiScoringCriterionInput"
      }
    }
  },
  "additionalProperties": false,
  "$defs": {
    "AiScoringCriterionInput": {
      "type": "object",
      "required": [
        "text",
        "weight"
      ],
      "properties": {
        "id": {
          "type": "integer",
          "description": "ID of an existing criterion attached to this job. When present, the\ncorresponding criterion is updated in place (or, if it has scores,\ndetached and replaced — `ManageCriteria::createOrUpdate` semantics).\nOmit `id` to create a new criterion. Items existing on the job that\nare not present in the request (matched by `id`) are removed\n(soft-deleted if unscored, detached if used in past evaluations).\n"
        },
        "title": {
          "type": "string",
          "description": "Optional short label for the criterion."
        },
        "text": {
          "type": "string",
          "description": "Prompt text describing what to evaluate. Required."
        },
        "weight": {
          "type": "integer",
          "minimum": 1,
          "maximum": 10,
          "description": "Relative importance of this criterion in the total score (1–10)."
        }
      }
    }
  }
}