> ## Documentation Index
> Fetch the complete documentation index at: https://swapnice.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Data collection

> What the Swapnice SDK observes, how it is collected, when it is transmitted, and what is never sent.

The SDK and API only process data a partner chooses to send, after a user grants specific consent purposes. Swapnice does not scrape a partner app, does not read the consumer Swapnice wallet or payment stack, and does not pull another partner's events.

The Partner API is a different system from the Swapnice consumer app. Consumer records split **private account**, **public profile**, **wallet**, and **places**. Partners never receive wallet, Plaid, government-id, notification tokens, or precise home address from that stack. If a user later links a Swapnice account that has those records internally, the link does not export them.

## Collection model

```mermaid theme={null}
flowchart TB
    User["User action in partner app"] --> Observe["SDK observer or partner backend"]
    Observe --> Gate{"Consent receipt exists for this purpose?"}
    Gate -->|No| Local["Stay local or skip"]
    Gate -->|Yes| Build["Build event: type, object, payload, context, provenance"]
    Build --> Send["Transmit to Partner API"]
    Send --> Accept["Accept + FIFO sequence"]
    Accept --> Archive["Async archive and profile resolution"]
```

Three rules apply to every signal:

1. **Partner-declared.** You register the event types you will send. The SDK does not invent events outside that set.
2. **Purpose-limited.** The same click can be stored for `personalization` and still be unusable for another purpose until that purpose is granted.
3. **Receipt-bound.** Ingest requires a `consent_receipt_id` (or an equivalent effective grant) for the event's `consent_purpose`.

## What is collected

### Identity the partner already has

| Field                         | Source                       | Transmitted             | Notes                                         |
| ----------------------------- | ---------------------------- | ----------------------- | --------------------------------------------- |
| `external_id`                 | Partner user id              | Yes, on customer upsert | Stays scoped to your app                      |
| `customer_id`                 | Swapnice partner-customer id | Yes, on later calls     | Not a global Swapnice account id              |
| Public profile display fields | Optional partner-supplied    | Yes, if you send them   | Username, bio, avatar, coarse public location |
| Partner `metadata`            | Optional                     | Yes                     | Strings, numbers, booleans only               |

The partner customer is your record. Linking to a Swapnice account happens through a connection session, not by uploading a raw email or phone.

### Consent evidence

| Field                             | When collected            | Transmitted                            |
| --------------------------------- | ------------------------- | -------------------------------------- |
| Requested purposes                | Connection session create | Yes                                    |
| Granted purposes                  | After the user accepts    | Yes, as a receipt                      |
| Terms and privacy policy versions | Receipt create            | Yes                                    |
| Session status                    | During connect            | Yes, to the partner                    |
| Revocation                        | User or partner request   | Yes, immediately in the consent object |

Swapnice stores purpose grants so later reads can be denied when consent is missing or revoked.

### Activity events

These are the behavioral signals the SDK or API transmits.

| Field                            | Meaning                         | Example                                                               |
| -------------------------------- | ------------------------------- | --------------------------------------------------------------------- |
| `event_type`                     | What happened                   | `purchase.completed`, `collection.interacted`, `profile.selected`     |
| `ontology` / `ontology_category` | Intent family                   | `intent` / `outcome`, `habit`, `affiliate`, `sentiment`, `activation` |
| `object`                         | What the action was about       | SKU, collection, event, or creator id                                 |
| `payload`                        | Partner-defined attributes      | `{ "sku": "...", "amount": 24.99 }`                                   |
| `context`                        | Where it happened               | screen, storefront, surface                                           |
| `occurred_at`                    | Client or server timestamp      | ISO-8601                                                              |
| `confidence`                     | How certain the signal is       | `1` for a completed purchase                                          |
| `provenance`                     | Who collected it and when       | `source_partner`, `source_system`, `collected_at`                     |
| `consent_purpose`                | Allowed use                     | `personalization`                                                     |
| `consent_receipt_id`             | Grant that authorizes the event | `cr_44aa01`                                                           |

### Context the runtime SDK may attach

When you use the client SDK (not a raw server POST), it can add runtime context you do not have to assemble yourself:

* App identifier and SDK version
* Surface or screen name you registered
* Session id for the current app session
* Event timestamp
* Network retry metadata (not user content)

That is the extra collection the SDK performs relative to the API. It still only sends events you enabled.

## How it is collected

<Tabs>
  <Tab title="SDK observers">
    You register event names in your app. When the user performs that action, the SDK builds the payload, attaches consent and context, queues it, and sends it.

    ```ts theme={null}
    swapnice.observe("purchase.completed", ({ order }) => ({
      object: order.sku,
      payload: { sku: order.sku, amount: order.total, currency: order.currency },
      ontology_category: "outcome",
      consent_purpose: "personalization",
    }));
    ```

    Observers do not run until a connection exists and the purpose is granted.
  </Tab>

  <Tab title="Partner backend">
    Your servers POST events after the fact: checkout webhooks, CRM updates, or stadium POS. The body is the same as the SDK. You must attach `consent_receipt_id`, `consent_purpose`, and `provenance` yourself.
  </Tab>

  <Tab title="Explicit user choices">
    Profile selections, self-labels, and opt-in preferences are stored as claims, not overwrites. An explicit "favorite team" outranks a later inferred signal when profiles are resolved.
  </Tab>
</Tabs>

## When it is transmitted

| Moment                | What is sent                   | Timing                                            |
| --------------------- | ------------------------------ | ------------------------------------------------- |
| App / customer create | App config or customer upsert  | Immediately, server-to-server                     |
| Connection session    | Session id, requested purposes | Immediately                                       |
| Consent grant         | Receipt and purposes           | Immediately after accept                          |
| In-app event          | One activity event             | After consent, as soon as the SDK queue can flush |
| Batch job             | Up to 100 events               | When your backend sends the batch                 |
| Consent revoke        | Revocation + workflow id       | Accepted immediately; archive is async            |
| Profile read          | Nothing new collected          | Partner pulls resolved facts                      |

The ingest path is non-blocking: `POST /v1/events` returns `202` after Durable Object acceptance. Projection into the long-lived archive happens on a queue. Profile reads are synchronous against already-archived events.

<Note>
  If the device is offline, the SDK holds consented events in a local retry queue and sends them when connectivity returns. It does not send events that were observed before consent, and it drops queued events whose receipt has since been revoked.
</Note>

## What is transmitted to Swapnice

A typical event on the wire looks like this:

```json theme={null}
{
  "customer_id": "cust_8f2a1c",
  "client_event_id": "evt_checkout_8891",
  "consent_receipt_id": "cr_44aa01",
  "consent_purpose": "personalization",
  "event_type": "purchase.completed",
  "ontology": "intent",
  "ontology_category": "outcome",
  "object": "sku_blue_eyes_tin",
  "occurred_at": "2026-08-26T16:04:00.000Z",
  "payload": { "sku": "sku_blue_eyes_tin", "currency": "USD", "amount": 24.99 },
  "context": { "surface": "checkout", "storefront": "ios" },
  "confidence": 1,
  "provenance": {
    "source_partner": "partner_app",
    "source_system": "ios",
    "collected_at": "2026-08-26T16:04:00.000Z"
  }
}
```

OAuth traffic also sends `client_id`, PKCE challenge/verifier, and scoped tokens. Tokens are hashed at rest.

## What is never collected or sent

The partner SDK/API will not collect or accept:

* Payment provider tokens, Plaid credentials, or wallet private keys
* Government identification numbers
* Precise home address or raw geolocation unless you put it in a payload you control (do not)
* Email or phone in the clear as an identity key. If you need a join key later, use a hash you already store, under an approved purpose
* Contacts, photos, clipboard, or keystrokes
* Events for purposes the user did not grant
* Another partner's raw event stream or unresolved cross-app identity

<Warning>
  Do not put secrets, access tokens, or raw PII in `payload`, `context`, or `metadata`. Those objects are persisted and can appear in partner-visible claims.
</Warning>

## Consent purposes

| Purpose              | Typical use                                        | Default in conservative apps |
| -------------------- | -------------------------------------------------- | ---------------------------- |
| `account_linking`    | Connect the partner customer to a Swapnice account | Yes, during connect          |
| `personalization`    | In-app experience using the user's own profile     | Yes                          |
| `analytics`          | Aggregated product analytics                       | Yes                          |
| `support`            | Debugging a user's own connection                  | Yes                          |
| `event_enrichment`   | Attach catalog context to events                   | Optional                     |
| `profile_resolution` | Include the signal in resolved facts               | Optional                     |
| `measurement`        | Product-outcome reads                              | Optional, explicit           |

Request only the purposes the product actually uses. Do not bundle extra purposes into a basic connection.

## Retention and revocation

* Revoking a purpose stops new events for that purpose immediately.
* Profile reads that pass `consent_purpose` fail with `403 consent_required` after revoke.
* Archive and workflow records are updated asynchronously after the consent object accepts the revoke.
* A future profile-recompute cascade will drop revoked-purpose facts from resolved reads. Until that ships, partners must pass `consent_purpose` on reads so the live consent gate still applies.

## Related

* [Consent and account linking](/partners/consent)
* [Events and intent ontology](/partners/events-and-intents)
* [Outputs the partner receives](/partners/outputs)
* [End-to-end architecture](/partners/architecture)
