Copyright © 2026 The OJCP Authors. Licensed under the Apache License, Version 2.0. See GOVERNANCE.md for the project's governance model.
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.eeoonly 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 § 8 Provider Trust.
- Trust Tier
-
A registry-assigned classification (
unverified,verified, oraudited) indicating the level of trust signals available for a provider. Drives agent obligations around PII transmission. See § 8.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:
-
ojcp_version— The OJCP specification version (currently"0.3"). -
provider— An object with at minimum anamefield identifying the provider. -
tools— An array of OJCP tool names supported by this provider.
The manifest SHOULD include:
-
mcp_endpoint— A URL for MCP-compatible tool invocation. -
feed_endpoints— REST API endpoints for job data access. -
apply_paths— Application mechanisms supported by this provider. -
auth— Authentication requirements. -
rate_limits— Rate limiting policy. -
supported_verifiers— Accepted Identity Verifiers for apply paths that require verification (see § 11 Identity Verification).
The manifest MAY include:
-
resume_upload— An endpoint accepting candidate resume uploads from agents that lack their own file hosting. See § 7 Resume Upload. -
signature— A cryptographic Manifest Signature enabling agents to verify the manifest’s authenticity. Required to reach theverifiedregistry trust tier. See § 8 Provider Trust. -
attribution— How the provider attributes applications to the source that surfaced the job. See § 6 Attribution.
{ "ojcp_version" : "0.3" , "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" ] } ], "attribution" : { "supported" : true , "methods" : [ "impression_match" , "reference" ], "window_days" : 30 } }
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, agent_declaration, pagination
When Candidate Context is provided, the provider SHOULD return personalized results including fit_score and fit_rationale for each result.
When an Agent Declaration is provided, the provider MAY use the declared agent_id to return jobs restricted to that agent, subject to § 3.1.1 Agent-Scoped Visibility.
{ "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" }, "remote_policy" : { "type" : "array" , "items" : { "type" : "string" , "enum" : [ "on_site" , "hybrid" , "remote" , "flexible" ] } }, "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: intern, entry, mid, senior, staff, principal, 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" }, "agent_declaration" : { "$ref" : "https://ojcp.dev/schemas/v0.1/agent-declaration.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. Each job MAY include an attribution_ref (attribution reference; see § 6 Attribution).
3.1.1. Agent-Scoped Visibility
A provider MAY limit which callers see a job by setting the job’s visibility block on its § 4.1 JobPosting. The block has two fields:
-
tier— one of:-
public(the default) — returned to all callers, as when novisibilityblock is present. -
restricted— returned only when the request carries a verifiedagent_declarationwhoseagent_idis inaudience. -
private— asrestricted, and the caller MUST additionally hold the provider-grantedrestricted_feedscope (§ 9.5 Manifest Declaration). The existence of aprivatejob MUST NOT be disclosed to unauthorized callers.
-
-
audience— an array ofagent_ids permitted to see the job whentierisrestrictedorprivate. Matching is performed server-side only, against the verified identity.
The declared agent_id MUST be verified per § 9.4 Verification before any restricted or private job is returned. An absent, unsigned, or unverifiable agent_declaration MUST be treated as anonymous, and an anonymous caller MUST receive only public jobs. A provider that gates any part of its feed MUST list restricted_feed in auth.agent_signatures.required_for (§ 9.5 Manifest Declaration) so agents know a signed request is required. A provider grants a partner agent the restricted_feed scope through its existing allowlist or onboarding mechanism (§ 14.5 Employer Controls); possession of the scope, together with a verified identity, is what unlocks private jobs.
Providers:
-
MUST evaluate
visibilityserver-side, and MUST NOT returnrestrictedorprivatejobs to callers that do not satisfy the audience and scope requirements against a verified identity. -
MUST make
total_resultsreflect only the set of jobs visible to the caller, and MUST NOT leak the count, existence, or any field of non-visible jobs to unauthorized callers. -
MUST count only jobs visible to the caller in
get_employer_context’sopen_roles_countand in any other count or aggregate of jobs they return. -
MUST answer a
get_job_detailorbegin_applicationrequest whosejob_idnames a job not visible to the caller exactly as for a nonexistent job, with ajob_not_founderror (§ 3.7 Error Responses), so that the response does not reveal that the job exists. -
MUST NOT return
visibility.audienceinsearch_jobsorget_job_detailresponses, since it would reveal other agents' identities. Returningvisibility.tieris OPTIONAL. -
MUST ignore a
visibilityblock they do not support and treat the job aspublic, for forward compatibility.
Agents:
-
SHOULD sign
search_jobsrequests (§ 9 Agent Identity) and include anagent_declarationwhen they expect access to a provider’s restricted inventory. -
MUST NOT treat the absence of a job as proof that it does not exist.
Visibility gating narrows who sees a job; it MUST NOT become a tool for opaque or discriminatory gatekeeping:
-
Gating MUST be based on the agent’s identity and the provider’s relationship with it, never on characteristics of the candidate the agent represents. A provider MUST NOT vary
visibilityby protected-class or protected-class-proxy attributes of candidates. -
Restricting a job to an
audienceMUST NOT be used to evade equal-opportunity advertising obligations. Providers remain responsible for their own regulatory compliance regardless of feed segmentation. -
Restriction is an explicit, opt-in provider choice for a specific job; absent a
visibilityblock, a job ispublic.
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. MAY include an attribution_ref (attribution reference; see § 6 Attribution).
A job not visible to the caller under § 3.1.1 Agent-Scoped Visibility MUST be answered exactly as a nonexistent job, with job_not_found. The returned job MUST NOT include visibility.audience.
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. open_roles_count counts only jobs visible to the caller (§ 3.1.1 Agent-Scoped Visibility).
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)." }, "attribution_ref" : { "type" : "string" , "description" : "An attribution_ref previously returned by this provider for the job being applied to." } } } }, "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 § 11.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 provider that accepts user mandates for submit_application MUST include mandate_nonce, an unpredictable single-use value the agent binds into the mandate’s action statement (see § 10.5.2 Mandate Nonce).
A begin_application request for a job not visible to the caller under § 3.1.1 Agent-Scoped Visibility MUST be answered exactly as for a nonexistent job, with job_not_found.
A provider that declares attribution support establishes the application’s attribution when it handles begin_application, and MAY report the outcome in an attribution object (method, and referred_by where applicable). See § 6 Attribution.
begin_application response requiring identity verification:
{ "ojcp_version" : "0.3" , "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 § 7 Resume Upload) to obtain a valid URL. Agents that already have a hosted resume URL pass it directly.
When a provider requires a user mandate for the call, the agent carries it in agent_declaration.user_mandate, bound to this request’s application_id, mandate_nonce, and candidate data (see § 10 User Mandates).
{ "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" }, "attribution_ref" : { "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.3" , "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:
-
ojcp_version— The OJCP specification version. -
error_code— A machine-readable error code from the standard set. -
message— A human-readable error message.
The error envelope MAY include:
-
retry_after_seconds— Seconds to wait before retrying. MUST be present whenerror_codeisrate_limited. -
details— Additional error context; shape varies by error code.
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 § 9 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_id_malformed
| The agent_id presented for verification does not match the grammar in § 9.3.1 Agent ID Grammar.
|
agent_identity_mismatch
| The agent_id does not bind to the Signature-Agent origin by either mechanism in § 9.3 Identity Binding.
|
user_mandate_required
| The context requires a user mandate and none was presented, or the presented mandate failed verification. See § 10.7 Signalling and Errors. |
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:
-
ojcp_id— Unique job identifier. Providers are RECOMMENDED to use the format{provider_domain}:{internal_id}(e.g.,careers.acme.com:swe-42091) to enable cross-provider deduplication when the same job appears on multiple boards. The value MUST be stable for the lifetime of the posting. -
title— Job title -
employer— Object with at minimum anamefield -
datePosted— ISO 8601 date
A conforming JobPosting SHOULD include:
-
apply_paths— At least one apply path withsupports_agent_submissionindicated -
skills_required— Required skills for agent matching -
baseSalary— Compensation information -
employmentType— Type of employment -
official_job_url— Canonical URL of the posting on the employer’s official careers site or ATS, usable as a candidate-facing trust anchor
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.
experienceLevel defines the standard values intern, entry, mid, senior, staff, principal, lead, director, and executive. The field is open — providers MAY use additional values — but the standard set is what an agent can rely on reading, so a level outside it is effectively invisible to any agent that does not know the provider.
Three of these name levels that cannot be folded into a neighbour without losing what a candidate is searching for:
-
internis an internship, not a junior role. A candidate looking for one does not want the other, so collapsing it intoentryreturns the wrong jobs in both directions. A posting SHOULD carryemploymentType: internshipalongside it; the two fields answer different questions — the nature of the engagement, and the seniority it is pitched at. -
staffandprincipalare senior individual-contributor levels. Collapsing them intoseniormakes the IC track invisible: an agent searching for a principal role receives ordinary senior ones.
jobLocation accepts either a single Place or an array of Place. schema.org’s JobPosting already permits both, and a role that an employer will fill in more than one place cannot be expressed by a single value. A posting open in several locations SHOULD carry every location the employer accepts.
Providers MUST NOT reduce a multi-location posting to one of its locations. Doing so asserts a geographic restriction the employer never stated, and an agent filtering on that value discards the posting everywhere else it is open. Where no location is known, the field is omitted rather than guessed.
Agents MUST accept both forms. A single Place remains valid and is the expected shape for a single-location role, so consumers written against earlier versions keep working; an agent that reads only the single form will misread a multi-location posting and SHOULD be updated to handle the array.
{ "jobLocation" : { "@type" : "Place" , "address" : { "addressLocality" : "Chicago" , "addressRegion" : "IL" , "addressCountry" : "US" } } }
{ "jobLocation" : [ { "@type" : "Place" , "address" : { "addressLocality" : "Remote" , "addressCountry" : "US" } }, { "@type" : "Place" , "address" : { "addressLocality" : "Remote" , "addressCountry" : "CA" } } ] }
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:
-
skills_required/skills_preferred— Structured skill lists for agent matching -
team_context— Team and reporting structure description -
urgency— How urgently the role needs to be filled (low,moderate,high,critical) -
application_volume_signal— Relative application volume (low,moderate,high,very_high) -
requisition_id— Internal ATS requisition or job ID for application routing -
department— Department or business unit -
hiring_manager— Name of the hiring manager -
remote_policy— Remote work policy (on_site,hybrid,remote,flexible) -
eligibility— Hard eligibility gates (visa sponsorship, relocation, security clearance); see § 4.1.1 Eligibility Gates -
agent_notes— Free-text notes for agent consumption -
visibility— Which callers may see the job insearch_jobs(tier:public,restricted,private;audience: permittedagent_ids). Absent meanspublic. See § 3.1.1 Agent-Scoped Visibility. -
official_job_url— The canonical public URL of the posting on the employer’s official careers site or ATS
The posting MAY also carry url, the general schema.org web URL for the posting. url and official_job_url serve distinct purposes and SHOULD NOT be conflated: url is a general link that MAY point to an aggregator or third-party board, whereas official_job_url asserts the canonical source hosted by the employer.
official_job_url is a candidate-facing verification anchor, and MUST be an https URL. Its trust is rooted in the provider that serves it: when a provider serves a signed manifest (§ 8 Provider Trust) from a verified domain, an agent extends official_job_url the same trust it extends that provider. The URL’s own registrable domain is NOT the anchor. An employer that publishes through a third-party ATS has a canonical URL on the ATS vendor’s domain (for example acme.myworkdayjobs.com), which is legitimate and matches neither the employer’s nor the provider’s domain, so a domain-equality check against the provider would reject valid postings.
Where an employer’s legitimate hosts are declared in employer.official_domains, an agent MAY corroborate official_job_url against that list. Because ATS hosts are shared across tenants, this comparison MUST be at the host level, not the registrable domain. It is a corroboration signal, not proof of employer identity: the employer is not a party to OJCP and attests nothing directly, so a match raises confidence while a mismatch does not by itself invalidate the posting.
On a URL it cannot corroborate, an agent SHOULD lower its confidence or surface the URL for the candidate to confirm, and MUST NOT discard the posting on that basis alone. It MAY surface official_job_url as a way to manually confirm a posting’s legitimacy or as a fallback when no agent apply path is available. Agents MUST NOT treat url as carrying any of these properties.
{ "@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" , "official_job_url" : "https://careers.acme.com/jobs/swe-42091" , "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.1.1. Eligibility Gates
A JobPosting MAY carry an eligibility object describing the hard eligibility gates of the role: conditions that decide whether an application can succeed at all, not whether it is a good fit. The object and every field inside it are OPTIONAL.
| Gate | Values | Meaning |
|---|---|---|
visa_sponsorship
| offered, not_offered, unspecified
| Whether the employer will sponsor a work visa for this role. |
relocation
| offered, not_offered, required, unspecified
| Whether the employer supports relocation. required means the role cannot be performed without relocating — a constraint on the candidate, not an offer to them.
|
security_clearance
| required, not_required, unspecified
| Whether the role requires a government security clearance. |
Each gate is a closed enum that includes a member meaning "the posting did not say" (unspecified). The gates share this rule, not a vocabulary: a clearance is never offered, and relocation has a state (required) that neither offered nor not_offered expresses.
A gate that is absent, or set to unspecified, MUST NOT be read as a negative. An absent gate means exactly unspecified. Agents MUST NOT treat it as not_offered or not_required, and MUST NOT exclude a posting on the basis of a gate the posting did not state. Collapsing "not stated" into "no" asserts a restriction the employer never wrote, and the failure is silent: the candidate never learns the job existed.
Absence is permitted and means unspecified, but it cannot distinguish a provider that does not implement eligibility from a posting that said nothing. Providers MAY set unspecified explicitly to make their silence deliberate and readable.
Agents MUST ignore enum members of a gate they do not recognise rather than rejecting the posting, per § 17.3 Schema Extensibility. A gate may gain a state in a later revision, and that MUST NOT invalidate existing postings.
{ "eligibility" : { "visa_sponsorship" : "not_offered" , "relocation" : "required" , "security_clearance" : "unspecified" } }
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:
-
ojcp_candidate_context_version— Schema version -
consent_scope— The scope of consent granted by the candidate. Standard values:-
search_personalization— allows use for personalized job search results -
fit_scoring— allows use for fit score calculation -
application_prefill— allows use for pre-filling application fields (includes PII like name, email, resume) -
full_profile— allows full use of all candidate data
-
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:
-
agent_id— Reverse-domain-notation identifier -
acting_on_behalf_of— Who the agent represents (human_user,recruiter,autonomous_workflow)
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 not defined by this specification — providers decide what level of trust to require. Action-level user authorization is defined by § 10 User Mandates.
Agents MAY include a user_mandate: a user mandate by which the user authorizes the agent’s signing key to perform one specific action. Unlike user_consent_token, its profile and verification are defined by this specification (§ 10 User Mandates).
{ "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:
-
Providers MUST NOT require any EEO field for application acceptance. An application MUST be acceptable with an empty or omitted
eeoobject. -
Providers MUST NOT use EEO data in fit scoring, screening question evaluation, or any decision that affects whether an application is forwarded, surfaced, or ranked.
-
Providers MUST treat
decline_to_answeras semantically equivalent to omission — neither indicates non-membership in any protected class. -
Providers SHOULD store EEO data segregated from primary application data, accessible only to compliance reporting workflows.
Agent obligations:
-
Agents MUST only transmit EEO data when the candidate has explicitly chosen to disclose it for this specific application. Agents MUST NOT pre-fill EEO from a stored profile without per-submission consent.
-
Agents MUST NOT log or persist EEO data beyond the duration of the submission.
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:
-
gender—male,female,non_binary,self_describe,decline_to_answer -
gender_self_described— Free-text whengenderisself_describe -
race_ethnicity— Multi-select array per US EEOC categories (american_indian_or_alaska_native,asian,black_or_african_american,hispanic_or_latino,native_hawaiian_or_other_pacific_islander,white,two_or_more_races,decline_to_answer) -
veteran_status—protected_veteran,not_protected_veteran,decline_to_answer -
disability_status—yes,no,decline_to_answer -
jurisdiction— ISO 3166-1 alpha-2 country code identifying which regulatory framework’s enumerations apply (default:US)
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:
-
step_id— Unique identifier within the application session -
type— Verification type (identity,government_id,biometric,address,employment,education,background_check) -
verifier_id— Identifier of the Identity Verifier -
verification_url— URL the candidate must visit to complete verification -
human_required— Boolean; when true, the agent must hand off to the candidate
A Verification Step MAY include:
-
required— Boolean indicating whether this step must be completed for the application to proceed. Defaults to true. -
proof_delivery—"agent_submitted"(default) or"provider_managed". Determines whether the agent must collect and submit the proof, or the provider handles it directly. -
verifier_name— Human-readable name of the verifier -
instructions— Instructions for the agent to relay to the candidate -
estimated_completion_minutes— Expected time to complete -
expires_at— When the verification URL expires -
ui_resource— Aui://URI of an MCP Apps resource the host can render inline as an alternative to openingverification_url. See § 11.5.3 Inline Rendering via MCP Apps.
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:
-
step_id— The verification step this proof satisfies -
verifier_id— Identifier of the issuing verifier (MUST match theissclaim inproof_token) -
verification_type— The type of verification performed -
proof_token— JWS compact serialization (RFC 7515). The payload MUST includeiss,aud,sub,iat,exp, andnonceclaims. -
subject_hash— SHA-256 hex digest of the verifier’s canonical subject identifier (not reversible to PII) -
issued_at— ISO 8601 timestamp (MUST match theiatclaim) -
expires_at— ISO 8601 timestamp (MUST match theexpclaim)
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:
-
ojcp_verifier_version— Schema version ("0.1") -
verifier_id— Unique identifier for the verifier -
verifier_name— Human-readable name -
verification_types— Array of supported verification types -
proof_format— Format of issued proofs ("jws"for v0.1) -
public_keys_url— URL to a JWK Set for proof signature validation
A Verifier Manifest SHOULD include:
-
verification_endpoint— URL for initiating verification sessions -
proof_delivery_methods— Supported delivery methods (callback,redirect,polling,embedded_app).embedded_appadvertises that the verifier can be rendered inline through MCP Apps; see § 11.5.3 Inline Rendering via MCP Apps. -
signing_algorithms— Supported JWS algorithms (ES256 RECOMMENDED) -
proof_ttl_seconds— Default time-to-live for issued proofs
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:
-
type— One of the types above -
supports_agent_submission— Boolean indicating agent compatibility
Each apply path object SHOULD include:
-
url— Application entry point -
required_fields— Fields required for submission -
optional_fields— Additional fields the provider will accept but does not require -
estimated_completion_minutes— Expected time to complete
Each apply path object MAY include:
-
product_name— Vendor product name for this apply path (e.g., "Hosted Apply", "Indeed Apply", "Easy Apply"). Informational only; agents MUST NOT use this for logic. -
form_skill_url— URL to a form skill descriptor that teaches agents how to fill out this apply path’s form, including field mappings to Candidate Context, validation rules, conditional logic, and submission instructions. Providing a form skill can enable agent submission even for apply paths (likeats_direct) that would otherwise require human interaction. The form skill schema is defined in a companion specification. -
requires_verification— Boolean indicating whether identity verification is required before the application can be submitted via this path. -
accepted_verifiers— Array of Identity Verifier identifiers (e.g.,"id.me","clear") accepted for this apply path. If omitted whenrequires_verificationis true, the provider accepts any verifier listed in the manifest’ssupported_verifiers.
Providers are RECOMMENDED to implement at least one apply path where supports_agent_submission is true.
6. Attribution
Employers and their distribution partners pay for job distribution on the outcome it produces: an application. This section defines how a provider establishes which source surfaced the job behind an application.
Attribution is derived by the provider from evidence it already holds: the identity of the caller and the jobs it served to that caller. An agent that searches and applies under the same identity is credited without relaying anything. Attribution references exist only for the case the provider cannot observe: a handoff between two different identities.
This section standardizes how attribution evidence is established and reported. How credit is priced, split between sources, or billed is out of scope.
Attribution is OPTIONAL. A provider that does not declare it in its Job Manifest has no obligations under this section.
6.1. Definitions
- Impression
-
A provider returning a job (by
ojcp_id) to a caller in asearch_jobsorget_job_detailresponse. - Attributable identity
-
The identity an impression or application is recorded against. In order of strength: a verified
agent_id(§ 9.4 Verification), then a credential the provider issued (API key, OAuth client). A self-asserted, unverifiedagent_idis not an attributable identity. - Attribution window
-
The period after an impression during which an application can be credited to it.
- Attribution reference
-
An opaque, provider-issued value bound to one impression, carried as
attribution_ref.
6.2. Establishing Attribution
A provider that declares attribution support:
-
SHOULD record each impression against the caller’s attributable identity, keyed by
ojcp_id, with the time and the result’s rank. It MAY also recordfit_score. Impression records MUST NOT contain Candidate Context contents or any candidate identifier. -
MUST establish attribution when it handles
begin_application, applying these rules in order:-
Reference. If
source_attribution.attribution_refis present, valid, unexpired, and bound to the requestedjob_id, the impression it names is credited. If the reference was issued to a different attributable identity than the caller’s, that identity is recorded asreferred_by, and the caller as the applying agent (see § 6.7 Security Considerations). -
Impression match. Otherwise, the most recent impression of the requested
job_idserved to the caller’s attributable identity within the attribution window is credited (last touch). -
None. Otherwise, the application is unattributed.
-
-
MUST NOT treat a missing
source_attributionas unattributed when rule 2 applies. -
MUST NOT match on IP address, user agent, device characteristics, or any other fingerprint. Rule 2 applies only to attributable identities, so an anonymous caller can be credited only through rule 1.
-
MUST fix the attribution when it handles
begin_application.submit_applicationinherits it throughapplication_id. A provider MAY applysource_attributionsupplied onsubmit_applicationonly when the application was unattributed atbegin_application.
A get_job_detail response counts as an impression: an agent that fetched a job’s detail has engaged with it, whether or not it ran a search first.
6.3. Attribution References
A provider MAY include an attribution_ref on each job in a search_jobs response and on a get_job_detail response. An agent that hands a job to another agent or platform SHOULD pass the attribution_ref along with it. An agent MAY pass it back in source_attribution.attribution_ref; an agent that applies under the same identity that received the job need not.
An attribution_ref:
-
MUST be opaque to agents and integrity-protected, so that an agent can neither forge one nor alter its contents.
-
MUST be bound to the
ojcp_idit was issued for. A provider MUST ignore a reference presented for a differentjob_id. -
SHOULD be bound to the attributable identity it was issued to, and SHOULD expire no later than the end of the attribution window.
A provider MUST treat an invalid, expired, or mismatched reference as absent, falling through to rule 2. A bad reference MUST NOT cause the application to be rejected.
source_attribution.referrer and source_attribution.reference_id are values the source supplies for its own reconciliation, which a provider MAY echo back in reports. Providers MUST NOT require them, and SHOULD NOT rely on them alone as attribution evidence, because they are self-asserted.
6.4. Off-Protocol Applications
When an apply path is completed outside OJCP (email, external_redirect, and any path with supports_agent_submission: false), the provider SHOULD embed an attribution reference in the apply url it returns. The parameter name and encoding are the provider’s choice. Agents MUST pass apply URLs to the candidate unmodified, and in particular MUST NOT strip query parameters.
Reporting the resulting conversion from an employer’s ATS back to the provider is outside the agent–provider relationship and out of scope for this specification.
6.5. Manifest Declaration
A provider that establishes attribution SHOULD declare it in the Job Manifest:
"attribution" : { "supported" : true , "methods" : [ "impression_match" , "reference" ], "window_days" : 30 }
-
methodslists the rules in § 6.2 Establishing Attribution that the provider applies. -
window_daysis the attribution window, RECOMMENDED 30. A provider MUST NOT retain impression records longer than its declared window.
6.6. Attribution Outcome
A provider MAY report the outcome in the begin_application response, so that a source can confirm it was credited without waiting for an out-of-band report:
method is one of reference, impression_match, or none. referred_by is present only when an attribution reference issued to another attributable identity was credited. A provider MUST NOT reveal anything else about another identity’s impressions.
6.7. Security Considerations
-
Spoofed identity. Impression matching keys on attributable identities only. Matching on a self-asserted
agent_idwould let any caller manufacture or misdirect credit; see also § 9 Agent Identity. -
Fabricated conversions.
begin_applicationis cheap to call. A provider that bills on outcomes SHOULD treat a submitted application, not an initiated one, as the conversion. -
Reference stuffing. An identity could push its references into other agents' flows to claim credit for applications it did not drive. Rule 1 therefore records a foreign reference as
referred_byalongside the applying identity, and never erases the applying identity’s own evidence. How credit is divided is a commercial decision. -
Replay. Binding a reference to an
ojcp_idand expiring it with the attribution window limits replay to the job it was issued for and the window it was issued in.
7. 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.
7.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.
7.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:
-
Authorization: Bearer {session_token}— thesession_tokenreturned bybegin_application. Uploads are bound to a specific application session. Anonymous uploads are NOT permitted. -
A single
resumefield in the multipart body containing the file.
The request MAY include additional form fields the provider documents — these are out of scope for OJCP v0.1.
7.3. Upload Response
On success, the provider returns HTTP 200 with a JSON body:
{ "ojcp_version" : "0.3" , "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:
-
ojcp_version— The OJCP version. -
resume_url— A URL the agent passes insubmit_application.application_data.resume_url. The URL MUST be valid for at least as long as the application session. -
expires_at— ISO 8601 timestamp after which the URL stops resolving.
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.
7.4. Server-Side Validation
Providers MUST:
-
Validate the
Content-Lengthheader before accepting the upload. Reject (HTTP 413) if it exceedsmax_size_mb. Streaming uploads MUST be aborted if the cumulative byte count exceeds the cap mid-transfer. -
Validate the file’s MIME type by inspecting the file content (magic bytes), not by trusting the client-supplied
Content-Type. Reject if the content type is not inaccepted_mime_types. -
Enforce
max_uploads_per_sessionagainst the suppliedsession_token. -
Reject uploads with an invalid, expired, or revoked
session_tokenwith errorinvalid_session_token.
Providers SHOULD scan uploads for malware before issuing the resume_url. Providers MAY reject any upload at their discretion.
7.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.
7.6. Security
-
Session binding. Each upload is scoped to one
application_idvia thesession_token. Reusing a token from a different session SHOULD fail. -
URL non-enumeration. Returned
resume_urlvalues MUST NOT be sequential or guessable. Providers SHOULD use signed URLs or opaque tokens with sufficient entropy (≥128 bits). -
TTL enforcement. Providers MUST stop serving the resume URL after
expires_at, even if the application is still active. Resume URLs inapplication_data.resume_urlare intended for the provider’s ATS pipeline, not for long-term hosting. -
PII handling. Resumes contain PII. Providers MUST treat uploaded files with the same protections as candidate-supplied PII elsewhere in OJCP, including the segregation guidance in § 4.4 EEOData where applicable.
-
Cleanup. Providers SHOULD purge or anonymize uploaded resumes when the application is abandoned per § 11.9.4 Abandonment Semantics.
8. 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.
8.1. Threat Model
OJCP-aware agents face three primary trust risks when interacting with providers:
-
Imposter providers. A malicious actor stands up
careers.fake-employer.comwith 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. -
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.
-
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 by § 9 Agent Identity, § 10 User Mandates and § 14.5 Employer Controls.
8.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:
-
alg— Signing algorithm.ES256(ECDSA with P-256 and SHA-256) is RECOMMENDED.RS256,ES384, andRS384are acceptable. -
kid— Key identifier matching a key in the provider’s JWK Set (§ 8.3 JWK Set Discovery). -
signed_at— ISO 8601 timestamp when the signature was created. -
expires_at— ISO 8601 timestamp when the signature expires. RECOMMENDED 24 hours fromsigned_at. -
value— Base64url-encoded signature bytes.
The signature object SHOULD include:
-
iss— The provider domain or identifier this signature attests. Agents MUST verify this matches the domain serving the manifest.
{ "ojcp_version" : "0.3" , "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..." } }
8.2.1. Canonicalization
To produce or verify a signature, both parties MUST canonicalize the manifest content identically:
-
Remove the
signaturefield from the manifest object. -
Serialize the resulting object using JCS (JSON Canonicalization Scheme, [RFC8785]).
-
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.
8.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:
-
Fetching the JWK Set from
{provider_origin}/.well-known/ojcp-keys.json. -
Resolving the
kidfrom the manifest’ssignatureobject against the set. -
Verifying the
algin the signature matches the key’salg. -
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.
8.3.1. Key Rotation
Providers SHOULD rotate signing keys periodically (RECOMMENDED: every 90 days). Rotation procedure:
-
The provider MUST publish both old and new keys in the JWK Set for an overlap period of at least 24 hours.
-
The provider SHOULD begin signing new manifests with the new key as soon as the new key is published.
-
After the overlap period, the provider MAY remove the old key. Manifests signed with the removed key will no longer validate — this is expected, since they should have expired.
Providers MUST NOT hard-code kid values or signing keys in agent libraries. Keys are always resolved dynamically from the JWK Set.
8.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:
-
Domain match. Verify that the manifest was fetched over HTTPS from a domain matching the
signature.issclaim (when present). Reject on mismatch — error:domain_mismatch. -
Canonicalize. Reproduce the canonicalized manifest bytes per § 8.2.1 Canonicalization.
-
Resolve key. Fetch (or use cached) JWK Set from
{origin}/.well-known/ojcp-keys.json. Find the key matchingsignature.kid. Refetch on cache miss. Reject if not found — error:key_resolution_failed. -
Verify algorithm. Verify
signature.algmatches the JWK’s declared algorithm. Reject on mismatch — error:algorithm_mismatch. -
Verify signature. Validate the JWS signature over the canonicalized bytes using the resolved key. Reject on failure — error:
invalid_signature. -
Check expiration. Verify
signature.expires_atis 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 (§ 8.7 Agent Obligations).
8.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:
-
Submitting
application_dataviasubmit_application -
Transmitting
candidate_contextwithconsent_scopeofapplication_prefillorfull_profile
Agents MAY skip validation for read-only operations:
-
search_jobs -
get_job_detail -
get_employer_context -
Transmitting
candidate_contextwithconsent_scopeofsearch_personalizationorfit_scoring
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.
8.6. Registry Trust Tiers
The OJCP Registry (§ 13.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 (§ 8.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."
8.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.
8.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:
-
The signing key MUST be kept secure. A compromised signing key allows an attacker to mint manifests that agents will trust until the key is revoked from the JWK Set.
-
Manifests SHOULD be re-signed regularly (RECOMMENDED: daily) to keep the
signature.expires_atwindow short. This limits the window in which a compromised manifest can be served. -
Build pipelines SHOULD sign manifests automatically rather than requiring human intervention. Long-lived signatures from manual signing increase risk.
-
Providers operating multiple subdomains (e.g.,
careers.acme.comandjobs.acme.com) MAY share a single JWK Set if both domains can serve it — thesignature.issclaim distinguishes which domain a given signature is bound to.
9. 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.
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 this specification (the agent is treated as unauthenticated). Agent identity is distinct from § 11 Identity Verification, which verifies the human candidate; the two compose.
9.1. Signing Profile
When an agent signs a request it MUST produce an [RFC9421] signature with:
-
Covered components:
@method,@authority,@path,@query,content-digest, and thesignature-agentheader. Covering@queryis REQUIRED; omitting it permits query-parameter tampering ([RFC9421] §7.2.1). Implementations MAY cover@target-uriin place of@authority/@path/@query, but@methodMUST remain covered in either case. -
Signature parameters:
created,expires,nonce,keyid, andtag="web-bot-auth"— all MUST be present. -
**
keyid:** the base64url-encoded JWK SHA-256 thumbprint of the signing key per [RFC7638]. -
**
Content-Digest** ([RFC9530],sha-256) computed over the exact transmitted body bytes. For bodyless requests (e.g. GET) the digest MUST be computed over empty content and still covered, so verification does not break.
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.
9.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 § 8.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 § 8.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.
9.3. Identity Binding
Proving a signature proves control of the Signature-Agent origin. Binding is what makes that
proof say something about agent_id: without it, any origin could sign and declare any
identifier. Authority follows the DNS hierarchy — whoever controls a host controls every name
beneath it — and an agent_id binds to an origin in exactly two ways, checked in this order.
9.3.1. Agent ID Grammar
An agent_id presented for verification MUST match:
agent-id = label 1*( "." label ) ; at least two labels label = let-dig [ *61( let-dig / "-" ) let-dig ] let-dig = %x61-7A / DIGIT ; a-z / 0-9, lowercase only
and MUST NOT exceed 253 octets. Each label is a lowercase LDH hostname label ([RFC1123]
§2.1); internationalized labels appear only in A-label (xn--) form. Providers MUST reject a
non-conforming agent_id with agent_id_malformed and MUST NOT normalize it (no case folding,
no IDNA conversion, no trimming): every comparison in this section is byte-exact.
The grammar is that of a hostname because § 9.3.3 Delegated Binding resolves the identifier as one. It is enforced on the verification path only; unsigned requests are unaffected.
9.3.2. Namespace Binding
Let host be the Signature-Agent origin’s host, lowercased, and let namespace be its labels
in reverse order joined by .. The agent_id binds when it equals namespace or begins with
namespace followed by .. IP-literal hosts never bind.
Signature-Agent host
| agent_id
| Binds |
|---|---|---|
agent.wayfarer.ai
| ai.wayfarer.agent
| Yes — exact |
wayfarer.ai
| ai.wayfarer.agent
| Yes — a host vouches for names beneath it |
evil.wayfarer.ai
| ai.wayfarer.agent
| No — a child cannot claim its parent’s names |
alice.github.io
| io.github.bob.agent
| No — siblings cannot claim each other’s names |
wayfarer.ai
| ai.wayfarerx.agent
| No — prefixes match on label boundaries |
This is the rule the MCP Registry applies to com.example.* names, applied here at request time
instead of publish time. Because authority only ever flows from a host to names beneath it, no
Public Suffix List lookup is needed: a tenant of a shared host (alice.github.io) can claim only
its own names, and the only party able to claim names under a shared host is that host’s
operator, who already controls every subdomain’s DNS.
9.3.3. Delegated Binding
An agent whose keys are hosted by a third party — a signing service or an agent platform
operating on its customer’s behalf — signs from an origin outside its own namespace. The domain
the agent_id names authorizes that origin explicitly.
When § 9.3.2 Namespace Binding does not hold, the provider MUST fetch an Agent Identity
Document from https:// + the agent_id’s labels in reverse order + /.well-known/ojcp-agent.json
(for com.acme.agent, https://agent.acme.com/.well-known/ojcp-agent.json). The binding holds
only if the document is a valid Agent Identity Document whose agent_id equals the presented
agent_id and whose signers array contains the Signature-Agent origin. Origins are compared
as ASCII serializations per [RFC6454] §6.2 (https://host[:port], lowercase, no trailing
slash).
The document is served by the host the agent_id names, never by the signer, so only the party
controlling that host can authorize a signer for the identifier. The fetch is triggered by
attacker-chosen input and MUST satisfy every SSRF requirement in § 9.2 Key Discovery.
Providers SHOULD cache the document under the guidance of § 8.3 JWK Set Discovery; removing an origin
from signers revokes it once cached copies expire. A missing, unreachable, oversized or
invalid document means the binding does not hold.
Providers MUST NOT fetch the Agent Identity Document when the namespace binding already holds, and MUST NOT consult any other location for it.
Neither mechanism consults the Public Suffix List. (Note: the host in this section is the
Signature-Agent origin’s, unrelated to the @authority covered component in
§ 9.1 Signing Profile, which is the request’s own host.)
9.4. Verification
A provider that accepts a signed request MUST, in order:
-
Resolve the signing key from the
Signature-Agentdirectory bykeyidthumbprint. -
Verify the signature per [RFC9421], deriving the algorithm from the resolved key.
-
Verify
Content-Digestmatches the received (or empty) body. -
Verify freshness:
createdandexpiresMUST both be present;createdMUST NOT be in the future beyond the clock-skew tolerance (RECOMMENDED ≤ 60s);expiresMUST NOT be in the past; and the window (expiresminuscreated) MUST NOT exceed 300s. -
Verify the
noncehas not been seen for thiskeyidwithin the freshness window. Providers MUST maintain a replay cache retaining each(keyid, nonce)at least until itsexpires; because the window is capped at 300s (step 4) the cache is bounded. [RFC9421] definesnoncebut leaves replay detection to the application. -
Verify
agent_idagainst the grammar (§ 9.3.1 Agent ID Grammar). -
Verify the identity binding: § 9.3.2 Namespace Binding, else § 9.3.3 Delegated Binding.
-
Treat
agent_idas 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_id_malformed, agent_identity_mismatch — and MUST NOT process the request as if the
identity were verified. A binding that fails by either mechanism, including an unavailable or
invalid Agent Identity Document, is reported as agent_identity_mismatch.
When a required context (see § 9.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.
An agent_id on a request that does not complete the steps above — an unsigned request, in
practice — is self-asserted. Providers MAY record it for audit, and MUST then record it as
unverified. Providers MUST NOT use an unverified agent_id for allowlists, rate limiting,
session binding, or any other access decision.
9.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:
-
begin_application,submit_application— match a call to that tool. -
application_prefill,full_profile— match when the request’s Candidate Context declares thatconsent_scope(i.e., PII is in play). -
restricted_feed— match when that optional scope is exercised, including anysearch_jobsrequest for which the provider would returnrestrictedorprivatejobs (§ 3.1.1 Agent-Scoped Visibility). A provider that gates any part of its feed MUST list this token.
Permissionless unsigned access SHOULD remain available for anonymous search_jobs at
fit_scoring scope and below.
9.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:
-
Platform attestation — § 9.1 Signing Profile through § 9.4 Verification, signed with the platform’s key. The
agent_ididentifies the platform (e.g.,ai.northstar.assistant, signed fromhttps://northstar.ai, whose namespaceai.northstarit falls under — § 9.3.2 Namespace Binding). This proves which software is calling; it does not establish user authority. -
User authorization — an OPTIONAL
user_mandatecarried in theAgentDeclaration: a user mandate issued under the user’s authority that endorses the agent’s signing key (a key-binding) and is bound to a canonical statement 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. Its profile is defined in § 10 User Mandates.
Normative rules:
-
Providers MUST NOT treat
acting_on_behalf_of(a self-asserted string) as delegated user authority. Where user authority matters (e.g.,submit_applicationfor a named candidate), a provider requiring it MUST require auser_mandateadmitted by its mandate-admission policy (§ 10.3 Mandate Admission), whose authority is rooted in the user and never in the platform key. -
The mandate’s bound action statement MUST cover the material application content so a captured mandate cannot be replayed against a different submission (§ 10.5 Action Statement).
-
Agents SHOULD honor the existing
interaction_modedistinction (assisted/supervised= user-present;autonomous= pre-authorized). -
To preserve candidate privacy, agents SHOULD NOT transmit
acting_on_behalf_ofvalues or user identifiers in the clear where a selectively-discloseduser_mandatecan carry them; providers MUST NOT require disclosure of the underlying human beyond what the matched context needs.
§ 10 User Mandates defines the minimum security profile of user_mandate: the claims it carries,
the action it binds, and how a provider verifies and consumes it.
10. User Mandates
A platform signature (§ 9.4 Verification) proves which software made a request. It does not prove that a person authorized that software to take a particular action. This section defines the minimum interoperable security profile for AgentDeclaration.user_mandate: a credential that binds a user’s authorization to one OJCP action and to the agent signing key that may exercise it.
User mandates are OPTIONAL. This section applies only when a provider requires a user mandate for a request. A provider that requires none, and an agent that sends none, behave exactly as elsewhere in this specification. This section fixes the security boundary every user mandate MUST satisfy; the credential’s media type and exact claim profile, a registry of action statement types, and manifest signalling are not defined by this version, and any credential that meets this profile conforms.
10.1. Definitions
- User mandate
-
A credential, carried in
AgentDeclaration.user_mandate, by which a user authorizes one agent signing key to perform one OJCP action under the constraints it discloses. - Action statement
-
The canonical
ojcp_actionobject describing the authorized action (§ 10.5 Action Statement). - Mandate-admission policy
-
A provider’s policy that determines which mandate issuers or user-rooted keys are admissible for an action (§ 10.3 Mandate Admission).
10.2. Three Claims, Three Decisions
Providers MUST treat the following claims independently:
| Claim | Typical evidence | What it proves | What it does not prove |
|---|---|---|---|
| Platform / agent identity | [RFC9421] signature and Signature-Agent (§ 9 Agent Identity)
| The verified agent platform made this request. | A person authorized the request. |
| Candidate identity | Verification Proof (§ 11 Identity Verification) or provider account flow | A verifier or provider made the stated identity claim. | That an agent may submit a particular application. |
| User authorization | Valid user_mandate admitted by the provider’s mandate-admission policy
| A user-authorized key authorized this action under the disclosed constraints. | The candidate’s civil identity, unless a separate identity flow establishes it. |
A provider MUST NOT promote a successful result in one row to a successful result in another. A Verification Proof is not a submission mandate, and a platform-signed acting_on_behalf_of value is not delegated user authority.
10.3. Mandate Admission
Cryptographic validity is necessary but not sufficient. A provider that requires a user mandate MUST have a mandate-admission policy that determines which mandate issuers or user-rooted keys are admissible for the relevant action. The policy sits outside the credential: an issuer cannot make itself authorized merely by issuing a valid credential, and a credential signed only by the agent platform’s key is never user authority.
Providers MAY admit a user-controlled key, a provider account-bound credential, or an independent identity-provider credential. A provider that must establish that the authorizing user is the named applicant MUST perform that binding through its own account, verified email, or § 11 Identity Verification process. A newly generated public key alone proves control of the key, not a person’s legal identity.
10.4. Mandate Profile
user_mandate SHOULD be an SD-JWT Verifiable Credential ([SD-JWT-VC], [RFC9901]) in compact serialization, presented with a key-binding proof by the agent’s signing key. A mandate accepted for an OJCP action MUST convey, directly or by selectively-disclosed claims:
-
issand an issuer or user-root public-key reference; -
cnf.jkt([RFC7800], [RFC9449]), the [RFC7638] thumbprint of the agent signing key authorized to exercise the mandate — the same thumbprint the agent’s request signature carries askeyid(§ 9.1 Signing Profile); -
aud, the origin of the OJCP resource server that will consume the mandate. For a multi-tenant ATS this identifies the ATS endpoint, not the employer’s public website (§ 10.5.1 Multi-Tenant Resource Servers); -
iat,nbfwhen used,exp, and a uniquejti; -
an OJCP version identifier;
-
the action statement (§ 10.5 Action Statement); and
-
a revocation or status mechanism, unless the mandate has a short, provider-defined maximum lifetime and is single-use.
The credential MUST NOT carry acting_on_behalf_of or other direct user identifiers unless the provider’s matched action actually needs them. A provider MUST NOT require disclosure of a stable user identifier merely to validate delegation.
submit_application call. The mandate carries the action statement by digest; it has no status claim because it is single-use and valid for ten minutes. Values are illustrative.
{ "iss" : "did:jwk:eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6Ik..." , "vct" : "urn:example:ojcp-user-mandate" , "aud" : "https://careers.acme.com" , "iat" : 1773844800 , "nbf" : 1773844800 , "exp" : 1773845400 , "jti" : "mnd_RGBLQJ-l7qj9DiNH" , "cnf" : { "jkt" : "tKwHslziExQ7dQgDX2HX8q9EuvAYmS_A3SKfjNHpdX8" }, "ojcp_version" : "0.1" , "ojcp_action" : "sha256:f-iFWllBWhQTjicNqPPPyN25DsN5rkUQThmL2e2VahM" }
10.5. Action Statement
A mandate binds a stable representation of the action, not an HTTP or JSON-RPC payload. The action statement is a JSON object, ojcp_action. A mandate conveys it either as the object itself or, when embedded by digest, as the string sha256: followed by the base64url encoding (without padding) of the SHA-256 hash of its JCS serialization ([RFC8785]).
| Field | Requirement | Value |
|---|---|---|
ojcp_version
| OPTIONAL | The OJCP version, when the mandate carries its version identifier here. |
resource_server
| REQUIRED | The origin of the OJCP endpoint that consumes the mandate; equal to aud.
|
employer
| REQUIRED when the resource server serves more than one employer | The employer context of the job (§ 10.5.1 Multi-Tenant Resource Servers). |
tool
| REQUIRED | The OJCP tool name, e.g. submit_application.
|
job_id
| REQUIRED | The job’s ojcp_id.
|
application_id
| REQUIRED for submit_application
| The application_id returned by begin_application.
|
agent_id
| REQUIRED | The verified agent_id (§ 9.4 Verification).
|
agent_key_thumbprint
| REQUIRED | The [RFC7638] thumbprint of the agent signing key; equal to cnf.jkt.
|
candidate_data_digest
| REQUIRED for submit_application
| A digest of the semantically material candidate data, defined below. |
mandate_nonce
| REQUIRED for submit_application
| The single-use nonce the provider returned with the application session (§ 10.5.2 Mandate Nonce). |
candidate_data_digest covers the semantically material candidate data sent with the request, excluding transport metadata and the mandate itself. It prevents substituting a different resume reference, screening answer, contact address, or profile after consent. Its value is sha256: followed by the base64url encoding (without padding) of the SHA-256 hash of the JCS serialization of an object whose members are the request’s application_data and candidate_context, exactly as sent; a member absent from the request is omitted.
Providers and agents MUST ignore action-statement fields they do not recognize. A provider MUST reject a mandate when a field required for the requested tool is absent or does not match the action statement the provider independently reconstructs.
{ "ojcp_version" : "0.1" , "resource_server" : "https://careers.acme.com" , "employer" : { "ojcp_employer_id" : "acme-corp" }, "tool" : "submit_application" , "job_id" : "careers.acme.com:analyst-2026-001" , "application_id" : "app_abc123" , "agent_id" : "ai.northstar.assistant" , "agent_key_thumbprint" : "tKwHslziExQ7dQgDX2HX8q9EuvAYmS_A3SKfjNHpdX8" , "candidate_data_digest" : "sha256:l8Kt_6K5fFTzhjg_iIYDcXjskULviQZ5h7QClTNOeqo" , "mandate_nonce" : "mn_xBVlesPAiuvWDyyxUDEDzDTG" }
10.5.1. Multi-Tenant Resource Servers
An ATS that hosts application flows for many employers separates two security properties: the recipient of the mandate, and the employer the application is made to.
-
audandresource_serveridentify the OJCP endpoint that consumes the mandate, such as an ATS tenant or a shared ATS API origin. They prevent a credential issued for one resource server from being presented to another. -
The action statement MUST also bind the employer context of the job. A multi-tenant provider MUST include
employer.ojcp_employer_idfrom the JobPosting (§ 4.1 JobPosting) when it is available. It MAY also include the posting’sofficial_job_urlwhen the provider has verified its association with the job. An unverified URL alone is not an authorization boundary.
{ "resource_server" : "https://apply.example-ats.com" , "employer" : { "ojcp_employer_id" : "acme" }, "tool" : "submit_application" , "job_id" : "apply.example-ats.com:1234" , "application_id" : "app_7f3a..." , "agent_id" : "ai.example.agent" , "agent_key_thumbprint" : "base64url-rfc7638-thumbprint" , "candidate_data_digest" : "sha256:base64url(JCS(candidate fields and answers))" , "mandate_nonce" : "provider-issued-single-use-nonce" }
The provider MUST reconstruct both bindings from its own session state, and MUST reject a mandate that matches its endpoint but names a different employer, tenant, job, or official-job anchor. A mandate for one employer therefore cannot be reused for another employer on the same ATS.
10.5.2. Mandate Nonce
For begin_application, a provider MAY accept a mandate without application_id. When it does, the action statement MUST still bind the resource server, the employer when multi-tenant, the job, the agent signing key, the requested consent_scope, and any candidate data to be prefetched (by candidate_data_digest over the request’s candidate_context).
Before a mandate-bound submit_application, the provider MUST return an unpredictable, single-use mandate_nonce with the application session in the begin_application response. The agent binds it into the action statement for submit_application. The nonce keeps a low-entropy candidate-data digest from serving alone as a reusable or dictionary-attackable authorization handle.
10.6. Verification
A mandate binds to the agent signing key of a verified request, so a provider MUST NOT evaluate a user_mandate on a request whose agent identity has not been verified per § 9.4 Verification. Such a request fails with the applicable agent-signature error code.
For a context that requires a user mandate, a provider MUST, in order:
-
Verify the credential signature and the holder proof required by its selected SD-JWT VC profile.
-
Apply its mandate-admission policy (§ 10.3 Mandate Admission); cryptographic validity alone is not sufficient.
-
Verify
aud, the time bounds (nbfwhen present, andexp), the OJCP version, and thatcnf.jktequals the thumbprint of the currently verified agent signing key. -
Reconstruct the action statement from the received request and the provider’s session state, then verify every field required for the requested tool, or the action digest.
-
Enforce the mandate’s revocation or status rule before executing the action. A mandate with no status mechanism is acceptable only within the provider’s maximum lifetime for single-use mandates.
-
Reject a mandate whose
jtiormandate_noncehas already been consumed. For a successful terminal submission, atomically record both as consumed. A retry after an indeterminate network failure MAY use the provider’s ordinary idempotency mechanism, but MUST NOT create a second application. -
Bind the acceptance decision to the application session, and invalidate it when the user revokes consent or the mandate expires.
Failure at any step MUST fail closed, and MUST NOT fall back to treating acting_on_behalf_of, a platform signature, or a Verification Proof as authorization.
10.6.1. Key Binding and Rotation
A mandate bound through cnf.jkt authorizes that exact agent signing key, not every key the agent platform may publish later. An agent’s key directory (§ 9.2 Key Discovery) MUST retain the mandated key until the mandate expires; key rotation does not transfer a mandate to a new key. If the old key cannot remain available for verification, the user mandate MUST be reissued for the new key. This keeps the mandate’s lifetime within the key-retention and rotation window, and avoids silently widening the user’s authorization.
10.7. Signalling and Errors
A provider that requires a user mandate MUST document the requirement in its tool descriptions. This version defines no Job Manifest field for mandate requirements.
When a context requires a user mandate and none is presented, or the presented mandate fails any step of § 10.6 Verification, the provider MUST return the standard error envelope (§ 3.7 Error Responses) with error_code user_mandate_required. One code covers both cases, so the response does not reveal which check failed.
The experimental USER_MANDATE_FIXTURES in the OJCP conformance suite exercise these decisions — a missing or unadmitted mandate, a platform credential or Verification Proof presented as user authority, agent-key, audience, employer, job and candidate-data mismatches, expiry, revocation, and replay. They distinguish failure classes internally to test each check; those classes are not wire error codes.
10.8. Security and Privacy Considerations
-
Hashes are not anonymization. The action statement binds hashes rather than duplicating candidate data, but a hash of low-entropy data is not safe to disclose on its own. The provider-issued
mandate_nonce, selective disclosure, short validity, and single use all contribute. Implementations SHOULD NOT log raw mandates, candidate data, or undisclosed SD-JWT claims. -
Revocation. A candidate may withdraw consent while a long-running verification or application session is pending. Once revocation takes effect, the provider MUST stop accepting the mandate and invalidate the associated OJCP session.
-
Key rotation. Rotation MUST NOT weaken agent binding. Providers SHOULD cache a mandated key only as long as the directory’s rotation and expiry rules allow, and agents MUST publish the old key for at least the full lifetime of every outstanding mandate that references it.
11. 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.
11.1. Overview
Identity verification in OJCP is a three-party interaction:
-
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).
-
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.
-
The **Identity Verifier** (e.g., ID.me, Clear) performs the actual verification (face scan, document upload, etc.) and issues a signed Verification Proof.
11.2. Flow
The verification flow depends on the proof_delivery mode declared in each Verification Step:
11.2.1. Agent-Submitted Flow
When proof_delivery is "agent_submitted" (or omitted — this is the default):
-
Agent calls
begin_application— provider returnsstatus: "pending_verification"with averification_stepsarray. -
Agent inspects each step’s
type,verifier_id,verification_url, andproof_delivery. -
Agent presents the
verification_urlto the candidate (opens browser, deep link, etc.). Thehuman_requiredflag indicates the agent cannot complete this step itself. -
Candidate completes verification with the Identity Verifier (face scan, government ID upload, etc.).
-
Identity Verifier issues a Verification Proof — a signed JWS artifact.
-
Proof is delivered to the agent (see § 11.5 Proof Delivery).
-
Agent calls
submit_applicationwith theverification_proofsarray and thesession_tokenfrom step 1. -
Provider validates each proof (see § 11.10 Provider Claim Validation).
-
Application accepted — status transitions to
submitted.
11.2.2. Provider-Managed Flow
When proof_delivery is "provider_managed" (common for embedded verification like an iframe within an apply form):
-
Agent calls
begin_application— provider returnsstatus: "pending_verification"with averification_stepsarray whereproof_deliveryis"provider_managed". -
Agent presents the
verification_urlto the candidate. -
Candidate completes verification. The proof is delivered directly to the provider via server callback or iframe
postMessage— the agent never handles the proof. -
Agent polls
check_application_statusuntil the status advances frompending_verification. -
Agent calls
submit_applicationwithoutverification_proofs— the provider already has them. -
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".
11.3. No PII Through OJCP
Verification Proofs MUST NOT contain personally identifiable information. A proof contains:
-
subject_hash— A one-way hash identifying the verified individual. Not reversible to PII. -
verification_type— What was verified (e.g.,identity,biometric). -
verifier_id— Who verified it. -
issued_at/expires_at— Timestamps. -
proof_token— The signed artifact itself.
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.
11.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:
-
provider_id— The provider’s domain or identifier. Becomes theaudclaim in the proof. -
verification_type— The type of verification required (e.g.,"identity","background_check"). -
nonce— A unique, application-scoped value that binds the resulting proof to this specific application session. Providers MUST use theapplication_idor a derivative. -
callback_url— (Forprovider_manageddelivery) The HTTPS URL where the verifier should POST the proof upon completion. -
redirect_url— (Foragent_submitteddelivery via redirect) The URL to redirect the candidate’s browser to after verification, with theproof_tokenappended as a query parameter.
The verifier responds with:
-
session_id— The verifier’s internal session identifier. -
verification_url— A session-scoped URL where the candidate completes verification. This URL is what the provider includes in theverification_stepsarray. -
expires_at— When the verification session expires.
POST htt ps: //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.
11.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:
11.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:
-
Server callback — The verifier POSTs the signed proof to the
callback_urlthe provider supplied during § 11.4 Session Initiation. This is the RECOMMENDED channel for provider-managed delivery. The callback payload SHOULD includesession_id,proof_token, andverification_type. -
Client-side messaging — For iframe embeddings, the verifier’s page sends the proof to the parent frame via
window.parent.postMessage(). The provider’s page listens for the message and extracts the proof.
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.
11.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:
-
Redirect — After verification, the verifier redirects the candidate’s browser to the
redirect_url(if provided during session initiation) withproof_tokenas a query parameter. The agent or its browser extension captures the token from the redirect. -
Polling — The agent polls the verifier’s session status endpoint (e.g.,
GET {verification_endpoint}/{session_id}) to check whether verification is complete and retrieve theproof_token. Verifiers that support polling SHOULD declare"polling"in their manifest’sproof_delivery_methods.
Agents SHOULD prefer redirect-based delivery when available, falling back to polling. Agents MUST NOT poll more frequently than once every 5 seconds.
11.5.3. Inline Rendering via MCP Apps
A Verification Step MAY be completed inline in the conversation instead of through verification_url. The MCP Apps extension lets a tool reference a ui:// resource that the host renders in a sandboxed iframe. Inline rendering is an alternative delivery surface for the candidate interaction; it is not a third proof_delivery mode, and it does not change how proofs are delivered or validated.
An Identity Verifier that supports inline rendering advertises it by including "embedded_app" in its manifest’s proof_delivery_methods (see § 11.7 Verifier Discovery). A provider MAY then include the verifier’s resource URI as ui_resource on the Verification Step:
{ "step_id" : "vs_1" , "type" : "identity" , "verifier_id" : "id.me" , "human_required" : true , "verification_url" : "https://verify.id.me/flow/abc123" , "ui_resource" : "ui://id.me/verify/abc123" , "proof_delivery" : "provider_managed" , "expires_at" : "2026-09-25T14:30:00Z" }
Fallback. ui_resource is OPTIONAL and verification_url remains REQUIRED. A host that does not support MCP Apps, or a candidate on a surface that cannot render the resource, uses verification_url with no loss of function. Providers and Identity Verifiers MUST NOT return a Verification Step that can only be completed through ui_resource.
Proof delivery is unchanged. For provider_managed steps, the verifier delivers the proof to the provider through the channels in § 11.5.1 Provider-Managed Delivery; the app only drives the candidate interaction. For agent_submitted steps, the app returns the proof to the host, which supplies it to submit_application in verification_proofs, exactly as for a proof collected through the hosted flow. The exact channel by which an app hands the proof back to the host is not defined by this version.
Obligations:
-
Identity Verifiers that declare
embedded_appMUST serve aui://resource that completes the same verification and yields the same Verification Proof as their hosted flow, and MUST keepverification_urlvalid as the fallback. -
Providers MUST treat a step whose proof arrives through the app identically to one completed through
verification_url. The delivery surface does not change how the proof is validated (see § 11.10 Provider Claim Validation). -
Hosts render the resource in a sandbox and remain the enforcement point for permissions and for which origins the app may load (Content Security Policy), per MCP Apps.
Privacy. The verifier owns and hosts the UI, which runs in the verifier’s own origin inside the host’s sandbox. Biometric data, documents and other PII captured by the app are handled by the verifier and MUST NOT be transmitted through OJCP (see § 11.3 No PII Through OJCP). Only the signed, opaque Verification Proof crosses back through the protocol.
OJCP defines only the advertisement (embedded_app), the ui_resource reference and the fallback contract; it does not define the UI itself.
11.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.
11.6.1. JWS Header
The JWS header MUST include:
-
alg— The signing algorithm.ES256(ECDSA with P-256 and SHA-256) is RECOMMENDED.RS256(RSASSA-PKCS1-v1_5 with SHA-256) is acceptable.ES384andRS384(384-bit variants) are also permitted. Verifiers declare supported algorithms in their manifest’ssigning_algorithmsfield. -
kid— Key identifier matching a key in the verifier’s JWK Set.
11.6.2. Required JWT Claims
The JWS payload MUST include the following claims:
-
iss(Issuer) — Theverifier_idof the Identity Verifier that issued the proof. Providers MUST verify this matches the expected verifier. -
aud(Audience) — The provider domain or identifier this proof is intended for. Proofs with a specificaudare scoped to one provider. Verifiers MAY issue proofs withaud: "*"to enable cross-provider reuse; providers MAY reject wildcard-audience proofs. -
sub(Subject) — Thesubject_hash— a SHA-256 hex digest of the verifier’s canonical subject identifier. MUST NOT be reversible to PII. -
iat(Issued At) — NumericDate when the proof was issued ([RFC7519] Section 4.1.6). -
exp(Expiration Time) — NumericDate when the proof expires. -
nonce— An application-scoped value binding this proof to a specific application session. Thenonceclaim MUST always be present. Providers MUST use theapplication_idas the nonce value. Providers MUST reject proofs where thenoncedoes not match the expectedapplication_idfor the application.
11.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.
11.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:
-
Fetch the JWK Set over HTTPS and cache it. A cache TTL of 1 hour is RECOMMENDED; providers MUST refetch when encountering an unknown
kid. -
Match the
kidfrom the JWS header to a key in the JWK Set. -
Verify the
algin the JWS header matches the key’salgfield. -
Reject proofs signed with algorithms not in the verifier’s declared
signing_algorithms.
11.6.5. Key Rotation
Identity Verifiers SHOULD rotate signing keys periodically (RECOMMENDED: every 90 days). During rotation:
-
The verifier MUST publish both old and new keys in its JWK Set for an overlap period of at least 24 hours. This ensures proofs signed with the outgoing key remain validatable during the transition.
-
The verifier SHOULD begin signing new proofs with the new key immediately, while keeping the old key in the JWK Set.
-
After the overlap period, the verifier MAY remove the old key from the JWK Set. Proofs signed with the removed key will no longer validate — this is expected, as they should have expired by then.
Providers MUST handle key rotation gracefully:
-
When a
kidis not found in the cached JWK Set, refetch the set before rejecting the proof. -
Do not hard-code
kidvalues or signing keys. Always resolve dynamically from the JWK Set.
11.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.
11.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" , "embedded_app" ], "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]). JWE handling rules (key exchange, encryption algorithms, decryption procedures) are not defined by this specification. Providers SHOULD reject jws+jwe proofs they cannot decrypt.
11.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.
-
Audience-scoped proofs (
audset to a specific provider): Bound to one provider and one application. -
Wildcard-audience proofs (
aud: "*"): Can be submitted to any provider, but only for the application matching thenonce. This allows a candidate to verify once and apply to multiple jobs in a single session, as long as the provider mints a newapplication_id(and thus a newnonce) for each.
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.
11.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.
11.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 |
11.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:
-
Active phase (elapsed time ≤ estimated_completion_minutes): Poll every 30 seconds.
-
Extended phase (1× to 3× estimated_completion_minutes): Poll every 5 minutes.
-
Long-tail phase (3× to 24× estimated_completion_minutes): Poll every 1 hour.
-
Stalled phase (> 24× estimated_completion_minutes, but before
verification_deadline): Poll every 6 hours.
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.
11.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:
-
If all verification steps are
Fasttier: 24 hours frombegin_application. -
If any step is
Slowtier: 14 days frombegin_application. -
Providers MAY set custom deadlines; the value MUST be returned to the agent so it can plan polling behavior.
When verification_deadline is reached:
-
Provider MUST transition the application status to
expired. -
Provider MUST NOT accept further
verification_proofsfor thatapplication_id. -
Provider MUST NOT accept
submit_applicationfor thatapplication_id. -
Agents MUST stop polling for that application.
11.9.4. Abandonment Semantics
An application is considered abandoned when:
-
verification_deadlineis reached with one or more required steps incomplete, OR -
The candidate explicitly cancels (out of band — UI cancellation, agent revokes consent), OR
-
The agent has not polled or otherwise interacted for a provider-defined inactivity period (RECOMMENDED: 7 days for Slow tier, 1 hour for Fast tier).
On abandonment, providers:
-
MUST transition application status to
expired. -
SHOULD purge or anonymize any application data (resume URL, screening answers, candidate context) within 30 days unless retention is required by law (e.g., OFCCP record-keeping) or the candidate has explicitly opted into a candidate-facing pipeline (e.g., a talent pool).
-
MUST NOT continue to call the verifier or hold open verification sessions for an abandoned application.
11.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.
11.9.6. Agent Behavior on Long-Running Verifications
For Slow-tier verifications, agents SHOULD:
-
Inform the candidate that verification will take time and the application will not complete in the same session.
-
Persist the
application_id,session_token, andverification_deadlineso the agent can resume polling later. -
Schedule polling at the cadence above rather than blocking on the verification.
-
Notify the candidate (via the agent’s own UI or notification channel) when verification completes or expires.
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).
11.10. Provider Claim Validation
When a provider receives a Verification Proof, it MUST perform the following validation steps:
-
Structure — Verify the
proof_tokenis valid JWS compact serialization (three Base64url-encoded segments separated by.). Error:invalid_structure. -
Key resolution — Extract
kidfrom the JWS header. Fetch (or use cached) JWK Set from the verifier’spublic_keys_url. Find the matching key. Error:key_resolution_failed. -
Signature — Validate the JWS signature using the resolved public key and declared
alg. Error:invalid_signature. -
Issuer — Verify
issmatches the expectedverifier_id. Error:issuer_mismatch. -
Audience — Verify
audmatches the provider’s domain or identifier, or is"*". Error:audience_mismatch. -
Expiry — Verify
expis in the future andiatis in the past (with clock skew tolerance per § 11.6.6 Clock Skew). Error:expired_proof. -
Nonce — Verify
noncematches the expectedapplication_idfor this application session. Error:nonce_mismatch. -
Type — Verify the outer
verification_typematches 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.
11.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).
{ "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" ] } ] }
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.3" , "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:
-
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.
-
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 (§ 11.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.
12. 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.
12.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.
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.
12.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}] })) ); } });
12.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.
13. Feed Discovery
13.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.
13.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 (§ 8.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 listing review are defined and operated by the OJCP Registry.
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.
14. Security and Privacy
14.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.
14.2. Candidate Privacy
-
Candidate Context is opt-in and scoped. Agents MUST declare
consent_scopewith every tool call that includes candidate data. -
resume_embedding_hashenables fit-scoring without transmitting sensitive PII. -
Candidates MUST be able to revoke agent access tokens, immediately invalidating in-flight apply sessions.
-
Impressions are recorded against the calling software’s attributable identity, never against the candidate, and are retained no longer than the declared attribution window (see § 6 Attribution). Providers MUST NOT fingerprint callers for attribution.
14.3. Data Freshness
Job data is inherently volatile — positions are filled, salaries change, and postings expire. Agents and providers MUST account for staleness:
-
Manifests. Agents SHOULD re-fetch
/.well-known/ojcp.jsonno more than once per hour. Providers SHOULD serve manifests withCache-Control: max-age=3600. Signed manifests have an explicitsignature.expires_atwhich takes precedence. -
Search results. Search results are ephemeral. Agents MUST NOT cache search results for more than 15 minutes. Providers SHOULD include a
Cache-Control: max-age=900header on search responses. Job IDs from cached search results MAY be stale — agents MUST handlejob_not_founderrors gracefully when following up withget_job_detail. -
Job detail. Individual job postings change less frequently than search rankings. Agents MAY cache
get_job_detailresponses for up to 1 hour. ThevalidThroughfield, when present, indicates the posting expiration date — agents MUST NOT present expired postings to candidates. -
Application sessions. Session tokens have a RECOMMENDED TTL of 30 minutes (see § 14.7 Identity Verification Security). Agents MUST NOT cache or reuse session tokens across application attempts.
-
Provider obligation. Providers SHOULD set appropriate HTTP cache headers (
Cache-Control,ETag,Last-Modified) on all tool responses. When a provider cannot guarantee data freshness (e.g., upstream ATS sync delays), the response SHOULD include awarningsarray entry with code"stale_data".
14.4. Rate Limiting
Rate limits declared in the Job Manifest MUST be enforced by the provider.
When a client exceeds the declared rate limit:
-
REST/HTTP endpoints: Providers MUST return HTTP
429 Too Many Requestswith aRetry-Afterheader indicating the number of seconds before the client may retry. -
MCP transport: Providers MUST return a JSON-RPC error with code
-32029and includeretry_after_secondsin the errordataobject.
Agents MUST NOT retry before the indicated Retry-After / retry_after_seconds period. Agents SHOULD implement exponential backoff with jitter for subsequent retries.
14.5. Employer Controls
-
Providers MAY require Agent Declaration and allowlist specific agent IDs. An allowlist MUST match only verified agent IDs (§ 9.4 Verification). Providers MAY restrict individual jobs to allowlisted agents via the job’s visibility block (§ 3.1.1 Agent-Scoped Visibility).
-
All agent-initiated application events SHOULD be logged with agent identity and timestamp.
-
Providers MAY disable
supports_agent_submissionon any apply path at any time.
14.6. Ecosystem Integrity
-
The OJCP Registry MUST perform provider verification before listing.
-
Agents SHOULD include a
user_consent_tokenwhen initiating applications. Providers MAY reject submissions without one. -
Agent spam and fake application abuse are mitigated via Agent Declaration and provider-side rate limiting.
14.7. Identity Verification Security
-
Verification Proofs MUST be JWS-signed by the Identity Verifier using a key published at the verifier’s
public_keys_url(JWK Set per [RFC7517]). Providers MUST validate signatures using the full claim validation procedure defined in § 11.10 Provider Claim Validation before accepting proofs. -
The JWS signing algorithm MUST be one of
ES256(RECOMMENDED),RS256,ES384, orRS384. Providers MUST reject proofs signed with algorithms not declared in the verifier’ssigning_algorithms. -
Verification Proofs MUST NOT contain PII. The
subject_hashMUST be computed asSHA-256(canonical_subject_identifier)and MUST NOT be reversible to the candidate’s identity. -
Providers SHOULD reject proofs older than the verifier’s declared
proof_ttl_seconds. -
Agents MUST NOT cache Verification Proofs beyond their
expires_attimestamp. -
Verification URLs MUST use HTTPS and SHOULD include a session parameter that binds the verification to the specific application.
-
Identity Verifiers MUST NOT share verification data with OJCP providers beyond the signed Verification Proof.
-
Providers MUST validate the
audclaim to prevent cross-provider proof replay. -
Providers MUST validate that the
nonceclaim matches the expectedapplication_idto prevent replay attacks. -
Error responses and
warningsarrays MUST NOT include candidate PII. Field names are acceptable; field values are not. -
Session tokens MUST be cryptographically random with a minimum of 256 bits of entropy. Session tokens MUST have an expiration time (RECOMMENDED 30 minutes). Providers SHOULD bind session tokens to the originating verified
agent_idand rejectsubmit_applicationcalls from a different verified agent than the one that calledbegin_application; an unverifiedagent_idMUST NOT be used for this binding (§ 9.4 Verification). Theapplication_idMUST NOT be sequential or predictable — providers SHOULD use UUIDv4 or equivalent.
15. 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.
|
| MCP Apps | Identity verifiers MAY render verification inline as a sandboxed ui:// resource (§ 11.5.3 Inline Rendering via MCP Apps).
|
| HTTP Message Signatures / Web Bot Auth | Verifiable agent identity (§ 9 Agent Identity). |
| SD-JWT VC | User mandates SHOULD be key-bound SD-JWT verifiable credentials (§ 10 User Mandates). |
| 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. |
16. 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.3. Minor versions are additive and backward-compatible: the schema namespace stays at /v0.1/, and ojcp_version accepts every earlier minor version, so implementations of earlier versions remain conforming.
17. Conformance
17.1. Conforming Provider
A conforming OJCP provider MUST:
-
Serve a valid Job Manifest at
/.well-known/ojcp.jsonover HTTPS. -
Implement the
search_jobstool with a conforming input and response schema. -
Return
Content-Type: application/jsonfor all OJCP endpoints. -
Enforce declared
rate_limitsand return appropriate rate-limit errors.
A conforming OJCP provider SHOULD:
-
Implement
get_job_detail,get_employer_context,begin_application,submit_application, andcheck_application_status. -
Validate Verification Proofs per § 11.10 Provider Claim Validation when identity verification is required.
17.2. Conforming Agent
A conforming OJCP agent MUST:
-
Discover provider capabilities by fetching
/.well-known/ojcp.jsonbefore invoking tools. -
Respect
consent_scopewhen transmitting Candidate Context and MUST NOT include PII fields beyond the declared scope. -
Include an Agent Declaration when calling
begin_applicationorsubmit_application. -
Respect rate limits and honor
Retry-After/retry_after_secondsvalues.
17.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.
18. IANA Considerations
This specification registers three well-known URIs per [RFC8615]:
| URI suffix | Change controller | Specification |
|---|---|---|
ojcp.json
| OJCP community | § 2.1 Discovery |
ojcp-verifier.json
| OJCP community | § 11.7 Verifier Discovery |
ojcp-agent.json
| OJCP community | § 9.3.3 Delegated Binding |
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 (§ 9.2 Key Discovery); that URI is defined and registered by the Web
Bot Auth directory specification, not by OJCP.
19. Scope
The following are deliberately outside this specification:
-
Form skill descriptors — The
form_skill_urlfield on apply paths references a form skill descriptor (field mappings, validation rules, conditional logic, submission instructions). Its format is defined outside this specification. -
User mandate credential format — § 10 User Mandates defines the security profile every user mandate MUST satisfy. The credential media type, exact claim profile, and a registry of action statement types are not defined here.
-
Registry operations — Registration, verification criteria, provider auditing, and dispute resolution are defined and operated by the OJCP Registry.
-
Verifier internals — 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 out of scope. -
Conversion reporting — How an employer’s ATS reports the outcome of an application completed off-protocol back to the provider (§ 6.4 Off-Protocol Applications) is between those parties.
20. Acknowledgements
The editor would like to thank the following individuals for their contributions to this specification:
Todor Minakov (Recruitics), Wilber Acosta (Recruitics).