User-bound offer delivery API
Use this flow for OFFERWALL and OFFER_API slots. It is bound to a publisher placement, user, environment, platform identity, and optional slot. A session token expires after 15 minutes. Use HTTPS and treat the token and any hosted URL containing it as sensitive.
1. Create a session
POST /sdk/v2/sessions accepts:
| Field | Requirement |
|---|---|
placementId | Existing verified placement |
adSlotId | Optional named slot to bind the session |
externalUserId | Stable publisher user ID, 1–128 characters |
platform | android, ios, or web, matching the placement |
sdkVersion | Supported semantic version at or above the placement minimum |
packageBundleOrDomain | Exact package, bundle, or hostname registered on the placement |
environment | sandbox, development, or production |
consent | GRANTED, DENIED, UNKNOWN, or NOT_REQUIRED |
attStatus | Optional app tracking transparency state |
POST /sdk/v2/sessions
Content-Type: application/json
{
"placementId": "PLACEMENT_ID",
"adSlotId": "SLOT_ID",
"externalUserId": "user-123",
"platform": "android",
"sdkVersion": "2.0.0",
"packageBundleOrDomain": "com.example.puzzlequest",
"environment": "development",
"consent": "GRANTED"
}
A 201 response includes sessionId, token, issuedAt, expiresAt, contractVersion, available adSlots, capability flags, and possibly a hostedOfferwallUrl. Production sessions require a released placement. Mismatched identity, platform, SDK version, or required consent are rejected.
The session creation rate limit is 60 requests per minute per placement/IP. It does not use a publisher dashboard bearer token. Protect the resulting bearer token as a user credential; if a session is compromised, revoke it and issue a new one. The configuration endpoint returns the same session shape and checks that the path sessionId matches the bearer token.
2. Use the session
Send Authorization: Bearer SESSION_TOKEN on subsequent SDK requests:
| Method | Path | Successful response | Purpose |
|---|---|---|---|
| GET | /sdk/v2/sessions/{sessionId}/configuration | 200 session | Read current configuration |
| POST | /sdk/v2/sessions/{sessionId}/refresh | 200 replacement session | Refresh before expiry; requires Idempotency-Key |
| POST | /sdk/v2/sessions/{sessionId}/revoke | 204 | Revoke; requires Idempotency-Key |
| GET | /sdk/v2/ad-slots/{adSlotId}/offers | 200 eligible offer page | User-specific inventory; private cache up to 60 seconds |
| GET | /sdk/v2/offers/{offerId} | 200 offer | Offer details for this session |
| POST | /sdk/v2/offers/{offerId}/impressions | 201 receipt | Record a shown impression; requires Idempotency-Key |
| POST | /sdk/v2/offers/{offerId}/launch | 201 signed launch | Reserve and return the authorized destination; requires Idempotency-Key |
| GET | /sdk/v2/reward-status | 200 status collection | Current session user's progress |
| GET | /sdk/v2/reward-status/{offerId} | 200 status | One offer's progress |
GET /sdk/v2/ad-slots/SLOT_ID/offers
Authorization: Bearer SESSION_TOKEN
The returned offer list is filtered by session identity, slot, country, platform, campaign status, caps, and suitability. Handle no-fill as an ordinary empty state. For a launch, use the server-provided signed destination and preserve its click ID. Never construct a destination from the offer object or accept a client-supplied reward amount.
GET /ad-slots/{adSlotId}/offers accepts optional country, stateCode, deviceType, deviceManufacturer, osVersion, browser, browserVersion, cursor, and limit query parameters. The response includes items, page.nextCursor, page.limit, a 60-second cache, contractVersion, and context with placement/slot and currency information. The service still validates these values against the authenticated session and user. Use the cursor returned by the prior page; do not construct one.
Each item contains a campaignId, public title/copy, requirements and disclaimer, image URLs, reward goals, totalReward, currency, attribution window, creative revision ID, and availability. A goal has an external id, name, reward, pending minutes, and completion window. Display the campaign's requirements and any payment condition before launch. Item presence is not a guarantee that a later launch will pass caps and budget checks.
{
"items": [{
"campaignId": "CAMPAIGN_ID",
"title": "Complete the verified action",
"requirements": "Complete the stated steps",
"goals": [{"id": "qualified_purchase", "name": "Verified purchase", "reward": 50, "pendingMinutes": 0}],
"totalReward": 50,
"availability": {"status": "AVAILABLE", "reservationRequired": true}
}],
"page": {"nextCursor": null, "limit": 20},
"cache": {"maxAgeSeconds": 60},
"contractVersion": "2.0.0"
}
For a custom display, record an impression when the offer is actually shown. POST /offers/{offerId}/impressions with Idempotency-Key returns impressionId, recordedAt, expiresAt, and duplicate. The impression expires after ten minutes. Then call POST /offers/{offerId}/launch with a different Idempotency-Key and JSON {"impressionId":"IMPRESSION_ID"}. The launch requires that impression and returns sessionId, traceId, launchUrl, and expiresAt; the signed launch URL expires after five minutes. The launch reserves eligibility, cap, and budget capacity. Repeating a timed-out launch with the same idempotency key returns its existing session rather than reserving another one.
3. Support and reward verification
GET /sdk/v2/reward-status returns items, totals (pendingMinorUnits, earnedMinorUnits, reversedMinorUnits), generatedAt, and contractVersion. GET /sdk/v2/reward-status/{offerId} filters the same shape for one offer or session. This is a UI progress view; final publisher credit still comes from the signed reward callback.
GET /sdk/v2/support returns the signed-in session user's cases as {items,count}. POST /sdk/v2/support requires Idempotency-Key and JSON sessionId, reasonCode (3–64 uppercase letters, digits, or underscores), and message (10–2,000 characters). It returns 201 with the created or existing case. The offer session must belong to the same user and placement. POST /sdk/v2/support/{supportCaseId}/evidence accepts multipart field evidence and Idempotency-Key only when development upload capability is enabled; it rejects production uploads. The upload limit is 5 MiB. Do not show an upload control in a production experience based on this endpoint.
SDK offer reads are limited to 120 requests per minute per token and writes to 60 per minute per token. A 429 may carry Retry-After. Cache offer reads only for the response's short private window and refresh after a status or session change.
On 401 sdk_session_expired, create a new session for the same user and placement rather than replaying the expired token. See API errors.