# Scores (/concepts/scores)



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`](/reference/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](/concepts/calibration) for what that rests on and how to choose thresholds.

## Example data [#example-data]

Create a score with [`POST /v1/scores`](/reference/create-score) 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:

```csv title="examples.csv"
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 [#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`](/reference/list-scores); if you leave it out, Finic writes the criteria from your examples.

### What to include [#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 [#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:

```json title="400"
{ "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/score` | Does                                                                                                                                                 |
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `skip_schema_validation` | Scores this record without the check; the schema doesn't change. For one-off records you know differ                                                 |
| `update_schema`          | Scores 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 [#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](/concepts/calibration#calibrate-a-score) the score.

## Jobs [#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}`](/reference/get-job-status) 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](/reference#scores).

## What's stored [#whats-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](/concepts/data#data-retention). Your scores are listed by [`list_scores`](/reference/list-scores), and in the Finic Portal under [Scores](https://portal.finic.ai/scores).
