Open Job Context Protocol

Living Document,

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

Abstract

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

1. Introduction

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

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

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

1.1. Terminology

Job Context Provider

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

Job Agent

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

Job Manifest

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

Job Tool

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

Candidate Context

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

Apply Path

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

Agent Declaration

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

Identity Verifier

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

Verification Step

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

Verification Proof

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

Verifier Manifest

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

EEO Data

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

Manifest Signature

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

Trust Tier

A registry-assigned classification (unverified, verified, or audited) indicating the level of trust signals available for a provider. Drives agent obligations around PII transmission. See § 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:

The manifest SHOULD include:

The manifest MAY include:

{
  "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: entry, mid, senior, lead, director, executive. Providers MAY accept additional values."
          },
          "posted_within_days": { "type": "integer" }
        }
      },
      "candidate_context": { "$ref": "https://ojcp.dev/schemas/v0.1/candidate-context.json" },
      "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:

"visibility": {
  "tier": "restricted",
  "audience": ["ai.wayfarer.agent", "ai.northstar.assistant"]
}

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:

Agents:

Visibility gating narrows who sees a job; it MUST NOT become a tool for opaque or discriminatory gatekeeping:

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.

A 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:

The error envelope MAY include:

Standard error codes:

Code Meaning
invalid_request Malformed request or missing required parameters.
missing_required_field A required field is missing from the input.
invalid_field_value A field value is outside the allowed range or format.
job_not_found The specified job_id does not exist.
employer_not_found The specified employer_id does not exist.
application_not_found The specified application_id does not exist.
invalid_session_token The session_token is invalid or does not match the application.
session_expired The application session has expired.
no_agent_apply_path No apply path supports agent submission for this job.
rate_limited Request rate limit exceeded. Check retry_after_seconds.
unauthorized Authentication required but not provided.
forbidden Authentication provided but insufficient permissions.
provider_error Internal provider error. Agent should retry with backoff.
agent_signature_missing A signature was required for this context but none was provided. See § 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:

A conforming JobPosting SHOULD include:

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

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:

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:

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

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

full_profile permits all fields with no restrictions.

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

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

4.3. AgentDeclaration

Agent self-identification for audit trails and rate limiting.

A conforming Agent Declaration MUST include:

Agents SHOULD include a user_consent_token when submitting applications. For autonomous interaction modes, the token represents the operator’s authorization rather than per-action user consent. The token format and verification mechanism are 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:

Agent obligations:

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

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

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

4.5. VerificationStep

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

A conforming Verification Step MUST include:

A Verification Step MAY include:

4.6. VerificationProof

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

A conforming Verification Proof MUST include:

4.7. VerifierManifest

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

A conforming Verifier Manifest MUST include:

A Verifier Manifest SHOULD include:

5. Apply Paths

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

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

Each apply path object MUST include:

Each apply path object SHOULD include:

Each apply path object MAY include:

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

6. 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 a search_jobs or get_job_detail response.

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, unverified agent_id is 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:

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:

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
}

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:

"attribution": {
  "method": "reference",
  "referred_by": "com.example.jobboard"
}

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

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:

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:

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:

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

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:

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

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

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

Provider trust addresses all three. It does not address agent-side threats (a bad agent submitting spam to a legitimate provider) — those are addressed 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:

The signature object SHOULD include:

A signed 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:

  1. Remove the signature field from the manifest object.

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

  3. Compute the signature over the canonicalized bytes.

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

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:

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

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

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

  4. Verifying the signature over the canonicalized manifest content.

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

8.3.1. Key Rotation

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

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

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:

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

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

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

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

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

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

If any step fails, the agent MUST treat the manifest as unverified and apply the corresponding restrictions (§ 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:

Agents MAY skip validation for read-only operations:

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

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:

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:

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).

{
  "agent_id": "com.acme.agent",
  "signers": ["https://acme.agentplatform.example"]
}

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:

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

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

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

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

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

  6. Verify agent_id against the grammar (§ 9.3.1 Agent ID Grammar).

  7. Verify the identity binding: § 9.3.2 Namespace Binding, else § 9.3.3 Delegated Binding.

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

On any failure the provider MUST return the standard OJCP error envelope (§ 3.7 Error Responses) with one of the agent-signature error codes registered in § 3.7 Error Responses: agent_signature_missing, agent_signature_invalid, agent_key_not_found, agent_digest_mismatch, agent_algorithm_unsupported, agent_signature_expired, agent_nonce_replayed, agent_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:

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:

  1. Platform attestation — § 9.1 Signing Profile through § 9.4 Verification, signed with the platform’s key. The agent_id identifies the platform (e.g., ai.northstar.assistant, signed from https://northstar.ai, whose namespace ai.northstar it falls under — § 9.3.2 Namespace Binding). This proves which software is calling; it does not establish user authority.

  2. User authorization — an OPTIONAL user_mandate carried in the AgentDeclaration: 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:

§ 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_action object 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:

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.

The decoded issuer-signed claims of a single-use mandate for a 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.

The action statement whose digest the mandate above carries:
{
  "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.

{
  "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:

  1. Verify the credential signature and the holder proof required by its selected SD-JWT VC profile.

  2. Apply its mandate-admission policy (§ 10.3 Mandate Admission); cryptographic validity alone is not sufficient.

  3. Verify aud, the time bounds (nbf when present, and exp), the OJCP version, and that cnf.jkt equals the thumbprint of the currently verified agent signing key.

  4. 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.

  5. 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.

  6. Reject a mandate whose jti or mandate_nonce has 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.

  7. 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

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:

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

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

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

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):

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

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

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

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

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

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

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

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

  9. 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):

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

  2. Agent presents the verification_url to the candidate.

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

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

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

  6. Application accepted — status transitions to submitted.

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

11.3. No PII Through OJCP

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

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

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:

The verifier responds with:

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

Verifier response:

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

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

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:

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:

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:

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:

11.6.2. Required JWT Claims

The JWS payload MUST include the following claims:

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:

11.6.5. Key Rotation

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

Providers MUST handle key rotation gracefully:

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.

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:

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:

When verification_deadline is reached:

11.9.4. Abandonment Semantics

An application is considered abandoned when:

On abandonment, providers:

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:

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:

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

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

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

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

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

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

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

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

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

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

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).

Manifest excerpt declaring multiple verifiers:
{
  "supported_verifiers": [
    {
      "verifier_id": "clear",
      "verifier_name": "Clear",
      "verifier_manifest_url": "https://clear.me/.well-known/ojcp-verifier.json",
      "verification_types": ["identity", "biometric"],
      "required_for_paths": ["ats_direct"]
    },
    {
      "verifier_id": "checkr",
      "verifier_name": "Checkr",
      "verifier_manifest_url": "https://checkr.com/.well-known/ojcp-verifier.json",
      "verification_types": ["background_check"],
      "required_for_paths": ["ats_direct"]
    }
  ]
}
A begin_application response with multiple verification steps — one provider-managed (Clear embedded in the apply form), one agent-submitted (Checkr sends the candidate a separate link):
{
  "ojcp_version": "0.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:

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

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

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

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

The provider validates the Checkr proof using the standard claim validation procedure (§ 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.

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

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

14.3. Data Freshness

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

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:

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

14.6. Ecosystem Integrity

14.7. Identity Verification Security

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:

A conforming OJCP provider SHOULD:

17.2. Conforming Agent

A conforming OJCP agent MUST:

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:

  1. Form skill descriptors — The form_skill_url field on apply paths references a form skill descriptor (field mappings, validation rules, conditional logic, submission instructions). Its format is defined outside this specification.

  2. 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.

  3. Registry operations — Registration, verification criteria, provider auditing, and dispute resolution are defined and operated by the OJCP Registry.

  4. 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.

  5. 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).

Index

Terms defined by this specification

Terms defined by reference

References

Normative References

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

Non-Normative References

[DOM]
Anne van Kesteren. DOM Standard. Living Standard. URL: https://dom.spec.whatwg.org/
[RFC1123]
R. Braden, Ed.. Requirements for Internet Hosts - Application and Support. October 1989. Internet Standard. URL: https://www.rfc-editor.org/info/rfc1123/
[RFC1918]
Y. Rekhter; et al. Address Allocation for Private Internets. February 1996. Best Current Practice. URL: https://www.rfc-editor.org/info/rfc1918/
[RFC6454]
A. Barth. The Web Origin Concept. December 2011. Proposed Standard. URL: https://www.rfc-editor.org/info/rfc6454/
[RFC7515]
M. Jones; J. Bradley; N. Sakimura. JSON Web Signature (JWS). May 2015. Proposed Standard. URL: https://www.rfc-editor.org/info/rfc7515/
[RFC7516]
M. Jones; J. Hildebrand. JSON Web Encryption (JWE). May 2015. Proposed Standard. URL: https://www.rfc-editor.org/info/rfc7516/
[RFC7517]
M. Jones. JSON Web Key (JWK). May 2015. Proposed Standard. URL: https://www.rfc-editor.org/info/rfc7517/
[RFC7519]
M. Jones; J. Bradley; N. Sakimura. JSON Web Token (JWT). May 2015. Proposed Standard. URL: https://www.rfc-editor.org/info/rfc7519/
[RFC7638]
M. Jones; N. Sakimura. JSON Web Key (JWK) Thumbprint. September 2015. Proposed Standard. URL: https://www.rfc-editor.org/info/rfc7638/
[RFC7800]
M. Jones; J. Bradley; H. Tschofenig. Proof-of-Possession Key Semantics for JSON Web Tokens (JWTs). April 2016. Proposed Standard. URL: https://www.rfc-editor.org/info/rfc7800/
[RFC8615]
M. Nottingham. Well-Known Uniform Resource Identifiers (URIs). May 2019. Proposed Standard. URL: https://www.rfc-editor.org/info/rfc8615/
[RFC9421]
A. Backman, Ed.; J. Richer, Ed.; M. Sporny. HTTP Message Signatures. February 2024. Proposed Standard. URL: https://www.rfc-editor.org/info/rfc9421/
[RFC9449]
D. Fett; et al. OAuth 2.0 Demonstrating Proof of Possession (DPoP). September 2023. Proposed Standard. URL: https://www.rfc-editor.org/info/rfc9449/
[RFC9530]
R. Polli; L. Pardue. Digest Fields. February 2024. Proposed Standard. URL: https://www.rfc-editor.org/info/rfc9530/
[RFC9901]
D. Fett; K. Yasuda; B. Campbell. Selective Disclosure for JSON Web Tokens. November 2025. Proposed Standard. URL: https://www.rfc-editor.org/info/rfc9901/
[SD-JWT-VC]
SD-JWT-based Verifiable Digital Credentials (SD-JWT VC). Internet-Draft. URL: https://datatracker.ietf.org/doc/draft-ietf-oauth-sd-jwt-vc/