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 (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)
{
"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 |
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:
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:
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:
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:
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.
Response shape
{
"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)
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:
- A bare filename / S3 key — e.g.
33eac80b-58c4-46d3-a82b-b70c875d333f-cakes-and-cream.png
- A full URL — e.g.
https://cdn.drgreennft.com/strains/...
Defensively prefix bare keys with the Dr Green CDN base when rendering:
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 — adding strains to a customer’s cart
- Orders — placing orders, including the strain price snapshot at order time
- Revenue — sales aggregated by strain