> ## 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.

# Strains

# Strains (Catalogue)

> **Endpoint reference: ✅ Verified live against production on 10 May 2026.**
> Strains are the products in your store. The Dr Green platform handles inventory and fulfilment; your job is to surface the catalogue, filtered to what's available in the customer's country, and link strain selections into orders.

***

## Endpoints

| Method | Path                       | Auth          | Description                           |
| ------ | -------------------------- | ------------- | ------------------------------------- |
| `GET`  | `/dapp/strains`            | API-key + sig | List strains for a country, paginated |
| `GET`  | `/dapp/strains/{strainId}` | API-key + sig | Get one strain with full detail       |

Both routes accept `DualAuthGuard` — JWT (DAPP UI) or API-key+signature (your store).

***

## `GET /dapp/strains`

List strains available in the given country. Returns a paginated wrapper.

### Query parameters

| Name          | Type    | Required          | Description                                                                                                  |
| ------------- | ------- | ----------------- | ------------------------------------------------------------------------------------------------------------ |
| `countryCode` | string  | **Yes**           | ISO 3166-1 **alpha-3** code, uppercase. E.g. `GBR`, `USA`, `DEU`, `ZAF`. Alpha-2 (`GB`, `US`) returns `400`. |
| `page`        | integer | No (default `1`)  | 1-indexed page number                                                                                        |
| `limit`       | integer | No (default `10`) | Items per page (server may cap; 50 is safe)                                                                  |
| `search`      | string  | No                | Free-text match on strain name                                                                               |

### Canonical payload (for signing)

`urlencode(query)` — e.g. `countryCode=GBR&page=1&limit=10`

### Response shape (verified)

```json theme={null}
{
  "success": true,
  "statusCode": 200,
  "message": "Success",
  "data": {
    "strains": [ /* array of strain summary objects, see below */ ],
    "pageMetaDto": {
      "page": "1",
      "take": 10,
      "itemCount": 47,
      "pageCount": 5,
      "hasPreviousPage": false,
      "hasNextPage": true
    }
  }
}
```

### Strain summary object

The `strains[]` items contain the fields needed to render a catalogue card. The full schema isn't formally declared in the OpenAPI spec; the verified shape from production includes:

| Field                       | Type          | Notes                                                 |
| --------------------------- | ------------- | ----------------------------------------------------- |
| `id`                        | string (UUID) | Strain identifier — use this everywhere               |
| `name`                      | string        | Display name                                          |
| `imageUrl`                  | string        | S3 path or full URL — see [§ Image URLs](#image-urls) |
| `wholeSalePrice`            | number (USD)  | Holder's wholesale cost                               |
| `retailPrice`               | number (USD)  | Suggested retail to the customer                      |
| `currency`                  | string        | `"USD"` for the canonical price                       |
| `description`               | string        | Marketing copy                                        |
| `thcContent` / `cbdContent` | number        | Cannabinoid percentages                               |
| `strainType`                | string        | e.g. `INDICA`, `SATIVA`, `HYBRID`                     |
| `availableCountries`        | array         | Countries this strain ships to                        |

> ⚠️ Field set varies by deployment. **Always defensively read fields** (`strain.imageUrl ?? null`) — fields can be added without notice.

### Worked examples

**cURL:**

```bash theme={null}
PAYLOAD='countryCode=GBR&page=1&limit=10'
SIG=$(printf '%s' "$PAYLOAD" | openssl dgst -sha256 -sign ~/.drgreen/secret.pem | base64 -w0)
curl "https://api.drgreennft.com/api/v1/dapp/strains?$PAYLOAD" \
  -H "x-auth-apikey: $DRGREEN_API_KEY_B64" \
  -H "x-auth-signature: $SIG"
```

**Node.js / TypeScript:**

```typescript theme={null}
import { canonicalPayload, authHeaders } from './sign';
async function listStrains(countryCode: string, page = 1, limit = 10) {
  const query = { countryCode, page: String(page), limit: String(limit) };
  const payload = canonicalPayload('GET', null, query);
  const headers = authHeaders(process.env.DRGREEN_API_KEY!, process.env.DRGREEN_SECRET_KEY!, payload);
  const url = new URL('https://api.drgreennft.com/api/v1/dapp/strains');
  Object.entries(query).forEach(([k, v]) => url.searchParams.set(k, v));
  const res = await fetch(url, { headers });
  const json = await res.json();
  if (!res.ok) throw new Error(`${json.statusCode}: ${json.message}`);
  return json.data; // { strains: [...], pageMetaDto: {...} }
}
```

**Python:**

```python theme={null}
import os, httpx
from urllib.parse import urlencode
from sign import canonical_payload, auth_headers

def list_strains(country_code: str, page: int = 1, limit: int = 10):
    query = {"countryCode": country_code, "page": str(page), "limit": str(limit)}
    payload = canonical_payload("GET", query=query)
    headers = auth_headers(os.environ["DRGREEN_API_KEY"], os.environ["DRGREEN_SECRET_KEY"], payload)
    url = f"https://api.drgreennft.com/api/v1/dapp/strains?{urlencode(query)}"
    r = httpx.get(url, headers=headers, timeout=15.0)
    r.raise_for_status()
    return r.json()["data"]
```

**PHP:**

```php theme={null}
function list_strains(string $countryCode, int $page = 1, int $limit = 10): array {
    $query = ['countryCode' => $countryCode, 'page' => (string)$page, 'limit' => (string)$limit];
    $payload = dr_green_canonical('GET', null, $query);
    $headers = dr_green_headers($_ENV['DRGREEN_API_KEY'], $_ENV['DRGREEN_SECRET_KEY'], $payload);
    $url = 'https://api.drgreennft.com/api/v1/dapp/strains?' . http_build_query($query, '', '&', PHP_QUERY_RFC3986);
    $ch = curl_init($url);
    curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => $headers]);
    $res = curl_exec($ch);
    if ($res === false) throw new RuntimeException('curl: ' . curl_error($ch));
    $body = json_decode($res, true);
    if (($body['statusCode'] ?? 500) !== 200) throw new RuntimeException($body['message'] ?? 'unknown');
    return $body['data'];
}
```

### Errors specific to this endpoint

| Status                                                   | Cause                                | Fix                                  |
| -------------------------------------------------------- | ------------------------------------ | ------------------------------------ |
| `400` `"countryCode must not be empty"`                  | Missing `countryCode` query param    | Always send it                       |
| `400` `"countryCode must be a valid ISO 3166-1 alpha-3"` | You sent `"GB"` instead of `"GBR"`   | Use alpha-3 codes                    |
| `200` with `strains: []`                                 | No strains available in that country | Show an empty-state UI; not an error |

***

## `GET /dapp/strains/{strainId}`

Get one strain with full detail. Use this when rendering a strain detail page or refreshing data on an item already in the cart.

### Path parameters

| Name       | Type          | Description                     |
| ---------- | ------------- | ------------------------------- |
| `strainId` | string (UUID) | The `id` from the list endpoint |

### Canonical payload (for signing)

`{}` — no query params, the empty-params fallback. **Not `""`.** See [02-authentication.md § Canonical payload](../02-authentication.md#canonical-payload).

### Response shape

```json theme={null}
{
  "success": true,
  "statusCode": 200,
  "message": "Success",
  "data": {
    "id": "c77dd198-ec9a-4ced-8105-937fde104424",
    "name": "Femme Fatale",
    "imageUrl": "33eac80b-58c4-46d3-a82b-b70c875d333f-cakes-and-cream.png",
    "wholeSalePrice": 99,
    "retailPrice": 165,
    "currency": "USD",
    "description": "...",
    "thcContent": 22.5,
    "cbdContent": 0.3,
    "strainType": "HYBRID",
    "availableCountries": [ /* alpha-3 codes */ ],
    "createdAt": "2025-02-26T05:49:11.465Z",
    "updatedAt": "2025-05-13T13:05:08.284Z"
  }
}
```

### Worked example (cURL)

```bash theme={null}
STRAIN_ID="c77dd198-ec9a-4ced-8105-937fde104424"
SIG=$(printf '%s' '{}' | openssl dgst -sha256 -sign ~/.drgreen/secret.pem | base64 -w0)
curl "https://api.drgreennft.com/api/v1/dapp/strains/$STRAIN_ID" \
  -H "x-auth-apikey: $DRGREEN_API_KEY_B64" \
  -H "x-auth-signature: $SIG"
```

### Errors

| Status                           | Cause                                     | Fix                                         |
| -------------------------------- | ----------------------------------------- | ------------------------------------------- |
| `404` `"Strain not found"`       | Strain was soft-deleted by Dr Green admin | Refresh your local cache, drop the stale ID |
| `401` `"User is not authorized"` | Signed `""` instead of `"{}"`             | Use the canonical payload `"{}"`            |

***

## Image URLs

Strain `imageUrl` values come in two forms:

1. **A bare filename / S3 key** — e.g. `33eac80b-58c4-46d3-a82b-b70c875d333f-cakes-and-cream.png`
2. **A full URL** — e.g. `https://cdn.drgreennft.com/strains/...`

Defensively prefix bare keys with the Dr Green CDN base when rendering:

```ts theme={null}
const imageSrc = strain.imageUrl?.startsWith('http')
  ? strain.imageUrl
  : `https://cdn.drgreennft.com/strains/${strain.imageUrl}`;
```

🔒 **The CDN base URL** for bare-key images is not formally documented and may differ between staging and production. Confirm with Dr Green; I've inferred this pattern from production but haven't verified the exact base.

***

## Caching guidance

The catalogue changes infrequently — new strains land, occasionally one is removed. Cache aggressively:

* **List response:** TTL 5 minutes per `(countryCode, page, limit, search)` cache key.
* **Detail response:** TTL 10 minutes per `strainId`.
* **404 on detail:** drop the strain from your local index immediately and refresh the list.

***

## See also

* [Carts](./carts.md) — adding strains to a customer's cart
* [Orders](./orders.md) — placing orders, including the strain price snapshot at order time
* [Revenue](./revenue.md) — sales aggregated by strain
