From 99c0618222d42db08a12883f918971fe75bb1a7b Mon Sep 17 00:00:00 2001 From: Dread Date: Sun, 12 Jul 2026 20:07:28 -0700 Subject: [PATCH] docs: add a narrative API Keys guide to the landing page Adds an 'API Keys' section to the docs overview (src/index.html, mirrored to public/): what API keys are (fk__, hash-only storage, shown once), creating one with apiKeyCreate, authenticating via the X-API-KEY header, the deny-by-default scope table, per-key rate limits (TOO_MANY_REQUESTS + retryAfterSeconds), and listing/rotating/revoking. Matches the existing section styling (code-samples, sidebar link, notice box) and adds a scope-table style. --- public/index.html | 110 +++++++++++++++++++++++++++++++++++++++++++++- src/index.html | 110 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 218 insertions(+), 2 deletions(-) diff --git a/public/index.html b/public/index.html index cb22962..9022f02 100644 --- a/public/index.html +++ b/public/index.html @@ -231,6 +231,31 @@ ul li, ol li { margin-bottom: 0.5rem; } + + .scope-table { + border-collapse: collapse; + width: 100%; + margin: 1rem 0 1.5rem; + font-size: 0.95rem; + } + + .scope-table th, .scope-table td { + text-align: left; + padding: 0.6rem 0.9rem; + border-bottom: 1px solid #e1e1e1; + } + + .scope-table th { + background-color: #f5f5f5; + color: var(--color-secondary); + font-weight: 600; + } + + .scope-table td code { + background-color: #f5f5f5; + padding: 2px 6px; + border-radius: 4px; + } @@ -254,6 +279,7 @@

Quick Links

- + +

API Keys

+

API keys let server-side integrations and third-party developers call the Flash API without a user session — a payment processor accepting Bitcoin, an analytics job reading balances, or a bot creating invoices. Unlike a short-lived login token, a key is long-lived, scoped to specific permissions, and managed by the account owner.

+ +

A key has the format fk_<keyId>_<secret>. Only a hash of the secret is ever stored, so the full key is shown once at creation and can never be retrieved again — store it securely.

+ +

Creating a key

+

Keys are created from an authenticated user session (an API key cannot create or manage keys — this prevents a leaked key from minting more). Call apiKeyCreate with a name, the scopes it needs, and optionally an expiry and a per-key rate limit:

+
+
mutation {
+  apiKeyCreate(input: {
+    name: "BTCPayServer integration"
+    scopes: [read_user, read_wallet, write_wallet]
+    expiresIn: 7776000        # optional — seconds until expiry (here, 90 days). Omit for no expiry.
+    rateLimitPerMinute: 300   # optional — omit to use the platform default.
+  }) {
+    apiKey {
+      keyId
+      apiKey      # the raw key — returned once, copy it now
+      scopes
+      expiresAt
+    }
+    errors { message }
+  }
+}
+
+ +

Authenticating with a key

+

Send the key in the X-API-KEY header — not the Authorization header, which carries user session tokens:

+
+
curl -X POST https://api.flashapp.me/graphql \
+  -H 'Content-Type: application/json' \
+  -H "X-API-KEY: fk_<keyId>_<secret>" \
+  -d '{"query":"query { me { defaultAccount { wallets { walletCurrency balance } } } }"}'
+
+

An invalid, revoked, or expired key — or one used from a disallowed IP — is rejected with 401 Unauthorized.

+ +

Scopes

+

Access is deny-by-default: a key can only reach the operations covered by the scopes it was granted. Grant the minimum an integration needs.

+ + + + + + + + + + + + + +
ScopeGrants
read_userRead profile and account details
write_userModify profile and account settings
read_walletRead wallet balances and details
write_walletMove funds — send and receive
read_transactionsRead transaction history
write_transactionsCreate transactions
adminFull account access
+

write_* implies the matching read_*, and admin implies every scope. A request outside a key's scopes is rejected with a GraphQL error naming the missing scope. Sensitive operations — login, two-factor, and API-key management itself — are never available to API keys, only to user sessions.

+ +

Rate limits

+

Each key has a requests-per-minute budget (its rateLimitPerMinute, or the platform default). When the budget is exhausted, requests are rejected with a GraphQL error carrying extensions.code = "TOO_MANY_REQUESTS" and a retryAfterSeconds hint — back off for that long before retrying:

+
+
{
+  "data": null,
+  "errors": [
+    {
+      "message": "API key rate limit exceeded",
+      "extensions": {
+        "code": "TOO_MANY_REQUESTS",
+        "retryAfterSeconds": 42,
+        "rateLimit": { "limit": 300, "remaining": 0 }
+      }
+    }
+  ]
+}
+
+ +

Managing keys

+

From a user session (not from a key), you can list, rotate, and revoke:

+
    +
  • apiKeys — list the account's keys with their scopes, status, last-used and expiry. Never returns secret material.
  • +
  • apiKeyRotate(input: { id }) — issue a fresh secret for a key and retire the old one in a single step. The new raw key is returned once.
  • +
  • apiKeyRevoke(input: { id }) — permanently disable a key; it stops authenticating on the next request.
  • +
+ +
Treat an API key like a password. Grant the narrowest scopes that work, set an expiry and IP allowlist where you can, and rotate or revoke a key immediately if it may have been exposed.
+

Query Example

Fetch account details and wallet balances (requires a valid auth token):

diff --git a/src/index.html b/src/index.html index cb22962..9022f02 100644 --- a/src/index.html +++ b/src/index.html @@ -231,6 +231,31 @@ ul li, ol li { margin-bottom: 0.5rem; } + + .scope-table { + border-collapse: collapse; + width: 100%; + margin: 1rem 0 1.5rem; + font-size: 0.95rem; + } + + .scope-table th, .scope-table td { + text-align: left; + padding: 0.6rem 0.9rem; + border-bottom: 1px solid #e1e1e1; + } + + .scope-table th { + background-color: #f5f5f5; + color: var(--color-secondary); + font-weight: 600; + } + + .scope-table td code { + background-color: #f5f5f5; + padding: 2px 6px; + border-radius: 4px; + } @@ -254,6 +279,7 @@

Quick Links

- + +

API Keys

+

API keys let server-side integrations and third-party developers call the Flash API without a user session — a payment processor accepting Bitcoin, an analytics job reading balances, or a bot creating invoices. Unlike a short-lived login token, a key is long-lived, scoped to specific permissions, and managed by the account owner.

+ +

A key has the format fk_<keyId>_<secret>. Only a hash of the secret is ever stored, so the full key is shown once at creation and can never be retrieved again — store it securely.

+ +

Creating a key

+

Keys are created from an authenticated user session (an API key cannot create or manage keys — this prevents a leaked key from minting more). Call apiKeyCreate with a name, the scopes it needs, and optionally an expiry and a per-key rate limit:

+
+
mutation {
+  apiKeyCreate(input: {
+    name: "BTCPayServer integration"
+    scopes: [read_user, read_wallet, write_wallet]
+    expiresIn: 7776000        # optional — seconds until expiry (here, 90 days). Omit for no expiry.
+    rateLimitPerMinute: 300   # optional — omit to use the platform default.
+  }) {
+    apiKey {
+      keyId
+      apiKey      # the raw key — returned once, copy it now
+      scopes
+      expiresAt
+    }
+    errors { message }
+  }
+}
+
+ +

Authenticating with a key

+

Send the key in the X-API-KEY header — not the Authorization header, which carries user session tokens:

+
+
curl -X POST https://api.flashapp.me/graphql \
+  -H 'Content-Type: application/json' \
+  -H "X-API-KEY: fk_<keyId>_<secret>" \
+  -d '{"query":"query { me { defaultAccount { wallets { walletCurrency balance } } } }"}'
+
+

An invalid, revoked, or expired key — or one used from a disallowed IP — is rejected with 401 Unauthorized.

+ +

Scopes

+

Access is deny-by-default: a key can only reach the operations covered by the scopes it was granted. Grant the minimum an integration needs.

+ + + + + + + + + + + + + +
ScopeGrants
read_userRead profile and account details
write_userModify profile and account settings
read_walletRead wallet balances and details
write_walletMove funds — send and receive
read_transactionsRead transaction history
write_transactionsCreate transactions
adminFull account access
+

write_* implies the matching read_*, and admin implies every scope. A request outside a key's scopes is rejected with a GraphQL error naming the missing scope. Sensitive operations — login, two-factor, and API-key management itself — are never available to API keys, only to user sessions.

+ +

Rate limits

+

Each key has a requests-per-minute budget (its rateLimitPerMinute, or the platform default). When the budget is exhausted, requests are rejected with a GraphQL error carrying extensions.code = "TOO_MANY_REQUESTS" and a retryAfterSeconds hint — back off for that long before retrying:

+
+
{
+  "data": null,
+  "errors": [
+    {
+      "message": "API key rate limit exceeded",
+      "extensions": {
+        "code": "TOO_MANY_REQUESTS",
+        "retryAfterSeconds": 42,
+        "rateLimit": { "limit": 300, "remaining": 0 }
+      }
+    }
+  ]
+}
+
+ +

Managing keys

+

From a user session (not from a key), you can list, rotate, and revoke:

+
    +
  • apiKeys — list the account's keys with their scopes, status, last-used and expiry. Never returns secret material.
  • +
  • apiKeyRotate(input: { id }) — issue a fresh secret for a key and retire the old one in a single step. The new raw key is returned once.
  • +
  • apiKeyRevoke(input: { id }) — permanently disable a key; it stops authenticating on the next request.
  • +
+ +
Treat an API key like a password. Grant the narrowest scopes that work, set an expiry and IP allowlist where you can, and rotate or revoke a key immediately if it may have been exposed.
+

Query Example

Fetch account details and wallet balances (requires a valid auth token):