{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://artifacts.apis.io/jobmojito-com/json-schema/jobmojito-com-job-interview-get-response-schema.json",
  "title": "JobInterviewGetResponse",
  "x-generated": "2026-11-13",
  "x-method": "derived",
  "x-generator": "derive-json-schema.py",
  "x-source": "openapi/jobmojito-com-openapi.json#/components/schemas/JobInterviewGetResponse",
  "type": "object",
  "properties": {
    "name": {
      "type": [
        "string",
        "null"
      ],
      "description": "Interview or position name."
    },
    "created_at": {
      "type": [
        "string",
        "null"
      ]
    },
    "updated_at": {
      "type": [
        "string",
        "null"
      ]
    },
    "calc_definition_json": {
      "nullable": true,
      "description": "The compiled interview definition JSON (structure varies by interview type)."
    },
    "questions": {
      "type": [
        "array",
        "null"
      ],
      "items": {
        "$ref": "#/$defs/JobInterviewCreateFromArrayQuestion"
      },
      "description": "The interview questions, in the order they are asked, in the same format job-interview-create-from-array accepts — so this array can be edited and sent straight back to job-interview-update, or used to create a copy of this interview. Each `id` is the question's real identifier: send it back unchanged and the question keeps its existing record (and with it its answer rules and any rendered avatar video). The welcome, thank-you and instructional-video steps are NOT included — they are not questions in this format, and job-interview-update leaves them untouched. Null for multi-stage positions, whose questions live on the individual interview stages."
    },
    "is_multistage": {
      "type": "boolean",
      "description": "True when the id resolved to a multi-stage position rather than a single interview."
    },
    "status": {
      "type": "string",
      "enum": [
        "draft",
        "active",
        "archived",
        "deleted",
        "preparing",
        "completed"
      ],
      "description": "Publication status of the interview/position. The vacancy lifecycle of a position (open, on hold, filled...) is `lifecycle` (include=lifecycle)."
    },
    "visibility": {
      "type": "string",
      "enum": [
        "public",
        "merchant_public",
        "merchant_invite",
        "merchant_unlisted",
        "sub_item"
      ],
      "description": "Who can see and access the interview/position."
    },
    "stage": {
      "type": [
        "string",
        "null"
      ],
      "enum": [
        "Interest",
        "Application submitted",
        "Interview 1st planned",
        "Interview 1st completed",
        "Interview 2nd planned",
        "Interview 2nd completed",
        "Interview 3rd planned",
        "Interview 3rd completed",
        "Offer negotiations",
        "Hired",
        "Rejected",
        "Offer declined",
        "Other"
      ],
      "description": "Hiring-pipeline stage. Null for multi-stage positions."
    },
    "type": {
      "type": [
        "string",
        "null"
      ],
      "enum": [
        "coaching",
        "interview",
        "persona",
        "persona_interview",
        "public_avatar",
        "assessment"
      ],
      "description": "Interview type. Null for multi-stage positions."
    },
    "environment": {
      "type": [
        "string",
        "null"
      ],
      "enum": [
        "production",
        "uat",
        "development",
        "demo"
      ],
      "description": "Which webhook environment this interview's results are delivered to. Null for multi-stage positions, whose stages each carry their own."
    },
    "code": {
      "type": [
        "string",
        "null"
      ],
      "description": "Caller-supplied external code/reference."
    },
    "interview_location": {
      "type": [
        "string",
        "null"
      ],
      "description": "Location (create field `location`)."
    },
    "cover_image_url": {
      "type": [
        "string",
        "null"
      ],
      "description": "Cover image URL."
    },
    "merchant_id": {
      "type": [
        "string",
        "null"
      ],
      "description": "Owning merchant id."
    },
    "interview_template_id": {
      "type": [
        "string",
        "null"
      ],
      "description": "Interview template id. For multi-stage positions this is the first interview step's template."
    },
    "interview_template_type": {
      "type": [
        "string",
        "null"
      ],
      "enum": [
        "offline_elai",
        "offline_synthesia",
        "interactive_heygen",
        "offline_heygen",
        "interactive_elevenlabs",
        "interactive_spatius"
      ],
      "description": "Type of the linked interview template. `interactive_elevenlabs` is voice-only; the others (`interactive_spatius`, `interactive_heygen`, `offline_heygen`, `offline_elai`, `offline_synthesia`) are avatar-based. Null when the template could not be resolved."
    },
    "is_voice_only": {
      "type": [
        "boolean",
        "null"
      ],
      "description": "Convenience flag derived from interview_template_type: true when voice-only (`interactive_elevenlabs`), false when avatar-based, null when the template type could not be resolved."
    },
    "knowledge_base_store_id": {
      "type": [
        "string",
        "null"
      ],
      "description": "Linked knowledge base store id. Null for multi-stage positions."
    },
    "mojito_language_code": {
      "type": [
        "string",
        "null"
      ],
      "description": "Platform (mojito) language code."
    },
    "speech_language_code": {
      "type": [
        "string",
        "null"
      ],
      "description": "Azure speech language code. Null for multi-stage positions."
    },
    "speech_language_name": {
      "type": [
        "string",
        "null"
      ],
      "description": "Azure speech language display name. Null for multi-stage positions."
    },
    "recording": {
      "type": [
        "string",
        "null"
      ],
      "enum": [
        "audio_first_5_answers",
        "audio_all",
        "video_all",
        "video_first_5_answers"
      ],
      "description": "Per-answer recording mode. Null for multi-stage positions."
    },
    "recording_full_session": {
      "type": [
        "string",
        "null"
      ],
      "enum": [
        "audio_first_5_answers",
        "audio_all",
        "video_all",
        "video_first_5_answers"
      ],
      "description": "Full-session recording mode. Null for multi-stage positions."
    },
    "type_credit": {
      "type": [
        "string",
        "null"
      ],
      "enum": [
        "resume_check",
        "interview_coach_starter",
        "interview_coach_contributor",
        "interview_coach_manager",
        "cover_letter"
      ],
      "description": "Credit bucket the interview draws from. Null for multi-stage positions."
    },
    "result_view": {
      "type": [
        "string",
        "null"
      ],
      "enum": [
        "minimal",
        "advanced",
        "full",
        "full_expand_scores",
        "minimal_with_score",
        "none"
      ],
      "description": "Result view level. Null for multi-stage positions."
    },
    "candidate_video_introduction": {
      "type": [
        "string",
        "null"
      ],
      "enum": [
        "hidden",
        "optional",
        "required"
      ],
      "description": "Whether a candidate video introduction is hidden/optional/required. Null for multi-stage positions."
    },
    "description": {
      "type": [
        "string",
        "null"
      ],
      "description": "Short description."
    },
    "interview_description_long": {
      "type": [
        "string",
        "null"
      ],
      "description": "Long description (create/update field `description_long`). Rendered as Markdown on the candidate-facing position page — formatting guide: https://developer.jobmojito.com/cookbooks/format-content-with-markdown"
    },
    "candidate_expectations": {
      "type": [
        "string",
        "null"
      ],
      "description": "Free-text candidate expectations. Null for multi-stage positions."
    },
    "candidate_expectations_json": {
      "nullable": true,
      "description": "Structured candidate expectations JSON — the scoring rubric. Null for multi-stage positions."
    },
    "result_scoring": {
      "nullable": true,
      "description": "Resolved result-scoring config (update field `custom_scoring`; its `max_retries` is the update field `interview_attempts`). Null means the platform defaults apply. Null for multi-stage positions."
    },
    "creation_parameters": {
      "nullable": true,
      "description": "The creation parameters recorded at build time (interview_type, interview_tone, interview_length, additional_context, include_rapport_question, include_closing_prompt, knowledge_base_store_id, seniority_level, hiring_for_company)."
    },
    "interview_department": {
      "type": [
        "string",
        "null"
      ],
      "description": "Department the position belongs to."
    },
    "interview_salary": {
      "type": [
        "string",
        "null"
      ],
      "description": "Salary range shown for the position."
    },
    "interview_available_till": {
      "type": [
        "string",
        "null"
      ],
      "description": "ISO date/time after which the interview is no longer available to candidates. Null means always available."
    },
    "candidate_notification_channel": {
      "type": [
        "string",
        "null"
      ],
      "enum": [
        "reminders",
        "last_reminder",
        "all"
      ],
      "description": "SMS / WhatsApp candidate notifications: null = off, reminders = with both e-mail reminders, last_reminder = only with the final reminder, all = invitation + reminders + pre-screening accepted."
    },
    "country_availability": {
      "nullable": true,
      "description": "Portal availability by the candidate's country: {countries_allowed?: string[], countries_allowed_eu?: boolean, countries_blocked?: string[], countries_blocked_eu?: boolean} (ISO 3166-1 alpha-2, EU = EU + EEA + Switzerland; blocked wins), judged by the candidate's IP on the portal; invited candidates keep access. Null = not used, every country. Set it with job-interview-update."
    },
    "recruiter_profile_id": {
      "type": [
        "string",
        "null"
      ],
      "description": "Profile id of the recruiter owning this interview/position."
    },
    "slug": {
      "type": [
        "string",
        "null"
      ],
      "description": "URL slug of the public listing, when one was generated."
    },
    "tags": {
      "type": [
        "array",
        "null"
      ],
      "items": {
        "type": "string"
      },
      "description": "Free-form tags (interview_def_set.tags, or position_def_set.tags for a multi-stage position). Also the coaching-catalogue mapping key: a catalogue directory lists this session when its `tags_interview_set_filter` is a subset of these tags. Null when none are set."
    },
    "coach_plan": {
      "type": [
        "string",
        "null"
      ],
      "enum": [
        "demo",
        "screening",
        "2nd",
        "3rd",
        "closing",
        "job-specific",
        "other"
      ],
      "description": "Coaching-plan stage this session belongs to. Null for multi-stage positions and for sessions outside any plan."
    },
    "interview_conversation_speed": {
      "type": [
        "string",
        "null"
      ],
      "description": "Conversation pace of the AI avatar (slower/normal/faster/fluent). Null keeps the template default. Null for multi-stage positions."
    },
    "max_followups": {
      "type": [
        "number",
        "null"
      ],
      "description": "Maximum number of AI follow-up questions; null uses the template default. Null for multi-stage positions."
    },
    "max_duration": {
      "type": [
        "number",
        "null"
      ],
      "description": "Live session limit in seconds. Null for multi-stage positions."
    },
    "questions_random_subset": {
      "type": [
        "number",
        "null"
      ],
      "description": "Fraction of the questions actually asked (0.01-0.9); null asks all of them. Null for multi-stage positions."
    },
    "required_pronunciation": {
      "type": [
        "boolean",
        "null"
      ],
      "description": "Whether a pronunciation assessment is required. Null for multi-stage positions."
    },
    "result_enable_edit_transcript": {
      "type": [
        "boolean",
        "null"
      ],
      "description": "Whether the candidate may edit the transcript on the result view. Null for multi-stage positions."
    },
    "pdf_export_auto_config": {
      "nullable": true,
      "description": "Auto-PDF-report options applied when the interview completes; null when auto-export is off. Null for multi-stage positions."
    },
    "interview_category": {
      "type": [
        "string",
        "null"
      ],
      "description": "Job category shown on public listings above the department (free text), e.g. \"Ashore\"."
    },
    "emoji": {
      "type": [
        "string",
        "null"
      ],
      "description": "Emoji marker used in the admin lists."
    },
    "reminder_schedule": {
      "nullable": true,
      "description": "Candidate reminder schedule override (JSON); null uses the company default."
    },
    "is_ats": {
      "type": "boolean",
      "description": "True when the definition is linked to a job in a connected ATS (Kombo)."
    },
    "collect_resume_cover_letter": {
      "type": [
        "string",
        "null"
      ],
      "enum": [
        "hidden",
        "optional",
        "required"
      ],
      "description": "Whether a resume / cover letter is asked for: hidden, optional or required. Null for multi-stage positions."
    },
    "allow_human_interview": {
      "type": [
        "boolean",
        "null"
      ],
      "description": "A recruiter may hold the interview instead of the AI. Null for multi-stage positions (each interview step carries its own, see include=process)."
    },
    "target_days_to_fill": {
      "type": [
        "number",
        "null"
      ],
      "description": "Target days to fill the position. Null for single-stage interviews."
    },
    "access_scope": {
      "type": [
        "string",
        "null"
      ],
      "enum": [
        "company",
        "confidential"
      ],
      "description": "Position access: company (every recruiter) or confidential (its team and owners only). Null for single-stage interviews."
    },
    "process": {
      "$ref": "#/$defs/PositionProcess"
    },
    "team": {
      "$ref": "#/$defs/PositionTeam"
    },
    "lifecycle": {
      "$ref": "#/$defs/PositionLifecycle"
    },
    "openings": {
      "$ref": "#/$defs/PositionOpenings"
    },
    "publishing": {
      "$ref": "#/$defs/PositionPublishing"
    },
    "custom_fields": {
      "type": "object",
      "additionalProperties": {
        "nullable": true
      },
      "description": "Company-defined fields (key: value) of a multi-stage position, when custom fields are enabled for the company (multi-stage positions only: absent for an interview). Keys are the field keys of the company's custom field definitions; values are text, numbers, YYYY-MM-DD dates, option values (a list for multiple choice), {min, max, currency} ranges or user profile ids."
    }
  },
  "required": [
    "name",
    "created_at",
    "updated_at",
    "questions",
    "is_multistage",
    "status",
    "visibility",
    "stage",
    "type",
    "environment",
    "code",
    "interview_location",
    "cover_image_url",
    "merchant_id",
    "interview_template_id",
    "interview_template_type",
    "is_voice_only",
    "knowledge_base_store_id",
    "mojito_language_code",
    "speech_language_code",
    "speech_language_name",
    "recording",
    "recording_full_session",
    "type_credit",
    "result_view",
    "candidate_video_introduction",
    "description",
    "interview_description_long",
    "candidate_expectations",
    "interview_department",
    "interview_salary",
    "interview_available_till",
    "candidate_notification_channel",
    "recruiter_profile_id",
    "slug",
    "tags",
    "coach_plan",
    "interview_conversation_speed",
    "max_followups",
    "max_duration",
    "questions_random_subset",
    "required_pronunciation",
    "result_enable_edit_transcript",
    "interview_category",
    "emoji",
    "is_ats",
    "collect_resume_cover_letter",
    "allow_human_interview",
    "target_days_to_fill",
    "access_scope"
  ],
  "$defs": {
    "JobInterviewCreateFromArrayQuestion": {
      "type": "object",
      "properties": {
        "question": {
          "type": "string",
          "description": "The question text shown to the candidate."
        },
        "id": {
          "type": "string",
          "description": "Identifier for this question. job-interview-get returns the question's real id here; send it back to job-interview-update so an unchanged question keeps its existing record (and with it its answer rules and any rendered avatar video). Also the handle another question references via conditional_question_main_id. On job-interview-create-from-array it is a caller-local value, only needed for those references."
        },
        "duration": {
          "type": [
            "number",
            "null"
          ],
          "description": "Answer duration in seconds for this question."
        },
        "mojito_language_code": {
          "type": [
            "string",
            "null"
          ],
          "enum": [
            "ar",
            "bg",
            "zh",
            "hr",
            "cs",
            "da",
            "nl",
            "en",
            "fil",
            "fi",
            "fr",
            "de",
            "el",
            "hi",
            "hu",
            "id",
            "it",
            "ja",
            "ko",
            "ms",
            "no",
            "pl",
            "pt",
            "br",
            "ro",
            "ru",
            "sk",
            "es",
            "sv",
            "ta",
            "th",
            "tr",
            "uk",
            "vi"
          ],
          "description": "Per-question language override (one of the listed codes). Inherits the interview language when omitted."
        },
        "label": {
          "type": [
            "string",
            "null"
          ],
          "description": "Optional label/tag stored on the question."
        },
        "is_conditional": {
          "type": [
            "boolean",
            "null"
          ],
          "description": "A yes/no question that decides which follow-ups are asked (view 'with listening conditional'). Its follow-ups point at it with conditional_question_main_id."
        },
        "is_without_scoring": {
          "type": [
            "boolean",
            "null"
          ],
          "description": "Question is asked but not scored (view 'without scoring')."
        },
        "is_candidate_asking_recruiter": {
          "type": [
            "boolean",
            "null"
          ],
          "description": "Candidate-asks-recruiter prompt (view 'candidate asking recruiter')."
        },
        "is_expert": {
          "type": [
            "boolean",
            "null"
          ],
          "description": "Expert listening question (view 'with listening expert')."
        },
        "is_multiple_choice": {
          "type": [
            "boolean",
            "null"
          ],
          "description": "Multiple-choice question (view 'multiple choice')."
        },
        "question_alternatives": {
          "type": [
            "array",
            "null"
          ],
          "items": {
            "type": "string"
          },
          "description": "Alternative phrasings for the question."
        },
        "conditional_question_main_id": {
          "type": [
            "string",
            "null"
          ],
          "description": "For a follow-up question, the id (the \"id\" field above) of the yes/no question (is_conditional) in this same array that decides whether it is asked. The yes/no question must appear earlier in the array. Use conditional_question_answer to choose the answer that shows it."
        },
        "conditional_question_answer": {
          "type": [
            "boolean",
            "null"
          ],
          "description": "With conditional_question_main_id: the answer to the yes/no question that shows this follow-up. `true` (the default) asks it after a yes, `false` after a no, so each answer can lead to its own follow-ups. Ignored on a question without conditional_question_main_id."
        },
        "is_skippable": {
          "type": [
            "boolean",
            "null"
          ],
          "description": "The candidate may skip this question: the recorded (offline avatar) interview shows a Skip button on it, and a skipped question is saved without an answer. Defaults to false."
        },
        "knowledge_base_id": {
          "type": [
            "string",
            "null"
          ],
          "description": "Knowledge-base store id (uuid) the question draws context from."
        },
        "external_id": {
          "type": [
            "string",
            "null"
          ],
          "description": "External identifier stored on the question. job-interview-update matches on this first, so an ATS that owns stable ids can send its own array and have the diff line up without round-tripping our ids."
        },
        "external_data": {
          "type": [
            "object",
            "null"
          ],
          "additionalProperties": {
            "nullable": true
          },
          "description": "Arbitrary JSON metadata stored on the question."
        },
        "candidate_expectations_json": {
          "type": [
            "object",
            "null"
          ],
          "properties": {
            "weak": {
              "type": "array",
              "items": {
                "type": "string"
              },
              "description": "Baseline requirements every viable candidate should meet (table stakes)."
            },
            "moderate": {
              "type": "array",
              "items": {
                "type": "string"
              },
              "description": "Requirements expected of a solid, competent candidate."
            },
            "strong": {
              "type": "array",
              "items": {
                "type": "string"
              },
              "description": "High-bar requirements only standout candidates clear."
            }
          },
          "description": "Per-question candidate expectations, bucketed by requirement level (weak/moderate/strong). Extra keys are preserved."
        }
      },
      "required": [
        "question"
      ],
      "additionalProperties": {
        "nullable": true
      }
    },
    "PositionLifecycle": {
      "type": "object",
      "properties": {
        "status": {
          "type": "string",
          "enum": [
            "draft",
            "pending_approval",
            "open",
            "applications_closed",
            "on_hold",
            "filled",
            "cancelled",
            "closed"
          ],
          "description": "Vacancy lifecycle. Without a stored lifecycle it follows the publication status: draft/preparing = draft, active = open, archived/completed = closed."
        },
        "reason": {
          "type": [
            "string",
            "null"
          ],
          "description": "Label of the reason picked for the last change (position status reasons)."
        },
        "note": {
          "type": [
            "string",
            "null"
          ],
          "description": "Note given with the last change."
        },
        "changed_at": {
          "type": [
            "string",
            "null"
          ],
          "description": "When the lifecycle last changed (ISO 8601)."
        }
      },
      "required": [
        "status",
        "reason",
        "note",
        "changed_at"
      ],
      "description": "include=lifecycle (multi-stage positions, the position_lifecycle feature): the vacancy lifecycle."
    },
    "PositionMeetingSlot": {
      "type": "object",
      "properties": {
        "id": {
          "type": "string",
          "description": "Slot id (position_meeting_slot.id)."
        },
        "start_at": {
          "type": "string",
          "description": "Start (ISO 8601)."
        },
        "duration_minutes": {
          "type": "number",
          "description": "Length in minutes."
        },
        "capacity": {
          "type": "number",
          "description": "How many candidates can book it."
        },
        "booked": {
          "type": "number",
          "description": "Active bookings (booking_status booked)."
        },
        "location": {
          "type": [
            "string",
            "null"
          ],
          "description": "Place of an in-person meeting."
        },
        "meeting_url": {
          "type": [
            "string",
            "null"
          ],
          "description": "Video call link."
        },
        "interviewer_profile_id": {
          "type": [
            "string",
            "null"
          ],
          "description": "Interviewer (profile.id)."
        },
        "panel": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "profile_id": {
                "type": "string",
                "description": "Panel member (profile.id)."
              },
              "role": {
                "type": "string",
                "enum": [
                  "lead",
                  "member"
                ],
                "description": "Panel role: lead or member."
              }
            },
            "required": [
              "profile_id",
              "role"
            ]
          },
          "description": "Panel interviewers of the slot (position_meeting_slot_interviewer); empty when none."
        }
      },
      "required": [
        "id",
        "start_at",
        "duration_minutes",
        "capacity",
        "booked",
        "location",
        "meeting_url",
        "interviewer_profile_id",
        "panel"
      ]
    },
    "PositionOpenings": {
      "type": "object",
      "properties": {
        "openings_total": {
          "type": [
            "number",
            "null"
          ],
          "description": "Openings to fill; null = not set."
        },
        "filled": {
          "type": "number",
          "description": "Openings filled (hired)."
        },
        "remaining": {
          "type": [
            "number",
            "null"
          ],
          "description": "Openings still to fill; null when openings_total is not set."
        },
        "auto_close": {
          "type": "boolean",
          "description": "The position closes by itself once every opening is filled."
        }
      },
      "required": [
        "openings_total",
        "filled",
        "remaining",
        "auto_close"
      ],
      "description": "include=lifecycle (multi-stage positions, the position_bulk_create feature): openings and how many are filled."
    },
    "PositionProcess": {
      "type": "object",
      "properties": {
        "structure_editable": {
          "type": "boolean",
          "description": "True while the position is a draft: only then can steps be added, removed or reordered. Step settings can be changed at any time."
        },
        "steps": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/PositionProcessStep"
          },
          "description": "The live steps, in order (deleted steps left out). Empty for a hiring manager account, which cannot read steps."
        }
      },
      "required": [
        "structure_editable",
        "steps"
      ],
      "description": "include=process (multi-stage positions): the ordered steps with typed settings."
    },
    "PositionProcessStep": {
      "type": "object",
      "properties": {
        "id": {
          "type": "string",
          "format": "uuid",
          "description": "The step (position_def_step.id)."
        },
        "type": {
          "type": "string",
          "enum": [
            "pre-screening",
            "interview",
            "invited",
            "meeting",
            "external_assessment",
            "certo_verification"
          ],
          "description": "Step type: pre-screening, interview (AI interview), meeting, external_assessment, certo_verification, or the legacy invited."
        },
        "name": {
          "type": [
            "string",
            "null"
          ],
          "description": "The name recruiters gave the step; null = the type's default label."
        },
        "position_id": {
          "type": "string",
          "format": "uuid",
          "description": "The position (position_def_set.id)."
        },
        "sequence_no": {
          "type": "number",
          "description": "Place in the process, 1 = first."
        },
        "updated_at": {
          "type": "string",
          "description": "Version of the step (its last change, else when it was made). Send it back as expected_updated_at to merchant-position-step-update so a concurrent change is refused instead of overwritten."
        },
        "interview_set_id": {
          "type": "string",
          "format": "uuid",
          "description": "AI interview steps: the interview (interview_def_set.id); its questions are changed with update_interview."
        },
        "interview_pre_screening_id": {
          "type": "string",
          "format": "uuid",
          "description": "Pre-screening steps: interview_def_pre_screening.id."
        },
        "advance": {
          "type": "string",
          "enum": [
            "auto",
            "manual"
          ],
          "description": "auto or manual (see the request field)."
        },
        "kombo": {
          "$ref": "#/$defs/PositionStepKombo"
        },
        "meeting": {
          "$ref": "#/$defs/PositionStepMeeting"
        },
        "certo": {
          "type": "object",
          "properties": {
            "wait_for_identity": {
              "type": "boolean",
              "description": "See the request field."
            },
            "include_contact": {
              "type": "boolean",
              "description": "See the request field."
            },
            "scope_types": {
              "type": [
                "array",
                "null"
              ],
              "items": {
                "type": "string",
                "enum": [
                  "employment",
                  "education",
                  "certification",
                  "role",
                  "language",
                  "right_to_work"
                ]
              },
              "description": "null = everything."
            }
          },
          "required": [
            "wait_for_identity",
            "include_contact",
            "scope_types"
          ],
          "description": "certo_verification steps."
        },
        "external_assessment": {
          "$ref": "#/$defs/PositionStepExternalAssessment"
        },
        "interview": {
          "$ref": "#/$defs/PositionStepInterview"
        },
        "pre_screening": {
          "$ref": "#/$defs/PositionStepPreScreening"
        },
        "status": {
          "type": "string",
          "enum": [
            "draft",
            "active",
            "archived",
            "deleted",
            "preparing",
            "completed"
          ],
          "description": "Publication status of the step."
        },
        "candidates_on_step": {
          "type": [
            "number",
            "null"
          ],
          "description": "Candidates in progress on the step (waiting for the candidate or a recruiter, shortlisted, processing, scheduled), test runs left out. Null when your account may not read the position's candidates."
        },
        "slots": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/PositionMeetingSlot"
          },
          "description": "include=slots, meeting steps only: the upcoming active slots, earliest first (at most 500 per position)."
        }
      },
      "required": [
        "id",
        "type",
        "name",
        "position_id",
        "sequence_no",
        "updated_at",
        "advance",
        "status",
        "candidates_on_step"
      ],
      "description": "A step with its settings: advance, kombo when mapped, and only the settings object of its own type (meeting, certo, external_assessment, interview, pre_screening)."
    },
    "PositionPublishing": {
      "type": "object",
      "properties": {
        "publish_from": {
          "type": [
            "string",
            "null"
          ],
          "description": "Go-live (ISO 8601); null = now."
        },
        "publish_until": {
          "type": [
            "string",
            "null"
          ],
          "description": "Closing time (ISO 8601); null = open-ended."
        },
        "audience": {
          "type": [
            "string",
            "null"
          ],
          "enum": [
            "external",
            "internal",
            "both"
          ],
          "description": "Who sees the listing: external, internal (employees), both."
        },
        "internal_until": {
          "type": [
            "string",
            "null"
          ],
          "description": "Audience both: employees only until this time (ISO 8601)."
        },
        "internal_days": {
          "type": [
            "number",
            "null"
          ],
          "description": "Audience both: internal-first days (internal_until minus publish_from); null when not computable."
        },
        "state": {
          "type": [
            "string",
            "null"
          ],
          "enum": [
            "scheduled",
            "closed",
            "internal",
            "public"
          ],
          "description": "What the window means now: scheduled, closed, internal or public."
        }
      },
      "required": [
        "publish_from",
        "publish_until",
        "audience",
        "internal_until",
        "internal_days",
        "state"
      ],
      "description": "include=publishing (multi-stage positions, the position_audience feature): the publishing window."
    },
    "PositionStepExternalAssessment": {
      "type": "object",
      "properties": {
        "provider_id": {
          "type": "string",
          "format": "uuid",
          "description": "An active assessment provider of this company, set up in the admin (a step's current one is in job-interview-get include=process). Required on a new step."
        },
        "instructions": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 3000,
          "description": "Shown to the candidate (falls back to the provider's instructions). null removes the setting (its default applies again); left out keeps it."
        },
        "due_days": {
          "type": "integer",
          "minimum": 1,
          "maximum": 60,
          "description": "Days the candidate has to finish, 1 to 60 (default 7)."
        },
        "pass_score": {
          "type": [
            "number",
            "null"
          ],
          "description": "Optional pass mark: a result without a pass flag passes when its score is at least this. null removes the setting (its default applies again); left out keeps it."
        }
      },
      "additionalProperties": false,
      "description": "Settings of an external assessment step (the external_assessment feature)."
    },
    "PositionStepInterview": {
      "type": "object",
      "properties": {
        "allow_human_interview": {
          "type": "boolean",
          "description": "Let candidates ask for an interview with a person instead of the AI. Question content is changed with update_interview, on the step's interview_set_id."
        }
      },
      "additionalProperties": false,
      "description": "Settings of an AI interview step."
    },
    "PositionStepKombo": {
      "type": "object",
      "properties": {
        "accept_stage_id": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 200,
          "description": "ATS stage (Kombo id) a candidate moves to when accepted on this step. null removes the setting (its default applies again); left out keeps it."
        },
        "reject_reason_id": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 200,
          "description": "ATS rejection reason (Kombo id) used when a recruiter rejects on this step. null removes the setting (its default applies again); left out keeps it."
        },
        "reject_reason_ai_id": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 200,
          "description": "ATS rejection reason (Kombo id) used when the AI rejects on this step. null removes the setting (its default applies again); left out keeps it."
        }
      },
      "additionalProperties": false,
      "description": "Mapping of this step to the connected ATS (Kombo). Each key is merged separately."
    },
    "PositionStepMeeting": {
      "type": "object",
      "properties": {
        "mode": {
          "type": [
            "string",
            "null"
          ],
          "enum": [
            "slots",
            "availability"
          ],
          "description": "How candidates book: slots (hand-made slots, the default; add them with manage_meeting_slot) or availability (free times of the interviewers). null removes the setting (its default applies again); left out keeps it."
        },
        "duration_minutes": {
          "type": [
            "integer",
            "null"
          ],
          "minimum": 5,
          "maximum": 1440,
          "description": "Meeting length in minutes, 5 to 1440 (default 30). null removes the setting (its default applies again); left out keeps it."
        },
        "location": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 500,
          "description": "Address or room shown to the candidate when a slot has none. null removes the setting (its default applies again); left out keeps it."
        },
        "conference_provider": {
          "type": [
            "string",
            "null"
          ],
          "enum": [
            "in_person",
            "fixed",
            "zoom",
            "teams",
            "meet"
          ],
          "description": "The meeting link: in_person (no link), fixed (meeting_url), zoom / teams / meet (made at booking with the interviewer's own calendar account, meeting_url is the fallback). Absent: fixed when meeting_url is set, else in_person. null removes the setting (its default applies again); left out keeps it."
        },
        "meeting_url": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 2000,
          "pattern": "^https?:\\/\\/\\S+$",
          "description": "Video link (http or https) used when a slot has none. null removes the setting (its default applies again); left out keeps it."
        },
        "instructions": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 4000,
          "description": "Instructions shown to the candidate. null removes the setting (its default applies again); left out keeps it."
        },
        "reschedule_cutoff_hours": {
          "type": [
            "integer",
            "null"
          ],
          "minimum": 0,
          "maximum": 8760,
          "description": "Hours before the meeting after which the candidate can no longer reschedule or cancel (default 24). null removes the setting (its default applies again); left out keeps it."
        },
        "nudge_after_days": {
          "type": [
            "integer",
            "null"
          ],
          "minimum": 0,
          "maximum": 365,
          "description": "Days after which a candidate who has not booked gets a reminder; 0 = never (default 3). null removes the setting (its default applies again); left out keeps it."
        },
        "interviewer_profile_ids": {
          "type": [
            "array",
            "null"
          ],
          "items": {
            "type": "string",
            "format": "uuid"
          },
          "maxItems": 50,
          "description": "Availability mode: the interviewers whose free times are offered (profile.id of users of the company); empty = the position's recruiter. null removes the setting (its default applies again); left out keeps it."
        },
        "panel_profile_ids": {
          "type": [
            "array",
            "null"
          ],
          "items": {
            "type": "string",
            "format": "uuid"
          },
          "minItems": 2,
          "maxItems": 8,
          "description": "Availability mode, company feature meeting_panels: a panel of 2 to 8 users who all attend (profile.id); the first leads the meeting and cannot be a read-only user. Candidates are offered only times when everyone is free. null = one interviewer. null removes the setting (its default applies again); left out keeps it."
        },
        "slot_interval_minutes": {
          "type": [
            "integer",
            "null"
          ],
          "minimum": 5,
          "maximum": 240,
          "description": "Availability mode: start times every N minutes, 5 to 240 (default 30). null removes the setting (its default applies again); left out keeps it."
        },
        "min_notice_hours": {
          "type": [
            "integer",
            "null"
          ],
          "minimum": 0,
          "maximum": 720,
          "description": "Availability mode: earliest booking, hours from now, 0 to 720 (default 12). null removes the setting (its default applies again); left out keeps it."
        },
        "horizon_days": {
          "type": [
            "integer",
            "null"
          ],
          "minimum": 1,
          "maximum": 365,
          "description": "Availability mode: latest booking, days from now, 1 to 365 (default 21). null removes the setting (its default applies again); left out keeps it."
        },
        "data_collection": {
          "type": [
            "object",
            "null"
          ],
          "properties": {
            "mode": {
              "type": "string",
              "enum": [
                "none",
                "form",
                "text"
              ],
              "description": "none, form (a meeting form, taxonomy_id) or text (free notes)."
            },
            "taxonomy_id": {
              "type": [
                "string",
                "null"
              ],
              "format": "uuid",
              "description": "A meeting form of this company, made in the admin (the current one is in job-interview-get include=process); required for mode form."
            }
          },
          "required": [
            "mode"
          ],
          "additionalProperties": false,
          "description": "What the interviewer records after the meeting. Replaced as a whole. null removes the setting (its default applies again); left out keeps it."
        },
        "email_variant": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 100,
          "description": "Needs the meeting_email_variants feature: the name of the e-mail version used for this step's meeting e-mails. null removes the setting (its default applies again); left out keeps it."
        }
      },
      "additionalProperties": false,
      "description": "Settings of a meeting step. Each key is merged separately; keys left out keep their value."
    },
    "PositionStepPreScreening": {
      "type": "object",
      "properties": {
        "type": {
          "type": "string",
          "enum": [
            "resume",
            "form",
            "resume_with_form"
          ],
          "description": "What the candidate sends: resume, form, or resume_with_form."
        },
        "form_fields": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "minLength": 1
              }
            },
            "required": [
              "id"
            ]
          },
          "maxItems": 100,
          "description": "The whole list of form fields (replaced, not merged), each with its own id: standard fields reuse the rule id (e.g. form_education, with mandatory), custom fields use custom_<timestamp> with title, field_type (text, file, radio), type (required, optional) and, for radio, options (a list of strings), e.g. {\"id\": \"custom_1731100000000\", \"title\": \"Do you have a work permit?\", \"field_type\": \"radio\", \"type\": \"required\", \"options\": [\"Yes\", \"No\"]}. A screening rule on it (assessment_rules) is optional."
        },
        "assessment_rules": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "minLength": 1
              }
            },
            "required": [
              "id"
            ]
          },
          "maxItems": 100,
          "description": "The whole list of rules (replaced, not merged), each with its own id: resume_match (resume_score_accept / resume_score_reject, whole numbers 0 to 10), resume_technical_experience, form_* rules with type screening, required or optional and action reject or mark_for_review."
        },
        "position_country": {
          "type": "string",
          "pattern": "^[A-Za-z]{2}$",
          "description": "ISO country code of the position; stored upper case."
        },
        "candidate_expectations": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 4000,
          "description": "What the AI expects of a candidate, in words. null or empty removes it."
        },
        "all_rules_fulfilled_auto_approve": {
          "type": "boolean",
          "description": "true: when every rule is fulfilled the pre-screening is approved without a recruiter's review. Whether the approved candidate then moves to the next step by themselves is `advance` (auto) or a recruiter's move (manual)."
        }
      },
      "additionalProperties": false,
      "description": "Settings of the pre-screening step."
    },
    "PositionTeam": {
      "type": "object",
      "properties": {
        "enabled": {
          "type": "boolean",
          "description": "Whether the company has the hiring team switched on (the hiring_team feature); members is empty while it is off."
        },
        "owner": {
          "type": [
            "object",
            "null"
          ],
          "properties": {
            "profile_id": {
              "type": [
                "string",
                "null"
              ],
              "description": "Owning recruiter (profile.id)."
            },
            "name": {
              "type": [
                "string",
                "null"
              ],
              "description": "Name."
            },
            "email": {
              "type": [
                "string",
                "null"
              ],
              "description": "E-mail."
            }
          },
          "required": [
            "profile_id",
            "name",
            "email"
          ],
          "description": "The position's recruiter (recruiter_profile_id); null when none."
        },
        "members": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "description": "Membership id (position_team.id)."
              },
              "role": {
                "type": "string",
                "enum": [
                  "recruiter",
                  "sourcer",
                  "coordinator",
                  "hiring_manager",
                  "interviewer",
                  "viewer"
                ],
                "description": "Role on the hiring team."
              },
              "profile_id": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "A person (profile.id); null for a group."
              },
              "profile_group_id": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "A group (profile_group.id); null for a person."
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Person or group name."
              },
              "email": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "E-mail of a person."
              },
              "group_name": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Group name."
              },
              "group_member_count": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "People in a group (with its subgroups)."
              }
            },
            "required": [
              "id",
              "role",
              "profile_id",
              "profile_group_id",
              "name",
              "email",
              "group_name",
              "group_member_count"
            ]
          },
          "description": "Members by role, then name."
        }
      },
      "required": [
        "enabled",
        "owner",
        "members"
      ],
      "description": "include=team (multi-stage positions): the hiring team."
    }
  }
}