Open Job Context Protocol

Living Document,

This version:
0.1
Version History:
https://github.com/ojcp-org/ojcp/commits/main
Issue Tracking:
GitHub
Editor:
(Recruitics)

Abstract

AI agents are beginning to search for jobs, evaluate opportunities, and submit applications on behalf of candidates — but existing job feeds, careers pages, and ATS apply flows were never designed for machine consumption. The Open Job Context Protocol (OJCP) defines how job opportunities, employer context, and application affordances should be expressed so that AI agents can discover, reason over, and act on them. OJCP achieves this through MCP-compatible tools with structured input and output schemas, a standard job manifest for capability discovery, and integration with WebMCP for browser-native agent access.

1. Introduction

The Open Job Context Protocol (OJCP) defines a standard for expressing job opportunities, employer context, and application affordances in a form that AI agents can discover, reason over, and act upon.

OJCP is not a competing protocol. It is a vertical application of MCP for the job data domain — the same way industry-specific schemas build on schema.org without replacing it. MCP defines how agents call tools; OJCP defines which tools exist for job search, application, and verification, what their inputs and outputs look like, and how trust and consent work in the hiring context. An agent that speaks MCP already speaks OJCP — it just needs to discover the provider’s manifest and call the standard tools.

OJCP is designed to interoperate with the Model Context Protocol (MCP), WebMCP, and structured data conventions including JSON-LD and schema.org. MCP is the primary integration path today. WebMCP is the natural progression as browser-native agent capabilities mature — career pages already have forms, and WebMCP lets providers annotate them as tools without deploying a separate MCP server.

1.1. Terminology

Job Context Provider

A server, web application, or API endpoint that publishes job opportunities and associated context in OJCP format.

Job Agent

An AI agent or LLM-powered application that consumes OJCP-formatted data to discover, evaluate, or act on job opportunities on behalf of a user or autonomous workflow.

Job Manifest

A machine-readable JSON document describing a Job Context Provider’s available tools, endpoints, and capabilities.

Job Tool

An MCP-compatible callable function exposed by a Job Context Provider.

Candidate Context

Structured data about a candidate passed by a Job Agent when calling Job Tools.

Apply Path

A declared, structured description of how a candidate can apply for a role.

Agent Declaration

Structured self-identification that agents MUST provide when initiating applications.

Identity Verifier

A third-party service (e.g., ID.me, Clear) that performs identity verification and issues cryptographic proofs. Verifiers are external to OJCP.

Verification Step

A discrete verification action required before an application can proceed, typically requiring human interaction (face scan, document upload, etc.).

Verification Proof

A signed, opaque cryptographic artifact issued by an Identity Verifier after successful verification. Contains no PII — only a subject hash, timestamps, and the verifier’s signature.

Verifier Manifest

A machine-readable JSON document hosted by an Identity Verifier at /.well-known/ojcp-verifier.json, declaring supported verification types, proof format, and public keys.

EEO Data

Voluntary Equal Employment Opportunity self-identification data (gender, race/ethnicity, veteran status, disability status) collected per applicable regulations. Carried in application_data.eeo only when the candidate has explicitly chosen to disclose.

Manifest Signature

An optional cryptographic signature embedded in a Job Manifest that allows agents to verify the manifest’s authenticity and binding to the serving domain. See § 7 Provider Trust.

Trust Tier

A registry-assigned classification (unverified, verified, or audited) indicating the level of trust signals available for a provider. Drives agent obligations around PII transmission. See § 7.6 Registry Trust Tiers.

2. Job Manifest

2.1. Discovery

Every OJCP-compliant Job Context Provider MUST expose a Job Manifest at the well-known path:

/.well-known/ojcp.json

The manifest MUST be served with Content-Type: application/json.

2.2. Format

The manifest MUST include:

The manifest SHOULD include:

The manifest MAY include:

{
  "ojcp_version": "0.1",
  "provider": {
    "name": "Acme Corp Careers",
    "employer_id": "acme-corp",
    "logo_url": "https://careers.acme.com/logo.png",
    "description": "Acme Corp is a global leader in industrial innovation.",
    "culture_context": "We operate with a bias for action and invest heavily in internal mobility.",
    "industries": ["manufacturing", "engineering", "logistics"],
    "hq_location": { "city": "Chicago", "state": "IL", "country": "US" }
  },
  "feed_endpoints": {
    "search": "https://careers.acme.com/ojcp/v1/search",
    "detail": "https://careers.acme.com/ojcp/v1/jobs/{job_id}",
    "apply_init": "https://careers.acme.com/ojcp/v1/apply/init"
  },
  "mcp_endpoint": "https://careers.acme.com/ojcp/mcp",
  "tools": ["search_jobs", "get_job_detail", "get_employer_context", "begin_application", "submit_application", "check_application_status"],
  "apply_paths": ["ats_direct", "provider_hosted"],
  "auth": {
    "required": false,
    "optional_scopes": ["candidate_context", "application_tracking"]
  },
  "rate_limits": {
    "anonymous_rps": 10,
    "authenticated_rps": 100
  },
  "supported_verifiers": [
    {
      "verifier_id": "id.me",
      "verifier_name": "ID.me",
      "verifier_manifest_url": "https://id.me/.well-known/ojcp-verifier.json",
      "verification_types": ["identity", "government_id"],
      "required_for_paths": ["ats_direct"]
    }
  ]
}

3. Job Tools

OJCP defines a standard set of MCP-compatible tools. Providers MUST implement at least search_jobs. All other tools are RECOMMENDED.

3.1. search_jobs

Search for open job opportunities. Returns a ranked list of jobs matching the provided criteria.

Required input: query (string)

Optional input: location, filters, candidate_context, pagination

When Candidate Context is provided, the provider SHOULD return personalized results including fit_score and fit_rationale for each result.

{
  "name": "search_jobs",
  "description": "Search for open job opportunities. Returns a ranked list of jobs matching the provided criteria.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "query": {
        "type": "string",
        "description": "Natural language search query, e.g. 'senior backend engineer remote'"
      },
      "location": {
        "type": "object",
        "properties": {
          "city": { "type": "string" },
          "state": { "type": "string" },
          "country": { "type": "string" },
          "remote_ok": { "type": "boolean" },
          "radius_miles": { "type": "number" }
        }
      },
      "filters": {
        "type": "object",
        "properties": {
          "employment_type": {
            "type": "string",
            "description": "Standard values: full_time, part_time, contract, internship, temporary. Providers MAY accept additional values."
          },
          "salary_min": { "type": "number" },
          "salary_max": { "type": "number" },
          "experience_level": {
            "type": "string",
            "description": "Standard values: entry, mid, senior, lead, director, executive. Providers MAY accept additional values."
          },
          "posted_within_days": { "type": "integer" }
        }
      },
      "candidate_context": { "$ref": "https://ojcp.dev/schemas/v0.1/candidate-context.json" },
      "pagination": {
        "type": "object",
        "properties": {
          "limit": { "type": "integer", "default": 10, "maximum": 50 },
          "offset": { "type": "integer", "default": 0 }
        }
      }
    },
    "required": ["query"]
  }
}

Response: Returns ojcp_version, query, total_results, returned, offset, and a jobs array. Each job includes core fields (ojcp_id, title, employer, datePosted) plus summary data. When Candidate Context was provided, each job includes fit_score (0.0–1.0) and fit_rationale.

3.2. get_job_detail

Retrieve full details for a specific job posting, including responsibilities, qualifications, compensation, team context, and available apply paths.

Required input: job_id (string)

Optional input: include_employer_context, candidate_context

{
  "name": "get_job_detail",
  "description": "Retrieve full details for a specific job posting.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "job_id": { "type": "string", "description": "The unique OJCP job identifier" },
      "include_employer_context": { "type": "boolean", "default": true },
      "candidate_context": { "$ref": "https://ojcp.dev/schemas/v0.1/candidate-context.json" }
    },
    "required": ["job_id"]
  }
}

Response: Returns ojcp_version and a full job object conforming to the JobPosting schema. When include_employer_context is true, includes an employer_context object. When Candidate Context was provided, includes fit_score and fit_rationale.

3.3. get_employer_context

Retrieve contextual information about an employer — culture, team structure, benefits, and hiring practices.

Required input: employer_id (string)

{
  "name": "get_employer_context",
  "description": "Retrieve contextual information about an employer including culture, team structure, benefits, and hiring practices.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "employer_id": { "type": "string", "description": "The OJCP employer identifier" }
    },
    "required": ["employer_id"]
  }
}

Response: Returns ojcp_version, employer_id, name, and optional fields including description, culture_context, industries, hq_location, benefits, hiring_process, and open_roles_count.

3.4. begin_application

Initiate an application for a job. Returns an apply path descriptor including required fields, the preferred apply mechanism, and a session token for multi-step apply flows.

Required input: job_id (string)

Optional input: apply_path, candidate_context, agent_declaration, source_attribution

Providers SHOULD require Agent Declaration and MAY reject requests without a valid user_consent_token.

{
  "name": "begin_application",
  "description": "Initiate an application for a job.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "job_id": { "type": "string" },
      "apply_path": {
        "type": "string",
        "enum": ["ats_direct", "provider_hosted", "platform_native", "email", "external_redirect", "custom"],
        "description": "Preferred application mechanism. If omitted, the provider selects the optimal path. Note: email and external_redirect do not support agent submission — begin_application will return the URL for the candidate to complete manually."
      },
      "candidate_context": { "$ref": "https://ojcp.dev/schemas/v0.1/candidate-context.json" },
      "agent_declaration": { "$ref": "https://ojcp.dev/schemas/v0.1/agent-declaration.json" },
      "source_attribution": {
        "type": "object",
        "description": "Attribution for the traffic source that led the candidate to this job. Enables job boards and aggregators to track referral value.",
        "properties": {
          "referrer": { "type": "string", "description": "The source that referred the candidate (e.g., domain name, platform identifier)." },
          "reference_id": { "type": "string", "description": "Opaque token the source can use for reconciliation (e.g., click ID, session ID)." }
        }
      }
    },
    "required": ["job_id"]
  }
}

Response: Returns ojcp_version, application_id, status (initiated, pending_submission, or pending_verification), and an apply_path descriptor with type, required_fields, url, and supports_agent_submission. When status is pending_verification, includes verification_steps — an array of Verification Steps the candidate must complete before the application can proceed — and verification_deadline, an ISO 8601 timestamp after which incomplete verifications cause the application to expire (see § 9.9.3 Verification Deadline). May include session_token for multi-step flows, expires_at, and screening_questions — an array of questions the candidate must answer, each with question_id, question_text, answer_type, and optional options. Questions MAY include validation constraints: min_length/max_length for text, pattern (regex) for format validation, and min/max for number and date answers. Agents include the answers in submit_application’s application_data.answers keyed by question_id.

A begin_application response requiring identity verification:
{
  "ojcp_version": "0.1",
  "application_id": "app_abc123",
  "status": "pending_verification",
  "apply_path": {
    "type": "provider_hosted",
    "url": "https://careers.defense-corp.com/apply/analyst-2026-001",
    "required_fields": ["resume", "email", "name", "security_clearance_level"],
    "supports_agent_submission": true,
    "requires_verification": true,
    "accepted_verifiers": ["id.me"]
  },
  "verification_steps": [
    {
      "step_id": "vs_1",
      "type": "identity",
      "verifier_id": "id.me",
      "verifier_name": "ID.me",
      "verification_url": "https://verify.id.me/ojcp?session=xyz789",
      "required": true,
      "human_required": true,
      "proof_delivery": "agent_submitted",
      "estimated_completion_minutes": 5,
      "instructions": "Candidate must complete ID.me identity verification. Open the verification URL in a browser."
    }
  ],
  "session_token": "sess_def456",
  "expires_at": "2026-03-20T12:00:00Z"
}

3.5. submit_application

Submit a previously initiated application, including Verification Proofs when identity verification was required. This tool completes multi-step apply flows — particularly those requiring identity verification.

Required input: application_id (string), session_token (string)

Conditionally required input: verification_proofs — MUST be provided for each Verification Step where proof_delivery is "agent_submitted" (or omitted). Providers MUST return verification_failed with error missing_proof if proofs are omitted for agent-submitted steps. For steps where proof_delivery is "provider_managed", the provider already holds the proof — agents MUST NOT include proofs for those steps.

Optional input: application_data, candidate_context, agent_declaration, source_attribution

The application_data object carries the actual application payload — screening question answers, cover letter, resume reference, EEO data, and any custom fields required by the ATS. Its shape is determined by the required_fields, optional_fields, and screening_questions returned in the begin_application response.

The resume_url field requires a hosted URL. Agents with locally-stored resume files SHOULD use the provider’s resume upload endpoint when declared (see § 6 Resume Upload) to obtain a valid URL. Agents that already have a hosted resume URL pass it directly.

{
  "name": "submit_application",
  "description": "Submit a previously initiated application with verification proofs and/or candidate data.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "application_id": { "type": "string", "description": "Application ID from begin_application" },
      "session_token": { "type": "string", "description": "Session token from begin_application" },
      "application_data": {
        "type": "object",
        "description": "Application payload — screening question answers, cover letter, resume reference, and any custom fields. Shape is determined by the required_fields, optional_fields, and screening_questions from begin_application.",
        "properties": {
          "answers": {
            "type": "object",
            "description": "Screening question answers keyed by question_id.",
            "additionalProperties": true
          },
          "cover_letter": { "type": "string", "description": "Cover letter text." },
          "resume_url": { "type": "string", "format": "uri", "description": "URL to the candidate's resume document." },
          "eeo": { "$ref": "https://ojcp.dev/schemas/v0.1/eeo-data.json" },
          "custom_fields": {
            "type": "object",
            "description": "Additional ATS-specific fields. Keys match the field names from required_fields or optional_fields.",
            "additionalProperties": true
          }
        }
      },
      "candidate_context": { "$ref": "https://ojcp.dev/schemas/v0.1/candidate-context.json" },
      "verification_proofs": {
        "type": "array",
        "items": { "$ref": "https://ojcp.dev/schemas/v0.1/verification-proof.json" },
        "description": "Cryptographic proofs from completed verification steps"
      },
      "agent_declaration": { "$ref": "https://ojcp.dev/schemas/v0.1/agent-declaration.json" },
      "source_attribution": {
        "type": "object",
        "description": "Attribution for the traffic source. Same schema as begin_application.",
        "properties": {
          "referrer": { "type": "string" },
          "reference_id": { "type": "string" }
        }
      }
    },
    "required": ["application_id", "session_token"]
  }
}

Response: Returns ojcp_version, application_id, status (one of submitted, verification_failed, validation_failed, expired, rejected), and optional message and next_steps. When verification proofs fail validation, includes verification_errors with details per step. When screening question answers fail validation, returns status: "validation_failed" with a screening_errors array — each entry has question_id, error (one of required_missing, invalid_format, out_of_range, invalid_option, too_short, too_long), and message. Agents SHOULD fix the answers and resubmit. Responses MAY include a warnings array for non-fatal issues (e.g., fields_ignored, scope_exceeded).

3.6. check_application_status

Check the status of a previously initiated application. Agents SHOULD poll this tool during provider-managed verification flows to detect when verification completes and the application is ready for submission.

Required input: application_id (string)

{
  "name": "check_application_status",
  "description": "Check the status of a previously initiated application.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "application_id": { "type": "string", "description": "The application session ID returned by begin_application" }
    },
    "required": ["application_id"]
  }
}

Response: Returns ojcp_version, application_id, status, updated_at, and optional next_steps and estimated_response_days. When status is pending_verification, includes verification_status with steps_total, steps_completed, pending_steps, and completed_steps.

The status field spans the full application lifecycle. The pre-submission states (initiated, pending_submission, pending_verification) are returned by begin_application. The submission states (submitted, verification_failed, validation_failed, expired, rejected) are returned by submit_application. The post-submission states (received, reviewing, interview_scheduled, offer_extended, withdrawn) are exclusive to check_application_status and represent the employer-side hiring pipeline. rejected may appear at submission time (immediate rejection) or post-submission (employer decision).

Status Phase Returned by
initiated Pre-submission begin_application, check_application_status
pending_submission Pre-submission begin_application, check_application_status
pending_verification Pre-submission begin_application, check_application_status
submitted Submission submit_application, check_application_status
verification_failed Submission submit_application, check_application_status
validation_failed Submission submit_application
expired Submission submit_application
rejected Submission / Post-submission submit_application, check_application_status
received Post-submission check_application_status
reviewing Post-submission check_application_status
interview_scheduled Post-submission check_application_status
offer_extended Post-submission check_application_status
withdrawn Post-submission check_application_status

3.7. Error Responses

When any OJCP tool call fails, the provider MUST return a standard error envelope:

{
  "ojcp_version": "0.1",
  "error_code": "job_not_found",
  "message": "No job found with ID careers.acme.com:swe-99999.",
  "details": { "job_id": "careers.acme.com:swe-99999" }
}

The error envelope MUST include:

The error envelope MAY include:

Standard error codes:

Code Meaning
invalid_request Malformed request or missing required parameters.
missing_required_field A required field is missing from the input.
invalid_field_value A field value is outside the allowed range or format.
job_not_found The specified job_id does not exist.
employer_not_found The specified employer_id does not exist.
application_not_found The specified application_id does not exist.
invalid_session_token The session_token is invalid or does not match the application.
session_expired The application session has expired.
no_agent_apply_path No apply path supports agent submission for this job.
rate_limited Request rate limit exceeded. Check retry_after_seconds.
unauthorized Authentication required but not provided.
forbidden Authentication provided but insufficient permissions.
provider_error Internal provider error. Agent should retry with backoff.
agent_signature_missing A signature was required for this context but none was provided. See § 8 Agent Identity.
agent_signature_invalid The RFC 9421 signature failed verification.
agent_key_not_found No key matching keyid in the agent’s signature directory.
agent_digest_mismatch Content-Digest does not match the request body.
agent_algorithm_unsupported Signature algorithm not accepted, or the wire alg conflicts with the resolved key.
agent_signature_expired The signature is expired, not yet valid, or its window exceeds the permitted maximum.
agent_nonce_replayed The (keyid, nonce) pair was already seen within the freshness window.
agent_identity_mismatch The Signature-Agent origin does not match the agent_id registrable domain.

Verification-specific errors (e.g., invalid_signature, expired_proof) are returned in the verification_errors array of submit_application responses, not as top-level error codes.

For MCP transport, errors MUST be returned as a JSON-RPC error with code -32000 and the error envelope in the data field. For REST transport, errors MUST use appropriate HTTP status codes (400, 401, 403, 404, 429, 500) with the error envelope as the response body.

3.8. Custom Tools

The six tools defined in § 3.1 search_jobs through § 3.6 check_application_status are the standard OJCP vocabulary. Providers MAY expose additional, provider-specific tools.

Custom tools MUST use a namespaced name in the format {provider}:{tool_name} to distinguish them from standard tools. For example, acme:get_referral_link or greenhouse:schedule_interview. Standard tools use bare names (no prefix) — agents MUST recognize them.

Custom tools are self-described via MCP’s native tools/list method, which returns the tool name, description, and inputSchema. No additional manifest declaration is required, though providers MAY list custom tool names in the manifest tools array for discoverability.

Agents SHOULD treat unrecognized tool names (those with a : separator) as provider-specific extensions and MAY invoke them based on their description and inputSchema. Agents MUST NOT assume custom tools follow standard OJCP response schemas.

4. Data Schemas

4.1. JobPosting

OJCP extends schema.org’s JobPosting with agent-specific fields.

A conforming JobPosting MUST include:

A conforming JobPosting SHOULD include:

Note: Job posting fields that originate from schema.org (e.g., employmentType, experienceLevel, jobLocation, baseSalary) use camelCase to maintain compatibility. OJCP-specific extension fields (e.g., skills_required, team_context, urgency) and tool input parameters (e.g., experience_level, salary_min) use snake_case. Agents SHOULD handle both conventions.

Note: Enumerated fields such as employmentType and experience_level define standard values but are intentionally open — providers MAY use additional values (e.g., freelance, volunteer, apprenticeship) and agents SHOULD handle unrecognized values gracefully. OJCP normalizes employment types to lowercase (e.g., full_time); schema.org uses UPPERCASE (FULL_TIME) — providers SHOULD map accordingly.

OJCP-specific extension fields:

{
  "@context": ["https://schema.org", "https://ojcp.dev/context/v1"],
  "@type": "JobPosting",
  "ojcp_id": "careers.acme.com:swe-42091",
  "title": "Senior Software Engineer, Platform",
  "employer": { "@type": "Organization", "name": "Acme Corp", "ojcp_employer_id": "acme-corp" },
  "datePosted": "2026-02-28",
  "validThrough": "2026-04-15",
  "employmentType": "full_time",
  "baseSalary": {
    "@type": "MonetaryAmountDistribution",
    "currency": "USD",
    "minValue": 140000,
    "maxValue": 185000,
    "unitText": "YEAR"
  },
  "skills_required": ["Go", "Kubernetes", "distributed systems"],
  "skills_preferred": ["Rust", "eBPF"],
  "team_context": "Platform team of 12 engineers; reports to VP Engineering.",
  "urgency": "high",
  "apply_paths": [
    {
      "type": "provider_hosted",
      "url": "https://careers.acme.com/apply/swe-42091",
      "estimated_completion_minutes": 8,
      "required_fields": ["resume", "work_authorization"],
      "supports_agent_submission": true
    }
  ]
}

4.2. CandidateContext

A minimal, consent-scoped candidate profile. Agents MUST NOT transmit candidate data beyond the declared consent_scope. The resume_embedding_hash field enables semantic match scoring without transmitting full resume text.

A conforming Candidate Context MUST include:

The following table defines which fields are permitted at each consent scope level. Each scope includes all fields from lower scopes.

Field Minimum Scope
skills, experience_years, location_preference, employment_type_preference search_personalization
current_title, salary_expectation, work_authorization, resume_embedding_hash fit_scoring
name, email, resume_url application_prefill

full_profile permits all fields with no restrictions.

Agents MUST NOT include fields beyond the declared consent_scope. Providers MUST ignore any field that exceeds the declared scope — dropping the field rather than rejecting the request. Providers SHOULD include a warnings array in the response with code "fields_ignored" listing the dropped field names so agents can detect misconfiguration.

{
  "ojcp_candidate_context_version": "0.1",
  "consent_scope": "fit_scoring",
  "skills": ["Python", "machine learning", "SQL"],
  "experience_years": 6,
  "current_title": "ML Engineer",
  "location_preference": { "city": "Austin", "remote_ok": true },
  "employment_type_preference": ["full_time"],
  "salary_expectation": { "min": 150000, "currency": "USD" },
  "work_authorization": "US_citizen"
}

4.3. AgentDeclaration

Agent self-identification for audit trails and rate limiting.

A conforming Agent Declaration MUST include:

Agents SHOULD include a user_consent_token when submitting applications. For autonomous interaction modes, the token represents the operator’s authorization rather than per-action user consent. The token format and verification mechanism are out of scope for v0.1 — providers decide what level of trust to require.

{
  "agent_id": "com.apple.intelligence.jobseeker",
  "agent_version": "1.0",
  "acting_on_behalf_of": "human_user",
  "user_consent_token": "eyJhbGciOiJFZERTQSJ9...",
  "interaction_mode": "assisted"
}

4.4. EEOData

Voluntary Equal Employment Opportunity self-identification data carried in application_data.eeo when the candidate has chosen to disclose. EEO data collection is regulated — in the US by the EEOC and OFCCP; in the EU as Article 9 GDPR special-category data — and OJCP imposes additional protocol-level requirements beyond what those frameworks mandate.

Provider obligations:

Agent obligations:

A conforming EEO Data object MAY include any of the following — all fields are optional, and decline_to_answer MUST be a valid value where applicable:

Providers operating in non-US jurisdictions MAY define alternate enumerations for race_ethnicity and veteran_status appropriate to the local regulatory framework. The jurisdiction field signals which enumeration set is in effect.

{
  "eeo": {
    "gender": "female",
    "race_ethnicity": ["asian"],
    "veteran_status": "not_protected_veteran",
    "disability_status": "decline_to_answer",
    "jurisdiction": "US"
  }
}

4.5. VerificationStep

A discrete verification action required before an application can proceed. Returned in the verification_steps array of a begin_application response when status is pending_verification.

A conforming Verification Step MUST include:

A Verification Step MAY include:

4.6. VerificationProof

A signed cryptographic artifact issued by an Identity Verifier after the candidate completes verification. Contains no PII.

A conforming Verification Proof MUST include:

4.7. VerifierManifest

Discovery document hosted by Identity Verifiers at /.well-known/ojcp-verifier.json. Enables providers to discover verifier capabilities and retrieve public keys for proof validation.

A conforming Verifier Manifest MUST include:

A Verifier Manifest SHOULD include:

5. Apply Paths

OJCP normalizes the fragmented landscape of application mechanisms into a standard taxonomy:

Type Description Agent Submission
ats_direct Apply directly via ATS (Workday, Greenhouse, Lever, etc.) Varies by ATS
provider_hosted Provider controls the apply flow and delivers to ATS Full support
platform_native Third-party platform owns the flow (e.g., Indeed Apply, Easy Apply) Limited
email Legacy email-based application Not supported
external_redirect Redirect to opaque external page Not supported
custom Catch-all for non-standard apply mechanisms Varies

Each apply path object MUST include:

Each apply path object SHOULD include:

Each apply path object MAY include:

Providers are RECOMMENDED to implement at least one apply path where supports_agent_submission is true.

6. Resume Upload

application_data.resume_url requires a hosted URL pointing to the candidate’s resume. Agents working with locally-stored resumes (computer-use agents, browser extensions reading user files, mobile agents) need a way to obtain that URL. OJCP defines an optional resume upload endpoint that providers can expose so agents do not have to operate their own file hosting.

Resume upload is OPTIONAL. Providers that prefer not to host candidate files do not declare the endpoint, and agents are responsible for hosting resumes themselves.

6.1. Endpoint Discovery

Providers that accept resume uploads declare the endpoint in their Job Manifest under resume_upload:

{
  "resume_upload": {
    "endpoint": "https://careers.acme.com/ojcp/v1/upload/resume",
    "max_size_mb": 10,
    "max_uploads_per_session": 5,
    "accepted_mime_types": [
      "application/pdf",
      "application/msword",
      "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
    ]
  }
}

Agents check for resume_upload in the manifest. If absent, the provider does not accept uploads — agents must supply a hosted resume_url they obtained elsewhere.

6.2. Upload Request

Upload is a multipart/form-data POST to the declared endpoint:

POST /ojcp/v1/upload/resume HTTP/1.1
Host: careers.acme.com
Authorization: Bearer {session_token}
Content-Type: multipart/form-data; boundary=----abc

------abc
Content-Disposition: form-data; name="resume"; filename="resume.pdf"
Content-Type: application/pdf

[binary file content]
------abc--

The request MUST include:

The request MAY include additional form fields the provider documents — these are out of scope for OJCP v0.1.

6.3. Upload Response

On success, the provider returns HTTP 200 with a JSON body:

{
  "ojcp_version": "0.1",
  "resume_url": "https://uploads.careers.acme.com/r/abc123def?sig=...",
  "expires_at": "2026-04-30T12:00:00Z",
  "filename": "resume.pdf",
  "size_bytes": 245678,
  "mime_type": "application/pdf"
}

The response MUST include:

The response SHOULD include filename, size_bytes, and mime_type to confirm what was accepted.

The returned URL SHOULD be cryptographically signed and time-limited so it cannot be reused outside the issuing session.

6.4. Server-Side Validation

Providers MUST:

Providers SHOULD scan uploads for malware before issuing the resume_url. Providers MAY reject any upload at their discretion.

6.5. Error Responses

The upload endpoint returns the standard OJCP error envelope (§ 3.7 Error Responses) with the following additional codes:

Code HTTP status Meaning
upload_too_large 413 File exceeds max_size_mb.
upload_quota_exceeded 429 Session has reached max_uploads_per_session.
upload_format_rejected 415 File MIME type is not in accepted_mime_types, or content-sniffed type does not match the claimed type.
upload_virus_detected 422 File failed the provider’s malware scan.
invalid_session_token 401 Bearer token missing, invalid, or expired.

The standard rate_limited error code applies when the manifest’s rate_limits are exceeded. Upload-specific 429 responses (quota exceeded) are distinct from rate limiting and use upload_quota_exceeded.

6.6. Security

7. Provider Trust

Any site can publish an OJCP manifest. Without a trust mechanism, agents have no way to distinguish a legitimate employer from a malicious actor harvesting candidate PII. OJCP defines a layered trust model so agents can verify "the manifest is authentic and authorized by the domain owner" before transmitting candidate data.

The model uses existing primitives — HTTPS, JWS, JWK Sets — rather than introducing new infrastructure. Manifest signing is OPTIONAL for v0.1; unsigned providers remain interoperable but cannot reach the verified or audited registry tiers and SHOULD be treated with reduced trust by agents handling PII.

7.1. Threat Model

OJCP-aware agents face three primary trust risks when interacting with providers:

  1. Imposter providers. A malicious actor stands up careers.fake-employer.com with an OJCP manifest declaring fake jobs. Agents submit applications containing candidate PII (resume URL, email, work authorization) which the actor harvests for phishing, identity fraud, or sale.

  2. Manifest tampering. A legitimate provider’s manifest is intercepted or compromised — the served manifest is modified to redirect application submissions to an attacker-controlled endpoint.

  3. Stale or revoked credentials. A formerly-legitimate provider’s signing key is compromised; agents that cached the manifest continue to trust it after the key has been rotated.

Provider trust addresses all three. It does not address agent-side threats (a bad agent submitting spam to a legitimate provider) — those are addressed separately in § 12.5 Employer Controls and § 17 Open Questions.

7.2. Manifest Signing

A provider MAY sign its manifest by including a signature field. The signature is a JSON Web Signature ([RFC7515]) computed over the manifest content with the signature field excluded.

The signature object MUST include:

The signature object SHOULD include:

A signed manifest:
{
  "ojcp_version": "0.1",
  "provider": { "name": "Acme Corp Careers", "employer_id": "acme-corp" },
  "tools": ["search_jobs", "get_job_detail", "begin_application", "submit_application", "check_application_status"],
  "apply_paths": ["provider_hosted", "ats_direct"],
  "signature": {
    "alg": "ES256",
    "kid": "acme-key-2026-q2",
    "iss": "careers.acme.com",
    "signed_at": "2026-04-29T12:00:00Z",
    "expires_at": "2026-04-30T12:00:00Z",
    "value": "MEUCIQDx2..."
  }
}

7.2.1. Canonicalization

To produce or verify a signature, both parties MUST canonicalize the manifest content identically:

  1. Remove the signature field from the manifest object.

  2. Serialize the resulting object using JCS (JSON Canonicalization Scheme, [RFC8785]).

  3. Compute the signature over the canonicalized bytes.

Agents MUST use JCS for verification. Providers that produce signatures using other canonicalization schemes will produce signatures agents cannot validate.

7.3. JWK Set Discovery

A signing provider MUST publish a JWK Set ([RFC7517]) at:

/.well-known/ojcp-keys.json

The JWK Set MUST be served over HTTPS with Content-Type: application/jwk-set+json. Each key MUST include kid, kty, and alg fields.

Agents validate signatures by:

  1. Fetching the JWK Set from {provider_origin}/.well-known/ojcp-keys.json.

  2. Resolving the kid from the manifest’s signature object against the set.

  3. Verifying the alg in the signature matches the key’s alg.

  4. Verifying the signature over the canonicalized manifest content.

Agents SHOULD cache JWK Sets with a TTL of 1 hour. Agents MUST refetch the JWK Set when encountering an unknown kid.

7.3.1. Key Rotation

Providers SHOULD rotate signing keys periodically (RECOMMENDED: every 90 days). Rotation procedure:

Providers MUST NOT hard-code kid values or signing keys in agent libraries. Keys are always resolved dynamically from the JWK Set.

7.4. Validation Procedure

When an agent encounters a manifest with a signature field, it MUST perform the following validation steps before trusting the manifest for Candidate Context transmission at consent_scope of application_prefill or full_profile, or before submitting application_data:

  1. Domain match. Verify that the manifest was fetched over HTTPS from a domain matching the signature.iss claim (when present). Reject on mismatch — error: domain_mismatch.

  2. Canonicalize. Reproduce the canonicalized manifest bytes per § 7.2.1 Canonicalization.

  3. Resolve key. Fetch (or use cached) JWK Set from {origin}/.well-known/ojcp-keys.json. Find the key matching signature.kid. Refetch on cache miss. Reject if not found — error: key_resolution_failed.

  4. Verify algorithm. Verify signature.alg matches the JWK’s declared algorithm. Reject on mismatch — error: algorithm_mismatch.

  5. Verify signature. Validate the JWS signature over the canonicalized bytes using the resolved key. Reject on failure — error: invalid_signature.

  6. Check expiration. Verify signature.expires_at is in the future, with a clock skew tolerance of 60 seconds. Reject if expired — error: expired_signature.

If any step fails, the agent MUST treat the manifest as unverified and apply the corresponding restrictions (§ 7.7 Agent Obligations).

7.5. When to Validate

Signature validation imposes cost (one extra HTTPS fetch per provider on cold cache, plus a signature verify). To minimize overhead, agents validate lazily — at the consent boundary.

Agents MUST validate the manifest signature before:

Agents MAY skip validation for read-only operations:

This means an agent browsing jobs pays no signing cost. Validation runs only when the agent is about to send PII or initiate a real application.

7.6. Registry Trust Tiers

The OJCP Registry (§ 11.2 OJCP Registry) classifies listed providers into three tiers based on accumulated trust signals:

Tier Requirements
unverified Manifest exists at /.well-known/ojcp.json and parses as valid OJCP. No signature required. Default tier on first registration.
verified All unverified requirements, plus: manifest carries a valid signature (§ 7.2 Manifest Signing) and the registry has confirmed the employer identity (e.g., business registration cross-check, LinkedIn company page match, DUNS lookup, or equivalent).
audited All verified requirements, plus: the provider passes the OJCP conformance test suite, has completed a security review covering manifest signing key handling and PII flows, and has been listed without abuse reports for at least 90 days.

The find_ojcp_providers response includes a trust_tier field for each provider. Agents MAY filter results by min_trust_tier (e.g., only return verified or higher).

The registry exposes a manifest_signed boolean alongside trust_tier so agents can distinguish "the manifest is signed" from "the registry has additionally verified employer identity."

7.7. Agent Obligations

Agents MUST adjust their behavior based on the trust signals available for a provider:

Provider state Maximum consent_scope Submission allowed?
Manifest unsigned, no registry record fit_scoring Only with explicit, per-submission user consent
Manifest signed, registry tier unverified fit_scoring Only with explicit, per-submission user consent
Registry tier verified application_prefill Yes, subject to standard consent rules
Registry tier audited full_profile Yes, subject to standard consent rules

Agents SHOULD surface the trust tier to end users before submission so candidates can make informed decisions. Agents MUST NOT attempt to upgrade a provider’s effective trust tier through caching or by ignoring failed signature validations.

7.8. Provider-Side Considerations

Providers SHOULD sign their manifests to enable the verified registry tier and avoid the agent-side restrictions on unsigned providers. Implementation notes:

8. Agent Identity

AgentDeclaration.agent_id is self-asserted: without proof, any caller can claim to be ai.wayfarer.agent, so every mechanism that keys on agent_id (agent-scoped visibility, employer allowlists, rate limiting, abuse attribution, audit trails) is spoofable. This section defines how an agent makes its agent_id cryptographically verifiable using [RFC9421] HTTP Message Signatures with key discovery bound to the agent’s reverse-domain identity. It was adopted via RFC 0001.

This is an identity layer, not an authorization layer, and it is OPTIONAL. A provider that declares no signature requirements and an agent that signs nothing behave exactly as elsewhere in v0.1 (the agent is treated as unauthenticated). Agent identity is distinct from § 9 Identity Verification, which verifies the human candidate; the two compose.

8.1. Signing Profile

When an agent signs a request it MUST produce an [RFC9421] signature with:

Algorithms use [RFC9421] registry labels: ed25519 is RECOMMENDED (and is required for interoperability with currently-deployed verifiers, which are Ed25519-only); ecdsa-p256-sha256, ecdsa-p384-sha384, and rsa-pss-sha512 are PERMITTED; rsa-v1_5-sha256 MUST NOT be used. The verifier MUST derive the algorithm from the resolved key, not from any wire alg parameter, and MUST reject a wire alg that conflicts with the key ([RFC9421] §7.3.6).

POST /ojcp/mcp?limit=10 HTTP/1.1
Host: careers.acme.com
Signature-Agent: "https://agent.wayfarer.ai"
Content-Digest: sha-256=:X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=:
Content-Type: application/json
Signature-Input: ojcp=("@method" "@authority" "@path" "@query" "content-digest" "signature-agent");\
  created=1750684800;expires=1750685040;nonce="a1b2c3d4";keyid="poqkLGiymh_W0uP6PZFw-...";tag="web-bot-auth"
Signature: ojcp=:k8J...:

The Content-Digest, Signature, and keyid values above are illustrative placeholders, not a real signature over the example body.

8.2. Key Discovery

The Signature-Agent header carries an https:// directory origin. The provider fetches a JWK Set at <origin>/.well-known/http-message-signatures-directory (media type application/http-message-signatures-directory+json) and selects the key whose [RFC7638] thumbprint equals keyid. Unlike the manifest-signing JWKS in § 7.3 JWK Set Discovery (which resolves by kid at a different path), the agent directory resolves by thumbprint; providers SHOULD apply the same key-rotation, overlap, and cache guidance as § 7.3 JWK Set Discovery but MUST NOT assume the same path, media type, or kid-based lookup.

Because this fetch is triggered by an unauthenticated, attacker-influenced origin, it is an SSRF vector and MUST be hardened. The fetch MUST use HTTPS and MUST NOT follow cross-host redirects. Providers MUST resolve the origin’s host and refuse the fetch when it resolves to a loopback, private ([RFC1918]), link-local, or unique-local address, and MUST re-validate the address actually connected to against that rule (defeating DNS rebinding). Providers MUST enforce a response size limit and a connect/read timeout. This well-known URI is defined by the Web Bot Auth directory draft and is consumed — not registered — by OJCP.

8.3. Identity Binding

The Signature-Agent origin MUST correspond to the registrable domain of agent_id — the domain obtained by reversing the reverse-domain identifier and taking its registrable domain per the Public Suffix List. For ai.wayfarer.agent the registrable domain is wayfarer.ai, so Signature-Agent MUST be on wayfarer.ai or a subdomain. The Public Suffix List including its PRIVATE section MUST be used (using the ICANN-only section would collapse, e.g., user.github.io to github.io and let any github.io agent bind another’s agent_id). Both the agent_id-derived domain and the Signature-Agent host MUST be normalized to their A-label (punycode) form and lowercased before comparison. This binding is what prevents an attacker from hosting keys on a domain they control while claiming another party’s agent_id. (Note: this "registrable domain" is unrelated to the @authority covered component in § 8.1 Signing Profile, which is the request’s own host.)

8.4. Verification

A provider that accepts a signed request MUST, in order:

  1. Resolve the signing key from the Signature-Agent directory by keyid thumbprint.

  2. Verify the signature per [RFC9421], deriving the algorithm from the resolved key.

  3. Verify Content-Digest matches the received (or empty) body.

  4. Verify freshness: created and expires MUST both be present; created MUST NOT be in the future beyond the clock-skew tolerance (RECOMMENDED ≤ 60s); expires MUST NOT be in the past; and the window (expires minus created) MUST NOT exceed 300s.

  5. Verify the nonce has not been seen for this keyid within the freshness window. Providers MUST maintain a replay cache retaining each (keyid, nonce) at least until its expires; because the window is capped at 300s (step 4) the cache is bounded. [RFC9421] defines nonce but leaves replay detection to the application.

  6. Verify the identity binding (§ 8.3 Identity Binding).

  7. Treat agent_id as verified only if all of the above pass.

On any failure the provider MUST return the standard OJCP error envelope (§ 3.7 Error Responses) with one of the agent-signature error codes registered in § 3.7 Error Responses: agent_signature_missing, agent_signature_invalid, agent_key_not_found, agent_digest_mismatch, agent_algorithm_unsupported, agent_signature_expired, agent_nonce_replayed, agent_identity_mismatch — and MUST NOT process the request as if the identity were verified. When a required context (see § 8.5 Manifest Declaration) is called without a valid signature, the provider MUST NOT fall back to unsigned processing.

With a verified identity, providers SHOULD bind the apply session to the signer: a submit_application whose verified signer differs from the begin_application signer for the same session SHOULD be rejected.

8.5. Manifest Declaration

Providers declare support and requirements in the Job Manifest auth block:

"auth": {
  "required": false,
  "optional_scopes": ["candidate_context", "application_tracking", "restricted_feed"],
  "agent_signatures": {
    "supported": true,
    "algorithms": ["ed25519", "ecdsa-p256-sha256"],
    "required_for": ["restricted_feed", "application_prefill", "submit_application"]
  }
}

required_for lists contexts where a verified identity is mandatory. Each entry is one context token from a fixed vocabulary, and a provider MUST require a verified agent_id for any request that matches a listed token:

Permissionless unsigned access SHOULD remain available for anonymous search_jobs at fit_scoring scope and below.

8.6. Personal-Agent Delegation

For an agent acting on behalf of an individual (who does not control a domain), identity is two facts established by two keys:

  1. Platform attestation§ 8.1 Signing Profile through § 8.4 Verification, signed with the platform’s key. The agent_id identifies the platform (e.g., ai.northstar.assistant, whose registrable domain — and therefore Signature-Agent origin — is northstar.ai). This proves which software is calling; it does not establish user authority.

  2. User authorization — an OPTIONAL user_mandate carried in the AgentDeclaration: a credential signed by a key the user controls that endorses the agent’s signing key (a key-binding) and is bound to a hash of the specific action, realized as an SD-JWT Verifiable Credential so the agent can prove "an authorized user of this platform mandated action X" without disclosing which human unless the provider requires it.

Normative rules:

The precise user_mandate schema is defined by a forthcoming consent RFC; this section fixes the requirement (user-rooted, key-bound, selectively disclosed) so the identity model is correct for personal agents from the outset.

9. Identity Verification

As AI agents begin submitting applications on behalf of candidates, employers increasingly need to verify that a real, identifiable human stands behind each submission. OJCP defines how agents discover, orchestrate, and submit identity Verification Proofs without PII ever flowing through the protocol.

9.1. Overview

Identity verification in OJCP is a three-party interaction:

  1. The provider declares which apply paths require verification and which Identity Verifiers are accepted. The provider initiates verification sessions with verifiers and may collect proofs directly (provider-managed) or delegate proof collection to the agent (agent-submitted).

  2. The agent detects verification requirements, presents the verification URL to the candidate, and — for agent-submitted flows — collects the resulting proof and submits it with the application.

  3. The **Identity Verifier** (e.g., ID.me, Clear) performs the actual verification (face scan, document upload, etc.) and issues a signed Verification Proof.

9.2. Flow

The verification flow depends on the proof_delivery mode declared in each Verification Step:

9.2.1. Agent-Submitted Flow

When proof_delivery is "agent_submitted" (or omitted — this is the default):

  1. Agent calls begin_application — provider returns status: "pending_verification" with a verification_steps array.

  2. Agent inspects each step’s type, verifier_id, verification_url, and proof_delivery.

  3. Agent presents the verification_url to the candidate (opens browser, deep link, etc.). The human_required flag indicates the agent cannot complete this step itself.

  4. Candidate completes verification with the Identity Verifier (face scan, government ID upload, etc.).

  5. Identity Verifier issues a Verification Proof — a signed JWS artifact.

  6. Proof is delivered to the agent (see § 9.5 Proof Delivery).

  7. Agent calls submit_application with the verification_proofs array and the session_token from step 1.

  8. Provider validates each proof (see § 9.10 Provider Claim Validation).

  9. Application accepted — status transitions to submitted.

9.2.2. Provider-Managed Flow

When proof_delivery is "provider_managed" (common for embedded verification like an iframe within an apply form):

  1. Agent calls begin_application — provider returns status: "pending_verification" with a verification_steps array where proof_delivery is "provider_managed".

  2. Agent presents the verification_url to the candidate.

  3. Candidate completes verification. The proof is delivered directly to the provider via server callback or iframe postMessage — the agent never handles the proof.

  4. Agent polls check_application_status until the status advances from pending_verification.

  5. Agent calls submit_application without verification_proofs — the provider already has them.

  6. Application accepted — status transitions to submitted.

For provider-managed steps, verification_proofs in submit_application is NOT required. Providers MUST NOT return missing_proof errors for steps where proof_delivery was "provider_managed".

9.3. No PII Through OJCP

Verification Proofs MUST NOT contain personally identifiable information. A proof contains:

Biometric data, government ID numbers, names, and dates of birth MUST remain with the Identity Verifier and MUST NOT be transmitted through OJCP or to the provider.

9.4. Session Initiation

When a provider receives a begin_application request for a job that requires identity verification, the provider MUST create a verification session with each required Identity Verifier before returning the pending_verification response.

The provider initiates a session by sending a POST request to the verifier’s verification_endpoint (declared in the verifier’s manifest). The request SHOULD include:

The verifier responds with:

Provider → Verifier session initiation request:
POST https://verify.clear.me/ojcp/start
{
  "provider_id": "careers.acme.com",
  "verification_type": "identity",
  "nonce": "app_abc123",
  "callback_url": "https://careers.acme.com/ojcp/verify/callback"
}

Verifier response:

{
  "session_id": "clear_sess_789",
  "verification_url": "https://verify.clear.me/session/clear_sess_789",
  "expires_at": "2026-03-20T12:00:00Z"
}

Note: The session initiation protocol between provider and verifier is intentionally minimal. Verifiers MAY require additional parameters (API keys, webhook signatures, etc.) — these are bilateral agreements outside the scope of OJCP. OJCP defines the proof format, delivery model, and validation procedure; the verifier’s internal session API is their own.

9.5. Proof Delivery

After a candidate completes verification, the Verification Proof must reach the party that will submit it to the provider. OJCP defines two delivery models, declared via the proof_delivery field on each Verification Step:

9.5.1. Provider-Managed Delivery

When proof_delivery is "provider_managed", the provider collects the proof directly from the verifier without agent involvement. This model is typical for embedded verification flows — for example, an Identity Verifier rendered as an iframe step within an apply form.

Delivery channels for provider-managed proofs:

In both cases, the agent never handles the proof. The agent polls check_application_status until verification completes, then calls submit_application without verification_proofs.

9.5.2. Agent-Submitted Delivery

When proof_delivery is "agent_submitted" (or omitted), the agent is responsible for collecting the proof and including it in submit_application. Delivery channels:

Agents SHOULD prefer redirect-based delivery when available, falling back to polling. Agents MUST NOT poll more frequently than once every 5 seconds.

9.6. Proof Format and Required Claims

The proof_token in a Verification Proof MUST be a JWS (JSON Web Signature, [RFC7515]) in compact serialization format.

9.6.1. JWS Header

The JWS header MUST include:

9.6.2. Required JWT Claims

The JWS payload MUST include the following claims:

9.6.3. Subject Hash

The subject_hash (and corresponding sub claim) MUST be computed as:

subject_hash = SHA-256( canonical_subject_identifier )

Where canonical_subject_identifier is a stable, verifier-internal identifier for the verified individual (e.g., the verifier’s internal user ID). The algorithm is always SHA-256; the output is a lowercase hex string. The identifier MUST be stable across verification sessions for the same individual to enable proof reuse.

9.6.4. JWKS Discovery

Providers validate proof signatures against the verifier’s published JWK Set ([RFC7517]). The JWK Set URL is declared in the verifier manifest as public_keys_url.

Providers SHOULD:

9.6.5. Key Rotation

Identity Verifiers SHOULD rotate signing keys periodically (RECOMMENDED: every 90 days). During rotation:

Providers MUST handle key rotation gracefully:

9.6.6. Clock Skew

When validating iat and exp claims, providers MUST allow for reasonable clock drift between systems. A tolerance of 60 seconds is RECOMMENDED. Providers MUST NOT accept proofs where exp is more than 60 seconds in the past, or where iat is more than 60 seconds in the future.

9.7. Verifier Discovery

Identity Verifiers MAY host a descriptor at /.well-known/ojcp-verifier.json:

{
  "ojcp_verifier_version": "0.1",
  "verifier_id": "id.me",
  "verifier_name": "ID.me",
  "description": "Identity verification for workforce and government applications.",
  "verification_types": ["identity", "government_id", "biometric"],
  "verification_endpoint": "https://verify.id.me/ojcp/start",
  "proof_delivery_methods": ["callback", "redirect", "polling"],
  "proof_format": "jws",
  "signing_algorithms": ["ES256"],
  "proof_ttl_seconds": 86400,
  "public_keys_url": "https://id.me/.well-known/ojcp-verifier-keys.json",
  "supported_countries": ["US"]
}

The public_keys_url endpoint returns a JWK Set that providers use to validate Verification Proof signatures.

Providers declare accepted verifiers in their Job Manifest via the supported_verifiers field.

Note: The verifier manifest schema also allows proof_format: "jws+jwe" to signal support for signed-then-encrypted proofs ([RFC7516]). However, JWE handling rules (key exchange, encryption algorithms, decryption procedures) are not defined in OJCP v0.1. Providers SHOULD reject jws+jwe proofs they cannot decrypt. Full JWE support will be defined in a future version of this specification.

9.8. Proof Reuse

Verification Proofs are bound to a specific application session via the nonce claim and are NOT reusable across applications. A proof issued for one application_id cannot be replayed against a different application.

Agents MUST NOT cache proofs beyond their expires_at. Providers MUST reject proofs where the nonce does not match the expected application_id. Providers MAY reject proofs from unrecognized verifiers. Providers SHOULD reject proofs older than the verifier’s declared proof_ttl_seconds even if expires_at has not passed.

9.9. Asynchronous Verification

Some verifications complete in seconds (face scan, government ID upload). Others take days (employment verification, background check). OJCP defines polling guidance, completion expectations, and abandonment semantics so agents and providers behave predictably across the full timescale.

9.9.1. Expected Completion Times

The following ranges are typical defaults. Providers MAY override per-step via estimated_completion_minutes on a Verification Step. Agents SHOULD prefer the per-step value when present.

Verification type Typical range Polling tier
identity 2–10 minutes Fast
government_id 2–10 minutes Fast
biometric 2–10 minutes Fast
address 1–24 hours Slow
employment 1–3 business days Slow
education 2–7 business days Slow
background_check 1–5 business days Slow

9.9.2. Polling Cadence

When proof_delivery is provider_managed, agents poll check_application_status to detect verification completion. Cadence depends on the elapsed time relative to estimated_completion_minutes:

Agents MUST NOT poll check_application_status more frequently than once every 30 seconds for any single application. Providers SHOULD return HTTP 429 with Retry-After if an agent exceeds this rate.

Agents SHOULD honor any Retry-After value returned by the provider, even when it exceeds the cadence above.

9.9.3. Verification Deadline

Each application with verification requirements has a verification_deadline — the absolute time after which any incomplete verification causes the application to transition to expired.

The verification_deadline is returned in the begin_application response and is a top-level field on the response object alongside verification_steps. Default values:

When verification_deadline is reached:

9.9.4. Abandonment Semantics

An application is considered abandoned when:

On abandonment, providers:

9.9.5. Resumption

If a candidate returns to complete verification after an agent stops polling (e.g., user closes the browser, agent times out), the agent or candidate MUST initiate a new application with begin_application. Providers MAY choose to recognize the candidate (via prior identity verification) and pre-fill or fast-track the new application, but each application has its own application_id, nonce, and verification_deadline.

Providers MUST NOT extend an existing verification_deadline retroactively — once an application is expired, it remains expired.

9.9.6. Agent Behavior on Long-Running Verifications

For Slow-tier verifications, agents SHOULD:

Agents MUST NOT promise the candidate a specific completion time tighter than estimated_completion_minutes (or the typical range above when no estimate is provided).

9.10. Provider Claim Validation

When a provider receives a Verification Proof, it MUST perform the following validation steps:

  1. Structure — Verify the proof_token is valid JWS compact serialization (three Base64url-encoded segments separated by .). Error: invalid_structure.

  2. Key resolution — Extract kid from the JWS header. Fetch (or use cached) JWK Set from the verifier’s public_keys_url. Find the matching key. Error: key_resolution_failed.

  3. Signature — Validate the JWS signature using the resolved public key and declared alg. Error: invalid_signature.

  4. Issuer — Verify iss matches the expected verifier_id. Error: issuer_mismatch.

  5. Audience — Verify aud matches the provider’s domain or identifier, or is "*". Error: audience_mismatch.

  6. Expiry — Verify exp is in the future and iat is in the past (with clock skew tolerance per § 9.6.6 Clock Skew). Error: expired_proof.

  7. Nonce — Verify nonce matches the expected application_id for this application session. Error: nonce_mismatch.

  8. Type — Verify the outer verification_type matches the step’s required type. Error: type_mismatch.

The complete set of verification error codes is: invalid_structure, key_resolution_failed, invalid_signature, issuer_mismatch, audience_mismatch, expired_proof, nonce_mismatch, type_mismatch, unrecognized_verifier, missing_proof.

If any check fails, the provider MUST return verification_failed with the appropriate error code in verification_errors.

9.11. Multi-Verifier Example

Jobs may require multiple verification types from different Identity Verifiers. For example, a financial compliance role might require both identity verification (Clear) and a background check (Checkr).

Manifest excerpt declaring multiple verifiers:
{
  "supported_verifiers": [
    {
      "verifier_id": "clear",
      "verifier_name": "Clear",
      "verifier_manifest_url": "https://clear.me/.well-known/ojcp-verifier.json",
      "verification_types": ["identity", "biometric"],
      "required_for_paths": ["ats_direct"]
    },
    {
      "verifier_id": "checkr",
      "verifier_name": "Checkr",
      "verifier_manifest_url": "https://checkr.com/.well-known/ojcp-verifier.json",
      "verification_types": ["background_check"],
      "required_for_paths": ["ats_direct"]
    }
  ]
}
A begin_application response with multiple verification steps — one provider-managed (Clear embedded in the apply form), one agent-submitted (Checkr sends the candidate a separate link):
{
  "ojcp_version": "0.1",
  "application_id": "app_fin_456",
  "status": "pending_verification",
  "apply_path": {
    "type": "ats_direct",
    "url": "https://careers.globex.com/apply/ciso-2026-003",
    "required_fields": ["resume", "email", "name"],
    "supports_agent_submission": true,
    "requires_verification": true,
    "accepted_verifiers": ["clear", "checkr"]
  },
  "verification_steps": [
    {
      "step_id": "vs_1",
      "type": "identity",
      "verifier_id": "clear",
      "verifier_name": "Clear",
      "verification_url": "https://verify.clear.me/session/clear_sess_789",
      "required": true,
      "human_required": true,
      "proof_delivery": "provider_managed",
      "estimated_completion_minutes": 3,
      "instructions": "Identity verification is embedded in the apply form. The candidate will complete a face scan within the application flow."
    },
    {
      "step_id": "vs_2",
      "type": "background_check",
      "verifier_id": "checkr",
      "verifier_name": "Checkr",
      "verification_url": "https://checkr.com/verify/chk_abc123",
      "required": true,
      "human_required": true,
      "proof_delivery": "agent_submitted",
      "estimated_completion_minutes": 10,
      "instructions": "Candidate must authorize a background check via Checkr. Open the verification URL in a browser."
    }
  ],
  "session_token": "sess_globex_789",
  "expires_at": "2026-03-20T12:00:00Z"
}

In this example, the agent handles the two steps differently:

  1. Clear (provider-managed) — The agent presents the apply form to the candidate. Clear’s verification is embedded as a step in the form. The provider receives the proof directly via callback. The agent does not handle this proof.

  2. Checkr (agent-submitted) — The agent opens the Checkr verification URL for the candidate. After the candidate completes the background check, the agent collects the proof (via redirect or polling) and includes it in submit_application.

When calling submit_application, the agent only includes proofs for agent_submitted steps:

{
  "application_id": "app_fin_456",
  "session_token": "sess_globex_789",
  "verification_proofs": [
    {
      "step_id": "vs_2",
      "verifier_id": "checkr",
      "verification_type": "background_check",
      "proof_token": "eyJhbGciOiJFUzI1NiIsImtpZCI6ImNoZWNrci1rZXktMSJ9.eyJpc3MiOiJjaGVja3IiLCJhdWQiOiJjYXJlZXJzLmdsb2JleC5jb20iLCJzdWIiOiJhMWIyYzNkNC4uLiIsImlhdCI6MTcxMDc3MjIwMCwiZXhwIjoxNzEwODU4NjAwLCJub25jZSI6ImFwcF9maW5fNDU2In0.signature",
      "subject_hash": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2",
      "issued_at": "2026-03-18T14:30:00Z",
      "expires_at": "2026-03-19T14:30:00Z"
    }
  ]
}

The provider validates the Checkr proof using the standard claim validation procedure (§ 9.10 Provider Claim Validation) and confirms that Clear’s provider-managed proof was already received via callback. If both are valid, the application transitions to submitted.

10. WebMCP Integration

WebMCP is a proposed web standard that exposes structured tools for AI agents on existing websites. OJCP tools can be registered via either the imperative or declarative WebMCP APIs.

10.1. Imperative API

Providers MAY register OJCP tools programmatically via document.modelContext.registerTool():

if ("modelContext" in document) {
  document.modelContext.registerTool({
    name: "search_jobs",
    description: "Search for open job opportunities. Pass a natural language query.",
    inputSchema: {
      type: "object",
      properties: {
        query: {
          type: "string",
          description: "Search query, e.g. 'senior backend engineer remote'"
        },
        remote_ok: {
          type: "string",
          enum: ["true", "false"],
          description: "Filter for remote-friendly positions"
        }
      },
      required: ["query"]
    },
    execute: async (params) => {
      const results = await fetchJobs(params);
      return {
        content: [
          { type: "text", text: `Found ${results.total} matching roles.` },
          { type: "json", data: results.jobs }
        ]
      };
    }
  });
}

Providers SHOULD call document.modelContext.unregisterTool(name) to remove tools when they are no longer applicable to the current page state.

The modelContext namespace is scoped to the active document. Earlier WebMCP drafts exposed it on navigator; following WebML CG guidance (webmcp#173), it now lives on document so that registered tools are not inadvertently shared across same-Window navigations. As of this writing the move is reflected in the WebMCP draft but not yet shipped in Chromium (crbug 515330187); implementers targeting current Chromium builds should feature-detect both locations during the transition.

10.2. Declarative API

Providers MAY annotate HTML forms as WebMCP tools using the toolname and tooldescription attributes. This is particularly relevant for apply paths where the application form already exists in the DOM:

<form toolname="begin_application"
      tooldescription="Apply for the Senior Backend Engineer role at Acme Corp"
      action="/apply/sbe-2026-001">
  <label for="name">Full Name</label>
  <input type="text" name="name" required
         toolparamdescription="Candidate's full legal name">

  <label for="email">Email</label>
  <input type="email" name="email" required>

  <input type="file" name="resume" accept=".pdf,.doc,.docx"
         toolparamdescription="Resume file (PDF or Word)">

  <button type="submit">Submit Application</button>
</form>

The browser automatically translates annotated form elements into a structured tool schema that agents can interpret. When an agent invokes the tool, the browser populates the form fields and — if toolautosubmit is set — submits the form automatically.

Providers can detect agent-initiated submissions via SubmitEvent.agentInvoked and return structured results using SubmitEvent.respondWith():

form.addEventListener("submit", (e) => {
  if (e.agentInvoked) {
    e.preventDefault();
    e.respondWith(
      submitApplication(new FormData(form)).then(result => ({
        content: [{ type: "json", data: result }]
      }))
    );
  }
});

10.3. Conformance

WebMCP-registered tools — whether imperative or declarative — MUST conform to the same input schemas as MCP-served tools defined in § 3 Job Tools. The inputSchema for imperative tools MUST use JSON Schema format with explicit type, properties, and required fields.

11. Feed Discovery

11.1. Well-Known Manifest

Any site hosting jobs MAY expose /.well-known/ojcp.json (§ 2 Job Manifest). Agents and browsers SHOULD probe this endpoint to discover OJCP capabilities before attempting tool invocation.

11.2. OJCP Registry

A public registry at registry.ojcp.dev indexes verified OJCP providers. The registry exposes an MCP-compatible tool:

{
  "name": "find_ojcp_providers",
  "description": "Find OJCP-compliant job data providers by industry, location, or employer name.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "industry": { "type": "string" },
      "employer_name": { "type": "string" },
      "location": { "type": "string" },
      "has_agent_apply": { "type": "boolean" },
      "min_trust_tier": {
        "type": "string",
        "enum": ["unverified", "verified", "audited"],
        "description": "Filter results to providers at this trust tier or higher. See spec § Provider Trust."
      }
    }
  }
}

Response: Returns ojcp_version, total_results, and a providers array. Each provider includes domain, name, manifest_url, mcp_endpoint, industries, location, tools, apply_paths, has_agent_apply, trust_tier (§ 7.6 Registry Trust Tiers), and manifest_signed.

Registration is free and open. The registry performs basic provider verification before listing. The registration process, verification criteria, and governance model for the OJCP Registry will be defined in a separate operational document before the registry launches.

Note: The find_ojcp_providers response includes JSON-LD @context and @type fields on provider entries to support linked-data consumers. Standard OJCP tool responses (e.g., search_jobs, get_job_detail) do not include JSON-LD context — they use plain JSON. Job postings returned by providers MAY include JSON-LD context when served as structured data on web pages.

12. Security and Privacy

12.1. Transport Security

All OJCP endpoints — including mcp_endpoint, feed_endpoints, manifest URLs, and verification URLs — MUST use HTTPS. Agents MUST NOT connect to OJCP providers over plain HTTP.

12.2. Candidate Privacy

12.3. Data Freshness

Job data is inherently volatile — positions are filled, salaries change, and postings expire. Agents and providers MUST account for staleness:

12.4. Rate Limiting

Rate limits declared in the Job Manifest MUST be enforced by the provider.

When a client exceeds the declared rate limit:

Agents MUST NOT retry before the indicated Retry-After / retry_after_seconds period. Agents SHOULD implement exponential backoff with jitter for subsequent retries.

12.5. Employer Controls

12.6. Ecosystem Integrity

12.7. Identity Verification Security

13. Interoperability

Standard Relationship
MCP OJCP tools are valid MCP tools. Any MCP client can call OJCP-compliant endpoints.
WebMCP OJCP tools can be registered via the imperative API (document.modelContext.registerTool()) or the declarative API (toolname/tooldescription form attributes) for browser agent access.
schema.org/JobPosting OJCP’s JobPosting extends schema.org. Existing structured data remains valid.
OpenAPI 3.1 OJCP REST endpoints SHOULD be documented with OpenAPI 3.1.
Indeed XML / ZipRecruiter API OJCP can be layered over existing feeds via adapter.

14. Versioning

This specification uses semantic versioning. Breaking changes require a major version bump and a 12-month deprecation window for the previous version.

The current version is 0.1 (Draft).

15. Conformance

15.1. Conforming Provider

A conforming OJCP provider MUST:

A conforming OJCP provider SHOULD:

15.2. Conforming Agent

A conforming OJCP agent MUST:

15.3. Schema Extensibility

OJCP schemas permit additional properties beyond those defined in this specification. This is intentional — providers and agents MAY include extension fields for forward compatibility. Implementations MUST ignore unrecognized fields rather than rejecting them.

16. IANA Considerations

This specification registers two well-known URIs per [RFC8615]:

URI suffix Change controller Specification
ojcp.json OJCP community § 2.1 Discovery
ojcp-verifier.json OJCP community § 9.7 Verifier Discovery

Registration will be submitted to IANA when this specification reaches stable status.

OJCP additionally consumes the /.well-known/http-message-signatures-directory well-known URI for agent key discovery (§ 8.2 Key Discovery); that URI is defined and registered by the Web Bot Auth directory specification, not by OJCP.

17. Open Questions

  1. Form skill schema — The form_skill_url field on apply paths references a companion specification for form skill descriptors. This schema — covering field mappings, validation rules, conditional logic, and submission instructions — will be defined in a separate RFC.

  2. CandidateContext as companion spec — Privacy sensitivity may warrant splitting Candidate Context into its own RFC track in a future version.

  3. Agent trust modelResolved by § 8 Agent Identity (RFC 0001): agent identity is made verifiable via [RFC9421] request signatures with domain-bound key discovery, distinct from and composing with candidate § 9 Identity Verification. Remaining open sub-question: whether the user-authorization user_mandate (see § 8.6 Personal-Agent Delegation) should be standardized in a future version rather than deferred to the companion consent RFC.

  4. Registry governance — Registration process, verification criteria, provider auditing, and dispute resolution for the OJCP Registry at registry.ojcp.dev.

  5. Verifier internal protocol — OJCP defines the Verification Proof envelope (JWS format, required claims, JWKS discovery, validation procedure) and the verifier discovery convention (/.well-known/ojcp-verifier.json). How verifiers conduct verification internally — candidate interaction flows, biometric capture, document processing — is intentionally out of scope. A companion specification may define verifier interoperability requirements in a future version.

18. Acknowledgements

The editor would like to thank the following individuals for their contributions to this specification:

Todor Minakov (Recruitics), Wilber Acosta (Recruitics).

Index

Terms defined by this specification

Terms defined by reference

References

Normative References

[RFC8785]
A. Rundgren; B. Jordan; S. Erdtman. JSON Canonicalization Scheme (JCS). June 2020. Informational. URL: https://www.rfc-editor.org/info/rfc8785/

Non-Normative References

[DOM]
Anne van Kesteren. DOM Standard. Living Standard. URL: https://dom.spec.whatwg.org/
[RFC1918]
Y. Rekhter; et al. Address Allocation for Private Internets. February 1996. Best Current Practice. URL: https://www.rfc-editor.org/info/rfc1918/
[RFC7515]
M. Jones; J. Bradley; N. Sakimura. JSON Web Signature (JWS). May 2015. Proposed Standard. URL: https://www.rfc-editor.org/info/rfc7515/
[RFC7516]
M. Jones; J. Hildebrand. JSON Web Encryption (JWE). May 2015. Proposed Standard. URL: https://www.rfc-editor.org/info/rfc7516/
[RFC7517]
M. Jones. JSON Web Key (JWK). May 2015. Proposed Standard. URL: https://www.rfc-editor.org/info/rfc7517/
[RFC7519]
M. Jones; J. Bradley; N. Sakimura. JSON Web Token (JWT). May 2015. Proposed Standard. URL: https://www.rfc-editor.org/info/rfc7519/
[RFC7638]
M. Jones; N. Sakimura. JSON Web Key (JWK) Thumbprint. September 2015. Proposed Standard. URL: https://www.rfc-editor.org/info/rfc7638/
[RFC8615]
M. Nottingham. Well-Known Uniform Resource Identifiers (URIs). May 2019. Proposed Standard. URL: https://www.rfc-editor.org/info/rfc8615/
[RFC9421]
A. Backman, Ed.; J. Richer, Ed.; M. Sporny. HTTP Message Signatures. February 2024. Proposed Standard. URL: https://www.rfc-editor.org/info/rfc9421/
[RFC9530]
R. Polli; L. Pardue. Digest Fields. February 2024. Proposed Standard. URL: https://www.rfc-editor.org/info/rfc9530/