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

# 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 sandbox 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://api.sandbox.swapnice.com` (or a Swapnice-issued sandbox host).

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://api.sandbox.swapnice.com
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.
