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.

Keys (API Key Lifecycle)

Endpoint reference: ⚠️ JWT-auth required. The /keys endpoints are how a holder generates, lists, and revokes the API key pairs they hand to store builders. Store builders don’t call these directly — they consume the resulting (apiKey, secretKey) pair. This page documents the lifecycle so you understand how key rotation works and what to expect when a holder revokes a pair.

Endpoints

MethodPathAuthDescription
POST/keysJWTGenerate a new (apiKey, secretKey) pair
GET/keysJWTList the holder’s active key pairs (public part only)
PATCH/keys/{id}JWTUpdate a key (e.g. label)
PATCH/keys/deleteJWTRevoke (soft-delete) one or more keys
⚠️ All key endpoints require a JWT, not an API-key signature. A store builder cannot generate or revoke keys for a holder — only the holder can, via the DAPP UI (which holds the JWT).

Key lifecycle at a glance

Holder logs into DAPP UI (JWT obtained via /auth/dapp/signIn)


POST /keys  →  receives { apiKey, secretKey }
    │       (this is the ONLY time the secretKey is returned;
    │        if the holder loses it, they must generate a new pair)

Hands { apiKey, secretKey } to store developer (out-of-band — encrypted email,
                                                  password manager share, etc.)


Store uses the pair on every /dapp/* request


PATCH /keys/delete  →  revokes the pair
    │       (subsequent requests using the pair return 401)

Holder generates a new pair if needed and re-shares with store

POST /keys — generate a new pair

Creates a new ECDSA secp256k1 keypair on the server, stores the public key, and returns both halves to the caller.

Request body

Per ApiKeyRequestDto:
{
  "label": "Production WooCommerce Store"
}
FieldTypeRequiredNotes
labelstringHuman-readable name shown in the DAPP UI keys list. Be descriptive: include environment and purpose.

Response

201 Created. The response is the only time the secretKey is returned in plaintext. 🔒 Exact response shape pending live capture. Conventional shape:
{
  "success": true,
  "statusCode": 201,
  "message": "Success",
  "data": {
    "id": "<key-uuid>",
    "label": "Production WooCommerce Store",
    "apiKey": "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0K...",
    "secretKey": "LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCg...",
    "createdAt": "2026-05-10T12:00:00.000Z"
  }
}
FieldTypeNotes
idstring (UUID)Internal identifier for management ops
labelstringAs submitted
apiKeystringBase64-encoded PEM SPKI public key — safe to store
secretKeystringBase64-encoded PEM PKCS8 private key — handle as a secret
createdAtstring (ISO 8601)

What the holder must do with the pair

  1. Copy both values. The DAPP UI will warn that secretKey is shown once.
  2. Hand off via a secure channel. Encrypted email, 1Password share, or in-person. Never plain SMS or unsecured Slack.
  3. The store stores the pair in their secrets manager — AWS Secrets Manager, GCP Secret Manager, HashiCorp Vault, or at minimum environment variables in a secure deploy pipeline.

Limit

A holder can have up to 100 active key pairs. Hitting the limit returns an error; the holder must PATCH /keys/delete an unused pair before generating a new one.

GET /keys — list active pairs

Returns the holder’s key pairs without secret keys (those are unrecoverable after creation).

Canonical payload (if reachable via API key — currently JWT-only)

{} — but again, this endpoint requires JWT in practice.

Response

🔒 Pending live capture. Expected shape:
{
  "data": {
    "keys": [
      {
        "id": "<uuid>",
        "label": "Production WooCommerce Store",
        "apiKey": "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0K...",
        "isActive": true,
        "createdAt": "2026-05-10T12:00:00.000Z",
        "lastUsedAt": "2026-05-10T15:32:11.428Z"
      }
    ]
  }
}
The lastUsedAt field (if present) gives the holder visibility into stale keys — useful for cleanup before hitting the 100-key limit.

PATCH /keys/{id} — update a key

Updates a key’s metadata. Typically used to rename:
{
  "label": "Production WooCommerce Store (v2)"
}
Cannot rotate the keypair via this endpoint — to change the secret, the holder must generate a new pair and revoke the old one. 🔒 Full PATCH semantics not yet captured live.

PATCH /keys/delete — revoke (soft-delete)

Marks one or more keys as deleted. Soft-delete — the keys are not physically removed from the database, but subsequent authenticated requests using them return 401.

Request body

Per SoftDelete DTO:
{
  "ids": [
    "<key-uuid-1>",
    "<key-uuid-2>"
  ]
}
FieldTypeRequiredNotes
idsarray of stringsOne or more key UUIDs to revoke

Response

200 OK with a count of revoked keys.
🪲 Note the unusual REST shape. This is a PATCH to /keys/delete rather than a DELETE to /keys/{id} or DELETE /keys with a body. It’s a backend convention; just match it.

What happens after revocation

  • All in-flight requests using the revoked pair return 401 "User is not authorized" (the DAPP guard rejects).
  • The holder can re-issue a new pair at any time.
  • There’s no notification to the store — your store will start seeing 401s unexpectedly. Build for this:
async function withKeyRotationHandling(fn: () => Promise<unknown>) {
  try {
    return await fn();
  } catch (e: any) {
    if (e.statusCode === 401 && e.message === 'User is not authorized') {
      // Likely revoked. Surface to ops, don't silently retry.
      await alertOps('Dr Green API key may have been revoked');
    }
    throw e;
  }
}

What store builders should know

You don’t call any of these endpoints. But you should:

Have a key-rotation runbook

Document the steps for when:
  • A key is suspected compromised
  • An employee with access leaves
  • The annual rotation date arrives
The runbook should be:
  1. Holder logs into DAPP UI
  2. Generates new pair → labels it clearly (<store-name> rotation 2026-Q2)
  3. Updates secrets manager with new pair
  4. Deploys / reloads store config
  5. Verifies a few read calls succeed with new pair
  6. Revokes the old pair via DAPP UI
  7. Verifies old pair now returns 401 (sanity check)

Plan for sudden 401s

A holder might revoke without warning. Your store should:
  • Detect the rotation-flavored 401 (“User is not authorized” with otherwise-correct signing)
  • Page your on-call
  • Surface a clear “store temporarily disconnected from Dr Green” status to customers in flight

Never request the holder’s wallet credentials

The wallet is the root of trust. The holder uses it to sign the SIWE message, get a JWT, and generate API keys — and that’s where your involvement starts. Never ask for the wallet seed phrase, private key, or anything beyond the API key pair. If a Dr Green integration ever asks for those, it’s compromised.

See also