From 95b5f4d4f6200fc1da5105e3adcc56094e7a181c Mon Sep 17 00:00:00 2001 From: Sweets Sweetman Date: Wed, 8 Jul 2026 12:08:39 -0500 Subject: [PATCH 1/6] =?UTF-8?q?docs(credits):=20reprice=20POST=20/api/rese?= =?UTF-8?q?arch/web=205=E2=86=921=20credit=20+=20auto=20top-up=20opt-out?= =?UTF-8?q?=20contract?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Web-search research now costs 1 credit per call (provider cost is $0.005/request flat). Adds GET/PATCH /api/accounts/{id}/auto-recharge to the accounts OpenAPI: the opt-out lives on the account's Stripe customer record, disabling never removes the saved card, and opted-out accounts fail closed to the existing 402 + checkoutUrl manual-checkout path. Contract for recoupable/chat#1861 (merge first: docs → api → chat). Co-Authored-By: Claude Fable 5 --- api-reference/accounts/auto-recharge-get.mdx | 4 + .../accounts/auto-recharge-update.mdx | 4 + api-reference/openapi/accounts.json | 206 ++++++++++++++++++ credits.mdx | 27 ++- docs.json | 4 +- 5 files changed, 242 insertions(+), 3 deletions(-) create mode 100644 api-reference/accounts/auto-recharge-get.mdx create mode 100644 api-reference/accounts/auto-recharge-update.mdx diff --git a/api-reference/accounts/auto-recharge-get.mdx b/api-reference/accounts/auto-recharge-get.mdx new file mode 100644 index 00000000..2e7e2773 --- /dev/null +++ b/api-reference/accounts/auto-recharge-get.mdx @@ -0,0 +1,4 @@ +--- +title: "Get Auto Top-Up Setting" +openapi: "/api-reference/openapi/accounts.json GET /api/accounts/{id}/auto-recharge" +--- diff --git a/api-reference/accounts/auto-recharge-update.mdx b/api-reference/accounts/auto-recharge-update.mdx new file mode 100644 index 00000000..15ff5258 --- /dev/null +++ b/api-reference/accounts/auto-recharge-update.mdx @@ -0,0 +1,4 @@ +--- +title: "Update Auto Top-Up Setting" +openapi: "/api-reference/openapi/accounts.json PATCH /api/accounts/{id}/auto-recharge" +--- diff --git a/api-reference/openapi/accounts.json b/api-reference/openapi/accounts.json index 8b7858d0..037f774f 100644 --- a/api-reference/openapi/accounts.json +++ b/api-reference/openapi/accounts.json @@ -1000,6 +1000,167 @@ } } }, + "/api/accounts/{id}/auto-recharge": { + "get": { + "description": "Retrieve whether automatic top-up is enabled for the account. `enabled: true` is the default for every account. `enabled: false` means the account has opted out: no off-session charge is ever attempted, and billed requests that exceed the remaining balance return HTTP 402 with a `checkoutUrl` for an explicit manual top-up instead. The setting lives on the account's Stripe customer record; this endpoint reads it live.", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The unique identifier (UUID) of the account. Must be the authenticated account or another accessible via organization membership.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "security": [ + { + "apiKeyAuth": [] + }, + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Auto top-up setting returned successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AccountAutoRechargeResponse" + } + } + } + }, + "401": { + "description": "Unauthorized - invalid or missing authentication", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AccountAutoRechargeErrorResponse" + }, + "example": { + "error": "Unauthorized" + } + } + } + }, + "403": { + "description": "Forbidden - account not accessible to the authenticated account", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AccountAutoRechargeErrorResponse" + } + } + } + }, + "404": { + "description": "Account not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AccountAutoRechargeErrorResponse" + } + } + } + } + } + }, + "patch": { + "description": "Enable or disable automatic top-up for the account. Disabling never removes the saved card: manual top-ups via checkout keep working, and re-enabling later requires no card re-entry. While disabled, billed requests that exceed the remaining balance return HTTP 402 with a `checkoutUrl` instead of silently charging the saved card.", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The unique identifier (UUID) of the account. Must be the authenticated account or another accessible via organization membership.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "security": [ + { + "apiKeyAuth": [] + }, + { + "bearerAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AccountAutoRechargeUpdateRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Auto top-up setting returned successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AccountAutoRechargeResponse" + } + } + } + }, + "400": { + "description": "Invalid request body - `enabled` must be a boolean", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AccountAutoRechargeErrorResponse" + }, + "example": { + "error": "enabled must be a boolean" + } + } + } + }, + "401": { + "description": "Unauthorized - invalid or missing authentication", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AccountAutoRechargeErrorResponse" + }, + "example": { + "error": "Unauthorized" + } + } + } + }, + "403": { + "description": "Forbidden - account not accessible to the authenticated account", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AccountAutoRechargeErrorResponse" + } + } + } + }, + "404": { + "description": "Account not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AccountAutoRechargeErrorResponse" + } + } + } + } + } + } + }, "/api/credits/sessions": { "post": { "description": "Top up credits on the authenticated account.\n\n**One credit equals one US cent (\\$0.01).** The customer is charged `credits` plus a Stripe processing fee (US card pricing: 2.9% + \\$0.30) \u2014 e.g. `credits: 10000` charges \\$103.30 total (\\$100.00 credits + \\$3.30 fee).\n\n**Two outcomes, distinguished by response shape:**\n\n- **Auto-charged** \u2014 if the account has a card on file (from a prior subscription or top-up), the card is charged immediately and the response is `{ paymentIntentId, creditsPurchased, totalCents }`. Credits land in the account's balance asynchronously via Stripe webhook (typically within seconds). No human interaction required.\n\n- **Checkout required** \u2014 if no card is on file, or the saved card requires 3-D Secure authentication, the response is `{ id, url }` with a hosted Stripe Checkout URL. Redirect to that URL; credits land on successful payment.\n\nClients should discriminate on the presence of `url` (Checkout) vs `paymentIntentId` (auto-charged). Cards entered through the Checkout fallback are saved for future top-ups, so a customer's second top-up typically auto-charges.", @@ -4277,6 +4438,51 @@ } } }, + "AccountAutoRechargeResponse": { + "type": "object", + "required": [ + "account_id", + "enabled" + ], + "properties": { + "account_id": { + "type": "string", + "format": "uuid", + "description": "The unique identifier of the account this setting belongs to.", + "example": "550e8400-e29b-41d4-a716-446655440000" + }, + "enabled": { + "type": "boolean", + "description": "Whether automatic top-up is enabled. `false` means the account has opted out and only explicit checkout top-ups can charge it.", + "example": true + } + } + }, + "AccountAutoRechargeUpdateRequest": { + "type": "object", + "required": [ + "enabled" + ], + "properties": { + "enabled": { + "type": "boolean", + "description": "Set `false` to opt out of automatic top-up, `true` to opt back in." + } + } + }, + "AccountAutoRechargeErrorResponse": { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "type": "string", + "description": "Human-readable error message.", + "example": "Unauthorized" + } + } + }, "CreateCreditsSessionRequest": { "type": "object", "required": [ diff --git a/credits.mdx b/credits.mdx index ab68b776..b346ef19 100644 --- a/credits.mdx +++ b/credits.mdx @@ -115,7 +115,7 @@ Response shape: ## Automatic top-up -Every billed API request runs a credit gate before it executes. If `remaining_credits` doesn't cover the request's cost **and** the account has a saved Stripe card, Recoup silently charges **$5 (500 credits)** off-session and lets the request proceed. Most accounts with a working card never see an "insufficient credits" failure — the top-up is invisible. +Every billed API request runs a credit gate before it executes. If `remaining_credits` doesn't cover the request's cost **and** the account has a saved Stripe card **and** the account hasn't opted out (see [Opting out](#opting-out) below), Recoup silently charges **$5 (500 credits)** off-session and lets the request proceed. Most accounts with a working card never see an "insufficient credits" failure — the top-up is invisible. The decision tree: @@ -125,10 +125,33 @@ The decision tree: ### When auto top-up doesn't trigger +- The account has opted out of automatic top-up → 402 with `checkoutUrl`, no `declineReason`. The saved card is never charged off-session while opted out. - The account has no saved Stripe card → 402 with `checkoutUrl`, no `declineReason`. - Stripe declines the saved card (insufficient funds, expired, fraud, 3-D Secure required, etc.) → 402 with `checkoutUrl` + `declineReason`. - A single request needs more than 500 credits (rare — only `POST /api/research/deep` at 25 and oversized `extract` calls come close) → request fails even though the top-up succeeded. +### Opting out + +Automatic top-up is on by default and can be disabled per account: + +```bash +curl -X PATCH "https://api.recoupable.dev/api/accounts/{id}/auto-recharge" \ + -H "x-api-key: $RECOUP_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"enabled": false}' +``` + +```json +{ + "account_id": "acc_…", + "enabled": false +} +``` + +While opted out, no off-session charge is ever attempted: billed requests that exceed the remaining balance return **HTTP 402** with a `checkoutUrl` (see below), and topping up stays an explicit action. Opting out does **not** remove the saved card — manual top-ups through checkout keep working, and re-enabling later (`{"enabled": true}`) requires no card re-entry. Read the current setting with `GET /api/accounts/{id}/auto-recharge`. Full schema at [Get Auto Top-Up Setting](/api-reference/accounts/auto-recharge-get) and [Update Auto Top-Up Setting](/api-reference/accounts/auto-recharge-update). + +The setting is stored on the account's Stripe customer record, so it survives card changes and is visible to support without a deploy. + ### Tuning The auto top-up amount is currently a constant at **$5 / 500 credits**. Per-account thresholds are not yet exposed — reach out to support if you need a different default. @@ -179,7 +202,7 @@ Current as of this revision of the page. The authoritative source is the per-end | [`POST /api/chat`](/api-reference/chat/workflow) (streaming) | ≥1 credit per turn — variable based on model token usage | | [`GET /api/research/*`](/api-reference/research/search) (artist & non-artist research) | 5 credits per call | | [`POST /api/research/people`](/api-reference/research/people) | 5 credits per call | -| [`POST /api/research/web`](/api-reference/research/web) | 5 credits per call | +| [`POST /api/research/web`](/api-reference/research/web) | 1 credit per call | | [`POST /api/research/extract`](/api-reference/research/extract) | 5 credits × number of URLs | | [`POST /api/research/enrich`](/api-reference/research/enrich) | 5 / 10 / 25 credits (base / core / ultra processor) | | [`POST /api/research/deep`](/api-reference/research/deep) | 25 credits per call | diff --git a/docs.json b/docs.json index dc905ccf..cb0fc0a8 100644 --- a/docs.json +++ b/docs.json @@ -336,7 +336,9 @@ "api-reference/accounts/subscription-get", "api-reference/credits/sessions-create", "api-reference/accounts/credits-get", - "api-reference/accounts/payment-method-get" + "api-reference/accounts/payment-method-get", + "api-reference/accounts/auto-recharge-get", + "api-reference/accounts/auto-recharge-update" ] }, { From 940211b6fb680a4ea1098b0ab160b6bbab41b7b3 Mon Sep 17 00:00:00 2001 From: Sweets Sweetman Date: Wed, 8 Jul 2026 12:43:45 -0500 Subject: [PATCH 2/6] docs(credits): use $ACCOUNT_ID in the opt-out curl example (cubic P3) Co-Authored-By: Claude Fable 5 --- credits.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/credits.mdx b/credits.mdx index b346ef19..08427dea 100644 --- a/credits.mdx +++ b/credits.mdx @@ -135,7 +135,7 @@ The decision tree: Automatic top-up is on by default and can be disabled per account: ```bash -curl -X PATCH "https://api.recoupable.dev/api/accounts/{id}/auto-recharge" \ +curl -sS -X PATCH https://api.recoupable.dev/api/accounts/$ACCOUNT_ID/auto-recharge \ -H "x-api-key: $RECOUP_API_KEY" \ -H "Content-Type: application/json" \ -d '{"enabled": false}' @@ -148,7 +148,7 @@ curl -X PATCH "https://api.recoupable.dev/api/accounts/{id}/auto-recharge" \ } ``` -While opted out, no off-session charge is ever attempted: billed requests that exceed the remaining balance return **HTTP 402** with a `checkoutUrl` (see below), and topping up stays an explicit action. Opting out does **not** remove the saved card — manual top-ups through checkout keep working, and re-enabling later (`{"enabled": true}`) requires no card re-entry. Read the current setting with `GET /api/accounts/{id}/auto-recharge`. Full schema at [Get Auto Top-Up Setting](/api-reference/accounts/auto-recharge-get) and [Update Auto Top-Up Setting](/api-reference/accounts/auto-recharge-update). +While opted out, no off-session charge is ever attempted: billed requests that exceed the remaining balance return **HTTP 402** with a `checkoutUrl` (see below), and topping up stays an explicit action. Opting out does **not** remove the saved card — manual top-ups through checkout keep working, and re-enabling later (`{"enabled": true}`) requires no card re-entry. Read the current setting with `GET /api/accounts/$ACCOUNT_ID/auto-recharge`. Full schema at [Get Auto Top-Up Setting](/api-reference/accounts/auto-recharge-get) and [Update Auto Top-Up Setting](/api-reference/accounts/auto-recharge-update). The setting is stored on the account's Stripe customer record, so it survives card changes and is visible to support without a deploy. From 651a061a84634da141f538e14b6d24e80cbe792f Mon Sep 17 00:00:00 2001 From: "sweetman.eth" Date: Thu, 9 Jul 2026 11:00:02 -0500 Subject: [PATCH 3/6] Update api-reference/openapi/accounts.json --- api-reference/openapi/accounts.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api-reference/openapi/accounts.json b/api-reference/openapi/accounts.json index 037f774f..387a93a9 100644 --- a/api-reference/openapi/accounts.json +++ b/api-reference/openapi/accounts.json @@ -1002,7 +1002,7 @@ }, "/api/accounts/{id}/auto-recharge": { "get": { - "description": "Retrieve whether automatic top-up is enabled for the account. `enabled: true` is the default for every account. `enabled: false` means the account has opted out: no off-session charge is ever attempted, and billed requests that exceed the remaining balance return HTTP 402 with a `checkoutUrl` for an explicit manual top-up instead. The setting lives on the account's Stripe customer record; this endpoint reads it live.", + "description": "Retrieve whether automatic top-up is enabled for the account.", "parameters": [ { "name": "id", From a492a29e448cdf90c91baf9851db6d2711a920fe Mon Sep 17 00:00:00 2001 From: "sweetman.eth" Date: Thu, 9 Jul 2026 11:00:11 -0500 Subject: [PATCH 4/6] Update api-reference/openapi/accounts.json --- api-reference/openapi/accounts.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api-reference/openapi/accounts.json b/api-reference/openapi/accounts.json index 387a93a9..b8c50f33 100644 --- a/api-reference/openapi/accounts.json +++ b/api-reference/openapi/accounts.json @@ -1070,7 +1070,7 @@ } }, "patch": { - "description": "Enable or disable automatic top-up for the account. Disabling never removes the saved card: manual top-ups via checkout keep working, and re-enabling later requires no card re-entry. While disabled, billed requests that exceed the remaining balance return HTTP 402 with a `checkoutUrl` instead of silently charging the saved card.", + "description": "Enable or disable automatic top-up for the account.", "parameters": [ { "name": "id", From 9f5e9e12e53725e18e5140253efbf70e8cc13d4c Mon Sep 17 00:00:00 2001 From: "sweetman.eth" Date: Thu, 9 Jul 2026 11:00:21 -0500 Subject: [PATCH 5/6] Update credits.mdx --- credits.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/credits.mdx b/credits.mdx index 08427dea..b4cf0ba6 100644 --- a/credits.mdx +++ b/credits.mdx @@ -115,7 +115,7 @@ Response shape: ## Automatic top-up -Every billed API request runs a credit gate before it executes. If `remaining_credits` doesn't cover the request's cost **and** the account has a saved Stripe card **and** the account hasn't opted out (see [Opting out](#opting-out) below), Recoup silently charges **$5 (500 credits)** off-session and lets the request proceed. Most accounts with a working card never see an "insufficient credits" failure — the top-up is invisible. +Every billed API request runs a credit gate before it executes. If `remaining_credits` doesn't cover the request's cost **and** the account has a saved card **and** the account hasn't opted out (see [Opting out](#opting-out) below), Recoup silently charges **$5 (500 credits)** off-session and lets the request proceed. Most accounts with a working card never see an "insufficient credits" failure — the top-up is invisible. The decision tree: From c9d7372e5442f5ce1cf165bfba8f018858fad3a3 Mon Sep 17 00:00:00 2001 From: "sweetman.eth" Date: Thu, 9 Jul 2026 11:00:31 -0500 Subject: [PATCH 6/6] Update credits.mdx --- credits.mdx | 1 - 1 file changed, 1 deletion(-) diff --git a/credits.mdx b/credits.mdx index b4cf0ba6..a928d1cd 100644 --- a/credits.mdx +++ b/credits.mdx @@ -150,7 +150,6 @@ curl -sS -X PATCH https://api.recoupable.dev/api/accounts/$ACCOUNT_ID/auto-recha While opted out, no off-session charge is ever attempted: billed requests that exceed the remaining balance return **HTTP 402** with a `checkoutUrl` (see below), and topping up stays an explicit action. Opting out does **not** remove the saved card — manual top-ups through checkout keep working, and re-enabling later (`{"enabled": true}`) requires no card re-entry. Read the current setting with `GET /api/accounts/$ACCOUNT_ID/auto-recharge`. Full schema at [Get Auto Top-Up Setting](/api-reference/accounts/auto-recharge-get) and [Update Auto Top-Up Setting](/api-reference/accounts/auto-recharge-update). -The setting is stored on the account's Stripe customer record, so it survives card changes and is visible to support without a deploy. ### Tuning