API reference

The Finic API's six endpoints, how requests authenticate, the errors they return and the limits they run under.

Base URL https://api.finic.ai. Send an API key as Authorization: Bearer gsk_… (Authentication) and JSON bodies (Content-Type: application/json); the score endpoints also take multipart/form-data. Every response has an X-Request-Id header.

EndpointRequestWhat it does
scorePOST /v1/scoreScores one record from 0 to 1000
create_scorePOST /v1/scoresCreates a score from labeled examples
list_scoresGET /v1/scoresLists your scores, with what each predicts
calibratePOST /v1/calibrateSends outcomes, to improve a score
get_job_statusGET /v1/jobs/{job_id}Whether creating or calibrating has finished
decidePOST /v1/decideAnswers your questions about a case

Errors

A failed request returns an error status and this JSON:

{ "error": { "type": "invalid_request", "message": "\" Yes\" appears twice in the criteria.", "param": "questions.q.criteria" } }

type is one of the types below: branch on it. message is for people and can change, so don't parse it. param is the dotted path to the field at fault, or null. Validation stops at the first problem.

HTTPtypeWhenRetry
400invalid_requestMalformed JSON, or the request breaks a rule of its endpointNo. Fix the request
400schema_mismatchA record sent to score has fields that differ from its score's schemaNo. Fix the record, or send skip_schema_validation or update_schema
401authentication_errorMissing or invalid API key, including a revoked keyNo. Check the key
402insufficient_creditThe organization's credit is used up (decide and score calls)No. An admin adds credit in the Finic Portal under Settings → Billing
404model_not_foundUnknown model in a decide requestNo. Use grayson-1
404score_not_foundNo score with that model_id in your organizationNo. Check the model_id
404job_not_foundNo job with that id in your organizationNo. Check the job_id
404invalid_requestA path or method the API doesn't haveNo. Fix the URL
409score_not_readyThe score is still being created, or creating it failedAfter its job completes. If the job failed, create the score again
409duplicate_score_idThe score already has a call with this score_idNo. Use a new score_id for each record
409calibration_in_progressThe score is already being calibratedAfter that calibration's job completes
413request_too_largeA body over 2 MB, a decide request over 32,768 tokens, or a record too large to scoreNo. Send less
429rate_limitedOver the request rate, or a daily limit (100 new scores per organization, 10 calibrations per score)The request rate: yes, with backoff. A daily limit: after it resets
500internal_errorSomething failed on Finic's sideYes
503overloadedTemporarily at capacity, the decision didn't finish within 30 seconds, or creating scores isn't available right nowYes, with backoff
  • Out of credit, decide and score calls get 402 before their data is checked; creating and calibrating never do.
  • A 502 or 504 from the network edge, during a deployment or an outage, has no JSON body or X-Request-Id: treat it like 503.
  • 202 from create_score and calibrate means accepted, with the work running in a job. A failed job reports its error at get_job_status, in the same shape without param.
  • A 429 for the request rate says Too many requests for this organization: the limit is 5 requests per second across its API keys and the playground. and clears in moments. A daily limit's (This organization has created 100 scores in the last 24 hours, the limit.) doesn't, so retrying soon fails the same way.

Some invalid_request errors:

Causeparammessage
data of only whitespacedatadata must not be blank
A decide request with neither data nor contextdatadata is required: the JSON object, JSON array or text the questions are about. Free text can go in context.
A question name starting with a digitquestions.1stinvalid name '1st': must match ^[A-Za-z_][A-Za-z0-9_]{0,63}$
Two options whose descriptions differ only in case or whitespacequestions.q.criteria" Yes" appears twice in the criteria.
A score question with one levelquestions.risk.criteriaA score's criteria must list 2 to 32 levels as text, lowest first.
A noul's criteria with a key other than true or falsequestions.q.criteriaA noul's criteria can only describe "true" and "false", each as text.
A choice's base_rate shares that don't add up to 1questions.q.base_rateA choice's base_rate shares must add up to 1.
An unknown top-level field, such as temperaturenullUnknown field "temperature".
An unknown field inside a questionquestions.q.temperatureExtra inputs are not permitted
A score call without score_idscore_idscore_id is required: your unique id for this record's score, used later to calibrate.
A score call whose data holds several recordsdatadata must hold one record; got 3. Send one score call per record.
Outcomes whose score_ids match no score calldataNone of the score_ids match a call to this score. Send the score_id you sent to POST /v1/score.

Retrying

  • decide is safe to retry; a retry of a request that succeeded makes the decision again.
  • score with the same score_id scores a record once: if the first call succeeded, the retry gets 409 duplicate_score_id, without the score.
  • create_score retried after a timeout can create a second score, counted toward the daily limit.
  • calibrate retried resends the same outcomes, which replace themselves; while the first call's job runs, it gets 409 calibration_in_progress.
  • get_job_status is always safe.

Retry 429 (except a daily limit), 500, 502, 503, 504, network errors and timeouts with exponential backoff and jitter; don't retry other 4xx without changing the request. Set client timeouts above 30 seconds, the longest a decision can run.

import random
import time

import requests

RETRYABLE = {429, 500, 502, 503, 504}


def post(path: str, body: dict, api_key: str, attempts: int = 5) -> dict:
    for attempt in range(attempts):
        last = attempt == attempts - 1
        try:
            response = requests.post(
                f"https://api.finic.ai{path}",  # "/v1/decide" or "/v1/score"
                headers={"Authorization": f"Bearer {api_key}"},
                json=body,
                timeout=60,
            )
        except (requests.ConnectionError, requests.Timeout):
            if last:
                raise
        else:
            if response.status_code not in RETRYABLE or last:
                break
        time.sleep(min(8.0, 0.5 * 2**attempt) * random.uniform(0.5, 1.0))

    if not response.ok:
        request_id = response.headers.get("X-Request-Id")
        try:
            error = response.json()["error"]
        except ValueError:  # an edge 502/504 has no JSON body
            raise RuntimeError(f"{response.status_code} (request {request_id})")
        raise RuntimeError(f"{response.status_code} {error['type']}: {error['message']} (request {request_id})")
    return response.json()

For help, email support@finic.ai with the X-Request-Id. Support sees a request's time, key, endpoint, status and error type, not its content: if the problem depends on the content, describe it or send a redacted example.

Limits

LimitValue
Request body2 MB
Request rate5 per second per organization, in bursts of up to 10

Rate limits

The request rate covers all of an organization's API keys, agent sign-ins and playground runs, for decide, score, create and calibrate calls; checking a job doesn't count. Requests over it get 429 rate_limited, with no Retry-After header: retry with exponential backoff (Retrying). For higher volume, contact support@finic.ai.

Scores

LimitValue
Rows to create a scoreAt least 100, with at least 20 where is_bad is true and 20 where it's false
Rows usedUp to 2,000; more are sampled down, keeping the mix of outcomes
A row, or a record to score60,000 bytes as compact JSON
description4,000 characters
score_id200 characters, unique within the score
New scores100 per organization per 24 hours
Calibrations10 per score per 24 hours, one at a time
Creating a scoreAbout 5 to 10 minutes

Over a daily limit, requests get 429 rate_limited until the 24 hours pass.

Decide

LimitValue
Tokens per request (data + context + questions + markers)32,768
Questions per request1–128
Options per choice or multi_choice2–255
Levels per score question2–32
Question and option key length1–64 characters

Over a limit, a request gets 400 invalid_request, or 413 request_too_large for tokens and body size.

How tokens are counted

usage.input_tokens counts everything the model reads:

  • data and context: a string as sent, an object or array as compact JSON.
  • A noul, choice or score question: its instructions and criteria, plus 2 marker tokens per question and 1 per option or level.
  • A multi_choice: each option is asked separately, so each costs the instructions, the list of all options (with 16 or fewer), the option and 2 markers. One with 8 options reads its instructions 8 times.

Question names and option keys aren't read, so they don't count. To size requests, send a representative one and read usage.input_tokens; see Data & context for fitting a long history.

On this page