Scores

A 0–1000 risk score for one kind of decision, built from your labeled examples. The example data, schema checks, score ids and jobs.

A score rates the risk of one kind of decision, such as a payment, a sign-up or a loan application. You create it once from past records whose outcomes you know, score each new record with POST /v1/score, and send outcomes as you learn them so it keeps improving. Each score is your own.

The score is an integer from 0 (lowest risk) to 1000 (highest), and divided by 1,000 it's about the probability that the outcome is bad: of records scored near 250, about 1 in 4 turn out bad. See Calibration for what that rests on and how to choose thresholds.

Example data

Create a score with POST /v1/scores from past records, each with is_bad: true when the outcome was bad, false when it was good. Send them as a JSON array of objects (nested objects and arrays are fine) or as CSV with a header row, as text or a file:

amount_usd,merchant_category,account_age_days,new_device,billing_country,ip_country,is_bad
2400.00,electronics,19,true,US,NG,true
58.12,grocery,1260,false,US,US,false
310.00,gift_cards,3,true,US,US,true

Rules

  • At least 100 rows, with at least 20 of each outcome. Up to 2,000 are used; a larger set is sampled down, keeping its mix of outcomes. The request can be up to 2 MB.
  • is_bad is true or false, 1 or 0, or the text "true", "false", "1", "0", "yes" or "no", in any case.
  • Every row has at least one other field, and is up to 60,000 bytes as compact JSON without is_bad.
  • CSV: every row has as many columns as the header, which names each column once. An empty cell is null, true and false are booleans, plain numbers are numbers, and the rest is text (leading zeros, as in ZIP code 02139, stay text). A dotted column name is a nested field: merchant.category is {"merchant": {"category": …}}, so CSV examples match JSON records scored later. A dotted name that clashes with another column stays one field.
  • description (optional, up to 4,000 characters): what the decision is and what is_bad means, such as "Card-not-present payments. is_bad: the payment was charged back as fraud within 90 days." It's the score's criteria in list_scores; if you leave it out, Finic writes the criteria from your examples.

What to include

  • Customer identity or event data in JSON: sign-ups, logins, payments, transactions, applications. One row per past decision, with its real outcome as is_bad.
  • The fields you'll send when you score, with the same names and shapes, from the same systems.
  • Only what was known at the time of the decision. A later chargeback, fraud flag or analyst verdict makes the score look better than it will be on new records.
  • Readable, consistent fields: names in words (amount_usd, not amt), units and currencies, codes expanded, the same type for a field in every row.
  • Recent, representative records from the population you'll score.

Schema

Creating a score records its schema: every field in the examples and its type (text, number, boolean, object or array). Nested fields have paths such as account.age_days, and fields in arrays paths such as logins[].country. A field every example has, never null, is required.

Each record you score is checked against it and rejected with 400 schema_mismatch if a required field is missing or null, a field is new, or a field has a type the examples never had. Text counts as the same type as numbers and booleans, so "58.12" fits a number field. The error lists up to 20 differences:

{ "error": { "type": "schema_mismatch", "message": "data doesn't match the score's schema: missing field: account_age_days; unexpected field: account_age. Send skip_schema_validation to score it anyway, or update_schema to make this the new schema.", "param": "data" } }
Flag on POST /v1/scoreDoes
skip_schema_validationScores this record without the check; the schema doesn't change. For one-off records you know differ
update_schemaScores this record without the check and makes its fields the schema from then on. For a lasting change, such as a new field; send a complete record

A score is most reliable on records like its examples. If your data changes a lot, create a new score.

Score ids

Every score call carries a score_id: your own id for that record, such as the payment's id, up to 200 characters. It's unique within the score: sending it again gets 409 duplicate_score_id. Keep it with the record, and send it back with the outcome to calibrate the score.

Jobs

Creating and calibrating return 202 with a job_id at once; the work runs in a job.

  • Creating takes about 5 to 10 minutes. Until then, score calls get 409 score_not_ready.
  • Check the job with GET /v1/jobs/{job_id} every 30 seconds or so, until complete is true.
  • A failed job has an error saying what to do. A failed creation leaves the score unusable: create it again. job_interrupted means maintenance interrupted it; send the same data again.
  • During a calibration, the score keeps scoring with its current version.

New scores are limited to 100 per organization per day, and calibrations to 10 per score per day; see Limits.

What's stored

A score keeps its description (or the criteria Finic wrote, if you gave none) and its schema (field names and types). Each score call keeps its score_id, its score, the numbers the score computed from the record and, once you calibrate, the outcome. The examples and records you send aren't stored, whatever your data retention. Your scores are listed by list_scores, and in the Finic Portal under Scores.

On this page