Skip to main content

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, success is false, statusCode is the HTTP status, message describes what went wrong, and data is usually null or an array of validation issues.

Response envelope

A NestJS global response interceptor wraps every response in this shape:
{
  "success": true,
  "statusCode": 200,
  "message": "Success",
  "data": { /* endpoint-specific payload */ }
}
Every authenticated endpoint returns this shape. So does the public /healthStatus check. Build your client around this envelope from day one — don’t unwrap manually per-endpoint.

Successful responses

{
  "success": true,
  "statusCode": 200,
  "message": "Success",
  "data": { "id": "abc123", "..." }
}
statusCode is 200 for retrievals/updates and 201 for resource creation, mirroring the HTTP status of the response itself.

Error responses

{
  "success": false,
  "statusCode": 400,
  "message": "Validation failed",
  "data": [
    "email must be a valid email",
    "phoneNumber should not be empty"
  ]
}
When the failure is a validation error from 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

CodeMeaningWhat your store should do
200 OKSuccessful retrieval, update, or no-opUse response data
201 CreatedResource created (POST endpoints)Read data.id for the new resource
400 Bad RequestValidation failure or malformed inputShow validation messages to the user; do not retry
401 UnauthorizedMissing/invalid x-auth-apikey or x-auth-signatureVerify keys, signature, and canonical payload. Do not retry blindly — the same request will fail the same way
403 ForbiddenAuthenticated but not allowed (wrong holder, wrong NFT, deleted key)Check the resource belongs to this holder/key
404 Not FoundResource doesn’t exist or isn’t visible to this callerDon’t retry; treat as legitimate “not there”
409 ConflictState conflict (duplicate email on client create, double-spend on order, etc.)Reconcile state and re-attempt only after fixing
422 Unprocessable EntitySemantically invalid (rare; mostly business-rule violations)Surface message; don’t retry
429 Too Many RequestsRate limited (not currently enforced — see 03-environment.md § Rate limits)Back off exponentially when implemented
500 Internal Server ErrorUnexpected server failureRetry once after a 1–2s delay; surface error to the user if it persists
502 / 503 / 504Upstream / 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 declares 200, 201, 400, and 404 responses. 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:
  1. 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.stringify twice — 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.
  2. You signed without the right Content-Type. Express body-parser only treats the body as JSON if Content-Type: application/json is set. Without it, the server reproduces the canonical payload as a raw string and your signature won’t match.
  3. Wrong canonical payload for the HTTP method. GET with query params signs urlencode(query), not "". GET with route params only signs JSON.stringify(routeParams). See 02-authentication.md.
  4. Whitespace in your JSON. The server expects compact JSON (no spaces between separators). JSON.stringify(obj) without a space arg gives you that in JS; json.dumps(obj, separators=(",", ":")) in Python; json_encode($obj) in PHP.
  5. 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

ScenarioRetry?Strategy
Network timeout, connection refused✅ YesExponential backoff: 1s, 2s, 4s, max 3 attempts
5xx from Envoy/upstream✅ YesSame as above
429 Too Many Requests (when enforced)✅ YesHonour Retry-After if present, else exponential backoff
4xx (other than 429)❌ NoFix the request; the same input will produce the same error

Idempotency

The Dr Green API does not currently support an Idempotency-Key header. This means:
  • Safe to retry on idempotent verbs: GET, PATCH updates that set absolute values, DELETE of 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)
For risky operations, prefer:
  1. Optimistic generation of a client-side correlation ID stored in your DB before the request
  2. On any retry, first GET /dapp/orders?... filtered by your correlation ID to see if the original succeeded
  3. Only re-POST if you confirm the original didn’t make it through
🔒 An 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 statusCode and message
  • Response time
  • A correlation ID you generated client-side
Never log:
  • The full secretKey
  • The raw x-auth-signature header 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)
For 401/403 errors, log the 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).