API Reference
Reference for the Humanbased data API endpoints currently documented for external integrations.
Response format
Every endpoint wraps its payload in a StandardResponse envelope:
| Field | Type | Description |
|---|---|---|
data | object | array | null | The actual response payload (shape varies per endpoint). null on error. |
success | bool | true on 2xx, false on any 4xx/5xx. |
errorCode | int | 0 on success, otherwise an HTTP-derived code. |
errorMessage | string | "SUCCESS" on success, otherwise a short failure reason. |
Below, every endpoint shows the complete response body — envelope and all — so you can paste it straight into a client mock or contract test.
Paginated endpoints (e.g. /v1/live/pull) put their array inside data.items and the pagination cursors at data.next_cursor, data.has_more, data.count. Single-list endpoints (e.g. /v1/frontiers) put their array straight at data — no inner key, no data.data double-wrap.
Public endpoints
No authentication required.
List Frontiers
GET /v1/frontiers?status=onlineBrowse available data sources from production. Returns frontiers with task and submission counts.
| Param | Type | Required | Default | Description |
|---|---|---|---|---|
status | string | no | online | online for live sources, all to include historical |
Response:
{
"data": [
{
"domain_id": "8114254168500106625",
"title": "CEX Hot Wallet",
"status": "ONLINE",
"task_count": 31,
"total_submissions": 812878
}
],
"success": true,
"errorCode": 0,
"errorMessage": "SUCCESS"
}domain_id is the frontier's snowflake — pass it as the domain_id query param on /v1/frontiers/tasks to drill into the tasks.
List Frontier Tasks
GET /v1/frontiers/tasks?domain_id=<id>List tasks under a frontier with submission counts.
| Param | Type | Required | Description |
|---|---|---|---|
domain_id | string | yes | Frontier snowflake from /v1/frontiers |
Response:
{
"data": [
{
"task_id": "8495394906500108169",
"domain_id": "8114254168500106625",
"name": "Collect deposit address images from exchanges for week10.",
"task_type": "submission",
"status": "PAUSE",
"submission_count": 70944
}
],
"success": true,
"errorCode": 0,
"errorMessage": "SUCCESS"
}status is one of COLLECTING / PAUSE / COMPLETED. task_type reflects the contributor flow — typically submission for new data collection.
List Verticals (Simulated)
GET /v1/data/verticalsReturns all available simulated data verticals.
Response:
{
"data": [
{
"id": "afbb8950-4937-4549-a982-907b9c839baf",
"slug": "crypto_account_annotation",
"name": "Crypto Account Annotation",
"description": "Blockchain address entity classification across 35+ networks.",
"base_price_usd": 10.0
}
],
"success": true,
"errorCode": 0,
"errorMessage": "SUCCESS"
}base_price_usd is per-record pricing and varies per vertical (e.g. fashion annotation is 0.025, crypto account annotation is 10.0). Treat the example as illustrative — read the live value from this endpoint before billing.
Portal-authenticated endpoints
Preview Task Submissions
GET /v1/frontiers/tasks/preview?domain_id=<id>&task_id=<id>&limit=5Preview sample adopted submissions for a task. Requires JWT authentication (dashboard).
| Param | Type | Required | Default | Description |
|---|---|---|---|---|
domain_id | string | yes | — | Frontier snowflake |
task_id | string | yes | — | Task snowflake |
limit | int | no | 5 | Max items (max 20) |
Response:
{
"data": [
{
"submission_id": "123456789",
"data": { "address": "0x...", "chain": "ethereum" },
"quality_score": 0.97,
"quality_grade": "S",
"source": "codatta-app",
"created_at": "2026-03-28T10:00:00",
"audit_rating": 5,
"audit_reason": null
}
],
"success": true,
"errorCode": 0,
"errorMessage": "SUCCESS"
}Note: preview mirrors /v1/live/pull's shape — data is a structured object on both endpoints (the pre-2026-07-16 hotfix /v1/live/pull returned data as a JSON string; that was changed at the same time this section was written and both endpoints now behave identically here).
Production data endpoints
All require Authorization: Bearer hb_live_sk_... header. These endpoints read from the production database via frontier-based subscriptions.
Pull Live Data
GET /v1/live/pull?subscription_id=<id>&limit=50
GET /v1/live/pull?subscription_id=<id>&limit=50&from=beginning&cursor=<next_cursor>&task_ids=<csv>&campaign_ids=<csv>Read the submissions delivered to a frontier subscription, at most 200 per call. The endpoint has two modes.
Incremental pull (default — no from). The server keeps a read position per subscription. Each call returns the records not yet delivered to you and advances that position. Send no cursor; call again while has_more is true. When has_more is false you are caught up — call again later to pick up new records. An incremental page may carry a next_cursor; you never need to send it back.
Full pull (from=beginning). Returns every delivered record from the first, including records above the read position. The read position does not move, so a full pull never disturbs your incremental pulls. Page with cursor=<next_cursor> — an opaque hbrp_… token. Stop when has_more is false. Do not stop on next_cursor alone — has_more is the authoritative signal.
Both modes return at most limit records per call (default 50, maximum 200).
# Incremental pull — no cursor; repeat while has_more is true
curl -G https://api.humanbased.ai/v1/live/pull \
-H "Authorization: Bearer YOUR_API_KEY" \
-d subscription_id=YOUR_SUBSCRIPTION_ID \
-d limit=200
# Full pull — first page
curl -G https://api.humanbased.ai/v1/live/pull \
-H "Authorization: Bearer YOUR_API_KEY" \
-d subscription_id=YOUR_SUBSCRIPTION_ID \
-d from=beginning \
-d limit=200
# Full pull — next page: pass the previous response's next_cursor
curl -G https://api.humanbased.ai/v1/live/pull \
-H "Authorization: Bearer YOUR_API_KEY" \
-d subscription_id=YOUR_SUBSCRIPTION_ID \
-d from=beginning \
-d cursor=NEXT_CURSOR_FROM_PREVIOUS_PAGE \
-d limit=200| Param | Type | Required | Default | Description |
|---|---|---|---|---|
subscription_id | string | yes | — | Frontier subscription ID |
limit | int | no | 50 | Records per page. Maximum 200; a larger value is rejected with 422. |
from | string | no | — | The only accepted value is beginning, which selects a full pull. Omit it for an incremental pull. Any other value is rejected with 400. |
cursor | string | no | — | Opaque paging token copied from the previous page's next_cursor (hbrp_…). Only meaningful with from=beginning; an incremental pull needs no cursor. |
task_ids | string (CSV) | no | — | Comma-separated task IDs. Narrows the result to submissions whose task_id is in the list. Full pull only — on an incremental pull the request is rejected with 400 filters_need_reread. Empty value (task_ids=) or all-empty tokens (,,,) is treated as "no filter". Capped at 200 IDs. |
campaign_ids | string (CSV) | no | — | Comma-separated campaign IDs. Narrows the result to submissions whose campaign_id is in the list. Same full-pull-only, empty-value and 200-cap rules as task_ids. |
Both task_ids and campaign_ids intersect with the subscription's own scope — they narrow, never widen. A filter value that isn't part of your subscription returns an empty page rather than a 403.
Note for integrations written before 2026-09-08: cursor=0 used to mean "from the start". It is now an ordinary incremental pull — it returns the records after the subscription's read position, not the whole history. Use from=beginning for a full pull.
Response (a full-pull page is shown; an incremental page has the same shape):
{
"data": {
"items": [
{
"submission_id": "2026050121475400102454",
"task_id": "9573999127600106018",
"campaign_id": "8114254168500106625",
"template_id": "AIRDROP_CEX_HOT_WALLET_WITHDRAW",
"data": {
"coin": "BSC-USDT",
"type": "withdrawal",
"amount": "29",
"address": "0xe94e…",
"network": "BNB",
"tx_hash": "0x52c5…",
"exchange_name": "Binance",
"transaction_date": "2026-04-03"
},
"quality_score": 0.97,
"quality_grade": "S",
"unit_price_usd": 0.0,
"source": "mobile",
"created_at": "2026-05-01T21:47:55+00:00"
}
],
"next_cursor": "hbrp_MTIzNDU2Nzg5",
"has_more": true,
"count": 50
},
"success": true,
"errorCode": 0,
"errorMessage": "SUCCESS"
}| Field | Type | Notes |
|---|---|---|
submission_id | string | Snowflake. Unique per submission — the key to dedupe on. |
task_id | string | Snowflake. Same task can appear across many submissions. |
campaign_id | string | Snowflake. The campaign (a.k.a. frontier — same identifier space) this submission belongs to. Renamed from frontier_id in the 2026-07-16 hotfix so the response field name matches the DB column. |
template_id | string | The template_id of the form this submission was collected against. Empty string for rows collected before templateId was tracked; use List Subscription Templates to enumerate what your subscription can deliver. |
data | object | Structured payload — the fields the annotator filled in. Shape is template-specific (see below). Prior to the 2026-07-16 hotfix this was a JSON-encoded string and included taskId / templateId / device / channel / repeat_attrs as sibling keys; those siblings have been lifted to top-level fields or dropped. |
quality_score | float | 0.0 – 1.0. Adoption threshold is set per-subscription. |
quality_grade | string | S / A / B / C. Coarse band derived from quality_score. |
unit_price_usd | float | Unit value recorded on the delivery row; 0.0 when none is set. |
source | string | web / mobile / import / codatta-app. How the contributor submitted. |
created_at | string | ISO-8601 (UTC). The contributor's submission time. Pages are ordered by internal delivery order, not by created_at, so an incremental pull can return records whose created_at is older than ones you already hold. Dedupe on submission_id, not on time. |
The data object's shape is template-specific — its fields follow whichever template_id the task is bound to. Common templates include AIRDROP_CEX_HOT_WALLET_WITHDRAW, AIRDROP_CEX_HOT_WALLET_DEPOSIT, CEX_HOT_WALLET. Treat unknown fields as opaque; new fields can appear without breaking the envelope. Fetch the field contract for a given template_id via GET /v1/live/task/template?template_id=….
Migration note (2026-07-16): if you were calling this endpoint before this date:
dataused to be a JSON-encoded string; it's now an object. Drop theJSON.parse()step.template_idused to live inside that string (astemplateId); it's now a top-level field.frontier_idwas renamed tocampaign_id(same value, name matches the DB column now). Thefrontier_idsquery param — added in an earlier draft of this hotfix — is likewisecampaign_ids.- The
device,channel, andrepeat_attrssibling fields inside the old string are no longer surfaced by this endpoint; useGET /v1/live/task/templatefor the template'srepeat_attrs, and treatsourceas the modern equivalent ofdevice.
Adopt Live Submission
POST /v1/live/items/adoptRecord adopt feedback for a production submission.
Request body:
{ "submission_id": "123456789", "subscription_id": "uuid" }Response:
{
"data": { "ok": true, "submission_id": "123456789", "feedback": "adopt" },
"success": true,
"errorCode": 0,
"errorMessage": "SUCCESS"
}Dispute Live Submission
POST /v1/live/items/disputeRecord dispute feedback with optional reason.
Request body:
{ "submission_id": "123456789", "subscription_id": "uuid", "reason": "Incorrect label" }Response:
{
"data": { "ok": true, "submission_id": "123456789", "feedback": "dispute" },
"success": true,
"errorCode": 0,
"errorMessage": "SUCCESS"
}Sandbox data endpoints
All require Authorization: Bearer hb_live_sk_... header. These endpoints work with simulated vertical-based subscriptions.
Pull Data
GET /v1/data/pull?subscription_id=<id>&limit=50Pull pending delivery items for a simulated subscription.
| Param | Type | Required | Default | Description |
|---|---|---|---|---|
subscription_id | string | yes | — | The subscription to pull from |
limit | int | no | 50 | Max items (max 200) |
Response:
{
"data": {
"items": [
{
"id": "uuid",
"vertical_id": "uuid",
"topic_id": "uuid",
"payload": { "address": "0x...", "chain": "ethereum", "label": "Uniswap V3" },
"quality_score": 0.94,
"quality_method": "consensus",
"validator_count": 5,
"consensus_ratio": 0.92,
"unit_price_usd": 0.032,
"created_at": "2026-03-25T10:00:00Z"
}
],
"count": 1,
"next_cursor": 50
},
"success": true,
"errorCode": 0,
"errorMessage": "SUCCESS"
}next_cursor is an integer offset (not a snowflake like /v1/live/pull). Pass it back as the cursor query param to fetch the next page; null means no more items.
Adopt Item
POST /v1/data/items/adoptMark a delivery item as adopted (accepted). You are charged unit_price_usd.
Request body:
{ "item_id": "uuid" }Response:
{
"data": { "ok": true, "item_id": "uuid", "status": "adopted" },
"success": true,
"errorCode": 0,
"errorMessage": "SUCCESS"
}Dispute Item
POST /v1/data/items/disputeMark a delivery item as disputed (rejected). No charge — item is re-reviewed.
Request body:
{ "item_id": "uuid" }Response:
{
"data": { "ok": true, "item_id": "uuid", "status": "disputed" },
"success": true,
"errorCode": 0,
"errorMessage": "SUCCESS"
}Verify API Key
GET /v1/auth/verifyKeyValidate an API key without burning a pull-quota call. Returns the key's metadata (org binding, name, allowed subscription IDs).
Response:
{
"data": {
"key_id": "uuid",
"org_id": "snowflake",
"key_name": "humanbased-production-ingestion",
"subscription_ids": ["uuid", "uuid"]
},
"success": true,
"errorCode": 0,
"errorMessage": "SUCCESS"
}