TANKAAmbassadorDevelopers
TANKA AMBASSADOR / API v1

Attribution and Postback
Server integration guide

A guide for developers integrating with this service. It covers the data exchanged, authentication and API behavior from shared links through signup and the first successful Link.

BASE URLhttps://ambassador.tanka.aiproduction

Integration overview#

A qualified user has completed signup and then successfully completed a Link of any type for the first time.

Free users are eligible; payment is not a requirement. The reward rule effective at the reported first Link completion time applies. Each user generates a referral reward only once.

  1. 01Share links and retain attribution

    An administrator registers the URL and referral code issued by TANKA. The sender stores the touchpoint and attribution, and can look up historical mappings when needed.

  2. 02Send user states

    Send the complete user state after signup, the first successful Link, attribution resolution or a correction.

  3. 03Verify signatures, calculate rewards and reconcile

    Received events are persisted and processed asynchronously, then reconciled against a complete snapshot of the authoritative data.

There are two server integration endpoints: GET /integration/v1/referral-links/{code} for referral lookup and POST /integration/v1/events for sending user states.

Credentials and authentication#

Postback: Key ID + Secret

The Key ID issued by the receiver identifies the key. Store the Secret on the server and use it to calculate each request's HMAC signature. Send only the signature; never include the Secret itself in headers or the body.

Lookup: dedicated Bearer Token

Referral lookup uses a dedicated Token. Scheduled retrieval from the backfill API uses separate credentials issued by that API's provider.

Sender environment variables · example
AMBASSADOR_API_BASE_URL=https://ambassador.tanka.ai
TANKA_EVENT_KEY_ID=<issued Key ID>
TANKA_EVENT_SECRET=<issued signing Secret>
TANKA_LINK_LOOKUP_TOKEN=<issued lookup Token>

Use credentials dedicated to this integration. Supabase and Resend administrative keys are not needed. Keep production Secrets out of documentation, frontend code, shared links and browsers.

When rotating keys, switch the sender to the new Key ID / Secret. The receiver can accept the old key for a limited period. Refresh the timestamp and signature on every retry while retaining the original event_id and business-data body.

Send the complete user state#

POST/integration/v1/events

Use Content-Type application/json. The UTF-8 body limit is 65,536 bytes. Send a complete snapshot with the fixed field structure each time, using null for empty values. Count increments and partial patches are not supported.

When to send
FieldUsage
signup_completedWhen signup completes. Both first_link and first_link_completed_at are null. Signup alone does not generate a referral reward.
first_link_completedWhen a Link of any type first completes successfully after signup. Started, in-progress and failed operations do not qualify. Subsequent Links do not generate referral rewards.
attribution_updatedWhen attribution changes, such as resolving a pending referral. Send a complete snapshot retaining the signup and first Link facts.
user_state_correctedWhen correcting, excluding or restoring recorded facts. Use a new event ID, a higher user state version and supporting correction evidence.

The IDs, timestamps and link_type below illustrate the structure. Replace them with actual events and registered data. This page never sends these examples to the production API automatically.

The first example, codeOnly.json, sets all three internal IDs to null. IDs shown in other examples illustrate optional fields and must be replaced with actual lookup results.

Match by referral_code: three internal IDs are null
codeOnly.json
{
  "schema_version": "1.0",
  "event_id": "example.first-link.code-only",
  "event_type": "first_link_completed",
  "environment": "production",
  "program_id": "tanka-jp-ambassador",
  "user_id": "example-user-001",
  "state_version": 2,
  "occurred_at": "2026-09-08T00:05:00+09:00",
  "state_updated_at": "2026-09-08T00:05:00+09:00",
  "data": {
    "is_new_user": true,
    "registration_email": "[email protected]",
    "counting_status": "valid",
    "reason_code": null,
    "attribution": {
      "status": "attributed",
      "ambassador_id": null,
      "referral_code": "JP-example-only",
      "referral_link_id": null,
      "campaign_id": null,
      "touch_id": "example-touch-001",
      "touched_at": "2026-09-08T00:01:00+09:00",
      "bound_at": "2026-09-08T00:03:00+09:00",
      "model": "last_click",
      "window_days": 30,
      "rule_version": "attr-v1"
    },
    "milestones": {
      "signup_at": "2026-09-08T00:03:00+09:00",
      "first_link_completed_at": "2026-09-08T00:05:00+09:00"
    },
    "first_link": {
      "event_id": "example-link-success-001",
      "link_type": "calendar"
    },
    "qualification_rule_version": "first-link-v1",
    "correction": null
  }
}
Signup complete: first Link not yet completed
signup.json
{
  "schema_version": "1.0",
  "event_id": "example.signup.001",
  "event_type": "signup_completed",
  "environment": "production",
  "program_id": "tanka-jp-ambassador",
  "user_id": "example-user-001",
  "state_version": 1,
  "occurred_at": "2026-09-08T00:03:00+09:00",
  "state_updated_at": "2026-09-08T00:03:00+09:00",
  "data": {
    "is_new_user": true,
    "registration_email": "[email protected]",
    "counting_status": "valid",
    "reason_code": null,
    "attribution": {
      "status": "attributed",
      "ambassador_id": "11111111-1111-4111-8111-111111111111",
      "referral_code": "JP-example-only",
      "referral_link_id": "22222222-2222-4222-8222-222222222222",
      "campaign_id": "33333333-3333-4333-8333-333333333333",
      "touch_id": "example-touch-001",
      "touched_at": "2026-09-08T00:01:00+09:00",
      "bound_at": "2026-09-08T00:03:00+09:00",
      "model": "last_click",
      "window_days": 30,
      "rule_version": "attr-v1"
    },
    "milestones": {
      "signup_at": "2026-09-08T00:03:00+09:00",
      "first_link_completed_at": null
    },
    "first_link": null,
    "qualification_rule_version": "first-link-v1",
    "correction": null
  }
}
First Link complete: facts required for reward calculation are present
firstLink.json
{
  "schema_version": "1.0",
  "event_id": "example.first-link.001",
  "event_type": "first_link_completed",
  "environment": "production",
  "program_id": "tanka-jp-ambassador",
  "user_id": "example-user-001",
  "state_version": 2,
  "occurred_at": "2026-09-08T00:05:00+09:00",
  "state_updated_at": "2026-09-08T00:05:00+09:00",
  "data": {
    "is_new_user": true,
    "registration_email": "[email protected]",
    "counting_status": "valid",
    "reason_code": null,
    "attribution": {
      "status": "attributed",
      "ambassador_id": "11111111-1111-4111-8111-111111111111",
      "referral_code": "JP-example-only",
      "referral_link_id": "22222222-2222-4222-8222-222222222222",
      "campaign_id": "33333333-3333-4333-8333-333333333333",
      "touch_id": "example-touch-001",
      "touched_at": "2026-09-08T00:01:00+09:00",
      "bound_at": "2026-09-08T00:03:00+09:00",
      "model": "last_click",
      "window_days": 30,
      "rule_version": "attr-v1"
    },
    "milestones": {
      "signup_at": "2026-09-08T00:03:00+09:00",
      "first_link_completed_at": "2026-09-08T00:05:00+09:00"
    },
    "first_link": {
      "event_id": "example-link-success-001",
      "link_type": "calendar"
    },
    "qualification_rule_version": "first-link-v1",
    "correction": null
  }
}
Attribution pending: null fields remain explicit
pendingSignup.json
{
  "schema_version": "1.0",
  "event_id": "example.signup.pending",
  "event_type": "signup_completed",
  "environment": "production",
  "program_id": "tanka-jp-ambassador",
  "user_id": "example-user-pending",
  "state_version": 1,
  "occurred_at": "2026-09-08T00:03:00+09:00",
  "state_updated_at": "2026-09-08T00:03:00+09:00",
  "data": {
    "is_new_user": true,
    "registration_email": "[email protected]",
    "counting_status": "hold",
    "reason_code": "attribution_unverified",
    "attribution": {
      "status": "pending",
      "ambassador_id": null,
      "referral_code": null,
      "referral_link_id": null,
      "campaign_id": null,
      "touch_id": null,
      "touched_at": null,
      "bound_at": null,
      "model": "last_click",
      "window_days": 30,
      "rule_version": "attr-v1"
    },
    "milestones": {
      "signup_at": "2026-09-08T00:03:00+09:00",
      "first_link_completed_at": null
    },
    "first_link": null,
    "qualification_rule_version": "first-link-v1",
    "correction": null
  }
}
Resolve previously pending attribution
attributionUpdated.json
{
  "schema_version": "1.0",
  "event_id": "example.attribution.confirmed",
  "event_type": "attribution_updated",
  "environment": "production",
  "program_id": "tanka-jp-ambassador",
  "user_id": "example-user-pending",
  "state_version": 2,
  "occurred_at": "2026-09-08T00:04:00+09:00",
  "state_updated_at": "2026-09-08T00:04:00+09:00",
  "data": {
    "is_new_user": true,
    "registration_email": "[email protected]",
    "counting_status": "valid",
    "reason_code": null,
    "attribution": {
      "status": "attributed",
      "ambassador_id": "11111111-1111-4111-8111-111111111111",
      "referral_code": "JP-example-only",
      "referral_link_id": "22222222-2222-4222-8222-222222222222",
      "campaign_id": "33333333-3333-4333-8333-333333333333",
      "touch_id": "example-touch-001",
      "touched_at": "2026-09-08T00:01:00+09:00",
      "bound_at": "2026-09-08T00:04:00+09:00",
      "model": "last_click",
      "window_days": 30,
      "rule_version": "attr-v1"
    },
    "milestones": {
      "signup_at": "2026-09-08T00:03:00+09:00",
      "first_link_completed_at": null
    },
    "first_link": null,
    "qualification_rule_version": "first-link-v1",
    "correction": null
  }
}
Correction: exclude an incorrectly counted internal test user
corrected.json
{
  "schema_version": "1.0",
  "event_id": "example.correction.001",
  "event_type": "user_state_corrected",
  "environment": "production",
  "program_id": "tanka-jp-ambassador",
  "user_id": "example-user-001",
  "state_version": 3,
  "occurred_at": "2026-09-08T00:08:00+09:00",
  "state_updated_at": "2026-09-08T00:08:00+09:00",
  "data": {
    "is_new_user": true,
    "registration_email": "[email protected]",
    "counting_status": "invalid",
    "reason_code": "internal_test",
    "attribution": {
      "status": "attributed",
      "ambassador_id": "11111111-1111-4111-8111-111111111111",
      "referral_code": "JP-example-only",
      "referral_link_id": "22222222-2222-4222-8222-222222222222",
      "campaign_id": "33333333-3333-4333-8333-333333333333",
      "touch_id": "example-touch-001",
      "touched_at": "2026-09-08T00:01:00+09:00",
      "bound_at": "2026-09-08T00:03:00+09:00",
      "model": "last_click",
      "window_days": 30,
      "rule_version": "attr-v1"
    },
    "milestones": {
      "signup_at": "2026-09-08T00:03:00+09:00",
      "first_link_completed_at": "2026-09-08T00:05:00+09:00"
    },
    "first_link": {
      "event_id": "example-link-success-001",
      "link_type": "calendar"
    },
    "qualification_rule_version": "first-link-v1",
    "correction": {
      "reason_code": "internal_test",
      "reference_id": "example-audit-001",
      "supersedes_state_version": 2
    }
  }
}

Fields and business rules#

Every event JSON field is listed below with key presence, null rules and its source. Unknown fields are rejected. OpenAPI JSON, generated from the same validation schema, specifies types, lengths and basic formats.

Required, optional and null

  • Required: include the JSON key. Allowing a null value does not make the key optional.
  • Optional: the key may be left out. ambassador_id, referral_link_id, campaign_id and registration_email are optional. See each row for its source and null behavior.
  • Null is a value. For example, {"reason_code": null} is valid in a normal event, but removing the reason_code key is rejected. Each conditional null rule is explained in its row.
  • When first_link or correction is null, do not send its child fields. When sending an object, all child fields listed in its table are required and cannot be null.

OpenAPI JSON

Common event fields
FieldRequirementSourceUsage
schema_versionRequirednull: Not allowedIntegration configurationFixed value: "1.0".
event_idRequirednull: Not allowedGenerated by senderA unique, immutable business-event ID. Retain it on retries; use a new ID when correcting the content.
event_typeRequirednull: Not allowedProduct recordsOne of the four event types above.
environmentRequirednull: Not allowedIntegration configurationAccepted value: production. This project uses production only.
program_idRequirednull: Not allowedIntegration configurationFixed value: tanka-jp-ambassador.
user_idRequirednull: Not allowedProduct recordsThe immutable TANKA product user ID. Do not substitute a device ID, team ID or email address.
state_versionRequirednull: Not allowedGenerated by senderA positive integer that increases monotonically per user, up to 9007199254740991. Each user may start at 1.
state_updated_atRequirednull: Not allowedProduct recordsThe last update time of this complete state. It must be at or after the signup and first Link times included in the state.
occurred_atRequirednull: Not allowedProduct recordsThe actual business-event time, rather than the HTTP send or retry time.
dataRequirednull: Not allowedAssembled by senderThe complete user-state object described below.
data: complete user state
FieldRequirementSourceUsage
is_new_userRequirednull: Not allowedProduct recordsA boolean indicating a newly registered user. Do not set it to true for an existing user.
registration_emailOptionalnull: AllowedProduct recordsThe email address used when TANKA signup completed, up to 254 characters. It may be omitted or null for compatibility with older senders. When available, include the same registration-time value in every full state. It is shown only in the administrator referral-user list.
counting_statusRequirednull: Not allowedSender validity assessmentvalid / hold / invalid. Even with valid, rewards require a new user, valid attribution and a first successful Link.
reason_codeRequirednull: ConditionalSender validity assessmentWhen counting_status is hold / invalid, a non-empty reason code is required. A normal valid event may use null.
attributionRequirednull: Not allowedSender attribution recordsThe complete attribution object; see the fields below.
milestonesRequirednull: Not allowedProduct recordsObject containing the signup and first Link timestamps. An empty object is not valid.
milestones.signup_atRequirednull: Not allowedProduct recordsThe actual signup completion time.
milestones.first_link_completed_atRequirednull: ConditionalProduct recordsNull before the first successful Link. After completion, send its actual first success time together with the first_link object. Do not change it on retries or later Links.
first_linkRequirednull: ConditionalProduct recordsNull before the first successful Link. After completion, an object containing event_id and link_type is required together with first_link_completed_at.
qualification_rule_versionRequirednull: Not allowedIntegration configurationInitial value: first-link-v1. It denotes the first successful completion of any Link after signup.
correctionRequirednull: ConditionalSender correction recordsAn object is required for user_state_corrected. Other normal events may use null. Whenever an object is sent, all three child fields listed below are required.
data.attribution: complete attribution
FieldRequirementSourceUsage
statusRequirednull: Not allowedSender attribution recordsattributed / pending / unattributed.
ambassador_idOptionalnull: AllowedReceiver lookup resultThe ambassador ID in this system. Omit it or send null when not needed; otherwise use the UUID returned by referral lookup.
referral_link_idOptionalnull: AllowedReceiver lookup resultThe ID of the referral link registered in this system. Omit it or send null when not needed; otherwise use the lookup UUID.
campaign_idOptionalnull: AllowedReceiver lookup resultThe campaign ID in this system. Omit it or send null when not needed. Use the lookup UUID, not the sender's advertising campaign ID.
referral_codeRequirednull: ConditionalSender attribution recordsThe referral code issued by TANKA and registered by an administrator. A value is required for attributed and is the sole attribution matching key. Otherwise null is allowed when unknown. Length 1–64.
touch_idRequirednull: ConditionalSender attribution recordsAn ID generated and stored by the sender to identify the click. A value is required for attributed; otherwise null is allowed when unknown.
touched_atRequirednull: ConditionalSender attribution recordsThe actual click time. A value is required for attributed; otherwise null is allowed when unknown. Any supplied value must be at or before signup.
bound_atRequirednull: ConditionalSender attribution recordsThe time attribution was bound or resolved for the registered user. A value is required for attributed; otherwise null is allowed when unknown. It must be at or after signup and at or before state_updated_at.
modelRequirednull: Not allowedConfigured rules / lookupThe applicable attribution model; initially last_click. It must match the receiver configuration.
window_daysRequirednull: Not allowedConfigured rules / lookupThe attribution window in days; initially 30. It must match the receiver configuration.
rule_versionRequirednull: Not allowedConfigured rules / lookupThe applicable attribution rule version; initially attr-v1. Use the lookup result or integration configuration.
data.first_link: fields when sending an object
FieldRequirementSourceUsage
event_idRequired within objectnull: Not allowedProduct recordsThe actual product event ID identifying the first successful Link completion. It serves a different purpose from the outer delivery event_id.
link_typeRequired within objectnull: Not allowedProduct recordsThe actual type of the first successfully completed Link. Do not send a guessed value or an empty string.
data.correction: fields when sending an object
FieldRequirementSourceUsage
reason_codeRequired within objectnull: Not allowedSender correction recordsA non-empty code describing the reason for the correction.
reference_idRequired within objectnull: Not allowedSender correction recordsAn ID that traces the correction to supporting evidence, such as an audit or operation record.
supersedes_state_versionRequired within objectnull: Not allowedSender correction recordsThe user state version being corrected: a positive integer lower than the new state_version.
  • For attributed, referral_code, touch_id, touched_at and bound_at are required. ambassador_id, referral_link_id and campaign_id may be omitted or null.
  • first_link and first_link_completed_at must both have values or both be null. The first Link must be at or after signup.
  • Use RFC3339 timestamps with a timezone and up to 6 fractional-second digits. occurred_at and state_updated_at may be up to 300 seconds ahead of the receiver's current time. Backfill may contain actual historical business times.
  • Ordinary event IDs contain 1–160 letters, digits, underscores, periods, colons or hyphens. Referral codes use the same characters, with length 1–64. Optional internal IDs must be actual UUIDs obtained from lookup.
  • The first supplied registration_email is saved. Ordinary omission or null does not erase a known registration email. Replacing a known value requires a higher state version and correction. Explicit null for this key in a correction clears an incorrectly recorded email. Later account email changes do not change the registration-time record.
  • registration_email is used by administrators to verify referrals and is not returned to ambassadors. User identity and reward deduplication continue to use user_id. Names, bank details, payment amounts and subscription information are rejected as undefined fields. Reward rates are selected using the business time.

Generate signatures and send requests#

All four headers below are required.

Postback request headers
FieldUsage
Content-Typeapplication/json
X-Tanka-Key-IdUse the issued Key ID, never the Secret.
X-Tanka-TimestampThe send time as an integer string of Unix seconds, not milliseconds.
X-Tanka-SignatureA 64-character lowercase hexadecimal HMAC-SHA256 digest, without a prefix such as sha256=.
Signature formula
message = ASCII(timestamp) + ASCII(".") + raw_body_bytes
signature = lowercase_hex(HMAC_SHA256(UTF8(secret), message))

Send exactly the bytes that were signed. Do not change JSON formatting, line breaks, field order or encoding after signing. The clock tolerance is 300 seconds; keep server clocks synchronized. HTTPS protects transport, while HMAC verifies key possession and body integrity. Duplicate deliveries are deduplicated per event.

Node.js · sign and send the file's exact bytes
postback.mjs
// Node.js 20+ / ESM. Run: node postback.mjs approved-real-event.json
import { readFileSync } from "node:fs";
import { createHmac } from "node:crypto";

function required(name) {
  const value = process.env[name];
  if (!value) throw new Error("Missing environment variable: " + name);
  return value;
}
if (!process.argv[2]) throw new Error("Pass the path to a real event JSON file");
const url = new URL("/integration/v1/events", required("AMBASSADOR_API_BASE_URL"));
if (url.protocol !== "https:") throw new Error("HTTPS is required");
const keyId = required("TANKA_EVENT_KEY_ID");
const secret = required("TANKA_EVENT_SECRET");
const body = readFileSync(process.argv[2]);
JSON.parse(body.toString("utf8")); // Keep the ORIGINAL bytes for signing and sending.
const timestamp = Math.floor(Date.now() / 1000).toString();
const signature = createHmac("sha256", secret)
  .update(timestamp).update(".").update(body).digest("hex");

const response = await fetch(url, {
  method: "POST",
  redirect: "error",
  signal: AbortSignal.timeout(15000),
  headers: {
    "Content-Type": "application/json",
    "X-Tanka-Key-Id": keyId,
    "X-Tanka-Timestamp": timestamp,
    "X-Tanka-Signature": signature,
  },
  body,
});
console.log("HTTP " + response.status + " " + (await response.text()).slice(0, 4096));
if (![200, 202].includes(response.status)) process.exitCode = 1;
// Queue retries outside this example. Keep event_id/body, refresh timestamp/signature.
Python · generate the same signature with the standard library
postback.py
# Python 3.10+, standard library. Run: python3 postback.py approved-real-event.json
import hashlib
import hmac
import json
import os
from pathlib import Path
import sys
import time
import urllib.error
import urllib.parse
import urllib.request

def required(name):
    value = os.environ.get(name)
    if not value:
        raise SystemExit("Missing environment variable: " + name)
    return value

if len(sys.argv) != 2:
    raise SystemExit("Pass the path to a real event JSON file")
url = urllib.parse.urljoin(required("AMBASSADOR_API_BASE_URL"), "/integration/v1/events")
if urllib.parse.urlparse(url).scheme != "https":
    raise SystemExit("HTTPS is required")
key_id = required("TANKA_EVENT_KEY_ID")
secret = required("TANKA_EVENT_SECRET")
body = Path(sys.argv[1]).read_bytes()
json.loads(body)  # Keep the ORIGINAL bytes for signing and sending.
timestamp = str(int(time.time()))
signature = hmac.new(secret.encode("utf-8"), timestamp.encode("ascii") + b"." + body,
                     hashlib.sha256).hexdigest()

class NoRedirect(urllib.request.HTTPRedirectHandler):
    def redirect_request(self, req, fp, code, msg, headers, newurl):
        return None

request = urllib.request.Request(url, data=body, method="POST", headers={
    "Content-Type": "application/json",
    "X-Tanka-Key-Id": key_id,
    "X-Tanka-Timestamp": timestamp,
    "X-Tanka-Signature": signature,
})
opener = urllib.request.build_opener(NoRedirect())
try:
    response = opener.open(request, timeout=15)
except urllib.error.HTTPError as error:
    response = error
with response:
    status = response.code
    print("HTTP", status, response.read(4096).decode("utf-8", errors="replace"))
if status not in (200, 202):
    raise SystemExit(1)
# Queue retries outside this example. Keep event_id/body, refresh timestamp/signature.

The examples read the body from a file and credentials from server environment variables, then send once. Production senders need a persistent queue, failure alerts and retry handling. OpenAPI's security definitions describe the headers; the sender must implement signing.

Responses, retries and corrections#

Handling by HTTP status and error code
FieldUsage
202 · accepted{event_id,status:"accepted"}: the event has been persisted. Delivery is complete, but asynchronous reward processing may still be pending.
200 · duplicate{event_id,status:"duplicate"}: the same event with identical content was already received. Stop retrying.
400 · INVALID_INPUTInspect path and message in issues, correct the input and retry. Include the receiver-issued request_id when requesting support.
401 · UNAUTHORIZEDThe response contains only error: UNAUTHORIZED. Check the Key ID, send time, original body and signature. Pause retries while investigating credentials and server clocks.
409 · EVENT_ID_CONFLICTThe content for an existing event ID has changed. Check the send records. Retrying with a different body cannot overwrite the existing event.
413 · INVALID_INPUTThe body exceeds the limit. Adjust the payload and remove unnecessary data.
422 · ENVIRONMENT_MISMATCH / FUTURE_EVENTCheck the production environment and program ID, or correct an invalid future business timestamp.
503 / other 5xx / timeoutThe event may be undelivered, or received with its response lost. Retain event_id and body, refresh the timestamp and signature, and retry with backoff and jitter.
429 (if returned by an edge service)The receiver API does not define its own 429 response. If an upstream service returns one, respect Retry-After when present, wait and retry.

Persist business data and the send task before background delivery. Increase retry delays progressively, for example 5 seconds, 30 seconds, 2 minutes and 10 minutes, with jitter. Move expired failures to a review queue. A timeout alone is not a reason to generate a new event ID.

An older version cannot overwrite a newer state for the same user. Different content at the same version is quarantined during asynchronous processing. HTTP 202 does not guarantee a reconciliation match. A new event ID does not create duplicate rewards for the same user. Corrections require a new event_id, a higher state_version and a traceable correction. Confirmed line items may be adjusted through a later difference entry.

Backfill and reconciliation API#

The ambassador system periodically retrieves the integration partner's API with GET. Configure the full endpoint path in TANKA_BACKFILL_BASE_URL and its dedicated Bearer Token in TANKA_BACKFILL_TOKEN.

Request direction: ambassador system → integration partner
GET <partner feed URL>?program_id=tanka-jp-ambassador&limit=500&cursor=<next cursor>
Authorization: Bearer <backfill Token>
Reconciliation API page response
{
  "items": [
    {
      "schema_version": "1.0",
      "event_id": "example.first-link.001",
      "event_type": "first_link_completed",
      "environment": "production",
      "program_id": "tanka-jp-ambassador",
      "user_id": "example-user-001",
      "state_version": 2,
      "occurred_at": "2026-09-08T00:05:00+09:00",
      "state_updated_at": "2026-09-08T00:05:00+09:00",
      "data": {
        "is_new_user": true,
        "registration_email": "[email protected]",
        "counting_status": "valid",
        "reason_code": null,
        "attribution": {
          "status": "attributed",
          "ambassador_id": "11111111-1111-4111-8111-111111111111",
          "referral_code": "JP-example-only",
          "referral_link_id": "22222222-2222-4222-8222-222222222222",
          "campaign_id": "33333333-3333-4333-8333-333333333333",
          "touch_id": "example-touch-001",
          "touched_at": "2026-09-08T00:01:00+09:00",
          "bound_at": "2026-09-08T00:03:00+09:00",
          "model": "last_click",
          "window_days": 30,
          "rule_version": "attr-v1"
        },
        "milestones": {
          "signup_at": "2026-09-08T00:03:00+09:00",
          "first_link_completed_at": "2026-09-08T00:05:00+09:00"
        },
        "first_link": {
          "event_id": "example-link-success-001",
          "link_type": "calendar"
        },
        "qualification_rule_version": "first-link-v1",
        "correction": null
      }
    }
  ],
  "next_cursor": null,
  "watermark": "2026-09-08T00:06:00+09:00",
  "snapshot_id": "example-snapshot-001",
  "complete": true
}
  • Return the complete state of every user related to this program as of watermark, including pending attribution, invalidated, revoked and corrected states. Aggregate counts or a list of valid users alone cannot be reconciled.
  • items contains at most 500 entries per page. Return complete events, or complete user states containing environment / program_id / user_id / state_version / state_updated_at / data.
  • next_cursor=null marks the end. Omit cursor on the first request; pass the returned cursor unchanged for subsequent pages. An empty page is not an end marker.
  • Keep watermark and the snapshot identifier fixed throughout retrieval of a snapshot. Every state update time must be at or before watermark to prevent omissions caused by changes during pagination.
  • snapshot_id is optional; when omitted, watermark identifies the snapshot. complete is also optional, but if present it must agree with whether this is the final page.
  • Each page is limited to 4 MiB, with at most 2,000 pages. If another page exists beyond this limit, reconciliation fails with FEED_TOO_LONG. Requests time out after 30 seconds and do not follow redirects. After failure, retrieval restarts at the beginning of the snapshot and already received data is deduplicated.
  • Monthly confirmation requires a complete reconciliation within the last 24 hours and a watermark covering the end of the previous month. Ordinary CSV/JSON imports do not establish completeness.