Upgrade to v2
Move an existing v1 integration to API v2.
This guide is for integrations that call /api/v1 today. New integrations should start with the Quickstart.
What's new in v2
- Bearer API keys. Every request authenticates with
Authorization: Bearer fce_….api_tokenin the body or query string no longer authenticates. - One authentication error. A rejected key returns HTTP
401with a JSONerroron every endpoint, instead of a404array on offer and accept. - Consistent JSON errors. Every error is JSON, with the same shapes on every endpoint, whatever
Acceptheader you send. - Optional email.
emailis no longer required. - Stricter phone numbers.
cell_phone_numberis length-checked and stored in one format. - Validated targeted referrals. Leads sent with
partner_idsare now validated like any other lead, capped at 50 partners, and the response reports each referral. - Deduplication. Sending the same applicant again inside the deduplication window returns the existing lead instead of creating a new one.
- Typed offer fields.
rankandprobabilityare integers,probability_conditionsis an array, every match has the same flat keys, and each value appears once. - Typed requests.
POST /leadandPOST /offertake each field in its JSON type and name any field sent with the wrong one. - Private lead lookups.
GET /lead/{hashid}and the partner link only return your own leads. - Status-only error codes. The HTTP status is the code. Bodies carry a
message, not acode. - Safer accepts.
partner_idis validated, and a lead can only be accepted in a state that allows it.
Deprecations
api_tokenauthentication is removed.- The singular
partner_idonPOST /leadandPOST /offeris rejected. Usepartner_ids. company_logo_path,live_score, anduserableare no longer returned in offers. Usecompany_logo_url,live_quotes, and the top-levelpartner_typeandprobability_conditions.live_scoreonPOST /acceptis rejected. Uselive_quotes.codeis no longer returned in response bodies.- The live-score marker
-2is gone. Partners whose live score is unavailable are left out ofmatches.
Upgrade your integration
1. Request v2 credentials
Email support@finch-technologies.com for a UAT key and, once you are ready, a Production key. Each key comes with the API origin it belongs to. See Environments.
2. Change the base path
Replace /api/v1 with /api/v2. The operation names stay the same; see the endpoint mapping.
3. Switch to Bearer authentication
Before (v1):
POST /api/v1/lead?api_token=your_v1_token
Content-Type: application/json
After (v2):
POST /api/v2/lead
Authorization: Bearer fce_your_key
Content-Type: application/json
Use referee_id in the body when a lead must be attributed to another affiliate.
4. Update lead payloads
POST /lead, POST /offer, and each row of POST /lead/batch share these fields. Rows marked Changed or Removed need your attention.
On POST /lead and POST /offer, send the body as Content-Type: application/json and each field in its JSON type: whole numbers for money and counts, true/false for yes-no fields, and strings for text, including id_number and cell_phone_number. v1 accepted numeric strings such as "25000" and words such as "yes"; v2 rejects them with 422 and names the field, for example "gross_income": ["must be an integer"]. A body with another Content-Type returns 415.
The other optional fields are title, language, education, employed, payday, bank_name, repayment_period, employment_period, company_name, debt_amount, open_loans, payment_frequency, debt_review, street_address, suburb, city, postal_code, and province.
Intent-specific fields
In v1, POST /lead and POST /offer both enforced the extra fields an intent requires, such as income and loan amount for Personal Loans. In v2 only POST /offer enforces them, and it reports what is missing in one list; see error handling. POST /lead accepts the lead without them, but partners have less to match on, so send what you have.
Placeholder values
Leave out fields you don't have, or send JSON null. Placeholder strings such as unknown, n/a, or "null" are validated like any other value, so "email": "unknown" is rejected with a 422.
The lead response
The success body drops code:
{
"id": "k3mN9pQx",
"status": "Lead successfully added"
}
- Duplicates. v1 created a new lead on every call. v2 returns the existing lead's
idwith HTTP200when the same applicant is sent again inside the deduplication window. Treat both as success. - Live feedback. If your account has live feedback enabled, v1 put the outcome under a key named
"0". v2 puts it underresponse, and omits it when you sendpartner_ids:
{
"id": "k3mN9pQx",
"status": "Lead successfully added",
"response": { "state": "accepted", "partner": "Example Lender" }
}
5. Update offer handling
The response still has two top-level keys, matches and the lead id, with no code. Each match changes.
Before (v1):
{
"matches": [
{
"id": 227,
"company_name": "Example Lender",
"company_logo_path": "images/partners/227/logo/logo.png",
"company_logo_url": "https://example.com/storage/images/partners/227/logo/logo.png",
"company_website_url": null,
"rank": "1",
"company_mini_logo_url": null,
"company_wide_logo_url": null,
"probability": 80,
"live_score": { "url": "https://partner.example.com/apply/k3mN9pQx" },
"end_to_end": false,
"userable": {
"partner_type": "api",
"probability_conditions": "[{\"field\":\"gross_income\",\"value\":7000,\"operator\":\"greater_than\",\"results\":80}]"
}
},
{
"id": 311,
"company_name": "Second Lender",
"company_logo_path": null,
"company_logo_url": null,
"company_website_url": "https://second.example.com",
"rank": "2",
"company_mini_logo_url": null,
"company_wide_logo_url": null,
"probability": null,
"live_score": -2,
"end_to_end": false,
"userable": { "partner_type": "api", "probability_conditions": null }
}
],
"id": "k3mN9pQx"
}
After (v2):
{
"matches": [
{
"id": 227,
"company_name": "Example Lender",
"company_website_url": "",
"company_logo_url": "https://partner.example.com/logo.png",
"company_mini_logo_url": "",
"company_wide_logo_url": "",
"partner_type": "api",
"probability_conditions": [
{ "field": "gross_income", "value": 7000, "operator": "greater_than", "results": 80 }
],
"partner_slug": "example-lender",
"rank": 1,
"probability": 80,
"live_scoring_api": true,
"live_quotes": { "url": "https://partner.example.com/apply/k3mN9pQx" },
"end_to_end": false
}
],
"id": "k3mN9pQx"
}
The second lender is missing from the v2 response because its live score was unavailable.
GET /intent returns only active intents. Do not reuse an intent id from v1 or from another environment.
6. Update accept handling
A v2 accept request:
{
"hashid": "k3mN9pQx",
"partner_id": 227,
"live_quotes": { "url": "https://partner.example.com/apply/k3mN9pQx" }
}
v2 also checks the lead's state. You can only accept a lead that POST /offer matched, or one whose previous accept was declined or failed. v1 had no such check.
Accept responses
See Accepting an Offer for how to handle each response.
7. Update error handling
In v1, error bodies differed by endpoint, and validation errors came back as JSON only when you sent Accept: application/json; otherwise Laravel redirected. In v2 every error is JSON with a fixed shape.
Authentication
Before (v1, offer and accept):
["Cannot be authorised or authenticated."]
After (v2, every endpoint):
{ "error": "unauthorized" }
Stop treating a 404 array as an authentication failure. Treat any 401 as a key problem.
Validation
Before (v1, POST /lead):
{
"message": "The required fields for the requested Intent are missing",
"errors": {
"first_name": ["The first name field is required."],
"id_number": ["The ID Number field is invalid"]
}
}
After (v2, every endpoint):
{
"message": "Validation failed.",
"errors": {
"first_name": ["required"],
"id_number": ["must be a valid RSA ID number"],
"gross_income": ["must be an integer"]
}
}
The keys in errors are still your JSON field names, but the messages are shorter and stable, so you can match on them:
Missing intent fields
v1 listed each missing intent field as a validation error, on /lead and /offer. v2 checks them only on /offer and returns them together:
{
"message": "intent schema validation failed",
"missing_fields": ["gross_income", "loan_amount_required"]
}
Business rules
Other errors
See Errors for retry guidance.
8. Verify in UAT
With your UAT key and origin:
- Call
GET /api/v2/intentand confirm HTTP200. - Send a fictional applicant through the flow you use:
POST /lead, orPOST /offerthenPOST /accept. - Send one invalid applicant and confirm your client reads the
422field errors. - Compare the v1 and v2 results for the same fictional applicant.
Keep Production traffic on v1 while you test.
9. Switch Production
When Fincheck approves your UAT run, change your Production client in one step:
- Replace the v1 token with your Production
fce_key. - Point the client at the Production origin that came with the key.
- Call
GET /api/v2/intentagainst Production before sending a live applicant. - Record the lead
idfrom your first live requests.
10. Stop calling v1
Once Production traffic succeeds on v2, remove every /api/v1 call and api_token from your integration.
Endpoint mapping
POST /lead/batch also changed shape: v1 took a semicolon-separated CSV only. v2 accepts CSV, XLSX, XLS, or JSON, validates each row, and reports per-row results. See Batch Import.
See the Changelog for the v1 retirement date once it is announced.