Developer docs

Build on verified skill data.

Use Humanproof's public API, sandbox fixtures, and signed webhooks to plug a credit score for your skills into search, profile, marketplace, and agent workflows.

Quickstart

A practical order for getting from dashboard setup to a production integration.

01

Create a scoped API key

Use least-privilege scopes. Candidate reads need candidates:read, score lookups need scores:read, webhook registration needs webhooks:write, and private agent reports need agent-decisions:write.

Manage API keys
02

Call sandbox routes

Use signed-in dashboard routes with fictional candidates and scores before touching live metered endpoints. Sandbox calls are never billed.

Open sandbox
03

Call the public v1 API

Send your API key as a bearer token. Include Humanproof-Version when you want explicit version pinning.

Open OpenAPI
04

Subscribe to signed events

Register an HTTPS endpoint, store the one-time signing secret, send a test event, then monitor recent delivery attempts.

Manage webhooks
05

Report agent outcomes

POST private decision reports with an idempotency key. Final outcomes feed the dashboard unlock preview for approved paid-read rate-limit boosts and future API discounts.

View reports

API reference

The OpenAPI document is the source of truth for route schemas. This table is the operator view.

GET/api/v1/candidates/searchcandidates:read or x402

Search public candidate previews by skill, display name, title, or handle.

GET/api/v1/candidates/{username}candidates:read or x402

Read one public candidate profile. Private evidence and worker ids are never returned.

GET/api/v1/scores/{scoreId}scores:read or x402

Read one active public score envelope for an onboarded public profile.

POST/api/v1/score-vc/verifypublic, optional scores:read

Verify a presented Score VC with issuer pinning, Ed25519 signature checks, and revocation lookup.

POST/api/v1/agent-decisionsagent-decisions:write

Report a private decision or final outcome after an agent uses candidate or score data.

GET/api/v1/webhooks/endpointswebhooks:write

List active webhook endpoints owned by the API key user. Signing secrets are never returned after creation.

POST/api/v1/webhooks/endpointswebhooks:write

Create an HTTPS webhook endpoint and receive the one-time signing secret.

DELETE/api/v1/webhooks/endpoints/{endpointId}webhooks:write

Disable a webhook endpoint. Disabled endpoints stop receiving future deliveries.

Sandbox routes

Use these from the signed-in dashboard to build search, profile, and score flows before switching to metered v1 endpoints.

GET/api/account/developer/sandbox/candidates/search?q=react&limit=2Dashboard session

Search fictional candidate previews without an API key or credit metering.

GET/api/account/developer/sandbox/candidates/{username}Dashboard session

Read a fictional candidate profile with the same public envelope shape as v1 candidate detail.

GET/api/account/developer/sandbox/scores/{scoreId}Dashboard session

Read a fictional score envelope with VC metadata and status URLs for client wiring.

Dashboard management routes

These same-origin routes power the signed-in developer dashboard for webhook diagnostics, test events, and agent report review.

GET/api/account/webhooks/endpointsDashboard session

List active webhook endpoints for the signed-in developer dashboard.

POST/api/account/webhooks/endpointsDashboard session, same origin

Create a webhook endpoint from the dashboard and show the signing secret once.

POST/api/account/webhooks/endpoints/{endpointId}/testDashboard session, same origin

Queue one synthetic webhook event for an endpoint. Test events are limited to one per minute.

GET/api/account/webhooks/endpoints/{endpointId}/deliveries?limit=5Dashboard session

Read recent delivery attempts for the dashboard diagnostics view.

GET/api/account/agent-decisions?limit=10Dashboard session

List private agent decision reports owned by the signed-in developer.

POST/api/account/agent-decisions/unlock-reviewDashboard session, same origin

Request Humanproof review for an agent reporting unlock.

Requests

Copy these shapes into your client, then swap in your saved API key and endpoint URL.

Authenticated request

Bearer auth works across paid v1 reads. Version pinning is optional, but recommended for production clients.

curl "https://humanproof.io/api/v1/candidates/search?q=react&limit=5" \
  -H "Authorization: Bearer hp_live_..." \
  -H "Humanproof-Version: 2026-05-14"
Dashboard sandbox request

Sandbox routes use your signed-in dashboard session, return synthetic data, and do not consume API credits.

const response = await fetch(
  "/api/account/developer/sandbox/candidates/search?q=react&limit=2",
  {
    headers: { accept: "application/json" },
    credentials: "include",
  },
);

const data = await response.json();
Create a webhook endpoint

Use an idempotency key when creating endpoints. Store the returned signing secret immediately.

curl "https://humanproof.io/api/v1/webhooks/endpoints" \
  -X POST \
  -H "Authorization: Bearer hp_live_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: endpoint-prod-001" \
  -d '{
    "url": "https://api.example.com/humanproof/webhooks",
    "description": "Production events",
    "events": ["score.updated", "score.revised"]
  }'
Read webhook diagnostics

Dashboard routes let signed-in developers inspect recent deliveries and queue test events without pasting an API key.

const endpointId = "00000000-0000-4000-8000-000000000000";

const response = await fetch(
  `/api/account/webhooks/endpoints/${endpointId}/deliveries?limit=5`,
  {
    headers: { accept: "application/json" },
    credentials: "include",
  },
);

const { deliveries } = await response.json();

await fetch(`/api/account/webhooks/endpoints/${endpointId}/test`, {
  method: "POST",
  headers: { "content-type": "application/json" },
  credentials: "include",
  body: JSON.stringify({ eventType: "score.updated" }),
});
Report an agent decision

Use this after an agent shortlists, rejects, contacts, or otherwise acts on Humanproof data.

curl "https://humanproof.io/api/v1/agent-decisions" \
  -X POST \
  -H "Authorization: Bearer hp_live_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: decision-req-frontend-123" \
  -d '{
    "subject": { "kind": "candidate", "username": "maya-chen" },
    "decision": "shortlisted",
    "outcome": "pending",
    "agentName": "Talent routing agent",
    "jobRef": "req-frontend-123",
    "reasonCodes": ["react_match", "verified_score"]
  }'
Webhook payload envelope

Every event has a stable id, type, creation timestamp, and event-specific data.

{
  "schemaVersion": "humanproof.webhook_event.v1",
  "id": "evt_score_test_00001",
  "type": "score.updated",
  "createdAt": "2026-06-01T00:00:00.000Z",
  "data": {
    "workerId": "00000000-0000-4000-8000-00000000f001",
    "snapshotId": "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd",
    "score": 742,
    "confidenceLow": 710,
    "confidenceHigh": 774,
    "evidenceCount": 12,
    "sourceCount": 4,
    "scoreVcSha256": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
    "scoreVcStatusToken": "status_test_0001",
    "issuedAt": "2026-06-01T00:00:00.000Z"
  }
}
Verify webhook signatures

Compute HMAC SHA-256 over timestamp.rawBody and compare it with the v1 signature.

import { createHmac, timingSafeEqual } from 'node:crypto';

export function verifyHumanproofWebhook(rawBody, signatureHeader, secret) {
  if (typeof rawBody !== 'string' || typeof signatureHeader !== 'string') return false;
  if (signatureHeader.trim() === '' || typeof secret !== 'string' || secret === '') return false;

  const parts = Object.fromEntries(
    signatureHeader.split(',').map((part) => {
      const [key, value] = part.split('=');
      return [key, value];
    }),
  );
  const timestamp = parts.t;
  const received = parts.v1;
  if (!timestamp || !received) return false;
  const timestampSeconds = Number(timestamp);
  if (!Number.isFinite(timestampSeconds)) return false;
  if (!/^[0-9a-f]+$/i.test(received)) return false;
  if (Math.abs(Date.now() / 1000 - timestampSeconds) > 300) return false;

  const expected = createHmac('sha256', secret)
    .update(`${timestamp}.${rawBody}`)
    .digest('hex');

  const expectedBytes = Buffer.from(expected, 'hex');
  const receivedBytes = Buffer.from(received, 'hex');
  return expectedBytes.length === receivedBytes.length
    && timingSafeEqual(expectedBytes, receivedBytes);
}

Webhooks

Humanproof signs every delivery. Your endpoint should verify the signature before processing event data.

Event catalog
  • score.updated

    A worker score has a new issued value.

  • score.revised

    A previously issued score was corrected or replaced.

  • evidence.revoked

    A source record was removed from the active evidence set.

  • dispute.opened

    A worker opened a review or score dispute.

  • dispute.resolved

    A dispute reached a terminal decision.

  • candidate.consent_changed

    A candidate sharing permission changed.

Delivery headers
  • Humanproof-Webhook-Id

    Stable event id. Use it for idempotent processing.

  • Humanproof-Webhook-Event

    Event type, for example score.updated.

  • Humanproof-Webhook-Delivery

    Unique delivery attempt row id.

  • Humanproof-Webhook-Timestamp

    Unix timestamp used in the signed payload.

  • Humanproof-Webhook-Signature

    HMAC header in the form t=<timestamp>,v1=<hex digest>.

Retries and diagnostics
  • Only HTTPS endpoints are accepted. URLs with credentials, fragments, whitespace, or private network targets are rejected.
  • A 2xx response marks the delivery as delivered. Network failures and non-2xx responses retry up to 8 attempts.
  • Retry delay starts at 30 seconds, doubles between attempts, and caps at 6 hours.
  • Payloads are JSON, capped at 256 KB, and include schemaVersion, id, type, createdAt, and data.
  • The dashboard can queue one test event per endpoint every 60 seconds.
  • Dashboard diagnostics are scoped to the recent delivery window and hide when delivery history cannot be refreshed.