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

# Nfts

# NFTs (Digital Keys)

> **Endpoint reference: ⚠️ Limited surface for store builders.**
> The Dr Green platform is built around NFTs ("Digital Keys") that grant holder rights. Most NFT-related endpoints exist for the marketplace and the holder's own DAPP UI — and those are **JWT-only**, not reachable via API key. As a store builder, your effective surface is:
>
> 1. The holder's `nft` field on every authenticated response (the primary NFT scope of the API key)
> 2. Setting the primary NFT (which the holder typically does in the DAPP UI before issuing keys)

***

## Key concept: API keys are scoped to a primary NFT

Every API key issued by a holder is tied to **one primary NFT** at the time of issuance. All `/dapp/*` calls operate within that NFT's scope:

* Clients you create are linked to that NFT
* Orders are placed against that NFT's product allocation
* Commissions accrue to that NFT
* Revenue and dashboard counts are per-NFT

The `nft` object embedded in many response shapes (`{tokenId, ownerId}`) tells you which NFT the API key is currently scoped to.

If a holder owns multiple NFTs and wants to operate a separate store per NFT, they must:

* Either generate one API key per NFT (recommended), or
* Use the DAPP UI to switch primary NFT and re-test

***

## Endpoints

| Method                 | Path                            | Auth                  | Description                                  | Reachable from store? |
| ---------------------- | ------------------------------- | --------------------- | -------------------------------------------- | --------------------- |
| `GET`                  | `/dapp/users/nfts`              | JWT only              | List all NFTs the holder owns                | ❌ No                  |
| `PATCH`                | `/dapp/users/primary-nft`       | JWT only *(presumed)* | Set the primary NFT for the holder's session | ❌ No                  |
| (read-only inspection) | embedded in `/dapp/*` responses | API-key + sig         | Read the current primary NFT context         | ✅ Implicit            |

***

## ❌ `GET /dapp/users/nfts` — JWT-only

This endpoint lists all NFTs the authenticated user owns. **You cannot reach it with API-key authentication.** Verified live: returns `401 "Unauthorized"` (43-byte response, the short-form JWT-rejection message) regardless of signature correctness.

### Why this matters for store builders

Your store does **not** know which NFTs the holder owns. The holder must pick their primary NFT in the DAPP UI before generating API keys for you. Once they do, every authenticated response from your `/dapp/*` calls will include the `nft` field showing the scope.

### Workaround: read the embedded `nft` field

Every authenticated `/dapp/*` response that returns a client, order, or related entity includes an `nft` object showing the API key's current scope:

```json theme={null}
{
  "nft": {
    "tokenId": 56,
    "ownerId": "0x553374bd6e81f952821e37f4b8e208a81164fb2e"
  }
}
```

| Field     | Type   | Notes                                     |
| --------- | ------ | ----------------------------------------- |
| `tokenId` | int    | The on-chain NFT token ID (1–5145)        |
| `ownerId` | string | Ethereum address of the holder, lowercase |

Cache this on first authenticated call to know which NFT the holder is operating under.

```ts theme={null}
// Discover the API key's NFT scope
async function discoverNftScope(): Promise<{ tokenId: number; ownerId: string }> {
  const { data } = await listClients(1, 1);
  if (data.clients.length > 0) return data.clients[0].nft;
  const summary = await getDashboardSummary();
  // Dashboard summary doesn't expose nft directly; fall back to other reads
  throw new Error('No NFT context — holder may not have set a primary NFT');
}
```

***

## ⚠️ `PATCH /dapp/users/primary-nft` — JWT-only (presumed)

The DAPP UI calls this endpoint when a holder switches between their owned NFTs. The request body shape per `UpdatePrimaryNftDto`:

```json theme={null}
{
  "tokenId": 56
}
```

🔒 **Not verified live with API-key auth** — likely also JWT-only. Even if reachable, **store builders should not call this**. Switching primary NFT mid-session would change the scope of every subsequent call and likely break the holder's reporting. Treat as a DAPP-UI-only operation.

If a holder wants to operate two NFTs from one store, generate two API key pairs (one per primary NFT) and route per-NFT in your store's logic.

***

## NFT cap and tier structure

Confirmed from backend research, useful context for stores:

| Tier          | Count     | Notes                                  |
| ------------- | --------- | -------------------------------------- |
| Platinum      | 50        | Premium tier                           |
| Gold          | 55        | Mid tier                               |
| Standard      | 5,040     | Mass tier                              |
| **Total cap** | **5,145** | All NFTs are minted; scarcity is fixed |

The `tokenId` ranges and tier mapping are not exposed via the DAPP API. The `marketplace/*` endpoints (which sit outside the DAPP scope) handle this for the public marketplace.

***

## Marketplace endpoints (out of scope for store builders)

The Dr Green API has a substantial `/api/v1/marketplace/*` surface (NFT trading, listings, transactions, planet metadata) but these are intended for the marketplace UI, not external store integrations. They use a mix of public and JWT auth and are not relevant to a holder-store integration.

If your store needs to display NFT metadata (e.g. "powered by Dr Green Digital Key #56"), use the `tokenId` from the embedded `nft` field plus your own static mapping of tier → display string, or query Dr Green's public CDN if available. **Do not** integrate the marketplace endpoints into a store backend.

***

## Common patterns

### Display the holder's NFT badge

```ts theme={null}
function nftBadge(nft: { tokenId: number; ownerId: string }): string {
  return `Dr Green Digital Key #${nft.tokenId}`;
}
```

### Verify the API key is scoped to an expected NFT

If your store is built for a specific holder/NFT, verify on startup:

```ts theme={null}
async function verifyExpectedNft(expectedTokenId: number) {
  const scope = await discoverNftScope();
  if (scope.tokenId !== expectedTokenId) {
    throw new Error(`Wrong API key — expected NFT #${expectedTokenId}, got #${scope.tokenId}`);
  }
}
```

***

## See also

* [Authentication](../02-authentication.md) — why API-key auth is scoped per primary NFT
* [Keys](./keys.md) — how holders generate per-NFT API key pairs
* [Clients](./clients.md), [Orders](./orders.md) — embedded `nft` field on every entity
