Billing

How credits are reserved, charged, topped up, and audited.

Datapoint bills per response collected, not per job created. Credits are purchased in advance and spent as jobs run.

How cost is computed

A standalone job's maximum cost is:

max_responses × datapoints × num_dimensions × per-response rate
  • datapoints: length of the datapoints array in your job.
  • max_responses: the job's max_responses_per_datapoint (default 10).
  • num_dimensions: 1 for single-instruction jobs, N for multi-dimension jobs. This factor applies to standalone jobs only and is mutually exclusive with steps — a job is either dimensional or a chain, never both.

A chain job (a job with steps) is priced instead by:

max_responses × steps × datapoints × per-response rate

where steps is the number of chain steps (1–5). Each datapoint fans out into one task per step, and every step is capped at max_responses.

Either way the total is maximal at submission time — it's what you'd pay if every task collected its full response cap. Partial chains (walks abandoned mid-way) and jobs that settle with unused capacity consume less; the unspent portion is released back to your available balance (see below).

Targeting surcharges

Jobs that target a specific audience pay a per-response surcharge on top of your account's base rate:

  • Geography targeting (annotator_filter with country, country_name, region, city, postal, or median_household_income): the surcharge depends on the countries targeted — some countries carry a premium. When multiple countries are listed, the highest applicable surcharge is used. Targeting region, city, or postal without naming the countries is priced at the highest country premium — list the countries (by country code or country_name) to get their actual rates. Income targeting (median_household_income) is a geographic attribute and is priced as a geo surcharge, not a demographic one.
  • Demographic targeting (annotator_filter with gender, age_range, or a custom dimension, or a chain with screening steps — demographic questions or early-exit rules): a flat surcharge, applied once regardless of how many demographic dimensions you combine.

The effective rate — base plus surcharges — is fixed when the job is created and returned as credits_per_response (with a pricing breakdown) by POST /jobs. Later pricing changes never affect a running job: reservation, the running cost_credits, and final settlement all use the job's own rate. Untargeted jobs pay the base rate.

Surcharge amounts are part of the platform's pricing configuration and can change over time — any credit values in the examples on this page are illustrative. To see the exact rate a targeted job would be priced at, use the quote endpoint below.

Credit lifecycle

Every job's credit moves through these ledger entries:

  1. Reserve: at job creation, the full estimated cost is moved from available to reserved. If available isn't enough, the API rejects with 402 Payment Required and returns needed_credits and available_credits.
  2. Release (settlement): when the job reaches a terminal state — it completes, is cancelled, or every datapoint fails or is blocked during ingestion — the job is billed for the responses actually collected (capped at the reservation) and any unspent portion is returned to available. Settlement happens once, at the job level; a single datapoint failing mid-run does not release credits on its own.
  3. Release (dispatch failure): if tasks can't be dispatched (a transient 503 from POST /jobs), the entire reservation is released immediately and the failed job is renamed, so you can resubmit under the same name.

A job that stalls without ever completing is settled automatically by a background sweep after roughly 14 days, releasing its unused reservation the same way.

Your reserved_credits grows when you submit jobs and shrinks as they settle. Your available_credits is the amount you could safely reserve for a new job.

Other ledger entries can appear on your account:

  • Purchase: a top-up adds to available.
  • Grant: promotional or signup credits added to available (also reflected in granted_credits). New accounts receive a one-time signup grant.
  • Refund: a reversed purchase. It removes the credits the original purchase granted, so it lowers available.
  • Planner fee: a successful POST /surveys/plan costs a small flat fee (see the survey planner).
  • Message fee: a signed charge for a dashboard-chat turn. Any account that used the chat builder sees these entries; if a turn fails after being charged, a matching positive message_fee entry nets it back out.

Check balance

GET/billing/balanceAuth: API key

Returns your account's current credit balance.

curl https://api.trydatapoint.com/data-labelling/v1/billing/balance \
  -H "X-API-Key: $DATAPOINT_KEY"

Response (200 OK)

{
  "available_credits": 4725,
  "reserved_credits": 275,
  "total_purchased_credits": 5000,
  "granted_credits": 100
}
FieldTypeDescription
available_creditsintCredits you can reserve for a new job right now.
reserved_creditsintCredits held against in-flight jobs. Released when a job settles.
total_purchased_creditsintLifetime purchased credits for the account.
granted_creditsintPromotional or signup credits granted to the account. Spendable, tracked separately from purchased credits.

Quote a per-response rate

POST/billing/pricing/quoteAuth: API key

Returns the effective per-response rate a job with the given targeting would be priced at — before any job exists. Use it to show an accurate cost preview while the user is still choosing their audience. Read-only and free to call.

curl -X POST https://api.trydatapoint.com/data-labelling/v1/billing/pricing/quote \
  -H "X-API-Key: $DATAPOINT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"annotator_filter": {"country": ["US", "GB"]}}'

Request body

FieldTypeDescription
annotator_filterobjectThe audience filter you would pass to POST /jobs, restricted to allow-list columns (e.g. `{"country": ["US"]}`). Omit for an untargeted quote.
has_screening_stepsboolean
default: false
Set true when the prospective job is a chain with screening steps (demographic questions or early-exit rules) — they price like a demographic filter.

Response (200 OK)

{
  "base_credits_per_response": 5,
  "demographic_surcharge_credits": 0,
  "geo_surcharge_credits": 3,
  "credits_per_response": 8
}

credits_per_response (base plus the surcharges) is the rate POST /jobs will reserve at for the same filter — the example values above are illustrative; the endpoint returns your account's current rates. Allow-list keys the filter validator would ultimately reject still return a quote — that validation happens at job creation, not here.


Top up

POST/billing/checkoutAuth: API key

Starts a hosted checkout session. Redirect the user to the returned checkout_url. On success the payment provider notifies Datapoint and your available_credits updates within a few seconds.

curl -X POST https://api.trydatapoint.com/data-labelling/v1/billing/checkout \
  -H "X-API-Key: $DATAPOINT_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'

Request body

FieldTypeDescription
product_idstringOptional. A specific purchasable product to check out. When omitted, the response opens a product picker chosen from your billing state rather than a single fixed product.
metadataobjectOptional string-keyed map forwarded with the checkout, echoed back so post-payment side-effects can be routed.

Empty body ({}) is valid — it opens the default picker.

Response (200 OK)

{
  "checkout_url": "https://buy.polar.sh/s/...",
  "session_id": "cs_abc..."
}

Error cases

StatusCause
400Unknown or disallowed product_id.
403Credit packs are not available (code: "credit_packs_disabled").
429Rate limit exceeded.
500Checkout products are not configured for this environment.

View history

GET/billing/historyAuth: API key

Paginated transaction history: every purchase, grant, reserve, release, refund, and fee.

Query parameters

FieldTypeDescription
pageinteger
default: 1
1-indexed page (min 1).
per_pageinteger
default: 50
Entries per page (min 1, max 200).
curl "https://api.trydatapoint.com/data-labelling/v1/billing/history?page=1&per_page=50" \
  -H "X-API-Key: $DATAPOINT_KEY"

Response (200 OK)

{
  "entries": [
    {
      "entry_type": "purchase",
      "amount_credits": 5000,
      "job_id": null,
      "description": "Credit purchase (5000 credits, $50.00 paid, order=ord_...)",
      "created_at": "2026-04-15 08:21:04.123456+00:00"
    },
    {
      "entry_type": "reserve",
      "amount_credits": -150,
      "job_id": "f3a7...",
      "description": "Reserved for job",
      "created_at": "2026-04-15 09:10:22.123456+00:00"
    },
    {
      "entry_type": "release",
      "amount_credits": 15,
      "job_id": "f3a7...",
      "description": "Released unused credits (15 credits)",
      "created_at": "2026-04-15 11:42:17.123456+00:00"
    },
    {
      "entry_type": "planner_fee",
      "amount_credits": -50,
      "job_id": null,
      "description": "Survey planner fee",
      "created_at": "2026-04-15 12:01:09.123456+00:00"
    }
  ],
  "total": 4,
  "page": 1,
  "per_page": 50
}

Ledger entry

FieldTypeDescription
entry_type"purchase" | "grant" | "reserve" | "release" | "refund" | "planner_fee" | "message_fee"Kind of credit movement. `purchase`, `grant`, and `release` add to `available`; `reserve` and `refund` are debits (a `refund` reverses the credits of the original purchase, so its `amount_credits` is negative); `planner_fee` and `message_fee` are signed (a charge is negative, and a same-type refund is positive).
amount_creditsintSigned integer credits. Credits to your balance are positive; debits are negative.
job_idstring | nullA stable job reference shared by that job's ledger entries — not the `job_id` returned by `POST /jobs`. Null for purchases, grants, and fees.
descriptionstring | nullHuman-readable note — a provider order reference or a fixed system string such as `Reserved for job` or `Released unused credits`.
created_atstringDatetime string: `'YYYY-MM-DD HH:MM:SS.ffffff+00:00'`.

Handling insufficient balance

When POST /jobs responds with 402, parse detail to show a clear top-up prompt. The global error envelope mirrors detail into a top-level message field; see the errors page for the full shape.

{
  "detail": {
    "message": "Insufficient balance",
    "needed_credits": 150,
    "available_credits": 75
  }
}

Direct the user through POST /billing/checkout, wait for the balance to update, and retry the job with the same request body. Job creation is idempotent on name: if the original request was already received, the retry returns that job's original fields (and omits the pricing breakdown, which is only present on a fresh creation) rather than reserving again.

Next

  • Errors: handling 402 and other status codes
  • Jobs: creating jobs that draw from your balance