The same endpoint supports incremental delivery and replay of an already-readable range.
GET /v1/live/pull
Authorization: Bearer <API_KEY>For a guided first request, see Get your first authorized data.
Hosts and authentication
| Environment | API origin |
|---|---|
| Staging | https://api.staging.humanbased.ai |
| Production | https://api.humanbased.ai |
Use a full organizationThe Developer-side unit that holds Campaigns, data access, and funds API key issued for the selected environment. Pass it in the Authorization header with the exact Bearer prefix. Do not include credentials in the query string.
The key must be active, unexpired, permit data delivery, and cover the requested subscription. The subscription must belong to the key's organization and be active. A browser session, CLI sign-in session, masked key, or CampaignA developer's data-production project and its settings ID is not a replacement.
Authentication does not expand the data's usage terms. Confirm permitted use separately, including restrictions on sharing, training, retention, or redistribution that apply to the data.
Reading modes and recovery
Choose the mode explicitly before making a request.
| Replay | Incremental delivery | |
|---|---|---|
| Request | Include from=beginning. | Omit from. |
| Purpose | Read or reconstruct the subscription's readable range. | Obtain records beyond the shared reading position. |
| Pagination | Return the latest next_cursor as cursor; preserve mode and filters. | Repeat the request while has_more is true; the server holds the position. |
| Shared position | Does not move it. | Advances it for the whole subscription, including other keys and consumers. |
| Metering | Creates no new metered pull charge; receipt is null. | May charge for newly delivered records according to the subscription. |
| Filters | Optional task and campaign filters narrow the permitted range. | Task and campaign filters are rejected. |
For zero-priced subscriptions, replay can include eligible delivered records beyond the incremental reading position. For priced subscriptions, replay stays within the previously acquired range. A full pull means this readable scope; it does not bypass payment or grant access to all campaign submissionsThe record created when work is submitted.
Replay is suitable for recovering a lost local copy. Retrying it does not advance the feed. The endpoint does not promise a frozen snapshot: new deliveries or corrections may affect what is readable over time.
Incremental delivery is stateful even though it uses HTTP GET. Save each complete response before requesting more. If a response is lost, the server may already have advanced the shared position. Another incremental request can move on to the next page. Recover by replaying the acquired range and reconciling records against your local copy. For campaign submissions, use submission_id to identify repeat records; do not use timestamps as cursors.
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
subscription_id | string | Yes | The active subscription to read. It is not a Campaign ID. |
limit | integer | No | Maximum records per page: 1–200. Default: 50. |
from | string | No | Set to beginning for replay. Omit for incremental delivery. No other value is supported. |
cursor | string | No | For replay, use the previous response's opaque next_cursor. Omit on the first page. |
task_ids | string | No | Comma-separated task IDs; replay only. Narrows the subscription scope. |
campaign_ids | string | No | Comma-separated campaign IDs; replay only. Narrows the subscription scope. |
The two filters are applied together when both are present. Each list accepts up to 200 IDs; additional entries are truncated by the current implementation, so split larger requests yourself. Whitespace is trimmed and empty entries are ignored. An entirely empty filter means no filter, not “return nothing”. Validate IDs before sending them.
A filter outside the subscription's scope normally returns an empty page; it never expands authorization. Preserve the same filters on every replay page. Treat cursors as opaque, and never generate one from a TaskA specific item a contributor claims or enters ID, Submission ID, or timestamp.
Example: replay one page
Set HB_API_KEY securely and set HB_SUBSCRIPTION_ID to your own production subscription before running this request:
curl --silent --show-error --get \
'https://api.humanbased.ai/v1/live/pull' \
--header "Authorization: Bearer $HB_API_KEY" \
--data-urlencode "subscription_id=$HB_SUBSCRIPTION_ID" \
--data-urlencode 'from=beginning' \
--data-urlencode 'limit=5'The response below illustrates the wire shape. The identifiers, values, and delivered fields are examples, not downloadable sample data. data.items[].data varies with the delivery's output schema.
{
"data": {
"items": [
{
"submission_id": "784210006031",
"task_id": "784210005001",
"campaign_id": "784210001001",
"template_id": "784210002001",
"data": {
"case_id": "returns-014",
"decision": "escalate",
"reason": "The supplied evidence does not establish an exception."
},
"quality_score": 0.85,
"quality_grade": "A",
"unit_price_usd": 0.02,
"source": "web",
"created_at": "2026-10-01T09:30:00+00:00"
}
],
"next_cursor": null,
"has_more": false,
"count": 1,
"receipt": null
},
"success": true,
"errorCode": 0,
"errorMessage": "SUCCESS"
}The item can have a nonzero price while replay's receipt is null. The price describes the subscription's resolved per-record price; it is not proof that this request charged that amount.
Success response
The HTTP response uses a standard envelope. Require success: true before treating data as a successful page.
| Field | Type | Meaning |
|---|---|---|
success | boolean | Whether the operation succeeded. |
errorCode | integer | 0 on success. |
errorMessage | string | SUCCESS on success; diagnostic text on failure. |
data.items | array | The returned records. An empty array is a valid result. |
data.count | integer | Number of records on this page. |
data.has_more | boolean | Whether another page was available when this page was read. |
data.next_cursor | string or null | Continuation marker. In replay mode, pass it back unchanged while has_more is true. |
data.receipt | object or null | Metering result for a priced incremental read; null for replay and reads without a charge. |
When a receipt is present, it contains usage_event_id, amount_usd, quantity, and balance_available_usd. Save the receipt with its response. Usage and reconciliation shows how to match it against the organization's usage list.
Record fields
| Field | Type | Meaning |
|---|---|---|
submission_id | string | Source submission identifier; can be empty when unavailable. |
task_id | string | Source task identifier; can be empty. |
campaign_id | string | Source campaign identifier; can be empty. |
template_id | string | Template identifier; empty means unknown. |
data | JSON value | Delivered content after any configured output mapping. Do not parse it as a JSON string a second time. |
quality_score | number | Score mapped from the recorded grade; 0 when no recognized grade is present. |
quality_grade | string | S, A, B, C, or D; empty when no recognized grade is present. |
unit_price_usd | number | Resolved per-record price for this subscription. It is not a payment receipt. |
source | string | Source label; can be empty. |
created_at | string | Original submission timestamp; can be empty. |
Known grades map to scores as follows: S → 0.97, A → 0.85, B → 0.70, C → 0.50, D → 0.30. These are grade mappings, not calibrated probabilities or measurements of downstream model performance.
Records are paged in delivery order. An older created_at in a later page does not by itself indicate a duplicate. IDs are strings; preserve them without numeric conversion. Preserve the source precision of arbitrary numeric values inside data when selecting a JSON parser.
Get the field schema for data
The fields inside each record's data depend on its template_id. Two reads return that contract, so you can decode records without guessing:
GET /v1/live/task/templates?subscription_id=<SUBSCRIPTION_ID>
GET /v1/live/task/template?template_id=<TEMPLATE_ID>
Authorization: Bearer <API_KEY>The plural form lists every template the subscription's Campaign is configured to deliver, so you can cache them before any record arrives. It returns subscription_id, count, and templates; templates is empty when none is configured. The singular form looks up one template_id met in a record and is not limited to one subscription. Both need a Key with data access: a Key made only for Campaign configuration gets 403. On the plural form, a subscription outside the Key's selected scope also gets 403, and one your organization does not have gets 404.
| Field | Type | Meaning |
|---|---|---|
template_id | string | Matches template_id in pull records |
name | string | Template name |
description | string | What the template collects |
kind | string | Template kind, form for form-based work |
fields | array | One entry per field in data: name, type, required, description, and enum when the value comes from a fixed set |
A missing template_id or subscription_id is refused by request validation with 422 and a message naming the parameter, without an errorCodeName. A template_id that is present but empty returns 400 with TEMPLATE_ID_REQUIRED, and an unknown template returns 404 with TEMPLATE_NOT_FOUND.
Usage and reconciliation
GET /v1/live/usage
Authorization: Bearer <API_KEY>This endpoint lists the usage events your organization has been charged for, newest first. Each priced incremental pull that returns records creates one event, and that event's usage_event_id is the one in the pull's receipt. Reading the list charges nothing and moves no reading position.
Use the same host and Key as GET /v1/live/pull; the Key needs data access. The list covers the whole organization in that environment, including subscriptions outside the Key's own scope. It takes no subscription_id.
| Parameter | Type | Required | Description |
|---|---|---|---|
limit | integer | No | Number of events to return. Default: 50. Maximum: 200; a larger value answers 422. |
The endpoint has no cursor. It returns the newest events and nothing older.
| Field | Type | Meaning |
|---|---|---|
data.items | array | The usage events, newest first. |
data.count | integer | Number of events in items. |
usage_event_id | string | The event's identifier. Equal to receipt.usage_event_id of the pull that was charged. |
subscription_id | string | The subscription the pull ran under. |
dataset_id | string or null | The dataset the charge is attributed to; null when the price did not come from a dataset. |
vertical_id | string or null | The subscription's vertical; null when it has none. |
quantity | integer | Records served by that pull. |
unit_price_usd | number | Price per record at the time of the pull. |
amount_usd | number | Amount charged: unit_price_usd times quantity, rounded to six decimal places. |
currency | string | USD. |
occurred_at | string | When the charge was recorded. |
The values below are illustrations. A pull response saved with this receipt:
{
"usage_event_id": "784210009001",
"amount_usd": 0.1,
"quantity": 5,
"balance_available_usd": 49.9
}matches this event in the usage list:
{
"data": {
"items": [
{
"usage_event_id": "784210009001",
"subscription_id": "784210003001",
"dataset_id": null,
"vertical_id": "784210004001",
"quantity": 5,
"unit_price_usd": 0.02,
"amount_usd": 0.1,
"currency": "USD",
"occurred_at": "2026-10-01T09:31:00+00:00"
}
],
"count": 1
},
"success": true,
"errorCode": 0,
"errorMessage": "SUCCESS"
}To reconcile, look up each saved receipt's usage_event_id in data.items, then compare quantity and amount_usd.
| You find | What it means | What to do now |
|---|---|---|
An event with the receipt's usage_event_id | The charge is recorded once. | Check that quantity and amount_usd equal the receipt's. |
No such event, and count equals your limit | The event may be older than the events you asked for. | Request again with a larger limit, up to 200. |
| No such event among the 200 newest | The event is older than this endpoint returns. | Keep the saved receipt as your record, and review the period's charges on the organization's Billing page. |
| An event with no saved receipt | A pull was charged and its response was not saved, for example because it was lost in transit. | Recover the records by replay, as described in Reading modes and recovery. |
A saved response with receipt: null | The read was a replay, or it charged nothing. | Nothing to reconcile. |
Rate limits
Each endpoint allows a set number of requests per API Key, counted separately for each endpoint. The default is 100 requests per minute. Every response says where you stand:
| Header | Meaning |
|---|---|
X-RateLimit-Limit | Requests allowed in the current window |
X-RateLimit-Remaining | Requests left in that window |
Retry-After | On a 429, seconds to wait before the next request |
Over the limit, the gateway answers 429 with errorMessage Too many requests and no errorCodeName. Wait Retry-After seconds, then continue more slowly. A paging loop should pause when X-RateLimit-Remaining reaches 0.
Errors
Use the HTTP status and envelope together. errorCode is numeric. errorCodeName may be present for some structured errors but is not present for every failure. Some detailed errors are JSON serialized inside errorMessage; clients must not assume one universal named-error shape.
{
"data": null,
"success": false,
"errorCode": 401,
"errorMessage": "Missing API key"
}A data-API refusal that carries detail puts it in errorMessage as a JSON string, and errorCodeName is absent. For example, a priced read with too little balance:
{
"data": null,
"success": false,
"errorCode": 402,
"errorMessage": "{\"error\": \"insufficient_balance\", \"available_usd\": 0.0, \"required_usd\": 0.02}"
}When errorMessage starts with {, parse it and branch on its error field: invalid_cursor and filters_need_reread (400), insufficient_balance and entitlement_required (402, the second with dataset_id), or pull_contended and billing_unavailable (503).
| HTTP status | Cause or example | Recovery |
|---|---|---|
400 | Unsupported from, inactive subscription, invalid replay cursor, or filters on incremental mode (filters_need_reread). | Correct the parameter or subscription state. Use a cursor returned by this endpoint. |
401 | Missing, malformed, unknown, revoked, or expired key. | Correct the full Bearer credential or obtain a replacement through an administrator. |
402 | insufficient_balance on a priced incremental read. | Have the responsible person check available billing funds. |
402 | entitlement_required for a price-on-application source. | No self-serve purchase exists for these sources yet, and adding funds does not change that. Contact support with the dataset_id from the response. |
403 | No data-delivery access or the subscription is outside the key's scope. | Correct the authorized key or subscription scope. |
404 | Subscription not found for the caller. | Verify organization, environment, and ID. |
422 | Missing required parameter or an invalid limit. | Use the query contract above. |
429 | Too many requests to this endpoint with this Key. | Wait Retry-After seconds; see Rate limits. |
503 | Pricing/balance/billing unavailable, or concurrent pull contention (pull_contended). | Retry after a short delay; apply the reading mode's recovery rules. |
500 or an unfamiliar response | Unexpected service failure or contract mismatch. | Preserve a sanitized diagnostic and contact support; do not report success. |
Honor server-provided retry guidance, including Retry-After when present. Keep retries bounded and avoid a tight polling loop.
Delivery and completion boundaries
Campaign records must reach the eligible delivery set. An accepted review alone is insufficient: the campaign delivery path also waits for the reward to be awarded and for the delivery record to be produced. Subscription scope and quality filters further limit the result. A campaign's internal sink output is not automatically the same as this endpoint's delivered record.
This is a synchronous response, not an export job. There is no operation ID to poll. A successful page proves only that the included records were returned. Reaching has_more: false completes the current traversal; it does not establish immutable versioning, a license grant, downstream model quality, or permanent availability of every returned record.
For support, send hello@humanbased.ai the environment, request time, HTTP status, and sanitized error text. Exclude API keys and private payloads. For safe agent execution, see Use an agent with Humanbased.