openapi: 3.2.0 info: contact: email: support@aiapplyd.com name: AI Applyd url: https://aiapplyd.com description: 'The AI Applyd API runs the whole job hunt for you: it matches you to roles that fit your profile, rewrites your resume for each posting, writes the cover letter the form asks for, and auto-applies across the major applicant tracking systems.' license: name: Proprietary url: https://aiapplyd.com/legal/terms title: AI Applyd Dashboard API version: 115c47ce servers: - description: Current environment url: https://api.aiapplyd.com/api/v1 security: - BearerAuth: [] tags: - name: Dashboard paths: /dashboard/bootstrap: get: tags: - Dashboard summary: Dashboard bootstrap aggregate description: Single aggregate that collapses the per-slice dashboard reads (health, overview, analytics, jobs, documents, preferences, matches, agent config, pending review) into one envelope. Each slice degrades to a safe fallback on failure so one failing slice never 500s the whole response. responses: '200': description: Dashboard bootstrap aggregate envelope content: application/json: schema: $ref: '#/components/schemas/DashboardBootstrapResponse' '401': description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/ApiErrorResponse' '404': description: Not Found content: application/json: schema: $ref: '#/components/schemas/ApiErrorResponse' '500': description: Internal Server Error content: application/json: schema: $ref: '#/components/schemas/ApiErrorResponse' operationId: getDashboardBootstrap components: schemas: OverviewData: type: object properties: aiHistoryCount: type: integer description: Total number of AI operations example: 200 formsLeftData: $ref: '#/components/schemas/FormsLeftData' fillsOverTime: type: array items: $ref: '#/components/schemas/TimeSeriesData' description: AI operation counts over time averageTokensPerFill: type: number description: Average tokens used per operation example: 1500 totalTokensUsed: type: number description: Total tokens used example: 300000 successRate: $ref: '#/components/schemas/SuccessRateData' periodChange: type: number description: Period-over-period change percentage example: 15.5 jobListingsCount: type: integer description: Total number of active job listings example: 12 averageAtsScore: type: number description: Average ATS score across all scored jobs example: 72 outcomes: $ref: '#/components/schemas/OutcomeMetrics' required: - aiHistoryCount - formsLeftData - fillsOverTime - averageTokensPerFill - totalTokensUsed - successRate - periodChange - jobListingsCount - averageAtsScore - outcomes UserDocumentListData: type: - object - 'null' properties: items: type: array items: $ref: '#/components/schemas/UserDocument' pagination: type: object properties: hasNext: type: boolean hasPrev: type: boolean limit: type: integer exclusiveMinimum: 0 page: type: integer exclusiveMinimum: 0 totalCount: type: integer minimum: 0 totalPages: type: integer minimum: 0 required: - hasNext - hasPrev - limit - page - totalCount - totalPages additionalProperties: false required: - items - pagination AuthHealthUser: type: object properties: id: type: string description: User identifier example: user_123 email: type: string format: email description: User email address example: user@example.com name: type: - string - 'null' description: User display name example: John Doe image: type: - string - 'null' description: User avatar URL example: https://example.com/avatar.jpg phone: type: - string - 'null' description: User phone number example: '+1234567890' emailVerified: type: - string - 'null' description: Email verification timestamp example: '2024-01-01T00:00:00Z' stripeCustomerId: type: - string - 'null' description: Stripe customer ID example: cus_1234567890 role: type: - string - 'null' description: User role (e.g. "user" or "admin"). Admins are never forced through onboarding. example: user company: type: - string - 'null' description: User-supplied current employer name (B2B segmentation). example: Acme Corp companySlug: type: - string - 'null' description: Slugified company key used as PostHog group identifier. example: acme-corp createdAt: type: string description: Account creation timestamp example: '2024-01-01T00:00:00Z' updatedAt: type: string description: Last update timestamp example: '2024-01-01T00:00:00Z' required: - id - email - name - image - phone - emailVerified - stripeCustomerId - role - company - companySlug - createdAt - updatedAt ExperienceEntry: type: object properties: bullets: type: array items: type: string example: - Led migration of monolithic architecture to microservices, reducing deployment time by 60% company: type: string example: Acme Corp current: type: - boolean - 'null' endDate: type: - string - 'null' example: 2024-01 location: type: - string - 'null' startDate: type: string example: 2020-01 title: type: string example: Senior Engineer required: - bullets - company - startDate - title additionalProperties: false DashboardBootstrapData: type: object properties: health: $ref: '#/components/schemas/AuthHealthData' overview: allOf: - $ref: '#/components/schemas/OverviewData' - {} analytics: $ref: '#/components/schemas/AnalyticsData' jobs: type: array items: $ref: '#/components/schemas/JobListingListItem' userDocuments: $ref: '#/components/schemas/UserDocumentListData' userPreferences: $ref: '#/components/schemas/UserPreferencesData' jobMatches: $ref: '#/components/schemas/MatchListData' agentConfig: $ref: '#/components/schemas/ApplicationConfigData' pendingReview: type: object properties: items: type: array items: $ref: '#/components/schemas/ApplicationResultData' pagination: type: object properties: hasNext: type: boolean hasPrev: type: boolean limit: type: integer exclusiveMinimum: 0 page: type: integer exclusiveMinimum: 0 totalCount: type: integer minimum: 0 totalPages: type: integer minimum: 0 required: - hasNext - hasPrev - limit - page - totalCount - totalPages additionalProperties: false staleCount: type: integer minimum: 0 required: - items - pagination - staleCount tabCounts: type: - object - 'null' properties: saved: type: integer minimum: 0 applied: type: integer minimum: 0 skipped: type: integer minimum: 0 review: type: integer minimum: 0 required: - saved - applied - skipped - review degradedSlices: type: array items: type: string required: - health - overview - analytics - jobs - userDocuments - userPreferences - jobMatches - agentConfig - pendingReview - tabCounts MatchListData: type: object properties: items: type: array items: $ref: '#/components/schemas/MatchData' pagination: type: object properties: hasNext: type: boolean hasPrev: type: boolean limit: type: integer exclusiveMinimum: 0 page: type: integer exclusiveMinimum: 0 totalCount: type: integer minimum: -1 description: 'Total matches across all pages. Page-mode: a real count (>=0). Cursor-mode: the sentinel -1 (COUNT(*) skipped for keyset-paging speed); cursor callers read `hasNextPage`/`nextCursor` instead.' example: -1 totalPages: type: integer minimum: 0 hasNextPage: type: boolean description: True when another page is available. Mirrors `hasNext` for cursor-mode callers; page-mode callers can keep using `hasNext`. nextCursor: type: - string - 'null' description: Opaque cursor for the next keyset page, or null when no further rows. Pass back as `?cursor=` on the next request. Page-mode callers can ignore this. required: - hasNext - hasPrev - limit - page - totalCount - totalPages - hasNextPage - nextCursor additionalProperties: false searchCompleted: type: boolean default: false description: True once a scrape for the user's current criteria has completed at least once. Drives whether the empty state may say "nothing found" vs "still searching". example: false relaxation: type: - string - 'null' enum: - seniority_plus_two - role_family_neighbors - include_remote_auto - all_relaxations - null default: null description: Summary bucket describing how far the matcher widened the user's filters (null = strict match). Drives the Discover relaxation banner. example: null relaxationDetail: type: - object - 'null' properties: deltas: type: array items: type: object properties: field: type: string enum: - seniority - workStyle - city - roleFamily from: anyOf: - type: string - type: array items: type: string to: anyOf: - type: string - type: array items: type: string required: - field - from - to additionalProperties: false finalCount: type: integer minimum: 0 level: type: string enum: - seniority_plus_two - role_family_neighbors - include_remote_auto - all_relaxations description: Summary bucket describing how far the matcher widened the user's filters to fill the Discover feed. Null means strict match (no widening). example: seniority_plus_two strictCount: type: integer minimum: 0 default: null required: - deltas - finalCount - level - strictCount additionalProperties: false description: Rich per-delta relaxation payload - lists each filter the matcher widened and the specific "from → to" values. Null when no relaxation applied OR when the cascade did not write a detail payload (legacy path). Consumers should prefer this over `relaxation` for banner copy. example: null relaxations: type: array items: type: string enum: - none - seniority_plus_two - role_family_neighbors - include_remote - include_remote_auto - all_relaxations - region - drop_country description: Candidate-pool cascade relaxation code emitted by the job matcher. `none` is telemetry-only; the wire `relaxations[]` only ever carries the subset of tiers that actually filled the feed. example: seniority_plus_two default: [] description: Per-tier filter relaxation telemetry labels (cascade detail). Consumers use `relaxation` for UI copy. example: [] state: type: string enum: - strict - searching - widened default: strict description: 'Current state of the Discover feed: `strict` (exact matches), `searching` (empty list, first-pass, scrape still running), or `widened` (relaxed matches shown while strict search continues). Drives the Discover banner variant.' example: strict widenedDimension: type: - string - 'null' default: null description: Single human-readable name of the dimension that was widened (e.g. "location radius"). Non-null only when `state` is `widened`. UI shows at most one dimension change to keep the message scannable. example: null widenedDelta: type: - string - 'null' default: null description: Short human-readable description of the single widening change (e.g. "by 25mi"). Non-null only when `state` is `widened`. example: null liveRows: type: array items: $ref: '#/components/schemas/LiveMatchData' default: [] description: Supported-ATS jobs fetched live from the user's city during this request because the persisted pool was effectively empty. Not job_match rows - they have no id and cannot be swiped or applied to until the background scrape persists them. Render as a labelled "fresh from " band. example: [] liveCity: type: - string - 'null' default: null description: The city `liveRows` were fetched from, for the band label. Null when no live fetch ran. example: null searchProgress: type: object properties: found: type: integer minimum: 0 description: Total roles scored and kept for this user, all time. example: 77 lastFoundAt: type: - string - 'null' description: When the most recent role was added to this user's feed. Null when the feed has never been populated. example: '2026-08-27T05:51:40.000Z' lastRunAt: type: - string - 'null' description: When the last scoring pass finished. Null when no pass has been recorded in the last 24 hours. example: '2026-08-27T05:51:40.000Z' reviewed: type: integer minimum: 0 description: Roles this user has already dismissed or saved. example: 54 scanned: type: integer minimum: 0 description: Postings the last scoring pass looked at. 0 when no pass is on record. example: 1204 scored: type: integer minimum: 0 description: Postings from the last pass that cleared the match threshold. example: 31 added: type: integer minimum: 0 description: Roles the last pass added to the feed (the rest were already there). example: 11 default: added: 0 found: 0 lastFoundAt: null lastRunAt: null reviewed: 0 scanned: 0 scored: 0 required: - found - lastFoundAt - lastRunAt - reviewed - scanned - scored - added additionalProperties: false description: 'Counted facts about this user''s search: roles found all time, how many they have reviewed, and what the last scoring pass looked at. Zeroed on responses that do not compute it.' workModeBlocked: type: - object - 'null' properties: count: type: integer description: Persisted matches that pass every other Discover gate and fail only the work-mode gate. Always above zero; a zero is sent as a null payload. example: 6 exclusiveMinimum: 0 arrangements: type: object properties: onsite: type: integer minimum: 0 hybrid: type: integer minimum: 0 remote: type: integer minimum: 0 required: - onsite - hybrid - remote description: The same total split by the work style each blocked row needs. The three sum to `count`. example: hybrid: 2 onsite: 4 remote: 0 cities: type: array items: type: string maxItems: 3 description: Up to three real city names read off the blocked rows, commonest first. example: - Edmonton roleFamily: type: - string - 'null' enum: - software_engineer_frontend - software_engineer_backend - software_engineer_fullstack - software_engineer_mobile - software_engineer_embedded - software_engineer_games - software_engineer_generic - devops_sre - platform_infra - security_engineer - qa_test - data_engineer - data_scientist - data_analyst - ml_engineer - ai_research - product_manager - project_program_manager - designer_product - designer_ux_research - designer_visual - engineering_manager - technical_writer - developer_advocate - solutions_engineer - support_engineer - sales - marketing - operations - executive - finance - legal - hr_recruiting - customer_success - healthcare - creative-design - trades-manual - education - hospitality - logistics-supply - manufacturing - gov-nonprofit - marketing-sales - consulting - retail - insurance - admin_office - other - robotics_engineer - control_systems_engineer - mechatronics_engineer - firmware_engineer - fpga_engineer - hardware_engineer - electrical_engineer - rf_engineer - analog_engineer - biomedical_engineer - aerospace_engineer - avionics_engineer - simulation_engineer - computer_vision_engineer - autonomous_vehicles_engineer - graphics_engineer - blockchain_engineer - smart_contract_engineer - game_designer - ios_engineer - android_engineer - mlops_engineer - ai_infrastructure_engineer - llm_engineer - prompt_engineer - research_engineer - applied_scientist - cloud_architect - solutions_architect - enterprise_architect - release_engineer - network_engineer - systems_engineer - database_administrator - appsec_engineer - pentest_engineer - soc_analyst - incident_responder - analytics_engineer - data_architect - bioinformatics_engineer - computational_biologist - quant_analyst - quant_developer - trading_systems_engineer - risk_analyst - sdet - automation_engineer - product_manager_growth - ux_writer - content_designer - design_engineer - documentation_engineer - customer_engineer - implementation_engineer - civil_engineer - mechanical_engineer - null description: The commonest role family among the blocked rows. Null when none of them carries one. example: civil_engineer unlockWorkStyles: type: array items: type: string enum: - onsite - hybrid - remote description: User's preferred work style, physical mode only, no geo scoping example: remote description: The work styles the one-tap action adds. Only styles that actually unlock rows appear, so the offer never names a mode worth nothing. example: - onsite - hybrid default: null required: - count - arrangements - cities - roleFamily - unlockWorkStyles description: Counted matches this user's work-mode allow list is holding back, with the arrangement split, up to three real city names and the commonest role family. Null unless a page-1 Discover feed rendered zero cards and the count is above zero. example: null required: - items - pagination PersonalInfo: type: object properties: email: anyOf: - type: string format: email - type: string enum: - '' - {} example: name@example.com fullName: type: - string - 'null' example: Ada Lovelace linkedin: type: - string - 'null' location: type: - string - 'null' example: San Francisco, CA phone: type: - string - 'null' example: +1 (555) 123-4567 title: type: - string - 'null' example: Senior Software Engineer website: type: - string - 'null' additionalProperties: false ApplicationResultData: type: object properties: applicationStage: type: - string - 'null' enum: - applied - callback - phone_screen - interview - offered - rejected - no_response - null description: Stage of the job application pipeline example: applied appliedAt: type: - string - 'null' applyMode: type: - string - 'null' enum: - copilot - auto_approve - null description: 'What the user asked for on this job, recorded at ingress and never rewritten: auto_approve (we submit) or copilot (they review first). NULL on legacy rows. Read this in preference to `status` when deciding whether a row is waiting on the user - `status` is mutated by recovery lanes.' example: auto_approve submittingStartedAt: type: - string - 'null' description: When a browser actually began this run (application_result.submitting_started_at). NULL means the row is dispatched but no browser has claimed it yet — never read preparation_status=submitting as a run having started. example: '2026-08-08T20:10:31.000Z' autoRetryStopped: type: boolean description: True when no automatic lane will ever retry this application again (submit evidence, spent retry budget, an unowned terminal error code, hourly browser cap, cancelled, an unwatched pending_review, or an orphaned job match). False whenever any lane could still claim the row or it cannot be evaluated. example: false autoRetryStoppedReason: type: - string - 'null' enum: - submit_evidence - attempts_spent - not_retryable - cancelled - needs_review - orphaned - null description: Which permanent-abandonment lane stopped this row (submit_evidence / attempts_spent / not_retryable / cancelled / needs_review / orphaned). Null when the row is not stopped OR could not be evaluated - never render null as "still going". example: null nextAutoRetryAt: type: - string - 'null' description: Earliest ISO timestamp an automatic attempt can re-claim this row, from the hourly stuck-sweep selection rounded up to the next cron tick. A floor, not a guarantee. Null means we are not naming a time - NOT that no retry exists. example: null updatedAt: type: - string - 'null' submitEvidenceTier: type: string enum: - other_side_confirmed - our_click_only - no_submit_evidence description: 'Whose observation backs this row: other_side_confirmed (the employer produced a receipt), our_click_only (we clicked Submit but could not read a receipt - an absence, never proof it landed), or no_submit_evidence (we never clicked).' example: no_submit_evidence employerReceiptConfirmed: type: boolean description: Receipt proof from the employer exists for this application. Screenshot or confirmation-page proof is represented separately in landingProofs. Outranks our own status. example: false landingProofs: type: array items: type: string enum: - receipt - screenshot description: Who authored the evidence that this application reached the employer. `receipt` = the employer emailed a confirmation linked to this row. `screenshot` = a review of the stored post-submit screenshot read the employer's OWN confirmation page. Both are the other side's answer and both count as a proven landing; a row may hold either or both. example: screenshot description: 'Employer-authored proof this application landed: `receipt` (their confirmation email, linked) and/or `screenshot` (a review read their own confirmation page). Empty = no such proof, which is NOT a failure.' example: - screenshot statusDisplay: anyOf: - type: object properties: label: type: string minLength: 1 description: Legacy English badge text for rolling compatibility. V2 clients localize from code and params instead of rendering this field. example: Trying again shortly · pass 3 of 4 noCreditUsed: type: boolean description: True only when no billable AI or browser work ran for this row (the skipped short-circuits). An errored apply is never "free". example: false pendingRetryAt: type: - string - 'null' format: date-time description: Earliest ISO timestamp an automatic attempt can re-claim this row, echoed only with matching V2 scheduledAttempt params. Null means we are not naming a time, never that no retry exists. example: null tone: type: string enum: - success - warning - info - destructive - settled - neutral - muted description: Platform-agnostic colour verdict for a derived application status badge. Web maps it to a Tailwind bg-* class, mobile to a theme hex. Never a class name on the wire. example: info code: type: string enum: - queued - preparing - queued_to_apply - waiting_for_browser - submitting - action_needed - waiting_for_review - applied - applied_confirming_landing - employer_receipt_confirmed - sent_no_receipt_read - submitted_verifying - already_applied - retrying_our_hiccup - retrying_soon - employer_rejected_retrying - failed_retrying - resumes_when_funded - signing_in - paused - resuming - resume_required - not_through - no_form - not_ready_to_send - not_submitted - linkedin_easy_apply - job_unavailable - aggregator_skipped - result_submitted - result_submitted_awaiting_receipt - cancelled - result_error - result_preparing - result_pending_approval - result_ready_to_submit - result_skipped - stopped_generic - stopped_submit_evidence - stopped_attempts_spent - stopped_not_retryable - stopped_cancelled - stopped_needs_review - stopped_orphaned - stopped_our_click_only - unknown description: Stable semantic code for a derived application status display. Clients localize this code and use the legacy label only for rolling compatibility. example: queued_to_apply params: type: object properties: attemptsMade: type: - integer - 'null' exclusiveMinimum: 0 retryBudget: type: - object - 'null' properties: remaining: type: integer minimum: 0 total: type: integer exclusiveMinimum: 0 required: - remaining - total additionalProperties: false scheduledAttempt: type: - object - 'null' properties: current: type: integer exclusiveMinimum: 0 total: type: integer exclusiveMinimum: 0 required: - current - total additionalProperties: false required: - attemptsMade - retryBudget - scheduledAttempt additionalProperties: false schemaVersion: type: number enum: - 2 required: - label - noCreditUsed - pendingRetryAt - tone - code - params - schemaVersion additionalProperties: false - type: object properties: label: type: string minLength: 1 description: Legacy English badge text for rolling compatibility. V2 clients localize from code and params instead of rendering this field. example: Trying again shortly · pass 3 of 4 noCreditUsed: type: boolean description: True only when no billable AI or browser work ran for this row (the skipped short-circuits). An errored apply is never "free". example: false pendingRetryAt: type: - string - 'null' format: date-time description: Earliest ISO timestamp an automatic attempt can re-claim this row, echoed only with matching V2 scheduledAttempt params. Null means we are not naming a time, never that no retry exists. example: null tone: type: string enum: - success - warning - info - destructive - settled - neutral - muted description: Platform-agnostic colour verdict for a derived application status badge. Web maps it to a Tailwind bg-* class, mobile to a theme hex. Never a class name on the wire. example: info required: - label - noCreditUsed - pendingRetryAt - tone additionalProperties: false - {} description: The server-derived status verdict. V2 clients localize its stable code and typed params, then map the semantic tone to platform tokens. The English label remains only for rolling compatibility. Null means the row has no apply-pipeline state, so clients fall back to the listing status. example: null browserbaseDebugUrl: type: - string - 'null' browserbaseSessionId: type: - string - 'null' browserbaseSessionUrl: type: - string - 'null' city: type: - string - 'null' description: Parsed city from the parent job match, when available. company: type: - string - 'null' companyLogoUrl: type: - string - 'null' companyDomain: type: - string - 'null' description: Trusted company-intel domain (company.domain) from the parent job match for the card logo fallback. Null when the row has no linked company record or the company has no domain. example: acme.com country: type: - string - 'null' description: Canonical country code from the parent job match. contentLocked: type: boolean description: True when the generated content on this row is truncated because it was prepared without a paid plan. example: true revealPreviewChars: type: integer description: Character budget of the preview when contentLocked is true. Lets the client size the fade without hardcoding the server constant. example: 220 coverLetterContent: type: - string - 'null' coverLetterDocId: type: - integer - 'null' coverLetterPdfR2Key: type: - string - 'null' createdAt: type: - string - 'null' documentsOutdatedAt: type: - string - 'null' errorCode: type: - string - 'null' enum: - access_blocked - agent_stuck - agent_stuck_dom_unchanged - agent_stuck_no_cdp_progress - agent_stuck_no_progress_after_warmup - aggregator_preflight - aggregator_redirect - already_applied - apply_button_not_found - ashby_autofill_banner_failed - auth_wall_blocking - auth_wall_detected - account_door_unsupported - account_created_reapply - auto_retries_exhausted - browser_session_lost - browser_timeout - captcha_blocked - captcha_requires_human - captcha_unsolvable - cloudflare_challenge - credits_exhausted - d1_unavailable_at_claim - daily_spend_cap - auth_required - do_reset - duplicate - duplicate_fence_held - emailed_code_never_arrived - element_stale - employer_eligibility_knockout - false_positive_blocked - field_unfillable - form_not_found - hourly_browser_cap - html_content_guard - insufficient_tokens - invalid_apply_url - job_closed - job_not_found - job_removed - job_unsaved - kill_switch_active - manual_test_no_run - max_steps_reached - multi_step_stalled - network_error - no_form_present - no_resume_uploaded - oauth_session_expired - oauth_wall_detected - page_navigated_away - page_too_complex - page_unreachable - page_unreadable - post_submit_unconfirmed - preparation_incomplete - proxy_exhausted - proxy_tunnel_dead - quota_exhausted - rate_limited - redirect_blocked - requires_human_review - service_unavailable - session_expired - session_stale - spam_flagged - stuck_in_loop - submission_error - submit_deadline_exhausted - submit_did_not_take - submit_transport_failed - submit_validation_rejected - submit_withheld_form_refused - two_factor_required - universal_fill_zero_match - validation_error - worker_died - wrong_landing_page - unknown - null description: Classification of submission failure for strategy-aware retry example: network_error errorMessage: type: - string - 'null' failurePhase: type: - string - 'null' enum: - prepare - navigate - login - fill - submit - verify - aggregate - unknown - null description: Coarse-grained phase where a submission failure occurred example: submit id: type: integer improvementSuggestions: type: - array - 'null' items: type: string jobListingId: type: - integer - 'null' jobMatchId: type: - integer - 'null' jobTitle: type: - string - 'null' jobType: type: - string - 'null' description: Job type (full_time, contract, etc.) from the parent job match. jobUrl: type: string location: type: - string - 'null' description: Free-text location string from the parent job match. matchConfidence: type: - integer - 'null' outreachMessages: type: - string - 'null' postedAt: type: - string - 'null' description: Original posting date from the parent job match. salary: type: - string - 'null' description: Salary string from the parent job match. source: type: - string - 'null' description: Aggregator / source of the parent job match (greenhouse, lever, linkedin, etc.). workArrangement: type: - string - 'null' description: Canonical work-arrangement (remote_global / remote_country / remote_region / hybrid / onsite / unknown) from the parent job match. phase: type: - string - 'null' enum: - processing - idle - paused - null description: Server-truth lifecycle phase for a single auto-apply run row. `processing` = worker actively running a step. `idle` = between steps, waiting for next pickup. `paused` = rate-limit backoff, captcha wait, or explicit user pause. example: processing preparationStatus: type: string enum: - pending - preparing - ready - submitting - submitted - failed - cancelled description: Status of application preparation (resume tailoring + cover letter) example: pending retryCount: type: integer minimum: 0 qualityGateAction: type: - string - 'null' enum: - approved - flagged - blocked - null description: 'Outcome of the quality gate check: approved (proceed), flagged (needs review), blocked (rejected)' example: approved screenshotAfterUrl: type: - string - 'null' screenshotBeforeUrl: type: - string - 'null' screenshotKey: type: - string - 'null' sessionLiveViewUrl: type: - string - 'null' format: uri status: type: string enum: - pending - pending_review - ready_to_submit - applied - applied_unconfirmed - skipped - error - cancelled description: Status of an individual application result example: pending submissionPlatform: type: - string - 'null' submittedVia: type: - string - 'null' enum: - direct_api - form_post - linkedin_easy_apply - indeed_apply - glassdoor_apply - ziprecruiter_apply - authenticated_browser - workday_navigator - ai_browser_agent - email_fallback - null description: Strategy used to submit a job application example: direct_api tailoredResumeContent: type: - string - 'null' tailoredResumeDocId: type: - integer - 'null' tailoredResumePdfR2Key: type: - string - 'null' tailoredResumeStructured: $ref: '#/components/schemas/AIResumeData' tokensRefunded: type: - integer - 'null' refundReason: type: - string - 'null' enum: - job_closed - service_unavailable - credits_exhausted - session_expired - session_stale - infra_error - null description: Why a token reservation was refunded in full after a non-user-fault submission failure example: job_closed legitimacyTier: type: - string - 'null' enum: - high_confidence - proceed_with_caution - suspicious - null description: Deterministic posting-legitimacy verdict from the parent job match. Null when not yet evaluated. example: proceed_with_caution legitimacySignals: anyOf: - type: string - {} description: Serialized LegitimacySignals payload (score + signals[] + tier) from the parent job match. Null when not yet evaluated. freshnessStatus: type: - string - 'null' enum: - fresh - aging - stale - unknown - null description: Posting age bucket (fresh / aging / stale / unknown) from the scraped_job snapshot. example: fresh repostedCount: type: - integer - 'null' minimum: 0 description: Number of prior live scraped_job rows for this URL family. 0 when no repost history. example: 0 lastRepostDate: type: - string - 'null' description: posted_date of the most-recent prior repost. Null when no prior posting exists. example: '2026-03-04' linkedinApplyMethod: type: - string - 'null' enum: - easy_apply - offsite - unknown - null description: Resolved LinkedIn apply target from the parent job match. `offsite` → applies on a company ATS (suppress "Reconnect LinkedIn" copy); `easy_apply` → in-platform LinkedIn Easy Apply; `unknown` → not yet resolved / not a LinkedIn posting. Null when the row has no linked job match. example: offsite submissionInfo: $ref: '#/components/schemas/SubmissionInfo' required: - applicationStage - appliedAt - autoRetryStopped - autoRetryStoppedReason - nextAutoRetryAt - updatedAt - submitEvidenceTier - employerReceiptConfirmed - statusDisplay - city - company - companyLogoUrl - country - coverLetterContent - coverLetterDocId - coverLetterPdfR2Key - createdAt - documentsOutdatedAt - errorCode - errorMessage - id - improvementSuggestions - jobListingId - jobMatchId - jobTitle - jobType - jobUrl - location - matchConfidence - outreachMessages - postedAt - salary - source - workArrangement - phase - preparationStatus - retryCount - qualityGateAction - screenshotAfterUrl - screenshotBeforeUrl - screenshotKey - sessionLiveViewUrl - status - submissionPlatform - submittedVia - tailoredResumeContent - tailoredResumeDocId - tailoredResumePdfR2Key - tokensRefunded - refundReason additionalProperties: false PlanLimitations: type: object properties: initialTokens: type: number isProSubscriber: type: boolean maxJobListings: type: number maxResumeBuilds: type: number maxTokens: type: number maxUserDocuments: type: number planTier: type: string enum: - job_seeker - hired_in_30 - hired_yesterday description: Subscription plan tier level example: job_seeker tokensRemaining: type: number description: Combined-pool balance (plan + onboarding grant + permanent purchased value). For DISPLAY only - sidebar wallet, low-balance banner, plan badges. NOT the amount a normal apply can spend. tokensSpendable: type: number description: Balance a fresh (non-first-apply) submission can actually reserve = plan pool plus permanent purchased pool. The apply-affordability gate MUST read this, never tokensRemaining, so the FE button gate matches what the backend reserve will spend. required: - initialTokens - isProSubscriber - maxJobListings - maxResumeBuilds - maxTokens - maxUserDocuments - planTier - tokensRemaining - tokensSpendable additionalProperties: false LiveMatchData: type: object properties: city: type: - string - 'null' description: Structured city published by the upstream feed. example: Berlin company: type: string description: Employer name. example: Acme GmbH companyLogoUrl: type: - string - 'null' description: Employer logo, when the feed published one. example: null location: type: - string - 'null' description: Free-text location as published. example: Berlin, Germany persistedJobId: type: - integer - 'null' description: 'scraped_job.id once this live row provably exists in the database, else null. Null means "not yet confirmed", never "failed" - persistence is asynchronous. Never optimistic: only written after the id is read back out of the database post-insert.' example: null exclusiveMinimum: 0 postedDate: type: - string - 'null' description: ISO date the posting went live. example: '2026-07-21' salary: type: - string - 'null' description: Salary string as published, when present. example: null source: type: string enum: - linkedin - indeed - glassdoor - google - ziprecruiter - remotive - jobicy - arbeitnow - ashby - greenhouse - himalayas - lever - recruitee - smartrecruiters - themuse - workingnomads - join - bayt - naukri - bdjobs - web - company_career_page - manual - pool - workable - remoteok - weworkremotely - reed - wellfound - dice - monster - careerbuilder - simplyhired - builtin - snagajob - seek - totaljobs - stepstone - nofluffjobs - devitjobs - workday - jobspresso - landingjobs - personio - rss - teamtailor - breezy - jazzhr - pinpoint - comeet - bamboohr - icims - polymer - gohire - eddy - recruiterflow - hireology - workstream - bullhorn - rippling - softgarden - jobtech description: The supported direct-ATS provider this row resolved onto. Never an aggregator - those are filtered out before the row reaches this schema. example: greenhouse title: type: string description: Job title. example: Backend Engineer url: type: string description: Direct ATS apply URL. example: https://boards.greenhouse.io/acme/jobs/123 required: - city - company - companyLogoUrl - location - persistedJobId - postedDate - salary - source - title - url additionalProperties: false FormsLeftData: type: object properties: count: type: integer description: Estimated AI operations remaining example: 100 percent: type: number description: Percentage of tokens remaining example: 75 required: - count - percent additionalProperties: false DailyApplication: type: object properties: count: type: integer date: type: string description: Date in YYYY-MM-DD format example: '2026-03-24' required: - count - date additionalProperties: false MatchQualityWeek: type: object properties: excellent: type: integer description: Roles scored 90 or above fair: type: integer description: Roles scored 60 to 79 strong: type: integer description: Roles scored 80 to 89 week: type: string description: ISO year-week key, e.g. 2026-31 example: 2026-31 required: - excellent - fair - strong - week additionalProperties: false JobMatchPreferencesData: type: object properties: benefits: type: - array - 'null' items: type: string companySize: type: - array - 'null' items: type: string createdAt: type: - string - 'null' educationLevel: type: - string - 'null' excludeIndustries: type: - array - 'null' items: type: string experienceLevel: type: - string - 'null' experienceLevels: type: - array - 'null' items: type: string id: type: integer includeRemote: type: boolean industries: type: - array - 'null' items: type: string isActive: type: boolean jobTypes: type: - array - 'null' items: type: string locations: type: - array - 'null' items: type: string remoteGlobal: type: boolean salaryCurrency: type: string enum: - usd - eur - gbp - cad - aud - inr description: Currency for salary values example: usd salaryMax: type: - number - 'null' salaryMin: type: - number - 'null' skills: type: - array - 'null' items: type: string targetRoles: type: - array - 'null' items: type: string updatedAt: type: - string - 'null' visaSponsorship: type: - string - 'null' workStyles: type: array items: type: string enum: - onsite - hybrid - remote description: User's preferred work style, physical mode only, no geo scoping example: remote required: - benefits - companySize - createdAt - educationLevel - excludeIndustries - experienceLevel - experienceLevels - id - includeRemote - industries - isActive - jobTypes - locations - remoteGlobal - salaryCurrency - salaryMax - salaryMin - skills - targetRoles - updatedAt - visaSponsorship - workStyles TimeSeriesData: type: object properties: date: type: string description: Date in YYYY-MM-DD format example: '2024-01-01' count: type: integer description: Count for this date example: 5 required: - date - count additionalProperties: false SubmissionInfo: type: object properties: authPlatform: type: string description: Which platform needs auth credentials (e.g. linkedin) example: linkedin canSubmit: type: boolean description: Whether the application can be submitted to this platform hasCredentials: type: boolean description: Whether the user has stored credentials for the required platform platformType: type: string description: Detected submission platform type example: linkedin reason: type: string description: Reason submission is blocked or limited requiresAuth: type: boolean description: Whether the platform requires user authentication credentials required: - canSubmit - platformType additionalProperties: false description: Pre-flight submission requirements and credential status ApplicationConfigData: type: object properties: status: type: string enum: - active - paused - completed description: Status of an AI agent config example: paused totalApplications: type: integer description: Postings the user has applications against, one unit each. Every other counter except totalEmployerReceipts is a slice of it and they sum to it exactly. Absent means not computed, which is NOT the same as zero. example: 49 totalApplied: type: integer description: 'Applications with retained employer proof: a linked employer receipt or a retained post-submit screenshot audited as employer confirmation. A legacy applied status or landing confidence grade alone does not count.' example: 3 totalErrors: type: integer totalSkipped: type: integer totalSubmitConfirmed: type: integer totalSubmitClicked: type: integer totalDeadPostings: type: integer description: Applications whose posting no longer exists (job_closed / job_removed). Excluded from every other counter and from every rate, in both numerator and denominator. Absent means not computed, which is NOT the same as zero. example: 2 totalEmployerReceipts: type: integer description: Distinct linked employer or ATS confirmation emails. OVERLAPS totalApplied and must not be summed with the partitioning counters. Absent means not computed, which is NOT the same as zero. example: 0 totalPendingReview: type: integer description: Postings whose newest row the USER parked for review (pending_review with a copilot apply mode). Never a row our own worker parked. Absent means not computed, which is NOT the same as zero. example: 1 totalQueuedToApply: type: integer description: 'Postings we will submit or retry without the user: newest row pending / ready_to_submit, or a pending_review row our worker parked and the backstop retries. Absent means not computed, which is NOT the same as zero.' example: 2 required: - status - totalApplied - totalErrors - totalSkipped - totalSubmitConfirmed - totalSubmitClicked additionalProperties: false UserDocument: type: object properties: id: type: integer description: User document identifier example: 1 userId: type: string description: Owner user identifier example: user_123 title: type: string description: Document title example: My Resume documentType: type: string enum: - cover_letter - resume - custom - tailored_resume - translation - match_report description: Type of document (cover letter, resume, custom, tailored resume, translation, or match report) example: cover_letter isBase: type: integer description: Whether this is the base document for its type (0 or 1) example: 0 isPublic: type: integer description: Whether this document is publicly shared (0 or 1) example: 0 status: type: string enum: - draft - generating - ready - failed description: Lifecycle status of a document example: ready modelName: type: - string - 'null' description: AI model used if document was AI-generated example: google/gemini-2.5-flash-lite sourceJobId: type: - integer - 'null' description: Job listing ID if document was tailored for a job example: 5 sourceJobTitle: type: - string - 'null' description: Title of the linked job listing (resolved from sourceJobId) example: Senior Software Engineer sourceJobCompany: type: - string - 'null' description: Company of the linked job listing (resolved from sourceJobId) example: Acme Corp r2Key: type: - string - 'null' description: R2 storage key for the uploaded file example: user-documents/user_123/1/resume.pdf r2Filename: type: - string - 'null' description: Original filename of the uploaded file example: resume.pdf r2Filesize: type: - integer - 'null' description: File size in bytes example: 102400 r2MimeType: type: - string - 'null' description: MIME type of the uploaded file example: application/pdf shareToken: type: - string - 'null' description: Share token for public URL example: a1b2c3d4e5f6... atsScore: type: - integer - 'null' description: ATS compatibility score (0-100) example: 85 atsSuggestions: type: - array - 'null' items: type: string description: ATS improvement suggestions example: - Add more keywords - Use standard section headings atsStrengths: type: - array - 'null' items: type: string description: ATS detected strengths example: - Clear formatting - Relevant skills listed atsWeaknesses: type: - array - 'null' items: type: string description: ATS detected weaknesses example: - Missing quantified achievements createdAt: type: - string - 'null' description: Creation timestamp example: '2024-01-01T00:00:00Z' updatedAt: type: - string - 'null' description: Last update timestamp example: '2024-01-15T10:30:00Z' contentMarkdown: type: - string - 'null' description: Markdown/text content of the document. Populated by get-by-id only, list responses omit it to keep payloads small. example: '# Jane Doe Senior Software Engineer ...' contentLocked: type: boolean description: True when generated document content exists but is withheld for the current plan. Populated by get-by-id only. example: true resolvable: type: boolean description: 'Server-computed "AI can resolve resume text from this row" flag, only present on resume rows in the ?type=resume list. Canonical rule isResolvableResumeRow (resume-text.ts): a STORED FILE (r2Key) is usable regardless of parse status - so an onboarding resume shows as resolvable the moment it uploads, not only after the async parse finishes - OR a text-only row once generation/parse has completed. The single source of truth the FE useHasResume() hook reads so FE/BE cannot drift.' example: true builderImportable: type: boolean description: Server-computed source-evidence flag for resume-builder import. List responses expose this without sending document markdown. example: true required: - id - userId - title - documentType - isBase - isPublic - status - modelName - sourceJobId - r2Key - r2Filename - r2Filesize - r2MimeType - createdAt - updatedAt OutcomeMetrics: type: object properties: emailResponseRate: type: number description: Share of classified inbound job emails that carry a positive signal (interview / scheduling / offer), as a percentage. 0 when no emails are classified. example: 42.5 interviewsBooked: type: integer description: Applications that reached an interview-booked outcome (stage phone_screen/interview, or interview_booked_at set). example: 3 nextInterviewResultId: type: - integer - 'null' description: Application-result id behind the SOONEST upcoming interview event, for the applications deep-link. Null when that event has no application behind it; absent on payloads that do not resolve it. example: 1481 offers: type: integer description: Applications with an offer outcome (applicationStage = offered). example: 1 positiveEmailSignals: type: integer description: Count of inbound emails classified as a positive signal (interview_invite / scheduling / reschedule / offer_letter). example: 5 upcomingInterviews: type: integer description: Synced calendar events flagged as interviews whose start time is in the future. example: 2 required: - emailResponseRate - interviewsBooked - offers - positiveEmailSignals - upcomingInterviews additionalProperties: false description: Real-outcome success metrics from the email + calendar pipeline (interviews booked, offers, upcoming interviews, email response rate). AuthHealthData: type: - object - 'null' properties: status: type: string enum: - success user: $ref: '#/components/schemas/AuthHealthUser' limitations: $ref: '#/components/schemas/PlanLimitations' hasCompletedSetup: type: boolean description: Whether the user has completed onboarding (has job match preferences) example: false onboardingCompleted: type: boolean description: Whether the user finished the onboarding wizard (user.onboarding_completed_at is set) example: false required: - status - user - limitations - hasCompletedSetup - onboardingCompleted UserPreferencesData: type: - object - 'null' properties: applicationProfile: $ref: '#/components/schemas/ApplicationProfileData' jobMatchPreferences: $ref: '#/components/schemas/JobMatchPreferencesData' aiPersona: $ref: '#/components/schemas/AIPersonaData' required: - applicationProfile - jobMatchPreferences - aiPersona AIPersonaData: type: object properties: id: type: string description: AI persona identifier example: abc-123 userId: type: string description: User identifier example: user_abc123 presetId: type: string enum: - balanced - the_professional - the_conversationalist - the_storyteller - the_closer - the_enthusiast - custom description: Selected persona preset example: balanced personalDescription: type: - string - 'null' description: Free text about the user for AI personalization example: Senior engineer with 10 years in fintech toneInstructions: type: - string - 'null' description: Custom rules for AI tone and style example: Always mention my passion for open-source samplePhrases: type: - array - 'null' items: type: string description: Example phrases the user would naturally use example: - Let me walk you through my approach - I thrive in fast-paced environments generateCoverLetterPerJob: type: boolean description: Whether AI generates a cover letter for each job application example: true tailorResumePerJob: type: boolean description: Whether AI tailors resume for each job application example: true resumeStrategy: type: string enum: - base - tailor description: Resume handling strategy example: tailor coverLetterStrategy: type: string enum: - 'off' - use_base - tailor_base - generate_new description: Cover letter handling strategy example: generate_new translationStrategy: type: string enum: - 'off' - translate - translate_and_tailor description: Translation strategy example: 'off' mode: type: string enum: - copilot - auto_approve description: 'Application mode: copilot (review before submitting) or auto_approve (submits automatically)' example: copilot allowFabrication: type: boolean description: Fill-gaps for the apply agent, enabled by default. When true, a required field the agent cannot take directly from profile facts is sourced real-data-first (the resume, tailored resume, and cover letter for this application, then job preferences, then profile) and only fabricated as a last resort, marked as assumed. When false (explicit opt-out), it never fabricates such an answer. Contact/identity completion (phone, city, postal, profile links) always completes required fields and is not governed by this flag. example: true scoringWeights: type: - object - 'null' properties: location: type: number minimum: 0 maximum: 2 remote: type: number minimum: 0 maximum: 2 salary: type: number minimum: 0 maximum: 2 seniority: type: number minimum: 0 maximum: 2 skills: type: number minimum: 0 maximum: 2 visa: type: number minimum: 0 maximum: 2 required: - location - remote - salary - seniority - skills - visa description: Per-dimension match scoring weights. NULL when the user has never customised them (scorer falls back to uniform defaults). example: skills: 1 location: 1 remote: 1 seniority: 1 salary: 1 visa: 1 createdAt: type: string description: Persona creation timestamp example: '2026-01-15T12:00:00Z' updatedAt: type: string description: Persona last update timestamp example: '2026-03-10T08:30:00Z' required: - id - userId - presetId - personalDescription - toneInstructions - samplePhrases - generateCoverLetterPerJob - tailorResumePerJob - resumeStrategy - coverLetterStrategy - translationStrategy - mode - allowFabrication - scoringWeights - createdAt - updatedAt AnalyticsData: type: - object - 'null' properties: atsScoreDistribution: type: object properties: excellent: type: integer description: Scores >= 80 fair: type: integer description: Scores >= 40 and < 60 good: type: integer description: Scores >= 60 and < 80 poor: type: integer description: Scores < 40 required: - excellent - fair - good - poor additionalProperties: false description: ATS score distribution across 4 buckets avgAtsScore: type: - number - 'null' description: Average ATS score across all scored jobs dailyAnswers: type: array items: $ref: '#/components/schemas/DailyApplication' description: Employer-confirmed answers per day for the last 14 days dailyApplications: type: array items: $ref: '#/components/schemas/DailyApplication' description: Application counts per day for the last 14 days matchQualityByWeek: type: array items: $ref: '#/components/schemas/MatchQualityWeek' description: Roles scored for this user per ISO week over the last 8 weeks, split into quality bands. Weeks with no supply are omitted. monthlyApplications: type: integer description: Jobs applied this month outcomes: allOf: - $ref: '#/components/schemas/OutcomeMetrics' - description: Real-outcome success metrics from the email + calendar pipeline. previousWeekApplications: type: integer description: Jobs applied 14-7 days ago for trend comparison recentActivity: type: array items: type: object properties: company: type: - string - 'null' companyDomain: type: - string - 'null' description: Trusted company-intel domain (company.domain) for the FE logo favicon fallback companyLogoUrl: type: - string - 'null' description: Company logo URL (company-intel R2 logo, falling back to the scraped snapshot) id: type: integer status: type: string enum: - saved - preparing - applied - interviewing - offered - rejected - archived description: Status of a job listing example: saved title: type: string updatedAt: type: - string - 'null' required: - company - companyDomain - companyLogoUrl - id - status - title - updatedAt additionalProperties: false responseRate: type: number description: Percentage of applied jobs with response example: 35.5 statusCounts: type: object properties: applied: type: integer archived: type: integer interviewing: type: integer offered: type: integer preparing: type: integer rejected: type: integer saved: type: integer required: - applied - archived - interviewing - offered - preparing - rejected - saved additionalProperties: false topCompanies: type: array items: type: object properties: company: type: string companyDomain: type: - string - 'null' description: Trusted company-intel domain (company.domain) for the FE logo favicon fallback companyLogoUrl: type: - string - 'null' description: Company logo URL count: type: integer recentJobTitle: type: - string - 'null' description: Most recent job title at this company required: - company - companyDomain - companyLogoUrl - count - recentJobTitle additionalProperties: false description: Most applied-to companies with recent job title totalJobs: type: integer unactionedHighMatches: type: integer description: Roles scored 80+ that the user has neither dismissed nor applied to. The one figure on the analytics screen that names a next action. example: 942 weeklyApplications: type: integer description: Jobs applied this week required: - atsScoreDistribution - avgAtsScore - dailyAnswers - dailyApplications - matchQualityByWeek - monthlyApplications - outcomes - previousWeekApplications - recentActivity - responseRate - statusCounts - topCompanies - totalJobs - unactionedHighMatches - weeklyApplications MatchData: type: object properties: appliedAt: type: - string - 'null' description: Timestamp when the application was confirmed submitted. Null until submission completes. example: '2026-04-20T10:15:00Z' applicationStatus: type: - string - 'null' enum: - pending - pending_review - ready_to_submit - applied - applied_unconfirmed - skipped - error - cancelled - null description: Status of the latest applicationResult row for this match (pending / pending_review / ready_to_submit / applied / skipped / error). Null when no application has been started. example: ready_to_submit authPlatform: type: - string - 'null' description: Platform credential key required for auto-apply (e.g. "linkedin", "indeed"), or null if no auth needed example: linkedin requiresAuth: type: boolean description: True when this match's platform requires authenticated apply AND the user has not connected the corresponding integration. Frontend renders a locked card with a "Connect {authPlatform}" CTA when true. example: false platformType: type: string description: Resolved URL platform classifier (e.g. "linkedin", "greenhouse", "lever"). Always present. example: greenhouse benefits: type: - array - 'null' items: type: string company: type: - string - 'null' companyLogoUrl: type: - string - 'null' companyDomain: type: - string - 'null' description: Trusted company-intel domain (company.domain) for the company-jobs page logo fallback. Null when the match has no linked company record or the company has no domain. example: acme.com companySize: type: - string - 'null' createdAt: type: - string - 'null' description: type: - string - 'null' dismissed: type: boolean educationRequired: type: - string - 'null' extractedSkills: type: - array - 'null' items: type: string id: type: integer publicJobId: type: - integer - 'null' jobType: type: - string - 'null' listingStatus: type: - string - 'null' enum: - saved - preparing - applied - interviewing - offered - rejected - archived - null description: Status from the user's jobListings row for this match (saved / preparing / applied / rejected / interviewing / offered / archived). Null when the match has never been saved. example: preparing location: type: - string - 'null' locationMatch: type: - boolean - 'null' description: Job country is in user preferences OR job is remote. Null when both sides lack data. geoTier: type: string enum: - exact_city - within_radius - in_country - remote - off_country - unknown description: Rung this job occupies on the user's location-priority ladder (exact_city > within_radius > in_country > remote > off_country > unknown). `unknown` means the job could not be placed and must NEVER be rendered as a location match. example: within_radius distanceKm: type: - number - 'null' description: Whole-km great-circle distance to the user's NEAREST preferred city. Null means NOT COMPUTABLE (missing coordinates on either side) and must be read as unknown, never as "far" and never as 0. example: 24 matchScore: type: - number - 'null' postedAt: type: - string - 'null' preparationStatus: type: - string - 'null' enum: - pending - preparing - ready - submitting - submitted - failed - cancelled - null description: Preparation progress for the latest application (pending / preparing / ready / submitting / submitted / failed). Null when no application has been started. example: preparing remoteMatch: type: - boolean - 'null' description: Job supports any remote arrangement. Null when work arrangement is missing. resolvedApplyUrl: type: - string - 'null' description: Resolved external ATS apply URL (e.g. the Greenhouse/Lever page a LinkedIn-scraped job actually applies on). Null when the job applies on its own page. The apply gate prefers this over `url` for platform detection. example: https://boards.greenhouse.io/acme/jobs/123 linkedinApplyMethod: type: string enum: - easy_apply - offsite - unknown description: Resolved LinkedIn apply target. `offsite` → applies on a company ATS (use resolvedApplyUrl for the provider badge); `easy_apply` → in-platform LinkedIn Easy Apply ("LinkedIn Easy Apply" badge); `unknown` → not yet resolved / not a LinkedIn posting (fall back to `source` for the badge). example: offsite salary: type: - string - 'null' salaryMatch: type: - boolean - 'null' description: Job salary meets or exceeds user minimum. Null when either side lacks a salary value. saved: type: boolean seniorityMatch: type: - boolean - 'null' description: Job seniority band equals user preference. Null when either side lacks a band. skillsMatch: type: - boolean - 'null' description: At least one user canonical skill appears on the job. Null when either side lacks a skills list. skillsMatchCount: type: - integer - 'null' minimum: 0 description: Count of user canonical skills matched on this job. Null when either side lacks a skills list. source: type: string title: type: string url: type: string visaMatch: type: - boolean - 'null' description: User does not require sponsorship OR job offers sponsorship. Null when sponsorship status is unknown. visaSponsorship: type: - string - 'null' whyGoodFit: type: - string - 'null' description: 'Enrichment-v2: one-paragraph (<=280 chars) neutral third-person blurb describing why a generic candidate might want this role. Null when the description is too thin.' example: Remote-first Series B fintech with strong mentorship, modern TypeScript stack, and paid conference budget. Limited on-call and a small team where individual impact is high. workArrangement: type: - string - 'null' description: Canonical work-arrangement enum (remote_global / remote_country / remote_region / hybrid / onsite / unknown). Drives the "Remote" chip + filter. Null only when enrichment hasn't run yet. example: remote_global city: type: - string - 'null' description: Parsed city component of location. country: type: - string - 'null' description: Canonical country code. legitimacyTier: type: - string - 'null' enum: - high_confidence - proceed_with_caution - suspicious - null description: Deterministic posting-legitimacy verdict. Null when not yet evaluated. example: proceed_with_caution freshnessStatus: type: - string - 'null' enum: - fresh - aging - stale - unknown - null description: Posting age bucket (fresh / aging / stale / unknown). example: fresh repostedCount: type: - integer - 'null' minimum: 0 description: Number of prior live scraped_job rows for this URL family. 0 when no repost history. example: 0 lastRepostDate: type: - string - 'null' description: posted_date of the most-recent prior repost. Null when no prior posting exists. example: '2026-03-04' legitimacySignals: type: - string - 'null' description: Serialized LegitimacySignals payload (score + signals[] + tier). Null when not yet evaluated. seniorityBand: type: - string - 'null' enum: - intern - entry - junior - mid - senior - staff - principal - lead - manager - director - vp - cxo - null description: Canonical seniority band parsed from the listing (intern / entry / junior / mid / senior / staff / principal / lead / manager / director / vp / cxo). Null when the band could not be inferred. example: senior salaryMinUsd: type: - integer - 'null' description: Lower bound of the listing salary range normalized to USD/year. Null when no parseable salary range exists. example: 120000 salaryMaxUsd: type: - integer - 'null' description: Upper bound of the listing salary range normalized to USD/year. Null when no parseable salary range exists. example: 180000 salaryCurrency: type: - string - 'null' description: ISO-4217 currency code of the original (pre-normalization) salary range (USD, EUR, GBP, ...). Null when no salary was reported. example: USD needsLivenessCheck: type: boolean description: True when the match is a stale-candidate on a browser-required aggregator (indeed/linkedin/glassdoor) and the frontend should call POST /job-matches/{id}/verify-live before allowing swipe-right. False means the listing is fresh enough to swipe directly. example: false required: - appliedAt - applicationStatus - authPlatform - requiresAuth - platformType - benefits - company - companyLogoUrl - companySize - createdAt - description - dismissed - educationRequired - extractedSkills - id - publicJobId - jobType - listingStatus - location - locationMatch - geoTier - distanceKm - matchScore - postedAt - preparationStatus - remoteMatch - resolvedApplyUrl - linkedinApplyMethod - salary - salaryMatch - saved - seniorityMatch - skillsMatch - skillsMatchCount - source - title - url - visaMatch - visaSponsorship - whyGoodFit - workArrangement - city - country - legitimacyTier - freshnessStatus - repostedCount - lastRepostDate - legitimacySignals - seniorityBand - salaryMinUsd - salaryMaxUsd - salaryCurrency - needsLivenessCheck additionalProperties: false JobListingListItem: type: object properties: company: type: - string - 'null' description: Company name example: Acme Corp companyLogoUrl: type: - string - 'null' description: Company logo URL example: https://www.google.com/s2/favicons?domain=google.com&sz=128 companyDomain: type: - string - 'null' description: Company domain for the FE DDG-favicon logo fallback example: acme.com location: type: - string - 'null' description: Job location example: Remote, US salary: type: - string - 'null' description: Salary range or details example: $120,000 - $160,000 sourceUrl: type: - string - 'null' description: URL where the job listing was found example: https://linkedin.com/jobs/123 title: type: string description: Job title example: Senior Software Engineer createdAt: type: - string - 'null' description: Creation timestamp example: '2024-01-01T00:00:00Z' id: type: integer description: Job listing identifier example: 1 jobMatchId: type: - integer - 'null' description: Associated job match identifier example: 42 status: type: string enum: - saved - preparing - applied - interviewing - offered - rejected - archived description: Status of a job listing example: saved updatedAt: type: - string - 'null' description: Last update timestamp example: '2024-01-15T10:30:00Z' source: type: - string - 'null' enum: - linkedin - indeed - glassdoor - google - ziprecruiter - remotive - jobicy - arbeitnow - ashby - greenhouse - himalayas - lever - recruitee - smartrecruiters - themuse - workingnomads - join - bayt - naukri - bdjobs - web - company_career_page - manual - pool - workable - remoteok - weworkremotely - reed - wellfound - dice - monster - careerbuilder - simplyhired - builtin - snagajob - seek - totaljobs - stepstone - nofluffjobs - devitjobs - workday - jobspresso - landingjobs - personio - rss - teamtailor - breezy - jazzhr - pinpoint - comeet - bamboohr - icims - polymer - gohire - eddy - recruiterflow - hireology - workstream - bullhorn - rippling - softgarden - jobtech - null description: Source platform where the job was found (from linked job match) example: ashby resolvedApplyUrl: type: - string - 'null' description: Resolved external ATS apply URL from the linked job match (e.g. the Greenhouse/Lever page a LinkedIn-scraped job actually applies on). Prefer this over `source` for the provider badge. example: https://boards.greenhouse.io/acme/jobs/123 linkedinApplyMethod: type: - string - 'null' enum: - easy_apply - offsite - unknown - null description: Resolved LinkedIn apply target from the linked job match. `offsite` → applies on a company ATS (use resolvedApplyUrl for the badge); `easy_apply` → "LinkedIn Easy Apply"; `unknown`/null → fall back to `source`. example: offsite atsScoreCount: type: integer minimum: 0 description: Number of ATS scores for this job listing example: 2 latestAtsScore: type: - integer - 'null' minimum: 0 maximum: 100 description: Most recent ATS overall score (0-100), null if no scores exist example: 78 preparationStatus: type: - string - 'null' description: Preparation status of the linked application result (null if no application) example: ready autoRetryStopped: type: boolean description: True when NO automatic lane will retry this application (submit evidence fences it, or the retry budget is spent). The UI must not promise a retry when this is true. example: false autoRetryStoppedReason: type: - string - 'null' enum: - submit_evidence - attempts_spent - not_retryable - cancelled - needs_review - orphaned - null description: Which permanent-abandonment lane stopped this row, or null when it is not stopped / could not be evaluated. example: null employerReceiptConfirmed: type: boolean description: True when employer receipt proof exists. Page or screenshot proof stays separate in landingProofs. Outranks every status/error/retry field on this row. example: false submitEvidenceTier: type: string enum: - other_side_confirmed - our_click_only - no_submit_evidence description: 'Whose observation backs this row: other_side_confirmed (the employer produced a receipt), our_click_only (we clicked Submit but read no receipt), or no_submit_evidence (we never clicked).' example: no_submit_evidence nextAutoRetryAt: type: - string - 'null' description: Earliest ISO timestamp an automatic attempt can re-claim this row. A floor, not a guarantee. Null means we are not naming a time - NOT that no retry exists. example: null statusDisplay: anyOf: - type: object properties: label: type: string minLength: 1 description: Legacy English badge text for rolling compatibility. V2 clients localize from code and params instead of rendering this field. example: Trying again shortly · pass 3 of 4 noCreditUsed: type: boolean description: True only when no billable AI or browser work ran for this row (the skipped short-circuits). An errored apply is never "free". example: false pendingRetryAt: type: - string - 'null' format: date-time description: Earliest ISO timestamp an automatic attempt can re-claim this row, echoed only with matching V2 scheduledAttempt params. Null means we are not naming a time, never that no retry exists. example: null tone: type: string enum: - success - warning - info - destructive - settled - neutral - muted description: Platform-agnostic colour verdict for a derived application status badge. Web maps it to a Tailwind bg-* class, mobile to a theme hex. Never a class name on the wire. example: info code: type: string enum: - queued - preparing - queued_to_apply - waiting_for_browser - submitting - action_needed - waiting_for_review - applied - applied_confirming_landing - employer_receipt_confirmed - sent_no_receipt_read - submitted_verifying - already_applied - retrying_our_hiccup - retrying_soon - employer_rejected_retrying - failed_retrying - resumes_when_funded - signing_in - paused - resuming - resume_required - not_through - no_form - not_ready_to_send - not_submitted - linkedin_easy_apply - job_unavailable - aggregator_skipped - result_submitted - result_submitted_awaiting_receipt - cancelled - result_error - result_preparing - result_pending_approval - result_ready_to_submit - result_skipped - stopped_generic - stopped_submit_evidence - stopped_attempts_spent - stopped_not_retryable - stopped_cancelled - stopped_needs_review - stopped_orphaned - stopped_our_click_only - unknown description: Stable semantic code for a derived application status display. Clients localize this code and use the legacy label only for rolling compatibility. example: queued_to_apply params: type: object properties: attemptsMade: type: - integer - 'null' exclusiveMinimum: 0 retryBudget: type: - object - 'null' properties: remaining: type: integer minimum: 0 total: type: integer exclusiveMinimum: 0 required: - remaining - total additionalProperties: false scheduledAttempt: type: - object - 'null' properties: current: type: integer exclusiveMinimum: 0 total: type: integer exclusiveMinimum: 0 required: - current - total additionalProperties: false required: - attemptsMade - retryBudget - scheduledAttempt additionalProperties: false schemaVersion: type: number enum: - 2 required: - label - noCreditUsed - pendingRetryAt - tone - code - params - schemaVersion additionalProperties: false - type: object properties: label: type: string minLength: 1 description: Legacy English badge text for rolling compatibility. V2 clients localize from code and params instead of rendering this field. example: Trying again shortly · pass 3 of 4 noCreditUsed: type: boolean description: True only when no billable AI or browser work ran for this row (the skipped short-circuits). An errored apply is never "free". example: false pendingRetryAt: type: - string - 'null' format: date-time description: Earliest ISO timestamp an automatic attempt can re-claim this row, echoed only with matching V2 scheduledAttempt params. Null means we are not naming a time, never that no retry exists. example: null tone: type: string enum: - success - warning - info - destructive - settled - neutral - muted description: Platform-agnostic colour verdict for a derived application status badge. Web maps it to a Tailwind bg-* class, mobile to a theme hex. Never a class name on the wire. example: info required: - label - noCreditUsed - pendingRetryAt - tone additionalProperties: false - {} description: The server-derived application status verdict. V2 clients localize its stable code and typed params, then map the semantic tone to platform tokens. The English label remains only for rolling compatibility. Null means the listing has no linked application, so clients fall back to the job listing status. example: null errorCode: type: - string - 'null' description: Terminal error code of the latest application result, or null. Drives the status label. example: agent_stuck applicationResultStatus: type: - string - 'null' description: Status of the linked application result (null if no application) example: ready_to_submit legitimacyTier: type: - string - 'null' enum: - high_confidence - proceed_with_caution - suspicious - null description: Deterministic posting-legitimacy verdict from the parent job match. Null when not yet evaluated. example: proceed_with_caution legitimacySignals: anyOf: - type: string - {} description: Serialized LegitimacySignals payload (score + signals[] + tier) from the parent job match. Null when not yet evaluated. freshnessStatus: type: - string - 'null' enum: - fresh - aging - stale - unknown - null description: Posting age bucket (fresh / aging / stale / unknown) from the scraped_job snapshot. example: fresh repostedCount: type: - integer - 'null' minimum: 0 description: Number of prior live scraped_job rows for this URL family. 0 when no repost history. example: 0 lastRepostDate: type: - string - 'null' description: posted_date of the most-recent prior repost. Null when no prior posting exists. example: '2026-03-04' required: - company - companyLogoUrl - location - salary - sourceUrl - title - createdAt - id - jobMatchId - status - updatedAt - source - resolvedApplyUrl - linkedinApplyMethod - atsScoreCount - latestAtsScore - preparationStatus - autoRetryStopped - autoRetryStoppedReason - employerReceiptConfirmed - submitEvidenceTier - nextAutoRetryAt - statusDisplay - errorCode - applicationResultStatus SuccessRateData: type: object properties: successRate: type: number description: Overall success rate percentage example: 92.5 successCount: type: integer description: Number of successful operations example: 185 failureCount: type: integer description: Number of failed operations example: 10 pendingCount: type: integer description: Number of pending/aborted operations example: 5 totalCount: type: integer description: Total number of operation attempts example: 200 required: - successRate - successCount - failureCount - pendingCount - totalCount additionalProperties: false AIResumeData: type: - object - 'null' properties: certifications: type: array items: type: string education: type: array items: $ref: '#/components/schemas/EducationEntry' experience: type: array items: $ref: '#/components/schemas/ExperienceEntry' personalInfo: $ref: '#/components/schemas/PersonalInfo' skills: type: array items: type: string summary: type: - string - 'null' ResponseMetadata: type: object properties: duration: type: number description: Request duration in milliseconds example: 150 exclusiveMinimum: 0 performance: type: object properties: dbQueries: type: integer minimum: 0 totalDbTime: type: number minimum: 0 description: Performance metrics (preview/local only) requestId: type: string description: Unique request identifier example: req_abc123 timestamp: type: string format: date-time description: ISO 8601 timestamp example: '2026-09-25T17:29:47.224Z' version: type: string description: API version example: v1 DashboardBootstrapResponse: type: object properties: data: $ref: '#/components/schemas/DashboardBootstrapData' meta: $ref: '#/components/schemas/ResponseMetadata' success: type: boolean enum: - true required: - data - success additionalProperties: false description: Successful API response ApplicationProfileData: type: object properties: id: type: string description: Application profile identifier example: abc-123 userId: type: string description: User identifier example: user_abc123 phone: type: - string - 'null' description: Phone number example: +1-555-123-4567 addressLine1: type: - string - 'null' description: Address line 1 example: 123 Main St addressLine2: type: - string - 'null' description: Address line 2 example: Apt 4B city: type: - string - 'null' description: City example: San Francisco state: type: - string - 'null' description: State or province example: CA zipCode: type: - string - 'null' description: ZIP or postal code example: '94105' country: type: - string - 'null' description: Country example: US linkedinUrl: type: - string - 'null' description: LinkedIn profile URL example: https://linkedin.com/in/johndoe portfolioUrl: type: - string - 'null' description: Portfolio website URL example: https://johndoe.dev githubUrl: type: - string - 'null' description: GitHub profile URL example: https://github.com/johndoe currentEmployer: type: - string - 'null' description: Current employer / company. Helps tailor recommendations; unlocks team analytics if the company onboards. example: Acme Corp dateOfBirth: type: - string - 'null' description: Date of birth (ISO 8601 date string) example: '1995-06-15' workAuthorization: type: - string - 'null' enum: - us_citizen - permanent_resident - visa_holder - require_sponsorship - other - null description: Work authorization status example: us_citizen willingToRelocate: type: boolean description: Willing to relocate for a job example: false relocationLocations: type: - array - 'null' items: type: string description: Preferred relocation locations as "City, Country" or "Country". Middle admin segments stripped on read. example: - New York, United States - Austin, United States salaryCurrency: type: - string - 'null' enum: - usd - eur - gbp - cad - aud - inr - null description: Salary currency example: usd availableStartDate: type: - string - 'null' description: Earliest available start date (ISO 8601 date string) example: '2026-04-01' noticePeriod: type: - string - 'null' enum: - immediately - two_weeks - one_month - two_months - three_months - flexible - null description: Notice period before starting new position example: two_weeks personalNarrative: type: - string - 'null' description: Free-form narrative about gaps / transitions / short stints. Drives interview-prep red-flag question generation (F8). example: Took a 14-month gap in 2024 for caregiving; returning with a refreshed cloud certification. travelWillingness: type: - string - 'null' enum: - none - up_to_25 - up_to_50 - up_to_75 - up_to_100 - null description: Willingness to travel for work example: up_to_25 securityClearance: type: - string - 'null' enum: - none - confidential - secret - top_secret - ts_sci - prefer_not_to_say - null description: Security clearance level example: none restrictiveCovenantStatus: type: - string - 'null' enum: - 'yes' - 'no' - prefer_not_to_say - null description: Currently subject to a non-compete / non-solicit / restrictive covenant with a former employer example: 'no' criminalRecordStatus: type: - string - 'null' enum: - 'yes' - 'no' - prefer_not_to_say - null description: Has a criminal conviction they are legally required to disclose example: 'no' backgroundCheckConsent: type: - string - 'null' enum: - 'yes' - 'no' - prefer_not_to_say - null description: Consents to a background / reference / credit check or drug screen where the role requires one example: 'yes' relatedToEmployeeStatus: type: - string - 'null' enum: - 'yes' - 'no' - prefer_not_to_say - null description: 'Has a family member or close personal relationship with an employee of a company they apply to. Global, not per-employer: only a "no" is committable on a form.' example: 'no' exportControlUsPerson: type: - string - 'null' enum: - 'yes' - 'no' - prefer_not_to_say - null description: Is a U.S. person for export-control (ITAR / EAR) purposes. The only declaration whose "yes" is the permissive answer. example: 'yes' highestEducation: type: - string - 'null' enum: - high_school - associate - bachelor - master - doctorate - any - null description: Highest education level completed example: bachelor yearsOfExperience: type: - integer - 'null' description: Total years of professional experience example: 5 veteranStatus: type: - string - 'null' enum: - not_a_veteran - veteran - disabled_veteran - recently_separated_veteran - active_duty_wartime_veteran - armed_forces_service_medal_veteran - prefer_not_to_say - null description: Veteran status for EEO forms example: prefer_not_to_say disabilityStatus: type: - string - 'null' enum: - 'yes' - 'no' - prefer_not_to_say - null description: Disability status for EEO forms example: prefer_not_to_say gender: type: - string - 'null' enum: - male - female - non_binary - prefer_not_to_say - other - null description: Gender for EEO forms example: prefer_not_to_say ethnicity: type: - string - 'null' enum: - american_indian_or_alaska_native - asian - black_or_african_american - hispanic_or_latino - native_hawaiian_or_pacific_islander - white - two_or_more_races - prefer_not_to_say - other - null description: Ethnicity for EEO forms example: prefer_not_to_say sexualOrientation: type: - string - 'null' enum: - heterosexual - gay_or_lesbian - bisexual - prefer_not_to_say - other - null description: Sexual orientation for EEO forms example: prefer_not_to_say pronouns: type: - string - 'null' maxLength: 40 description: Preferred pronouns, self-described free text. Never inferred from a name or from `gender`. example: she/her applyReadiness: type: object properties: missingRequiredForApply: type: array items: type: string enum: - zip_code - state - country description: An application_profile column an employer form can require and that we never invent example: zip_code description: Profile columns an employer form can require that are empty. We never invent these; the user fills them. example: - zip_code - state required: - missingRequiredForApply description: Required-for-apply columns that are still empty addressCompleteness: type: object properties: city: type: boolean complete: type: boolean country: type: boolean postalCode: type: boolean state: type: boolean street: type: boolean required: - city - complete - country - postalCode - state - street createdAt: type: string description: Profile creation timestamp example: '2026-01-15T12:00:00Z' updatedAt: type: string description: Profile last update timestamp example: '2026-03-10T08:30:00Z' required: - id - userId - phone - addressLine1 - addressLine2 - city - state - zipCode - country - linkedinUrl - portfolioUrl - githubUrl - currentEmployer - dateOfBirth - workAuthorization - willingToRelocate - relocationLocations - salaryCurrency - availableStartDate - noticePeriod - personalNarrative - travelWillingness - securityClearance - restrictiveCovenantStatus - criminalRecordStatus - backgroundCheckConsent - relatedToEmployeeStatus - exportControlUsPerson - highestEducation - yearsOfExperience - veteranStatus - disabilityStatus - gender - ethnicity - sexualOrientation - pronouns - applyReadiness - addressCompleteness - createdAt - updatedAt EducationEntry: type: object properties: degree: type: string example: B.S. Computer Science endDate: type: - string - 'null' gpa: type: - string - 'null' highlights: type: - array - 'null' items: type: string institution: type: string example: MIT location: type: - string - 'null' startDate: type: - string - 'null' required: - degree - institution additionalProperties: false ApiErrorResponse: type: object properties: error: type: object properties: code: type: string context: oneOf: - type: object properties: errorType: type: string enum: - validation field: type: string fieldErrors: type: array items: type: object properties: code: type: string expected: type: string field: type: string message: type: string received: type: string required: - field - message schemaName: type: string required: - errorType - type: object properties: attemptedEmail: type: string format: email errorType: type: string enum: - authentication failureReason: type: string enum: - invalid_credentials - account_locked - expired_token - token_expired - missing_token - invalid_token - session_expired description: Reason for authentication failure example: token_expired ipAddress: type: string operation: type: string service: type: string userAgent: type: string required: - errorType - type: object properties: actualPermission: type: string errorType: type: string enum: - authorization requiredPermission: type: string resource: type: string resourceId: type: string userId: type: string required: - errorType - type: object properties: constraint: type: string errorType: type: string enum: - database operation: type: string enum: - insert - update - delete - select - batch - transaction - migration description: Type of database operation example: select resourceId: type: string sqlState: type: string table: type: string userId: type: string required: - errorType - type: object properties: endpoint: type: string errorType: type: string enum: - external_service httpStatus: type: number operation: type: string resourceId: type: string responseTime: type: number retryAttempt: type: integer service: type: string serviceName: type: string userId: type: string required: - errorType - type: object properties: errorType: type: string enum: - resource resource: type: string resourceId: type: string service: type: string userId: type: string required: - errorType - type: object properties: errorType: type: string enum: - rate_limit limit: type: number resetTime: type: string windowMs: type: number required: - errorType - type: object properties: configKey: type: string errorType: type: string enum: - configuration operation: type: string service: type: string required: - errorType description: Type-safe error context information example: errorType: validation fieldErrors: - field: email message: Invalid email format details: oneOf: - type: object properties: detailType: type: string enum: - validation validationErrors: type: array items: type: object properties: code: type: string field: type: string message: type: string required: - field - message required: - detailType - validationErrors additionalProperties: false - type: object properties: detailType: type: string enum: - service_error originalError: type: string serviceName: type: string required: - detailType - serviceName additionalProperties: false - type: object properties: currentSize: type: number detailType: type: string enum: - batch originalError: type: string statementCount: type: number required: - detailType additionalProperties: false - type: object properties: detailType: type: string enum: - rate_limit limit: type: number resetTime: type: string windowMs: type: number required: - detailType - limit - resetTime - windowMs additionalProperties: false - type: object properties: conflictingField: type: string detailType: type: string enum: - resource resourceId: anyOf: - type: string - type: number resourceType: type: string required: - detailType additionalProperties: false - type: object properties: detailType: type: string enum: - generic info: type: string required: - detailType additionalProperties: false - type: object properties: currentTokens: type: integer minimum: 0 detailType: type: string enum: - insufficient_tokens requiredTokens: type: integer minimum: 0 required: - currentTokens - detailType - requiredTokens additionalProperties: false message: type: string validation: type: array items: type: object properties: code: type: string field: type: string message: type: string required: - field - message additionalProperties: false required: - code - message additionalProperties: false meta: $ref: '#/components/schemas/ResponseMetadata' success: type: boolean enum: - false required: - error - success additionalProperties: false securitySchemes: BearerAuth: description: 'Session token from signing in to AI Applyd, sent as `Authorization: Bearer `.' scheme: bearer type: http