# Route: user-based API integration

Use this route when the publisher chooses a custom API-powered earning interface. Implement the UI and API adapter in the publisher's actual repository, preserving its framework, authentication, wallet and deployment conventions. This is implementation guidance, not a pre-certified application or a replacement for signed reward callbacks. Official contracts below, including the published Client API OpenAPI 2.0.152 definitions, were checked on 2026-09-24; reopen the linked reference before implementing an optional endpoint or field.

## Continue the conversation, one decision at a time

Reuse the workspace, selected app, access and explicit business choices from the supplied setup workflow (`bitlabs setup guide`). Do not restart onboarding or ask the publisher to paste a user ID or token already discoverable. An API choice should lead to repository work, not another iframe example.

1. Inspect the existing earning screen, current-user source, application service layer and wallet. Identify the actual files to change. If demand is unresolved, use the shared chooser: **Surveys**, **Offers** and **Gaming**, allowing multiple selections and showing current selections. Reuse explicit choices. Follow the selected Games/Offers or Surveys contract below; verify its account and platform availability before promising it. Do not hide or silently drop an existing or requested selection. If a choice is unavailable for this route, explain the specific limitation and ask that one unresolved scope or route decision before changing app settings; do not silently switch integrations.
2. Reuse demand, currency name and units-per-USD answers. If missing, ask each separately with the shared suggestions. For a new currency default to whole units, scale 0 and BitLabs flooring; preserve an existing wallet or explicit decimal choice. Do not add routine precision/rounding questions or multiply returned user rewards by the conversion factor again.
3. Select client requests or an existing application backend based on the inspected architecture and the constraints below. Ask only if an unresolved hosting choice or required account activation blocks that decision. Implement compatible independent UI work while awaiting the answer.
4. Reuse the explicit callback/setup-versus-preview decision. Otherwise ask whether to set up S2S reward callbacks now or keep the integration preview-only. Complete the backend, registration and evidence stages for real rewards; an API list rendering successfully is not completion.

Keep API route state in ordinary integration notes. Do not invent an API recipe value in the strict helper manifest: its existing `iframe-node-sqlite-v1` recipe does not certify this API UI. Shared callback code may be reused only through a compatible reviewed wallet/host adapter.

## Identity and credential boundaries

Resolve the BitLabs UID automatically from the authenticated publisher account on every user-specific request. Reuse its persistent server-side opaque alias if needed. Never substitute `0`, the nil UUID, a simple numeric value or a shared placeholder. Do not change historical mappings. A project without any identity system may generate a cryptographically random, session-persisted demo UID; isolate its data from redeemable balances. A logged-out real user is not a demo user.

On logout or account change, immediately clear the previous user's lists, details, progress and links; abort in-flight requests and discard late responses using an identity/request generation check. Clear user-scoped query state before fetching the next account. A validation error must not leave the old account's earning view usable. Keep UID resolution identical to the signed callback's account mapping; never truncate an ID to fit the callback recipe's 65-character ASCII limit.

The inventory/history endpoints below use **`X-Api-Token` with the public App Token** and **`X-User-Id` with the resolved UID**. Survey click capabilities and privacy/admin operations have the separate contracts described below. Put the public token in designated application configuration. The Management API key remains in the authorized local setup CLI; the App Secret stays in the backend secret store for callback verification. Neither belongs in client code, API query strings, logs or model-visible input. A separate S2S API token is unnecessary for the basic inventory/history integration; do not confuse a publisher-side proxy with BitLabs' separate publisher/S2S APIs. The [Offers reference](https://developer.bitlabs.ai/reference/getoffersv2) and [Surveys reference](https://developer.bitlabs.ai/reference/getsurveysv2) identify their headers.

### Keep UID transport specific to the selected endpoint

For the inventory/history endpoints in this guide, send the resolved UID in the **`X-User-Id` request header** to `https://api.bitlabs.ai`, with the public App Token in `X-Api-Token`. Do not substitute the iframe's `?uid=...&token=...` opening parameters or add a duplicate UID to the request body/query. Apply this header contract only to endpoints that document it; optional user-token, privacy and publisher endpoints below have their own authorization contracts.

If the UI also opens an iframe, construct its `https://web.bitlabs.ai/` URL with `uid` and `token` query parameters as documented in the [iframe guide](https://developer.bitlabs.ai/docs/iframeweb-integration). Do not forward API headers to that navigation or to returned tracking links. Mobile SDKs take UID through their documented initialization argument. [Management API app discovery](https://developer.bitlabs.ai/docs/management-api-apps) instead uses the private `X-Api-Key` and workspace/app IDs; it does not select a reward user with `X-User-Id`.

## Request architecture

The [Games & Offers guide](https://developer.bitlabs.ai/docs/offer-api) permits client or server calls. For a browser-first implementation, call the documented HTTPS origin with the public headers, `Accept: application/json`, `credentials: "omit"` and the actual signed-in identity. Verify browser CORS behavior in staging; do not work around it with an unreviewed public proxy or `no-cors` catalog requests.

For a publisher backend, derive UID from its authenticated session, bind the configured app on the server, and use a fixed upstream origin/path allowlist. Do not expose an arbitrary URL-fetch endpoint. Forward real device context only through the documented fields and trusted ingress metadata; never use the server's IP/user agent as the user's, accept arbitrary forwarded-IP headers, or invent advertising IDs. Offers use `client_ip` and `client_user_agent`; `client_ip` is documented as IPv4. Do not invent an IPv4 address for an IPv6-only user; resolve the supported transport with BitLabs. The `User-Agent` header represents the user's device. Browsers supply that header themselves. Optional platform identifiers must reflect the actual device and permission state, including ATT for IDFA. [User-Based Offer API](https://developer.bitlabs.ai/docs/user-based-offer-api).

**Survey backend calls require prior BitLabs activation.** Both `client_ip` and `client_useragent` overrides require account-manager enablement; note the different spelling of `client_useragent` for surveys. If activation is unconfirmed, prepare the adapter but do not claim that a server request represents the user's eligibility. The [Survey API guide](https://developer.bitlabs.ai/docs/user-based-survey-api) and [endpoint reference](https://developer.bitlabs.ai/reference/getsurveysv2) describe this requirement.

Default user inventory/history responses to `Cache-Control: private, no-store`; exclude them from shared CDN, service-worker and local-storage caches. Do not substitute another user's cached inventory on errors. Use timeouts, cancellation and bounded response validation. Return safe error categories and a sanitized trace reference; omit upstream tracking URLs, identities and response bodies from operational logs.

## Implement the selected endpoints

All paths in this table use `https://api.bitlabs.ai` and the public headers above. Retain the response envelope and validate the endpoint-specific arrays; do not assume every `data` value is a flat list.

| Purpose | Request | Official contract |
| --- | --- | --- |
| Games/offers inventory and active progress | `GET /v2/client/offers` | [Get Offers](https://developer.bitlabs.ai/reference/getoffersv2) |
| Offer history | `GET /v2/client/offers/history` | [Get offer history](https://developer.bitlabs.ai/reference/getofferhistoryv2) |
| A previously interacted offer's details | `GET /v1/client/user/history/offers/{offerId}` | [History offer details](https://developer.bitlabs.ai/reference/getuserhistoryofferdetails) |
| Survey inventory | `GET /v2/client/surveys` | [Get Surveys](https://developer.bitlabs.ai/reference/getsurveysv2) |
| User activity/history | `GET /v1/client/user/history` | [Get user history](https://developer.bitlabs.ai/reference/getuserhistory) |

For offers, omitted `is_game` means both types, `true` games and `false` non-game offers. Preserve explicit false values in query serialization. `in_app=true` requests store-compatible inventory; omission uses the app setting. `limit` caps results and **is not page-based pagination**. `offer_ids` selects a separate by-ID mode that ignores catalog scoping such as `limit`, `is_game` and `devices`; do not promise otherwise or add invented cursors/page numbers. Preserve upstream order unless the publisher explicitly requires another presentation. [Get Offers](https://developer.bitlabs.ai/reference/getoffersv2).

Offer history can use `continue_playing`, `offer_ids` and `lang` where supported. Offer inventory has `data.offers` and `data.started_offers`; offer history has `data.offers`; survey inventory has `data.surveys`. User history is a heterogeneous `data` array. Preserve these shapes rather than flattening them into one list. User history accepts a `filter` list and is a latest-state view rather than a complete financial ledger. Use the relevant history detail endpoint and respect `has_details` when present. Do not interpret an item disappearing from inventory as proof of rejection or successful payment, or sum unrelated history amount fields into a wallet balance; some entries contain USD and others publisher currency. [Offer history](https://developer.bitlabs.ai/reference/getofferhistoryv2), [user history](https://developer.bitlabs.ai/reference/getuserhistory).

Callback tags are optional metadata, never account authorization or reward amounts. Use the selected endpoint's encoding rules and validate limits; do not attach sensitive data or add marketing tracking by default. The offers/surveys references limit tags to ten pairs with bounded key/value lengths and warn that users can modify them.

## Build the earning screens and tracking behavior

For offers, build a browsable list, a requirements/details view, progress/history and a working support path. Clearly show the required offer information before activation. These are BitLabs' display requirements, not optional decorative fields:

| Fields | What the user must see or be able to do |
| --- | --- |
| `anchor`, `description` | Identify the offer and understand the activity. |
| `requirements`, `things_to_know`, `disclaimer` | Read completion conditions, restrictions and terms. |
| `session_hours`, `events.ttc_minutes` | Understand the overall and per-event deadlines. |
| `pending_time` | Understand any delayed crediting period. |
| `events.name`, `events.points` | See each task and its user reward. |
| `click_url` | Start the offer through its supplied tracking link. |
| `support_url` | Reach the supplied support form, or the publisher's explicitly chosen support alternative. |

Apply the schema's reward display rules as well: use non-null `promotional_points` instead of an event's `points`, and non-null `total_points_promotional` instead of `total_points`. Zero is a value, not absence. Respect `display_type`: `none` is an ordinary amount, `up_to` is a maximum, and `commission` expresses points per the returned `commission_amount` in dollars. Dynamic events are not guaranteed flat payouts. Do not treat publisher `payout` as the user's reward or use `payable` alone to erase a nonzero user amount; official examples and that flag's prose are inconsistent. [Offer schema](https://developer.bitlabs.ai/reference/getoffersv2), [revenue models](https://developer.bitlabs.ai/docs/cpi-cpe-revenue-models-explained).

Honor mobile-only eligibility (`web_to_mobile`, its devices and verification requirements), expiry and the documented `is_sticky` field; the prose guide sometimes calls it `sticky`, while the schema/examples use `is_sticky`. Do not invent a verification API or bypass it. Keep relative completion deadlines, absolute expiration and reward-pending time distinct.

These requirements and allowed alternatives are specified in [Games & Offers API](https://developer.bitlabs.ai/docs/offer-api). Do not hide critical terms on mobile, invent missing deadlines, or start an offer whose necessary terms cannot be presented. Render returned text safely; do not inject remote HTML into the page without reviewed sanitization.

Use returned `click_url` exactly for a genuine user click. Validate it as an HTTPS navigation target, then retain its original bytes: do not encode the entire URL, rebuild its query, strip tracking parameters or replace it with a store URL. Use the documented `continue_url` for a started offer where available. Do not prefetch, health-check or crawl click links; those actions may start tracking. Links that open another tab should protect the opener.

POST to an offer's supplied `impression_url` when the offer actually becomes visible in the session. As an implementation safeguard, deduplicate by identity, impression URL and session; do not send impressions merely because JSON was fetched or a hidden card rendered. Do not forward app authorization headers, cookies or private credentials to tracking destinations. Prefer visibility-triggered browser delivery; a publisher backend must not expose an arbitrary tracking-URL proxy. If server forwarding is needed, review destination/redirect restrictions and enforce HTTPS, public-host, timeout and size limits without forwarding credentials. An opaque response or dispatch alone is not proof the tracker accepted the impression. An impression failure is distinct from catalog failure. [User-Based Offer API](https://developer.bitlabs.ai/docs/user-based-offer-api).

Use `started_offers` and event states for progress; expired or completed offers can disappear from that array, so keep history accessible. Respect `multiple_conversions_allowed` and its documented rule object; a first conversion is not necessarily the final one. A progress label is not wallet credit. [User-Based Offer API](https://developer.bitlabs.ai/docs/user-based-offer-api).

For surveys, use `sdk=CUSTOM` for this integration; `platform` and `os` are deprecated. Display the returned user `value`, duration (`loi`) and category. A survey `rating` is difficulty (1 hard, 5 easy), not a customer-review score. Preserve relevant `pii`/`recontact` disclosures. Separate loading, empty inventory, `restriction_reason`, expired inventory and request errors. Navigate to the returned `click_url` on user action and **refresh survey inventory after opening a survey** so the next selection has a new impression. Preserve the initial profiler/`start_bonus` flow when returned; do not manufacture regular surveys for a new user. [Survey API](https://developer.bitlabs.ai/docs/user-based-survey-api).

`restriction_reason` is an object: handle `not_verified`, `using_vpn`, `unsupported_country`, `banned`, `review` and `on_hold` distinctly. The surveys endpoint can return a VPN restriction with HTTP 200, rather than the 406 used elsewhere. An empty list with a restriction is not a transient load failure; do not repeatedly retry or expose internal fraud reasons. Account verification and survey backend activation are separate checks. [Survey schema](https://developer.bitlabs.ai/reference/getsurveysv2).

Use exact decimal handling for displayed user amounts; offer `events.points` and survey `value` are not publisher payout fields. Preserve existing currency formatting. Unexpected fractional values under an agreed whole-unit policy require a configuration check, not fabricated entitlements. Only signed callbacks and the authoritative wallet determine credited balances; neither clicks, API progress, `payout`/`cpi` fields nor client-side totals may award rewards.

For failures, retain a useful retry state without showing stale cross-user content. Handle invalid requests, authorization/configuration failure, applicable VPN/restriction responses, rate limits and transient failures separately. Respect `Retry-After` when returned and bound retries. Do not blindly repeat click creation, answers or other tracking mutations. A successful HTTP status with an invalid/application-error envelope is not a successful load.

## Keep optional survey qualification separate

The initial route should use the returned survey click link and BitLabs' hosted qualification flow. Only implement custom qualification if requested, after the basic API route works. The documented flow starts by POSTing the supplied click URL with its `s` session and `i` impression parameters, then uses `GET /v2/client/clicks/{clickId}` and `POST /v2/client/clicks/{clickId}` with the documented question/action objects. Those click operations explicitly require no App Token header in the current OpenAPI: the session/impression/click values carry the capability. Bind them to the publisher session, keep them private and allowlist the official origin/path before programmatic requests. Preserve server validation and allowed skip behavior; never auto-answer for the user. [Create click](https://developer.bitlabs.ai/reference/createclickv2), [Get click](https://developer.bitlabs.ai/reference/getclickv2), [Update click](https://developer.bitlabs.ai/reference/updateclickv2).

If custom qualification is explicitly selected, implement TEXT, SINGLE, MULTI, NUMBER and DATE controls using returned answer codes, `validation_info`, `can_skip` and duplicate-question context. Birthdate limits represent age. Submit `answer_question: {answers: ["..."]}`, `skip_question: true`, or `leave_survey: {reason: "..."}` using the documented enum. Handle allowed 422 validation codes without echoing answers or arbitrary upstream text. Update responses already contain the next state. Distinguish redirect types `survey`, `termination` and `start_bonus`; do not treat every redirect as a survey start. `rate_survey` is deprecated and performs no action. Treat even the GET opening URL as a tracking mutation: never cache, prefetch or automatically retry it.

## Profile and privacy scope

Do not add profile imports, new personal-data collection or advertising-ID collection automatically. Reuse legitimately collected publisher data only under an explicitly selected integration and its existing consent/privacy process. Imports use `POST /v1/publishers/users` with **`X-S2S-Token`**, require account-manager activation and allow only one bulk import at a time. The current schema requires a `users` array whose entries include `uid`, `birthdate`, `gender` and `questions`; the prose guide's old public-token example is inconsistent. Send the approved full updated profile, validate every `data.errors` entry and `success_count`, and never call mixed failures a successful import. [Import schema](https://developer.bitlabs.ai/reference/importusers).

Connect the publisher's existing privacy-request workflow to BitLabs, or record the unresolved integration explicitly. Publisher-side access requests use `POST /v1/publishers/users/{userId}/gdpr-requests` and status uses GET on that path with S2S authorization. Account-data deletion is `DELETE /v1/publishers/users/{userId}` and needs the user's verified, explicit deletion intent. A 202 accepts a request; it does not prove completion. Profile lookup is not a complete export. The alternative client `/v1/client/user/gdpr-requests` operations require a user **Bearer token**, not just the public App Token and UID; DELETE there also deletes user data, not an export request. Keep tokens and export links private. These operations need separately authorized access, not an extra credential request for basic inventory setup. [Privacy workflow](https://developer.bitlabs.ai/docs/user-privacy-requests-gdpr), [publisher request](https://developer.bitlabs.ai/reference/accessuser), [client request](https://developer.bitlabs.ai/reference/accessgdpruser), [client deletion](https://developer.bitlabs.ai/reference/deletegdpruser).

## CLI support and limits

Build the publisher's API adapter in its own service/UI layer with runtime identity and device context. CLI diagnostics are not an application runtime or endpoint-contract certification.

CLI **2.0.4** forces user client reads live, blocks local fallback and user-response persistence, and preserves explicit `--is-game=false`/`--in-app=false`. Verify the exact installed release before separately authorized diagnostics, supply actual user/device context and verify endpoint support. Do not use generic sync/local fallback as an earning feed. Older releases do not provide these safeguards.

Do not invoke the uncorrected optional generated click, advertising-ID, user-token, profile-import or GDPR commands as an onboarding implementation. Their known mismatches include click `survey_id` instead of query `s`/`i`, generic click action bodies, advertising `maid_type` instead of required `type`, auth `publisher_user_id` instead of `user_id`, missed import partial failures, and incorrect client GDPR auth/deletion descriptions. Avoid model-visible token outputs. Implement an explicitly selected optional flow against its official contract and validate it separately. Ordinary development uses synthetic responses without real credentials, impressions, clicks, uploads or privacy mutations.

## Complete callbacks and prove the integration

Follow [callbacks](../callbacks.md), the compatible [host adapter](../hosts/node-sqlite.md) and [verification](../verification.md). Reuse the existing wallet rather than creating a parallel balance. Resolve unknown reward bounds or reconciliation policy one question at a time. Prepare the exact endpoint/macros before human configuration. The human enters the App Secret directly into the reviewed backend's secret interface; preserve existing destinations and obtain the required authorization before live changes.

Track `backend_ready`, `secret_configured`, `callback_registered`, `debug_callback_verified` and `signed_test_verified` separately. The dashboard tester's debug delivery proves connectivity/signature/debug isolation, not real wallet crediting. Separately authorized non-debug staging callbacks must prove account mapping, exact amounts, duplicate handling and reconciliation. For a preview-only choice, mark only unperformed checks `NOT_RUN` and preserve existing configuration and verified evidence. Report the observed reward state and any verification still outstanding. A preview alone is not evidence of verified monetization.

Run the publisher build and meaningful synthetic tests for:

- Authenticated UID resolution, stable aliases/demo persistence, account switching, aborted/late responses and no cross-user cache reuse.
- Required offer fields, mobile/keyboard access, safe text and URL handling, whole/decimal/zero rewards, pending/expired/multi-conversion states and support access.
- `is_game=false`, omitted versus false options, catalog versus by-ID mode, real visibility impressions, deduplication, intact click URLs and survey refresh/new impression behavior.
- Empty/restricted inventory, malformed/oversized responses, timeouts, authorization failures, rate limits and bounded retry behavior without automatic click mutations.
- Promotion null versus zero, up-to/commission display, mobile-only targeting, restricted surveys and qualification validation/redirect state without automatic mutation retries.
- Explicit profile-import mixed failures and privacy acceptance/status/deletion boundaries, only when those flows are in scope; no new personal data or real privacy actions in fixtures.
- Signed callback authentication, duplicate/concurrent delivery, wallet atomicity, unknown-user rejection, debug isolation and reconciliation holds for each selected demand format.

Mock the API and tracking destinations in automated tests. Then, with authorized staging access, verify the actual browser transport, device context, selected demand, support path and callback ledger evidence. Save changed files, detailed results (`PASS`, `FAIL`, `NOT_RUN` or `HUMAN_CONFIRMED`) and non-secret evidence references in a sanitized setup report; distinguish mocked checks, observed staging and human production review. Keep the final chat reply concise and in plain language: what works, a verified preview/app link when available, whether real rewards are configured and verified, and the next action. Link the report instead of pasting the file inventory and technical test checklist. State any missing live checks plainly; do not hide failures or claim live readiness from CLI availability, a rendered list or HTTP 200 alone.
