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

# Headless Affiliate And Customer Portals

> Build your own branded customer and affiliate dashboard while Shoppex handles orders, attribution, balances, and payout requests.

## Overview

Use this setup when you want the frontend to feel fully yours, but you still want Shoppex to handle the hard backend parts.

<CardGroup cols={3}>
  <Card title="Your Frontend" icon="palette">
    You control the branding, layout, auth flow, and customer experience.
  </Card>

  <Card title="Shoppex Backend" icon="server">
    Shoppex handles order creation, affiliate attribution, balances, and payout requests.
  </Card>

  <Card title="API-First Flow" icon="code">
    You connect your app to Shoppex through API endpoints instead of using a hosted dashboard UI.
  </Card>
</CardGroup>

<Info>
  This guide is for merchants who want a white-label customer or affiliate portal.
</Info>

***

## What Shoppex Handles

<CardGroup cols={2}>
  <Card title="Orders With Affiliate Attribution" icon="receipt">
    Create an order and attach an `affiliate_code` so the referral stays linked to the invoice.
  </Card>

  <Card title="Affiliate Stats And Balance" icon="chart-bar">
    Read referral links, clicks, sales, commissions, claims, and balance buckets for your frontend.
  </Card>

  <Card title="Convert To Store Balance" icon="wallet">
    Let affiliates move approved earnings into store balance with an API-triggered action.
  </Card>

  <Card title="Crypto Payout Requests" icon="coins">
    Accept payout requests without forcing automatic onchain payouts.
  </Card>
</CardGroup>

***

## Recommended Portal Experience

For a strong headless setup, your portal usually needs three surfaces:

<CardGroup cols={3}>
  <Card title="Orders" icon="shopping-bag">
    Show invoices, payment state, and order history.
  </Card>

  <Card title="Affiliate" icon="users">
    Show referral code, referral link, clicks, conversions, and commissions.
  </Card>

  <Card title="Balance Actions" icon="arrow-path">
    Let affiliates convert earnings into store balance or request a payout.
  </Card>
</CardGroup>

## Typical Integration Flow

<Steps>
  <Step title="Create the order">
    Your frontend or backend creates the order and includes the affiliate code.
  </Step>

  <Step title="Start provider payment">
    Your app starts the payment session for the gateway you want, like Stripe or PayPal.
  </Step>

  <Step title="Render affiliate dashboard data">
    Your dashboard reads affiliate stats and balance data from Shoppex.
  </Step>

  <Step title="Handle balance actions">
    Your UI lets the affiliate convert earnings into store balance or submit a payout request.
  </Step>
</Steps>

***

## Resolve And Attribute Affiliate Codes In A Custom Storefront

If you run your own storefront, use the Storefront affiliate endpoints before checkout to validate a code and read its customer discount metadata.

When a customer lands on `?ref=creator10`, your frontend calls `POST /v1/storefront/affiliates/resolve` to check if the code is valid and whether it unlocks a discount. When the customer continues, your frontend calls `POST /v1/storefront/affiliates/attribution` to lock in the referral.

<Info>
  These are Storefront API endpoints under `/v1/storefront/*`, not Dev API endpoints under `/dev/v1/*`.
</Info>

<CodeGroup>
  ```bash cURL theme={"system"}
  curl -X POST https://api.shoppex.io/v1/storefront/affiliates/resolve \
    -H "Content-Type: application/json" \
    -d '{
      "shop_slug": "cheatsmarket",
      "code": "creator10"
    }'
  ```

  ```typescript TypeScript theme={"system"}
  async function resolveAffiliate(shopSlug: string, code: string) {
    const response = await fetch('https://api.shoppex.io/v1/storefront/affiliates/resolve', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        shop_slug: shopSlug,
        code,
      }),
    });

    const payload = await response.json();

    if (!response.ok) {
      throw new Error(payload?.error ?? 'Failed to resolve affiliate code');
    }

    return payload.data;
  }
  ```
</CodeGroup>

Resolve response example:

```json theme={"system"}
{
  "status": 200,
  "data": {
    "valid": true,
    "affiliate_code": "creator10",
    "discount_active": true,
    "discount_percent": 12
  }
}
```

Attribution response example:

```json theme={"system"}
{
  "status": 200,
  "data": {
    "accepted": true,
    "affiliate_code": "creator10",
    "discount_active": true,
    "discount_percent": 12
  }
}
```

If `discount_active` is `true` and `discount_percent` is `12`, your storefront can immediately show "12% affiliate discount applied".

This makes it easier to build a custom referral banner, pricing preview, or coupon-style affiliate UX without guessing the effective discount.

***

## 1. Create The Order

Create the invoice or order from your frontend or backend and attach the affiliate code.

Your backend calls the invoice create flow and includes `affiliate_code: "creator123"` — Shoppex stores the affiliate attribution on the invoice automatically.

<Tip>
  Use this when you want your own landing page, pricing page, or custom checkout entry point.
</Tip>

This keeps the affiliate relationship on the order and uses the same attribution rules as the modern storefront flow.

## 2. Start Payment With The Provider You Want

You do not need to use a generic Shoppex checkout page if your flow is more API-first.

Create the invoice, start a Stripe or PayPal payment session, send the customer into that provider flow, and let Shoppex complete the invoice through the normal payment lifecycle.

<Info>
  Think of Shoppex as the payment and accounting backend, while your frontend stays fully branded.
</Info>

***

## 3. Read Affiliate Data For Your Frontend

For a headless customer or affiliate dashboard, use the Dev API affiliate endpoints.

<CardGroup cols={3}>
  <Card title="Affiliate Summary" icon="user-circle">
    `GET /dev/v1/customers/{id}/affiliate`
  </Card>

  <Card title="Convert To Balance" icon="credit-card">
    `POST /dev/v1/customers/{id}/affiliate/convert-to-balance`
  </Card>

  <Card title="Payout Request" icon="coins">
    `POST /dev/v1/customers/{id}/affiliate/payout-requests`
  </Card>
</CardGroup>

<CodeGroup>
  ```bash cURL theme={"system"}
  curl https://api.shoppex.io/dev/v1/customers/cus_123/affiliate \
    -H "Authorization: Bearer shx_your_api_key"
  ```

  ```typescript TypeScript theme={"system"}
  async function fetchAffiliateSummary(customerId: string, apiKey: string) {
    const response = await fetch(`https://api.shoppex.io/dev/v1/customers/${customerId}/affiliate`, {
      headers: {
        Authorization: `Bearer ${apiKey}`,
      },
    });

    if (!response.ok) {
      throw new Error(`Failed to load affiliate summary (${response.status})`);
    }

    const { data } = await response.json();
    return data;
  }
  ```
</CodeGroup>

The affiliate summary gives your frontend the building blocks for a real dashboard:

* referral link and code
* clicks and sales
* commissions
* claims
* balance buckets like `available`, `requested`, and `converted`

Example response:

```json theme={"system"}
{
  "data": {
    "customer_id": "cus_123",
    "state": "active",
    "link": {
      "code": "creator123",
      "url": "https://example.test/?ref=creator123",
      "total_clicks": 14,
      "total_sales": 3,
      "total_revenue": 299
    },
    "balances": {
      "available": 25,
      "requested": 0,
      "converted": 0
    }
  }
}
```

## 4. Convert Affiliate Balance Into Store Balance

This is the self-serve flow for affiliates who want to spend their earnings inside the store instead of cashing out.

<CodeGroup>
  ```bash cURL theme={"system"}
  curl -X POST https://api.shoppex.io/dev/v1/customers/cus_123/affiliate/convert-to-balance \
    -H "Authorization: Bearer shx_your_api_key" \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: affiliate-convert-001" \
    -d '{
      "amount": 25
    }'
  ```

  ```typescript TypeScript theme={"system"}
  async function convertAffiliateBalance(customerId: string, apiKey: string, amount: number) {
    const response = await fetch(`https://api.shoppex.io/dev/v1/customers/${customerId}/affiliate/convert-to-balance`, {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${apiKey}`,
        'Content-Type': 'application/json',
        'Idempotency-Key': `affiliate-convert-${customerId}-${amount}`,
      },
      body: JSON.stringify({ amount }),
    });

    const payload = await response.json();

    if (!response.ok) {
      throw new Error(payload?.error?.message ?? 'Failed to convert affiliate balance');
    }

    return payload.data;
  }
  ```
</CodeGroup>

If the affiliate balance is `25` and the user enters `10`, Shoppex creates the balance claim and credits `10` into store balance.

Example response:

```json theme={"system"}
{
  "data": {
    "claim_id": "claim_123",
    "amount": 10,
    "status": "COMPLETED",
    "wallet_transaction_id": "wallet_tx_123",
    "new_balance": 10
  }
}
```

<Tip>
  Use idempotency keys for button-triggered actions so retries do not duplicate balance conversions.
</Tip>

## 5. Create A Crypto Payout Request

This flow is for affiliates who want to request a payout without forcing fully automatic onchain settlement.

<Info>
  This does not go through a payment gateway like Stripe or PayPal. The API stores a payout request, and your own ops flow, webhook consumer, or treasury process decides whether and how the crypto payout gets fulfilled.
</Info>

<CodeGroup>
  ```bash cURL theme={"system"}
  curl -X POST https://api.shoppex.io/dev/v1/customers/cus_123/affiliate/payout-requests \
    -H "Authorization: Bearer shx_your_api_key" \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: affiliate-payout-001" \
    -d '{
      "amount": 7,
      "asset": "usdt",
      "address": "TQ2nBylQp1n7hT8t9r3V7A4C6X9K1M2P3Q",
      "network": "tron",
      "note": "Pay out weekly affiliate earnings"
    }'
  ```

  ```typescript TypeScript theme={"system"}
  async function requestCryptoPayout(customerId: string, apiKey: string) {
    const response = await fetch(`https://api.shoppex.io/dev/v1/customers/${customerId}/affiliate/payout-requests`, {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${apiKey}`,
        'Content-Type': 'application/json',
        'Idempotency-Key': `affiliate-payout-${customerId}-weekly`,
      },
      body: JSON.stringify({
        amount: 7,
        asset: 'usdt',
        address: 'TQ2nBylQp1n7hT8t9r3V7A4C6X9K1M2P3Q',
        network: 'tron',
        note: 'Pay out weekly affiliate earnings',
      }),
    });

    const payload = await response.json();

    if (!response.ok) {
      throw new Error(payload?.error?.message ?? 'Failed to create payout request');
    }

    return payload.data;
  }
  ```
</CodeGroup>

The affiliate enters an amount and wallet address, your UI submits the payout request, Shoppex stores it, and your ops flow or webhook worker decides what happens next.

Example response:

```json theme={"system"}
{
  "data": {
    "claim_id": "claim_456",
    "amount": 7,
    "status": "PENDING",
    "asset": "USDT",
    "address": "TQ2nBylQp1n7hT8t9r3V7A4C6X9K1M2P3Q",
    "network": "tron",
    "note": "Pay out weekly affiliate earnings"
  }
}
```

<Warning>
  This request flow is intentionally safer than “blind auto payout”. It gives you an approval or ops step instead of pushing funds automatically.
</Warning>

***

## 6. Process Payout Requests In Your Merchant UI

If you are building a headless merchant dashboard or an ops backoffice, you can now process affiliate payout requests through the Dev API as well.

The affiliate submits a payout request, your merchant tool lists pending requests, your ops user approves or rejects, and the affiliate sees the updated state in their portal.

<CardGroup cols={2}>
  <Card title="List Requests" icon="list">
    `GET /dev/v1/affiliates/payout-requests`
  </Card>

  <Card title="Approve Request" icon="check">
    `POST /dev/v1/affiliates/payout-requests/{id}/approve`
  </Card>

  <Card title="Reject Request" icon="xmark">
    `POST /dev/v1/affiliates/payout-requests/{id}/reject`
  </Card>

  <Card title="Complete Or Cancel" icon="arrows-rotate">
    `POST /dev/v1/affiliates/payout-requests/{id}/complete`
    and
    `POST /dev/v1/affiliates/payout-requests/{id}/cancel`
  </Card>
</CardGroup>

<CodeGroup>
  ```bash cURL theme={"system"}
  curl https://api.shoppex.io/dev/v1/affiliates/payout-requests?status=pending \
    -H "Authorization: Bearer shx_your_api_key"
  ```

  ```bash cURL theme={"system"}
  curl -X POST https://api.shoppex.io/dev/v1/affiliates/payout-requests/claim_456/approve \
    -H "Authorization: Bearer shx_your_api_key" \
    -H "Idempotency-Key: affiliate-payout-approve-claim-456"
  ```

  ```typescript TypeScript theme={"system"}
  async function approveAffiliatePayoutRequest(claimId: string, apiKey: string) {
    const response = await fetch(`https://api.shoppex.io/dev/v1/affiliates/payout-requests/${claimId}/approve`, {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${apiKey}`,
        'Idempotency-Key': `affiliate-payout-approve-${claimId}`,
      },
    });

    const payload = await response.json();

    if (!response.ok) {
      throw new Error(payload?.error?.message ?? 'Failed to approve payout request');
    }

    return payload.data;
  }
  ```
</CodeGroup>

Example list response:

```json theme={"system"}
{
  "data": {
    "payout_requests": [
      {
        "id": "claim_456",
        "affiliate_customer_id": "cus_123",
        "affiliate_code": "creator123",
        "affiliate_email": "creator@example.com",
        "status": "pending",
        "amount": 7,
        "currency": "USD",
        "payout_asset": "USDT",
        "payout_network": "tron",
        "payout_address": "TQ2nBylQp1n7hT8t9r3V7A4C6X9K1M2P3Q",
        "note": "Pay out weekly affiliate earnings"
      }
    ],
    "page": 1,
    "limit": 25,
    "total": 1
  }
}
```

Example action response:

```json theme={"system"}
{
  "data": {
    "status": "processing",
    "already_updated": false
  }
}
```

<Tip>
  Use `affiliates.read` to list merchant-side payout requests and `affiliates.write` for approve, reject, complete, and cancel actions.
</Tip>

***

## Recommended Scopes

A good starting scope set for this type of portal is:

| Scope              | Why you usually need it                                                     |
| ------------------ | --------------------------------------------------------------------------- |
| `affiliates.read`  | List affiliate applications, merchant payout requests, and partner ops data |
| `affiliates.write` | Approve or reject applications and process payout requests                  |
| `customers.read`   | Read affiliate summaries and customer-linked data                           |
| `customers.write`  | Convert balances and create payout requests                                 |
| `orders.read`      | Show order history or order-linked affiliate activity                       |
| `invoices.read`    | Read invoice state for payment and attribution context                      |

If the same integration also manages recurring billing, add subscription scopes separately.

## Implementation Rule

Keep one clear source of truth:

* Shoppex owns affiliate balances, claims, payout requests, and invoice attribution
* your frontend owns branding, navigation, and UX

That split keeps the system clean and avoids fragile merchant-specific logic.

***

## Best Fit

This setup is a strong fit when you want:

* a fully branded customer portal
* a branded affiliate dashboard
* custom onboarding or checkout entry pages
* Shoppex handling the accounting and payment-side complexity in the background
