DEVELOPERS

Reffolio API

Request employment references programmatically, track them through to consent, and receive the verified record — with webhooks or polling. Available on the Growth and Business plans.

How it works

The API mirrors the Reffolio workflow exactly. You create a request; Reffolio emails the previous employer a secure link; they complete a sector-specific form; the candidate consents; you receive a verified, tamper-evident record. You can be notified by webhook at each step, or poll for status.

  1. Create a request — POST /v1/requests — get a request_id (status pending).
  2. The referee completes the reference — you receive a referee_submitted webhook (status becomes awaiting_consent).
  3. The candidate consents — you receive consent_granted and reference_received (status becomes received).
  4. Read the reference — GET /v1/references/{id}.

Authentication

An organisation admin generates keys in the dashboard under API. The full key (rfl_live_) is shown once — store it securely. Send it as a bearer token on every request:

Authorization: Bearer rfl_live_EXAMPLEKEY1234567890

Base URL:

https://reference-platform-production.up.railway.app

A quick check from your terminal:

curl -H "Authorization: Bearer rfl_live_..." \
  https://reference-platform-production.up.railway.app/v1/ping

Requests & references

POST/v1/requests

Request a reference about a candidate from a previous employer. Reffolio emails the referee a secure link and, once completed, asks the candidate to consent — exactly as the dashboard does.

Request body
{
  "worker_name": "Sam Jones",
  "worker_email": "sam@example.com",
  "referee_email": "manager@previousemployer.co.uk",
  "referee_name": "A. Manager",
  "prev_employer_name": "Previous Employer Ltd",
  "template_id": null,
  "message": "Optional note shown to the referee."
}
Response
{
  "request_id": "...",
  "status": "pending",
  "email_sent": true,
  "domain_verified": true
}
GET/v1/requests/{id}

Poll one request: its lifecycle status, and the produced reference + consent state once available.

Response
{
  "request_id": "...",
  "status": "received",
  "candidate_name": "Sam Jones",
  "referee_email": "manager@previousemployer.co.uk",
  "consent_status": "granted",
  "reference": {
    "reference_id": "...",
    "ref_number": "REF-2026-000123",
    "content_hash": "770c44...",
    "readable": true
  }
}
GET/v1/requests

List your organisation’s reference requests with their current lifecycle status.

Response
[
  { "request_id": "...", "status": "awaiting_consent", "candidate_name": "Sam Jones", "consent_status": "pending" }
]
GET/v1/references/{id}

Fetch a received reference’s full content. Only references sent to your organisation with consent granted are readable.

Response
{
  "reference_id": "...",
  "ref_number": "REF-2026-000123",
  "candidate_name": "Sam Jones",
  "sector": "care",
  "content": { "...": "..." },
  "content_hash": "770c44...",
  "consent_status": "granted"
}

Request lifecycle

The status field on a request moves through these values:

pendingRequest created; the referee has been emailed but hasn’t opened the link.
openedThe referee has opened the secure link.
awaiting_consentThe referee has completed the reference; waiting for the candidate to consent.
receivedThe candidate consented; the reference is readable.
declinedThe candidate declined consent; the reference is not released.

Webhooks

Register a URL and Reffolio will POST a signed JSON payload when events happen. Polling (GET /v1/requests/{id}) is always available as a fallback.

POST/v1/webhooks

Register an endpoint to receive signed event deliveries. The signing secret is returned ONCE.

Request body
{
  "url": "https://your-system.example.com/reffolio/hook",
  "events": ["referee_submitted", "consent_granted", "reference_received"]
}
Response
{
  "id": "...",
  "url": "https://your-system.example.com/reffolio/hook",
  "events": ["referee_submitted", "consent_granted", "reference_received"],
  "active": true,
  "secret": "whsec_..."
}
GET/v1/webhooks

List your registered webhooks and their last delivery status. Secrets are never returned.

Response
[
  { "id": "...", "url": "...", "events": ["..."], "active": true, "last_status": 200 }
]
DELETE/v1/webhooks/{id}

Delete a webhook endpoint.

Response
{
  "deleted": true,
  "id": "..."
}

Events

referee_submittedThe previous employer has completed the reference. It is held pending the candidate’s consent.
consent_grantedThe candidate has consented; the reference is now released to you.
reference_receivedFired alongside consent_granted — the reference is now readable via GET /v1/references/{id}.

Payload & signature

Each delivery has this shape, with the event name and data:

{
  "event": "reference_received",
  "data": {
    "reference_id": "...",
    "ref_number": "REF-2026-000123"
  }
}

Verify authenticity using the X-Reffolio-Signature header, which is sha256= followed by the HMAC-SHA256 of the raw request body, keyed with your webhook secret:

import hmac, hashlib

def verify(secret, raw_body, header):
    expected = "sha256=" + hmac.new(
        secret.encode(), raw_body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, header)

Delivery is best-effort: respond with a 2xx status quickly. If your endpoint is unavailable, use polling to reconcile any missed events.

Errors

Errors return a JSON body { "detail": "..." } with a standard HTTP status:

401Missing or invalid API key, or the key was revoked.
402Your plan does not include API access. Upgrade to Growth or Business.
403The resource belongs to another organisation, or consent has not been granted.
404The request, reference or webhook was not found.
422Validation error (e.g. a required field is missing or the URL is invalid).

Notes

  • Keys are scoped to one organisation and only ever access that organisation’s own data.
  • Requests created via the API send the same emails and follow the same consent flow as the dashboard.
  • References are only readable once the candidate has granted consent.
  • Keep your keys and webhook secrets safe. If a key is exposed, revoke it in the dashboard and generate a new one.

Ready to integrate?

Generate your first key in the dashboard, or talk to us about integrations.

Go to dashboard