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.
Errors
TL;DR. Every Dr Green API response — success or failure — uses the same envelope:{ success, statusCode, message, data }. On error,successisfalse,statusCodeis the HTTP status,messagedescribes what went wrong, anddatais usuallynullor an array of validation issues.
Response envelope
A NestJS global response interceptor wraps every response in this shape:/healthStatus check. Build your client around this envelope from day one — don’t unwrap manually per-endpoint.
Successful responses
statusCode is 200 for retrievals/updates and 201 for resource creation, mirroring the HTTP status of the response itself.
Error responses
class-validator, data contains a string array of human-readable validation messages — one per field violation.
For other errors (auth, not-found, conflict), data is typically null and message is the only useful field. Always surface message to your developer-facing logs.
Status codes you should handle
| Code | Meaning | What your store should do |
|---|---|---|
| 200 OK | Successful retrieval, update, or no-op | Use response data |
| 201 Created | Resource created (POST endpoints) | Read data.id for the new resource |
| 400 Bad Request | Validation failure or malformed input | Show validation messages to the user; do not retry |
| 401 Unauthorized | Missing/invalid x-auth-apikey or x-auth-signature | Verify keys, signature, and canonical payload. Do not retry blindly — the same request will fail the same way |
| 403 Forbidden | Authenticated but not allowed (wrong holder, wrong NFT, deleted key) | Check the resource belongs to this holder/key |
| 404 Not Found | Resource doesn’t exist or isn’t visible to this caller | Don’t retry; treat as legitimate “not there” |
| 409 Conflict | State conflict (duplicate email on client create, double-spend on order, etc.) | Reconcile state and re-attempt only after fixing |
| 422 Unprocessable Entity | Semantically invalid (rare; mostly business-rule violations) | Surface message; don’t retry |
| 429 Too Many Requests | Rate limited (not currently enforced — see 03-environment.md § Rate limits) | Back off exponentially when implemented |
| 500 Internal Server Error | Unexpected server failure | Retry once after a 1–2s delay; surface error to the user if it persists |
| 502 / 503 / 504 | Upstream / gateway issues (Envoy in front of the backend) | Retry with exponential backoff (max 3 attempts); these are typically transient |
Spec note. The live OpenAPI spec only formally declares200,201,400, and404responses. The other codes are returned by the global exception filter, NestJS’s auth guards, and the upstream Envoy proxy, and they all conform to the same envelope. The enriched OpenAPI 3.1 spec in this repo declares all of them per-endpoint.
Common failure modes (and how to diagnose them)
401 Unauthorized — “Invalid signature”
By far the most common error during integration. Causes, in descending order of frequency:
- You signed a different string than you sent. The signed payload must be byte-identical to the request body on the wire. If you
JSON.stringifytwice — once for signing, once for sending — JavaScript may produce different output between calls (object key ordering, etc.). Stringify once, sign that string, send that string. - You signed without the right
Content-Type. Express body-parser only treats the body as JSON ifContent-Type: application/jsonis set. Without it, the server reproduces the canonical payload as a raw string and your signature won’t match. - Wrong canonical payload for the HTTP method.
GETwith query params signsurlencode(query), not"".GETwith route params only signsJSON.stringify(routeParams). See 02-authentication.md. - Whitespace in your JSON. The server expects compact JSON (no spaces between separators).
JSON.stringify(obj)without aspacearg gives you that in JS;json.dumps(obj, separators=(",", ":"))in Python;json_encode($obj)in PHP. - API key was revoked. Check the DAPP’s keys page — keys are soft-deleted, so a revoked key returns 401, not 404.
401 Unauthorized — “API key not found”
Your x-auth-apikey value doesn’t match any active key for any holder. Either the key was never issued, was revoked, or you Base64-decoded/re-encoded it somewhere along the way and corrupted it. Compare byte-for-byte against the apiKey returned by POST /keys.
403 Forbidden — on a /dapp/clients/:id endpoint
The client exists but doesn’t belong to the NFT (Digital Key) currently set as your primary NFT. Either set the right NFT as primary via PATCH /dapp/users/primary-nft, or use a different API key tied to a different holder.
400 Bad Request — ["countryCode must be a valid ISO 3166-1 alpha-3 country code"]
The strain catalogue and several other endpoints expect ISO 3166-1 alpha-3 codes (e.g. GBR, USA, DEU) — not alpha-2 (GB, US, DE).
404 Not Found — on a strain ID that you saw 30 seconds ago
Strains can be soft-deleted by the Dr Green admin team. They disappear from the catalogue immediately but the IDs persist in your local cache. Always handle 404s on strain lookups gracefully and refresh your cached catalogue.
502 / 503 Service Unavailable — Envoy upstream errors
The backend sits behind an Envoy proxy on production. Transient 5xx with the message upstream connect error or disconnect/reset before headers is an Envoy → backend issue, not your problem. Retry with exponential backoff. If it persists more than 5 minutes, escalate to Dr Green.
Retry guidance
| Scenario | Retry? | Strategy |
|---|---|---|
| Network timeout, connection refused | ✅ Yes | Exponential backoff: 1s, 2s, 4s, max 3 attempts |
5xx from Envoy/upstream | ✅ Yes | Same as above |
429 Too Many Requests (when enforced) | ✅ Yes | Honour Retry-After if present, else exponential backoff |
4xx (other than 429) | ❌ No | Fix the request; the same input will produce the same error |
Idempotency
The Dr Green API does not currently support anIdempotency-Key header. This means:
- Safe to retry on idempotent verbs:
GET,PATCHupdates that set absolute values,DELETEof a specific ID - Risky to retry on:
POST /dapp/orders(will create a duplicate order),POST /dapp/clients(will fail with conflict on duplicate email — recoverable),POST /dapp/sales(duplicate sale)
- Optimistic generation of a client-side correlation ID stored in your DB before the request
- On any retry, first
GET /dapp/orders?...filtered by your correlation ID to see if the original succeeded - Only re-
POSTif you confirm the original didn’t make it through
Idempotency-Key header would meaningfully simplify store integration. Worth requesting from the Dr Green backend team.
Logging recommendations
For every API call, log at minimum:- HTTP method + path
- Response
statusCodeandmessage - Response time
- A correlation ID you generated client-side
- The full
secretKey - The raw
x-auth-signatureheader value (it’s deterministic for a given payload+key, so logging it is functionally equivalent to leaking part of the key’s signing surface) - Customer KYC fields (date of birth, ID document numbers, medical record content)
x-auth-apikey so you can trace which key was rejected, but truncate to the first 16 chars (it’s the public key, not catastrophic, but principle-of-least-disclosure applies).