Tarifs Documentation Vérification des badges
Nouveau

Badge Check-in Integration

Badge Check-in verifies that the person checking in is your enrolled rep — and that their badge is genuine — from a single selfie. Your backend makes one authenticated call and gets back a clear pass/fail with a confidence score.

Before you start

You’ll need:

  • Credentials: a client ID + secret and the token endpoint, issued by Parselo.
  • The scope checkin/verify.
  • A reference-photo endpoint on your backend that Parselo calls to fetch each rep’s enrollment photo — see Reference Photo Endpoint below for the exact contract to implement. Set its base URL on your dashboard’s Badge Check-in page (or send it with each request — see Per-request overrides), then click Generate token — Parselo creates the credential for you; copy it into your own backend’s config so it can check the Authorization header on incoming requests.

1. Get an access token

Badge Check-in authenticates machine-to-machine, via OAuth2 client_credentials — this is a service credential held by your backend, not a per-rep login.

curl -X POST https://<YOUR_AUTH_DOMAIN>/oauth2/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -u "<CLIENT_ID>:<CLIENT_SECRET>" \
  -d "grant_type=client_credentials&scope=checkin/verify"

Response:

{ "access_token": "<JWT>", "expires_in": 3600, "token_type": "Bearer" }

2. Verify a check-in

POST /verify-checkin with the rep id, the mode, and the selfie + badge as base64.

curl -X POST https://<PARSELO_API_HOST>/verify-checkin \
  -H "Authorization: Bearer <ACCESS_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{
    "repId": "<REP_ID>",
    "mode": "3-leg",
    "selfie": "<BASE64_JPEG>",
    "badge": "<BASE64_JPEG>"
  }'

3. Read the result

{
  "decision": "yes",
  "mode": "3-leg",
  "confidence": 96,
  "faceMatch": {
    "selfieVsProfile": { "score": 96, "threshold": 90, "passed": true, "evaluated": true },
    "selfieVsBadge":   { "score": 88, "threshold": 80, "passed": true, "evaluated": true },
    "badgeVsProfile":  { "score": 91, "threshold": 80, "passed": true, "evaluated": true }
  },
  "badge": {
    "valid": true,
    "confidence": 82,
    "checks": {
      "qrPresent": true, "qrResolved": true, "repIdMatch": true,
      "registrationNumberPresent": true,
      "requiredTexts": [{ "text": "ACME Badge", "found": true }],
      "charityLogoDetected": true,
      "partnerLogoDetected": true,
      "facePresent": true
    }
  },
  "reasons": [],
  "requestId": "…",
  "processedRegion": "ca-central-1"
}

The two fields most callers act on: decision ("yes", "no", or "review") and confidence (0–100, the primary face match). faceMatch and badge carry the per-leg and badge-template detail for callers who want to show more than a pass/fail. reasons lists machine-readable codes explaining a "no" or "review" decision (e.g. "required_text_not_found:ACME Badge") — empty on a clean "yes". A "yes" can still list a required_text_not_found reason when that miss was within the Required texts allowed to be missing setting on your dashboard (at least one required text must always be found).

Note: requiredTexts is always an array of { "text": string, "found": boolean } objects — one per required text checked (your account’s, or the ones you sent with the request) — never a plain array of strings.

Go example

Steps 1 and 2 combined, using only the standard library — adapt to your own HTTP client.

// 1. Get a token (client_credentials).
form := url.Values{"grant_type": {"client_credentials"}, "scope": {"checkin/verify"}}
tokReq, _ := http.NewRequest("POST", authDomain+"/oauth2/token",
	strings.NewReader(form.Encode()))
tokReq.Header.Set("Content-Type", "application/x-www-form-urlencoded")
tokReq.SetBasicAuth(clientID, clientSecret)
// ... do request, decode {access_token} into `token` ...

// 2. Verify a check-in.
body, _ := json.Marshal(map[string]string{
	"repId": repID, "mode": "3-leg", "selfie": selfieB64, "badge": badgeB64,
})
req, _ := http.NewRequest("POST", parseloHost+"/verify-checkin", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
// ... do request, decode the response, branch on result.Decision ...

Modes

mode is 2-leg (selfie compared against the enrolled profile photo) or 3-leg (adds two cross-checks against the badge’s own photo). Your account has a default mode; pass mode explicitly to override it per request.

Per-request overrides

Like mode, the required badge text(s) and the reference-photo base URL set on your dashboard are defaults. Send either one with a /verify-checkin call to override it for that call only — useful when one account serves several campaigns, each with its own badge wording or photo endpoint. Omit a field (don’t send null) to use your account’s setting.

FieldTypeOverrides
requiredTextsarray of 1–10 strings, each up to 100 charactersThe text(s) that must appear on the badge. Replaces your account’s list for this call — it doesn’t add to it.
referencePhotoBaseUrlhttps:// URLThe base URL of your Reference Photo Endpoint.
{
  "repId": "<REP_ID>",
  "mode": "3-leg",
  "requiredTexts": ["ACME Badge"],
  "referencePhotoBaseUrl": "https://api.yourcompany.com",
  "selfie": "<BASE64_JPEG>",
  "badge": "<BASE64_JPEG>"
}
  • The reference-photo token can’t be overridden. You still generate it once on the dashboard, and Parselo sends it to whichever base URL applies — so only send base URLs of endpoints you control.
  • The other badge checks (logos, registration number, QR) and the number of required texts allowed to be missing always follow your account’s settings. That allowance applies to the texts you send too, but at least one of them must always be found — so a single sent text is always strictly required.
  • An invalid value is rejected with 400 and one of invalid_requiredTexts, text_too_long, too_many_texts, or invalid_referencePhotoBaseUrl — before anything is checked or billed.

Reference Photo Endpoint

This is the other half of the integration: an endpoint you host, that we call, to fetch each rep’s enrollment photo by repId at verification time. We never store it — every check-in fetches it fresh and discards it after comparison.

Where this fits. Your app calls our POST /verify-checkin (above) with a repId and a live selfie. We call your endpoint below with that same repId, compare the bytes we get back against the selfie, and return the result to you. No translation of the id happens on either side — it’s the same repId throughout.

Configure it: on your dashboard’s Badge Check-in page, under Reference photo integration, set your endpoint’s base URL (or send referencePhotoBaseUrl per request — see Per-request overrides), then click Generate token. Parselo creates the credential — you never invent one yourself — and shows it to you exactly once. Copy it into your own backend’s config (an env var, a secrets manager, whatever you already use) so your endpoint can check it on every incoming request. That’s the only setup step — nothing here requires a Parselo redeploy or a support ticket. To rotate later, click Rotate on the same card and update your backend’s copy; the old token stops working immediately.

Contract

MethodGET
Path{your base URL}/api/v1/reps/{repId}/photo
AuthAuthorization: Bearer <the token Parselo generated for you>
Success200 — raw image bytes
Not on file404 — a normal outcome, not an error
curl -H "Authorization: Bearer <YOUR_TOKEN>" \
     https://your-api.example.com/api/v1/reps/a1b2c3d4/photo \
     -o reference.png

Success response — 200

Body is the raw image bytes — not JSON, not a signed URL.

Content-Type: image/png        # or image/jpeg — whatever's actually on file
Cache-Control: no-store        # recommended: a rep's photo can change between calls

Error responses

Every non-200 response should use one JSON envelope:

{
  "error": {
    "code": "not_found",
    "message": "reference photo not found",
    "request_id": "a1c9e2f0...",
    "timestamp": "2026-09-29T03:31:36Z"
  }
}
StatusMeaningcode
400repId was empty/missing in the path — shouldn’t happen from us, but validate defensivelybad_request
401Missing/malformed header, or a token that doesn’t matchauthentication_required
404No reference photo on file for this rep — a normal outcome. We treat it as no/review, no retry.not_found
500Unexpected failure on your side. We treat this as a real error, distinct from 404 — safe for you to fix and have us retry.internal_error

What’s not supported

  • Bytes only — no signed-URL response mode.
  • No transformation — we use exactly what you return; resize/crop/re-encode on your side if needed.
  • One active token at a time — there’s no dual-key overlap window. Clicking Rotate on the dashboard issues a new one and invalidates the old one immediately.
  • You can’t choose your own token — Parselo always generates it, the same way it generates your Badge Check-in client secret. This is deliberate: you never have to invent a secret and keep it in sync across two systems by hand.