# Swapnice documentation
Source: https://swapnice.mintlify.app/introduction
Start with the Partner SDK and API, or browse consumer app guides for personas, badges, and inventory.
## Partner SDK
If you are integrating Swapnice into another product, this is the starting point. The Partner SDK is the supported way to connect customers, collect consent, send events, and read profiles.
TypeScript and Python clients, implementation requirements, sample code, data collection, outputs, architecture, and Development/Production hosts.
```bash theme={null}
npm install swapnice-sdk-ts
```
OAuth, consent, events, profile reads, and copy-paste TypeScript, Python, and cURL.
What the SDK adds beyond raw HTTP, and when to call the Partner API directly.
What the SDK observes, when it transmits, and what never leaves the device.
Development and Production gateway URLs, isolation, and client configuration.
Profiles, claims, and intent scores partners can read today.
Ordered sandbox calls from OAuth through profile read and revoke.
What a design-partner integration unlocks in the partner product.
OpenAPI playground for every Partner API route.
Switch to **Partner SDK & API** in the product switcher at any time, or use **Partner SDK** in the top navigation. Both go to the same integration docs.
## Using the Swapnice app
Guides for people using Swapnice directly: personas, badges, inventory, and the rest of the consumer product.
Personas are AI characters created from the item's details and experiences.
Attach a Swap Badge to give an item an NFC identity, history, and swapp data.
Activating a Swap Badge turns an item into a Switem with details, history, and swapps.
Track and manage your items, Switems, and Personas.
Verified item profiles that display and certify details, history, and swapps.
Apps that give Switems and Personas extra capabilities.
The app store for Swapps.
List items to be swapped, sold, or donated.
Discuss, approve, transact, and assess with realtime consensus.
Link financial accounts to send and receive funds from transactions and swapps.
Connect and chat with members of the Swapnice community.
## Performance-Driven Development
Swapnice is made with ❤️ by a passionate engineering-lead team with enterprise-grade world-class software, data, and AI research experience. Swapnice has researched and developed state of the art algorithms and systems to tackle the hardest problems in the industry. We hyper-scale productivity to make our lives as efficient as we can. Connect with us to learn more about our technology and how we can help you.
# API overview
Source: https://swapnice.mintlify.app/partners/api-overview
Partner API contract, authentication, and every public route.
The Partner API is documented from `openapi.yaml`. Each group below has a short guide plus a playground page for every route.
Base URLs:
* Development: `https://swapnice-gateway-dev.ceo-cbc.workers.dev`
* Production: `https://swapnice-gateway-production.ceo-cbc.workers.dev`
Partners call the gateway only. Details: [environments](/partners/environments).
Public fields use descriptive names. Compact consumer-app storage keys (`c0`, `p0`, and similar) are internal and are not part of this API.
Discover, authorize (JSON PKCE), token, introspect, revoke.
Register and update the partner OAuth application.
Start the account-linking session and poll status.
Grant, list, inspect, and revoke purpose receipts.
Upsert your user. Use `external_id` on create.
Ingest consented activity. `Idempotency-Key` required.
Read resolved facts, claims, and intent scores.
Catalog objects of intent and their field maps.
Register receivers for async processing updates.
## Authentication
Most routes require a Bearer access token from the OAuth 2.1 authorization-code + PKCE flow. App registration uses a separate registration key.
| Mechanism | Used for |
| -------------------------------------- | ------------------------------------- |
| `Authorization: Bearer ` | Partner API calls |
| PKCE `S256` | `/oauth/authorize` and `/oauth/token` |
| `Idempotency-Key` | All mutating `/v1` routes |
| App registration key | `POST /v1/apps` (issued by Swapnice) |
Tokens are short-lived (\~15 minutes). Secrets and tokens are hashed at rest. Redirect URIs must match exactly.
Full narrative: [authentication](/partners/authentication). Ordered calls: [request walkthrough](/partners/request-walkthrough).
## Scopes
```text theme={null}
apps:read apps:write
connections:read connections:write
consent:read consent:write
customers:read customers:write
events:read events:write
profile:read claims:read intents:read
entries:read entries:write
ontologies:read ontologies:write
webhooks:read webhooks:write
tokens:introspect tokens:revoke
workflows:read
```
Request the smallest set that matches your calls. See [scope templates](/partners/integration#recommended-scope-templates).
## Errors
```json theme={null}
{
"error": {
"code": "consent_required",
"message": "Effective consent does not allow this profile read.",
"request_id": "req_3e19c0"
}
}
```
Authorization failures stay generic. Response bodies do not include secrets or private wallet data.
## Playground
Open any route in this tab and send requests to **Development** with a token from your issued Development app. Do not point the playground at Production.
# Create a partner app
Source: https://swapnice.mintlify.app/partners/api-reference/apps-create
openapi.yaml POST /v1/apps
Register an OAuth application. Returns the client secret once.
Register an OAuth application. Returns the client secret once.
Usually called by Swapnice during onboarding with an app registration key. Store `client_secret` immediately.
# Get a partner app
Source: https://swapnice.mintlify.app/partners/api-reference/apps-get
openapi.yaml GET /v1/apps/{app_id}
Read app configuration, scopes, and redirect URIs.
Read app configuration, scopes, and redirect URIs.
Requires `apps:read`. The client secret is never returned again.
# Update a partner app
Source: https://swapnice.mintlify.app/partners/api-reference/apps-update
openapi.yaml PATCH /v1/apps/{app_id}
Change redirect URIs, scopes, consent purposes, or status.
Change redirect URIs, scopes, consent purposes, or status.
Requires `apps:write` and an `Idempotency-Key`.
# Create a connection session
Source: https://swapnice.mintlify.app/partners/api-reference/connections-create
openapi.yaml POST /v1/connection-sessions
Start the Plaid-Link-style account linking flow.
Start the Plaid-Link-style account linking flow.
Open `connection_url` for the user and poll the session. See [consent and account linking](/partners/consent).
# Cancel a connection session
Source: https://swapnice.mintlify.app/partners/api-reference/connections-delete
openapi.yaml DELETE /v1/connection-sessions/{session_id}
Revoke an unused linking session.
Revoke an unused linking session.
Requires `connections:write`. Create a new session if the user needs to try again.
# Get a connection session
Source: https://swapnice.mintlify.app/partners/api-reference/connections-get
openapi.yaml GET /v1/connection-sessions/{session_id}
Poll session status until authorized, completed, expired, or revoked.
Poll session status until authorized, completed, expired, or revoked.
Statuses: `created`, `authorized`, `completed`, `expired`, `revoked`.
# Create a consent receipt
Source: https://swapnice.mintlify.app/partners/api-reference/consent-create
openapi.yaml POST /v1/consent-receipts
Record purpose grants after the user accepts.
Record purpose grants after the user accepts.
Events for a purpose are rejected until this exists. Include `terms_version` and `privacy_policy_version`.
# Get effective consent
Source: https://swapnice.mintlify.app/partners/api-reference/consent-effective
openapi.yaml GET /v1/customers/{customer_id}/effective-consent
See which purposes are currently granted for a customer.
See which purposes are currently granted for a customer.
Use this before a purpose-scoped profile read so revoked users are excluded.
# List consent receipts
Source: https://swapnice.mintlify.app/partners/api-reference/consent-list
openapi.yaml GET /v1/consent-receipts
List receipts for a partner customer.
List receipts for a partner customer.
Filter with `customer_id`. Requires `consent:read`.
# Revoke consent
Source: https://swapnice.mintlify.app/partners/api-reference/consent-revoke
openapi.yaml POST /v1/consent-receipts/{receipt_id}/revoke
Revoke one or more purposes. Ingest and purpose-scoped reads fail closed immediately.
Revoke one or more purposes. Ingest and purpose-scoped reads fail closed immediately.
Returns `202` and a `workflow_id`. Stop using that customer's intelligence for the revoked purposes.
# Get a customer
Source: https://swapnice.mintlify.app/partners/api-reference/customers-get
openapi.yaml GET /v1/customers/{customer_id}
Read the partner customer record Swapnice assigned.
Read the partner customer record Swapnice assigned.
Use the returned `id` on connection, consent, event, and profile calls.
# Create or upsert a customer
Source: https://swapnice.mintlify.app/partners/api-reference/customers-upsert
openapi.yaml POST /v1/customers
Create the partner-scoped user. Body field is external_id, not customer_id.
Create the partner-scoped user. Body field is external\_id, not customer\_id.
`profile` accepts public display fields only. Do not send payment, government-id, or credential data.
# Get an entry
Source: https://swapnice.mintlify.app/partners/api-reference/entries-get
openapi.yaml GET /v1/entries/{entry_id}
Read one catalog object.
Read one catalog object.
Requires `entries:read`.
# Replace an entry
Source: https://swapnice.mintlify.app/partners/api-reference/entries-update
openapi.yaml PUT /v1/entries/{entry_id}
PUT uses the same validation as create. Identity fields must still match.
PUT uses the same validation as create. Identity fields must still match.
Requires `entries:write` and an `Idempotency-Key`.
# Create or upsert an entry
Source: https://swapnice.mintlify.app/partners/api-reference/entries-upsert
openapi.yaml POST /v1/entries
Add a catalog object events can reference.
Add a catalog object events can reference.
Use the returned `entry_id` as `object` on events. Guide: [entries and ontologies](/partners/catalog).
# Ingest a batch of events
Source: https://swapnice.mintlify.app/partners/api-reference/events-batch
openapi.yaml POST /v1/events/batch
Send 1–100 events. Ordering is guaranteed per customer, not across customers.
Send 1–100 events. Ordering is guaranteed per customer, not across customers.
Each item uses the same shape as a single ingest. Partial acceptance is possible.
# Get event status
Source: https://swapnice.mintlify.app/partners/api-reference/events-get
openapi.yaml GET /v1/events/{event_id}
Read processing status for an accepted event.
Read processing status for an accepted event.
Statuses: `accepted`, `processing`, `processed`, `failed`, `rejected`. Wait for `processed` before expecting profile facts.
# Ingest an event
Source: https://swapnice.mintlify.app/partners/api-reference/events-ingest
openapi.yaml POST /v1/events
Accept one consented activity event. Idempotency-Key is required.
Accept one consented activity event. Idempotency-Key is required.
Returns `202` after durable accept. Profile reads see the event after status is `processed`. See [events and intents](/partners/events-and-intents).
# Create an authorization code
Source: https://swapnice.mintlify.app/partners/api-reference/oauth-authorize
openapi.yaml POST /oauth/authorize
JSON PKCE authorize used by the SDK and sandbox walkthrough.
JSON PKCE authorize used by the SDK and sandbox walkthrough.
This is the partner-backend flow. `code_challenge_method` must be `S256`. The code expires in about five minutes and is single-use.
See [authentication](/partners/authentication) and the [request walkthrough](/partners/request-walkthrough).
# Start browser authorization
Source: https://swapnice.mintlify.app/partners/api-reference/oauth-authorize-browser
openapi.yaml GET /oauth/authorize
Browser-facing authorize page for hosted connect UX.
Browser-facing authorize page for hosted connect UX.
Use this only if you are embedding the hosted connection page. Partner backends should call [POST /oauth/authorize](/partners/api-reference/oauth-authorize) instead.
# Introspect a token
Source: https://swapnice.mintlify.app/partners/api-reference/oauth-introspect
openapi.yaml POST /oauth/introspect
Confirm a token is active and inspect its scopes.
Confirm a token is active and inspect its scopes.
Requires `tokens:introspect`. Use this in sandbox to verify the token you just issued.
# Get authorization server metadata
Source: https://swapnice.mintlify.app/partners/api-reference/oauth-metadata
openapi.yaml GET /.well-known/oauth-authorization-server
Discover authorize, token, introspect, and revoke URLs. No authentication.
Discover authorize, token, introspect, and revoke URLs. No authentication.
Call this first to confirm you are talking to the Partner API.
```bash theme={null}
curl https://swapnice-gateway-dev.ceo-cbc.workers.dev/.well-known/oauth-authorization-server
```
# Revoke a token
Source: https://swapnice.mintlify.app/partners/api-reference/oauth-revoke
openapi.yaml POST /oauth/revoke
Invalidate an access or refresh token.
Invalidate an access or refresh token.
Do not send a Bearer header. Body is `{ "token": "..." }`.
# Exchange or refresh a token
Source: https://swapnice.mintlify.app/partners/api-reference/oauth-token
openapi.yaml POST /oauth/token
Trade an authorization code or refresh token for a Bearer access token.
Trade an authorization code or refresh token for a Bearer access token.
Accepts JSON or form-encoded bodies. Access tokens last about 15 minutes. Include `code_verifier` for authorization-code exchanges.
# Get an ontology
Source: https://swapnice.mintlify.app/partners/api-reference/ontologies-get
openapi.yaml GET /v1/ontologies/{ontology_id}
Read the field map, types, and identity fields for one ontology.
Read the field map, types, and identity fields for one ontology.
You send readable field names from this map. Compact storage keys are not part of the public API.
# List ontologies
Source: https://swapnice.mintlify.app/partners/api-reference/ontologies-list
openapi.yaml GET /v1/ontologies
List catalog field maps available to the partner.
List catalog field maps available to the partner.
May return an empty list in a fresh sandbox. That is valid.
# Rebuild ontology fields
Source: https://swapnice.mintlify.app/partners/api-reference/ontologies-rebuild
openapi.yaml POST /v1/ontologies/{ontology_id}/fields/rebuild
Regenerate filter metadata after a bulk entry import.
Regenerate filter metadata after a bulk entry import.
Requires `ontologies:write`. Returns a workflow you can poll.
# List claims
Source: https://swapnice.mintlify.app/partners/api-reference/profiles-claims
openapi.yaml GET /v1/profiles/{customer_id}/claims
Unresolved evidence behind the resolved profile.
Unresolved evidence behind the resolved profile.
Requires `claims:read`. Same consent gate as the profile read.
# Get a resolved profile
Source: https://swapnice.mintlify.app/partners/api-reference/profiles-get
openapi.yaml GET /v1/profiles/{customer_id}
Synchronous facts with confidence, evidence, and alternatives.
Synchronous facts with confidence, evidence, and alternatives.
Pass `consent_purpose` to enforce the live consent gate. Sample bodies: [outputs](/partners/outputs).
# List intents
Source: https://swapnice.mintlify.app/partners/api-reference/profiles-intents
openapi.yaml GET /v1/profiles/{customer_id}/intents
Outcome, habit, affiliate, sentiment, and activation scores.
Outcome, habit, affiliate, sentiment, and activation scores.
Optional `category` query filters the five intent families.
# Register a webhook endpoint
Source: https://swapnice.mintlify.app/partners/api-reference/webhooks-create
openapi.yaml POST /v1/webhook-endpoints
Create a receiver. The signing secret is shown once.
Create a receiver. The signing secret is shown once.
Registration is live. Delivery is near-term. See [webhooks](/partners/webhooks).
# List webhook endpoints
Source: https://swapnice.mintlify.app/partners/api-reference/webhooks-list
openapi.yaml GET /v1/webhook-endpoints
List registered receivers for this app.
List registered receivers for this app.
Requires `webhooks:read`.
# Get workflow status
Source: https://swapnice.mintlify.app/partners/api-reference/workflows-get
openapi.yaml GET /v1/workflows/{workflow_id}
Poll async work such as consent revocation or field rebuilds.
Poll async work such as consent revocation or field rebuilds.
Statuses: `accepted`, `running`, `completed`, `failed`.
# Apps
Source: https://swapnice.mintlify.app/partners/api/apps
Register and update a partner OAuth application.
A partner app holds `client_id`, hashed `client_secret`, redirect URIs, allowed scopes, and allowed consent purposes.
`POST /v1/apps` is typically called by Swapnice during onboarding with an app registration key. Partners then use `GET` and `PATCH` with an access token that includes `apps:read` or `apps:write`.
The client secret is returned **once**. Store it in your secret manager.
## Endpoints in this group
| Method | Path | Scope |
| ------- | ---------------------------------------------------------- | -------------------- |
| `POST` | [`/v1/apps`](/partners/api-reference/apps-create) | App registration key |
| `GET` | [`/v1/apps/{app_id}`](/partners/api-reference/apps-get) | `apps:read` |
| `PATCH` | [`/v1/apps/{app_id}`](/partners/api-reference/apps-update) | `apps:write` |
Mutating calls require an `Idempotency-Key` header.
See [authentication](/partners/authentication) for redirect URI rules and scope templates.
# Entries and ontologies
Source: https://swapnice.mintlify.app/partners/api/catalog
Catalog objects of intent and the field maps that validate them.
An ontology is a versioned field map (`ygo`, `pokemon`, …). An entry is one object a user can show intent toward. You send readable field names; compact internal storage keys are not part of this API.
Use the returned `entry_id` as `object` on events so intents roll up.
## Endpoints in this group
| Method | Path | Scope |
| ------ | ------------------------------------------------------------------------------------------- | ------------------ |
| `GET` | [`/v1/ontologies`](/partners/api-reference/ontologies-list) | `ontologies:read` |
| `GET` | [`/v1/ontologies/{ontology_id}`](/partners/api-reference/ontologies-get) | `ontologies:read` |
| `POST` | [`/v1/ontologies/{ontology_id}/fields/rebuild`](/partners/api-reference/ontologies-rebuild) | `ontologies:write` |
| `POST` | [`/v1/entries`](/partners/api-reference/entries-upsert) | `entries:write` |
| `GET` | [`/v1/entries/{entry_id}`](/partners/api-reference/entries-get) | `entries:read` |
| `PUT` | [`/v1/entries/{entry_id}`](/partners/api-reference/entries-update) | `entries:write` |
`POST /v1/entries` upserts. `status` is `created`, `updated`, or `unchanged`. Identity fields for that ontology are required.
Guide: [entries and ontologies](/partners/catalog).
# Connection sessions
Source: https://swapnice.mintlify.app/partners/api/connections
Short-lived sessions that link a partner customer to a Swapnice account.
Create a session, open `connection_url` for the user, and poll until `authorized` or `completed`. Sessions expire. `DELETE` cancels an unused session.
This is the Plaid-Link-style step. You do not need the user's global Swapnice id to start.
## Endpoints in this group
| Method | Path | Scope |
| -------- | ------------------------------------------------------------------------------------ | ------------------- |
| `POST` | [`/v1/connection-sessions`](/partners/api-reference/connections-create) | `connections:write` |
| `GET` | [`/v1/connection-sessions/{session_id}`](/partners/api-reference/connections-get) | `connections:read` |
| `DELETE` | [`/v1/connection-sessions/{session_id}`](/partners/api-reference/connections-delete) | `connections:write` |
Statuses: `created`, `authorized`, `completed`, `expired`, `revoked`.
After the user accepts, record a [consent receipt](/partners/api/consent). Narrative: [consent and account linking](/partners/consent).
# Consent
Source: https://swapnice.mintlify.app/partners/api/consent
Grant, list, inspect, and revoke purpose-limited consent receipts.
Events and purpose-scoped profile reads fail closed without a grant. Revoke is accepted immediately (`202`); archive work is asynchronous.
## Endpoints in this group
| Method | Path | Scope |
| ------ | -------------------------------------------------------------------------------------------- | --------------- |
| `POST` | [`/v1/consent-receipts`](/partners/api-reference/consent-create) | `consent:write` |
| `GET` | [`/v1/consent-receipts`](/partners/api-reference/consent-list) | `consent:read` |
| `POST` | [`/v1/consent-receipts/{receipt_id}/revoke`](/partners/api-reference/consent-revoke) | `consent:write` |
| `GET` | [`/v1/customers/{customer_id}/effective-consent`](/partners/api-reference/consent-effective) | `consent:read` |
Include `terms_version` and `privacy_policy_version` on create. `consent_purpose` on later events must be one of the granted purposes.
Purposes in product language: [consent and account linking](/partners/consent).
# Customers
Source: https://swapnice.mintlify.app/partners/api/customers
Create and read the partner-scoped customer record.
A customer is **your** user. Key them with `external_id` on create — not `customer_id`. Swapnice returns `customer.id` for connection, consent, event, and profile calls.
Private consumer-app fields (wallet, Plaid, government id, precise address) are never accepted or returned here. `profile` is public display fields only.
## Endpoints in this group
| Method | Path | Scope |
| ------ | ---------------------------------------------------------------------- | ----------------- |
| `POST` | [`/v1/customers`](/partners/api-reference/customers-upsert) | `customers:write` |
| `GET` | [`/v1/customers/{customer_id}`](/partners/api-reference/customers-get) | `customers:read` |
`POST` is an upsert. Replaying the same `Idempotency-Key` or the same `external_id` does not create a duplicate.
```json theme={null}
{
"external_id": "partner_user_1842",
"profile": { "username": "collector1842" },
"metadata": { "source": "ios" }
}
```
# Events
Source: https://swapnice.mintlify.app/partners/api/events
Ingest consented activity and read processing status.
`POST /v1/events` is the main write. It is non-blocking: you get `202` after durable accept, FIFO sequence, and enqueue. Profile reads see the event after archive projection (`processed`).
`Idempotency-Key` is required. Replays return the original result. A reused key with a different body returns `409`.
## Endpoints in this group
| Method | Path | Scope |
| ------ | ------------------------------------------------------------- | -------------- |
| `POST` | [`/v1/events`](/partners/api-reference/events-ingest) | `events:write` |
| `POST` | [`/v1/events/batch`](/partners/api-reference/events-batch) | `events:write` |
| `GET` | [`/v1/events/{event_id}`](/partners/api-reference/events-get) | `events:read` |
Batch accepts 1–100 events. Ordering is guaranteed per customer, not across customers.
Statuses: `accepted`, `processing`, `processed`, `failed`, `rejected`.
How to shape `event_type`, `ontology_category`, and `object`: [events and intent ontology](/partners/events-and-intents).
# OAuth
Source: https://swapnice.mintlify.app/partners/api/oauth
Authorization-code + PKCE endpoints for the Partner API.
Use these routes to discover the authorization server, issue a code, exchange it for a token, introspect, and revoke.
The sandbox walkthrough uses **JSON `POST /oauth/authorize`**, then **`POST /oauth/token`**. Do not use a browser OAuth helper that expects a redirect-only authorize URL unless you are building the hosted connect page.
PKCE, scopes, token lifetime, and common errors.
Ordered sandbox calls with copy-paste bodies.
## Endpoints in this group
| Method | Path | Job |
| ------ | ----------------------------------------------------------------------------------- | -------------------------------- |
| `GET` | [`/.well-known/oauth-authorization-server`](/partners/api-reference/oauth-metadata) | Discovery |
| `POST` | [`/oauth/authorize`](/partners/api-reference/oauth-authorize) | Issue a code (partner JSON flow) |
| `GET` | [`/oauth/authorize`](/partners/api-reference/oauth-authorize-browser) | Browser authorize page |
| `POST` | [`/oauth/token`](/partners/api-reference/oauth-token) | Exchange or refresh |
| `POST` | [`/oauth/introspect`](/partners/api-reference/oauth-introspect) | Inspect a token |
| `POST` | [`/oauth/revoke`](/partners/api-reference/oauth-revoke) | Revoke a token |
Access tokens last about 15 minutes. Authorization codes last about 5 minutes and are single-use. `scope` on authorize is a space-separated string.
Open each route in the sidebar for the playground and full schema.
# Profiles
Source: https://swapnice.mintlify.app/partners/api/profiles
Synchronous, consent-aware reads for resolved facts, claims, and intents.
These routes return intelligence from archived events. Pass `consent_purpose` to enforce the live consent gate. Missing grant → `403 consent_required`.
Resolution method today: highest confidence, then most recent. Alternatives stay on the response.
## Endpoints in this group
| Method | Path | Scope |
| ------ | -------------------------------------------------------------------------------- | -------------- |
| `GET` | [`/v1/profiles/{customer_id}`](/partners/api-reference/profiles-get) | `profile:read` |
| `GET` | [`/v1/profiles/{customer_id}/claims`](/partners/api-reference/profiles-claims) | `claims:read` |
| `GET` | [`/v1/profiles/{customer_id}/intents`](/partners/api-reference/profiles-intents) | `intents:read` |
Intents accept an optional `category` query (`outcome`, `sentiment`, `affiliate`, `habit`, `activation`).
If a profile is empty right after ingest, poll `GET /v1/events/{event_id}` until `processed`, then retry.
Sample bodies: [outputs](/partners/outputs).
# Webhooks
Source: https://swapnice.mintlify.app/partners/api/webhooks
Register HTTPS receivers for async Swapnice events.
Registration is live. Signed delivery of `event.processed`, `event.failed`, `consent.revoked`, and `profile.updated` is near-term. Build the receiver now; until delivery ships, poll event and workflow status.
The signing secret is shown once.
## Endpoints in this group
| Method | Path | Scope |
| ------ | ------------------------------------------------------------------ | ---------------- |
| `POST` | [`/v1/webhook-endpoints`](/partners/api-reference/webhooks-create) | `webhooks:write` |
| `GET` | [`/v1/webhook-endpoints`](/partners/api-reference/webhooks-list) | `webhooks:read` |
```json theme={null}
{
"url": "https://partner.example.com/swapnice/webhooks",
"enabled_events": ["event.processed", "event.failed", "consent.revoked", "profile.updated"]
}
```
What to do with each type: [webhooks guide](/partners/webhooks).
# Architecture and data flow
Source: https://swapnice.mintlify.app/partners/architecture
End-to-end path from a consumer action in a partner app to a Swapnice profile or intent.
This is the system a partner integrates with: a Cloudflare Workers Partner API, Durable Object coordination for consent and per-customer event order, D1 as the system of record, and generated SDKs on top of the OpenAPI contract.
## System context
```mermaid theme={null}
flowchart TB
subgraph partner["Partner"]
App["Partner app + SDK"]
Backend["Partner backend"]
end
subgraph swapnice["Swapnice Partner platform"]
API["Partner API"]
SDKGen["TypeScript and Python SDKs"]
end
subgraph edge["Cloudflare"]
GW["Gateway"]
Auth["Auth"]
Ingest["Ingest"]
Consent["Consent"]
Read["Read"]
Catalog["Catalog"]
DO["Durable Objects"]
D1["D1 archive"]
Q["Archive queue"]
end
App --> API
Backend --> API
SDKGen -.-> App
SDKGen -.-> Backend
API --> GW
GW --> Auth
GW --> Ingest
GW --> Consent
GW --> Read
GW --> Catalog
Ingest --> DO
Consent --> DO
Ingest --> Q
Consent --> Q
Q --> D1
Read --> D1
Backend --> App
```
The consumer Swapnice app (Firebase, wallet, NFC) is a different system. Partner traffic does not go there.
## Environments
Partners call the **gateway** for the environment they are targeting. Development and Production are fully isolated (separate Workers, D1, queues, and Durable Objects).
| Environment | Gateway |
| ----------- | --------------------------------------------------------- |
| Development | `https://swapnice-gateway-dev.ceo-cbc.workers.dev` |
| Production | `https://swapnice-gateway-production.ceo-cbc.workers.dev` |
See [environments](/partners/environments) for credentials, isolation, and client configuration.
## End-to-end consumer path
The sequence below is the path a technical review should walk: user action → consent → event → archive → profile → partner product.
```mermaid theme={null}
sequenceDiagram
participant User as Consumer
participant App as Partner app + SDK
participant API as Partner API
participant DO as Consent + event DOs
participant Q as Archive queue
participant D1 as D1
participant Prod as Partner product
User->>App: Opens app, starts Swapnice connect
App->>API: POST /v1/connection-sessions
API-->>App: connection_url, client_token
User->>API: Authorizes + grants purposes
App->>API: POST /v1/consent-receipts
API->>DO: Persist grant
User->>App: Completes a product action
App->>API: POST /v1/events + Idempotency-Key
API->>DO: Consent check, FIFO sequence
DO-->>App: 202 accepted
DO->>Q: event_archive
Q->>D1: Persist claim
App->>API: GET /v1/profiles/:id?consent_purpose=...
API->>D1: Load archived events
API-->>App: Resolved facts, confidence, alternatives
App->>Prod: Permissioned facts and intents
```
## 1. Consumer interaction
A user does something in the partner product: views a collection, checks out, labels a fandom, or walks a stadium flow. The SDK observes only events you registered. Nothing is sent until a consent receipt exists for that purpose.
## 2. Connect and consent
```mermaid theme={null}
sequenceDiagram
participant Partner as Partner app
participant Consent as Consent worker
participant Session as ConnectionSessionObject
participant User as User
participant Auth as Auth worker
participant Grant as CustomerConsentObject
Partner->>Consent: POST /v1/connection-sessions
Consent->>Session: create
Session-->>Partner: session_id, connection_url
User->>Auth: OAuth authorize PKCE
Auth-->>Partner: authorization code / token
Partner->>Consent: POST /v1/consent-receipts
Consent->>Grant: grant purposes
Grant-->>Partner: receipt_id
```
Connection sessions expire. Consent can later be revoked in part or in full. After revoke, ingest and purpose-scoped reads fail closed.
## 3. Event acceptance
```mermaid theme={null}
sequenceDiagram
participant Partner as SDK or backend
participant Ingest as Ingest worker
participant Events as CustomerEventObject
participant Consent as CustomerConsentObject
participant Q as Archive queue
Partner->>Ingest: POST /v1/events
Ingest->>Events: ingest
alt same Idempotency-Key
Events-->>Ingest: replayed result
else new event
Events->>Consent: allows(purpose, receipt)
Consent-->>Events: granted or denied
Events->>Events: persist + sequence
Events->>Q: event_archive
Events-->>Ingest: 202 accepted
end
```
Guarantees partners can rely on:
* **FIFO per app + customer.** Events for one customer go through one Durable Object.
* **Idempotent writes.** Retries do not duplicate durable effects.
* **Consent at ingest.** No receipt, no accept.
* **Fast ACK.** Partners are not blocked on archive or profile recompute.
## 4. Profile resolution
V1 resolves profiles at read time from archived D1 events. There is no separate profile-recompute Durable Object yet.
```mermaid theme={null}
sequenceDiagram
participant Partner as Partner backend
participant Read as Read worker
participant Consent as CustomerConsentObject
participant D1 as D1
Partner->>Read: GET /v1/profiles/:id?consent_purpose=personalization
Read->>Consent: effective purposes
alt purpose not granted
Read-->>Partner: 403 consent_required
else granted
Read->>D1: listCustomerEvents
Read->>Read: group by attribute
Read->>Read: winner = highest confidence, then most recent
Read-->>Partner: facts, evidence, alternatives
end
```
The same gate applies to `/claims` and `/intents`.
## 5. Partner product output
```mermaid theme={null}
flowchart LR
Facts["Resolved facts"] --> PartnerUX["Partner app personalization"]
Intents["Intent scores"] --> Recs["In-app next-step prompts"]
```
The handoff is:
1. Swapnice returns permissioned profiles and intents for **connected, consented** users.
2. The partner uses those reads inside its own product.
3. Revoked purposes fail closed on ingest and purpose-scoped reads.
## Split-worker map
Production routes through a gateway. Local development can run a single worker.
| Path prefix | Worker | Role |
| ------------------------------------------------------------------------------------------ | ------- | -------------------------- |
| `/.well-known/*`, `/oauth/*` | Auth | Discovery, PKCE, tokens |
| `/v1/events*` | Ingest | Ordered, idempotent accept |
| `/v1/connection-sessions*`, `/v1/consent-receipts*` | Consent | Sessions and grants |
| `/v1/profiles*`, `/v1/workflows*` | Read | Synchronous intelligence |
| `/v1/apps*`, `/v1/customers*`, `/v1/entries*`, `/v1/ontologies*`, `/v1/webhook-endpoints*` | Catalog | Configuration and catalog |
## Persistence
| Layer | Holds | Not for |
| ------------------------- | ------------------------------------------------------------ | ----------------------------- |
| Durable Object SQLite | Sessions, effective consent, recent event order, idempotency | Long-term analytics |
| D1 | Apps, tokens (hashed), customers, event archive, workflows | Per-request locks |
| Queue + projection worker | Write-behind archive and revocation cascades | Synchronous partner ACKs |
| In-memory worker state | Nothing durable | Auth, consent, or idempotency |
## Security expectations
* OAuth 2.1 authorization code + PKCE (`S256`)
* Short-lived access tokens, hashed secrets and tokens at rest
* Exact redirect URI match
* Scoped tokens (`events:write`, `profile:read`, …)
* Revocable consent, purpose-checked ingest and reads
* Idempotency keys on mutating routes
* Correlation / request ids on errors
* No leakage of wallet, payment, or private identity fields
## Related
* [Integration and sample code](/partners/integration)
* [Data collection](/partners/data-collection)
* [Outputs](/partners/outputs)
# Authentication
Source: https://swapnice.mintlify.app/partners/authentication
OAuth 2.1, PKCE, scopes, tokens, and how partner apps authenticate to the Swapnice API.
The Partner API uses OAuth 2.1 with authorization-code + PKCE. It does not accept consumer-app Firebase tokens, implicit grants, or password grants.
## What you store
| Credential | Who holds it | Shown |
| --------------- | ----------------------------------------------------- | ------------------------------ |
| `app_id` | Partner backend | After Swapnice creates the app |
| `client_id` | Partner backend and public clients | After create |
| `client_secret` | Confidential backend only | Once, at create or rotation |
| Access token | Short-lived, in memory or a secret store | From `/oauth/token` |
| Refresh token | Confidential backend, if `offline_access` was granted | Rotates on use |
Public or browser clients can start a connection session. They must not hold `client_secret`.
## Discovery
```bash theme={null}
curl https://swapnice-gateway-dev.ceo-cbc.workers.dev/.well-known/oauth-authorization-server
```
The document lists `authorization_endpoint`, `token_endpoint`, `introspection_endpoint`, `revocation_endpoint`, supported grants, and `S256` as the only code-challenge method.
## Authorization-code + PKCE
The verifier is 43–128 characters of unreserved URL-safe text. The challenge is the Base64URL SHA-256 of that verifier, without padding.
`POST /oauth/authorize` with `response_type=code`, exact `redirect_uri`, `customer_id`, space-separated `scope`, and `code_challenge_method=S256`.
`POST /oauth/token` with `grant_type=authorization_code`, `code_verifier`, `client_id`, `client_secret`, and the same `redirect_uri`.
Send `Authorization: Bearer `. Access tokens last about 15 minutes. Authorization codes last about 5 minutes and are single-use.
```ts TypeScript theme={null}
const token = await swapnice.oauth.token({
grant_type: "authorization_code",
client_id: process.env.SWAPNICE_CLIENT_ID!,
client_secret: process.env.SWAPNICE_CLIENT_SECRET!,
code,
code_verifier: verifier,
redirect_uri: process.env.SWAPNICE_REDIRECT_URI!,
});
```
```bash cURL theme={null}
curl -X POST "$SWAPNICE_API_BASE_URL/oauth/authorize" \
-H "Content-Type: application/json" \
-d '{
"response_type": "code",
"client_id": "'"$SWAPNICE_CLIENT_ID"'",
"redirect_uri": "'"$SWAPNICE_REDIRECT_URI"'",
"code_challenge": "'"$CODE_CHALLENGE"'",
"code_challenge_method": "S256",
"customer_id": "cust_8f2a1c",
"scope": "connections:write consent:write events:write profile:read claims:read intents:read"
}'
curl -X POST "$SWAPNICE_API_BASE_URL/oauth/token" \
-H "Content-Type: application/json" \
-d '{
"grant_type": "authorization_code",
"client_id": "'"$SWAPNICE_CLIENT_ID"'",
"client_secret": "'"$SWAPNICE_CLIENT_SECRET"'",
"code": "'"$AUTH_CODE"'",
"code_verifier": "'"$CODE_VERIFIER"'",
"redirect_uri": "'"$SWAPNICE_REDIRECT_URI"'"
}'
```
Example token response:
```json theme={null}
{
"access_token": "swa_at_...",
"token_type": "Bearer",
"expires_in": 900,
"scope": "connections:write consent:write events:write profile:read claims:read intents:read"
}
```
Do not use Postman's built-in OAuth 2.0 helper. Authorize is a JSON `POST` that includes `customer_id`. See the [request walkthrough](/partners/request-walkthrough).
## Introspect and revoke
```bash theme={null}
curl -X POST "$SWAPNICE_API_BASE_URL/oauth/introspect" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "token": "'"$ACCESS_TOKEN"'" }'
curl -X POST "$SWAPNICE_API_BASE_URL/oauth/revoke" \
-H "Content-Type: application/json" \
-d '{ "token": "'"$ACCESS_TOKEN"'" }'
```
Introspect requires `tokens:introspect`. Revoke does not send a Bearer header. Tokens are hashed at rest.
## Redirect URIs
* Must be HTTPS in production (localhost is acceptable in Development)
* Must match a registered URI **exactly** — no wildcards, no trailing-slash drift
* Browser/public clients also need allowed origins if they call from a web SDK
## Scope templates
Start from a template. Add scopes only when a call needs them.
| Template | Scopes | Use |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ |
| Ingest only | `consent:write` `events:write` | Send consented events |
| Connect and ingest | `connections:write` `connections:read` `consent:write` `consent:read` `events:write` `events:read` | Full SDK connect flow |
| Profile reads | previous + `profile:read` `claims:read` `intents:read` | Read resolved intelligence |
| Full partner | previous + `customers:write` `customers:read` `webhooks:write` `webhooks:read` | Production integration |
| Admin / platform | `apps:read` `apps:write` `entries:read` `entries:write` `ontologies:read` `ontologies:write` `tokens:introspect` `tokens:revoke` `workflows:read` | Trusted internal or enterprise |
`scope` on authorize is a **space-separated** string. It must be a subset of the app's allowed scopes.
## Common errors
| Code | Cause | Fix |
| -------------------------------------- | -------------------------------------- | ----------------------------------------- |
| `invalid_client` / `invalid_client_id` | Unknown client | Confirm the Development `client_id` |
| `invalid_grant` | Code expired, reused, or PKCE mismatch | Re-run authorize with a fresh verifier |
| `invalid_scope` | Scope not allowed on the app | Ask Swapnice to add the scope, or drop it |
| `invalid_redirect_uri` | URI does not match registration | Copy the registered value exactly |
| `missing_bearer_token` | No `Authorization` header | Attach the access token |
| `401` on `/v1/*` | Token expired or missing scope | Re-authorize; tokens last \~15 minutes |
## Security rules the API enforces
* PKCE `S256` only (`plain` is rejected)
* Short-lived access tokens
* Hashed secrets and tokens at rest
* Exact redirect match
* Purpose-checked ingest and reads after you have a token
* Authorization failures stay generic and never include secrets
Next: [consent and account linking](/partners/consent) or the [request walkthrough](/partners/request-walkthrough).
# Entries and ontologies
Source: https://swapnice.mintlify.app/partners/catalog
How partners describe products, SKUs, and collectibles so events and intents attach to a shared catalog.
An **entry** is an object a user can show intent toward: a product, SKU, card, event, team, creator, badge, or fandom. An **ontology** is the versioned field map for that kind of object.
You speak in readable field names (`name`, `rarity`, `set`). Swapnice validates them against the ontology and stores them uniformly. Compact internal storage keys are not part of the public API.
## Why this matters for a pilot
If events say `object: "Blue-Eyes"` in one place and `object: "sku_blue_eyes_tin"` in another, intents do not roll up. Catalog entries give you a stable id that profile and intent reads can share.
## Common fields (every ontology)
| Field | Meaning |
| --------------------- | -------------------------------------------------------------------------- |
| `ontology` | Which field map to use (`ygo`, `pokemon`, `intent`, or a partner ontology) |
| `entry_name` | Display name |
| `owner_id` | Catalog owner (your app or a user) |
| `ingestion_origin` | Source system (`partner_catalog`, `shopify`, …) |
| `entry_id` | Optional. Deterministic for source-correlated records |
| `value` | Optional list or market value |
| `media.primary_image` | Optional image |
| `fields` | Ontology-specific attributes |
Suggested entry types as you extend beyond collectibles: `product`, `sku`, `collectible`, `event`, `team`, `creator`, `badge`, `fandom`, `activity`.
## Create or upsert
```ts theme={null}
await swapnice.entries.upsert({
ontology: "ygo",
owner_id: "partner_catalog",
ingestion_origin: "partner_catalog",
entry_name: "Blue-Eyes White Dragon",
value: 12.34,
media: { primary_image: "https://cdn.example.com/blue-eyes.webp" },
fields: {
name: "Blue-Eyes White Dragon",
yugipedia_uid: "46986414",
pcid: "2560454",
rarity: "Ultra Rare",
set: "Legend of Blue Eyes White Dragon",
},
});
```
```json theme={null}
{
"status": "created",
"entry": {
"entry_id": "46986414;2560454",
"ontology": "ygo",
"entry_name": "Blue-Eyes White Dragon",
"owner_id": "partner_catalog",
"ingestion_origin": "partner_catalog",
"value": 12.34,
"image_upload_status": "uploaded",
"fields": {
"name": "Blue-Eyes White Dragon",
"yugipedia_uid": "46986414",
"pcid": "2560454"
}
}
}
```
`status` is `created`, `updated`, or `unchanged` (checksum matched). Identity fields are required for that ontology — for Yu-Gi-Oh that includes the source ids that make the record correlatable. Do not send random ids for objects that already have a source key.
Unknown fields are rejected unless Swapnice agrees to evolve the ontology. Schema drift should be intentional.
## Read ontologies
```bash theme={null}
curl "$SWAPNICE_API_BASE_URL/v1/ontologies" \
-H "Authorization: Bearer $ACCESS_TOKEN"
```
Each ontology returns readable field names, types, and which fields are required for identity. After a bulk import, Swapnice can rebuild filter metadata with `POST /v1/ontologies/:id/fields/rebuild`.
## How events should reference entries
```json theme={null}
{
"event_type": "purchase.completed",
"ontology_category": "outcome",
"object": "46986414;2560454",
"payload": { "sku": "sku_blue_eyes_tin", "entry_id": "46986414;2560454" }
}
```
Use the entry id as `object` whenever you have one. Free-text names are a last resort.
## Partner-owned catalogs
Retail, merch, and event partners often already have a product catalog (Shopify, POS, ticketing). The usual pattern:
1. Upsert the SKUs or events you will send activity for (or a subset for the pilot)
2. Map your id → Swapnice `entry_id` once
3. Emit events against that id
4. Read intents grouped by those objects
You do not need to import an entire collectibles archive to start. Two or three SKUs is enough to prove the join.
# Consent and account linking
Source: https://swapnice.mintlify.app/partners/consent
How a partner customer is linked to a Swapnice account, which purposes exist, and how revocation works.
Swapnice treats consent as a first-class object, not a banner click. Events are rejected and purpose-scoped reads fail closed unless a receipt still grants that purpose.
## Two identities, one link
```mermaid theme={null}
flowchart LR
P["Partner customer
you create and own"] --- L["Connection + consent receipt"]
L --- S["Swapnice account
the user owns"]
```
| Record | Who creates it | What it is |
| ------------------ | -------------------------------------------- | --------------------------------- |
| Partner customer | Your backend, via `POST /v1/customers` | Your user, keyed by `external_id` |
| Connection session | Your app, via `POST /v1/connection-sessions` | Short-lived link attempt |
| Swapnice account | The user, inside Swapnice | Their Swapnice identity |
| Consent receipt | Recorded after they accept | Purpose grants bound to that link |
You never need the user's global Swapnice user id to start. The connection session is the Plaid-Link-style step that binds the two records after the user authorizes.
A Swapnice consumer account can later have wallet or bank linking **inside Swapnice**. That stack is not part of the Partner API and is never returned to partners.
## Connection session
```ts theme={null}
const session = await swapnice.connectionSessions.create({
customer_id: customer.id,
requested_purposes: ["personalization", "analytics"],
redirect_uri: process.env.SWAPNICE_REDIRECT_URI!,
});
// Open session.connection_url. Poll GET /v1/connection-sessions/:id
```
Statuses: `created` → `authorized` → `completed`, or `expired` / `revoked`.
Sessions expire. Do not reuse an expired `session_id`. Create a new one.
## Consent receipt
After the user accepts, persist the receipt. This is what later events must cite.
```json theme={null}
{
"receipt_id": "cr_44aa01",
"customer_id": "cust_8f2a1c",
"purposes": ["personalization", "analytics"],
"status": "granted",
"granted_at": "2026-08-26T16:02:12.000Z"
}
```
Include `terms_version` and `privacy_policy_version` so you can prove which policy the user saw.
Inspect current grants with `GET /v1/customers/:id/effective-consent`.
## Purposes in product language
| Purpose | What the user is allowing | Typical partner use | Default for a conservative app |
| -------------------- | ---------------------------------------------- | ------------------------------ | ------------------------------ |
| `account_linking` | Connect this app account to Swapnice | Complete the link | Yes, during connect |
| `personalization` | Use my activity to improve *this* experience | Home shelf, recommendations | Yes |
| `analytics` | Use my activity in aggregate product analytics | Funnels, quality dashboards | Yes |
| `support` | Let this app debug my own connection | Break-fix | Yes |
| `event_enrichment` | Attach catalog context to my events | SKU / series metadata | Optional |
| `profile_resolution` | Include this signal when facts are resolved | Winner-vs-alternative profiles | Optional |
| `measurement` | Use my activity to understand product outcomes | Optional product-outcome reads | Explicit opt-in |
A user who granted `personalization` has not granted every other purpose. The same event can be stored for one purpose and unusable for another.
Pilot recommendation: request `personalization` and `analytics`. Add other purposes only when the product actually uses them.
## Revocation
```ts theme={null}
await swapnice.consentReceipts.revoke(receiptId, {
purposes: ["personalization"],
reason: "user_request",
});
```
`reason` is one of `user_request`, `partner_request`, `policy_change`, `account_disconnected`, `other`.
What happens immediately:
* New events for a revoked purpose return `consent_required`
* Profile reads that pass that `consent_purpose` return `403`
* The partner product must stop using that customer's intelligence for the revoked purpose
The API accepts revoke synchronously (`202` plus a `workflow_id`). Archive and workflow records update asynchronously. Poll `GET /v1/workflows/:id`.
You can revoke a subset of purposes or the entire receipt.
## Collection notice
Your connect UI should say, in language the user understands:
1. Which app is asking
2. Which purposes, in the table above — not raw scope strings
3. That they can disconnect later
4. That they can revoke a purpose without deleting the rest of the connection
Swapnice can provide sample consent copy during onboarding. Do not invent a purpose that is not in the documented API enum.
## What consent does not cover
Consent to the Partner API does not grant access to:
* Wallet, Dwolla, or Plaid credentials
* Government identification
* Precise home address
* Another partner's users or raw events
* The right to resell the consolidated Swapnice profile
See [data collection](/partners/data-collection) for what is transmitted after a grant.
# Data collection
Source: https://swapnice.mintlify.app/partners/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
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.
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.
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.
## 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.
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.
## 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
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.
## 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)
# Environments
Source: https://swapnice.mintlify.app/partners/environments
Development and production Partner API hosts, what is isolated between them, and which URL partners should call.
The Partner API is deployed on Cloudflare as two isolated environments. Partners call the **gateway** only. Domain workers (auth, ingest, consent, read, catalog, projection) are internal and are not public API hosts.
## Base URLs
| Environment | Cloudflare env | Gateway | Use |
| ----------- | -------------- | --------------------------------------------------------- | ------------------------------------------- |
| Development | `dev` | `https://swapnice-gateway-dev.ceo-cbc.workers.dev` | Integration, fixtures, and pilot testing |
| Production | `production` | `https://swapnice-gateway-production.ceo-cbc.workers.dev` | Live consented traffic after go-live review |
| Local | — | `http://localhost:8787` | Swapnice-internal Worker development |
Discovery on any host:
```bash theme={null}
curl https://swapnice-gateway-dev.ceo-cbc.workers.dev/.well-known/oauth-authorization-server
```
The issuer in that document is the gateway origin you called. Custom domains (for example `api.swapnice.com`) can be attached later; until then, use the gateway URLs above.
Do not send live user traffic to Development. Do not reuse Development `client_id`, `client_secret`, or `APP_REGISTRATION_KEY` in Production.
## What is isolated
Each remote environment has its own Workers, Durable Objects, D1 database, and archive queue.
| | Development | Production |
| --------------- | -------------------------------------------------- | ----------------------------- |
| Public Worker | `swapnice-gateway-dev` | `swapnice-gateway-production` |
| D1 | `swapnice_api_dev` | `swapnice_api` |
| Archive queue | `swapnice-archive-dev` | `swapnice-archive` |
| Durable Objects | on `*-dev` scripts | on `*-production` scripts |
| Data | Isolated fixtures and partner-generated test users | Live consented traffic |
| Credentials | Separate `client_id` / `client_secret` | Separate credentials |
OAuth tokens, consent receipts, events, and profiles created in Development never appear in Production.
## Configure the client
```text theme={null}
# Development
SWAPNICE_API_BASE_URL=https://swapnice-gateway-dev.ceo-cbc.workers.dev
SWAPNICE_ENV=development
# Production
SWAPNICE_API_BASE_URL=https://swapnice-gateway-production.ceo-cbc.workers.dev
SWAPNICE_ENV=production
```
```ts theme={null}
import { SwapniceClient } from "swapnice-sdk-ts";
const swapnice = new SwapniceClient({
environment: process.env.SWAPNICE_ENV === "production" ? "production" : "development",
// Or set the gateway explicitly:
// baseUrl: process.env.SWAPNICE_API_BASE_URL,
clientId: process.env.SWAPNICE_CLIENT_ID!,
clientSecret: process.env.SWAPNICE_CLIENT_SECRET!,
redirectUri: process.env.SWAPNICE_REDIRECT_URI!,
});
```
## Playground and testing
Use Development in the API reference playground and for the [request walkthrough](/partners/request-walkthrough). Production credentials are issued after the [testing checklist](/partners/sandbox) passes.
Next: [sandbox and testing process](/partners/sandbox).
# Events and intent ontology
Source: https://swapnice.mintlify.app/partners/events-and-intents
How to shape partner activity into Swapnice events, claims, and intent categories.
Events are the main ingestion surface. Swapnice stores them as **claims**, not as destructive overwrites of a profile field. Resolved [outputs](/partners/outputs) are computed from those claims.
## What every event must carry
| Field | Role |
| ---------------------------------------- | ---------------------------------------------------------------- |
| `customer_id` | Partner customer the event belongs to |
| `event_type` | What happened (`purchase.completed`, `collection.interacted`, …) |
| `ontology` / `ontology_category` | Which intent family this is |
| `object` | What the user showed intent toward (SKU, series, event, creator) |
| `consent_purpose` + `consent_receipt_id` | Why you are allowed to send it |
| `occurred_at` | When it happened |
| `payload` | Partner attributes (amount, sku, screen) |
| `context` | Surface, storefront, platform |
| `confidence` | 1.0 for a completed purchase; lower for inferred views |
| `provenance` | Who collected it, in which system, when |
| `Idempotency-Key` | Header so retries do not duplicate |
Omit secrets, emails, and payment tokens from `payload` and `context`.
## Intent categories
These five categories are the ontology profile and intent reads speak.
| Category | Meaning | Strong signals | Weaker signals |
| ------------ | --------------------------------------------------------- | --------------------------------------------------- | ----------------------------- |
| `outcome` | Intent to purchase, participate, or complete a collection | `purchase.completed`, ticket scanned, set completed | Add-to-cart, checkout started |
| `sentiment` | Preference or satisfaction | Explicit thumbs-up / rating | Dwell time |
| `affiliate` | Self-label or group association | "I collect Yu-Gi-Oh", team follow | Repeated category browse |
| `habit` | Repeated interest or activity | Nth view of the same series | First impression |
| `activation` | Ready for the next journey step | Finished onboarding step, claimed offer | Saw the prompt |
Designed finer types you can map into those categories:
* `outcome.participate`
* `outcome.purchase`
* `outcome.complete_collection`
* `sentiment.positive` / `sentiment.negative`
* `affiliate.self_label` / `affiliate.group_association`
* `habit.repeated_interest` / `habit.repeated_activity`
* `activation.ready_for_next_step`
V1 accepts `ontology_category` as one of `outcome`, `sentiment`, `affiliate`, `habit`, `activation`. Put the finer type in `event_type` or `payload` if you need it.
## Suggested event catalog
Use this as the first mapping workshop with Swapnice. You do not need all of them for a pilot.
| Partner action | `event_type` | Category | Typical `object` | Typical purpose |
| ---------------------------- | ----------------------- | ------------ | ---------------- | ----------------- |
| User picks a favorite series | `profile.selected` | `affiliate` | series id | `personalization` |
| Views a collection or SKU | `collection.interacted` | `habit` | series or SKU | `personalization` |
| Completes a purchase | `purchase.completed` | `outcome` | SKU | `personalization` |
| Attends or checks in | `event.participated` | `outcome` | event id | `personalization` |
| Completes a set | `collection.completed` | `outcome` | set id | `personalization` |
| Rates or hearts an item | `sentiment.recorded` | `sentiment` | SKU | `personalization` |
| Finishes a journey step | `activation.ready` | `activation` | step id | `personalization` |
Pilot minimum: one explicit claim (`profile.selected`) and one behavioral conversion (`purchase.completed`).
## Example events
```json theme={null}
{
"customer_id": "cust_8f2a1c",
"client_event_id": "evt_pref_1",
"consent_receipt_id": "cr_44aa01",
"consent_purpose": "personalization",
"event_type": "profile.selected",
"ontology": "intent",
"ontology_category": "affiliate",
"object": "yugioh",
"occurred_at": "2026-08-26T16:03:00.000Z",
"payload": { "attribute": "favorite_series", "value": "yugioh" },
"context": { "surface": "onboarding" },
"confidence": 0.92,
"provenance": {
"source_partner": "partner_app",
"source_system": "ios",
"collected_at": "2026-08-26T16:03:00.000Z"
}
}
```
```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"
}
}
```
```json theme={null}
{
"customer_id": "cust_8f2a1c",
"client_event_id": "evt_view_441",
"consent_receipt_id": "cr_44aa01",
"consent_purpose": "personalization",
"event_type": "collection.interacted",
"ontology": "intent",
"ontology_category": "habit",
"object": "ygo_series_001",
"occurred_at": "2026-08-26T16:05:00.000Z",
"payload": { "screen": "series_detail" },
"confidence": 0.72,
"provenance": {
"source_partner": "partner_app",
"source_system": "ios",
"collected_at": "2026-08-26T16:05:00.000Z"
}
}
```
## Claims, not overwrites
If two sources disagree, both claims stay. The profile picker chooses a winner and keeps alternatives.
Resolution today: **highest confidence, then most recent**. Designed rules that later resolution will also honor:
* Explicit user-declared signals outrank inferred behavior
* Recency matters unless an older explicit signal is still active
* Higher-trust sources outrank lower-trust sources
* Truth can be context-specific ("favorite for apparel" ≠ "favorite for collectibles")
* A claim is only usable for purposes still granted
* Revoke, delete, and correction requests remove that claim from purpose-scoped reads
That is why the API returns `resolution_method`, `evidence`, and `alternatives`. Partners can see *why* a fact won.
## Ordering, idempotency, and async
* Events for one `app_id` + `customer_id` are FIFO through a single coordination object
* Replaying the same `Idempotency-Key` returns the original `event_id`
* A reused key with a different body returns `409 idempotency_conflict`
* `POST /v1/events` returns `202` with `processing_status: queued`
* Profile reads see the event after archive projection. If a profile is empty, wait and retry `GET /v1/events/:id` until `processed`
Batch: `POST /v1/events/batch` accepts 1–100 of the same shape.
## Confidence guidance
| Signal | Suggested `confidence` |
| ------------------------------------------------------ | ------------------------------------------------------------ |
| Completed purchase, ticket scan, explicit profile pick | `1.0` or `0.9+` |
| Repeated, same-object engagement | `0.7`–`0.85` |
| Single view or weak inference | `0.4`–`0.6` |
| Do not send | Guesswork, scraped third-party data, another partner's users |
Garbage confidence makes profile reads harder to trust. Prefer fewer, cleaner events in the pilot.
## Catalog objects
When the object is a card, SKU, or collectible, upsert it as an [entry](/partners/catalog) and use that id in `object`. Intents then attach to a shared catalog instead of free-text names.
# Integration and sample code
Source: https://swapnice.mintlify.app/partners/integration
Implementation requirements and end-to-end examples for the Swapnice SDK and Partner API.
This page is the implementation path for a V1 partner integration: register an app, complete OAuth, create a customer, collect consent, send events, and read a profile.
## Requirements
A confidential backend that can store `client_id` and `client_secret`. Public/browser clients can start connection sessions but must not hold the client secret.
Every `redirect_uri` must match a registered URI exactly. Wildcards are not accepted in production.
Authorization-code flow only. `code_challenge_method` must be `S256`. Access tokens are short-lived (about 15 minutes). Refresh tokens rotate when `offline_access` is granted.
`POST /v1/events` is rejected without a valid consent receipt for the event's `consent_purpose`.
Send an `Idempotency-Key` header on mutating calls. Replays return the original result. A reused key with a different body returns `409 idempotency_conflict`.
Request only the scopes you will call. A typical profile-reading integration uses `connections:write`, `consent:write`, `events:write`, `profile:read`, `claims:read`, and `intents:read`.
## Recommended scope templates
| Template | Scopes | Use |
| ------------------ | -------------------------------------------------------------------------------------------------- | -------------------------- |
| Ingest only | `consent:write` `events:write` | Send consented events |
| Connect and ingest | `connections:write` `connections:read` `consent:write` `consent:read` `events:write` `events:read` | Full SDK connect flow |
| Profile reads | previous + `profile:read` `claims:read` `intents:read` | Read resolved intelligence |
| Full partner | previous + `customers:write` `customers:read` `webhooks:write` `webhooks:read` | Production integration |
## 1. Configure the client
```ts TypeScript theme={null}
import { SwapniceClient } from "swapnice-sdk-ts";
const swapnice = new SwapniceClient({
environment: process.env.SWAPNICE_ENV === "production" ? "production" : "development",
clientId: process.env.SWAPNICE_CLIENT_ID!,
clientSecret: process.env.SWAPNICE_CLIENT_SECRET!,
redirectUri: process.env.SWAPNICE_REDIRECT_URI!,
});
```
```python Python theme={null}
from swapnice import Swapnice
client = Swapnice(
environment="development",
client_id=os.environ["SWAPNICE_CLIENT_ID"],
client_secret=os.environ["SWAPNICE_CLIENT_SECRET"],
redirect_uri=os.environ["SWAPNICE_REDIRECT_URI"],
)
```
```bash cURL theme={null}
export SWAPNICE_API_BASE_URL=https://swapnice-gateway-dev.ceo-cbc.workers.dev
export SWAPNICE_CLIENT_ID=swa_client_...
export SWAPNICE_CLIENT_SECRET=swa_secret_...
export SWAPNICE_REDIRECT_URI=https://partner.example.com/swapnice/callback
```
## 2. Create or upsert the partner customer
The customer is **your** user. You do not need their global Swapnice user id.
```ts TypeScript theme={null}
const { customer } = await swapnice.customers.upsert({
external_id: "partner_user_1842",
profile: { username: "collector1842" },
metadata: { loyalty_tier: "gold", source: "ios" },
});
```
```bash cURL theme={null}
curl -X POST "$SWAPNICE_API_BASE_URL/v1/customers" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: cust_partner_user_1842" \
-d '{
"external_id": "partner_user_1842",
"profile": { "username": "collector1842" },
"metadata": { "loyalty_tier": "gold", "source": "ios" }
}'
```
Example response:
```json theme={null}
{
"status": "created",
"customer": {
"id": "cust_8f2a1c",
"app_id": "app_91b0",
"external_id": "partner_user_1842",
"status": "active",
"profile": { "username": "collector1842" },
"metadata": { "loyalty_tier": "gold", "source": "ios" },
"created_at": "2026-08-26T16:01:00.000Z",
"updated_at": "2026-08-26T16:01:00.000Z"
}
}
```
Customer `profile` accepts public display fields only (`username`, `bio_markdown`, `avatar_url`, `banner_url`, coarse public location). Do not send payment, government-id, or credential data.
## 3. Start a connection session
This is the Plaid-Link-style step: your app opens a Swapnice-hosted connection URL so the user can authorize the link.
```ts TypeScript theme={null}
const session = await swapnice.connectionSessions.create({
customer_id: customer.id,
requested_purposes: ["personalization", "analytics"],
redirect_uri: process.env.SWAPNICE_REDIRECT_URI!,
});
// Open session.connection_url in-app or in the system browser.
```
```bash cURL theme={null}
curl -X POST "$SWAPNICE_API_BASE_URL/v1/connection-sessions" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: cs_partner_user_1842_1" \
-d '{
"customer_id": "cust_8f2a1c",
"requested_purposes": ["personalization", "analytics"],
"redirect_uri": "https://partner.example.com/swapnice/callback"
}'
```
```json theme={null}
{
"session_id": "cs_0b77e2",
"client_token": "cst_...",
"connection_url": "https://swapnice-gateway-dev.ceo-cbc.workers.dev/connect/cs_0b77e2",
"status": "created",
"expires_at": "2026-08-26T16:06:00.000Z"
}
```
Poll `GET /v1/connection-sessions/:session_id` until `status` is `authorized` or `completed`.
## 4. Record the consent receipt
After the user accepts, persist the receipt. Events for a purpose are rejected until this exists.
```ts TypeScript theme={null}
const consent = await swapnice.consentReceipts.create({
customer_id: customer.id,
connection_session_id: session.session_id,
purposes: ["personalization", "analytics"],
terms_version: "2026-08-01",
privacy_policy_version: "2026-08-01",
});
```
```bash cURL theme={null}
curl -X POST "$SWAPNICE_API_BASE_URL/v1/consent-receipts" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: cr_partner_user_1842_1" \
-d '{
"customer_id": "cust_8f2a1c",
"connection_session_id": "cs_0b77e2",
"purposes": ["personalization", "analytics"],
"terms_version": "2026-08-01",
"privacy_policy_version": "2026-08-01"
}'
```
```json theme={null}
{
"receipt_id": "cr_44aa01",
"customer_id": "cust_8f2a1c",
"purposes": ["personalization", "analytics"],
"status": "granted",
"granted_at": "2026-08-26T16:02:12.000Z"
}
```
## 5. Ingest a consented event
The SDK can emit this from an observer. A backend can POST the same payload. Either way, include `consent_purpose` and an `Idempotency-Key`.
```ts TypeScript theme={null}
const accepted = await swapnice.events.create(
{
customer_id: customer.id,
client_event_id: "evt_checkout_8891",
consent_receipt_id: consent.receipt_id,
consent_purpose: "personalization",
event_type: "purchase.completed",
ontology: "intent",
ontology_category: "outcome",
object: "sku_blue_eyes_tin",
occurred_at: new Date().toISOString(),
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: new Date().toISOString(),
},
},
{ headers: { "Idempotency-Key": "evt_checkout_8891" } },
);
```
```bash cURL theme={null}
curl -X POST "$SWAPNICE_API_BASE_URL/v1/events" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: evt_checkout_8891" \
-d '{
"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"
}
}'
```
`202` response. The event is accepted and queued; heavier projection is asynchronous.
```json theme={null}
{
"event_id": "evt_9c21",
"status": "accepted",
"sequence": 7,
"accepted_at": "2026-08-26T16:04:00.120Z",
"processing_status": "queued"
}
```
Batch the same shape to `POST /v1/events/batch` (1–100 events).
## 6. Read the resolved profile and intents
Reads are synchronous and scoped to the caller plus optional `consent_purpose`.
```ts TypeScript theme={null}
const profile = await swapnice.profiles.get("cust_8f2a1c", {
consent_purpose: "personalization",
});
const intents = await swapnice.profiles.intents("cust_8f2a1c", {
consent_purpose: "personalization",
category: "outcome",
});
```
```bash cURL theme={null}
curl "$SWAPNICE_API_BASE_URL/v1/profiles/cust_8f2a1c?consent_purpose=personalization" \
-H "Authorization: Bearer $ACCESS_TOKEN"
curl "$SWAPNICE_API_BASE_URL/v1/profiles/cust_8f2a1c/intents?consent_purpose=personalization&category=outcome" \
-H "Authorization: Bearer $ACCESS_TOKEN"
```
Sample profile and intent bodies are on [outputs](/partners/outputs).
## 7. Register a webhook endpoint
```ts theme={null}
const { endpoint, signing_secret } = await swapnice.webhookEndpoints.create({
url: "https://partner.example.com/swapnice/webhooks",
enabled_events: ["event.processed", "event.failed", "consent.revoked", "profile.updated"],
});
```
Delivery of those events is near-term. Registration and secret issuance are live so you can build the receiver during the pilot.
## OAuth token exchange
If you are not using the SDK auth helper, the raw PKCE exchange is:
```bash theme={null}
# 1. Authorization code
curl -X POST "$SWAPNICE_API_BASE_URL/oauth/authorize" \
-H "Content-Type: application/json" \
-d '{
"response_type": "code",
"client_id": "'"$SWAPNICE_CLIENT_ID"'",
"redirect_uri": "'"$SWAPNICE_REDIRECT_URI"'",
"code_challenge": "'"$CODE_CHALLENGE"'",
"code_challenge_method": "S256",
"customer_id": "cust_8f2a1c",
"scope": "connections:write consent:write events:write profile:read claims:read intents:read"
}'
# 2. Token
curl -X POST "$SWAPNICE_API_BASE_URL/oauth/token" \
-H "Content-Type: application/json" \
-d '{
"grant_type": "authorization_code",
"client_id": "'"$SWAPNICE_CLIENT_ID"'",
"client_secret": "'"$SWAPNICE_CLIENT_SECRET"'",
"code": "'"$AUTH_CODE"'",
"code_verifier": "'"$CODE_VERIFIER"'",
"redirect_uri": "'"$SWAPNICE_REDIRECT_URI"'"
}'
```
Discovery lives at `GET /.well-known/oauth-authorization-server`.
## Error shape
All Partner API errors use a stable machine-readable code and a request id. They never include secrets or private wallet data.
```json theme={null}
{
"error": {
"code": "consent_required",
"message": "A valid consent receipt is required for this event purpose.",
"request_id": "req_3e19c0"
}
}
```
Common codes: `idempotency_key_required`, `idempotency_conflict`, `consent_required`, `invalid_scope`, `invalid_redirect_uri`.
## Next
* [Authentication](/partners/authentication) — PKCE, scopes, and token errors
* [Consent and account linking](/partners/consent)
* [Events and intent ontology](/partners/events-and-intents)
* [Request walkthrough](/partners/request-walkthrough)
* [What data is collected](/partners/data-collection)
* [API reference](/partners/api-overview)
# Partner SDK
Source: https://swapnice.mintlify.app/partners/introduction
Install the Swapnice Partner SDK and start a consented integration.
The Partner SDK is the supported way to integrate Swapnice. It wraps the Partner API so your app can connect a customer you already know, collect purpose-limited consent, send activity events, and read permissioned profile intelligence.
Partners do not call the consumer Firebase backend. Use this SDK (or the [HTTP API](/partners/api-overview) underneath it). Consumer product guides live under [Swapnice App](/introduction).
Implementation requirements, OAuth, consent, events, profile reads, and TypeScript, Python, and cURL examples.
```bash theme={null}
npm install swapnice-sdk-ts
```
## What this documentation covers
These guides are written for technical teams preparing an integration review or pilot.
Implementation requirements, OAuth, consent, events, and sample code.
Where the SDK adds capability beyond raw HTTP, and when to call the API directly.
What the SDK observes, how it is collected, when it is sent, and what never leaves the device.
Profiles, claims, and intent scores partners can read today.
End-to-end path from a consumer action to a Swapnice output.
Development and Production hosts, credentials, and the expected test process.
OAuth 2.1, PKCE, scopes, tokens, and the errors that usually block a first call.
Partner customer vs Swapnice account, purposes in product language, revocation.
Event catalog, intent ontology, claims vs overwrites, and confidence guidance.
How SKUs and collectibles become objects intents can attach to.
Worked partner scenarios for personalization, checkout, and catalog flows.
Ordered sandbox calls from authorize through profile read and revoke.
What integrating the SDK unlocks in the partner product.
OpenAPI playground for every Partner API route.
## How the product is structured
The partner creates a customer record, starts a connection session, and records a consent receipt for specific purposes such as `personalization` or `analytics`.
The SDK or API sends consented activity events. Swapnice accepts them quickly, preserves per-customer order, and archives them asynchronously.
The partner backend reads a resolved profile, underlying claims, and intent scores for that customer and purpose. Reads are synchronous and consent-aware.
## What is live in V1
| Surface | Status | Partner use |
| ------------------------------- | --------- | ---------------------------------------------------------------- |
| OAuth 2.1 apps, PKCE, tokens | Live | Register an app and authenticate |
| Connection sessions | Live | Link a partner customer to a Swapnice account |
| Consent receipts and revocation | Live | Grant, inspect, and revoke purposes |
| Event ingest and batch ingest | Live | Send consented activity |
| Customers, entries, ontologies | Live | Catalog the people and objects events refer to |
| Profiles, claims, and intents | Live | Read resolved facts and intent scores |
| Webhook endpoint registration | Live | Configure delivery destinations |
| Webhook delivery | Near-term | Push `event.processed`, `consent.revoked`, and `profile.updated` |
CRUD endpoints are infrastructure. The product partners integrate for is consented profile intelligence: resolved facts, provenance, and intent scores on top of the same event stream.
## Implementation requirements
A V1 integration needs:
* A confidential partner backend that can store `client_id` and `client_secret`
* HTTPS redirect URIs registered exactly (no production wildcards)
* PKCE (`S256`) for authorization-code exchange
* An idempotency key on every mutating request
* A consent receipt before events are accepted
* Scopes that match the calls you will make (`events:write`, `consent:write`, `profile:read`, and so on)
The [integration guide](/partners/integration) walks through each step with sample TypeScript, Python, and cURL.
## Environments
| Environment | Base URL | Use |
| ----------- | --------------------------------------------------------- | ------------------------------------------- |
| Development | `https://swapnice-gateway-dev.ceo-cbc.workers.dev` | Integration, fixtures, and pilot testing |
| Production | `https://swapnice-gateway-production.ceo-cbc.workers.dev` | Live consented traffic after go-live review |
Call the **gateway** only. Each environment has its own D1 database, queues, Durable Objects, and credentials. See [environments](/partners/environments) and the [testing process](/partners/sandbox).
## Related
* [Environments](/partners/environments)
* [API overview](/partners/api-overview) and the generated [API reference](/partners/api-overview)
# Outputs
Source: https://swapnice.mintlify.app/partners/outputs
Profiles, claims, intent scores, and sample responses partners receive from the SDK and API.
After events are accepted and archived, partners read intelligence back through the profile APIs. V1 returns **resolved profiles**, **claims**, and **intent scores**.
## What a partner can read today
| Output | Endpoint | Scope | Timing |
| ----------------- | ----------------------------------------- | ---------------- | ----------- |
| Resolved profile | `GET /v1/profiles/:customer_id` | `profile:read` | Synchronous |
| Underlying claims | `GET /v1/profiles/:customer_id/claims` | `claims:read` | Synchronous |
| Intent scores | `GET /v1/profiles/:customer_id/intents` | `intents:read` | Synchronous |
| Event status | `GET /v1/events/:event_id` | `events:read` | Synchronous |
| Effective consent | `GET /v1/customers/:id/effective-consent` | `consent:read` | Synchronous |
| Workflow status | `GET /v1/workflows/:workflow_id` | `workflows:read` | Synchronous |
Pass `consent_purpose` on profile reads when you need the consent gate enforced for that call. If the purpose is not granted, the API returns `403 consent_required`.
## Resolved profile
Swapnice does not overwrite contradictory facts. Each attribute is resolved from claims using `highest_confidence_then_most_recent`, with alternatives retained.
```bash theme={null}
curl "https://swapnice-gateway-dev.ceo-cbc.workers.dev/v1/profiles/cust_8f2a1c?consent_purpose=personalization" \
-H "Authorization: Bearer $ACCESS_TOKEN"
```
```json theme={null}
{
"customer_id": "cust_8f2a1c",
"resolved_at": "2026-08-26T16:10:02.000Z",
"facts": [
{
"attribute": "favorite_series",
"resolved_value": "yugioh",
"confidence": 0.91,
"resolution_method": "highest_confidence_then_most_recent",
"consent_purpose": "personalization",
"evidence": [
{
"id": "clm_101",
"customer_id": "cust_8f2a1c",
"attribute": "favorite_series",
"value": "yugioh",
"source": "partner_app",
"observed_at": "2026-08-20T18:00:00.000Z",
"context": "profile_selection",
"consent_purpose": "personalization",
"confidence": 0.91,
"provenance": {
"source_partner": "partner_app",
"source_system": "ios",
"collected_at": "2026-08-20T18:00:00.000Z"
}
}
],
"alternatives": [
{
"value": "pokemon",
"confidence": 0.64,
"reason": "lower_confidence_or_older_claim"
}
]
},
{
"attribute": "recent_purchase_object",
"resolved_value": "sku_blue_eyes_tin",
"confidence": 1,
"resolution_method": "highest_confidence_then_most_recent",
"consent_purpose": "personalization",
"evidence": [],
"alternatives": []
}
]
}
```
What you can do with this:
* Personalize the partner app from facts the user actually granted
* Show provenance when a value is inferred versus explicitly selected
* Keep a defensible audit trail instead of a single overwritten profile field
## Claims
Claims are the unresolved evidence behind a profile. Use them when you need the full history, not just the winner.
```json theme={null}
{
"data": [
{
"id": "clm_101",
"customer_id": "cust_8f2a1c",
"attribute": "favorite_series",
"value": "yugioh",
"source": "partner_app",
"observed_at": "2026-08-20T18:00:00.000Z",
"context": "profile_selection",
"consent_purpose": "personalization",
"confidence": 0.91,
"provenance": {
"source_partner": "partner_app",
"source_system": "ios",
"collected_at": "2026-08-20T18:00:00.000Z"
}
}
],
"pagination": { "has_more": false, "next_cursor": null }
}
```
## Intent scores
Intents are grouped into five categories.
| Category | Meaning | Example objects |
| ------------ | --------------------------------------------------------- | -------------------------- |
| `outcome` | Intent to purchase, participate, or complete a collection | SKU, event, set |
| `sentiment` | Positive or negative preference | Product, brand, experience |
| `affiliate` | Self-label or group association | Team, fandom, community |
| `habit` | Repeated interest or activity | Series, category, venue |
| `activation` | Readiness for the next step in the partner product | Onboarding step, offer |
```json theme={null}
{
"data": [
{
"id": "evt_9c21",
"category": "outcome",
"object": "sku_blue_eyes_tin",
"confidence": 1,
"consent_purpose": "personalization",
"evidence": [
{
"id": "clm_220",
"customer_id": "cust_8f2a1c",
"attribute": "purchase.completed",
"value": "sku_blue_eyes_tin",
"source": "partner_app",
"observed_at": "2026-08-26T16:04:00.000Z",
"consent_purpose": "personalization",
"confidence": 1,
"provenance": {
"source_partner": "partner_app",
"source_system": "ios",
"collected_at": "2026-08-26T16:04:00.000Z"
}
}
]
},
{
"id": "evt_9c18",
"category": "habit",
"object": "yugioh",
"confidence": 0.72,
"consent_purpose": "personalization",
"evidence": []
}
],
"pagination": { "has_more": false, "next_cursor": null }
}
```
In V1, an intent row is derived from archived events for that customer. Use high-confidence `habit` and `outcome` rows to decide what to show next in the partner product.
## Event acceptance and status
Immediately after ingest:
```json theme={null}
{
"event_id": "evt_9c21",
"status": "accepted",
"sequence": 7,
"accepted_at": "2026-08-26T16:04:00.120Z",
"processing_status": "queued"
}
```
Later, `GET /v1/events/evt_9c21`:
```json theme={null}
{
"event_id": "evt_9c21",
"customer_id": "cust_8f2a1c",
"status": "processed",
"accepted_at": "2026-08-26T16:04:00.120Z",
"processed_at": "2026-08-26T16:04:01.004Z"
}
```
Statuses: `accepted`, `processing`, `processed`, `failed`, `rejected`.
## Related
* [Events and intent ontology](/partners/events-and-intents)
* [What a pilot enables](/partners/pilot)
* [Example use cases](/partners/use-cases)
* [API overview](/partners/api-overview)
# What a pilot enables
Source: https://swapnice.mintlify.app/partners/pilot
What integrating the Swapnice SDK unlocks in the partner product.
A V1 pilot is a 60–90 day design-partner integration: one app, one consent flow, one or two event families, and limited API usage. The goal is to prove that consented events become usable profile intelligence the partner can read back into its own product.
## What the partner can do after SDK integration
| Capability | After V1 integrate | Notes |
| ------------------------------------- | ------------------ | ------------------------------------------------ |
| Connect a known user to Swapnice | Yes | Connection session + OAuth |
| Prove consent with a receipt | Yes | Purpose-limited, revocable |
| Send first-party product events | Yes | Ordered, idempotent ingest |
| Read a resolved profile for that user | Yes | Facts, confidence, alternatives |
| Read intent scores | Yes | Outcome, habit, affiliate, sentiment, activation |
| Suppress on revoke | Yes | Ingest and purpose-scoped reads fail closed |
| Build in-app personalization | Yes | From profile + intents |
The partner does **not** automatically receive:
* Raw access to other apps' users
* Wallet, payment, or consumer-app private fields
* The right to use personalization events for any purpose the user did not grant
## How this improves the partner product
```mermaid theme={null}
flowchart LR
A["User action in partner app"] --> B["SDK events from consented users"]
B --> C["Swapnice facts and intents"]
C --> D["Partner product"]
D --> D1["Home shelf and collection prompts"]
D --> D2["Checkout and catalog context"]
D --> D3["Drop revoked users from those reads"]
```
Concrete loop for a first slice:
1. **Before.** The partner app has orders and clicks, but no consented, purpose-scoped profile it can read back as facts and intents.
2. **SDK is live.** Users connect Swapnice and grant `personalization` (and optionally `analytics`). Product actions become claims and intents.
3. **In the product.** The partner backend reads profile and intents, then reorders shelves, highlights a collection in progress, or suppresses objects the user already completed.
4. **On revoke.** Purpose-scoped ingest and reads fail closed. The partner stops using that customer's intelligence for the revoked purpose.
## What the first pilot should implement
Keep the first slice small enough to finish.
| Workstream | Pilot scope |
| ----------- | ---------------------------------------------------------------------------------------------- |
| Surfaces | One consumer app or checkout surface |
| Purposes | `personalization` and `analytics` |
| Events | `purchase.completed` plus one engagement event (`collection.interacted` or `profile.selected`) |
| Reads | Profile + intents in the partner backend |
| Suppression | Revoke path tested in sandbox before go-live |
## Design-partner scope (default)
| Term | Default |
| ----------------- | ------------------------------------------------------------------------------------------- |
| Length | 60–90 days |
| Scope | One app, one or two event families, one consent flow, limited API usage |
| Swapnice provides | SDK, sample consent copy, sandbox app, implementation support |
| Partner provides | SDK in a build, approved consent language, valid receipts |
| Data rights | Process only for granted purposes; aggregated de-identified metrics to evaluate the product |
Partners do not own the consolidated cross-brand profile and do not get raw export of other apps' users.
Success metrics we typically write into the order form:
* SDK live in a sandbox build, then a production build
* Minimum connected users or eligible accounts
* Consent-receipt coverage above an agreed threshold
* Event schema quality above an agreed threshold
* At least one measurable product outcome, such as better personalization or a completed suppression test
## Next
* [Sandbox and testing process](/partners/sandbox)
* [Request walkthrough](/partners/request-walkthrough)
* [Example use cases](/partners/use-cases)
* [Outputs and sample responses](/partners/outputs)
# Request walkthrough
Source: https://swapnice.mintlify.app/partners/request-walkthrough
Ordered sandbox calls from OAuth through profile read, including the payloads and fields that usually trip teams up.
This is the happy path a partner engineer should complete in Development before the technical session. Use the [TypeScript SDK](/partners/integration) or raw HTTP. Postman works if you **do not** use its built-in OAuth 2.0 helper.
Base URL: `https://swapnice-gateway-dev.ceo-cbc.workers.dev` (see [environments](/partners/environments)).
All `/v1/*` calls after token exchange need `Authorization: Bearer $ACCESS_TOKEN`. Mutating calls also need `Idempotency-Key`. Access tokens last about **15 minutes**.
## Field-name cheat sheet
| Call | ID in the body |
| ------------------------------------- | ---------------------------------------- |
| `POST /v1/customers` | `external_id` — not `customer_id` |
| Connection, consent, events, profiles | `customer_id` (the id Swapnice returned) |
| Authorize | `customer_id` you will link |
## 1. Environment
```text theme={null}
SWAPNICE_API_BASE_URL=https://swapnice-gateway-dev.ceo-cbc.workers.dev
SWAPNICE_REDIRECT_URI=https://partner.example.com/swapnice/callback
```
Plus the `client_id` / `client_secret` Swapnice issued. Create a PKCE pair (`code_verifier` 43+ chars, `code_challenge` = Base64URL SHA-256).
A known-good verifier/challenge pair for a first sandbox pass:
| Variable | Value |
| ---------------- | -------------------------------------------------------------------- |
| `code_verifier` | `abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~` |
| `code_challenge` | `ImpiCd8pp4MveCNnbIS7-GXEtB0xF5HMIDoWqvGA5ig` |
## 2. Discovery
```bash theme={null}
curl "$SWAPNICE_API_BASE_URL/.well-known/oauth-authorization-server"
```
No auth. Confirms the host is the Partner API.
## 3. Authorization code
```bash theme={null}
curl -X POST "$SWAPNICE_API_BASE_URL/oauth/authorize" \
-H "Content-Type: application/json" \
-d '{
"response_type": "code",
"client_id": "'"$SWAPNICE_CLIENT_ID"'",
"redirect_uri": "'"$SWAPNICE_REDIRECT_URI"'",
"code_challenge": "'"$CODE_CHALLENGE"'",
"code_challenge_method": "S256",
"customer_id": "sandbox_user_001",
"scope": "connections:write connections:read consent:write consent:read customers:write customers:read events:write events:read profile:read claims:read intents:read webhooks:write tokens:introspect",
"state": "sandbox-walkthrough"
}'
```
`scope` is **space-separated** and must be allowed on the app. Save `code`. Codes expire in about 5 minutes and are single-use.
## 4. Token
```bash theme={null}
curl -X POST "$SWAPNICE_API_BASE_URL/oauth/token" \
-H "Content-Type: application/json" \
-d '{
"grant_type": "authorization_code",
"client_id": "'"$SWAPNICE_CLIENT_ID"'",
"client_secret": "'"$SWAPNICE_CLIENT_SECRET"'",
"code": "'"$AUTH_CODE"'",
"code_verifier": "'"$CODE_VERIFIER"'",
"redirect_uri": "'"$SWAPNICE_REDIRECT_URI"'"
}'
```
Save `access_token`. Optional: `POST /oauth/introspect` with that token (needs `tokens:introspect`).
## 5. Customer
```bash theme={null}
curl -X POST "$SWAPNICE_API_BASE_URL/v1/customers" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: cust_sandbox_user_001" \
-d '{
"external_id": "sandbox_user_001",
"metadata": { "source": "sandbox-walkthrough" }
}'
```
Save `customer.id` as `CUSTOMER_ID`. Replaying the same idempotency key should not create a second customer.
## 6. Connection session
```bash theme={null}
curl -X POST "$SWAPNICE_API_BASE_URL/v1/connection-sessions" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: cs_sandbox_user_001" \
-d '{
"customer_id": "'"$CUSTOMER_ID"'",
"requested_purposes": ["personalization", "analytics"],
"redirect_uri": "'"$SWAPNICE_REDIRECT_URI"'"
}'
```
Save `session_id`. Poll `GET /v1/connection-sessions/:session_id`. In a white-glove sandbox, Swapnice can complete the user accept step with you.
## 7. Consent receipt
```bash theme={null}
curl -X POST "$SWAPNICE_API_BASE_URL/v1/consent-receipts" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: cr_sandbox_user_001" \
-d '{
"customer_id": "'"$CUSTOMER_ID"'",
"connection_session_id": "'"$SESSION_ID"'",
"purposes": ["personalization", "analytics"],
"terms_version": "2026-08-01",
"privacy_policy_version": "2026-08-01"
}'
```
Save `receipt_id`. Then:
```bash theme={null}
curl "$SWAPNICE_API_BASE_URL/v1/customers/$CUSTOMER_ID/effective-consent" \
-H "Authorization: Bearer $ACCESS_TOKEN"
```
## 8. Event
Header `Idempotency-Key` is required.
```bash theme={null}
curl -X POST "$SWAPNICE_API_BASE_URL/v1/events" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: evt_sandbox_001" \
-d '{
"customer_id": "'"$CUSTOMER_ID"'",
"client_event_id": "evt_sandbox_001",
"consent_receipt_id": "'"$RECEIPT_ID"'",
"consent_purpose": "personalization",
"event_type": "collection.interacted",
"ontology": "intent",
"ontology_category": "habit",
"object": "ygo_series_001",
"occurred_at": "2026-08-26T16:04:00.000Z",
"confidence": 0.85,
"payload": { "screen": "series_detail" },
"context": { "platform": "sandbox" },
"provenance": {
"source_partner": "partner_app",
"source_system": "sandbox",
"collected_at": "2026-08-26T16:04:00.000Z"
}
}'
```
`consent_purpose` must be one you granted. Save `event_id`. Replay the same key — you should get the same event. Change the body on that key — you should get `409`.
```bash theme={null}
curl "$SWAPNICE_API_BASE_URL/v1/events/$EVENT_ID" \
-H "Authorization: Bearer $ACCESS_TOKEN"
```
Wait until `status` is `processed` before you expect profile facts.
## 9. Profile, claims, intents
```bash theme={null}
curl "$SWAPNICE_API_BASE_URL/v1/profiles/$CUSTOMER_ID?consent_purpose=personalization" \
-H "Authorization: Bearer $ACCESS_TOKEN"
curl "$SWAPNICE_API_BASE_URL/v1/profiles/$CUSTOMER_ID/claims?consent_purpose=personalization" \
-H "Authorization: Bearer $ACCESS_TOKEN"
curl "$SWAPNICE_API_BASE_URL/v1/profiles/$CUSTOMER_ID/intents?consent_purpose=personalization" \
-H "Authorization: Bearer $ACCESS_TOKEN"
```
If these are empty, the archive has not landed. Retry the event GET, then the profile.
## 10. Optional: webhook, entry, revoke
```bash theme={null}
curl -X POST "$SWAPNICE_API_BASE_URL/v1/webhook-endpoints" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: wh_sandbox_001" \
-d '{
"url": "https://webhook.site/your-unique-id",
"enabled_events": ["event.processed", "consent.revoked"]
}'
curl -X POST "$SWAPNICE_API_BASE_URL/v1/entries" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: en_sandbox_001" \
-d '{
"ontology": "ygo",
"owner_id": "'"$CUSTOMER_ID"'",
"ingestion_origin": "sandbox-walkthrough",
"entry_name": "Blue-Eyes White Dragon",
"value": 12.34,
"fields": { "name": "Blue-Eyes White Dragon" }
}'
curl -X POST "$SWAPNICE_API_BASE_URL/v1/consent-receipts/$RECEIPT_ID/revoke" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: rv_sandbox_001" \
-d '{ "reason": "partner_request" }'
```
Revoke returns `202` and a `workflow_id`. Poll `GET /v1/workflows/:id`. A follow-up event with `consent_purpose=personalization` should now fail.
Cleanup: `POST /oauth/revoke` with `{ "token": "..." }` and no Bearer header.
## Suggested collection order
```text theme={null}
Discovery
Authorize → Token → Introspect
POST /v1/customers
POST /v1/connection-sessions → GET session
POST /v1/consent-receipts → GET effective-consent
POST /v1/events (+ Idempotency-Key) → GET event
GET /v1/profiles/:id
GET /v1/profiles/:id/claims
GET /v1/profiles/:id/intents
POST revoke → confirm 403 on a personalization read
```
## Errors you should hit on purpose
| Test | Expected |
| ---------------------------------------------------------------- | --------------------------------------------- |
| Event with no receipt | `consent_required` |
| Event for a purpose that is not granted | `consent_required` |
| Same idempotency key, different body | `409 idempotency_conflict` |
| Missing `Idempotency-Key` | `idempotency_key_required` |
| `POST /v1/customers` with `customer_id` instead of `external_id` | Validation / server error — use `external_id` |
| Expired token | `401` — re-run authorize + token |
When you can complete this list, you are ready for a productive integration session. Bring the `request_id`s from any call that surprised you.
# Sandbox and testing
Source: https://swapnice.mintlify.app/partners/sandbox
Development and Production hosts, credentials, and the expected implementation and test process for a Swapnice pilot.
Partners integrate against **Development** first. Production credentials are issued after the Development checklist passes. Hosts and isolation details are on [environments](/partners/environments).
## Environments
| | Development | Production |
| ----------- | -------------------------------------------------- | --------------------------------------------------------- |
| API base | `https://swapnice-gateway-dev.ceo-cbc.workers.dev` | `https://swapnice-gateway-production.ceo-cbc.workers.dev` |
| Data | Isolated fixtures and partner-generated test users | Live consented traffic |
| Secrets | Separate `client_id` / `client_secret` | Separate credentials; never reuse Development secrets |
| Rate limits | Generous for integration | Contractual |
| Consent | Real protocol, test users | Real users, production policy versions |
Discovery: `GET {base}/.well-known/oauth-authorization-server`
Local Worker development (`http://localhost:8787`) is available for Swapnice-internal or white-glove sessions. Partner teams should treat Development as the source of truth unless we schedule a shared local walkthrough.
## What you receive at kickoff
Swapnice issues:
* Development `app_id`, `client_id`, and a one-time `client_secret`
* Allowed redirect URIs (you supply these first)
* Allowed scopes and consent purposes for the pilot
* A support alias for integration questions (`api@swapnice.com`)
Store the secret in your secret manager. It is shown once. Rotation is done by Swapnice until self-serve dashboard rotation ships.
Suggested environment variables:
```text theme={null}
SWAPNICE_API_BASE_URL=https://swapnice-gateway-dev.ceo-cbc.workers.dev
SWAPNICE_CLIENT_ID=swa_client_...
SWAPNICE_CLIENT_SECRET=swa_secret_...
SWAPNICE_REDIRECT_URI=https://partner.example.com/swapnice/callback
SWAPNICE_ENV=development
```
## Expected implementation process
Review [SDK versus API](/partners/sdk-vs-api), [integration](/partners/integration), [data collection](/partners/data-collection), and [outputs](/partners/outputs) before the technical session.
Send the exact Development and Production redirect URIs, plus the scope template you intend to use. Swapnice configures the Development app.
PKCE authorize → token → introspect. Confirm token expiry and refresh before writing events.
Create a customer, open `connection_url`, grant purposes, persist the receipt.
One explicit claim (for example `profile.selected`) and one behavioral event (for example `purchase.completed`). Confirm `202`, then `GET /v1/events/:id` until `processed`.
Call the three read routes with `consent_purpose`. Confirm facts, alternatives, and a `403` after you revoke that purpose.
Create the endpoint and verify signature handling. Live delivery is near-term; the receiver should still be ready.
Bring the payloads, failing cases, and scope questions. This is the meeting the docs are meant to make productive.
Production app, production policy versions, go-live event list, and a revoke/suppression test.
## Development checklist
Use this as the acceptance list before asking for production.
* [ ] Development token introspects with the expected scopes
* [ ] Redirect URI mismatches are rejected
* [ ] Customer upsert is idempotent on `external_id`
* [ ] Connection session expires if left unused
* [ ] Events without a receipt return `consent_required`
* [ ] Replaying the same `Idempotency-Key` returns the original event
* [ ] Changing the body on that key returns `409`
* [ ] Profile read returns resolved facts after the archive processes
* [ ] Revoking a purpose blocks later events and purpose-scoped reads
* [ ] No secrets, emails, or payment tokens appear in `payload` or `metadata`
* [ ] Webhook receiver verifies the signing secret (even if delivery is still test-only)
## Suggested test fixtures
| Fixture | Value |
| --------------------------- | ---------------------------------------------- |
| Test customer `external_id` | `sandbox_user_001` |
| Purposes | `personalization`, `analytics` |
| Event A | `profile.selected` / attribute favorite series |
| Event B | `purchase.completed` / a catalog SKU |
| Negative test | Event for a purpose that is not granted |
| Revoke test | Revoke `personalization`, then retry Event B |
## Postman and cURL
Follow the [request walkthrough](/partners/request-walkthrough) for the full ordered sequence (authorize → customer → connect → consent → event → profile → revoke).
If you use Postman:
1. Do not use the built-in OAuth 2.0 helper. Authorize is a JSON `POST` with `customer_id` and PKCE.
2. Set collection auth to Bearer `{{access_token}}` after the token call.
3. Tokens expire in about 15 minutes. Re-run authorize + token when you see `401`.
4. `POST /v1/customers` takes `external_id`, not `customer_id`.
## Common errors
| Error | Fix |
| -------------------------- | ----------------------------------------------------------- |
| `invalid_client_id` | Confirm the Development client Swapnice issued |
| `invalid_grant` | Code expired or reused — run authorize again |
| `invalid_scope` | Scope string must be space-separated and allowed on the app |
| `invalid_redirect_uri` | Must exactly match a registered URI |
| PKCE failure | `code_verifier` must pair with the `S256` challenge |
| `missing_bearer_token` | Add `Authorization: Bearer` |
| `401` on `/v1/*` | Token expired or missing scope |
| `consent_required` | Grant that purpose, or stop sending it |
| `idempotency_key_required` | Add `Idempotency-Key` on mutating calls |
| `500` on `/v1/customers` | Body field is `external_id`, not `customer_id` |
## What "done" looks like for a pilot
The Development integration is complete when:
1. A test user can connect, grant, generate events, and appear in a profile read.
2. Revocation suppresses that user.
3. Your backend can call profile/intent reads without the client SDK.
4. You have a written list of production event types and purposes.
That is also the bar for scheduling the implementation working session: your engineers will have exercised the API and can ask specific questions.
## Getting help
Email [api@swapnice.com](mailto:api@swapnice.com) with:
* Development `app_id` and `request_id` from the failing response
* Route, timestamp, and a redacted payload
* Whether the call came from the SDK or raw HTTP
Do not send client secrets or access tokens in email.
# SDK versus API
Source: https://swapnice.mintlify.app/partners/sdk-vs-api
Where the Swapnice SDK and Partner API overlap, and where their capabilities differ.
Both surfaces talk to the same Partner API. The SDK is the supported way to integrate. The REST API is the stable contract underneath it.
The important difference is not "SDK can read profiles and the API cannot." Both can. The SDK adds client-side collection, consent UX, retries, and typed helpers that a raw HTTP integration would otherwise have to build.
## Quick comparison
| Capability | TypeScript / Python SDK | Raw Partner API |
| ------------------------------------- | --------------------------------------- | ------------------------------------------- |
| OAuth 2.1 + PKCE token exchange | Built in | You implement |
| Connection session + consent UX | SDK presents or embeds the connect flow | You host redirects and poll session status |
| Automatic client-event capture | Yes, when you register observers | No. You must construct and POST every event |
| Device and session context enrichment | Added by the runtime SDK | You must attach `context` yourself |
| Offline queue and retry | Yes | You implement |
| Idempotency keys | Generated and retried for you | Required header on every mutating call |
| Typed requests and errors | Generated from the OpenAPI contract | You validate payloads |
| Server-to-server batch ingest | SDK `events.batch` helper | `POST /v1/events/batch` |
| Profile, claims, and intent reads | SDK read methods | `GET /v1/profiles/:id` and related routes |
| Webhook, app, and catalog admin | Thin SDK wrappers | Full HTTP surface |
| Consumer app Firebase APIs | Not available | Not available |
If a teammate heard that "the SDK has more functionality," this is what that means: the SDK can observe in-app behavior, run the Swapnice connection and consent flow, and keep events ordered through retries. The API can accept the same events and return the same profiles, but it will not collect or consent for you.
## Use the SDK when
* You are embedding Swapnice in a mobile app, web app, or checkout surface
* You want Swapnice to collect consented first-party events as users move through your product
* You need the hosted connection and consent experience rather than a custom OAuth UI
* You want typed clients, automatic idempotency, and token refresh
Install the generated client:
```bash theme={null}
npm install swapnice-sdk-ts
```
```ts theme={null}
import { SwapniceClient } from "swapnice-sdk-ts";
const swapnice = new SwapniceClient({
environment: "development",
clientId: process.env.SWAPNICE_CLIENT_ID!,
clientSecret: process.env.SWAPNICE_CLIENT_SECRET!,
});
```
The same OpenAPI contract also generates a Python client for backend jobs.
## Use the API directly when
* A partner backend or ETL job is the only writer
* You already collected consent in your own UI and only need to POST receipts and events
* You are registering webhook endpoints, managing apps, or rebuilding ontologies
* You are debugging with cURL, Postman, or an API gateway
Every SDK method maps to a documented HTTP route in the [API reference](/partners/api-overview). Replays and server jobs can call those routes without the client SDK.
## What neither surface does
Neither the SDK nor the Partner API exposes Swapnice consumer-app internals:
* Wallet, Dwolla, or Plaid credentials
* NFC pack activation internals
* Private account fields from the consumer user record
* Another partner's raw events or unresolved identities
Installing the SDK does not expand the purposes a user granted. Events collected for `personalization` stay unavailable for another purpose until that purpose is granted.
## Capability split in practice
```mermaid theme={null}
flowchart LR
subgraph sdk["SDK runtime"]
UI["Connect and consent UI"]
Cap["Event observers"]
Q["Retry and idempotency queue"]
Auth["PKCE and token refresh"]
end
subgraph api["Partner API"]
Sess["Connection sessions"]
Cons["Consent receipts"]
Ev["Event ingest"]
Prof["Profiles, claims, intents"]
Cat["Apps, customers, entries"]
end
UI --> Sess
UI --> Cons
Cap --> Q
Auth --> api
Q --> Ev
Ev --> Prof
```
The usual split is:
1. **Client SDK** in the consumer-facing app to connect, collect consent, and observe product interactions.
2. **Partner backend + API or server SDK** to upsert customers, register webhooks, and read profiles and intents for the partner product.
See [integration](/partners/integration) for the combined implementation path.
# Example use cases
Source: https://swapnice.mintlify.app/partners/use-cases
Worked integration scenarios for product, retail, and catalog partners.
These are the implementation patterns most partners start with. Each one uses the same building blocks: customer, connection, consent, events, profile read.
## 1. In-app personalization
**Who:** A consumer app that already has logged-in users.
**Consent:** `personalization`, `analytics`
**What you send:** Views, saves, self-labels, and purchases as events.
**What you get back:** Resolved facts (`favorite_series`, recent objects) and `habit` / `outcome` intents for that user.
**How you use it:** Reorder home-shelf modules, highlight a collection the user is completing, or hide objects they have already purchased.
```ts theme={null}
const intents = await swapnice.profiles.intents(customerId, {
consent_purpose: "personalization",
category: "habit",
});
const primary = intents.data[0];
if (primary && primary.confidence >= 0.7) {
return { shelf: "continue_collection", object: primary.object };
}
```
## 2. Retail or event checkout
**Who:** A merch site, POS, or stadium iPad flow.
**Consent:** Collected at account creation or checkout. Typical purposes: `personalization` and `analytics`.
**What you send:** `purchase.completed` with SKU, amount, and optional location or storefront context.
**What you get back:** A high-confidence `outcome` intent plus a processed event id you can join to the order.
**How you use it:** Confirm that Swapnice received the conversion, then use later profile reads to see whether the buyer is a completionist, a first-time buyer, or a fandom affiliate.
This is the pattern for a partner that already has Shopify or POS events and wants Swapnice to turn them into consented profile intelligence.
A concrete stadium shape we already design for:
1. Account creation (or a short iPad form) requires Swapnice connect — consent is not a buried toggle
2. JS SDK on the merch site (for example a Shopify storefront) sends views and purchases
3. POS / iPad at the venue sends the same `purchase.completed` body from the checkout lane
4. The partner app can later show the user "everything you bought from us" using their own orders **plus** Swapnice intents for what they are likely to complete next
Consent is part of getting the app or completing checkout, not an optional afterthought.
## 3. Catalog-aware collectibles
**Who:** A partner whose objects are cards, SKUs, or fandom entries.
**What you send:** Ontology-backed [entries](/partners/catalog) (`ygo`, `pokemon`, …) and events that reference those entry ids.
**What you get back:** Intents keyed to catalog objects, so a completionist on a set is a well-defined profile fact rather than free text.
```ts theme={null}
await swapnice.entries.upsert({
ontology: "ygo",
owner_id: "partner_catalog",
ingestion_origin: "partner_catalog",
entry_name: "Blue-Eyes White Dragon",
fields: {
name: "Blue-Eyes White Dragon",
yugipedia_uid: "46986414",
pcid: "2560454",
},
});
```
## 4. Consent change and suppression
**Who:** Any production integration.
**What happens:** The user revokes `personalization` in Swapnice or via the partner.
**What you must do:**
```ts theme={null}
await swapnice.consentReceipts.revoke(receiptId, {
purposes: ["personalization"],
reason: "user_request",
});
```
Immediately:
* New `personalization` events are rejected
* Profile reads with `consent_purpose=personalization` return `403`
* The partner product must stop using that customer's intelligence for the revoked purpose
Implement this path before go-live.
## Suggested first slice
For a design-partner pilot, implement one narrow slice rather than every use case:
1. One consent flow (`personalization` + `analytics`)
2. One or two event families (`purchase.completed` and `collection.interacted`)
3. One profile read in the partner backend
4. One sandbox test user walked through [the testing process](/partners/sandbox)
That is enough for a productive technical session: your team can bring real payloads and questions instead of reviewing the contract in the abstract.
# Webhooks
Source: https://swapnice.mintlify.app/partners/webhooks
Register endpoints, verify signatures, and handle Swapnice async events.
Ingest and consent revocation are accepted quickly. Work that happens after that — archive and profile refresh — is asynchronous. Webhooks are how your backend learns without polling.
Endpoint **registration** is live in V1. **Delivery** of signed posts is near-term. Build the receiver during the pilot so you are ready when delivery is turned on.
## Register an endpoint
```ts theme={null}
const { endpoint, signing_secret } = await swapnice.webhookEndpoints.create({
url: "https://partner.example.com/swapnice/webhooks",
enabled_events: ["event.processed", "event.failed", "consent.revoked", "profile.updated"],
});
```
```json theme={null}
{
"endpoint": {
"id": "wh_91",
"url": "https://partner.example.com/swapnice/webhooks",
"enabled_events": ["event.processed", "event.failed", "consent.revoked", "profile.updated"],
"status": "active",
"created_at": "2026-08-26T16:20:00.000Z"
},
"signing_secret": "whsec_..."
}
```
Store `signing_secret` once. List endpoints with `GET /v1/webhook-endpoints`.
## Event types
| Type | When | V1 status |
| ------------------------------------------- | -------------------------------------- | ------------------------------- |
| `event.processed` | Archive finished for an ingested event | Config live, delivery near-term |
| `event.failed` | Projection failed | Config live, delivery near-term |
| `consent.revoked` | A purpose or receipt was revoked | Config live, delivery near-term |
| `profile.updated` | Resolved facts changed for a customer | Config live, delivery near-term |
| `connection.created` / `connection.revoked` | Link lifecycle | Designed |
| `consent.granted` | Receipt created | Designed |
Until delivery ships, poll `GET /v1/events/:id` and `GET /v1/workflows/:id`.
## Designed delivery envelope
```json theme={null}
{
"id": "wev_3301",
"type": "event.processed",
"created_at": "2026-08-26T16:04:01.004Z",
"data": {
"event_id": "evt_9c21",
"customer_id": "cust_8f2a1c",
"status": "processed"
}
}
```
Verify the signature with the endpoint secret before you trust the body. Reject unsigned or stale timestamps. Respond `2xx` quickly; do heavier joins on your side asynchronously.
## What to do with each event
| Type | Partner action |
| ----------------- | ------------------------------------------------------------------------- |
| `event.processed` | Safe to read profile / intents for that customer |
| `event.failed` | Inspect the event, fix schema, retry with a new idempotency key if needed |
| `consent.revoked` | Stop using that customer's intelligence for the revoked purpose |
| `profile.updated` | Refresh a cached personalization payload |
`consent.revoked` is the suppression path to implement before go-live, even if you poll rather than wait for the webhook.
# Contacts
Source: https://swapnice.mintlify.app/swapnice-features/contacts
Connect with other users and manage your contacts.
08/26/24
### What are Contacts?
Contacts are a feature of the Swapnice platform that allows users to **connect with each other**. They are designed to help users interact with each other in a more personal and engaging way. Contacts are a way to **keep track of the people you have interacted with** on the platform, and to easily communicate with them. They are an essential part of the Swapnice platform, enabling users to build relationships and engage with each other in a meaningful way.
### Users should be able to contact each other in two ways.
* The first is via a listing. A user should be able to select a listing, and then press a button to contact the seller with an offer or questions.
* The second is via profile. Users should be able to view each other's profiles and message directly from there.
* Naturally, users will be able to accept or decline message requests, which means that no unwanted messaging will take place.
### How do I find my Contacts?
The easiest way to find all the contacts you have saved is to navigate to the Contacts page. Once on that page, you can view all the contacts you have saved. You can also click on the Contacts tab in the menu bar to get to the Contacts Page.
Once on the contacts page, you will be presented with a list of all the contacts you have saved. You can click on any of the contacts to view more information about them.
### How do I send requests?
There are two main ways to add someone to your contacts list:
1. You can add someone to your contacts list by clicking on the **Add Contact** button on their **profile** page. This will send them a contact request, which they can accept or decline. You can check on your request in the **Sent Requests** tab on the contacts page.
2. You can also add someone to your contacts list by clicking on the **Add Contact** button on a **listing** page. This will send them a contact request, which they can accept or decline. You can check on your request in the **Sent Requests** tab on the contacts page.
### How do I accept requests?
When someone sends you a contact request, you will receive a notification. You can view the request by navigating to the **Contacts** page and clicking on the **Contact Requests** tab. From there, you can accept or decline the request. If you have no requests, the tab will be say "No Pending Contact Requests".
### How do I interact with Contacts?
The main way to interact with contacts is by sending them **messages**. You can do this by clicking on the contact itself, which launches a **chat window**. You can then negotiate about a particular [**listing**](../swapnice-features/listings), talk about the new [**personas**](../swapnice-features/personas), or just talk about anything youd like.
# Hubs
Source: https://swapnice.mintlify.app/swapnice-features/hubs
Connect with Switems and Personas in Hubs.
08/13/24
### What are Hubs?
Hubs are digital spaces where you can connect with [Switems](../swapnice-features/switems) and [**Personas**](../swapnice-features/personas). They are designed to help you interact with your Switems and Personas in a more engaging and immersive way. Some hubs are customisable, allowing you to create unique spaces for your Switems and Personas. You can use Hubs to chat with Personas, view Switem information, list on the marketplace and much more. They are the backbone of the Swapnice platform, enabling you to create and share digital experiences with your Switems and Personas.
Hubs contain the following information:
* **HubID** - The unique identifier of the hub. This is the set of letters and numbers that starts with a '-' below the date.
* **Hub Name** - The name of the hub. This is displayed at the top of the hub.
* **Hub Description** - A brief description of the hub. This is displayed below the hub name.
* **Hub Image** - The image associated with the hub. This is displayed at the top of the hub.
* **Hub Date** - The date the hub was created. This is displayed below the description of the hub.
* **Owner of the Hub** - The user who owns the hub. This is displayed in a plaque at the bottom of the hub.
### How do I create a Hub?
A hub is automatically created when you create a new [Switem](../swapnice-features/switems) or a [**Personas**](../swapnice-features/personas). Each Switem and Persona has its own hub where you can interact with it.
### How do I find Hubs?
The easiest way to find all the hubs you own or have owned is to navigate to the [Inventory](../swapnice-features/inventory) page. Once on that page click on a switem hub or a persona's hub to interact with it. You can also click on the [Personas](../swapnice-features/personas) tab in the menu bar to view all the personas have interacted with.
### How do I interact with Hubs?
Once you are on the Hub you have these options:
* **Chat with Persona** - This allows you to chat with the persona associated with the hub.
* **List on Marketplace** - This allows you to list the switem on the marketplace. This is available only for switems.
* **View Information (Hub option)** - This allows you to view all the information stored on the switem.
* **[Swapps](../swapnice-features/swapps)** - This allows you to interact with the swapps associated with the switem.
# Inventory
Source: https://swapnice.mintlify.app/swapnice-features/inventory
Track and manage your Hubs in your Inventory.
08/13/24
### What is the Inventory?
The Inventory is a feature of the Swapnice platform that allows you to track and manage your Hubs. It is a digital record of all the Hubs you own, including their details and the items they contain. The Inventory is designed to help you keep track of your Hubs and the items they contain, making it easier to manage your collection.
The inventory page has 2 subpages:
1. **Currently Owned**
Shows you all the [**hubs**](../swapnice-features/hubs) you currently own. You can click on any of the icons to get to the hub for that [**Switem**](../swapnice-features/switems). These are ordered by the most recent hub you have interacted with.
2. **Previously Owned**
Shows you all the [**hubs**](../swapnice-features/hubs) you previously owned. You can click on any of the rows in the table to get to the hub for that [**Switem**](../swapnice-features/switems). These are ordered by the most recent hub you have interacted with.
### How do I navigate to the Inventory?
On Swapnice, you can access your Inventory by clicking on the Inventory icon in the menu bar. The menu bar is available once you click on the hamburger icon.
# Listings
Source: https://swapnice.mintlify.app/swapnice-features/listings
Create and manage listings for your items.
08/20/24
### What is a Listing?
1. Listings are atomic descriptions of something that can be gifted, sold, or swapped.
2. They all contain information regarding about something beneficial to another user.
3. Each user can easily make a listing for something of value, that other users can read and determine whether they are interested.
4. These listings need to be easy to make, yet allow for a very high level of customizability because users will want to be able to make sure they have the best chance to produce a transaction.
5. Each listing must also follow very basic requirements in order to avoid breaking any laws or disturbing other users.
6. Swapped should also create a system where it is punishing to write bad listings.
7. There should also be a limit to the amount of listings a typical user can make per day and have running in general.
### What are Listings used for?
Listings are a best effort attempt to allow users to market their items or services to others in order to promote transactions.
### How do I create a Listing?
Creating a listing is easy. You can create a listing by following these steps:
1. Open the Swapnice menu bar by clicking on the hamburger icon on the bottom-right of the screen (on mobile).
2. Click on Listings.
3. Click on the Create Listing button.
4. Fill out the form with the required information. Upload a photo of the item you would like to sell, trade or give away (required). You can also **preview** the listing before you create it, by clicking on the **Preview** button.
5. Agree to the terms of service and click the Create Listing button.
### How do I find my Listings?
The easiest way to find all the listings you have created is to navigate to the Listings page. Once on that page, you can view all the listings you have created. You can also click on the Listings tab in the menu bar to get to the Listings Page.
### How do I find other people's Listings?
On swapnice finding other people's listings is very easy. As soon as you are **logged in** to Swapnice you get the **Latest** Page. This page contains a mix of everything swapnice has to offer to users. On this page you can find listings from other users.
You can also use the **Search** feature to find listings from other users. Simply type in the name of the item you are looking for and hit enter. You will be presented with a list of listings that match your search query.
### How do I interact with Listings?
You can click on a listing to view more information about it. You can also click on the user's profile to view more information about them.
You have these options when you are viewing a listing:
1. **Add Contact**: This allows you to add the user to your contacts list.
2. **Make Offer**: This allows you to make an offer on the listing.
3. **Persona Chat**: This allows you to chat with the [**persona**](../swapnice-features/personas) associated with the listing.
4. **Animated Chat** ( Pro Feature ): This allows you to chat with the persona associated with the listing using animated chat.
The Listing page has valuable information about the listing, such as:
1. **Pictures**: Pictures of the item being listed.
2. **Description**: A detailed description of the item being listed.
3. **Price**: The price of the item being listed.
4. **Location**: The location of the item being listed.
5. **Type**: The type of listing (e.g. for sale, for trade, for free).
6. **Date**: The date the listing was created.
7. **Owner**: The user who created the listing.
8. **Reputation**: The reputation of the user who created the listing.
9. **Category**: The category of the listing.
10. **Sub-Category**: The sub-category of the listing.
# Personas
Source: https://swapnice.mintlify.app/swapnice-features/personas
Engaging Customisable AI Characters
08/13/24
### What are Personas?
Personas are AI characters that you can customise and use to interact with other Switems and Personas. They can be used to provide information, or simply to chat with. You can create and customise your own Personas, or use pre-made ones from the Swapnice community.
### How do I use Personas?
Personas can be used in Hubs, where you can interact with the Persona associated with the Switem. Your Inventory also contains a list of all the Personas you own as well as the ones you have interacted with.
### How do I create a Persona?
On Swapnice, creating a new persona is as simple as clicking a couple buttons.
A simple form allows every user to try out our characters. They are able to customise the character's name, facts about their appearance, and personality traits. Based off of these descriptions and traits, the we generate a unique character for the user to interact with. Also, the user is able to get a unique animation of their character by clicking the "Animated" button. You are able to customise the character through these options :-
* **Persona Full Name** - The full name of the persona.
* **Facts about Persona** - A thorough description of the persona.
* **Animated** - ( Requires Pro Membership ) A toggle switch that allows the user to get a unique animation of their character.
* **Add Optional Info** - Additional information about the persona.
* **Starting Prompt** - This is used to create the animation. This describes the persona's starting frame for the animation.
* **Ending Prompt** - This is used to create the animation. This describes the persona's ending frame for the animation. The animation will be a smooth transition from the starting prompt to the ending prompt.
* **Background Top Prompt** - This describes the persona's background top frame for the animation.
* **Background Bottom Prompt** - This is used to create the animation. This describes the persona's background bottom frame for the animation.
* **Category** - Category of the persona.
* **Sub-Category** - Sub-Category of the persona.
* **Serial Number** - Serial number of the persona.
* **Meta Tag** - Meta tag of the persona.
* **Images** - The user is able to upload upto 16 images of the persona.
### How do I interact with Personas?
You can interact with Personas in Hubs, where you can chat with them, ask them questions, or simply hang out.
You can also discover new personas to chat with using the Feed. The feed is the current homepage of swapnice. It is designed to provide you with mini experiences that you can interact with. The feed displays personas to chat with randomly and you are able to chat with them by clicking the chat button.
### How do I find the Personas I own?
In the Swapnice Menu, you have an option called personas. This is where you can find all the personas you have interacted with. You can also find the personas you own in your [Inventory](../swapnice-features/inventory).
# Swap Wallet
Source: https://swapnice.mintlify.app/swapnice-features/swap-wallet
09/02/24
### What is a Swap Wallet?
A Swap Wallet is a digital wallet that allows users to store, send, and receive money. It is designed to help users manage their monetary transactions on the Swapnice platform. The Swap Wallet is integrated with the Swapnice platform, allowing users to make payments, receive funds, and track their transactions. It is designed to be secure, easy to use, and convenient for users.
### How do I set up a Swap Wallet?
To set up a Swap Wallet:
1. Sign-up for a Swapnice account.
2. Go to the **Swap Wallet** section in your menu bar.
3. You will be prompted to create a new wallet.
4. You will be asked to complete a multi-part form to sign up for the wallet.
* Legal First Name
* Legal Last Name
* Date of Birth
* Phone Number
* Address
5. Once you have completed the form, your wallet will be created.
### How do I add funds to my Swap Wallet?
To add funds to your Swap Wallet:
1. Go to the **Swap Wallet** section in your menu bar.
2. Click on the **Add Funds** button.
3. You will be prompted to confirm the amount you would like to add to your wallet.
4. Once you have confirmed the amount, the funds will be added to your wallet.
### How do I withraw funds from my Swap Wallet?
To withdraw funds from your Swap Wallet:
1. Go to the **Swap Wallet** section in your menu bar.
2. Click on the **Withdraw Funds** button.
3. After confirming, the funds will be withdrawn from your wallet.
### How do I view my transaction history?
The **Transaction History** page allows you to view all the transactions you have made with your Swap Wallet. You can view the date, time, amount, and description of each transaction. This page is designed to help you keep track of your transactions and manage your funds effectively.
### How do I view/change my primary payment method?
The **Edit Funding Account** page allows you to view and change your primary payment method. You can add a new payment method, edit an existing payment method, or remove a payment method. This page is designed to help you manage your payment methods and ensure that your funds are secure.
### How do I view my wallet balance?
You can view your account balance by:-
1. Using the **Transfers** page to look at your monthly transactions
2. The **Add Funds** page to see your current balance
3. The **Withdraw Funds** page to see your current balance
# Swap Badge
Source: https://swapnice.mintlify.app/swapnice-features/swapbadge
NFC microchip that securely stores an item's history, identity details, and swapp data.
08/13/24
### What is a Swap Badge?
The Swap Badge is an NFC microchip that securely stores an item's history, identity details, and swapp data. You attach a **Swap Badge** to an item to make it a [**Switem**](../swapnice-features/switems). It is a digital passport that allows items to be authenticated and verified. The Swap Badge is a key component of the Switems ecosystem, enabling users to track and trace the proven history of an item.
### Features
* **Secure Storage**: The Swap Badge securely stores an item's history, identity details, and swapp data.
* **Authentication**: The Swap Badge allows items to be authenticated and verified.
* **Traceability**: The Swap Badge enables users to track and trace the proven history of an item.
* [**Hubs**](../swapnice-features/hubs): The Swap Badge can be used to connect with [**Swapps**](../swapnice-features/swapps) and [**Personas**](../swapnice-features/personas) in Hubs.
* [**Swapps**](../swapnice-features/swapps): The Swap Badge enables digital brand-specific experiences through interactions with Switems, Personas, and Swap badges.
* **Privacy**: The Swap Badge ensures the privacy and security of an item's data. We have a special focus on privacy and security, and we are committed to protecting your data.
### What NFC microchip is used in the Swap Badge?
We use the **NTAG424DNA** NFC microchip in the Swap Badge. The NTAG424DNA is a secure NFC Forum Type 4 Tag that provides state-of-the-art security features. It is designed to protect data integrity and privacy, making it ideal for secure applications such as the Swap Badge.
We get our badges from [NXP Semiconductors](https://www.nxp.com/). NXP is the world's leading provider of secure connectivity solutions for embedded applications.
### How does Swapnice interface with the Swap Badge?
We are the proud developers of multiple proprietary software libraries written in **Swift** and **Java** that allow us to interface with the Swap Badge. These libraries ensure smooth communication between the data stored on the Swap Badge and the Swapnice platform.
We use **LRP** (Leakage Resilient Primitives) to communicate with the Swap Badge. LRP is a standard protocol used to communicate with NFC tags and readers. It allows us to read and write data to the Swap Badge securely.
We also **rotate the AES encryption keys** on the Swap Badge to ensure the security of the data stored on it. This adds an extra layer of security to the Swap Badge, making it virtually impossible for unauthorized users to access the data stored on it.
### What is stored on the Swap Badge?
The Swap Badge securely stores the following information **on the microchip**:
* **HubID**: The unique identifier of the Hub associated with the item.
* **Product Name**: The name of the item.
* **AES Encryption Keys**: The encryption keys used to secure the data on the Swap Badge.
# Swapp Store
Source: https://swapnice.mintlify.app/swapnice-features/swapp-store
Buy and sell Swapps for Switems.
08/13/24
### What is the Swapp Store?
The Swapp Store is a marketplace where you can buy and sell Swapps for Switems. Swapps are software applications that give your Switems and Personas extra capabilities. They can be used to enhance the functionality of your Switems and Personas, making them more useful and engaging. The Swapp Store is designed to help you find the right Swapps for your Switems and Personas, making it easy to customise and personalise your digital experiences.
### How does the Swapp Store work?
The Swapp Store works like any other online marketplace. You can browse through the available Swapps, read reviews, and purchase the ones that you like. Once you have purchased a Swapp, you can download it and install it on your Switem or Persona. Swapps are designed to be easy to use and install, so you can start using them right away.
### What kind of Swapps are available in the Swapp Store?
Currently the Swapp Store offers its debut Swapp:
* **FearZU**: A Swapp created for the card company FearZU. It is a digital brand-specific experience that allows customers to access exclusive leaderboards and compete with other players. The Swapp is designed to enhance the gaming experience and provide additional rewards for customers. You can check out the [FearZU Swapp Demo](../swapnice-features/swapps) to see it in action.
# Swapps
Source: https://swapnice.mintlify.app/swapnice-features/swapps
Digital Brand-Specific Experiences through Interactions with Switems, Personas, and Swap badges.
08/13/24
### What are Swapps?
Swapps are digital brand-specific experiences that are enabled through interactions with [**Switems**](../swapnice-features/switems), [**Personas**](../swapnice-features/personas), and [**Swap badges**](../swapnice-features/swapbadge). Swapps are designed to provide users with a unique and engaging way to interact with brands and their products. They allow users to access exclusive content, promotions, and rewards by scanning Switems, Personas, and Swap badges.
### How do Swapps work?
Swapps work by leveraging the data stored on Switems, Personas, and Swap badges to create customised experiences for users. When a user interacts with a Switem, Persona, or Swap badge, the data stored on the device is read and used to trigger specific actions within the Swapp. These actions can include displaying product information, offering discounts, or providing access to exclusive content.
Swapps are comprised of these components:
1. **Views**: Views are the different screens or pages that make up the Swapp. They can include product pages, promotional offers, and interactive experiences.
2. **Widgets**: Widgets are interactive elements that allow users to engage with the Swapp. They can include buttons, sliders, and forms.
3. **Config**: Config are the settings and parameters that control the behaviour of the Swapp.
4. **Swapp Bucket**: The Swapp Bucket is a cloud-based storage system that stores the data and assets used by the Swapp. It allows brands to update and manage their Swapps remotely. The swapp bucket is made up of 2 parts
* **Swapp Data**: This is a JSON file that contains all the data used by the Swapp.
* **Swapp Blob**: This is a storage system that allows the Swapp to access images, videos, and other assets.
### How do I access Swapps?
Swapps can be accessed by scanning Switems using the Swapnice app. When a user scans a Switem, the [Hub](../swapnice-features/hubs) associated with that Switem is automatically opened. From the Hub, users can access the Swapp by clicking on the Swapp button. This will launch the Swapp and display the customised experience for that [Switem](../swapnice-features/switems).
### How do I create a Swapp?
We plan to release docs for this soon.
## Swapp Demo: \[FearZU]
Check out one of our success stories: [FearZU](https://fearzu.com/). They are an indie card game company that used Swapps to create a unique and engaging experience for their customers. By scanning their Switems, customers were able to access exclusive leaderboard comprised of only people who have purchased this card. This allowed them to compete with other players and win prizes.
Below you can see a demo of the Swapp in action:
# Switems
Source: https://swapnice.mintlify.app/swapnice-features/switems
Digitally Enhanced Items
08/13/24
### What are Switems?
Switems are digitally enhanced items that can be used in the Swapnice ecosystem. They can be used in various ways, such as in [**transactions**](../swapnice-features/transactions) and in the [**Swapp Store**](../swapnice-features/swapp-store). **Attaching a [**Swapp Badge**](../swapnice-features/swapbadge) to an item makes it a Switem.**
### How do I Create a Switem?
Creating a Switem is easy. You can create a Switem by attaching a [**Swap Badge**](../swapnice-features/swapbadge) to an item.
On swapnice you can create a Switem by following these steps:
* **Attach a Swap Badge**: Attach a Swap Badge to an item to make it a Switem.
* **Register the Switem**: Register the Switem on the Swapnice platform.
* **Create the Switem**: Create the Switem by entering information such as the item's name, description, and images.
* **Customise the Switem**: Customise the Switem by adding information such as the item's name, description, and images. ( See [**Personas**](../swapnice-features/personas) for full customisation options )
* **Use the Switem**: Use the Switem in transactions, in conjuction with the Swapp Store, and in other features of the Swapnice ecosystem.
### How do I Interact with a Switem?
Interaction with Switems can be done through clicking the red heart on mobile devices. This brings up the Switem Features page. You are presented with 4 buttons.
* The first button is an **animated red heart** in the middle of the screen. This is an **NFC Quick-Read** button. When you activate this feature you get the option to **scan a switem** to get to its [**Hub**](../swapnice-features/hubs). You **do not** rotate the keys when you access the information in this manner.
Under the red heart there are **3 buttons**:
* **Create Switem** - This button allows you to create a Switem.
* **Create Persona** - This button allows you to create a Persona.
* **Read Switem** - This button allows you to read all the data stored on a Switem, rotates the AES keys as well as navigates you to the hub. This may allow you to access **secrets**.
### What are secrets and why do they exist on Switems?
Secrets are hidden information that can be accessed by rotating the AES keys on a Switem. This information is not visible to the user unless they have the correct permissions. Secrets can be used to store sensitive information such as passwords, private keys, brand specific information, and other confidential data. Secrets are encrypted using the AES encryption keys stored on the Switem, making them secure and tamper-proof.
# Transactions
Source: https://swapnice.mintlify.app/swapnice-features/transactions
Secure and Transparent Transactions
09/02/24
### What are Transactions?
Using funds from their swap wallet each user can exchange items with other users.
### Transactions
Since each user will have a single swap wallet the wallet will accurately reflect any changes they observe with their swap wallet funds. Each transaction will also need to have information about precisely when it occurred and where the funds came from.
### How Do I Create a Transaction?
1. Find a [**Listing**](../swapnice-features/listings) you are interested in.
2. Click on the **"Make Offer"** button.
3. This launches the transaction page where you can make an offer.
4. You should add your offer to the transaction. This can be a swap or money. A Swap is a one-to-one exchange of items.
5. Once you have added your offer, it should show up on the offer section.
5. Click on the **"Approve"** button to send the offer to the seller.
6. The seller will then have the option to **"Approve"** or **"Reject"** the offer. If they approve the offer, the transaction will be completed once you click the **Trade** button.
7. You are asked to complete a short survey about the transaction. You have the option to chat with the other party.
The listing will then be added to your [**inventory**](../swapnice-features/inventory).
### How Do I Find My Transactions?
On Swapnice you can find your transactions by:
1. Navigating to the Transactions page. Click on the menu page and select Transactions.
2. On the transactions page, you can see two tabs: **"You Started"** and **"Others Startes"**. You can view all the transactions you have started and all the transactions others have started with you.
### Transactions: Items and Listings
The terms Items and Listings are synonymous, transactions store one or more ID's of listings which are the items that were swapped. The listings detail all of the information for the items when they were, added to the trade, what they’re worth (if possible), the status of this item being a “real” item or not (one that is clearly fake, or one with an item code, etc.)
# Activate Swap Badge
Source: https://swapnice.mintlify.app/swapnice-quick-start/activate_swap_badge
Step-by-step guide on activating Swap Badges.
10/11/25
## Video
### Welcome to Swapnice!
In this demo, we’ll show you how the **Swapnice Swap Badge** works.
Let’s say you’re a manufacturer and you’ve just produced a product. For this example, we’re using a **Funko Pop** (not the plushie in front of us). We’ve already attached an **NFC tag** under the chin—hidden from view. You can use any shape of NFC tag: circle, rectangle, etc.
Now, we’ll pretend this Funko Pop has just been manufactured with the NFC tag embedded. Using the **Swapnice app**, we’ll program the tag to serialize the item in our database—making it uniquely identifiable.
Open the **Swapnice app**, go to the **hamburger menu**, then tap **Inventory**. Under the **Switems** tab, create a new **Switem** and fill in the product details. Once submitted, it’s time to activate the **Swap Badge** by following the on-screen steps.
With the badge already attached, we’ll now link it. Hold your phone to the item for at least **two seconds** to complete the scan. The item is now registered as a **Switem**.
To interact with it, tap the **heart tab** at the bottom of the app, then the heart icon inside the box. Scan your Switem to view its details—such as creation date, serialization number, and owner.
A real-world example is the brand **Fearzu**. When you tap a Fearzu card, character info appears, including creation date, ownership, and even a chat with an animated character.
This is just a glimpse of what’s possible with **Swap Badges** and **Swapnice technology**.
### Thanks for watching!
# Quick Start Guide - Swapnice
Source: https://swapnice.mintlify.app/swapnice-quick-start/quick_start_guide
Swapnice guide for users to quickly get up and running.
09/20/25
## Quick Start
The Swapnice platform has many uses and features. This guide is intended to help you quickly set up an account and navigate the core sections of the app.
## Download App
Begin by downloading the Swapnice app from the App Store or Google Play.
## Open App
Open the app and select “Get Started” to begin. Select the “Log In” button if you already have an account.
## Complete Signup
Complete your signup by entering your email address, then select a secure password that is at least 12 characters in length. Note that your password must contain numbers, letters, and at least one special character.
## Notification and Display Name
Once you’ve completed your signup, select your notification preferences, then input your preferred display name. Press the “Done” button when you’re ready to move on.
## Setup Complete
Congratulations! Your setup is complete and you’re now on the homepage. The homepage displays a variety of collectibles that can be cycled through using the arrow buttons on the lower portion of the screen, and can be filtered using the filter icon in the upper-right corner.
## Essential Tools
The bottom bar contains essential tools that enable you to search and explore the app:
* **Home**: Returns you to the homepage as detailed above.
* **Search**: Enables you to browse and search collectibles by scrolling or searching using the search bar at the top of the page.
* **Swap**: If you’ve purchased a swap badge, use this option to connect with the badge and input details for a product or collectible. Learn more about swap badges here.
* **Contacts**: Access your stored contacts, review contact requests, and send requests to connect with others on the app.
* **Menu**: Access a variety of other essential features including your registered inventory, the personas feature, your profile, listings, and transaction, and much more.