Last updated 3 September 2026

Reference-check integration recipe

This is the complete Lovelio side of a partner reference-check flow. It starts a check after a successful interview, reads the candidate's referee contacts, waits for the partner system, attaches the completed report, and records the outcome in the candidate's native timeline.

Your reference provider decides its own start and status endpoints. Keep its check ID beside the Lovelio candidate and interview IDs. Everything below that calls Lovelio is exact and runnable.

Access to request

Describe the use case in the Connect portal first. Lovelio derives the smallest access set from that declaration. This recipe uses the permissions already declared by these four operations:

  • interviews:read for GET /interviews
  • candidates:read for GET /candidates/{candidate_id}/referees
  • documents:write for POST /documents
  • activities:write for POST /activities

Do not add read or write permissions that your implementation does not use. If you subscribe instead of polling, also request the existing webhooks:write permission to create the subscription and webhooks:read to read the event catalogue and inspect deliveries. The route declarations in the OpenAPI reference are the source of truth for all of these permissions.

Set these once for the examples:

export LOVELIO_HOST="https://us.lovelio.ai"
export LOVELIO_API_KEY="lc_at_..."

Use the regional host returned for the connected agency. Never assume US for every agency.

1. Find the trigger and candidate

Poll completed interviews with cursor pagination. status=completed includes both passed and failed interviews, so read outcome and start a check only when it is passed.

curl "$LOVELIO_HOST/api/v1/interviews?status=completed&limit=100" \
  -H "Authorization: Bearer $LOVELIO_API_KEY"

For every page, process data, then follow meta.next_cursor with after= while meta.has_more is true. Persist each processed interview ID. The interview response contains candidate.id; that is the cnd_... ID used below.

Call your reference provider's create-check endpoint once for that candidate and store its returned check ID. Poll the provider's status endpoint on its documented schedule until it says completed or failed. That provider poll is not a Lovelio task poll and Lovelio does not invent a status for it.

2. Read the referee contacts and their real status

export CANDIDATE_ID="cnd_..."
curl "$LOVELIO_HOST/api/v1/candidates/$CANDIDATE_ID/referees" \
  -H "Authorization: Bearer $LOVELIO_API_KEY"

Each item is a referee contact: name, email or phone, relationship, employment context, source, submitted_at, and created_at. A non-null submitted_at means the candidate supplied that contact information. It does not mean the referee replied and it does not mean a reference report is complete.

Pass eligible contacts to your provider using the provider's API. If there are no usable contacts, keep the check waiting and record that state in your own system. Do not claim completion in Lovelio.

The reference.received webhook has the same boundary: it means a candidate submitted referee contact details. It is not a completed-reference event.

3. Write status changes into the candidate timeline

When the provider check starts, fails, needs attention, or completes, add a concise candidate note. Use a new idempotency key for each logical status change and reuse that same key for retries of that change.

export ACTIVITY_KEY="reference-check-provider-check-123-started"
curl -X POST "$LOVELIO_HOST/api/v1/activities" \
  -H "Authorization: Bearer $LOVELIO_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $ACTIVITY_KEY" \
  -d '{
    "resource_type": "candidate",
    "resource_id": "'"$CANDIDATE_ID"'",
    "note": "Reference check started. Provider check: provider-check-123."
  }'

These are native note_added activities. They appear in the candidate Timeline and in activity reads; they are not a private integration log.

4. Attach the completed report

Only do this after the provider reports completion and gives you the final file. Upload the report to the candidate with multipart form data:

export REPORT_KEY="reference-check-provider-check-123-report"
curl -X POST "$LOVELIO_HOST/api/v1/documents" \
  -H "Authorization: Bearer $LOVELIO_API_KEY" \
  -H "Idempotency-Key: $REPORT_KEY" \
  -F "record_type=candidate" \
  -F "record_id=$CANDIDATE_ID" \
  -F "file=@./reference-report.pdf;type=application/pdf"

The report appears in the candidate Documents view. Lovelio also adds a native document_added timeline entry. Uploading a partner report does not change a Lovelio reference form's status and does not turn submitted contact details into a completed native reference.

Then add the completion activity with its own stable key:

export COMPLETE_KEY="reference-check-provider-check-123-completed"
curl -X POST "$LOVELIO_HOST/api/v1/activities" \
  -H "Authorization: Bearer $LOVELIO_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $COMPLETE_KEY" \
  -d '{
    "resource_type": "candidate",
    "resource_id": "'"$CANDIDATE_ID"'",
    "note": "Reference check completed. Report attached. Provider check: provider-check-123."
  }'

The resulting candidate UI has the report under Documents and two native Timeline entries for completion: Document Added plus the API-authored completion note. The Referees view continues to show the contact records and their actual submission state.

5. Retry without duplicates

For each Lovelio POST, create one idempotency key for one logical operation and store it with the provider check. Retry the exact method, path, query and body with that same key.

  • A completed retry replays the saved response and sets X-Idempotency-Replayed.
  • IDEMPOTENCY_IN_PROGRESS means the first attempt is still running. Wait for Retry-After, then send the identical request with the same key.
  • IDEMPOTENCY_KEY_REUSED means that key was paired with different input. Stop and fix the caller; do not disguise the mismatch with another key.
  • IDEMPOTENCY_OUTCOME_UNKNOWN means Lovelio cannot safely say whether the write finished. Keep the returned operation ID, reconcile the candidate in the UI, and do not create a new key until the outcome is known.
  • On network timeout or a 5xx response, retry the identical request with the same key. Never generate a fresh key merely because the response was lost.

If the provider check fails, do not upload a completion report. Add a failure or attention note with its own stable activity key, retain the provider check ID, and follow the provider's retry rules before starting another provider check.

Optional: subscribe instead of polling

Create a subscription for interview.outcome_recorded with POST /api/v1/webhooks. The event tells you an outcome was recorded; inspect the payload or read the interview and start only when its real outcome is passed.

Verify every delivery before parsing it. Compute HMAC-SHA256 over X-Lovelio-Timestamp + "." + raw_request_body with the one-time whsec_... secret, compare the sha256=... signature using a timing-safe comparison, and reject timestamps more than five minutes away. Deduplicate on payload event_id, return 2xx only after durable acceptance, and expect at-least-once delivery. Read /docs/agents/webhooks for runnable signature code, delivery inspection, replay, and secret rotation.

The subscription needs webhooks:write. Reading GET /webhooks/events or GET /webhooks/{id}/deliveries needs webhooks:read. Those permissions add subscription management only; they do not widen candidate, interview, document, or activity access.