# Anytime Capital Payments: complete developer docs > Every page of https://anytime-capital.com/docs as Markdown, in reading order, generated from the same source as the HTML pages. Each page is also available on its own at its URL plus .md. ## Contents - [Anytime Capital Payments developer docs](https://anytime-capital.com/docs.md): Docs for Anytime Capital Payments: payment links, invoices, website checkout, QR payments, payouts, the REST API, webhooks and an MCP server for AI tools. - [Quickstart: take your first payment](https://anytime-capital.com/docs/quickstart.md): Go from a new business account to your first paid payment link with the Anytime Capital Payments API: create a key, create a link, verify a webhook. - [Test mode: build without real money](https://anytime-capital.com/docs/test-mode.md): Build and test every payment flow without real money: test mode, ak_test_ API keys, sandbox test cards, test webhooks, sample data and event simulators. - [Guides and recipes](https://anytime-capital.com/docs/guides.md): Step-by-step guides with complete code for Anytime Capital Payments: Next.js checkout, webhooks, refunds, reconciliation, subscriptions, usage and stablecoins. - [Switch from Stripe](https://anytime-capital.com/docs/switch-from-stripe.md): Import customers, products, plans, discount codes, payment links and open invoices from Stripe, then move subscribers without charging anyone twice. - [Import from Shopify](https://anytime-capital.com/docs/import-from-shopify.md): Bring Shopify products and variants, customers and discount codes over in minutes with read-only access. See what moves, what doesn't, and why. - [Import from WooCommerce](https://anytime-capital.com/docs/import-from-woocommerce.md): Bring WooCommerce products and variations, customers and coupons over with a read-only REST API key. See what moves, what doesn't, and why. - [Import from Square](https://anytime-capital.com/docs/import-from-square.md): Bring your Square item catalog and customer directory over with an access token. See what moves, what doesn't, and how sandbox data works. - [Import customers and products from a CSV file](https://anytime-capital.com/docs/import-from-csv.md): Upload customers or products from any spreadsheet, or a Shopify, Wix, Squarespace or Etsy export. Every row is checked before anything is saved. - [Stripe to Anytime Capital: API mapping](https://anytime-capital.com/docs/stripe-api-mapping.md): Map Stripe API calls to Anytime Capital: checkout, payment links, customers, prices, subscriptions, invoices, refunds and webhooks, in Node and curl. - [Pricing](https://anytime-capital.com/docs/pricing.md): Anytime Capital Payments costs 2.75% + 30¢ per successful card payment, with no monthly, setup or PCI fees, and custom pricing at $1M+ a year. - [Support](https://anytime-capital.com/docs/support.md): Reach Anytime Capital's US-based business team by phone, chat and email, with a dedicated payments team and shared Slack channel for larger businesses. - [Anytime Capital Payments FAQ](https://anytime-capital.com/docs/faq.md): Answers about Anytime Capital Payments: payment methods, payouts, the API and webhooks, AI and MCP integration, security, verification and pricing. - [Hosted checkout, Pay button and branding](https://anytime-capital.com/docs/checkout.md): Take payments with hosted payment links, a Pay button or inline checkout on your website, your own checkout domain and your logo and brand colour. - [Accept a payment from a Next.js app](https://anytime-capital.com/docs/nextjs-checkout.md): A complete Next.js App Router integration: a single-use payment link per order made on the server, a redirect to it, and a verified webhook that marks it paid. - [In-person payments with a QR code](https://anytime-capital.com/docs/in-person.md): Take payments face to face with no card reader: key in an amount, show a QR code, and your customer pays on their own phone with Apple Pay, Google Pay or card. - [Payment methods](https://anytime-capital.com/docs/payment-methods.md): The cards, wallets, bank payments and stablecoins Anytime Capital Payments accepts today, where each is offered, and the methods that are coming soon. - [Stablecoin checkout (USDC and USDT)](https://anytime-capital.com/docs/stablecoin-checkout.md): Let customers pay your payment links and invoices in USDC or USDT, straight to your own business wallet, with exact-amount matching and on-chain confirmation. - [Take USDC and USDT payments in your app](https://anytime-capital.com/docs/accept-stablecoins.md): Take stablecoin payments with no change to your checkout code: switch them on, fulfil on payment.succeeded, and keep the on-chain transaction with each order. - [Fraud rules, 3D Secure and refunds](https://anytime-capital.com/docs/risk-and-3ds.md): Protect your Anytime Capital payments: block card countries, require 3D Secure above an amount, cap payment size, block high-risk scores and auto-refund alerts. - [Automatic Dispute Evidence: Packs, Responses and Win Rate](https://anytime-capital.com/docs/dispute-evidence.md): When a card dispute opens, we build an evidence pack from your records, write the response for the bank's reason and fill in your draft. Track your win rate. - [International Customers and Currency](https://anytime-capital.com/docs/international-customers.md): Customers outside the US pay your links and invoices by card in US dollars. How conversion, refunds and disputes work for them, and what isn't available yet. - [Issue refunds safely with idempotency](https://anytime-capital.com/docs/refund-safely.md): Refund payments from your code without ever refunding twice: a stored Idempotency-Key, retries that are safe, refunds waiting for approval, and events. - [Bank transfer instructions on invoices](https://anytime-capital.com/docs/invoice-bank-transfers.md): Let customers pay invoices by ACH or wire into your own bank account: your details on the invoice page, PDF and email, with the invoice number as reference. - [Discount Codes and Plan Changes on Subscriptions](https://anytime-capital.com/docs/subscription-discounts.md): Offer discount codes on subscription plans for the first payment, several months or every payment, and move subscribers between plans with fair proration. - [Sell subscriptions with coupons and plan changes](https://anytime-capital.com/docs/sell-subscriptions.md): Create subscription plans, send customers to subscribe, offer coupon codes, let them change plans, and keep access in your app in step with webhooks. - [Usage-based billing](https://anytime-capital.com/docs/usage-billing.md): Bill customers by use with Anytime Capital Payments: meters, usage events sent from your code, and usage invoiced and charged each billing period. - [Bill usage with meters: send usage from your app](https://anytime-capital.com/docs/bill-usage-with-meters.md): Send usage from your app to Anytime Capital meters in safe, idempotent batches of 100, read a customer's usage back, and let it bill on their subscription. - [Payouts: every way to get your money](https://anytime-capital.com/docs/payouts.md): Withdraw your Anytime Capital balance to your bank, standard or instant where eligible. Local rails in 71+ countries. - [Reconcile payments and payouts nightly](https://anytime-capital.com/docs/reconcile-payouts.md): A nightly job that pulls a day's payments, refunds and payouts from the Anytime Capital API, writes a CSV, matches payouts to your bank and flags problems. - [Accounting sync: QuickBooks Online and Xero](https://anytime-capital.com/docs/accounting-sync.md): Post every payment, refund, dispute, card processing charge and payout from Anytime Capital Payments to QuickBooks Online or Xero, once each. - [Revenue Analytics: Volume, MRR and Customers](https://anytime-capital.com/docs/revenue-analytics.md): How the Analytics page counts gross and net volume, fees, refunds, disputes, MRR movement, churn, retention cohorts, lifetime revenue and the payouts forecast. - [Authentication and API keys](https://anytime-capital.com/docs/authentication.md): Authenticate Anytime Capital Payments API requests with a secret key: restrict each key to the resources it needs, and rotate or revoke keys safely. - [Errors and retries](https://anytime-capital.com/docs/errors.md): Every Anytime Capital Payments API error has one JSON shape: a stable code to branch on and a message for people. How to handle them and retry safely. - [Pagination](https://anytime-capital.com/docs/pagination.md): List endpoints in the Anytime Capital Payments API return the newest objects first, a page at a time. Page with limit and the opaque nextCursor. - [Idempotent requests](https://anytime-capital.com/docs/idempotency.md): Send an Idempotency-Key header on every POST to the Anytime Capital Payments API so a retried request never creates a second link, invoice or email. - [Rate limits](https://anytime-capital.com/docs/rate-limits.md): Each Anytime Capital Payments API key may make 100 requests a minute. Over that, requests answer 429 with a Retry-After header in seconds. - [Webhooks](https://anytime-capital.com/docs/webhooks.md): Get Anytime Capital Payments events by webhook: payments, refunds, disputes, invoices and subscriptions, signed with HMAC-SHA256 and retried for 24 hours. - [Verify webhook signatures in Node.js, Python and PHP](https://anytime-capital.com/docs/webhook-signatures.md): Copy-paste webhook handlers that verify the Anytime-Signature header on the raw body: Express, Next.js, Flask, FastAPI and PHP, and how to test them locally. - [Node.js SDK (preview)](https://anytime-capital.com/docs/node-sdk.md): The @anytimecapital/node SDK preview: typed resources, auto-pagination, Idempotency-Key retries, typed errors and webhook verification for Node.js 18+. - [Anytime CLI (preview)](https://anytime-capital.com/docs/cli.md): The anytime CLI preview: forward test webhook events to localhost, signed like real deliveries, and trigger payment, refund and dispute test events. - [Postman collection](https://anytime-capital.com/docs/postman.md): Import every Anytime Capital Payments API endpoint into Postman, Insomnia or Bruno from one link, with your test key as a variable and example bodies. - [Use Anytime Capital with AI agents](https://anytime-capital.com/docs/build-with-ai.md): Connect Claude, Cursor, VS Code and your own agents to Anytime Capital Payments through the MCP server, give them the docs as Markdown, and keep them safe. - [Safe AI agents: restricted keys, approvals and limits](https://anytime-capital.com/docs/ai-agent-safety.md): Give AI agents least-privilege access to Anytime Capital Payments: a restricted key per agent, presets per use case, refund approval limits and idempotency. - [Build a custom agent on the MCP server](https://anytime-capital.com/docs/mcp-custom-agents.md): Connect your own agent to Anytime Capital Payments over MCP with an API key: raw JSON-RPC, the TypeScript and Python MCP SDKs, and the Claude API MCP connector. - [Prompt library for payments agents](https://anytime-capital.com/docs/ai-prompt-library.md): Prompts for AI agents connected to Anytime Capital Payments: invoice customers, chase overdue invoices, reconcile a month, refund safely and win customers back. - [Take payments on Wix, Squarespace, Webflow and other site builders](https://anytime-capital.com/docs/website-builders.md): Add a Pay button, the checkout or a payment link to Wix, Squarespace, Webflow, WordPress, Framer, GoDaddy, Carrd, Ghost, Linktree or plain HTML, step by step. - [Shopify](https://anytime-capital.com/docs/shopify.md): Take card payments at your Shopify checkout with the Anytime Capital Payments app: install, link your business, activate it, and refund from Shopify. - [Shopify orders paid by invoice](https://anytime-capital.com/docs/shopify-order-invoices.md): Take card payments on Shopify today: a manual payment method, your store's own app, and Anytime Capital invoices that mark orders paid in Shopify. - [WooCommerce plugin](https://anytime-capital.com/docs/woocommerce.md): Add Anytime Capital Payments to WooCommerce: install the plugin, connect your API key and a signed webhook, and take card payments with refunds from orders. - [Zapier](https://anytime-capital.com/docs/zapier.md): Connect Anytime Capital Payments to your other apps with Zapier: start Zaps on payments, refunds and invoices, and create payment links, invoices and refunds. - [Sale Alerts in Slack, Discord and Microsoft Teams](https://anytime-capital.com/docs/team-chat-alerts.md): Post new payments, refunds, disputes, payouts, subscriptions, invoices and a daily summary to your team's Slack, Discord or Microsoft Teams channel. - [Payments API](https://anytime-capital.com/docs/payments.md): List and retrieve payments with the Anytime Capital Payments API: status, amounts, net after fees, method, customer, metadata and disputes, filtered by date. - [Refunds API](https://anytime-capital.com/docs/refunds.md): Refund a payment in full or in part with the Anytime Capital Payments API, safely retried with an Idempotency-Key, within your refund approval limit. - [Disputes API](https://anytime-capital.com/docs/disputes.md): Read the disputes and chargebacks on your payments with the Anytime Capital Payments API: status, reason, amount, evidence deadline and evidence pack. - [Payment links API](https://anytime-capital.com/docs/payment-links.md): Create, update and deactivate hosted payment links with the Anytime Capital Payments API: one per order or reusable, with metadata, and prefilled checkout. - [Invoices API](https://anytime-capital.com/docs/invoices.md): Create, list and send invoices with the Anytime Capital Payments API: line items, discount, tax and deposits, emailed in your branding and paid by card. - [Customers API](https://anytime-capital.com/docs/customers.md): Create, update and look up your saved customers with the Anytime Capital Payments API, one per email address, to match payments and invoices to them. - [Products API](https://anytime-capital.com/docs/products.md): Manage your product catalog with the Anytime Capital Payments API: create products with a price, SKU and shipping flag, update them and archive them. - [Subscription plans and subscriptions API](https://anytime-capital.com/docs/subscriptions.md): Create subscription plans, list subscribers, and cancel, pause or resume subscriptions with the Anytime Capital Payments API, with trials and webhooks. - [Discount codes API](https://anytime-capital.com/docs/discount-codes.md): Create and deactivate discount codes with the Anytime Capital Payments API: a percentage or amount off, with an optional use limit and expiry date. - [Meters and usage events API](https://anytime-capital.com/docs/meters.md): Send usage events to Anytime Capital meters one at a time or in batches of 100, list your meters and read a customer's usage, for usage-based billing. - [Payouts and balance API](https://anytime-capital.com/docs/payouts-api.md): Read your payouts and your payments balance with the Anytime Capital Payments API: status, amount, bank trace number and expected arrival, read only. - [Webhook endpoints API](https://anytime-capital.com/docs/webhook-endpoints.md): Subscribe and unsubscribe webhook URLs from your own code with the Anytime Capital Payments API, REST-hook style, and get each endpoint's signing secret. - [Events API](https://anytime-capital.com/docs/events.md): Read every event Anytime Capital Payments emitted for your business in the last 30 days, to reconcile or catch up after missed webhook deliveries. - [Test helpers API](https://anytime-capital.com/docs/test-helpers.md): Send your test webhook endpoints a payment, refund, dispute, invoice or subscription event on demand, with an ak_test_ key. Never with a live key. - [OpenAPI 3.1 document](https://anytime-capital.com/docs/openapi.md): The whole Anytime Capital Payments API as an OpenAPI 3.1 document: every endpoint, schema, permission and error, public, for code generators and AI tools. - [API error code catalogue](https://anytime-capital.com/docs/error-codes.md): Every error code the Anytime Capital Payments API returns: its HTTP status, what it means, how to fix it, whether to retry, and which endpoints return it. - [Changelog](https://anytime-capital.com/docs/changelog.md): What's new in Anytime Capital Payments, newest first: API endpoints, webhook events, the MCP server, the Node SDK, checkout, billing and integrations. - [Glossary](https://anytime-capital.com/docs/glossary.md): The words used across the Anytime Capital Payments docs and API, from approval limit and clearing account to idempotency key, meter and signing secret. --- # Anytime Capital Payments developer docs > Docs for Anytime Capital Payments: payment links, invoices, website checkout, QR payments, payouts, the REST API, webhooks and an MCP server for AI tools. Source: https://anytime-capital.com/docs · Markdown: https://anytime-capital.com/docs.md · All docs: https://anytime-capital.com/docs/llms-full.txt Anytime Capital Payments lets a verified business take payments by payment link, invoice, a Pay button on its own website and QR code in person, then pay the money out from one balance. Online card payments are 2.75% + 30¢, and you can start without writing code: everything here is also in the dashboard. ## Choose your path ### I write code - [Quickstart](https://anytime-capital.com/docs/quickstart.md): From a new account to a paid payment link and a verified webhook. - [Accept a payment in Next.js](https://anytime-capital.com/docs/nextjs-checkout.md): The whole flow in one App Router app, every file included. - [API reference](https://anytime-capital.com/docs/payments.md): Every endpoint with curl, Node.js and Python, generated from the OpenAPI document. - [Verify webhooks](https://anytime-capital.com/docs/webhook-signatures.md): Signature checks for Express, Next.js, Flask, FastAPI and PHP. ### I don't write code - [Checkout & website](https://anytime-capital.com/docs/checkout.md): Payment links, a Pay button for your site, your logo and colours. - [In person (QR)](https://anytime-capital.com/docs/in-person.md): Key in an amount and let the customer pay on their phone. - [Shopify, WooCommerce and Zapier](https://anytime-capital.com/docs/guides.md#move-and-connect): Plug payments into the tools you already use. - [Payout options](https://anytime-capital.com/docs/payouts.md): Payouts to your bank today, how fast they arrive, and what is coming soon. ### I'm building with AI - [AI agents hub](https://anytime-capital.com/docs/build-with-ai.md): Connect Claude, Cursor or VS Code to your account through the MCP server. - [Safe AI agents](https://anytime-capital.com/docs/ai-agent-safety.md): A restricted key per agent, approval limits, and what agents can never do. - [Prompt library](https://anytime-capital.com/docs/ai-prompt-library.md): Prompts for invoicing, reconciling, refunds and subscriptions. - [Custom agents](https://anytime-capital.com/docs/mcp-custom-agents.md): The MCP server from your own code and the Claude API. > **Docs for your AI assistant:** Every page here is also Markdown (add `.md` to its URL), all of them are in one file at [/docs/llms-full.txt](https://anytime-capital.com/docs/llms-full.txt), and the whole API is in [OpenAPI 3.1](https://anytime-capital.com/docs/openapi.md). ## What you can build - **Payment links:** a hosted checkout page for one price, made in the dashboard or with `POST /api/v1/payment-links`, shared anywhere or created per order. - **Invoices:** line items, tax, deposits and reminders, emailed in your branding and paid by card. - **Website checkout:** a Pay button that opens checkout over your page, or the checkout inline, in a few lines of HTML. - **In person:** key in an amount, show a QR code, and the customer pays on their own phone. No card reader. - **Subscriptions and usage:** plans with trials, coupons and plan changes, and usage billed from meters. - **Automation:** read payments, refunds, payouts and disputes, manage customers, products and codes, and react to every change by webhook. - **AI agents:** let Claude or another assistant draft invoices, make links, look up payments and refund within your approval limit through the MCP server. ## The API at a glance | Topic | Detail | | --- | --- | | Base URL | `https://anytime-capital.com/api/v1` | | Authentication | `Authorization: Bearer ak_live_…`, a secret key from Settings → Developers. See [Authentication](https://anytime-capital.com/docs/authentication.md). | | Format | JSON in and out. Every object has an `object` field naming its type. | | Money and time | Integer cents (`12500` is $125.00); ISO 8601 times in UTC. | | Errors | `{ "error": { "code", "message" } }` with a stable code. See [Errors](https://anytime-capital.com/docs/errors.md). | | Lists | Newest first, `limit` up to 100 (default 25) and `cursor`. See [Pagination](https://anytime-capital.com/docs/pagination.md). | | Retries | `Idempotency-Key` on every POST, remembered 24 hours. See [Idempotency](https://anytime-capital.com/docs/idempotency.md). | | Rate limit | 100 requests a minute per key. See [Rate limits](https://anytime-capital.com/docs/rate-limits.md). | | Webhooks | Signed with HMAC-SHA256, retried for 24 hours. See [Webhooks](https://anytime-capital.com/docs/webhooks.md). | | MCP server | `https://anytime-capital.com/api/mcp`, same key. See [Build with AI](https://anytime-capital.com/docs/build-with-ai.md). | New to Anytime Capital? [Create your business account](https://anytime-capital.com/business/get-started): the API opens once your business is verified. ## Every page ### Get started - [Quickstart](https://anytime-capital.com/docs/quickstart.md): Go from a new business account to your first paid payment link with the Anytime Capital Payments API: create a key, create a link, verify a webhook. - [Test mode](https://anytime-capital.com/docs/test-mode.md): Build and test every payment flow without real money: test mode, ak_test_ API keys, sandbox test cards, test webhooks, sample data and event simulators. - [Guides & recipes](https://anytime-capital.com/docs/guides.md): Step-by-step guides with complete code for Anytime Capital Payments: Next.js checkout, webhooks, refunds, reconciliation, subscriptions, usage and stablecoins. - [Switch from Stripe](https://anytime-capital.com/docs/switch-from-stripe.md): Import customers, products, plans, discount codes, payment links and open invoices from Stripe, then move subscribers without charging anyone twice. - [Import from Shopify](https://anytime-capital.com/docs/import-from-shopify.md): Bring Shopify products and variants, customers and discount codes over in minutes with read-only access. See what moves, what doesn't, and why. - [Import from WooCommerce](https://anytime-capital.com/docs/import-from-woocommerce.md): Bring WooCommerce products and variations, customers and coupons over with a read-only REST API key. See what moves, what doesn't, and why. - [Import from Square](https://anytime-capital.com/docs/import-from-square.md): Bring your Square item catalog and customer directory over with an access token. See what moves, what doesn't, and how sandbox data works. - [Import a CSV file](https://anytime-capital.com/docs/import-from-csv.md): Upload customers or products from any spreadsheet, or a Shopify, Wix, Squarespace or Etsy export. Every row is checked before anything is saved. - [Stripe API mapping](https://anytime-capital.com/docs/stripe-api-mapping.md): Map Stripe API calls to Anytime Capital: checkout, payment links, customers, prices, subscriptions, invoices, refunds and webhooks, in Node and curl. - [Pricing](https://anytime-capital.com/docs/pricing.md): Anytime Capital Payments costs 2.75% + 30¢ per successful card payment, with no monthly, setup or PCI fees, and custom pricing at $1M+ a year. - [Support](https://anytime-capital.com/docs/support.md): Reach Anytime Capital's US-based business team by phone, chat and email, with a dedicated payments team and shared Slack channel for larger businesses. - [FAQ](https://anytime-capital.com/docs/faq.md): Answers about Anytime Capital Payments: payment methods, payouts, the API and webhooks, AI and MCP integration, security, verification and pricing. ### Payments - [Checkout & website](https://anytime-capital.com/docs/checkout.md): Take payments with hosted payment links, a Pay button or inline checkout on your website, your own checkout domain and your logo and brand colour. - [Guide: Next.js checkout](https://anytime-capital.com/docs/nextjs-checkout.md): A complete Next.js App Router integration: a single-use payment link per order made on the server, a redirect to it, and a verified webhook that marks it paid. - [In person (QR)](https://anytime-capital.com/docs/in-person.md): Take payments face to face with no card reader: key in an amount, show a QR code, and your customer pays on their own phone with Apple Pay, Google Pay or card. - [Payment methods](https://anytime-capital.com/docs/payment-methods.md): The cards, wallets, bank payments and stablecoins Anytime Capital Payments accepts today, where each is offered, and the methods that are coming soon. - [Stablecoin checkout](https://anytime-capital.com/docs/stablecoin-checkout.md): Let customers pay your payment links and invoices in USDC or USDT, straight to your own business wallet, with exact-amount matching and on-chain confirmation. - [Guide: take stablecoins](https://anytime-capital.com/docs/accept-stablecoins.md): Take stablecoin payments with no change to your checkout code: switch them on, fulfil on payment.succeeded, and keep the on-chain transaction with each order. - [Fraud rules & 3D Secure](https://anytime-capital.com/docs/risk-and-3ds.md): Protect your Anytime Capital payments: block card countries, require 3D Secure above an amount, cap payment size, block high-risk scores and auto-refund alerts. - [Dispute evidence](https://anytime-capital.com/docs/dispute-evidence.md): When a card dispute opens, we build an evidence pack from your records, write the response for the bank's reason and fill in your draft. Track your win rate. - [International customers](https://anytime-capital.com/docs/international-customers.md): Customers outside the US pay your links and invoices by card in US dollars. How conversion, refunds and disputes work for them, and what isn't available yet. - [Guide: safe refunds](https://anytime-capital.com/docs/refund-safely.md): Refund payments from your code without ever refunding twice: a stored Idempotency-Key, retries that are safe, refunds waiting for approval, and events. - [Bank transfers on invoices](https://anytime-capital.com/docs/invoice-bank-transfers.md): Let customers pay invoices by ACH or wire into your own bank account: your details on the invoice page, PDF and email, with the invoice number as reference. ### Billing - [Subscription discounts](https://anytime-capital.com/docs/subscription-discounts.md): Offer discount codes on subscription plans for the first payment, several months or every payment, and move subscribers between plans with fair proration. - [Guide: sell subscriptions](https://anytime-capital.com/docs/sell-subscriptions.md): Create subscription plans, send customers to subscribe, offer coupon codes, let them change plans, and keep access in your app in step with webhooks. - [Usage billing](https://anytime-capital.com/docs/usage-billing.md): Bill customers by use with Anytime Capital Payments: meters, usage events sent from your code, and usage invoiced and charged each billing period. - [Guide: report usage](https://anytime-capital.com/docs/bill-usage-with-meters.md): Send usage from your app to Anytime Capital meters in safe, idempotent batches of 100, read a customer's usage back, and let it bill on their subscription. ### Money movement - [Payout options](https://anytime-capital.com/docs/payouts.md): Withdraw your Anytime Capital balance to your bank, standard or instant where eligible. Local rails in 71+ countries. - [Guide: reconcile payouts](https://anytime-capital.com/docs/reconcile-payouts.md): A nightly job that pulls a day's payments, refunds and payouts from the Anytime Capital API, writes a CSV, matches payouts to your bank and flags problems. - [Accounting sync](https://anytime-capital.com/docs/accounting-sync.md): Post every payment, refund, dispute, card processing charge and payout from Anytime Capital Payments to QuickBooks Online or Xero, once each. - [Revenue analytics](https://anytime-capital.com/docs/revenue-analytics.md): How the Analytics page counts gross and net volume, fees, refunds, disputes, MRR movement, churn, retention cohorts, lifetime revenue and the payouts forecast. ### Developers - [Authentication](https://anytime-capital.com/docs/authentication.md): Authenticate Anytime Capital Payments API requests with a secret key: restrict each key to the resources it needs, and rotate or revoke keys safely. - [Errors & retries](https://anytime-capital.com/docs/errors.md): Every Anytime Capital Payments API error has one JSON shape: a stable code to branch on and a message for people. How to handle them and retry safely. - [Pagination](https://anytime-capital.com/docs/pagination.md): List endpoints in the Anytime Capital Payments API return the newest objects first, a page at a time. Page with limit and the opaque nextCursor. - [Idempotency](https://anytime-capital.com/docs/idempotency.md): Send an Idempotency-Key header on every POST to the Anytime Capital Payments API so a retried request never creates a second link, invoice or email. - [Rate limits](https://anytime-capital.com/docs/rate-limits.md): Each Anytime Capital Payments API key may make 100 requests a minute. Over that, requests answer 429 with a Retry-After header in seconds. - [Webhooks](https://anytime-capital.com/docs/webhooks.md): Get Anytime Capital Payments events by webhook: payments, refunds, disputes, invoices and subscriptions, signed with HMAC-SHA256 and retried for 24 hours. - [Verify webhooks](https://anytime-capital.com/docs/webhook-signatures.md): Copy-paste webhook handlers that verify the Anytime-Signature header on the raw body: Express, Next.js, Flask, FastAPI and PHP, and how to test them locally. - [Node SDK (preview)](https://anytime-capital.com/docs/node-sdk.md): The @anytimecapital/node SDK preview: typed resources, auto-pagination, Idempotency-Key retries, typed errors and webhook verification for Node.js 18+. - [CLI (preview)](https://anytime-capital.com/docs/cli.md): The anytime CLI preview: forward test webhook events to localhost, signed like real deliveries, and trigger payment, refund and dispute test events. - [Postman collection](https://anytime-capital.com/docs/postman.md): Import every Anytime Capital Payments API endpoint into Postman, Insomnia or Bruno from one link, with your test key as a variable and example bodies. ### AI - [AI agents hub](https://anytime-capital.com/docs/build-with-ai.md): Connect Claude, Cursor, VS Code and your own agents to Anytime Capital Payments through the MCP server, give them the docs as Markdown, and keep them safe. - [Safe AI agents](https://anytime-capital.com/docs/ai-agent-safety.md): Give AI agents least-privilege access to Anytime Capital Payments: a restricted key per agent, presets per use case, refund approval limits and idempotency. - [Custom agents](https://anytime-capital.com/docs/mcp-custom-agents.md): Connect your own agent to Anytime Capital Payments over MCP with an API key: raw JSON-RPC, the TypeScript and Python MCP SDKs, and the Claude API MCP connector. - [Prompt library](https://anytime-capital.com/docs/ai-prompt-library.md): Prompts for AI agents connected to Anytime Capital Payments: invoice customers, chase overdue invoices, reconcile a month, refund safely and win customers back. ### Integrations - [Website builders](https://anytime-capital.com/docs/website-builders.md): Add a Pay button, the checkout or a payment link to Wix, Squarespace, Webflow, WordPress, Framer, GoDaddy, Carrd, Ghost, Linktree or plain HTML, step by step. - [Shopify](https://anytime-capital.com/docs/shopify.md): Take card payments at your Shopify checkout with the Anytime Capital Payments app: install, link your business, activate it, and refund from Shopify. - [Shopify orders by invoice](https://anytime-capital.com/docs/shopify-order-invoices.md): Take card payments on Shopify today: a manual payment method, your store's own app, and Anytime Capital invoices that mark orders paid in Shopify. - [WooCommerce](https://anytime-capital.com/docs/woocommerce.md): Add Anytime Capital Payments to WooCommerce: install the plugin, connect your API key and a signed webhook, and take card payments with refunds from orders. - [Zapier](https://anytime-capital.com/docs/zapier.md): Connect Anytime Capital Payments to your other apps with Zapier: start Zaps on payments, refunds and invoices, and create payment links, invoices and refunds. - [Slack, Discord and Teams](https://anytime-capital.com/docs/team-chat-alerts.md): Post new payments, refunds, disputes, payouts, subscriptions, invoices and a daily summary to your team's Slack, Discord or Microsoft Teams channel. ### Reference - [Payments](https://anytime-capital.com/docs/payments.md): List and retrieve payments with the Anytime Capital Payments API: status, amounts, net after fees, method, customer, metadata and disputes, filtered by date. - [Refunds](https://anytime-capital.com/docs/refunds.md): Refund a payment in full or in part with the Anytime Capital Payments API, safely retried with an Idempotency-Key, within your refund approval limit. - [Disputes](https://anytime-capital.com/docs/disputes.md): Read the disputes and chargebacks on your payments with the Anytime Capital Payments API: status, reason, amount, evidence deadline and evidence pack. - [Payment links](https://anytime-capital.com/docs/payment-links.md): Create, update and deactivate hosted payment links with the Anytime Capital Payments API: one per order or reusable, with metadata, and prefilled checkout. - [Invoices](https://anytime-capital.com/docs/invoices.md): Create, list and send invoices with the Anytime Capital Payments API: line items, discount, tax and deposits, emailed in your branding and paid by card. - [Customers](https://anytime-capital.com/docs/customers.md): Create, update and look up your saved customers with the Anytime Capital Payments API, one per email address, to match payments and invoices to them. - [Products](https://anytime-capital.com/docs/products.md): Manage your product catalog with the Anytime Capital Payments API: create products with a price, SKU and shipping flag, update them and archive them. - [Subscriptions & plans](https://anytime-capital.com/docs/subscriptions.md): Create subscription plans, list subscribers, and cancel, pause or resume subscriptions with the Anytime Capital Payments API, with trials and webhooks. - [Discount codes](https://anytime-capital.com/docs/discount-codes.md): Create and deactivate discount codes with the Anytime Capital Payments API: a percentage or amount off, with an optional use limit and expiry date. - [Meters & usage events](https://anytime-capital.com/docs/meters.md): Send usage events to Anytime Capital meters one at a time or in batches of 100, list your meters and read a customer's usage, for usage-based billing. - [Payouts & balance](https://anytime-capital.com/docs/payouts-api.md): Read your payouts and your payments balance with the Anytime Capital Payments API: status, amount, bank trace number and expected arrival, read only. - [Webhook endpoints](https://anytime-capital.com/docs/webhook-endpoints.md): Subscribe and unsubscribe webhook URLs from your own code with the Anytime Capital Payments API, REST-hook style, and get each endpoint's signing secret. - [Events](https://anytime-capital.com/docs/events.md): Read every event Anytime Capital Payments emitted for your business in the last 30 days, to reconcile or catch up after missed webhook deliveries. - [Test helpers](https://anytime-capital.com/docs/test-helpers.md): Send your test webhook endpoints a payment, refund, dispute, invoice or subscription event on demand, with an ak_test_ key. Never with a live key. - [OpenAPI spec](https://anytime-capital.com/docs/openapi.md): The whole Anytime Capital Payments API as an OpenAPI 3.1 document: every endpoint, schema, permission and error, public, for code generators and AI tools. - [Error codes](https://anytime-capital.com/docs/error-codes.md): Every error code the Anytime Capital Payments API returns: its HTTP status, what it means, how to fix it, whether to retry, and which endpoints return it. - [Changelog](https://anytime-capital.com/docs/changelog.md): What's new in Anytime Capital Payments, newest first: API endpoints, webhook events, the MCP server, the Node SDK, checkout, billing and integrations. - [Glossary](https://anytime-capital.com/docs/glossary.md): The words used across the Anytime Capital Payments docs and API, from approval limit and clearing account to idempotency key, meter and signing secret. --- # Quickstart: take your first payment > Go from a new business account to your first paid payment link with the Anytime Capital Payments API: create a key, create a link, verify a webhook. Source: https://anytime-capital.com/docs/quickstart · Markdown: https://anytime-capital.com/docs/quickstart.md · All docs: https://anytime-capital.com/docs/llms-full.txt This guide takes you from a new account to a paid payment link and a verified webhook in about fifteen minutes. You need a terminal and, for the webhook, a server reachable over `https://`. ## 1. Open a business account and get verified [Create your business account](https://anytime-capital.com/business/get-started) and finish verification in **Settings → Verification**. The API answers `403 NOT_ENROLLED` until your business is verified and set up for card payments. > **Start in test mode:** You don't have to wait for verification to build: turn on **Test mode** in the dashboard header, create a test key (`ak_test_…`) and follow this guide with test data and the sandbox test cards. No real money moves. See [Test mode](https://anytime-capital.com/docs/test-mode.md). ## 2. Create a secret key In **Settings → Developers**, choose **Create key** and copy it: it's shown once. Keys made in test mode start with `ak_test_`, live ones with `ak_live_`. Keep it in an environment variable on your server, never in front-end code. Check it works: ```bash export ANYTIME_API_KEY="ak_live_…" curl "https://anytime-capital.com/api/v1/payments?limit=1" \ -H "Authorization: Bearer $ANYTIME_API_KEY" ``` A `200` with `{"object": "list", …}` means you're connected. A `401` means the key is wrong; see [Authentication](https://anytime-capital.com/docs/authentication.md). ## 3. Create a payment link **curl** ```bash curl -X POST https://anytime-capital.com/api/v1/payment-links \ -H "Authorization: Bearer $ANYTIME_API_KEY" \ -H "Idempotency-Key: order-1042" \ -H "Content-Type: application/json" \ -d '{ "title": "Consulting — October", "amountCents": 12500, "singleUse": true, "metadata": { "orderId": "1042" } }' ``` **Node.js 18+** ```javascript import { randomUUID } from "node:crypto"; // Server-side only: the key never reaches the browser. const res = await fetch("https://anytime-capital.com/api/v1/payment-links", { method: "POST", headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}`, "Content-Type": "application/json", "Idempotency-Key": randomUUID(), // store it with the order and reuse it on retries }, body: JSON.stringify({ title: "Consulting — October", amountCents: 12500, singleUse: true, metadata: { orderId: "1042" }, }), }); const link = await res.json(); if (!res.ok) throw new Error(`${link.error.code}: ${link.error.message}`); console.log(link.url); // send the customer here ``` **Python** ```python import os, uuid, requests res = requests.post( "https://anytime-capital.com/api/v1/payment-links", headers={ "Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}", "Idempotency-Key": str(uuid.uuid4()), # store it with the order and reuse it on retries }, json={"title": "Consulting — October", "amountCents": 12500, "singleUse": True, "metadata": {"orderId": "1042"}}, timeout=30, ) link = res.json() if not res.ok: raise RuntimeError(f"{link['error']['code']}: {link['error']['message']}") print(link["url"]) # send the customer here ``` **Response (201)** ```json { "object": "payment_link", "livemode": true, "id": "pl_4kTz8QwN2xYb6RcD1mPa", "url": "https://anytime-capital.com/pay/business/pl_4kTz8QwN2xYb6RcD1mPa", "title": "Consulting — October", "description": null, "amountCents": 12500, "currency": "usd", "active": true, "singleUse": true, "shippingCents": 0, "collectShipping": false, "allowDiscountCodes": false, "successUrl": null, "confirmationMessage": null, "requireTermsConsent": false, "metadata": { "orderId": "1042" }, "items": [], "createdAt": "2026-10-01T09:30:00.000Z", "deactivatedAt": null, "allowQuantity": false, "maxQuantity": null, "customFields": [], "tipsEnabled": false, "payWhatYouWant": false, "minAmountCents": null } ``` ## 4. Send the customer to the link Redirect the customer to `url`, email or text it, or show it as a QR code. To keep customers on your site, open the same link as a Pay button or an inline checkout: see [Checkout](https://anytime-capital.com/docs/checkout.md). Add `successUrl` when you create the link to bring them back to your site afterwards. ## 5. Confirm payment with a webhook In **Settings → Developers**, choose **Add endpoint**, enter your `https://` URL and pick `payment.succeeded`. Store the signing secret as `ANYTIME_WEBHOOK_SECRET`, verify the `Anytime-Signature` header on every request, and mark order `data.metadata.orderId` paid. The full verification code for Node.js, Next.js and Python is on [Webhooks](https://anytime-capital.com/docs/webhooks.md). ## 6. Check your payments ```bash curl "https://anytime-capital.com/api/v1/payments?status=paid&limit=10" \ -H "Authorization: Bearer $ANYTIME_API_KEY" ``` > **Test first, then go live:** Run every step above in test mode with a test key: pay the link with the sandbox test card, and use the [event simulator](https://anytime-capital.com/docs/test-mode.md#test-your-integration) to send your endpoint refunds, disputes and renewals. Then swap in a live key and a live endpoint's secret; nothing else in your code changes. ## Node SDK `@anytimecapital/node` wraps the API for Node.js 18 and later, with no dependencies: typed resources, `for await` over every page of a list, an Idempotency-Key on every write so its automatic retries (on `429`, `5xx` and network errors, honouring `Retry-After`) never act twice, typed errors, and webhook verification. It isn't on npm yet; until it is, call the API directly as above or generate a client from the OpenAPI document. Its whole interface is on [Node SDK](https://anytime-capital.com/docs/node-sdk.md). **Preview** ```typescript import { AnytimeCapital, AnytimeCapitalError } from "@anytimecapital/node"; const client = new AnytimeCapital({ apiKey: process.env.ANYTIME_API_KEY! }); const link = await client.paymentLinks.create( { title: "Order 1042", amountCents: 12500, singleUse: true, metadata: { orderId: "1042" } }, { idempotencyKey: "order-1042-link" }, ); for await (const payment of client.payments.list({ status: "paid" })) { console.log(payment.id, payment.paymentLinkId, payment.metadata.orderId); } // Catch up on events (the last 30 days), e.g. after your webhook endpoint was down: for await (const event of client.events.list({ type: "payment.*", createdFrom: "2026-10-20" })) { await handleOnce(event); // skip ids you've already handled } // In your webhook handler, with the raw request body. During a secret // rotation, pass both secrets: an event verifies if either signed it. const event = AnytimeCapital.webhooks.constructEvent(rawBody, signatureHeader, [ process.env.ANYTIME_WEBHOOK_SECRET!, process.env.ANYTIME_WEBHOOK_SECRET_PREVIOUS!, ]); // Every error carries the API's code and the Request-Id the request log shows. try { await client.refunds.create({ paymentId: "pay_7Hq2LmX9cR4tVb" }); } catch (error) { if (error instanceof AnytimeCapitalError) console.error(error.code, error.requestId); } // Test mode only (an ak_test_ key): send your test endpoints a dispute now. await client.testHelpers.events.create({ type: "dispute.opened" }); ``` `client.events` lists and retrieves [events](https://anytime-capital.com/docs/webhooks.md#events-api) (the `events` permission). `constructEvent` accepts one signing secret or several, and checks every `v1=` in the header, so events signed during a [secret rotation](https://anytime-capital.com/docs/webhooks.md#rotating-the-signing-secret) verify with the old secret or the new one. With a test key, `client.testHelpers.events.create` sends your test endpoints a [simulated event](https://anytime-capital.com/docs/test-mode.md#test-your-integration). ## OpenAPI The whole API is described in OpenAPI 3.1 at `https://anytime-capital.com/api/v1/openapi.json`: every endpoint with its parameters, bodies, responses and errors, the permission each needs (`x-anytime-permission`) and whether it takes an Idempotency-Key. It is public, so API clients, code generators and AI tools can load it directly. See [OpenAPI](https://anytime-capital.com/docs/openapi.md). ```bash curl https://anytime-capital.com/api/v1/openapi.json ``` ## Next steps - [Accept a payment in Next.js](https://anytime-capital.com/docs/nextjs-checkout.md): This flow as a complete App Router app. - [Invoices](https://anytime-capital.com/docs/invoices.md): Bill a customer by email with line items, tax and deposits. - [Errors and retries](https://anytime-capital.com/docs/errors.md): One error shape, and how to retry safely. - [AI agents](https://anytime-capital.com/docs/build-with-ai.md): Let Claude or your editor's assistant write the integration. --- # Test mode: build without real money > Build and test every payment flow without real money: test mode, ak_test_ API keys, sandbox test cards, test webhooks, sample data and event simulators. Source: https://anytime-capital.com/docs/test-mode · Markdown: https://anytime-capital.com/docs/test-mode.md · All docs: https://anytime-capital.com/docs/llms-full.txt Test mode is a full copy of your business where no real money moves. Payment links, invoices, quotes, checkout, subscriptions, refunds, the API and webhooks all work, on test data that is kept apart from your live data. You can use it before your business is verified, so you can build your integration while verification runs. ## Switch test mode on 1. In the dashboard, turn on **Test mode** in the header. Anyone on your team can: the test business has the same people with the same roles. 2. An amber bar reading **Test mode — no real money moves** shows on every page while it is on, and on your test checkout, invoice and billing pages. 3. Everything you create is test data: it never appears in live mode, and live data never appears in test mode. 4. Turn the switch off (or choose **Switch to live** in the bar) to go back to your live data. > **Live only:** Banking, crypto, payouts to your bank, verification, your team, referrals, custom checkout domains, accounting sync, stablecoin checkout and 1099-K requests work with real money or real accounts, so they stay in live mode. Your business name, logo and contact details come from live mode each time you switch. ## Test cards Pay your test checkout with these cards. Use any future expiry date and any three-digit security code. A real card is never charged in test mode. | Card number | What happens | Webhook event | | --- | --- | --- | | `4242 4242 4242 4242` | The payment succeeds. | `payment.succeeded` | | `4000 0000 0000 0002` | The card is declined; the payment is recorded as failed. | `payment.failed` | The sandbox has no test card yet for these: Insufficient funds and other specific decline reasons; 3-D Secure authentication; Disputes. Test how your integration handles them with the [event simulator](https://anytime-capital.com/docs/test-mode.md#test-your-integration). ## Test API keys With test mode on, **Settings → Developers** makes test keys, which start with `ak_test_`. A test key reaches your test data only; a live key (`ak_live_`) never reaches it. Everything else is the same: the base URL, restricted permissions, idempotency, rate limits, the [MCP server](https://anytime-capital.com/docs/build-with-ai.md) and the [Node SDK](https://anytime-capital.com/docs/quickstart.md#node-sdk). Every object says which mode it is in with `livemode` — `false` in test mode. ```bash export ANYTIME_API_KEY="ak_test_…" curl "https://anytime-capital.com/api/v1/payments?limit=1" \ -H "Authorization: Bearer $ANYTIME_API_KEY" ``` **Every object carries livemode** ```json { "object": "payment", "livemode": false, "id": "pay_7Hq2LmX9cR4tVb", "status": "paid", "amountCents": 12500, "amountRefundedCents": 0, "netCents": 12088, "currency": "usd", "description": "Consulting — October", "source": { "type": "payment_link", "id": "pl_4kTz8QwN2xYb6RcD1mPa" }, "paymentLinkId": "pl_4kTz8QwN2xYb6RcD1mPa", "customer": { "name": "Jordan Lee", "email": "jordan@example.com", "country": "US" }, "card": { "brand": "visa", "last4": "4242" }, "paymentMethod": { "type": "card", "label": "Visa •••• 4242" }, "clientReference": "order_1042", "metadata": { "campaign": "fall-launch" }, "amountDetails": { "subtotalCents": 12500, "discountCents": 0, "shippingCents": 0, "taxCents": 0, "customerFeeCents": 0 }, "dispute": null, "failureMessage": null, "createdAt": "2026-10-14T16:02:11.000Z", "paidAt": "2026-10-14T16:02:15.000Z" } ``` ## Test webhooks Webhook endpoints added in test mode receive test events only, and live endpoints never receive them. Each event says `livemode: false`, on the event and on the object it carries, and is signed with the endpoint's own secret exactly like a live one — so the same handler works for both. See [Webhooks](https://anytime-capital.com/docs/webhooks.md). **A test event** ```json { "id": "evt_3f9a1c2b7d4e6f8a0b1c2d3e4f5a6b7c", "type": "payment.succeeded", "created": "2026-10-14T16:02:16.000Z", "livemode": false, "data": { "object": "payment", "livemode": false, "id": "pay_7Hq2LmX9cR4tVb", "status": "paid", "amountCents": 12500, "amountRefundedCents": 0, "netCents": 12088, "currency": "usd", "description": "Consulting — October", "source": { "type": "payment_link", "id": "pl_4kTz8QwN2xYb6RcD1mPa" }, "paymentLinkId": "pl_4kTz8QwN2xYb6RcD1mPa", "customer": { "name": "Jordan Lee", "email": "jordan@example.com", "country": "US" }, "card": { "brand": "visa", "last4": "4242" }, "paymentMethod": { "type": "card", "label": "Visa •••• 4242" }, "clientReference": "order_1042", "metadata": { "campaign": "fall-launch" }, "amountDetails": { "subtotalCents": 12500, "discountCents": 0, "shippingCents": 0, "taxCents": 0, "customerFeeCents": 0 }, "dispute": null, "failureMessage": null, "createdAt": "2026-10-14T16:02:11.000Z", "paidAt": "2026-10-14T16:02:15.000Z" } } ``` ## Test your integration Some events take days to happen for real: a dispute, a renewal, an invoice paid by bank transfer. The event simulator sends one to your test endpoints now, built from your own test data with the same shape, signature and delivery as the real event. It changes nothing else and moves no money. Use **Send test event** in the test mode bar, or the API with a test key (a live key gets `403 TEST_MODE_ONLY`). `POST /test-helpers/events` with `type` and, optionally, `objectId` (a `pay_…`, `re_…`, `inv_…` or `sub_…`; left out, your newest test object that fits). It needs the `webhook_endpoints` write permission and answers `409 NOTHING_TO_SEND` when you have no test object of that kind yet: make a test payment, or load sample data. The full reference is [Test helpers API](https://anytime-capital.com/docs/test-helpers.md). **Request** ```bash curl -X POST https://anytime-capital.com/api/v1/test-helpers/events \ -H "Authorization: Bearer $ANYTIME_API_KEY" \ -H "Idempotency-Key: test-event-1" \ -H "Content-Type: application/json" \ -d '{"type":"payment.succeeded"}' ``` **Response (201)** ```json { "object": "test_event", "id": "evt_9b2c4d6e8f0a1b3c5d7e9f1a2b4c6d8e", "type": "payment.succeeded", "livemode": false, "objectId": "pay_7Hq2LmX9cR4tVb", "deliveries": 1, "sample": false } ``` | Event | What `data` carries | | --- | --- | | `payment.succeeded` | Your newest paid test payment (or `objectId`, a `pay_…`). | | `payment.failed` | Your newest failed test payment. | | `refund.created` | Your newest test refund (or a `re_…`). | | `dispute.opened` | Your newest paid test payment, shown disputed (`status: "disputed"`), as the real event carries it. | | `invoice.paid` | Your newest test invoice (or an `inv_…`), shown paid in full. | | `subscription.updated` | Your newest test subscription (or a `sub_…`). | ### Payment succeeded (payment.succeeded) Your newest paid test payment (or `objectId`, a `pay_…`). **curl** ```bash curl -X POST https://anytime-capital.com/api/v1/test-helpers/events \ -H "Authorization: Bearer $ANYTIME_API_KEY" \ -H "Idempotency-Key: test-payment-succeeded-1" \ -H "Content-Type: application/json" \ -d '{"type":"payment.succeeded"}' ``` **Node SDK** ```typescript const event = await client.testHelpers.events.create({ type: "payment.succeeded" }); console.log(event.id, event.deliveries); // evt_…, endpoints it was queued for ``` ### Payment failed (payment.failed) Your newest failed test payment. **curl** ```bash curl -X POST https://anytime-capital.com/api/v1/test-helpers/events \ -H "Authorization: Bearer $ANYTIME_API_KEY" \ -H "Idempotency-Key: test-payment-failed-1" \ -H "Content-Type: application/json" \ -d '{"type":"payment.failed"}' ``` **Node SDK** ```typescript const event = await client.testHelpers.events.create({ type: "payment.failed" }); console.log(event.id, event.deliveries); // evt_…, endpoints it was queued for ``` ### Refund created (refund.created) Your newest test refund (or a `re_…`). **curl** ```bash curl -X POST https://anytime-capital.com/api/v1/test-helpers/events \ -H "Authorization: Bearer $ANYTIME_API_KEY" \ -H "Idempotency-Key: test-refund-created-1" \ -H "Content-Type: application/json" \ -d '{"type":"refund.created"}' ``` **Node SDK** ```typescript const event = await client.testHelpers.events.create({ type: "refund.created" }); console.log(event.id, event.deliveries); // evt_…, endpoints it was queued for ``` ### Dispute opened (dispute.opened) Your newest paid test payment, shown disputed (`status: "disputed"`), as the real event carries it. **curl** ```bash curl -X POST https://anytime-capital.com/api/v1/test-helpers/events \ -H "Authorization: Bearer $ANYTIME_API_KEY" \ -H "Idempotency-Key: test-dispute-opened-1" \ -H "Content-Type: application/json" \ -d '{"type":"dispute.opened"}' ``` **Node SDK** ```typescript const event = await client.testHelpers.events.create({ type: "dispute.opened" }); console.log(event.id, event.deliveries); // evt_…, endpoints it was queued for ``` ### Invoice paid (invoice.paid) Your newest test invoice (or an `inv_…`), shown paid in full. **curl** ```bash curl -X POST https://anytime-capital.com/api/v1/test-helpers/events \ -H "Authorization: Bearer $ANYTIME_API_KEY" \ -H "Idempotency-Key: test-invoice-paid-1" \ -H "Content-Type: application/json" \ -d '{"type":"invoice.paid"}' ``` **Node SDK** ```typescript const event = await client.testHelpers.events.create({ type: "invoice.paid" }); console.log(event.id, event.deliveries); // evt_…, endpoints it was queued for ``` ### Subscription updated (subscription.updated) Your newest test subscription (or a `sub_…`). **curl** ```bash curl -X POST https://anytime-capital.com/api/v1/test-helpers/events \ -H "Authorization: Bearer $ANYTIME_API_KEY" \ -H "Idempotency-Key: test-subscription-updated-1" \ -H "Content-Type: application/json" \ -d '{"type":"subscription.updated"}' ``` **Node SDK** ```typescript const event = await client.testHelpers.events.create({ type: "subscription.updated" }); console.log(event.id, event.deliveries); // evt_…, endpoints it was queued for ``` ## Sample data Owners and admins can choose **Load sample data** in the test mode bar to fill test mode with three customers, three products, two payment links, an open invoice, a monthly plan with a subscriber, and payments that succeeded, failed and were refunded. Loading it again adds nothing. The links, the invoice and the plan are real test objects you can pay with a test card; the sample payments and the subscriber are records to explore (their ids start `pay_sample` and `mem_sample`), so refunding or cancelling one isn't possible. ## Reset test data **Reset test data** in the test mode bar (owners and admins) deletes every test payment, link, invoice, quote, customer, product, subscription and the rest of your test data. Test API keys, test webhook endpoints, your team and settings stay. Your live data is never touched. ## What is different in test mode - No real money moves, and nothing is paid out. - Emails never reach customers: receipts, invoices and reminders go to the business's owners and admins (and whoever acted) instead, marked [TEST], saying who they would have gone to. - Scheduled work runs in live mode only: automatic invoice reminders, trial and card-expiry reminders, scheduled plan changes, usage billing at period end and automatic decisions on held payments. Test your handling of those with the event simulator. - Test payments never show in Anytime Capital's own reports, and don't count toward pricing offers or referrals. ## Shopify, WooCommerce and Zapier - **Shopify**: with your store's payment provider in test mode, Shopify's test orders are paid on your Anytime Capital test mode with a test card. See [Shopify](https://anytime-capital.com/docs/shopify.md). - **WooCommerce**: turn on **Test mode** in the plugin settings and paste a test key (`ak_test_…`) and a test webhook endpoint's secret. See [WooCommerce](https://anytime-capital.com/docs/woocommerce.md#test-mode). - **Zapier**: connect with a test key to build and try your Zaps on test data, then reconnect with a live key. See [Zapier](https://anytime-capital.com/docs/zapier.md). ## Going live 1. Finish verification in **Settings → Verification** (live mode). 2. Create a live key and live webhook endpoints in **Settings → Developers** with test mode off. 3. Swap the key and webhook secret in your server's environment. Nothing else in your code changes. 4. Check `livemode` in your webhook handler if you send test and live events to the same server. --- # Guides and recipes > Step-by-step guides with complete code for Anytime Capital Payments: Next.js checkout, webhooks, refunds, reconciliation, subscriptions, usage and stablecoins. Source: https://anytime-capital.com/docs/guides · Markdown: https://anytime-capital.com/docs/guides.md · All docs: https://anytime-capital.com/docs/llms-full.txt Each guide is a whole task, start to finish, with code you can paste and run. They use only the endpoints and events the [API reference](https://anytime-capital.com/docs/payments.md) documents. ## Take payments - [Accept a payment in Next.js](https://anytime-capital.com/docs/nextjs-checkout.md): A payment link per order, a redirect, and a verified webhook that marks it paid. - [Verify webhooks](https://anytime-capital.com/docs/webhook-signatures.md): Express, Next.js, Flask, FastAPI and PHP handlers that check the signature on the raw body. - [Issue refunds safely](https://anytime-capital.com/docs/refund-safely.md): Refunds that happen exactly once, with retries and approvals. - [Take USDC and USDT](https://anytime-capital.com/docs/accept-stablecoins.md): Switch stablecoins on and keep each transaction with its order. ## Bill - [Sell subscriptions](https://anytime-capital.com/docs/sell-subscriptions.md): Plans, coupons, plan changes and access kept in step by webhook. - [Bill usage with meters](https://anytime-capital.com/docs/bill-usage-with-meters.md): Send usage in safe batches and show customers what they've used. ## Money and books - [Reconcile payouts nightly](https://anytime-capital.com/docs/reconcile-payouts.md): A day's payments, refunds and payouts as a CSV, checked against your bank. - [Sync to QuickBooks or Xero](https://anytime-capital.com/docs/accounting-sync.md): How each payment, fee, refund and payout posts to your books, automatically. ## Move and connect - [Switch from Stripe](https://anytime-capital.com/docs/switch-from-stripe.md): Import your catalog and customers and move subscribers without double charges. - [Import from Shopify](https://anytime-capital.com/docs/import-from-shopify.md): Products and variants, customers and discount codes, with read-only access. - [Import from WooCommerce](https://anytime-capital.com/docs/import-from-woocommerce.md): Products and variations, customers and coupons, with a read-only key. - [Import from Square](https://anytime-capital.com/docs/import-from-square.md): Your item catalog and customer directory, with an access token. - [Import a CSV file](https://anytime-capital.com/docs/import-from-csv.md): Customers or products from any spreadsheet, or a Wix, Squarespace or Etsy export. - [Shopify](https://anytime-capital.com/docs/shopify.md): Card payments at your Shopify checkout (waiting for Shopify's approval). - [WooCommerce](https://anytime-capital.com/docs/woocommerce.md): The plugin: an API key, a signed webhook, refunds from orders. - [Zapier](https://anytime-capital.com/docs/zapier.md): No-code automations on payments, invoices and customers (coming soon). ## With AI - [AI agents hub](https://anytime-capital.com/docs/build-with-ai.md): Connect Claude, Cursor or VS Code to your account through the MCP server. - [Build a custom agent](https://anytime-capital.com/docs/mcp-custom-agents.md): Call the MCP server from your own agent over HTTP. --- # Switch from Stripe > Import customers, products, plans, discount codes, payment links and open invoices from Stripe, then move subscribers without charging anyone twice. Source: https://anytime-capital.com/docs/switch-from-stripe · Markdown: https://anytime-capital.com/docs/switch-from-stripe.md · All docs: https://anytime-capital.com/docs/llms-full.txt Moving to Anytime Capital Payments takes minutes. The importer reads your Stripe account with a read-only key and brings your customers, catalog, discount codes, payment links and open invoices across. Your subscribers then move themselves from a link we email them, free until the period they already paid for in Stripe ends, so nobody is charged twice. Nothing in Stripe is changed, and nothing is emailed to your customers until you choose to. ## What comes across | In Stripe | Becomes | Notes | | --- | --- | --- | | Customer | Customer | Matched by email, so nobody is added twice. Address, phone, tax ID and metadata come with it; metadata lands in the customer's notes. | | Product with a one-time price | Product | One product per price. A product with several prices becomes one product per price, named by the price's nickname or amount. | | Recurring price | Subscription plan | Weekly, monthly, every 3 months or yearly, from $1.00 to $10,000.00 a period. Its free trial comes too, up to 90 days. | | Promotion code | Discount code | Percent or amount off, expiry date and the uses left. | | Coupon with no promotion code | Discount code | Named after the coupon's id, and flagged so you can archive it if customers shouldn't type it. | | Payment link | Payment link | When every item maps to an imported product. A link that sells one plan points you to that plan's own link. | | Open invoice you send yourself | Invoice, unsent | Line items, discount, tax and due date. Nothing is emailed: review and send each one. | | Active subscription | Subscriber to move | Listed with the plan it maps to and the date its current period ends. See below. | Run the importer again at any time. Everything already imported is skipped, a customer who already exists here is matched rather than copied, and anything that failed is tried again. ## What doesn't come across - **Saved cards.** Card numbers can't move through any API. See [Saved cards](#saved-cards) for how to move them in bulk. - **Payment history.** Past payments, refunds, disputes and payouts stay in Stripe for your records. - **Prices we can't charge the same way:** metered (usage-based), tiered and package prices, customer-chosen amounts, and prices in currencies other than US dollars. Each is listed in the summary with the reason. - **Codes with conditions:** codes for one customer, for first-time customers only, with a minimum order, or for certain products only. Codes here take money off the whole order for anyone who has them. - **Subscriptions that don't fit one plan:** several prices or a quantity above one, a discount, or paused payments. They're listed so you can set them up by hand. - **Invoices Stripe is still charging automatically, or that are partly paid.** Let those finish in Stripe. > **Repeating coupons:** A Stripe coupon that came off every renewal for a few months, or forever, is imported as a code that takes money off one payment, and flagged in the summary. ## Run the importer 1. In your dashboard, open **Switching from Stripe?** on your home page, or **Import from Stripe** under **Integrations**. Owners and admins of a verified business can run it. 2. In Stripe, open **Developers → API keys** in live mode and create a **restricted key** with the permissions below set to **Read** and everything else set to **None**. 3. Paste the key (it starts with `rk_live_`). We check it with Stripe and show how many of each thing your account holds, with a few examples. Secret keys and test-mode keys are refused. 4. Choose what to import and start. It runs in the background, so you can leave the page; progress shows as it goes, and your team is notified when it's done. 5. Review the summary: everything imported, matched or skipped, and each item that needs a look, with the reason. | Permission | Access | Used for | | --- | --- | --- | | Customers | Read | Names, emails, addresses and phone numbers | | Products | Read | Your catalog | | Prices | Read | One-time and recurring prices | | Coupons | Read | Percent or amount off, expiry and limits | | Promotion codes | Read | The codes customers type | | Payment Links | Read | Links and their line items | | Invoices | Read | Open invoices | | Subscriptions | Read | Who subscribes to what, and when their period ends | The key is used only to read. It's kept encrypted while the import runs and deleted the moment it finishes, fails or is cancelled. You can delete it in Stripe as soon as you're done. ## Move your subscribers Each active Stripe subscription becomes a row in **Move your subscribers**, with the imported plan it maps to and the day its current period ends. You choose who to invite. Each subscriber gets one email from us, in your branding, with a link that restarts the same plan at the same price with nothing to pay until that day. If they haven't moved after a few days, they get one reminder. - **Not invited:** on the list, waiting for you. - **Invited:** the email went out. The link works only for the address it was sent to, and only until they've moved. - **Moved:** they've restarted here. Their first payment here is on the day their Stripe period ends. - **Declined:** they chose not to continue, or you marked them as not moving. > **Cancel each subscription in Stripe:** We only ever have read access, so we can't cancel anything in Stripe. Once a subscriber has moved, cancel their Stripe subscription before its period ends, and tick it off in the list. That's what makes sure nobody is charged twice. A subscriber whose Stripe period ends more than 90 days out can be invited once it's within 90 days. If a period renews in Stripe after your import, run the import again for subscriptions to refresh the dates. ## Saved cards Under the card industry's security rules (PCI DSS), card numbers only ever move directly between payment processors, never through you or through an API. That's why subscribers re-enter their card when they move. If you'd rather move saved cards in bulk, choose **Request a card data migration** on the importer page: our business team checks what's possible for your account and walks you through it, and nothing changes until it's confirmed with you by email. ## API differences | | Stripe | Anytime Capital | | --- | --- | --- | | Base URL | `https://api.stripe.com/v1` | `https://anytime-capital.com/api/v1` | | Authentication | Secret key as a Bearer token | API key from **Settings → Developers**, starting `ak_live_`, as a Bearer token. See [Authentication](https://anytime-capital.com/docs/authentication.md). | | Request bodies | Form-encoded | JSON | | Amounts | Integer cents (`amount`) | Integer cents, named for what they are (`amountCents`) | | Pagination | `limit` with `starting_after` | `limit` with `cursor` from the previous page. See [Pagination](https://anytime-capital.com/docs/pagination.md). | | Idempotency | `Idempotency-Key` header | `Idempotency-Key` header on every POST. See [Idempotency](https://anytime-capital.com/docs/idempotency.md). | | Errors | `error.type`, `error.code`, `error.message` | `error.code` and `error.message`. See [Errors](https://anytime-capital.com/docs/errors.md). | | Checkout | Checkout Sessions and Payment Links | Payment links (`/api/v1/payment-links`) and invoices; the hosted checkout is the link's page. See [Payment links](https://anytime-capital.com/docs/payment-links.md). | For every object, call and webhook event side by side, with Node and curl examples, see [Stripe API to Anytime Capital](https://anytime-capital.com/docs/stripe-api-mapping.md). Selling on Shopify, WooCommerce or Square too? Their catalogues import the same way: see [Import from Shopify](https://anytime-capital.com/docs/import-from-shopify.md), [WooCommerce](https://anytime-capital.com/docs/import-from-woocommerce.md), [Square](https://anytime-capital.com/docs/import-from-square.md) or [a CSV file](https://anytime-capital.com/docs/import-from-csv.md). ## Webhook mapping | Stripe event | Anytime Capital event | | --- | --- | | `payment_intent.succeeded`, `checkout.session.completed` | `payment.succeeded` | | `payment_intent.payment_failed` | `payment.failed` | | `charge.refunded` | `payment.refunded` | | `charge.dispute.created` | `dispute.opened` | | `charge.dispute.closed` | `dispute.closed` | | `invoice.paid` | `invoice.paid` | | `customer.subscription.created` | `subscription.created` | | `customer.subscription.deleted` | `subscription.canceled` | Verify each delivery with the `Anytime-Signature` header instead of Stripe's signature, and skip event ids you've already handled. See [Webhooks](https://anytime-capital.com/docs/webhooks.md). --- # Import from Shopify > Bring Shopify products and variants, customers and discount codes over in minutes with read-only access. See what moves, what doesn't, and why. Source: https://anytime-capital.com/docs/import-from-shopify · Markdown: https://anytime-capital.com/docs/import-from-shopify.md · All docs: https://anytime-capital.com/docs/llms-full.txt The Shopify importer reads your store with read-only access you create, shows you what would happen before anything is saved, then brings your products, customers and discount codes across in the background. Nothing in Shopify changes, and nothing is emailed to your customers. Owners and admins can run it from **Import your business** in the dashboard. ## What comes across | In Shopify | Becomes | Notes | | --- | --- | --- | | Product variant | Product | One product per variant, named “T-shirt – Small / Red”; a product without variants keeps its name. Price, SKU, description (as plain text), whether it ships, and the product's image (PNG, JPEG or WebP up to 2 MB). | | Customer | Customer | Matched by email, so nobody is added twice. Phone, company, default address, tags and note come with it; tags and note land in the customer's notes. | | Code discount (percent or amount off the order) | Discount code | Each code, with its expiry and the uses left. A discount with several codes becomes several codes. | ## What doesn't come across - **Saved cards.** Card numbers never leave a payment processor, so customers enter their card the first time they pay you here. - **Orders, payouts and gift cards.** They stay in Shopify for your records. - **Discounts we can't represent:** automatic discounts, buy X get Y, free shipping, app discounts, discounts for certain customers or segments, for certain products or collections, with a minimum purchase, limited to one use per customer, scheduled to start later, or taken off each item. Each is listed with the reason. - **Prices in other currencies.** Products come across only when your store sells in US dollars. - **Archived products**, and a product's other images: one image per product here. ## Connect your store Since January 2026, Shopify makes new apps in its **Dev Dashboard**. If your store already has a custom app made in the admin before then, you can use its access token instead. 1. In Shopify's Dev Dashboard, create an app named “Anytime Capital import” in the same organization as your store. 2. In a new version of the app, add the Admin API access scopes below and nothing else, release it, and install the app on your store. 3. Copy the app's **Client ID** and **Client secret** from its settings. 4. In your dashboard, open **Import your business → From Shopify**, enter your store's myshopify.com address and paste them. We exchange them for a token that lasts a day, and ask again whenever an import needs one. 5. Check the preview: how many of each there are, how many are ready, already imported or can't come across, and why. Choose what to import and start. | Access scope | Used for | | --- | --- | | `read_products` | Products, variants, prices and images | | `read_customers` | Names, emails, phone numbers and addresses | | `read_discounts` | Discount codes | With a custom app made before 2026, choose **Custom app made before 2026** and paste its **Admin API access token** (it starts with `shpat_`) with the same scopes. Use your store's myshopify.com address either way: a custom domain can't reach Shopify's Admin API. > **Your credentials:** They're used only to read, kept encrypted while the import runs, and deleted the moment it finishes, fails or is cancelled. Uninstall the app in Shopify once you're done. ## Run it again Each record is remembered by its Shopify id, so running the import again never adds anything twice. A product the import made is brought up to date (name, description, price, SKU, shipping) as long as you haven't edited it here since; one you've changed is left as you have it. Customers only gain details they were missing, and discount codes are never changed by a re-run. A product whose SKU matches one you already had is matched, not copied. ## Rate limits and big stores We read Shopify's GraphQL Admin API (version 2026-10) a page at a time and follow its query cost limits, waiting when Shopify asks us to. A large store imports in the background over several minutes; you can leave the page, and your team is told when it's done. The summary lists everything that needs a look, and you can download every record's result as a CSV. In [test mode](https://anytime-capital.com/docs/test-mode.md), the import goes into your test data, never your live business. To take card payments on your Shopify checkout itself, see [Shopify](https://anytime-capital.com/docs/shopify.md). --- # Import from WooCommerce > Bring WooCommerce products and variations, customers and coupons over with a read-only REST API key. See what moves, what doesn't, and why. Source: https://anytime-capital.com/docs/import-from-woocommerce · Markdown: https://anytime-capital.com/docs/import-from-woocommerce.md · All docs: https://anytime-capital.com/docs/llms-full.txt The WooCommerce importer reads your store through the WooCommerce REST API (v3) with a read-only key you create, shows you what would happen before anything is saved, then brings your products, customers and coupons across in the background. Nothing in your store changes, and nothing is emailed to your customers. ## What comes across | In WooCommerce | Becomes | Notes | | --- | --- | --- | | Simple product | Product | Regular price, SKU, description (as plain text), whether it ships (not virtual or downloadable), and its first image. | | Variation of a variable product | Product | One product per variation, named “Hoodie – Large / Blue”, with its own price, SKU and image (or the product's). | | Customer | Customer | Registered customers, matched by email, with their billing phone, company and address. | | Percent or fixed cart coupon | Discount code | Its amount, expiry and the uses left. | > **Sale prices:** A product on sale comes across at its regular price, and the summary says what the sale price was. Codes and payment links are how you offer a lower price here. ## What doesn't come across - **Saved cards.** Card numbers never leave a payment processor, so customers enter their card the first time they pay you here. - **Orders, subscriptions and bookings.** They stay in WooCommerce for your records. - **Guest customers.** WooCommerce keeps guests only on their orders, not as customers. - **Grouped and external products.** A grouped product's products come across on their own. - **Coupons we can't represent:** fixed product coupons, coupons for certain products or categories, with a minimum or maximum spend, limited to certain emails or per customer, and free shipping on its own. Each is listed with the reason. - **Prices in other currencies.** Products come across only when your store sells in US dollars. ## Connect your store 1. In WordPress, open **WooCommerce → Settings → Advanced → REST API** and choose **Add key**. 2. Describe it as “Anytime Capital import”, choose an administrator or shop manager as the user, and set **Permissions** to **Read**. 3. Generate the key and copy the **Consumer key** (`ck_…`) and **Consumer secret** (`cs_…`). 4. In your dashboard, open **Import your business → From WooCommerce**, enter your store's address and paste them. 5. Check the preview, choose what to import and start. > **https only:** Your store's address must start with https://. The key travels with every request, so we refuse plain http addresses, and we don't follow redirects: if your site moves us elsewhere, we tell you the address to use. We send the key as WooCommerce documents for https sites. If your host strips that header, we send it in the address instead, still over https. Sites without pretty permalinks work too. The key is kept encrypted while the import runs and deleted the moment it finishes, fails or is cancelled; revoke it in WooCommerce once you're done. ## Run it again Each record is remembered by its WooCommerce id, so running the import again never adds anything twice. A product the import made is brought up to date as long as you haven't edited it here since. Customers only gain details they were missing, and discount codes are never changed by a re-run. Pages are read a few at a time, and busy hosts are retried with a pause, so a large store imports in the background; you can download every record's result as a CSV when it's done. In [test mode](https://anytime-capital.com/docs/test-mode.md), the import goes into your test data. To take card payments in your WooCommerce checkout, see [WooCommerce](https://anytime-capital.com/docs/woocommerce.md). --- # Import from Square > Bring your Square item catalog and customer directory over with an access token. See what moves, what doesn't, and how sandbox data works. Source: https://anytime-capital.com/docs/import-from-square · Markdown: https://anytime-capital.com/docs/import-from-square.md · All docs: https://anytime-capital.com/docs/llms-full.txt The Square importer reads your catalog and customer directory with an access token you copy from Square's Developer Console, shows you what would happen before anything is saved, then brings them across in the background. Nothing in Square changes, and nothing is emailed to your customers. ## What comes across | In Square | Becomes | Notes | | --- | --- | --- | | Item variation with a fixed price | Product | One product per variation, named “Latte – Large” (an item with one variation keeps its name), with its SKU, the item's description and first image. | | Customer | Customer | Matched by email, with their phone, company, address and note. | > **Shipping:** Square doesn't say whether an item is shipped, so products come across as not needing a shipping address. Switch it on for the products you ship to customers. ## What doesn't come across - **Cards on file.** Card numbers never leave a payment processor, so customers enter their card the first time they pay you here. - **Sales history, gift cards and loyalty.** They stay in Square for your records. - **Variable-price items** (priced at the time of sale), modifiers and discounts. - **Archived or deleted items**, and prices in currencies other than US dollars. ## Connect your account 1. Sign in to the **Square Developer Console** and open (or create) an application named “Anytime Capital import”. 2. On its **Credentials** page, choose **Production** and copy the **Access token**. 3. In your dashboard, open **Import your business → From Square**, choose **Production** and paste it. 4. Check the preview, choose what to import and start. The token is used only to read, kept encrypted while the import runs, and deleted the moment it finishes, fails or is cancelled. Rotate it in the Developer Console once you're done if you like. ## Sandbox and test mode Square's sandbox holds test data, so a sandbox token can only be imported into [test mode](https://anytime-capital.com/docs/test-mode.md): switch test mode on, then choose **Sandbox**. Production tokens import into whichever mode you're working in. ## Run it again Each record is remembered by its Square id, so running the import again never adds anything twice. A product the import made is brought up to date as long as you haven't edited it here since, and customers only gain details they were missing. We read Square's API (version 2025-01-23) a page at a time, pausing and retrying when Square asks us to slow down; you can download every record's result as a CSV when it's done. --- # Import customers and products from a CSV file > Upload customers or products from any spreadsheet, or a Shopify, Wix, Squarespace or Etsy export. Every row is checked before anything is saved. Source: https://anytime-capital.com/docs/import-from-csv · Markdown: https://anytime-capital.com/docs/import-from-csv.md · All docs: https://anytime-capital.com/docs/llms-full.txt Anything you can export as a spreadsheet can come across. Upload a CSV of customers or of products, match its columns (we fill them in for the usual exports), and see the first rows as they'd come across, with every row's problem and its line number, before anything is saved. ## Ready-made layouts | Layout | Holds | How to export it | | --- | --- | --- | | Shopify export | Customers, Products | In Shopify admin: Customers → Export → All customers → CSV for Excel, Numbers or other spreadsheet programs. In Shopify admin: Products → Export → All products → CSV for Excel, Numbers or other spreadsheet programs. | | Wix export | Customers, Products | In Wix: Contacts → More Actions → Export → All contacts. In Wix: Store Products → Products → More Actions → Export to CSV. | | Squarespace export | Customers, Products | In Squarespace: Contacts (or Customers) → the ••• menu → Export. In Squarespace: Commerce → Inventory → Export All. | | Etsy listings export | Products | On Etsy: Shop Manager → Settings → Options → Download Data → Download CSV (Currently for Sale Listings). | Etsy doesn't share buyers' email addresses in its exports, so only listings come across from Etsy. For anything else, choose **Any spreadsheet** and match the columns yourself. ## Columns | Customers | Products | | --- | --- | | Name | Product ID or handle (groups variant rows) | | First name | Name | | Last name | Description | | Email | Price (US dollars) | | Phone | Currency | | Company | SKU | | Address line 1 | Image link | | Address line 2 | Option 1 value | | City | Option 2 value | | State or region | Option 3 value | | Postal code | Requires shipping | | Country (2-letter code) | Status (archived rows are skipped) | | Notes | Row type (product or variant) | | Tags | | - A customer needs a name, email or phone number. Customers are matched by email, so nobody is added twice; two rows with the same email keep the first. - A product needs a name and a price in US dollars, written like `12.50` or `$1,299.00` (a comma for cents, like `12,50`, is refused rather than guessed). - Rows that share a product ID or handle are variants of one product: the first row's name, description and image carry down, and each variant's option values are added to its name. Rows that only add another image are skipped. - Products are matched by SKU (or by name when there's none), so the same file uploaded twice adds nothing twice. - An image link must be https. We copy PNG, JPEG or WebP images up to 2 MB into your catalog. ## Limits Up to 2 MB and 5,000 rows per file, with a header row. Commas, semicolons and tabs all work as separators, quoted fields may hold commas and new lines, and a byte order mark from Excel is fine. Split a larger list into several files: each upload skips what earlier ones brought over. ## Your file The file is kept encrypted only while its import runs and deleted the moment it finishes, fails or is cancelled. Nothing is emailed to your customers. In [test mode](https://anytime-capital.com/docs/test-mode.md), the import goes into your test data. When it's done, download every row's result as a CSV from the summary. --- # Stripe to Anytime Capital: API mapping > Map Stripe API calls to Anytime Capital: checkout, payment links, customers, prices, subscriptions, invoices, refunds and webhooks, in Node and curl. Source: https://anytime-capital.com/docs/stripe-api-mapping · Markdown: https://anytime-capital.com/docs/stripe-api-mapping.md · All docs: https://anytime-capital.com/docs/llms-full.txt If your code calls Stripe today, this page maps each object, call and webhook event a typical integration uses to its Anytime Capital equivalent, with the code side by side, and says plainly what has no equivalent yet. To move your customers, catalog and subscribers across, use the importer in [Switch from Stripe](https://anytime-capital.com/docs/switch-from-stripe.md). ## At a glance | | Stripe | Anytime Capital | | --- | --- | --- | | Base URL | `https://api.stripe.com/v1` | `https://anytime-capital.com/api/v1` | | Keys | `sk_live_` secret, `rk_live_` restricted, `pk_live_` publishable | `ak_live_` secret keys, each restrictable per resource. No publishable key: card details are only ever typed into our hosted or embedded checkout. See [Authentication](https://anytime-capital.com/docs/authentication.md). | | Test mode | `sk_test_` keys | `ak_test_` keys from test mode in the dashboard; objects and events carry `livemode: false`. See [Test mode](https://anytime-capital.com/docs/test-mode.md). | | Request bodies | Form-encoded | JSON | | Amounts | `amount` in the smallest unit | Integer cents, named for what they are: `amountCents`, `priceCents`, `unitCents` | | Currency | Many | US dollars (`currency: "usd"`). See [International customers](https://anytime-capital.com/docs/international-customers.md). | | Pagination | `limit` with `starting_after`; `has_more` | `limit` (up to 100) with `cursor` set to the last page's `nextCursor`; `hasMore`. Newest first. See [Pagination](https://anytime-capital.com/docs/pagination.md). | | Idempotency | `Idempotency-Key` header | The same header on every write, remembered 24 hours; a replay answers with `Idempotent-Replayed: true`. See [Idempotency](https://anytime-capital.com/docs/idempotency.md). | | Errors | `error.type`, `error.code`, `error.message`, `error.param` | `error.code` and `error.message` (it names the field). See [Error codes](https://anytime-capital.com/docs/error-codes.md). | | Request ids | `Request-Id` header | `Request-Id` header (`req_…`), also in the request log in **Settings → Developers** | | Metadata | On most objects | On payment links, copied onto each payment made on them. Customers have `notes`. | | Libraries | `stripe` for Node and others | `fetch` (every reference page has the Node.js version), a [Node SDK](https://anytime-capital.com/docs/node-sdk.md) preview, or a client generated from the [OpenAPI document](https://anytime-capital.com/docs/openapi.md) | ## Objects and calls | Stripe | Anytime Capital | Notes | | --- | --- | --- | | `checkout.sessions.create` (payment) | `POST /payment-links` with `singleUse: true` | Redirect to the link's `url`. `success_url` → `successUrl`, `metadata` → `metadata`, `allow_promotion_codes` → `allowDiscountCodes`, `shipping_address_collection` → `collectShipping`, `customer_email` → `?prefilled_email=` on the url, `client_reference_id` → `?ref=` on the url (stored as the payment's `clientReference`). | | `checkout.sessions.create` (subscription) | The subscription plan's `url` | Send customers to `url` from `POST /subscription-plans` or `GET /subscription-plans/{id}`. | | `paymentLinks.create` / `update` | `POST /payment-links` / `POST /payment-links/{id}` | A price in `amountCents` (no Price object). The price can't change after creation: create a new link. | | `paymentLinks.update({ active: false })` | `POST /payment-links/{id}/deactivate` | There's no API to turn a link back on. | | `paymentIntents.retrieve` / `list` | `GET /payments/{id}` / `GET /payments` | `status: "paid"` for succeeded. `amountCents`, `amountRefundedCents`, `netCents`, `metadata`, `clientReference`, `customer`. | | `customers.create` / `update` / `retrieve` | `POST /customers` / `PATCH /customers/{id}` / `GET /customers/{id}` | One customer per email address; card buyers are added on their own when they first pay. | | `customers.list({ email })` | `GET /customers?email=` | Filter by email only; there's no search query language. | | `products.create` + `prices.create` (one-time) | `POST /products` with `priceCents` | A product has one price. `products.update` → `PATCH /products/{id}`; `active: false` → `POST /products/{id}/archive`. | | `prices.create` (recurring) | `POST /subscription-plans` | `interval`: `week`, `month`, `quarter`, `year`; `trialDays` up to 90. | | `subscriptions.list` / `retrieve` | `GET /subscriptions` / `GET /subscriptions/{id}` | Filter by `status`, `planId` or `customerEmail`. | | `subscriptions.update({ cancel_at_period_end: true })` / `subscriptions.cancel` | `POST /subscriptions/{id}/cancel` with `atPeriodEnd` `true` / `false` | The subscriber gets the same email as when you cancel in the dashboard. | | `subscriptions.update({ pause_collection })` / resume | `POST /subscriptions/{id}/pause` / `POST /subscriptions/{id}/resume` | | | `invoices.create` + `invoiceItems.create` + `finalizeInvoice` + `sendInvoice` | `POST /invoices` | One call with `customerName`, `customerEmail`, `lineItems`, `dueDate` and optional `taxBps`, `discountCents`, `deposit`. Emailed at once unless `send: false`; `POST /invoices/{id}/send` sends it, or a reminder after that. | | `refunds.create` | `POST /refunds` | `payment_intent` → `paymentId`, `amount` → `amountCents`. Over your refund approval limit it answers `202` with `status: "pending_approval"` until an owner approves. | | `coupons.create` + `promotionCodes.create` | `POST /discount-codes` | `percentOffBps` (1500 is 15%) or `amountOffCents`, `maxRedemptions`, `expiresAt`. Deactivate with `POST /discount-codes/{id}/deactivate`. | | `disputes.list` / `retrieve` | `GET /disputes` / `GET /disputes/{id}` | Read only, with the automatic evidence pack. Respond in the dashboard. | | `payouts.list` / `balance.retrieve` | `GET /payouts` / `GET /balance` | Read only. Payouts go to your bank account on your schedule. | | `webhookEndpoints.create` | `POST /webhook-endpoints` | `enabled_events` → `events` (or `["*"]`). The response carries the signing `secret` once. | | `events.list` / `retrieve` | `GET /events` / `GET /events/{id}` | The last 30 days. `type` takes an event type, a family such as `payment.*`, or up to 10 of them; `createdFrom` and `createdTo` take ISO 8601 dates. | | `billing.meterEvents.create` | `POST /meter-events` | `event_name` → `eventName`, `payload.value` → `value`, with a required `idempotencyKey` per event. See [Meters](https://anytime-capital.com/docs/meters.md). | ## Accept a payment A Checkout Session becomes a single-use payment link: create it from your server with the order in `metadata`, redirect the customer to its `url`, and fulfil the order on the `payment.succeeded` webhook, where `data.metadata.orderId` comes back. There is no PaymentIntent to create or confirm yourself. **Stripe (Node)** ```javascript import Stripe from "stripe"; const stripe = new Stripe(process.env.STRIPE_SECRET_KEY); const session = await stripe.checkout.sessions.create( { mode: "payment", line_items: [ { price_data: { currency: "usd", unit_amount: 12500, product_data: { name: "Order #1042" } }, quantity: 1 }, ], success_url: "https://example.com/thanks", metadata: { orderId: "1042" }, }, { idempotencyKey: "order-1042-checkout" }, ); res.redirect(303, session.url); ``` **Anytime Capital (Node)** ```javascript // One single-use payment link per order: its url is the hosted checkout. const response = await fetch("https://anytime-capital.com/api/v1/payment-links", { method: "POST", headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}`, "Content-Type": "application/json", "Idempotency-Key": "order-1042-link", }, body: JSON.stringify({ title: "Order #1042", amountCents: 12500, singleUse: true, successUrl: "https://example.com/thanks", metadata: { orderId: "1042" }, }), }); const link = await response.json(); if (!response.ok) throw new Error(`${link.error.code}: ${link.error.message}`); res.redirect(303, link.url); ``` **Stripe (curl)** ```bash curl https://api.stripe.com/v1/checkout/sessions \ -u "$STRIPE_SECRET_KEY:" \ -H "Idempotency-Key: order-1042-checkout" \ -d mode=payment \ -d "line_items[0][price_data][currency]=usd" \ -d "line_items[0][price_data][unit_amount]=12500" \ -d "line_items[0][price_data][product_data][name]=Order #1042" \ -d "line_items[0][quantity]=1" \ -d success_url="https://example.com/thanks" \ -d "metadata[orderId]=1042" ``` **Anytime Capital (curl)** ```bash curl https://anytime-capital.com/api/v1/payment-links \ -H "Authorization: Bearer $ANYTIME_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: order-1042-link" \ -d '{"title":"Order #1042","amountCents":12500,"singleUse":true,"successUrl":"https://example.com/thanks","metadata":{"orderId":"1042"}}' ``` Line items with catalog products can't be sent through the API yet: send the order's total as `amountCents` and describe it in `title` and `description`. Links you build from products in the dashboard list their `items`. For a checkout on your own page rather than a redirect, put the same link in the [inline checkout](https://anytime-capital.com/docs/checkout.md#inline-checkout). ## Read a payment **Stripe (Node)** ```javascript const intent = await stripe.paymentIntents.retrieve("pi_3Q…"); if (intent.status === "succeeded") { console.log(intent.amount_received, intent.metadata.orderId); } ``` **Anytime Capital (Node)** ```javascript const response = await fetch("https://anytime-capital.com/api/v1/payments/pay_7Hq2LmX9cR4tVb", { headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}` }, }); const payment = await response.json(); if (payment.status === "paid") { // netCents is what you keep after fees; metadata came from the payment link. console.log(payment.amountCents, payment.netCents, payment.metadata.orderId); } ``` **Stripe (curl)** ```bash curl https://api.stripe.com/v1/payment_intents/pi_3Q… -u "$STRIPE_SECRET_KEY:" ``` **Anytime Capital (curl)** ```bash curl https://anytime-capital.com/api/v1/payments/pay_7Hq2LmX9cR4tVb \ -H "Authorization: Bearer $ANYTIME_API_KEY" ``` ## Customers **Stripe (Node)** ```javascript const [existing] = (await stripe.customers.list({ email: "jordan@example.com", limit: 1 })).data; const customer = existing ?? (await stripe.customers.create({ name: "Jordan Lee", email: "jordan@example.com", phone: "+1 555 0100" })); ``` **Anytime Capital (Node)** ```javascript const headers = { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}`, "Content-Type": "application/json", }; // One customer per email address: look before you create. const found = await fetch("https://anytime-capital.com/api/v1/customers?email=jordan%40example.com", { headers }).then((r) => r.json()); const customer = found.data[0] ?? (await fetch("https://anytime-capital.com/api/v1/customers", { method: "POST", headers: { ...headers, "Idempotency-Key": "customer-jordan" }, body: JSON.stringify({ name: "Jordan Lee", email: "jordan@example.com", phone: "+1 555 0100" }), }).then((r) => r.json())); ``` **Stripe (curl)** ```bash curl https://api.stripe.com/v1/customers \ -u "$STRIPE_SECRET_KEY:" \ -d name="Jordan Lee" -d email=jordan@example.com ``` **Anytime Capital (curl)** ```bash curl https://anytime-capital.com/api/v1/customers \ -H "Authorization: Bearer $ANYTIME_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: customer-jordan" \ -d '{"name":"Jordan Lee","email":"jordan@example.com"}' ``` ## Products, prices and plans There's no separate Price object. A product carries its one price in `priceCents`; a recurring price is a subscription plan, which has its own checkout `url`. **Stripe (Node)** ```javascript const mug = await stripe.products.create({ name: "Ceramic mug" }); await stripe.prices.create({ product: mug.id, unit_amount: 1800, currency: "usd" }); const membership = await stripe.products.create({ name: "Studio membership" }); await stripe.prices.create({ product: membership.id, unit_amount: 4900, currency: "usd", recurring: { interval: "month", trial_period_days: 14 }, }); ``` **Anytime Capital (Node)** ```javascript const post = (path, body, key) => fetch(`https://anytime-capital.com/api/v1${path}`, { method: "POST", headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}`, "Content-Type": "application/json", "Idempotency-Key": key, }, body: JSON.stringify(body), }).then((r) => r.json()); // A product carries its one price. const mug = await post("/products", { name: "Ceramic mug", priceCents: 1800 }, "product-mug"); // A recurring price is a subscription plan; customers subscribe on its url. const plan = await post( "/subscription-plans", { title: "Studio membership", amountCents: 4900, interval: "month", trialDays: 14 }, "plan-studio-monthly", ); console.log(plan.url); ``` **Anytime Capital (curl)** ```bash curl https://anytime-capital.com/api/v1/products \ -H "Authorization: Bearer $ANYTIME_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: product-mug" \ -d '{"name":"Ceramic mug","priceCents":1800}' curl https://anytime-capital.com/api/v1/subscription-plans \ -H "Authorization: Bearer $ANYTIME_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: plan-studio-monthly" \ -d '{"title":"Studio membership","amountCents":4900,"interval":"month","trialDays":14}' ``` ## Cancel a subscription **Stripe (Node)** ```javascript // At the end of the period… await stripe.subscriptions.update("sub_1Q…", { cancel_at_period_end: true }); // …or now. await stripe.subscriptions.cancel("sub_1Q…"); ``` **Anytime Capital (Node)** ```javascript const cancel = (atPeriodEnd) => fetch("https://anytime-capital.com/api/v1/subscriptions/sub_2208/cancel", { method: "POST", headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}`, "Content-Type": "application/json", "Idempotency-Key": `cancel-sub_2208-${atPeriodEnd}`, }, body: JSON.stringify({ atPeriodEnd }), }).then((r) => r.json()); await cancel(true); // cancelAtPeriodEnd is true until the period ends // await cancel(false); // ends it now ``` **Anytime Capital (curl)** ```bash curl https://anytime-capital.com/api/v1/subscriptions/sub_2208/cancel \ -H "Authorization: Bearer $ANYTIME_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: cancel-sub_2208" \ -d '{"atPeriodEnd":true}' ``` ## Invoices **Stripe (Node)** ```javascript const invoice = await stripe.invoices.create({ customer: "cus_Q…", collection_method: "send_invoice", days_until_due: 30 }); await stripe.invoiceItems.create({ customer: "cus_Q…", invoice: invoice.id, amount: 450000, currency: "usd", description: "Website redesign" }); await stripe.invoices.finalizeInvoice(invoice.id); await stripe.invoices.sendInvoice(invoice.id); ``` **Anytime Capital (Node)** ```javascript // One call: created, totalled and emailed (send: false to send it later). const response = await fetch("https://anytime-capital.com/api/v1/invoices", { method: "POST", headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}`, "Content-Type": "application/json", "Idempotency-Key": "invoice-northwind-oct", }, body: JSON.stringify({ customerName: "Northwind LLC", customerEmail: "ap@northwind.example", lineItems: [{ description: "Website redesign", quantity: 1, unitCents: 450000 }], taxBps: 825, dueDate: "2026-11-15", }), }); const invoice = await response.json(); // invoice.url is its pay page ``` **Anytime Capital (curl)** ```bash curl https://anytime-capital.com/api/v1/invoices \ -H "Authorization: Bearer $ANYTIME_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: invoice-northwind-oct" \ -d '{"customerName":"Northwind LLC","customerEmail":"ap@northwind.example","lineItems":[{"description":"Website redesign","quantity":1,"unitCents":450000}],"taxBps":825,"dueDate":"2026-11-15"}' ``` ## Refunds **Stripe (Node)** ```javascript await stripe.refunds.create( { payment_intent: "pi_3Q…", amount: 2500, reason: "requested_by_customer" }, { idempotencyKey: "refund-order-1042" }, ); ``` **Anytime Capital (Node)** ```javascript const response = await fetch("https://anytime-capital.com/api/v1/refunds", { method: "POST", headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}`, "Content-Type": "application/json", "Idempotency-Key": "refund-order-1042", }, body: JSON.stringify({ paymentId: "pay_7Hq2LmX9cR4tVb", amountCents: 2500, reason: "requested_by_customer" }), }); const refund = await response.json(); // 201: refunded. 202: over your approval limit, waiting for an owner (status "pending_approval"). ``` **Stripe (curl)** ```bash curl https://api.stripe.com/v1/refunds \ -u "$STRIPE_SECRET_KEY:" \ -H "Idempotency-Key: refund-order-1042" \ -d payment_intent=pi_3Q… -d amount=2500 -d reason=requested_by_customer ``` **Anytime Capital (curl)** ```bash curl https://anytime-capital.com/api/v1/refunds \ -H "Authorization: Bearer $ANYTIME_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: refund-order-1042" \ -d '{"paymentId":"pay_7Hq2LmX9cR4tVb","amountCents":2500,"reason":"requested_by_customer"}' ``` ## Webhooks Signatures work the same way: `Anytime-Signature` is `t=,v1=`, an HMAC-SHA256 of `.` keyed with your endpoint's `whsec_…` secret. Three things differ in the event itself: `data` is the object (not `data.object`), `created` is an ISO 8601 string (not Unix seconds), and there's no `previous_attributes`, so re-read the object from the API when you need what changed. **Stripe (Node, Express)** ```javascript app.post("/webhooks", express.raw({ type: "application/json" }), (req, res) => { const event = stripe.webhooks.constructEvent(req.body, req.get("Stripe-Signature"), process.env.STRIPE_WEBHOOK_SECRET); if (event.type === "checkout.session.completed") { fulfil(event.data.object.metadata.orderId); } res.sendStatus(200); }); ``` **Anytime Capital (Node, Express)** ```javascript import crypto from "node:crypto"; // Anytime-Signature: t=,v1=."> function verifyEvent(rawBody, header, secret) { const pairs = header.split(",").map((part) => part.trim().split("=")); const t = pairs.find(([key]) => key === "t")?.[1] ?? ""; const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex"); const signed = pairs.some( ([key, value = ""]) => key === "v1" && value.length === expected.length && crypto.timingSafeEqual(Buffer.from(value), Buffer.from(expected)), ); if (!signed || Math.abs(Date.now() / 1000 - Number(t)) > 300) throw new Error("Invalid signature"); return JSON.parse(rawBody); } app.post("/webhooks", express.raw({ type: "application/json" }), (req, res) => { let event; try { event = verifyEvent(req.body.toString("utf8"), req.get("Anytime-Signature") ?? "", process.env.ANYTIME_WEBHOOK_SECRET); } catch { return res.sendStatus(400); } if (event.type === "payment.succeeded") { fulfil(event.data.metadata.orderId); // event.data is the payment itself } res.sendStatus(200); }); ``` The [Node SDK](https://anytime-capital.com/docs/node-sdk.md) preview has the same `constructEvent(rawBody, header, secret)`. Endpoints must be public `https://` URLs: add them in **Settings → Developers** or with `POST /webhook-endpoints`. See [Verify webhook signatures](https://anytime-capital.com/docs/webhook-signatures.md) for Next.js, Python and PHP. ## Webhook events | Stripe event | Anytime Capital event | `data` is | | --- | --- | --- | | `checkout.session.completed`, `payment_intent.succeeded`, `charge.succeeded` | `payment.succeeded` | the payment | | `payment_intent.payment_failed`, `charge.failed` | `payment.failed` | the payment | | `charge.refunded` | `payment.refunded` | the payment | | `refund.created` | `refund.created` | the refund | | `refund.updated` (succeeded / failed) | `refund.succeeded` / `refund.failed` | the refund | | `charge.dispute.created` | `dispute.opened` | the payment | | `charge.dispute.updated` | `dispute.updated` | the dispute | | `charge.dispute.closed` | `dispute.closed` | the payment | | `invoice.created`, `invoice.finalized` | `invoice.created` | the invoice | | `invoice.sent` | `invoice.sent` | the invoice | | `invoice.paid` | `invoice.paid` | the invoice | | `invoice.overdue` | `invoice.overdue` | the invoice | | `invoice.voided` | `invoice.voided` | the invoice | | `customer.subscription.created` | `subscription.created` | the subscription | | `customer.subscription.updated` | `subscription.updated` | the subscription | | `customer.subscription.paused` / `resumed` | `subscription.paused` / `subscription.resumed` | the subscription | | `invoice.payment_failed` on a renewal | `subscription.past_due` | the subscription | | `customer.subscription.deleted` | `subscription.canceled` | the subscription | | `customer.created` / `customer.updated` | `customer.created` / `customer.updated` | the customer | | `payout.paid` / `payout.failed` | `payout.paid` / `payout.failed` | the payout | | `review.opened` | `payment.review_required` | the payment | Events with no counterpart here include `checkout.session.expired`, `payment_intent.created`, `payment_intent.processing`, `payment_intent.requires_action`, `setup_intent.*`, `payment_method.*`, `customer.deleted`, `product.*`, `price.*`, `invoice.upcoming` and `customer.subscription.trial_will_end`. See [Webhooks](https://anytime-capital.com/docs/webhooks.md#event-types) for every event type. ## Catch up on events **Stripe (Node)** ```javascript for await (const event of stripe.events.list({ type: "payment_intent.succeeded", created: { gte: 1760400000 } })) { handle(event); } ``` **Anytime Capital (Node)** ```javascript // Newest first; follow nextCursor until hasMore is false. let cursor; do { const url = new URL("https://anytime-capital.com/api/v1/events"); url.searchParams.set("type", "payment.*"); url.searchParams.set("createdFrom", "2026-10-14T00:00:00Z"); url.searchParams.set("limit", "100"); if (cursor) url.searchParams.set("cursor", cursor); const page = await fetch(url, { headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}` } }).then((r) => r.json()); for (const event of page.data) handle(event); cursor = page.hasMore ? page.nextCursor : undefined; } while (cursor); ``` **Anytime Capital (curl)** ```bash curl "https://anytime-capital.com/api/v1/events?type=payment.*&createdFrom=2026-10-14&limit=100" \ -H "Authorization: Bearer $ANYTIME_API_KEY" ``` ## Test locally The [Anytime CLI](https://anytime-capital.com/docs/cli.md) (in preview, not on npm yet) does what the Stripe CLI's `listen` and `trigger` do: it forwards your test events to localhost, signed with a local secret in the same header format, and sends test events through the [test helpers](https://anytime-capital.com/docs/test-helpers.md). ```bash # Before stripe listen --forward-to localhost:3000/webhooks stripe trigger payment_intent.succeeded # After (the Anytime CLI, in preview) anytime listen --forward-to localhost:3000/webhooks anytime trigger payment.succeeded ``` ## No equivalent yet - **Collecting or charging a card through the API.** There are no PaymentIntents, SetupIntents, PaymentMethods or Elements: cards are entered only in our hosted or embedded checkout, and there's no call to charge a saved card off session. - **Separate authorization and capture.** There's no `capture_method: manual`; payments our risk rules hold are approved in the dashboard (`payment.review_required`). - **Creating or changing subscriptions from your server.** Customers subscribe on a plan's `url`. Plan changes with proration, coupons on subscriptions and several items per subscription are dashboard features; there are no subscription schedules or test clocks. - **Currencies other than US dollars**, and automatic tax calculation: set `taxBps` on invoices yourself. - **Metadata beyond payment links and payments**, `expand[]`, search queries, and API version headers: objects carry what they need, and the [Changelog](https://anytime-capital.com/docs/changelog.md) and the OpenAPI document's version track changes. - **Deleting customers or products, voiding invoices and answering disputes through the API.** Do these in the dashboard; products are archived instead of deleted. - **Creating payouts through the API, connected accounts and card issuing.** Payouts and the balance can be read: `GET /payouts`, `GET /balance`. - **Expiring a checkout.** Deactivate the link instead: `POST /payment-links/{id}/deactivate`. - **Fraud rules through the API.** They're set in the dashboard: see [Fraud rules & 3D Secure](https://anytime-capital.com/docs/risk-and-3ds.md). - [Switch from Stripe](https://anytime-capital.com/docs/switch-from-stripe.md): Import customers, catalog, codes and open invoices, then move subscribers. - [Postman collection](https://anytime-capital.com/docs/postman.md): Every endpoint, ready to send with a test key. - [Next.js checkout](https://anytime-capital.com/docs/nextjs-checkout.md): A complete payment-link checkout and webhook in an App Router app. --- # Pricing > Anytime Capital Payments costs 2.75% + 30¢ per successful card payment, with no monthly, setup or PCI fees, and custom pricing at $1M+ a year. Source: https://anytime-capital.com/docs/pricing · Markdown: https://anytime-capital.com/docs/pricing.md · All docs: https://anytime-capital.com/docs/llms-full.txt | What | Price | | --- | --- | | Online card and wallet payments | **2.75% + 30¢** per successful payment | | Your first 90 days | **2.5% + 30¢** with the launch offer | | Each business you refer that switches on payments | **30 more days** at 2.5% + 30¢, up to 12 | | Monthly, setup and PCI fees | None | | Businesses processing $1M+ a year | Custom pricing | | API, webhooks and the MCP server | Available to every verified business account | ## Standard rate Online card and wallet payments cost 2.75% + 30¢ per successful payment. There is no monthly fee, no setup fee and no PCI fee. Each payment's `netCents` in the API is what you keep after fees. ## Launch offer 2.5% + 30¢ for your first 90 days of card payments. Claim it when you [create your business account](https://anytime-capital.com/business/get-started?offer=launch-90) or from the [pricing page](https://anytime-capital.com/business/pricing); after the 90 days, the standard rate applies. ## Referrals Your referral link is in **Settings → Referrals**. Businesses that sign up through it get the launch offer. Each one that is verified and switches card payments on earns you 30 more days at 2.5% + 30¢, added after any reduced-rate days you already have, for up to 12 businesses. Days earned before your own card payments are on wait for you and start the day they switch on. The reduced rate applies to one-time card and wallet payments; subscription renewals stay at the standard rate. ## Custom pricing Businesses processing $1M+ a year get pricing quoted for their volume, and a dedicated payments team: - Shared Slack channel with our payments and risk team - Named account manager and quarterly pricing review - Custom pricing, payout schedules and reserve terms ## Other fees Rates shown are for US card payments. International cards, currency conversion and disputes may carry additional fees, shown in your dashboard before you accept a payment. See the full [pricing page](https://anytime-capital.com/business/pricing) for details. --- # Support > Reach Anytime Capital's US-based business team by phone, chat and email, with a dedicated payments team and shared Slack channel for larger businesses. Source: https://anytime-capital.com/docs/support · Markdown: https://anytime-capital.com/docs/support.md · All docs: https://anytime-capital.com/docs/llms-full.txt Email [hello@anytime-capital.com](mailto:hello@anytime-capital.com), call (470) 748-2530, or chat with us from the dashboard and the site. Every business account has live support from people who can see your account. ## Support for every business ### Live business support **For:** Every business account. - US-based business team by phone, chat and email - Real people who can see your account, not a ticket queue - Help moving links, invoices and customers from your old processor ### Dedicated payments team **For:** Businesses processing $1M+ a year. - Shared Slack channel with our payments and risk team - Named account manager and quarterly pricing review - Custom pricing, payout schedules and reserve terms ## Getting a fast answer on an integration - Say which endpoint or MCP tool you called, and when (with your time zone). - Include the ids involved: `pay_…`, `pl_…`, `inv_…`, `cus_…` or the webhook event's `evt_…`. - Paste the error's `code` and `message` exactly as the API returned them. - Name the API key you used by its name and last four characters. Never send the key itself. - For webhooks, check the endpoint's delivery log in Settings → Developers first: it shows every attempt and the response your server gave. > **Ask your AI assistant first:** Point Claude or your editor's assistant at [/docs/llms-full.txt](https://anytime-capital.com/docs/llms-full.txt): it holds every docs page in one file, including every error code and what to do about it. --- # Anytime Capital Payments FAQ > Answers about Anytime Capital Payments: payment methods, payouts, the API and webhooks, AI and MCP integration, security, verification and pricing. Source: https://anytime-capital.com/docs/faq · Markdown: https://anytime-capital.com/docs/faq.md · All docs: https://anytime-capital.com/docs/llms-full.txt ## Taking payments ### What can my customers pay with? Cards, Apple Pay and Google Pay on every checkout, and bank account (ACH) on payment links and invoices once you turn it on in Settings → Payments. Customers can also pay USDC or USDT to your business wallet through a stablecoin payment request. Pay later (Klarna, Afterpay, Affirm), Cash App Pay and PayPal are coming soon and aren't offered at checkout yet. Details are on [Payment methods](https://anytime-capital.com/docs/payment-methods.md). ### Do I need a website or developers to take payments? No. Make a payment link or an invoice in the dashboard and share it by text, email, DM or QR code, or take a payment in person with a QR code. The API, webhooks and website Pay button are there when you want to automate. ### How large can a single payment be? A payment link takes $1.00 to $10,000.00; an invoice can total $1.00 to $100,000.00. You can set a lower maximum of your own in Settings → Risk. ### Can I take payments in person without a card reader? Yes. In Checkout tools → Collect in person, key in the amount and show the QR code; your customer scans it and pays on their own phone with Apple Pay, Google Pay or their card. Your screen shows Paid when it goes through. See [In person](https://anytime-capital.com/docs/in-person.md). ### Can checkout use my own domain and brand? Yes. Upload your logo and pick your brand colour in Settings → Branding & receipts, and connect a subdomain such as pay.yourbusiness.com in Settings → Domains. Payment links, invoices and receipts then open on your domain, in your branding. See [Checkout](https://anytime-capital.com/docs/checkout.md). ### How do refunds work? Refund all or part of a payment from Orders in the dashboard. The payment's `amountRefundedCents` goes up and you receive the `payment.refunded` webhook. Refunds above your approval limit, made by anyone but the owner, wait for the owner's approval (Settings → Team & approvals). Refunds aren't available through the API. ### What happens when a customer disputes a payment? You receive `dispute.opened`, the payment's status becomes `disputed`, and you respond from Disputes in the dashboard. You can also have pre-dispute alerts refunded automatically, up to a limit you set, in Settings → Risk. See [Fraud rules & 3D Secure](https://anytime-capital.com/docs/risk-and-3ds.md). ### Can I charge customers on a subscription? Yes. Set up plans in Subscriptions in the dashboard. Each charge is a payment with `source.type` `subscription`, and you receive `subscription.created` and `subscription.canceled` webhooks. Plans are set up in the dashboard; the API doesn't create subscriptions. ## Payouts ### How fast can I get my money? From Payouts in the dashboard: payouts to your own bank account, standard or instant where eligible, instant sweeps into an Anytime Capital business account, RTP or FedNow transfers to any US bank, same-day domestic wires, vendor payments by ACH, local rails in 71+ countries, stablecoin payouts from your balance, crypto conversion of your balance and employee cards funded from your balance, whenever you like or on your automatic schedule, with each payout's expected arrival shown before you confirm. Every option, with its speed, is on [Payouts](https://anytime-capital.com/docs/payouts.md). ### Can I pay suppliers outside the US? Yes: over local rails in 71+ countries, in the supplier's own currency (SEPA Instant, Faster Payments, PIX, SPEI, UPI and others), with SWIFT everywhere else, or in USDC and USDT. ### Can I take my balance in stablecoins or crypto? Yes. Send USDC or USDT on-chain (minutes, including weekends), or convert your balance to bitcoin, ether, solana and other assets and send it out the same day. ### Why keep payments and banking in one place? Payments, banking, cards, payouts and crypto share one balance at Anytime Capital, so money from a sale can pay a supplier, fund a card or move into stablecoins the same day, with no separate merchant account to reconcile and no transfer to wait for. ### Can I make payouts through the API? Not today. Payouts are made from Payouts in the dashboard by owners and admins, so neither an API key nor an AI assistant can move money out of your balance. ## API and docs ### What can I do with the API? Create and manage payment links, invoices, customers, products, discount codes and subscription plans; read payments, refunds, disputes, payouts and your balance; refund within your approval limit; cancel, pause and resume subscriptions; send usage for usage billing; and manage webhook endpoints. Webhooks tell you about each change as it happens. Every endpoint is in the [API reference](https://anytime-capital.com/docs/payments.md). ### Is there a sandbox or test mode? Yes. Turn on Test mode in the dashboard header to work on a copy of your business where no real money moves: pay test checkouts with the sandbox test cards, create ak_test_ API keys that reach test data only, receive test webhooks marked livemode: false, simulate events such as disputes and renewals, load sample data and reset it. You can use it before your business is verified. See [Test mode](https://anytime-capital.com/docs/test-mode.md). ### Is there an SDK? Not one you can install yet: a [Node SDK](https://anytime-capital.com/docs/node-sdk.md) is in preview and isn't on npm. The API is plain HTTPS and JSON, so any language's HTTP client works; every endpoint in the reference has curl, Node.js and Python examples, and code generators can build a client from the [OpenAPI document](https://anytime-capital.com/docs/openapi.md). ### Are the docs available as Markdown? Yes. Add .md to any docs URL (for example /docs/webhooks.md), or fetch every page in one file at /docs/llms-full.txt. Each page also has Copy page and View as Markdown buttons. ## AI and MCP ### Can an AI assistant integrate Anytime Capital Payments for me? Yes. Paste the integration prompt from [Build with AI](https://anytime-capital.com/docs/build-with-ai.md) into Claude Code, Cursor or another coding assistant in your project. It points the assistant at the Markdown docs and spells out the rules: key on the server, Idempotency-Key on every POST, verified webhooks. ### How do I connect Claude to my Anytime Capital account? Create a dedicated API key in Settings → Developers, then run `claude mcp add --transport http anytime-capital https://anytime-capital.com/api/mcp --header "Authorization: Bearer ak_live_..."` in Claude Code. Configs for Cursor, Claude Desktop and other clients are on [Build with AI](https://anytime-capital.com/docs/build-with-ai.md). ### What can an assistant do through the MCP server? Whatever its key allows, through one tool per API endpoint (usage billing's aside): read payments, refunds, payouts, disputes and your balance; make and change payment links, invoices, customers, products, discount codes and subscription plans; refund within your approval limit; and cancel, pause or resume subscriptions. Each tool calls the matching REST endpoint with your key, with the same validation, rate limit and idempotency. The full list is on [AI agents](https://anytime-capital.com/docs/build-with-ai.md#tools). ### Can an AI assistant move money or issue refunds? It can never move money out of your balance: there are no payout or transfer tools, and payouts are made in the dashboard. It can refund a payment only if its key has `refunds` write, and a refund over your approval limit waits for an owner. Give each assistant a restricted key with only what it needs ([Safe AI agents](https://anytime-capital.com/docs/ai-agent-safety.md)). Tools that change something are marked as such, so clients like Claude can ask before running them, and every change is written to your activity log under the key's name. Revoke the key in Settings → Developers to cut access instantly. ## Security ### Where should I keep my API key? On your server, in an environment variable or secret manager. Never in a browser, mobile app, repository or log. Use one key per integration (up to 10 active) so you can revoke one without the others. ### Do card numbers touch my servers? No. Customers enter card details on Anytime Capital's checkout: the hosted page, or the Pay button and inline checkout, which load that checkout from Anytime Capital inside your page. Your servers only ever see the payment's brand and last four digits through the API. ### How do I know a webhook really came from Anytime Capital? Verify the Anytime-Signature header: an HMAC-SHA256 of the timestamp and the raw body, keyed with your endpoint's whsec_ signing secret. Compare in constant time and reject old timestamps. Code for Node.js, Next.js and Python is on [Webhooks](https://anytime-capital.com/docs/webhooks.md). ## Onboarding and verification ### Who can use Anytime Capital Payments? Businesses with an Anytime Capital business account that have passed verification (KYB). [Create your account](https://anytime-capital.com/business/get-started), then finish verification in Settings → Verification. The API answers 403 NOT_ENROLLED until your business is set up for card payments. ### Is Anytime Capital a bank? No. Anytime Capital is a fintech company, not a bank. Payment processing, banking and payout services are provided by Anytime Capital's licensed partners. ### Who on my team can manage the integration? Owners and admins create and revoke API keys and webhook endpoints in Settings → Developers. Every team member can read the in-dashboard API reference. ## Pricing and support ### How much does Anytime Capital Payments cost? 2.75% + 30¢ per successful online card or wallet payment, with no monthly, setup or PCI fees. International cards, currency conversion and disputes may carry additional fees, shown in your dashboard before you accept a payment. See [pricing](https://anytime-capital.com/business/pricing). ### Is there an offer for new businesses? Yes: 2.5% + 30¢ for your first 90 days of card payments. Claim it when you sign up or from the [pricing page](https://anytime-capital.com/business/pricing). ### Is there a referral program? Every business account has a referral link in Settings → Referrals. A business that signs up through it pays 2.5% + 30¢ for its first 90 days of card payments. When it is verified and switches card payments on, you get 30 more days at 2.5% + 30¢, added after any days you already have, for up to 12 businesses. Referring a second business of your own doesn't count. ### Do you offer custom pricing? Yes, for businesses processing $1M+ a year, together with a dedicated payments team: shared Slack channel with our payments and risk team; named account manager and quarterly pricing review; custom pricing, payout schedules and reserve terms. ### How do I get help? Every business account has live, US-based support by phone, chat and email: hello@anytime-capital.com or (470) 748-2530. See [Support](https://anytime-capital.com/docs/support.md). --- # Hosted checkout, Pay button and branding > Take payments with hosted payment links, a Pay button or inline checkout on your website, your own checkout domain and your logo and brand colour. Source: https://anytime-capital.com/docs/checkout · Markdown: https://anytime-capital.com/docs/checkout.md · All docs: https://anytime-capital.com/docs/llms-full.txt Every way to take a payment online runs on the same hosted checkout: a page served by Anytime Capital where your customer pays by card, Apple Pay or Google Pay, or by bank account where you have turned it on. Card details are entered there, never on your servers. ## Hosted payment links A payment link's `url` (`https://anytime-capital.com/pay/business/pl_4kTz8QwN2xYb6RcD1mPa`) is a complete checkout page. Make links in the dashboard under **Payment Links**, or with the [Payment links API](https://anytime-capital.com/docs/payment-links.md). Share the link anywhere, download its QR code, or redirect a customer to it from your own checkout. - **One-time or reusable:** a `singleUse` link closes after its first payment; a reusable one takes payments until you deactivate it. - **After payment:** send the customer to your `successUrl`, or show your `confirmationMessage` on our confirmation page. - **Buyer choices:** quantity, tips, pay what you want, discount codes, shipping and up to three checkout questions. - **Terms consent:** with a terms link in Settings → Payments, a link can require customers to agree to your terms before paying. - **Name on card statements:** set it in Settings → Payments (owners and admins), with a live preview of the statement line. It goes on every card payment customers make at checkout and on charges you make from the dashboard; a name customers recognize means fewer disputes. - **Invoices** have their own pay page at the invoice's `url`, with the balance and any deposit. ## Pay button on your website **Checkout tools → Add to your website** in the dashboard writes this code for any of your links. The button is a plain link to the checkout page, so it still works where scripts are stripped; our script (`/embed/anytime-pay.js`) upgrades it to a checkout that opens over your page. **Pay button (opens checkout over your page)** ```html Pay $125.00 ``` ### Inline checkout Place the checkout itself inside your page. It grows with its content. ```html ``` ### Plain link, no script A styled link to the checkout page, in your brand colour. It works anywhere HTML can be pasted, including site builders that don't allow scripts. ```html Pay $125.00 ``` ### Prefill checkout Fill in the customer's email, apply a discount code and attach your own order reference. The reference is stored on the payment as `clientReference` and comes back in the API and in webhooks. A value the checkout can't use is ignored. ```html Pay https://anytime-capital.com/pay/business/pl_4kTz8QwN2xYb6RcD1mPa?prefilled_email=customer%40example.com&code=WELCOME10&ref=order-1042 ``` Your own script can also open a checkout: `AnytimePay.open("pl_…")`, or `AnytimePay.open("pl_…", { email, code, ref })`. ### React to a payment on your page When a payment succeeds, the element that opened the checkout (and the document) receive an `anytime-pay:succeeded` event. Unless you call `preventDefault()`, the page then goes on to the link's success URL when it has one. Use the event for the experience on the page, and the `payment.succeeded` webhook to fulfil the order. ```html ``` ## Your own checkout domain In **Settings → Domains**, connect a subdomain you own, such as `pay.yourbusiness.com`, and add the DNS record the page shows. Once it's connected your payment links, invoices and receipts open there, with Apple Pay and Google Pay set up on it for you. Your main website stays as it is: use a subdomain you don't use for anything else. A business can connect up to 2 domains. ## Your branding In **Settings → Branding & receipts**, upload your logo (PNG, JPEG, WEBP, up to 1 MB) and pick your brand colour. They appear as the merchant on your payment pages, invoices, invoice emails and receipts, with a live preview as you edit. Receipts can also show your support email and phone, your address, a thank-you message and a footer note such as your return policy. Checkout itself stays secured and processed by Anytime Capital Payments. ## Choosing an approach | You want to | Use | | --- | --- | | Sell one thing at a fixed price with no code | A reusable payment link from the dashboard, shared or as a QR code | | Add a Pay button to a site builder or landing page | The Pay button or plain link snippet: step by step for Wix, Squarespace, Webflow, WordPress and more in [Website builders](https://anytime-capital.com/docs/website-builders.md) | | Take payment on Shopify orders today | [Shopify orders paid by invoice](https://anytime-capital.com/docs/shopify-order-invoices.md) | | Charge a cart total your app calculates | Create a `singleUse` link per order from your backend and redirect to its `url` | | Bill a client with line items and a due date | An [invoice](https://anytime-capital.com/docs/invoices.md) | | Take a payment face to face | [In-person QR checkout](https://anytime-capital.com/docs/in-person.md) | --- # Accept a payment from a Next.js app > A complete Next.js App Router integration: a single-use payment link per order made on the server, a redirect to it, and a verified webhook that marks it paid. Source: https://anytime-capital.com/docs/nextjs-checkout · Markdown: https://anytime-capital.com/docs/nextjs-checkout.md · All docs: https://anytime-capital.com/docs/llms-full.txt This guide builds the whole flow in a Next.js App Router app: a Buy button posts to your server, your server creates the order and a single-use [payment link](https://anytime-capital.com/docs/payment-links.md) for it, the customer pays on Anytime Capital's hosted checkout, and a verified [webhook](https://anytime-capital.com/docs/webhooks.md) marks the order paid. Every file is complete: paste them into a new app and it runs. 1. Browser → `POST /api/checkout` (your server) → `POST /api/v1/payment-links` with the order id in `metadata` and the order's Idempotency-Key. 2. Your server redirects to the link's `url`; the customer pays by card, Apple Pay, Google Pay, or bank or stablecoin where you've turned them on. 3. We send `payment.succeeded` to `/api/webhooks/anytime`; you verify it and mark the order paid. Never fulfil on the redirect alone: bank payments take days, and customers close tabs. ## 1. Create the app and your keys ```bash npx create-next-app@latest shop --ts --app --eslint --no-src-dir --import-alias "@/*" --yes cd shop ``` In **Settings → Developers**, create an API key for this site. A restricted key with `payment_links` write is all this guide needs; add `payments` read if you'll look payments up. See [Authentication](https://anytime-capital.com/docs/authentication.md). **.env.local** ```bash # .env.local — never commit this file ANYTIME_API_KEY=ak_live_… # Settings → Developers → Create key ANYTIME_WEBHOOK_SECRET=whsec_… # shown when you add the webhook endpoint APP_URL=https://shop.example.com # your site; https:// to bring customers back after paying ``` ## 2. Orders Each order gets an Idempotency-Key when it's created and keeps it. If creating the link times out and you try again, the same key returns the first link instead of making a second one. **lib/orders.ts** ```typescript // lib/orders.ts — a stand-in for your database: swap it for a real table. import { randomUUID } from "node:crypto"; export type OrderStatus = "pending" | "paid" | "refunded"; export interface Order { id: string; title: string; amountCents: number; status: OrderStatus; /** Made once per order and reused on every retry, so a retry never makes a second link. */ idempotencyKey: string; paymentLinkId: string | null; paymentId: string | null; } // Kept on globalThis so the dev server's reloads don't empty it. One process only. const store = globalThis as typeof globalThis & { anytimeOrders?: Map; anytimeEvents?: Set; }; const orders = (store.anytimeOrders ??= new Map()); /** Event ids already handled: webhooks can arrive more than once. */ export const handledEvents = (store.anytimeEvents ??= new Set()); export function createOrder(input: { title: string; amountCents: number }): Order { const order: Order = { id: `order_${randomUUID().slice(0, 8)}`, title: input.title, amountCents: input.amountCents, status: "pending", idempotencyKey: randomUUID(), paymentLinkId: null, paymentId: null, }; orders.set(order.id, order); return order; } export function saveOrder(order: Order): void { orders.set(order.id, order); } export function findOrder(id: string): Order | null { return orders.get(id) ?? null; } ``` ## 3. Create the payment link **lib/anytime.ts** ```typescript // lib/anytime.ts — calls the Anytime Capital Payments API. Import it from server code only: // the key must never reach the browser. const API = "https://anytime-capital.com/api/v1"; export interface PaymentLink { id: string; url: string; } export async function createPaymentLink(input: { title: string; amountCents: number; orderId: string; idempotencyKey: string; successUrl?: string; }): Promise { const res = await fetch(`${API}/payment-links`, { method: "POST", headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}`, "Content-Type": "application/json", "Idempotency-Key": input.idempotencyKey, }, body: JSON.stringify({ title: input.title, amountCents: input.amountCents, singleUse: true, // closes after its first payment: one link per order metadata: { orderId: input.orderId }, ...(input.successUrl ? { successUrl: input.successUrl } : {}), }), cache: "no-store", }); const body = await res.json(); if (!res.ok) throw new Error(`${body.error.code}: ${body.error.message}`); return { id: body.id, url: body.url }; } ``` **app/api/checkout/route.ts** ```typescript // app/api/checkout/route.ts — makes the order and its payment link, then sends the customer to pay. import { createPaymentLink } from "@/lib/anytime"; import { createOrder, saveOrder } from "@/lib/orders"; export const runtime = "nodejs"; // Prices live on the server: never take an amount from the browser. const PRODUCTS = new Map([["consulting-hour", { title: "Consulting — 1 hour", amountCents: 12500 }]]); export async function POST(request: Request) { const form = await request.formData(); const product = PRODUCTS.get(String(form.get("productId"))); if (!product) return new Response("Unknown product", { status: 400 }); const order = createOrder(product); const appUrl = process.env.APP_URL ?? ""; const link = await createPaymentLink({ title: order.title, amountCents: order.amountCents, orderId: order.id, idempotencyKey: order.idempotencyKey, // Success URLs must be https://; on http://localhost the customer stays on our confirmation page. successUrl: appUrl.startsWith("https://") ? `${appUrl}/orders/${order.id}` : undefined, }); saveOrder({ ...order, paymentLinkId: link.id }); return Response.redirect(link.url, 303); } ``` **app/page.tsx** ```typescript // app/page.tsx — a Buy button. The form posts to your server, not to us. export default function Home() { return (

Consulting

); } ``` **app/orders/[id]/page.tsx** ```typescript // app/orders/[id]/page.tsx — where customers land after paying (with an https APP_URL). import { notFound } from "next/navigation"; import { findOrder } from "@/lib/orders"; export const dynamic = "force-dynamic"; export default async function OrderPage({ params }: { params: Promise<{ id: string }> }) { const { id } = await params; const order = findOrder(id); if (!order) notFound(); return (

{order.title}

{order.status === "paid" ? "Paid. Thank you!" : "Confirming your payment: refresh in a moment."}

); } ``` ## 4. Confirm payment by webhook **app/api/webhooks/anytime/route.ts** ```typescript // app/api/webhooks/anytime/route.ts — marks orders paid once the payment really went through. import crypto from "node:crypto"; import { findOrder, handledEvents, saveOrder } from "@/lib/orders"; export const runtime = "nodejs"; function verified(body: string, header: string, secret: string): boolean { const parts = Object.fromEntries(header.split(",").map((part) => part.split("="))); const expected = crypto.createHmac("sha256", secret).update(`${parts.t}.${body}`).digest("hex"); const signed = typeof parts.v1 === "string" && parts.v1.length === expected.length && crypto.timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected)); return signed && Math.abs(Date.now() / 1000 - Number(parts.t)) <= 300; } export async function POST(request: Request) { const body = await request.text(); // the raw body: the signature covers these exact bytes const header = request.headers.get("Anytime-Signature") ?? ""; if (!verified(body, header, process.env.ANYTIME_WEBHOOK_SECRET ?? "")) { return new Response("Invalid signature", { status: 400 }); } const event = JSON.parse(body); if (handledEvents.has(event.id)) return new Response(null, { status: 200 }); const payment = event.data; const order = typeof payment?.metadata?.orderId === "string" ? findOrder(payment.metadata.orderId) : null; // Only trust a payment made on this order's own link, for this order's amount. if (order && payment.paymentLinkId === order.paymentLinkId) { if (event.type === "payment.succeeded" && payment.amountCents === order.amountCents) { saveOrder({ ...order, status: "paid", paymentId: payment.id }); } if (event.type === "payment.refunded" && payment.amountRefundedCents >= payment.amountCents) { saveOrder({ ...order, status: "refunded" }); } } handledEvents.add(event.id); return new Response(null, { status: 200 }); } ``` Register the endpoint in **Settings → Developers → Add endpoint** with `https://your-site/api/webhooks/anytime` and the events `payment.succeeded` and `payment.refunded`, or from code with the [webhook endpoints API](https://anytime-capital.com/docs/webhook-endpoints.md). Copy its signing secret into `ANYTIME_WEBHOOK_SECRET`. Webhook URLs must be public `https://` addresses, so while you develop on your own machine, expose it with a tunnel such as ngrok or Cloudflare Tunnel. **Or register it with the API** ```bash curl -X POST https://anytime-capital.com/api/v1/webhook-endpoints \ -H "Authorization: Bearer $ANYTIME_API_KEY" \ -H "Idempotency-Key: shop-webhook-endpoint" \ -H "Content-Type: application/json" \ -d '{"url":"https://shop.example.com/api/webhooks/anytime","events":["payment.succeeded","payment.refunded"]}' ``` ## 5. Try it 1. `npm run dev`, open the site, and press **Buy**. You land on the hosted checkout. 2. With a test key (`ak_test_…`) and **Test mode** on, pay with a [test card](https://anytime-capital.com/docs/test-mode.md#test-cards): no real money moves. With a live key, pay a small amount with your own card and refund it from **Orders**. 3. With your tunnel's `https://` address as `APP_URL`, you come back to `/orders/` after paying, and it reads “Paid” once the webhook has arrived. 4. Refund it from **Orders** in the dashboard, and the `payment.refunded` event marks it refunded. ## Going to production - Swap `lib/orders.ts` for your database, with a unique index on the event ids you've handled. - Keep `ANYTIME_API_KEY` and `ANYTIME_WEBHOOK_SECRET` in your host's secret settings, never in code. - Want customers to stay on your page? Open the same link as an overlay with the [Pay button](https://anytime-capital.com/docs/checkout.md). - Bank payments sit at `pending` for days: show “confirming” until `payment.succeeded` arrives, and handle `payment.failed`. --- # In-person payments with a QR code > Take payments face to face with no card reader: key in an amount, show a QR code, and your customer pays on their own phone with Apple Pay, Google Pay or card. Source: https://anytime-capital.com/docs/in-person · Markdown: https://anytime-capital.com/docs/in-person.md · All docs: https://anytime-capital.com/docs/llms-full.txt Collect in person from any phone, tablet or computer with a browser. There is no card reader to buy: your customer pays on their own phone, with Apple Pay, Google Pay or their card, by scanning a QR code you show them. The product overview is at [In-person payments](https://anytime-capital.com/business/in-person-payments). ## How it works 1. Open **Checkout tools → Collect in person** in the dashboard. 2. Key in the amount ($1.00 to $10,000.00) on the keypad, or type it, and add an optional note such as the item or table. 3. Show the full-screen QR code to your customer. 4. They scan it with their phone's camera and pay on their own phone. 5. Your screen changes to **Paid** as soon as the payment goes through. Start the next sale. Each sale is a one-time payment link: it closes after its payment and appears in **Payment Links** and **Orders** like any other, with receipts in your branding. You can cancel a sale from the QR screen before it's paid. > **A standing QR code for your counter:** For a fixed price you charge all day (a class, a ticket, a set menu), make a reusable payment link and print its QR code from **Checkout tools → Add to your website → Link**. ## Tap to Pay on iPhone (coming soon) We're bringing Tap to Pay on iPhone to the Anytime Capital app: your customer taps their contactless card, phone or watch on your iPhone, and you see Approved or Declined right away. It isn't available yet. The QR code works today on any phone, tablet or computer, and stays as the fallback once Tap to Pay on iPhone arrives. - It will sit next to the QR code in **Checkout tools → Collect in person**, in the Anytime Capital app on iPhone. - An owner or admin turns it on once for the business, by accepting Apple's terms with their Apple ID. - It needs iPhone XS or later on a recent version of iOS. On other devices, the keypad shows the QR code as it does today. - Tapped payments appear in **Orders** with the same receipts and payouts as every other payment. ## Build it into your own point of sale The same flow works from your own app through the API: create a `singleUse` link for the sale, render its `url` as a QR code, and mark the sale paid when the `payment.succeeded` webhook arrives with `data.source.id` equal to the link's id. ```bash curl -X POST https://anytime-capital.com/api/v1/payment-links \ -H "Authorization: Bearer $ANYTIME_API_KEY" \ -H "Idempotency-Key: pos-7781" \ -H "Content-Type: application/json" \ -d '{ "title": "Table 12", "amountCents": 4850, "singleUse": true, "metadata": { "saleId": "pos-7781" } }' ``` Then show the returned `url` as a QR code with any QR library, and deactivate the link (`POST /api/v1/payment-links/{id}/deactivate`) if the customer walks away. --- # Payment methods > The cards, wallets, bank payments and stablecoins Anytime Capital Payments accepts today, where each is offered, and the methods that are coming soon. Source: https://anytime-capital.com/docs/payment-methods · Markdown: https://anytime-capital.com/docs/payment-methods.md · All docs: https://anytime-capital.com/docs/llms-full.txt What a customer can pay with depends on how you charge them: card checkout (payment links, invoices, subscriptions, the website Pay button and in-person QR), stablecoin checkout on payment links and invoices, or a stablecoin payment request. Checkout only ever shows a customer the methods they can actually use on their device. ## Available today | Group | Methods | Where | | --- | --- | --- | | Cards | Visa, Mastercard, American Express, Discover, JCB, Diners Club, UnionPay, Maestro | Every checkout, link, invoice and subscription | | Wallets | Apple Pay, Google Pay | Every checkout, including in person | | Bank | ACH bank debit | Payment links and invoices | | Stablecoins | USDC, USDT | Payment links, invoices and payment requests, paid straight to your wallet: USDC and USDT on Ethereum and Solana, USDT on Tron | - **Cards, Apple Pay and Google Pay** are on by default. Apple Pay and Google Pay appear on supported devices and browsers, including on your own checkout domain. - **Bank account (ACH)** is turned on in **Settings → Payments**. It is offered on payment links and invoices, not on subscriptions, and payments clear in 3–5 business days. - **Stablecoins** are turned on in **Settings → Stablecoins**: payment links and invoices then offer USDC and USDT on Ethereum and Solana, and USDT on Tron, paid straight to your business wallet. See [Stablecoin checkout](https://anytime-capital.com/docs/stablecoin-checkout.md). They aren't offered on subscriptions. A stablecoin payment request sent from the dashboard is paid to your wallet too, but isn't matched to an order. ## Coming soon | Group | Methods | Status | | --- | --- | --- | | Pay later & more wallets | Klarna, Afterpay, Affirm, Cash App Pay, PayPal | Not offered at checkout yet | Don't build for these yet: checkout doesn't offer them today, and the API never reports them. ## How a method shows up in the API Each [payment](https://anytime-capital.com/docs/payments.md) reports `paymentMethod.type` (`card`, `apple_pay`, `google_pay`, `us_bank_account` or `stablecoin`, with more types possible as methods are added) and a readable `paymentMethod.label`, such as “Visa •••• 4242” or “USDC on Ethereum”. Card and card-wallet payments also carry `card.brand` and `card.last4`. Treat an unknown `type` as valid and show its `label`. > **Bank payments settle later:** A bank account payment is `pending` until it clears, then `paid` or `failed`. Ship goods or grant access on the `payment.succeeded` webhook, not when checkout finishes. ## Currency Prices on payment links and invoices are set in US dollars, in integer cents. Customers can pay with cards issued outside the US; international cards and currency conversion may carry additional fees, shown in your dashboard before you accept a payment. --- # Stablecoin checkout (USDC and USDT) > Let customers pay your payment links and invoices in USDC or USDT, straight to your own business wallet, with exact-amount matching and on-chain confirmation. Source: https://anytime-capital.com/docs/stablecoin-checkout · Markdown: https://anytime-capital.com/docs/stablecoin-checkout.md · All docs: https://anytime-capital.com/docs/llms-full.txt Customers can pay your [payment links](https://anytime-capital.com/docs/payment-links.md) and [invoices](https://anytime-capital.com/docs/invoices.md) in stablecoins, sent from their own wallet straight to your business wallet. Anytime Capital never holds the funds: we watch the chain, match the transfer to the order, and record the payment once it is final, with a receipt in your branding. ## What is accepted | Network | Tokens | Final after | | --- | --- | --- | | Ethereum (ERC-20) | USDC, USDT | 12 confirmations (about 2–3 minutes) | | Solana (SPL) | USDC, USDT | Finalized (usually under a minute) | | Tron (TRC-20) | USDT | Confirmed (about a minute) | Prices stay in US dollars and are paid at 1 token = $1. When a token trades materially away from $1, checkout stops offering it until it is back. Stablecoins aren't offered on subscriptions, on links that collect a shipping address or ask required questions, or while sales tax is added at checkout. ## Turn it on 1. Set up your business wallet on the **Wallets** page, so its Ethereum, Solana and Tron addresses are on file. 2. Open **Settings → Stablecoins** (owners and admins), switch on **Accept stablecoins**, and choose the tokens and networks. 3. For each network, choose which of your business wallet's own addresses customers pay, and confirm it. Only your wallet's addresses can be chosen; an address can't be typed in. 4. Save. Your payment links and invoices now show **Pay with stablecoin** below the card form. ## How a customer pays 1. They choose a token and network and enter an email for the receipt (and, on a link, the quantity, amount or discount code it offers). 2. Checkout shows the exact amount to send, your address and a QR code: an Ethereum payment request (EIP-681) or a Solana Pay link that fills in the token and amount, or the plain address on Tron. They have 30 minutes. 3. The amount is the price plus a fraction of a cent (for example 25.004217 USDC for $25.00). No other open payment to your address asks for the same amount, which is how the transfer is matched to this order. 4. The page updates as the transfer is seen, confirmed and final, then shows the receipt. > **Exact amount, right network, right token:** A transfer on another network or in another token isn't matched to the order. Customers paying from an exchange should check that the amount that arrives is exact, since exchanges often take a fee out of a withdrawal. ## Statuses | Status | What it means | | --- | --- | | Pending | Waiting for the transfer. | | Confirming | The transfer is on the chain and waiting to be final. | | Underpaid | Less than the price arrived. The customer has 15 more minutes to send the rest to the same address. | | Paid | The price (or up to a cent over) arrived and is final. The payment is recorded. | | Overpaid | More arrived than asked. It counts as paid, and you're told the difference to send back. | | Expired | Nothing arrived in time, or not enough. If part of it arrived, you're told so you can refund it. | | On hold | The sending wallet is on our compliance blocklist. Nothing is recorded or fulfilled; our compliance team contacts you. | A transfer of the exact amount that arrives within a day after a payment expired is still matched to it and recorded, and you're told it came late. An amount that isn't exact is only taken as the customer's when theirs is the one payment open to your address and it is close to what was asked (at least half of it, and at most a quarter of the price or $10 over); anything else stays in your wallet, unmatched, for you to sort out. ## Once it's paid - The payment appears in **Orders** and **Payments** with the method “USDC on Ethereum” (or the token and network used), the transaction and the customer's wallet. - An invoice is marked paid, or partly paid for a deposit or part payment. A one-time link closes and a discount code counts one use. - We email the customer's receipt and your team's “New payment” alert, and send the `payment.succeeded` webhook. ## The payment.succeeded webhook A stablecoin payment's [webhook](https://anytime-capital.com/docs/webhooks.md) carries `paymentMethod.type` `stablecoin` and a `stablecoin` object with the chain, token, amounts, wallets and every transaction with its explorer link. Its payment id starts `pay_sc`. **payment.succeeded** ```json { "id": "evt_…", "type": "payment.succeeded", "created": "2026-10-24T14:05:12.000Z", "data": { "object": "payment", "id": "pay_scR8m2KqT4vX9bLw3NfYc7Hd1Pz", "status": "paid", "amountCents": 2500, "currency": "usd", "description": "Ceramic mug", "source": { "type": "payment_link", "id": "pl_8fK2mQ9xLw3NfYc7Hd1P" }, "customer": { "name": "Ana Ruiz", "email": "ana@example.com", "country": null }, "paymentMethod": { "type": "stablecoin", "label": "USDC on Ethereum" }, "card": null, "paidAt": "2026-10-24T14:05:09.000Z", "stablecoin": { "chain": "ethereum", "network": "ERC-20", "token": "USDC", "tokenContract": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", "amount": "25.004217", "requestedAmount": "25.004217", "overpaidAmount": null, "receivingAddress": "0x…your wallet", "payerAddress": "0x…customer's wallet", "txHash": "0x…", "explorerUrl": "https://etherscan.io/tx/0x…", "transactions": [ { "txHash": "0x…", "explorerUrl": "https://etherscan.io/tx/0x…", "amount": "25.004217" } ] } } } ``` ## Refunds Refunds are manual. The payment went straight to your wallet, so send the tokens back from it, in the same token and on the same network, to an address the customer gives you. **Refund** in Orders isn't available for stablecoin payments. --- # Take USDC and USDT payments in your app > Take stablecoin payments with no change to your checkout code: switch them on, fulfil on payment.succeeded, and keep the on-chain transaction with each order. Source: https://anytime-capital.com/docs/accept-stablecoins · Markdown: https://anytime-capital.com/docs/accept-stablecoins.md · All docs: https://anytime-capital.com/docs/llms-full.txt If your integration already creates [payment links](https://anytime-capital.com/docs/payment-links.md) or [invoices](https://anytime-capital.com/docs/invoices.md) and fulfils on the `payment.succeeded` webhook, it takes stablecoins as it is: the customer picks **Pay with stablecoin** on the same checkout, sends USDC or USDT from their wallet straight to your business wallet, and you get the same event once the transfer is final. This guide adds the on-chain details to your orders. How matching, confirmations and statuses work is on [Stablecoin checkout](https://anytime-capital.com/docs/stablecoin-checkout.md). ## 1. Switch it on 1. Set up your business wallet on the **Wallets** page. 2. In **Settings → Stablecoins** (owners and admins), turn on **Accept stablecoins**, pick tokens and networks, and confirm the receiving address for each. 3. Your links and invoices now offer **Pay with stablecoin** below the card form. Nothing changes in your code. > **Where it isn't offered:** Prices stay in US dollars, paid at 1 token = $1. Stablecoins aren't offered on subscriptions, on links that collect a shipping address or ask required questions, or while sales tax is added at checkout. ## 2. Fulfil on payment.succeeded A stablecoin payment's `payment.succeeded` has `paymentMethod.type` `stablecoin`, an id starting `pay_sc`, and a `stablecoin` object with the chain, token, amounts, wallets and each transaction's explorer link. The `stablecoin` object comes with the webhook only: `GET /api/v1/payments/{id}` returns the payment without it, so store what you need when the event arrives. **webhook.mjs** ```javascript // webhook.mjs — Node.js 18+ with Express: npm install express import crypto from "node:crypto"; import express from "express"; const app = express(); const secret = process.env.ANYTIME_WEBHOOK_SECRET; // whsec_… function verify(rawBody, header) { const parts = Object.fromEntries(String(header ?? "").split(",").map((part) => part.split("="))); const expected = crypto.createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex"); return ( typeof parts.v1 === "string" && parts.v1.length === expected.length && crypto.timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected)) && Math.abs(Date.now() / 1000 - Number(parts.t)) <= 300 ); } app.post("/webhooks/anytime", express.raw({ type: "application/json" }), async (req, res) => { const rawBody = req.body.toString("utf8"); if (!verify(rawBody, req.get("Anytime-Signature"))) return res.status(400).send("Invalid signature"); const event = JSON.parse(rawBody); // Skip event.id if you've handled it before. if (event.type === "payment.succeeded") { const payment = event.data; const order = { id: payment.metadata.orderId, paymentId: payment.id, paidCents: payment.amountCents }; if (payment.paymentMethod?.type === "stablecoin" && payment.stablecoin) { // Final on-chain: keep the proof with the order. order.method = payment.paymentMethod.label; // "USDC on Ethereum" order.txHash = payment.stablecoin.txHash; order.explorerUrl = payment.stablecoin.explorerUrl; order.tokenAmount = payment.stablecoin.amount; // what arrived, e.g. "25.004217" } await markPaid(order); // your database } res.sendStatus(200); }); async function markPaid(order) { console.log("paid", order); } app.listen(3000); ``` ## 3. Refunds and mistakes - Refunds are manual: the tokens went to your own wallet, so send them back from it, in the same token on the same network. The [refunds API](https://anytime-capital.com/docs/refunds.md) doesn't refund stablecoin payments. - An overpayment counts as paid and you're told the difference to send back; an expired or underpaid one tells you what arrived. Both show in the dashboard, not as webhooks. - Only `payment.succeeded` is sent: there's no event for a stablecoin payment that never arrives, so let unpaid orders time out on your side as you would for an abandoned card checkout. --- # Fraud rules, 3D Secure and refunds > Protect your Anytime Capital payments: block card countries, require 3D Secure above an amount, cap payment size, block high-risk scores and auto-refund alerts. Source: https://anytime-capital.com/docs/risk-and-3ds · Markdown: https://anytime-capital.com/docs/risk-and-3ds.md · All docs: https://anytime-capital.com/docs/llms-full.txt Every card payment is screened by our fraud checks before it's charged. On top of that, **Settings → Risk** gives you a handful of rules, each a single control with a plain sentence, plus automatic refunds for pre-dispute alerts. Owners and admins can change the rules, and the owner sets automatic refunds; rules apply to new payments. ## Payment rules | Rule | What it does | Setting | | --- | --- | --- | | Block cards from selected countries | Payments with cards issued in the countries you choose are declined. | Up to 50 countries, by where the card was issued. | | Require 3D Secure above an amount | Customers confirm larger payments with their card issuer, which usually makes the issuer, not you, responsible for fraud disputes on them. | An amount from $0.00 (every payment) up to $1,000,000.00. | | Maximum amount per payment | Card payments over this amount are declined. | An amount from $1.00 to $1,000,000.00. | | Block high-risk payments | Payments our fraud screening scores as risky are declined before the card is charged. | How strict: see the levels below. | ### High-risk levels “Block high-risk payments” declines payments by the 0–100 risk score our fraud screening gives each one: - Only the riskiest payments (score 90 or more) - High-risk payments (score 75 or more) - Elevated and high risk (score 60 or more) ## 3D Secure With “Require 3D Secure above an amount”, customers confirm larger payments with their card issuer, usually in their banking app. That usually makes the issuer, not you, responsible for fraud disputes on those payments. Set the amount to $0.00 to ask for it on every payment. ## Automatic refunds for pre-dispute alerts Card networks sometimes warn us that a customer is about to dispute a payment. A refund before the dispute is opened avoids the dispute. The owner can turn on automatic refunds in Settings → Risk with a limit from $1.00 to $10,000.00 (we suggest $100.00). - Only alerts nobody on your team has acted on, that are still open, are refunded. - The payment must be refundable and untouched: never a second refund, never a payment already refunded or disputed. - Both the payment and the amount the issuer reported must be at or under your limit. - It is always a full refund, and you receive `payment.refunded`. ## Refund approvals In **Settings → Team & approvals**, set an approval limit (up to $1,000,000.00). Refunds above it, made by anyone but the owner, wait in Orders until the owner approves or declines them, and the owner is notified. With no limit there is no approval step. ## Checkout rules **Settings → Risk → Checkout rules** run on every card checkout before the card is charged, on payment links and invoices alike. Each starts off. When one matches, it either declines the payment (the customer sees the usual decline, never which rule) or holds it for review. Next to each rule, the dashboard shows how many of the last 30 days' checkouts it would have matched, before you switch it on. | Rule | What it checks | Setting | | --- | --- | --- | | Too many attempts with one card | Someone testing stolen cards tries the same card again and again. This counts every checkout attempt with the same card number. | Attempts per hour and per 24 hours (we suggest 5 and 10). | | Too many attempts from one email | Counts checkout attempts with the same email address, whatever card is used. | Attempts per hour and per 24 hours (we suggest 5 and 15). | | Too many attempts from one connection | Counts checkout attempts from the same internet connection (IP address). Shared networks such as offices and schools can reach it sooner. | Attempts per hour and per 24 hours (we suggest 10 and 30). | | Block email domains | Payments from email addresses at the domains you list, and their subdomains. | The domains you list; their subdomains match too. | | Disposable email address | Throwaway inboxes (like Mailinator or Guerrilla Mail) that people use to avoid being contacted later. We keep the list up to date. | Nothing to set: we keep the list of throwaway email providers up to date. | | Location doesn't match billing country | The country the customer is connecting from isn't the country of their billing address. Travelers and VPN users can match too. | Nothing to set. | | First-time customer over an amount | A customer who has never paid you before spends more than this in one payment. | An amount (we suggest $500.00). | | Most one customer can spend in 24 hours | The total of one email address's payments in the last 24 hours, including this one. | An amount (we suggest $2,000.00). | To ask for 3D Secure, use the “Require 3D Secure above an amount” payment rule: 3D Secure can't be requested one payment at a time. ## Manual review A held payment is authorized on the customer's card but not taken. The customer sees “Payment received — the business is confirming your order”, and gets their receipt only once you approve. Owners and admins decide in **Payments → Review**, which shows why it was held, the card, where the customer is, 3D Secure, the fraud score, their history with you and the time left. Members can look. - Approve: the payment is taken and handled like any other — receipt, invoice, discount code, `payment.succeeded`. - Reject: the payment is canceled, the hold on the card released, and the customer told politely by email. Nothing is taken. - You receive `payment.review_required` when a payment is held, and your owners and admins get the “Payment held for review” alert. - A card can be held for 5 days at most. If nobody decides within 4 days, we cancel it (or approve it, if you choose so in Settings → Risk), and remind your team a day before. - Holds apply to one-time card payments. A subscription or bank payment that matches a hold rule goes through as usual. A payment rule on your account set to review is held the same way. ## One-click checkout When a customer ticks “Save my card for faster checkout”, we record their consent. Next time, on any of your payment links or invoices, they're offered “Pay faster with a saved card”: we email them a 6-digit code, and only then show their saved cards (brand, last four and expiry). They can trust their device for 30 days to skip the code. A saved card is only ever shown to the same email, with the same business. Customers can remove cards, sign out trusted devices or turn faster checkout off from your billing page. It's on by default; turn it off in Settings → Risk. ## Disputes When a customer disputes a payment you receive `dispute.opened`, the payment's `status` becomes `disputed` and `dispute.status` tracks it: `needs_response`, `under_review`, then `won`, `lost` or `closed`, with `dispute.closed` when it's decided. Respond from **Disputes** in the dashboard; your terms and refund policy from Settings → Payments can be used as evidence. We build the response for you: an [evidence pack](https://anytime-capital.com/docs/dispute-evidence.md) from your own records, with 3D Secure, the security code and address checks, the device and browser used at purchase and the customer's history for fraud claims. Its status is on every dispute in the [Disputes API](https://anytime-capital.com/docs/disputes.md) as `evidence`. - [Automatic dispute evidence](https://anytime-capital.com/docs/dispute-evidence.md): What goes into the evidence pack for each reason, automatic submission, and your win rate. --- # Automatic Dispute Evidence: Packs, Responses and Win Rate > When a card dispute opens, we build an evidence pack from your records, write the response for the bank's reason and fill in your draft. Track your win rate. Source: https://anytime-capital.com/docs/dispute-evidence · Markdown: https://anytime-capital.com/docs/dispute-evidence.md · All docs: https://anytime-capital.com/docs/llms-full.txt When a customer disputes a card payment, we assemble the evidence from your own records, write a response for the reason the customer's bank gave, attach a PDF of it all with the customer's receipt, and fill in your response draft. You review it on the **Disputes** page, add what only you know, and submit. Nothing is sent to the bank until the response is submitted. ## What goes into the pack | Evidence | Where it comes from | | --- | --- | | The order | What was bought, quantities, discounts, shipping, tax and the total, from your receipt. | | The customer | Name, email, billing address, and how long they have been your customer. | | Customer history | Their earlier payments to you that were not disputed, and which used the same card. | | Checkout verification | 3D Secure, the security code and address checks, the issuer's authorization code, the IP address and its country next to the card's billing country. | | Device and browser used at purchase | For fraud and “unrecognized” disputes: the browser and system the customer paid from, its full user agent and languages, and earlier payments from the same IP address. Recorded by our checkout on every card payment the customer makes themselves. | | Saved card | For one-click payments: when the customer saved the card with you, and that they verified their email address before paying. | | Terms and refund policy | Your terms and refund policy links (Settings, under Payments), and when the customer ticked “I agree” on a link that asks for it. | | Delivery | The shipping address, and when you added tracking: the carrier, tracking number, status and delivery date. | | Emails we sent | Receipts, invoices and reminders, refund receipts, and free-trial reminders, with the time each went out. | | Subscription | The plan, its price and billing period, when they subscribed, every payment on it, and whether and when they canceled. | | Refunds | Every refund on the payment, with the reference the customer's bank can trace it by. | Only facts we hold go in. The pack never guesses, and it never fills in a field you have already written: a field is filled while it is empty, or while it still holds what we wrote last time. The device details come from your hosted checkout, payment links, invoices, the embedded checkout and the Shopify and WooCommerce checkouts, whenever the customer pays by card themselves; renewals and charges you make from the dashboard have none. They are kept with the payment for as long as its record, because a dispute can arrive months later, and are used only as dispute evidence: never on a receipt, never in the API or webhooks. Test-mode payments keep their own, in test mode. ## A response for each reason | Bank's reason | What the response leads with | Filed as | | --- | --- | --- | | Fraudulent | 3D Secure, a saved card after email verification, security code and address checks, matching locations, the device and browser used at purchase, earlier undisputed payments. | Checkout details | | Unrecognized | The receipt we emailed, the name on card statements, the customer's history with you, the device and browser used at purchase. | Messages with the customer | | Product not received | Tracking and the delivery date; otherwise the receipt and your policies. | Proof of delivery, or messages | | Not as described | Your refund policy and terms, and how the customer saw them. | Messages with the customer | | Subscription canceled | When they subscribed, the renewal terms shown at checkout, every payment, and that they had not canceled before this one. | Subscription history | | Refund not processed | Any refund already made and its trace reference, and your refund policy. | Messages with the customer | | Duplicate | Whether another payment for the same amount exists that week, and if so what each one was for. | Customer's past orders | | Declined authorization, processing error, bank couldn't process | The issuer's approval and authorization code, and the receipt. | Checkout details | | Request for information, other | The receipt, delivery, verification and your policies. | Proof of delivery, or messages | Each pack also gets our read of the case — strong, fair or weak — and a checklist of what it includes and what only you can add, such as proof of delivery or why no refund is owed. Our read is for you; it is never sent to the bank. > **When we suggest accepting:** If your own records back the customer — the subscription was canceled before the charge, tracking shows the order was returned, or the same card was charged twice within an hour on the same link — the pack says so, and it is never submitted automatically. ## Reviewing and submitting - Open the dispute on the **Disputes** page. The evidence pack sits above your response: the checklist, the response we wrote and a PDF to download. - Edit any field in the response, upload files you have (photos, sign-ins, conversations), and save your draft. - Choose **Rebuild from latest records** after adding tracking or a refund policy: fields you edited keep your words. - Submit once. Evidence can't be changed after it is submitted. The PDF is attached to the response unless it already holds its maximum of 10 files. Owners and admins build, edit and submit; every member can read the pack. ## Automatic building and submission | Setting | What it does | Default | | --- | --- | --- | | Build evidence automatically | Builds the pack when a dispute opens and tells your owners and admins it is ready. | On | | Submit for me if nobody has | Submits the response for you 1 to 7 days before the deadline (you choose) when no one on your team has. Checked every hour; it never submits in the last 15 minutes before the deadline. | Off | Change them on the **Disputes** page, under **Evidence automation**. Deadline reminders, 3 days and 1 day before, say when the pack is ready and when it will be submitted for you. If an automatic submission doesn't go through, we email your owners and admins straight away so someone can submit before the deadline. ## Evidence status in the API Every dispute from the [Disputes API](https://anytime-capital.com/docs/disputes.md) (`GET /v1/disputes`, `GET /v1/disputes/{id}`, the `dispute.updated` webhook and the MCP tools `list_disputes` and `get_dispute`) carries its pack as `evidence`, or `null` while there is none: | Field | What it tells you | | --- | --- | | `status` | `building` right after the dispute opens or while it is rebuilt, `ready`, or `failed` (rebuild it from the Disputes page). | | `strength` | `strong`, `fair` or `weak`: our read of the case once built. For you only; never sent to the bank. | | `recommendAccept` | `true` when your own records back the customer. Such a pack is never submitted automatically. | | `documentReady` | `true` once the pack's PDF, with the customer's receipt, is attached to the response. | | `builtAt` | When the pack was last built. | | `submittedAt` | When the response went in with the pack, by your team or automatically. | | `autoSubmitAt` | When we'll submit it for you, while that is still ahead: automatic submission is on, nobody has submitted, we don't advise accepting, and the deadline hasn't passed. Otherwise `null`. | ```json { "status": "ready", "strength": "strong", "recommendAccept": false, "documentReady": true, "builtAt": "2026-10-18T08:31:12.000Z", "submittedAt": null, "autoSubmitAt": "2026-10-30T23:59:59.000Z" } ``` A test-mode key (`ak_test_…`) reads the packs of your test disputes. Responding still happens on the Disputes page. ## Win rate The top of the Disputes page shows the disputes opened in the last 365 days: the share the bank decided in your favor, the amounts won back and lost, what is still open, how disputes answered with an evidence pack fared against the rest, and the results by reason. Disputes closed without a ruling (refunded first, or an inquiry that ended) aren't counted as won or lost. - [Fraud rules, 3D Secure and refunds](https://anytime-capital.com/docs/risk-and-3ds.md): Prevent disputes: checkout rules, 3D Secure and pre-dispute alerts. - [Disputes API](https://anytime-capital.com/docs/disputes.md): Read disputes, their status and their evidence pack from the API and webhooks. --- # International Customers and Currency > Customers outside the US pay your links and invoices by card in US dollars. How conversion, refunds and disputes work for them, and what isn't available yet. Source: https://anytime-capital.com/docs/international-customers · Markdown: https://anytime-capital.com/docs/international-customers.md · All docs: https://anytime-capital.com/docs/llms-full.txt Your prices, charges and balance are in US dollars. Customers outside the US can pay your payment links, invoices and subscriptions with a card, Apple Pay or Google Pay, and are charged the dollar amount they see at checkout. ## What a customer outside the US sees | Topic | How it works | | --- | --- | | Price at checkout | In US dollars, exactly as you set it, with any shipping, tip and sales tax. | | On their statement | The dollar amount converted to their own currency by their card issuer, at the issuer's rate. Their issuer may also charge them a foreign transaction fee; it isn't part of your price and isn't paid to you. | | Bank payments | Bank (ACH) payments are for US bank accounts. | | Stablecoins | USDC and USDT are dollar stablecoins: customers pay the dollar amount. | ## Refunds and disputes - Refunds are in US dollars, the amount you choose. The customer's issuer converts it back at its rate on the day, so the amount in their currency can differ a little from what they paid. - Disputes are for the dollar amount charged, and the dollar amount is held from your balance while the bank decides. See [Automatic dispute evidence](https://anytime-capital.com/docs/dispute-evidence.md). > **Local-currency pricing isn't available yet:** Showing and charging customers in their own currency (euros, pounds, yen and others) while you keep pricing and getting paid in dollars is not offered yet. Until it is, every checkout shows and charges US dollars. - [Payment methods](https://anytime-capital.com/docs/payment-methods.md): Every way customers can pay, today and coming soon. - [Hosted checkout](https://anytime-capital.com/docs/checkout.md): What your customers see when they pay. --- # Issue refunds safely with idempotency > Refund payments from your code without ever refunding twice: a stored Idempotency-Key, retries that are safe, refunds waiting for approval, and events. Source: https://anytime-capital.com/docs/refund-safely · Markdown: https://anytime-capital.com/docs/refund-safely.md · All docs: https://anytime-capital.com/docs/llms-full.txt A refund moves money back to a customer, so it must happen exactly once even when networks fail. The pattern: decide the refund and store its Idempotency-Key first, send it with that key as many times as you need, and read the outcome. The key also keys the refund at the payments network, so even a retry after a lost response refunds once. Endpoint details are on [Refunds](https://anytime-capital.com/docs/refunds.md). ## What can come back | Answer | Meaning | Do | | --- | --- | --- | | `201`, `status: succeeded` | Refunded. | Done. | | `201`, `status: pending` | Sent, not settled yet. | Wait for `refund.succeeded` or `refund.failed`. | | `202`, `status: pending_approval` | Over your approval limit: an owner decides. Nothing refunded yet. | Keep the `re_appr_…` id; check it later or wait for `refund.created`. | | `409 REFUND_PENDING_APPROVAL` | Another refund on this payment already waits for approval. | Don't send another; wait. | | `409 NOT_REFUNDABLE`, `400 INVALID_INPUT` | Can't be refunded, or more than is left. | Show a person; don't retry unchanged. | | `429`, `502`, `503`, `500 INTERNAL_ERROR`, no answer | Not done, or not known. | Retry with the **same** key. | | `500 REFUND_NOT_RECORDED` | Sent, but our record isn't there yet. | Never resend: list `GET /api/v1/refunds?paymentId=…` in a minute. | ## Node.js **refunds.mjs** ```javascript // refunds.mjs — Node.js 18+. Run: node refunds.mjs pay_… 2500 import { randomUUID } from "node:crypto"; const API = "https://anytime-capital.com/api/v1"; const headers = { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}` }; // 1. Decide the refund once, and give it a key you store with it (a table row in real life). export function planRefund(paymentId, amountCents) { return { paymentId, amountCents, idempotencyKey: `refund-${randomUUID()}`, state: "planned", refundId: null }; } // 2. Send it. Safe to call again and again with the same plan: the key makes it happen once. export async function sendRefund(plan) { for (let attempt = 1; attempt <= 4; attempt++) { let res; try { res = await fetch(`${API}/refunds`, { method: "POST", headers: { ...headers, "Content-Type": "application/json", "Idempotency-Key": plan.idempotencyKey }, body: JSON.stringify({ paymentId: plan.paymentId, amountCents: plan.amountCents, reason: "requested_by_customer" }), }); } catch { await sleep(2 ** attempt); // the network failed: same key, try again continue; } const body = await res.json().catch(() => ({})); if (res.status === 201) return { ...plan, state: body.status, refundId: body.id }; // succeeded or pending if (res.status === 202) return { ...plan, state: "pending_approval", refundId: body.id }; // re_appr_…: an owner decides const code = body.error?.code; if (code === "REFUND_NOT_RECORDED") return { ...plan, state: "sent_unrecorded" }; // it went out: look it up, never resend if (res.status === 429 || res.status >= 500) { await sleep(Number(res.headers.get("Retry-After") ?? 2 ** attempt)); continue; // RATE_LIMITED, PROCESSOR_ERROR, NOT_CONFIGURED, INTERNAL_ERROR: same key again } // NOT_FOUND, NOT_REFUNDABLE, REFUND_PENDING_APPROVAL, INVALID_INPUT…: a person needs to look. return { ...plan, state: "failed", error: `${code}: ${body.error?.message}` }; } return { ...plan, state: "retry_later" }; // run sendRefund(plan) again later with the same plan } // 3. Later (or on a webhook): where does a refund that waited for approval stand? export async function checkRefund(refundId) { const res = await fetch(`${API}/refunds/${refundId}`, { headers }); const refund = await res.json(); if (!res.ok) throw new Error(`${refund.error.code}: ${refund.error.message}`); if (refund.status === "failed" && refund.failureReason === "approval_declined") return "declined"; if (refund.refundId) return `approved as ${refund.refundId}`; return refund.status; } const sleep = (seconds) => new Promise((resolve) => setTimeout(resolve, seconds * 1000)); if (process.argv[2]) { const plan = planRefund(process.argv[2], process.argv[3] ? Number(process.argv[3]) : undefined); console.log(await sendRefund(plan)); } ``` ## Python **refunds.py** ```python # refunds.py — pip install requests. Run: python refunds.py pay_… 2500 import os import sys import time import uuid import requests API = "https://anytime-capital.com/api/v1" HEADERS = {"Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}"} def plan_refund(payment_id, amount_cents=None): """Decide the refund once and store its key with it.""" return {"payment_id": payment_id, "amount_cents": amount_cents, "key": f"refund-{uuid.uuid4()}", "state": "planned"} def send_refund(plan): """Safe to call again with the same plan: the key makes the refund happen once.""" body = {"paymentId": plan["payment_id"], "reason": "requested_by_customer"} if plan["amount_cents"] is not None: body["amountCents"] = plan["amount_cents"] for attempt in range(1, 5): try: res = requests.post(f"{API}/refunds", headers={**HEADERS, "Idempotency-Key": plan["key"]}, json=body, timeout=30) except requests.RequestException: time.sleep(2**attempt) # the network failed: same key, try again continue try: data = res.json() except ValueError: data = {} if res.status_code == 201: return {**plan, "state": data["status"], "refund_id": data["id"]} if res.status_code == 202: return {**plan, "state": "pending_approval", "refund_id": data["id"]} code = data.get("error", {}).get("code") if code == "REFUND_NOT_RECORDED": return {**plan, "state": "sent_unrecorded"} # it went out: look it up, never resend if res.status_code == 429 or res.status_code >= 500: time.sleep(int(res.headers.get("Retry-After", 2**attempt))) continue return {**plan, "state": "failed", "error": f"{code}: {data.get('error', {}).get('message')}"} return {**plan, "state": "retry_later"} if __name__ == "__main__": amount = int(sys.argv[2]) if len(sys.argv) > 2 else None print(send_refund(plan_refund(sys.argv[1], amount))) ``` ## Partial refunds - Send `amountCents` for part of a payment; leave it out to refund everything still left. - Several partial refunds are fine, each with its own key, up to what was paid. Over that answers `400 INVALID_INPUT` naming `amountCents`. - On `payment.refunded`, `data.amountRefundedCents` is the total refunded so far, not this refund alone. ## Keep a person in the loop - Set a refund approval limit in **Settings → Team & approvals**: refunds over it by admins, API keys and AI agents wait for an owner. A limit of $0 makes every one of them wait. - Give the code that refunds its own [restricted key](https://anytime-capital.com/docs/ai-agent-safety.md) with `refunds` write and nothing it doesn't need. - Every refund is in your activity log under the key's name. > **Note:** Stablecoin payments (`pay_sc…`) are refunded from your own wallet, not with this endpoint. See [Stablecoin checkout](https://anytime-capital.com/docs/stablecoin-checkout.md#refunds). --- # Bank transfer instructions on invoices > Let customers pay invoices by ACH or wire into your own bank account: your details on the invoice page, PDF and email, with the invoice number as reference. Source: https://anytime-capital.com/docs/invoice-bank-transfers · Markdown: https://anytime-capital.com/docs/invoice-bank-transfers.md · All docs: https://anytime-capital.com/docs/llms-full.txt Some customers pay invoices by ACH or wire. Save your business's own bank details once and your invoices show them next to card payment, with the invoice number as the payment reference so each transfer is easy to match. ## Set up - In **Settings → Payouts → Bank transfer instructions for invoices**, an owner or admin enters the bank name, the name on the account, the 9-digit ABA routing number (checked as you save), the account number and account type. - For international wires, add the SWIFT/BIC code and the bank's address. - Set the **payment reference**. `{invoice_number}` becomes the invoice's number, so `ACME {invoice_number}` reads `ACME INV-0042`. - Choose whether new invoices show the instructions by default. The account number is encrypted when it is saved, and the dashboard only ever shows its last four digits. Every change is in the activity log. ## On each invoice - Owners and admins can switch the instructions on or off for one invoice, in the invoice editor or in the **Bank transfer** card on the invoice. - They appear on the hosted invoice page, its PDF and the invoice email while the invoice can be paid, and never on a paid, refunded or canceled invoice. - Card payment stays available on the same invoice. ## Record the payment A bank transfer goes straight to your bank account, so we don't see it arrive. When it does, open the invoice and use **Record payment** with the method **Bank transfer** (the full balance, or what came in). The invoice is marked paid or partly paid, and you can send the customer a receipt. See [Invoices](https://anytime-capital.com/docs/invoices.md) for the API. --- # Discount Codes and Plan Changes on Subscriptions > Offer discount codes on subscription plans for the first payment, several months or every payment, and move subscribers between plans with fair proration. Source: https://anytime-capital.com/docs/subscription-discounts · Markdown: https://anytime-capital.com/docs/subscription-discounts.md · All docs: https://anytime-capital.com/docs/llms-full.txt A discount code from the **Discounts** page can also discount subscription plans. Turn on **Also use on subscriptions** when you create a code, or choose **Use on subscriptions** from an existing code's menu. ## How long a discount lasts | Duration | What it discounts | | --- | --- | | First payment | The first payment, after any free trial. | | Several months | The first 2 to 36 monthly payments. Monthly plans only, since it counts payments. | | Every payment | Every payment for as long as the subscription lasts. | How long a discount lasts is fixed once set; create a new code for new terms. Which plans a code covers (all, or the ones you pick) and **New customers only** can change at any time. A new-customers-only code is refused for an email that has subscribed to, or paid, your business before. The code's expiry date and use limit work as on payment links, and a use on a subscription counts toward the same limit. Archiving a code stops new uses; subscriptions that already have it keep their discount for its duration. ## Where subscribers use a code - At checkout: the plan's subscribe page shows **Have a discount code?** whenever one of your subscription codes covers the plan. The order summary shows the discounted first payment and what later payments cost. - On an existing subscription: open the subscriber on the **Subscriptions** page and choose **Add discount**. It starts with their next payment. A subscription holds one code at a time. Receipts for discounted payments show the discount, and the subscriber's billing page and your dashboard show which code a subscription has. ## Changing a subscriber's plan From a subscriber's details on the **Subscriptions** page, choose **Change plan**, pick the plan and when it starts. You see exactly what happens before you confirm. | When | What happens | | --- | --- | | Switch now | The new plan starts today, charged in full to the subscriber's saved card. The unused part of the current period is refunded to the card that paid it: what they paid times the share of the period left, rounded up in their favour and never more than they paid. If the new plan's payment is declined, nothing changes. | | At next renewal | Nothing changes until the current period ends. Then the new plan starts and is charged to the saved card. You, or the subscriber, can undo it until then. | > **Note:** A discount on the current plan doesn't carry over to the new one. Add a code to the new subscription if you want it to. To let subscribers change plans themselves from your billing page, mark the plans they may switch to (**Let subscribers switch to it** in a plan's menu) and turn on **Subscribers can switch plans** on the **Plans** tab. - [Usage-based billing](https://anytime-capital.com/docs/usage-billing.md): Bill by use on top of a plan's price, or on its own. - [Checkout](https://anytime-capital.com/docs/checkout.md): What your customers see when they pay. --- # Sell subscriptions with coupons and plan changes > Create subscription plans, send customers to subscribe, offer coupon codes, let them change plans, and keep access in your app in step with webhooks. Source: https://anytime-capital.com/docs/sell-subscriptions · Markdown: https://anytime-capital.com/docs/sell-subscriptions.md · All docs: https://anytime-capital.com/docs/llms-full.txt Customers subscribe on Anytime Capital's hosted page for each plan. Your app creates the plans, sends customers to them, and keeps access in step with webhooks. Coupons and plan changes are switched on in the dashboard; no code needed for those. Endpoint details: [Subscriptions & plans](https://anytime-capital.com/docs/subscriptions.md). ## 1. Create your plans Make them in **Subscriptions → Plans** in the dashboard, or from code with a key that has `subscriptions` write. A plan's price and interval are fixed once it exists; for a new price, make a new plan. **create-plans.mjs** ```javascript // create-plans.mjs — run once: node create-plans.mjs const API = "https://anytime-capital.com/api/v1"; const PLANS = [ { key: "plan-basic-monthly", title: "Basic", amountCents: 1900, interval: "month", trialDays: 14 }, { key: "plan-pro-monthly", title: "Pro", amountCents: 4900, interval: "month", trialDays: 14 }, { key: "plan-pro-yearly", title: "Pro (yearly)", amountCents: 49000, interval: "year" }, ]; for (const { key, ...plan } of PLANS) { const res = await fetch(`${API}/subscription-plans`, { method: "POST", headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}`, "Content-Type": "application/json", "Idempotency-Key": key, // run it twice within 24 hours and you still get one plan each }, body: JSON.stringify(plan), }); const created = await res.json(); if (!res.ok) throw new Error(`${created.error.code}: ${created.error.message}`); console.log(created.id, created.title, created.url); // put each url behind its Subscribe button } ``` ## 2. Send customers to subscribe Each plan's `url` is its subscribe page: link your **Subscribe** buttons to it. Free trials start there, and the subscription appears on your **Subscriptions** page and in `subscription.created`. ## 3. Offer coupons 1. Create a code on the **Discounts** page, or with `POST /api/v1/discount-codes` (see [Discount codes](https://anytime-capital.com/docs/discount-codes.md)). 2. In the dashboard, turn on **Use on subscriptions** for it and choose how long it lasts: the first payment, several months, or every payment, and which plans it covers. 3. The plan's subscribe page then shows **Have a discount code?**. You can also add a code to an existing subscriber from their details. How long a discount lasts and new-customers-only rules are on [Subscription discounts](https://anytime-capital.com/docs/subscription-discounts.md); subscription settings for a code can't be set through the API. ## 4. Keep access in step Subscribe your endpoint to `subscription.created`, `subscription.updated`, `subscription.paused`, `subscription.resumed`, `subscription.past_due` and `subscription.canceled`. Events can arrive more than once and out of order, so on each one, read the subscription as it is now and store that. **subscriptions-webhook.mjs** ```javascript // subscriptions-webhook.mjs — Node.js 18+ with Express: npm install express import crypto from "node:crypto"; import express from "express"; const API = "https://anytime-capital.com/api/v1"; const secret = process.env.ANYTIME_WEBHOOK_SECRET; // whsec_… const access = new Map(); // email → { plan, status, until }: use your database /** Which statuses keep the product open. */ function hasAccess(status) { return ["trialing", "active", "canceling", "past_due"].includes(status); // past_due: renewal being retried } function verify(rawBody, header) { const parts = Object.fromEntries(String(header ?? "").split(",").map((part) => part.split("="))); const expected = crypto.createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex"); return ( typeof parts.v1 === "string" && parts.v1.length === expected.length && crypto.timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected)) && Math.abs(Date.now() / 1000 - Number(parts.t)) <= 300 ); } /** Events can arrive out of order: always store the subscription as it is now. */ async function syncSubscription(id) { const res = await fetch(`${API}/subscriptions/${id}`, { headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}` }, }); const subscription = await res.json(); if (!res.ok) throw new Error(`${subscription.error.code}: ${subscription.error.message}`); access.set(subscription.customer.email, { plan: subscription.plan.id, status: subscription.status, allowed: hasAccess(subscription.status), until: subscription.currentPeriodEnd, }); } const app = express(); app.post("/webhooks/anytime", express.raw({ type: "application/json" }), async (req, res) => { const rawBody = req.body.toString("utf8"); if (!verify(rawBody, req.get("Anytime-Signature"))) return res.status(400).send("Invalid signature"); const event = JSON.parse(rawBody); if (event.type.startsWith("subscription.")) { try { await syncSubscription(event.data.id); } catch (error) { console.error(error); return res.sendStatus(500); // not 2xx, so the event is sent again later } } res.sendStatus(200); }); app.listen(3000); ``` > **A nightly safety net:** Once a day, page through `GET /api/v1/subscriptions` and run each through the same sync. It catches anything a missed event would have changed, plan changes included. ## 5. Plan changes Move a subscriber with **Change plan** from their details on the **Subscriptions** page, now (with the unused part of the period refunded) or at the next renewal. To let subscribers switch themselves, mark the plans they may move to and turn on **Subscribers can switch plans**. Plan changes aren't in the API: read `plan.id` from the subscription, never from what you remember. See [plan changes](https://anytime-capital.com/docs/subscription-discounts.md#changing-a-subscriber-s-plan). ## 6. Cancel, pause and resume from your app **From your admin screen** ```javascript // From your admin screen: end, pause or resume a subscriber (subscriptions:write). async function subscriptionAction(id, action, body) { const res = await fetch(`https://anytime-capital.com/api/v1/subscriptions/${id}/${action}`, { method: "POST", headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}`, "Idempotency-Key": `${action}-${id}-${new Date().toISOString().slice(0, 10)}`, ...(body ? { "Content-Type": "application/json" } : {}), }, body: body ? JSON.stringify(body) : undefined, }); const subscription = await res.json(); if (!res.ok) throw new Error(`${subscription.error.code}: ${subscription.error.message}`); // e.g. CANNOT_PAUSE return subscription; } await subscriptionAction("sub_2208", "cancel", { atPeriodEnd: true }); // or { atPeriodEnd: false } to end now await subscriptionAction("sub_2208", "resume"); // undoes the cancellation set for the period's end ``` The subscriber gets the same emails as when you act from the dashboard. Errors such as `409 CANNOT_PAUSE` or `409 SUBSCRIPTION_ENDED` explain themselves: see the [error code catalogue](https://anytime-capital.com/docs/error-codes.md). - [Bill usage with meters](https://anytime-capital.com/docs/bill-usage-with-meters.md): Charge for what subscribers use on top of the plan. - [Verify webhooks](https://anytime-capital.com/docs/webhook-signatures.md): The signature check in Node.js, Python and PHP. --- # Usage-based billing > Bill customers by use with Anytime Capital Payments: meters, usage events sent from your code, and usage invoiced and charged each billing period. Source: https://anytime-capital.com/docs/usage-billing · Markdown: https://anytime-capital.com/docs/usage-billing.md · All docs: https://anytime-capital.com/docs/llms-full.txt A **meter** counts something your customers use (API calls, seats, gigabytes) and bills it each period at a price per unit. Create meters on the **Meters** tab of **Subscriptions** in the dashboard, then send usage from your code. Usage is billed on top of a subscription plan's price (attach the meter to the plan), or to a customer each calendar month without a plan. - [Meters & usage events API](https://anytime-capital.com/docs/meters.md): Every endpoint and field: record events one at a time or in batches, read usage. - [Recipe: report usage](https://anytime-capital.com/docs/bill-usage-with-meters.md): A complete usage reporter with safe retries and batching. ## How usage is billed 1. Your code sends an event for each thing used: the meter's `eventName`, the customer, and a `value`. 2. Events add up per customer and meter, the meter's way: `sum` adds values, `count` counts events, `max` takes the highest value, `last` the latest. 3. When a period closes (the subscription renews or ends, or the calendar month ends for a customer billed without a plan), the period's usage less the meter's included units is priced at the unit price. 4. Each closed period becomes an invoice with one line per meter, charged to the customer's saved card. With no saved card, or if the card is declined, the invoice is emailed for them to pay. > **Rounding:** Unit prices can be fractions of a cent (`unitPriceCents` is a decimal string, so `0.25` is a quarter of a cent). Each meter's line is worked out exactly, (usage − included units) × unit price, and rounded once, half up, to whole cents. Nothing is rounded per event or per day. Usage is matched to subscriptions and customers by email address, so a customer you name by id needs an email. A customer's usage on a meter is billed once: a period that has been billed is closed, and events stamped inside it are refused. ## Sending usage well - Give every event its own `idempotencyKey`, such as your request id, so a retry after a timeout never counts twice. - Send events as they happen, or batch up to 100 a request. Each API key may make a set number of requests a minute ([Rate limits](https://anytime-capital.com/docs/rate-limits.md)), so batching is the way to send a lot. - Timestamps may be up to five minutes ahead of our clock and at most 400 days old, and never inside a period already billed. - Refusals come back with a code (`METER_NOT_FOUND`, `PERIOD_CLOSED`, `TIMESTAMP_TOO_OLD`…): see [Error codes](https://anytime-capital.com/docs/error-codes.md). --- # Bill usage with meters: send usage from your app > Send usage from your app to Anytime Capital meters in safe, idempotent batches of 100, read a customer's usage back, and let it bill on their subscription. Source: https://anytime-capital.com/docs/bill-usage-with-meters · Markdown: https://anytime-capital.com/docs/bill-usage-with-meters.md · All docs: https://anytime-capital.com/docs/llms-full.txt Usage billing charges customers for what they use. You make a meter once in the dashboard; your app sends a usage event for each billable thing; each period, usage over the included units is invoiced and charged to the customer's saved card. How periods, rounding and invoices work is on [Usage billing](https://anytime-capital.com/docs/usage-billing.md); every field is on [Meters & usage events](https://anytime-capital.com/docs/meters.md). ## 1. Make a meter 1. In **Subscriptions → Meters**, create one: a name for invoices, the `eventName` your code will send (say `api_call`), how events add up (`sum`, `count`, `max` or `last`), the unit price and any included units. 2. Attach it to a subscription plan to bill usage on top of the plan's price, or bill customers each calendar month without a plan. 3. Create an API key with `subscriptions` write for the reporter. ## 2. Send usage Usage is matched to customers by email, so send `customerEmail` (or a `customerId` whose customer has an email). Give each event an `idempotencyKey` that's unique to the thing it counts, such as the request id: resending the same event never counts it twice, so the reporter can retry freely. **usage.mjs** ```javascript // usage.mjs — Node.js 18+. record() as things happen; batches go out every 10 seconds. const API = "https://anytime-capital.com/api/v1"; const headers = { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}`, // subscriptions:write "Content-Type": "application/json", }; // In production, keep this in a table or durable queue so a restart loses nothing. const queue = []; let flushing = false; /** One billable thing. `id` must be unique to it, such as your request id: it is the event's idempotencyKey. */ export function record({ eventName, customerEmail, value = 1, id }) { queue.push({ eventName, customerEmail, value, idempotencyKey: id, timestamp: new Date().toISOString() }); } /** Sends everything queued, 100 at a time. Safe to repeat: each idempotencyKey counts once. */ export async function flush() { if (flushing) return; flushing = true; try { while (queue.length > 0) { const batch = queue.slice(0, 100); let res; try { res = await fetch(`${API}/meter-events/batch`, { method: "POST", headers, body: JSON.stringify({ events: batch }) }); } catch { return; // network: keep the queue, the next flush sends the same events again } if (res.status === 429 || res.status >= 500) return; // try again on the next tick, same events const result = await res.json(); if (!res.ok) throw new Error(`${result.error.code}: ${result.error.message}`); // the batch's own shape for (const outcome of result.data) { if (outcome.status === "refused") { // PERIOD_CLOSED, TIMESTAMP_TOO_OLD, METER_NOT_FOUND…: won't succeed on a retry. Log it and move on. console.error("usage event refused", batch[outcome.index].idempotencyKey, outcome.error.code, outcome.error.message); } } queue.splice(0, batch.length); // created and duplicate both mean it's counted } } finally { flushing = false; } } setInterval(() => flush().catch((error) => console.error(error)), 10_000); // Example: bill one API call for the customer who made it. record({ eventName: "api_call", customerEmail: "jordan@example.com", id: "req_7f3a9c" }); ``` **usage.py** ```python # usage.py — pip install requests. One event at a time, safe to retry. import os import uuid from datetime import datetime, timezone import requests API = "https://anytime-capital.com/api/v1" HEADERS = {"Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}"} def record_usage(event_name, customer_email, value=1, key=None): event = { "eventName": event_name, "customerEmail": customer_email, "value": value, "timestamp": datetime.now(timezone.utc).isoformat(), "idempotencyKey": key or str(uuid.uuid4()), # pass your own request id to make retries safe } res = requests.post(f"{API}/meter-events", headers=HEADERS, json=event, timeout=30) body = res.json() if res.status_code in (200, 201): # 200: already recorded under this key return body raise RuntimeError(f"{body['error']['code']}: {body['error']['message']}") print(record_usage("api_call", "jordan@example.com", key="req_7f3a9c")) ``` > **Send it promptly:** A timestamp more than 5 minutes ahead, more than 400 days ago, or inside a period that has already been billed is refused. Send usage as it happens, and keep the reporter's queue short. ## 3. Show customers their usage **Usage this month** ```javascript // Show a customer their usage this month (subscriptions:read). const params = new URLSearchParams({ customerEmail: "jordan@example.com", from: new Date().toISOString().slice(0, 8) + "01", // the 1st of this month, UTC }); const res = await fetch(`https://anytime-capital.com/api/v1/meters/mtr_8fKq2LmNpR4sT6vW9xYz/usage?${params}`, { headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}` }, }); const usage = await res.json(); if (!res.ok) throw new Error(`${usage.error.code}: ${usage.error.message}`); console.log(usage.quantity, usage.daily); // "1210", [{ date: "2026-10-01", quantity: "480" }, …] ``` Windows run from `from` to `to` (now, by default), at most 366 days, with one point per UTC day. What a period will cost is on the subscriber's details in the dashboard. --- # Payouts: every way to get your money > Withdraw your Anytime Capital balance to your bank, standard or instant where eligible. Local rails in 71+ countries. Source: https://anytime-capital.com/docs/payouts · Markdown: https://anytime-capital.com/docs/payouts.md · All docs: https://anytime-capital.com/docs/llms-full.txt The money your customers pay lands in your Anytime Capital Payments balance. Today: payouts to your own bank account, standard or instant where eligible, instant sweeps into an Anytime Capital business account, RTP or FedNow transfers to any US bank, same-day domestic wires, vendor payments by ACH, local rails in 71+ countries, stablecoin payouts from your balance, crypto conversion of your balance and employee cards funded from your balance, payout by payout or on your automatic schedule. ## Available today | Option | Speed | What it's for | | --- | --- | --- | | **Payouts to your bank account** | Standard, or instant where eligible | Withdraw your balance to your business bank account from Payouts whenever you like, or let automatic payouts follow your schedule. Instant delivery is offered when your account and bank are eligible, and each payout shows its expected arrival before you confirm. | | **Your Anytime Capital business account** | Instant, 24/7 | Sweep settled sales into Anytime Capital business checking in seconds, nights and weekends included, and spend it the same minute. | | **RTP and FedNow instant transfers** | Seconds, any day | Push money to any US bank account that receives instant payments, around the clock, with confirmation the moment it lands. | | **Domestic wire** | Same day | Fedwire for large or time-critical payments: closings, inventory, payroll top-ups and anything a counterparty wants by wire. | | **ACH and same-day ACH** | Same day or next day | Low-touch transfers to any US bank account for vendors, contractors and your own accounts elsewhere, scheduled or on demand. | | **Local rails in 71+ countries** | Minutes to same day | Pay suppliers in their own currency over the rail they already use (SEPA Instant, Faster Payments, PIX, SPEI, UPI, PayNow and mobile money), with SWIFT for everywhere else. | | **Stablecoins: USDC and USDT** | Minutes, including weekends | Same-day on-chain transfers from your balance to a supplier, contractor or your own wallet, with no correspondent bank in the way. | | **Crypto liquidity** | Same day | Convert balance to bitcoin, ether, solana and other assets, or crypto back to dollars, at desk-quoted rates and send it out the same day. | | **Employee and vendor cards** | Instant | Fund physical and virtual business cards straight from your payments balance, with limits per person, team or merchant. | Instant delivery is offered when your account and bank account are eligible; otherwise a payout goes at standard speed. Payouts shows the expected arrival of each payout before you confirm it, and the arrival date afterwards. ## Local payout rails in 71+ countries Pay suppliers, contractors and your own accounts abroad in their currency, over the rail they already use. These are the main corridors; SWIFT covers everywhere else. See [Global payouts](https://anytime-capital.com/business/global-payouts) for the product overview. | Market | Currency | Rail | | --- | --- | --- | | United States | USD | RTP, FedNow, ACH, wire | | Eurozone | EUR | SEPA Instant | | United Kingdom | GBP | Faster Payments | | Mexico | MXN | SPEI | | Brazil | BRL | PIX | | India | INR | UPI, NEFT, RTGS | | Singapore | SGD | PayNow, FAST | | Canada | CAD | Bank transfer | | Kenya | KES | Mobile money | | Colombia | COP | Bank transfer, Nequi | | Philippines | PHP | Bank transfer | | Nigeria | NGN | Bank transfer | | United Arab Emirates | AED | Bank transfer | | West & Central Africa | XOF / XAF | Mobile money | ## Keep your finances in one hub Payments, banking, cards and payouts run on one balance at Anytime Capital, so there is no merchant account to reconcile against a separate bank and no transfer between them to wait for. ## Where payouts are made **Payouts** in the dashboard shows your Anytime Capital Payments balance, every movement in and out, and the history of each payout with the payments that built it. Owners and admins can withdraw; other team members see the balance and history. > **Note:** Payouts are made in the dashboard only. The REST API and the MCP server can read your payouts and balance, but an API key or an AI assistant can never move money out of your balance. ## Read payouts and your balance with the API With the `payouts` read permission, your systems can follow payouts and the balance behind them, for example to reconcile deposits in your accounting: each payout's status, what reached the bank, its expected arrival and the bank trace number. `payout.paid` and `payout.failed` webhooks report each payout's outcome. Every endpoint and field is on [Payouts & balance API](https://anytime-capital.com/docs/payouts-api.md), and a nightly job that puts it together is on [Reconcile payouts](https://anytime-capital.com/docs/reconcile-payouts.md). ## Payout schedule **Settings → Payouts** shows whether your available balance is also paid out to your default payout account automatically (daily, weekly on a set weekday, or monthly on a set day) or only when you withdraw (manual), and the next scheduled date. Only cleared money is paid out, and each payout's expected arrival date is in your payout history. - Changing the schedule isn't available in the dashboard yet. Owners and admins can withdraw the available balance from **Payouts** at any time, whatever the schedule. - If money is waiting on something from you, such as finishing verification or updating payout details, Settings → Payouts says what. - To have each payout booked in QuickBooks Online or Xero as a transfer to your bank, see [Accounting sync](https://anytime-capital.com/docs/accounting-sync.md). --- # Reconcile payments and payouts nightly > A nightly job that pulls a day's payments, refunds and payouts from the Anytime Capital API, writes a CSV, matches payouts to your bank and flags problems. Source: https://anytime-capital.com/docs/reconcile-payouts · Markdown: https://anytime-capital.com/docs/reconcile-payouts.md · All docs: https://anytime-capital.com/docs/llms-full.txt Once a day, pull what happened from the API, write it down, and check it against your bank: payments in, refunds out, and payouts to your bank with their trace numbers. The script below does that for one UTC day and exits non-zero when something needs a person, so your scheduler can alert you. > **A read-only key:** Create a key with **Read only** access, or Custom with `payments`, `refunds` and `payouts` set to read. A reporting job never needs to write. See [Authentication](https://anytime-capital.com/docs/authentication.md#restricted-keys-and-permissions). ## The script **reconcile.mjs** ```javascript // reconcile.mjs — Node.js 18+. Run nightly for yesterday (UTC), or: node reconcile.mjs 2026-10-23 import { writeFile } from "node:fs/promises"; const API = "https://anytime-capital.com/api/v1"; const headers = { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}` }; const DAY_MS = 86_400_000; /** GET with retries on 429 and 5xx (reads are always safe to repeat). */ async function get(path) { for (let attempt = 1; ; attempt++) { const res = await fetch(`${API}${path}`, { headers }); const body = await res.json().catch(() => ({})); if (res.ok) return body; if ((res.status === 429 || res.status >= 500) && attempt < 4) { const wait = Number(res.headers.get("Retry-After") ?? 2 ** attempt); await new Promise((resolve) => setTimeout(resolve, wait * 1000)); continue; } throw new Error(`GET ${path}: ${body.error?.code}: ${body.error?.message}`); } } /** Every object of a list, newest first, until `stop` says we've gone back far enough. */ async function* walk(path, params = {}, stop = () => false) { let cursor = null; do { const query = new URLSearchParams({ ...params, limit: "100", ...(cursor ? { cursor } : {}) }); const page = await get(`${path}?${query}`); for (const item of page.data) { if (stop(item)) return; yield item; } cursor = page.hasMore ? page.nextCursor : null; } while (cursor); } const day = process.argv[2] ?? new Date(Date.now() - DAY_MS).toISOString().slice(0, 10); const from = new Date(`${day}T00:00:00Z`); const to = new Date(from.getTime() + DAY_MS); const inDay = (iso) => new Date(iso) >= from && new Date(iso) < to; const before = (item) => new Date(item.createdAt) < from; // Payments filter by date on the server; refunds and payouts are newest first, so stop past the day. const payments = []; for await (const payment of walk("/payments", { createdFrom: day, createdTo: to.toISOString().slice(0, 10) })) { payments.push(payment); } const refunds = []; for await (const refund of walk("/refunds", {}, before)) if (inDay(refund.createdAt)) refunds.push(refund); const payouts = []; for await (const payout of walk("/payouts", {}, before)) if (inDay(payout.createdAt)) payouts.push(payout); const balance = await get("/balance"); const paid = payments.filter((payment) => payment.paidAt !== null); const sum = (items, field) => items.reduce((total, item) => total + (item[field] ?? 0), 0); const usd = (cents) => (cents / 100).toFixed(2); const problems = [ ...paid.filter((payment) => payment.netCents === null).map((payment) => `${payment.id}: net not reported yet`), ...refunds.filter((refund) => refund.status === "failed").map((refund) => `${refund.id}: refund failed (${refund.failureReason})`), ...payouts .filter((payout) => ["failed", "reversed", "denied", "canceled"].includes(payout.status)) .map((payout) => `${payout.id}: payout ${payout.status}: ${payout.failureMessage ?? ""}`), ]; // One CSV row per record, for your spreadsheet or accounting import. const rows = [ ["type", "id", "status", "amount_cents", "net_cents", "reference", "created_at"], ...payments.map((p) => ["payment", p.id, p.status, p.amountCents, p.netCents ?? "", p.metadata?.orderId ?? p.clientReference ?? "", p.createdAt]), ...refunds.map((r) => ["refund", r.id, r.status, -r.amountCents, "", r.paymentId, r.createdAt]), ...payouts.map((p) => ["payout", p.id, p.status, p.amountCents ?? "", p.netCents ?? "", p.traceCode ?? "", p.createdAt]), ]; const csv = rows.map((row) => row.map((cell) => `"${String(cell).replace(/"/g, '""')}"`).join(",")).join("\n"); await writeFile(`reconciliation-${day}.csv`, `${csv}\n`); console.log(`${day}: ${paid.length} payments, $${usd(sum(paid, "amountCents"))} gross, $${usd(sum(paid, "netCents"))} net`); console.log(`refunds: ${refunds.length}, $${usd(sum(refunds.filter((r) => r.status !== "failed"), "amountCents"))}`); for (const payout of payouts) { console.log(`payout ${payout.id} ${payout.status} $${usd(payout.netCents ?? 0)} trace ${payout.traceCode ?? "-"} arrives ${payout.estimatedArrival ?? "-"}`); } console.log(`balance now: $${usd(balance.availableCents)} available, $${usd(balance.pendingCents)} pending, $${usd(balance.reserveCents)} reserve`); if (problems.length > 0) { console.error(`Needs a look:\n${problems.join("\n")}`); process.exitCode = 1; } ``` ## Run it every night **crontab (Node.js 20.6+ reads .env with --env-file)** ```bash # 06:15 UTC every day, for the day before 15 6 * * * cd /srv/reconcile && node --env-file=.env reconcile.mjs >> reconcile.log 2>&1 ``` ## Match payouts to your bank - Each payout's `netCents` is what reaches your bank, and `traceCode` is the trace number your bank can find the deposit by: match on the amount, and on the trace number where your bank shows it. - `estimatedArrival` says when to expect it. A payout that is `completed` but not in your bank a day after that is worth a call to [Support](https://anytime-capital.com/docs/support.md). - `payout.paid` and `payout.failed` [webhooks](https://anytime-capital.com/docs/webhooks.md) tell you as it happens, if you'd rather not wait for the night. > **What the API doesn't tell you:** The API doesn't say which payments make up each payout: **Payouts** in the dashboard shows that, payout by payout. To have every payment, card processing charge, refund and payout booked for you, use [Accounting sync](https://anytime-capital.com/docs/accounting-sync.md) with QuickBooks Online or Xero, which posts them through a clearing account so the payouts match your bank feed. --- # Accounting sync: QuickBooks Online and Xero > Post every payment, refund, dispute, card processing charge and payout from Anytime Capital Payments to QuickBooks Online or Xero, once each. Source: https://anytime-capital.com/docs/accounting-sync · Markdown: https://anytime-capital.com/docs/accounting-sync.md · All docs: https://anytime-capital.com/docs/llms-full.txt Connect QuickBooks Online or Xero in **Settings → Accounting** and your Anytime Capital Payments activity posts to your books on its own: every payment, its card processing, every refund, every dispute and every payout to your bank. Owners and admins connect, map accounts and see what posted, what didn't and why. One set of books at a time. ## What syncs Your payments balance is a **clearing account** in your books: a bank-type account that stands for the money waiting to be paid out. Every record moves money in or out of it, on the day it happened in your business's time zone, in USD. | Record | QuickBooks Online | Xero | Account it books to | | --- | --- | --- | --- | | Payment | Sales receipt deposited to the clearing account | Receive Money into the clearing account | Sales (and Sales tax, if you post it separately) | | Card processing | Expense paid from the clearing account | Spend Money from the clearing account | Card processing fees | | Refund | Refund receipt paid from the clearing account | Spend Money from the clearing account | Refunds (or Sales) | | Dispute | Expense paid from the clearing account | Spend Money from the clearing account | Disputes (or Refunds) | | Dispute fee | Expense paid from the clearing account | Spend Money from the clearing account | Card processing fees | | Dispute won | Deposit into the clearing account | Receive Money into the clearing account | Disputes (or Refunds) | | Payout | Transfer from the clearing account to your bank account | Bank transfer from the clearing account to your bank account | Your bank account | | Payout fee | Expense paid from the clearing account | Spend Money from the clearing account | Card processing fees | - Payments post as cash sales rather than an invoice and a payment, because they were paid at checkout: there is nothing receivable to open and close. Refunds and disputes are money going back out for the same reason. - Each record's memo starts with **Anytime Capital Payments** and carries its payment or payout id (`pay_…`, `wdrl_…`), plus the refund or dispute id where there is one, so you can find any record from either side. - Card processing posts once it is final: as soon as the amount is known, or three days after the payment. - A dispute that took money posts on the day it opened, with its fee; if you win, the money coming back posts on the day it was won. Inquiries that never took money, and disputes settled by a refund (the refund already posted), don't post. - QuickBooks books sales through products and services, so the sync keeps three non-taxable service items of its own: **Anytime Capital Payments sales**, **… refunds** and **… sales tax**, each pointing at the account you mapped. Automated sales tax never adds tax to them. - Payments in another currency are skipped with the reason shown. Book them from the CSV export in Reports. ## How payouts reconcile with your bank Sales come into the clearing account at their full amount; card processing, refunds and disputes go out of it as they happen. Each payout is a transfer of what actually reached your bank, from the clearing account to your bank account, with any payout fee as an expense. So the transfer equals the deposit on your bank statement — your sales less card processing, refunds and disputes since the last payout — and the clearing account's balance always equals your Anytime Capital Payments balance. | Record | Clearing account | Bank account | | --- | --- | --- | | Payment of $200.00 | +200.00 | | | Card processing on it | −6.10 | | | Refund of an earlier sale | −64.00 | | | Payout to your bank | −129.90 | +129.90 | | After the payout | 0.00 | deposit of 129.90 | > **Important:** When the deposit shows up in your bank feed, **match** it to the transfer the sync made; don't add it as new income, or the sales are counted twice. ## Connect 1. In **Settings → Accounting**, choose the first day to bring in, then **Connect** and approve access as a QuickBooks or Xero admin. 2. Check the **account mapping**: the clearing account (or **Create one for me**, which adds a bank account named Anytime Capital Payments clearing), your sales income account, the expense account for card processing and fees, the refunds and disputes accounts (or the same as sales and refunds), and the bank account payouts go to. 3. Choose how **sales tax** collected at checkout posts: inside the income line, or on its own line to an account such as Sales Tax Payable. 4. Turn on **Sync customers** to name each sale's customer: one already in your books with the same email, else a new customer named “Name (email)”. Off, sales post without a customer (in Xero, to one shared contact). 5. Press **Save mapping**. Nothing posts until you do, so nothing lands in an account you didn't choose. > **Tip:** Already importing our QuickBooks or Xero CSV exports from Reports? Choose a start date after the last file you imported, so nothing is booked twice. The books must be kept in USD. If your company's home currency is something else, syncing stays paused and says so, rather than post amounts at the wrong value. In QuickBooks, a sandbox deployment shows **Intuit sandbox** next to the name and posts only to Intuit's test companies. ## When it syncs - Every 30 minutes, oldest first, a business at a time, and whenever you press **Sync now**. A scheduled sync works through up to 100 records per business and picks up where it stopped; **Sync now** does up to 40. - **Bring in earlier activity** moves your first day back. The next syncs work through the history oldest first, and Settings → Accounting shows how many payments and refunds are left. - Each record posts once. Every create carries an idempotency key (QuickBooks' requestid), and a record whose answer never arrived is looked up in your books before it's sent again, so a sync interrupted halfway never doubles anything. Disconnecting and reconnecting the same company picks up where it left off. - We stay well under QuickBooks' limit of 500 requests a minute per company (and Xero's 60), so your other apps aren't slowed down; if QuickBooks still asks us to slow down, the sync stops and the next one continues. ## When something goes wrong | What happened | What the sync does | What to do | | --- | --- | --- | | Your books refused one record (an archived account, a closed period, a duplicate name) | Shows it under **Didn't sync** with the reason in plain words and QuickBooks' own message, and retries it automatically up to 5 times, waiting longer each time. The other records keep syncing. | Fix the cause, then **Retry** it, or **Retry all**. | | QuickBooks or Xero was slow or down | Retries the request a few times, then leaves the record to the next sync. It is never posted twice. | Nothing. | | A mapped account was deleted or made inactive | Pauses syncing before posting anything, and tells owners and admins by email and in the app. | Choose another account and **Save mapping**; syncing resumes. | | The company's subscription ended, or its home currency isn't USD | Pauses syncing and says why. | Sort it out in QuickBooks or connect a USD company, then **Check again**. | | The connection stopped working (disconnected from inside QuickBooks, the admin who connected lost access, or no sync for 100 days; 60 in Xero) | Marks it **Reconnect needed**, stops trying, and tells owners and admins by email and in the app. | **Reconnect**. Nothing already posted is posted twice. | | Syncs keep failing | Tells owners and admins after 3 failing syncs in a row, and again each day it lasts. | Open **Sync activity** to see why. | **Sync activity** in Settings → Accounting lists what didn't sync, what posted most recently (with its id in your books), and the sync history: each sync that posted, failed or paused, and each one you asked for. The card shows the last sync and the **last successful sync**. ## Disconnect **Disconnect** revokes our access at QuickBooks or Xero, deletes our copy of it and stops syncing. Disconnecting from inside QuickBooks (Apps → Disconnect) does the same once we've checked with QuickBooks that our access has ended. Everything already posted stays in your books. > **Note:** We ask only for access to your accounting data, never see your QuickBooks or Xero password, and keep the access tokens encrypted. Anytime Capital is a fintech company, not a bank. --- # Revenue Analytics: Volume, MRR and Customers > How the Analytics page counts gross and net volume, fees, refunds, disputes, MRR movement, churn, retention cohorts, lifetime revenue and the payouts forecast. Source: https://anytime-capital.com/docs/revenue-analytics · Markdown: https://anytime-capital.com/docs/revenue-analytics.md · All docs: https://anytime-capital.com/docs/llms-full.txt **Payments → Analytics** in your dashboard shows how your revenue moves over any range up to two years: volume, fees, refunds and disputes against the period before (or the same period a year earlier), MRR and ARR with what moved them, subscriber retention, customers, products and how people pay. Everyone on your team can see it, viewers included; members, admins and owners can download any chart's data as CSV. ## Ranges, days and currencies - Pick 7 days, 30 days, 90 days, 12 months or year to date, or any dates up to 731 days apart. The page's address keeps the choice, so you can bookmark or share it with your team. - Every day is your business's own, in the time zone set in **Settings → Profile**: a 7pm sale in Los Angeles counts on that day. - Charts show days for ranges up to about two months, weeks (from Monday) up to about six months, and months beyond. - Money is shown one currency at a time and is never added across currencies. Usage billing and payouts are in US dollars. - Figures are kept for two minutes, so switching charts and exporting read the same numbers. ## Volume, fees, refunds and disputes | Figure | How it is counted | | --- | --- | | Gross volume | Payments paid in the range, before fees and refunds. | | Fees | Processing fees on those payments. | | Refunds | Refunds made in the range, on the day they were made, whenever the payment was. Partial refunds count for what was refunded. | | Net volume | What the range's payments keep after fees, less the range's refunds. | | Refund rate | Refunds ÷ gross volume. | | Disputes and dispute rate | Disputes opened in the range, and that number ÷ payments. | | Stablecoin share | USDC and USDT payments as a share of gross volume. | ## MRR and ARR MRR is every paying subscription's plan price as a monthly amount (a weekly plan is 52 ÷ 12 weeks a month, a quarterly one a third, a yearly one a twelfth). ARR is MRR × 12. A subscription adds to MRR from its first payment, so a free trial adds nothing until it converts, and stops on the day it ends. Recurring discount codes lower it: a code for every payment for good, a code for several months for those months, after which MRR steps back up. A first-payment-only discount doesn't change MRR. **MRR past due** is the MRR of subscriptions whose last renewal failed and is being retried. | Movement | Meaning | | --- | --- | | New | A customer's first paying subscription. | | Expansion | A paying customer's MRR goes up: a plan upgrade, a second subscription, or a discount running out. | | Contraction | A paying customer's MRR goes down but not to zero: a downgrade or a new recurring discount. | | Churn | A customer's MRR goes to zero: their last subscription ended. | | Reactivation | A customer who had churned starts paying again. | Movements are per customer, by email address. A plan change ends the old subscription and starts the new one at the same moment, so it counts once, as expansion or contraction, never as churn plus a new subscriber. See [plan changes](https://anytime-capital.com/docs/subscription-discounts.md) for how a move is billed. ## Churn, retention and lifetime revenue - **Subscriber churn**: customers who churned in the range ÷ paying subscribers at its start. - **Revenue churn**: churned and contracted MRR ÷ MRR at the start. **Net revenue retention**: (start MRR + expansion − contraction − churn) ÷ start MRR. - **ARPA**: MRR ÷ paying subscribers. **Predicted LTV**: ARPA ÷ monthly subscriber churn, shown once anyone has churned. - **Retention cohorts**: subscribers grouped by the month they first paid (the last twelve months), with the share still paying at each month-end since. The current month counts as of today. - **Average lifetime revenue**: everything customers have paid you, less refunds, ÷ everyone who has ever paid, up to the end of the range. **Top customers** are ranked by what they paid in the range after refunds on those payments. ## Products, payment methods and countries **Top products** counts catalog items sold on payment links: units, and units × the item's price before discounts, tax and shipping. **Top payment links and invoices** ranks what customers paid through, with plans and invoices named. Payment methods and countries are shares of gross volume, by how customers paid and their billing country. ## Failed payments and recovery Failed attempts are payments declined or stopped in the range. Attempts for the same subscription, or by the same customer on the same payment link, count as one failed payment; it is **recovered** when that customer then pays for it within 14 days of the first failure. ## Usage billing Usage billed is each closed [usage](https://anytime-capital.com/docs/usage-billing.md) period's total, by the day the period ended; collected is what has been paid, by card or on its invoice. ## Payouts forecast The forecast lists your next automatic payouts from your payout schedule (**Settings → Payouts**) with a rough amount for each: your available balance goes with the first, money still clearing goes with the first payout after it clears, and sales you haven't made yet are added at your last 30 days' average. It's an estimate, not a promise; with manual payouts it shows what you can pay out now. See [payouts](https://anytime-capital.com/docs/payouts.md) for every way to get your money. ## CSV exports Every chart and table has a **CSV** button that downloads the same rows for the range and currency on screen: volume, MRR movement, retention cohorts, top customers, products, payment links and invoices, payment methods, countries, failed payments and usage. Amounts are in major units (12.50, not 1250) and rates are 0–1. Each export is recorded in your activity log. --- # Authentication and API keys > Authenticate Anytime Capital Payments API requests with a secret key: restrict each key to the resources it needs, and rotate or revoke keys safely. Source: https://anytime-capital.com/docs/authentication · Markdown: https://anytime-capital.com/docs/authentication.md · All docs: https://anytime-capital.com/docs/llms-full.txt Every request to the API at `https://anytime-capital.com/api/v1` carries a secret key from **Settings → Developers** in your dashboard, sent as a Bearer token. Keys start with `ak_live_` and act for your business with the access you give them, so they belong on your server only. **An authenticated request** ```bash curl "https://anytime-capital.com/api/v1/payments?limit=1" \ -H "Authorization: Bearer $ANYTIME_API_KEY" ``` ## Live and test keys Keys made with **Test mode** on start with `ak_test_` instead. A test key works on your test data only and never moves real money; a live key never reaches test data. Both take the same permissions and work on the same URLs, and every object says which it is in `livemode`. Test keys work before your business is verified. See [Test mode](https://anytime-capital.com/docs/test-mode.md). ## Create a key 1. Open **Settings → Developers** in the Anytime Capital dashboard. Owners and admins can create keys. 2. Choose **Create key** and name it after where it will be used, such as “Website backend” or “Claude”. 3. Choose its access: **Full access**, **Read only**, or **Custom** to pick none, read or write for each resource. 4. Optionally, give it an expiry date and the IP addresses it may be used from (see below). 5. Copy the key straight away. It is shown once, in full, and never again: we keep only a hash of it. 6. Store it as a secret environment variable on your server, for example `ANYTIME_API_KEY`. > **Keep keys on the server:** Never put a key in a browser, a mobile app, a public repository or a log. Anyone holding it can do whatever the key is allowed to: give each integration only the access it needs. If a key may have leaked, revoke it in Settings → Developers and create a new one. ## Restricted keys and permissions Each key has an access level per resource: none, read or write (write includes read). **Full access** is write for everything, including resources added to the API later; **Read only** is read for everything. Payouts, disputes and events can only be read. Owners and admins can change a key's access at any time with **Edit** in Settings → Developers: it applies from the key's next request, and the change (before and after) is in the activity log. Keys created before restricted keys existed keep full access. A request the key isn't allowed to make answers `403 PERMISSION_DENIED`, and the message names the permission it needs, such as `refunds:write`. The [OpenAPI document](https://anytime-capital.com/docs/openapi.md) lists the permission on every operation as `x-anytime-permission`, and each endpoint in the API reference shows it. | Resource | Covers | Levels | Endpoints (level needed) | | --- | --- | --- | --- | | `payments` | Payments | None, read, write | `GET /payments` (read), `GET /payments/{id}` (read) | | `refunds` | Refunds | None, read, write | `GET /refunds` (read), `POST /refunds` (write), `GET /refunds/{id}` (read) | | `payment_links` | Payment links | None, read, write | `GET /payment-links` (read), `POST /payment-links` (write), `GET /payment-links/{id}` (read), `POST /payment-links/{id}` (write), `POST /payment-links/{id}/deactivate` (write) | | `invoices` | Invoices | None, read, write | `GET /invoices` (read), `POST /invoices` (write), `GET /invoices/{id}` (read), `POST /invoices/{id}/send` (write) | | `customers` | Customers | None, read, write | `GET /customers` (read), `POST /customers` (write), `GET /customers/{id}` (read), `PATCH /customers/{id}` (write) | | `products` | Products | None, read, write | `GET /products` (read), `POST /products` (write), `GET /products/{id}` (read), `PATCH /products/{id}` (write), `POST /products/{id}/archive` (write) | | `subscriptions` | Subscriptions | None, read, write | `GET /subscription-plans` (read), `POST /subscription-plans` (write), `GET /subscription-plans/{id}` (read), `GET /subscriptions` (read), `GET /subscriptions/{id}` (read), `POST /subscriptions/{id}/cancel` (write), `POST /subscriptions/{id}/pause` (write), `POST /subscriptions/{id}/resume` (write), `GET /meters` (read), `GET /meters/{id}/usage` (read), `POST /meter-events` (write), `POST /meter-events/batch` (write) | | `discount_codes` | Discount codes | None, read, write | `GET /discount-codes` (read), `POST /discount-codes` (write), `GET /discount-codes/{id}` (read), `POST /discount-codes/{id}/deactivate` (write) | | `payouts` | Payouts | None, read | `GET /payouts` (read), `GET /payouts/{id}` (read), `GET /balance` (read) | | `disputes` | Disputes | None, read | `GET /disputes` (read), `GET /disputes/{id}` (read) | | `webhook_endpoints` | Webhook endpoints | None, read, write | `GET /webhook-endpoints` (read), `POST /webhook-endpoints` (write), `GET /webhook-endpoints/{id}` (read), `PATCH /webhook-endpoints/{id}` (write), `DELETE /webhook-endpoints/{id}` (write), `POST /test-helpers/events` (write) | | `events` | Events | None, read | `GET /events` (read), `GET /events/{id}` (read) | For example, an online shop plugin needs `payment_links` write and `payments` read, and a reporting tool needs **Read only**. Paths are under `/api/v1`. Keys for AI agents: presets per use case are on [Safe AI agents](https://anytime-capital.com/docs/ai-agent-safety.md). ## Who can use the API Live keys work once your business is verified and set up for card payments. Until then every request with a live key answers `403 NOT_ENROLLED`; finish verification in **Settings → Verification**. Test keys work straight away. ## Managing keys - A business can hold up to 10 active keys at once. Use one per integration so you can revoke one without touching the others. - Settings → Developers lists each key by name, its first characters and last four, its access, its expiry and allowed IPs, and its last request: when, from which IP address and to which endpoint. Creating, editing, rolling and revoking keys are all recorded in the activity log. - **Edit** changes a key's name, access, expiry and allowed IP addresses. Its secret stays the same; changes apply from its next request. - Revoking a key takes effect immediately: requests with it fail with `401 UNAUTHORIZED`, exactly like an unknown key. ## Rolling a key **Roll** gives a key a new secret, with the same name, access, expiry and allowed IPs, shown once like a new key. The old secret keeps working for the overlap you choose (1 hour, 24 hours, 3 days, 7 days, or not at all), then expires and is revoked automatically. Deploy the new secret within the overlap and nothing fails in between. If a key may have leaked, roll it with no overlap, or revoke it. ## Expiry A key can be given an expiry date when it is created or later, up to three years ahead. From that moment requests with it answer `401 API_KEY_EXPIRED`, naming when it expired, and it is revoked within the hour. Keys without one never expire. Your owners and admins are reminded by email and in the dashboard a week before a key expires, a day before, and once it has expired, with the key's name, mode, last use and a link to roll it. Each chooses how in **Settings → Notifications** (API key expiring). A rolled key keeps its expiry, so edit the new key's expiry to keep it working longer. Test-mode keys are reminded too, marked as test. ## IP allowlists Restrict a key to the addresses your servers call from: up to 20 IPv4 or IPv6 addresses or CIDR ranges, such as `203.0.113.7`, `203.0.113.0/24` or `2001:db8::/32`. A request from anywhere else answers `403 IP_NOT_ALLOWED` with the address we saw, before it counts towards the key's rate limit. Leave the list empty to allow any address. MCP tool calls are checked against the address of the AI tool calling the MCP server. ## Request IDs and the request log Every API response carries a `Request-Id` header, such as `req_8fK2mQ9xLp3VtR7wZc4N1bYd`. **Settings → Developers → Request log** lists every request made with your keys over the last 30 days, MCP tool calls included: method, path, status, latency, IP address, Idempotency-Key, request ID and error code. Filter by key, status, method or time, or search by request ID, idempotency key, error code or path. Request and response bodies, and keys, are never logged. Quote the request ID when you contact support; the [Node SDK](https://anytime-capital.com/docs/quickstart.md#node-sdk) puts it on every error as `requestId`. ## What a key does on your behalf Everything a key creates is attributed to the team member who created the key, and every change it makes (creating a link, an invoice or a refund, cancelling a subscription, adding a webhook endpoint…) is written to your business's activity log with the key's name, so you can always see which integration did what. A key refunds like an admin: a refund over your approval limit waits for an owner. ## Authentication errors | Status | Code | Meaning | | --- | --- | --- | | 401 | `UNAUTHORIZED` | The `Authorization` header is missing, or the key is malformed, unknown or revoked. | | 401 | `API_KEY_EXPIRED` | The key is past its expiry (or the overlap after it was rolled ended). Use its replacement. | | 403 | `IP_NOT_ALLOWED` | The key has an IP allowlist and the request came from another address; the message names it. | | 403 | `NOT_ENROLLED` | The key is valid but the business isn't verified and set up for card payments yet. | | 403 | `PERMISSION_DENIED` | The key doesn't have the permission this request needs; the message names it. | | 403 | `TEST_MODE_ONLY` | A test helper was called with a live key. Use a test key (`ak_test_…`). | | 429 | `RATE_LIMITED` | The key went over its rate limit. See [Rate limits](https://anytime-capital.com/docs/rate-limits.md). | ## The same key works for AI tools The MCP server at `https://anytime-capital.com/api/mcp` takes the same key in the same header, so Claude and other assistants act with exactly the access the REST API gives. See [Build with AI](https://anytime-capital.com/docs/build-with-ai.md). --- # Errors and retries > Every Anytime Capital Payments API error has one JSON shape: a stable code to branch on and a message for people. How to handle them and retry safely. Source: https://anytime-capital.com/docs/errors · Markdown: https://anytime-capital.com/docs/errors.md · All docs: https://anytime-capital.com/docs/llms-full.txt Successful requests answer `200`, or `201` when something was created (`202` for a refund waiting for an owner's approval). Every error, on every endpoint, has the same shape: a stable `code` to branch on and a `message` written for people. Validation messages start with the field that needs fixing. **Error response** ```json { "error": { "code": "INVALID_INPUT", "message": "amountCents: The smallest link is $1.00" } } ``` ## Status codes | Status | Codes | Retry? | | --- | --- | --- | | 400 | `INVALID_JSON`, `INVALID_INPUT`, `INVALID_IDEMPOTENCY_KEY`, `NOT_EDITABLE`, `CUSTOMER_MISMATCH` | No: fix the request | | 401 | `UNAUTHORIZED`, `API_KEY_EXPIRED` | No: fix the request | | 403 | `NOT_ENROLLED`, `PERMISSION_DENIED`, `IP_NOT_ALLOWED`, `TEST_MODE_ONLY` | No: fix the request | | 404 | `NOT_FOUND`, `METER_NOT_FOUND`, `CUSTOMER_NOT_FOUND` | No: fix the request | | 409 | `IDEMPOTENCY_KEY_REUSED`, `NOTHING_TO_SEND`, `NOT_REFUNDABLE`, `REFUND_PENDING_APPROVAL`, `TERMS_URL_REQUIRED`, `INVOICE_CLOSED`, `DUPLICATE_CUSTOMER`, `CODE_TAKEN`, `LIMIT_REACHED`, `SUBSCRIPTION_ENDED`, `ALREADY_CANCELING`, `CANNOT_PAUSE`, `NOT_PAUSED`, `CHANGE_IN_PROGRESS`, `SWITCH_SCHEDULED` | No: fix the request; Yes, same Idempotency-Key | | 422 | `IDEMPOTENCY_KEY_REUSED`, `CUSTOMER_HAS_NO_EMAIL`, `TIMESTAMP_IN_FUTURE`, `TIMESTAMP_TOO_OLD`, `PERIOD_CLOSED` | No: fix the request | | 429 | `RATE_LIMITED`, `TOO_SOON` | After `Retry-After` | | 500 | `INTERNAL_ERROR`, `REFUND_NOT_RECORDED` | Yes, same Idempotency-Key; No: fix the request | | 502 | `PROCESSOR_ERROR`, `EMAIL_FAILED` | Yes, same Idempotency-Key | | 503 | `NOT_CONFIGURED` | Yes, same Idempotency-Key | What each code means, how to fix it and which endpoints return it: [Error code catalogue](https://anytime-capital.com/docs/error-codes.md). Each endpoint in the API reference also lists its own errors. ## Handling errors - Branch on `error.code`, never on the message text, which may be reworded. - Show `error.message` to the person who can fix it; it is written to be read. - On `429`, wait the number of seconds in `Retry-After`, then retry. - On `5xx` or a network failure, retry the same request with the same `Idempotency-Key`: you get the original result if the first attempt went through. The one exception is `500 REFUND_NOT_RECORDED`: the refund went out, so look it up instead. - Don't retry other `4xx` errors unchanged: fix the request first. - Log the `Request-Id` response header (`req_…`) with every error. It finds the exact request in **Settings → Developers → Request log**, and it's what support will ask for. **A small client with retries (Node.js 18+)** ```javascript import { randomUUID } from "node:crypto"; const API = "https://anytime-capital.com/api/v1"; export async function anytime(path, { method = "GET", body, idempotencyKey } = {}) { // One key for every attempt, so a retry can never act twice. const key = method === "GET" ? null : (idempotencyKey ?? randomUUID()); for (let attempt = 1; ; attempt++) { let res; try { res = await fetch(API + path, { method, headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}`, ...(body ? { "Content-Type": "application/json" } : {}), ...(key ? { "Idempotency-Key": key } : {}), }, body: body ? JSON.stringify(body) : undefined, }); } catch (networkError) { if (attempt === 3) throw networkError; await new Promise((resolve) => setTimeout(resolve, 2 ** attempt * 1000)); continue; } const data = await res.json().catch(() => ({})); if (res.ok) return data; const code = data.error?.code; const retryable = (res.status === 429 && code === "RATE_LIMITED") || (res.status >= 500 && code !== "REFUND_NOT_RECORDED"); if (!retryable || attempt === 3) { const error = new Error(`${code}: ${data.error?.message}`); error.code = code; error.status = res.status; error.requestId = res.headers.get("Request-Id"); // find it in the request log throw error; } const wait = Number(res.headers.get("Retry-After") ?? 2 ** attempt); await new Promise((resolve) => setTimeout(resolve, wait * 1000)); } } ``` The [Node SDK](https://anytime-capital.com/docs/node-sdk.md) does all of this for you, with a typed error class per kind of error. --- # Pagination > List endpoints in the Anytime Capital Payments API return the newest objects first, a page at a time. Page with limit and the opaque nextCursor. Source: https://anytime-capital.com/docs/pagination · Markdown: https://anytime-capital.com/docs/pagination.md · All docs: https://anytime-capital.com/docs/llms-full.txt Every list endpoint returns the newest objects first, a page at a time, in one shape. Pass `limit` (1–100, default 25) and, for the next page, the previous page's `nextCursor` as `cursor`. When `hasMore` is `false` you have everything. Webhook endpoints are the exception: a business has at most 10, so that list is always one page, oldest first. **A page** ```json { "object": "list", "data": [ { "object": "payment", "id": "pay_7Hq2LmX9cR4tVb", "…": "…" } ], "hasMore": true, "nextCursor": "pay_7Hq2LmX9cR4tVb" } ``` | Field | Type | Meaning | | --- | --- | --- | | `object` | string | Always `"list"`. | | `data` | array | This page's objects, newest first. | | `hasMore` | boolean | Whether there is another page after this one. | | `nextCursor` | string or null | Pass it as `cursor` to get the next page; null on the last page. For most lists it's the id of the last object on the page; for payouts and disputes it's an opaque string. Either way, pass it back exactly as it came. | ## Fetching the next page ```bash curl "https://anytime-capital.com/api/v1/payments?limit=100&cursor=pay_7Hq2LmX9cR4tVb" \ -H "Authorization: Bearer $ANYTIME_API_KEY" ``` A cursor is only valid for the list it came from and for your own business. An empty, over-long or unknown cursor answers `400 INVALID_INPUT` with the message `Invalid cursor.`; a `limit` outside 1–100 answers the same code naming `limit`. ## Reading every page **Node.js 18+** ```javascript async function* listAll(path, params = {}) { let cursor = null; do { const query = new URLSearchParams({ ...params, limit: "100", ...(cursor ? { cursor } : {}) }); const res = await fetch(`https://anytime-capital.com/api/v1${path}?${query}`, { headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}` }, }); const page = await res.json(); if (!res.ok) throw new Error(`${page.error.code}: ${page.error.message}`); yield* page.data; cursor = page.hasMore ? page.nextCursor : null; } while (cursor); } for await (const payment of listAll("/payments", { status: "paid", createdFrom: "2026-10-01" })) { console.log(payment.id, payment.amountCents); } ``` > **Tip:** Each page is one request against your [rate limit](https://anytime-capital.com/docs/rate-limits.md). Use the largest `limit` you need and filter on the server (`status`, `createdFrom`, `createdTo`, `email`, `active`) rather than paging through everything. --- # Idempotent requests > Send an Idempotency-Key header on every POST to the Anytime Capital Payments API so a retried request never creates a second link, invoice or email. Source: https://anytime-capital.com/docs/idempotency · Markdown: https://anytime-capital.com/docs/idempotency.md · All docs: https://anytime-capital.com/docs/llms-full.txt Every write (`POST`, `PATCH` and `DELETE`) except sending usage events accepts an `Idempotency-Key` header: any unique string of 1 to 255 printable characters, such as a UUID. If a request times out or the connection drops, send it again with the same key. You get the original response back, with the header `Idempotent-Replayed: true`, instead of creating a second payment link, refunding twice or emailing an invoice twice. Each endpoint in the API reference says whether it supports the header. > **Usage events are the exception:** `POST /api/v1/meter-events` and its batch don't read the header: each event carries its own `idempotencyKey` in the body, remembered for good. See [Meters & usage events](https://anytime-capital.com/docs/meters.md). ```bash curl -X POST https://anytime-capital.com/api/v1/payment-links \ -H "Authorization: Bearer $ANYTIME_API_KEY" \ -H "Idempotency-Key: 5f1c3e0a-7b2d-4c8e-9a61-2d4b8f0e7c13" \ -H "Content-Type: application/json" \ -d '{"title": "Consulting — October", "amountCents": 12500}' ``` ## How keys behave | Situation | What you get | | --- | --- | | First request with a key | The request runs and, if it succeeds, its response is remembered with the key. | | Same key, same request, within the window | The remembered response, with `Idempotent-Replayed: true`. Nothing runs again. | | Same key, different body or endpoint | `422 IDEMPOTENCY_KEY_REUSED`. Use a new key for a new request. | | Same key while the first request is still running | The second request waits for the first, then gets its result. | | The first request failed | Errors are not remembered, so you can fix the problem and retry with the same key. | | More than 24 hours later | The key is forgotten and the request runs as new. | | Empty, too long or unprintable key | `400 INVALID_IDEMPOTENCY_KEY`. | | No header | The request simply runs. It is still recorded in your activity log. | ## Choosing keys - Generate one key per operation you mean to happen once, such as “create the payment link for order 1042”, and store it with that order before you call the API. - Reuse the stored key on every retry of that operation, including after your own process restarts. - Never reuse a key for a different operation, even for the same customer. - Keys are scoped to your business: another business using the same string doesn't collide with you. > **MCP tools take the same key:** The MCP tools that change something (create, update, refund, send, cancel, pause, resume, deactivate, archive and delete) accept an `idempotencyKey` argument and send it as this header, with the same behaviour. See [AI agents](https://anytime-capital.com/docs/build-with-ai.md). --- # Rate limits > Each Anytime Capital Payments API key may make 100 requests a minute. Over that, requests answer 429 with a Retry-After header in seconds. Source: https://anytime-capital.com/docs/rate-limits · Markdown: https://anytime-capital.com/docs/rate-limits.md · All docs: https://anytime-capital.com/docs/llms-full.txt Each API key may make 100 requests a minute, counted per key, in fixed one-minute windows. MCP tool calls count against the key they use, exactly like REST requests. Past the limit, requests answer `429` with a `Retry-After` header giving the seconds to wait. **Over the limit** ```http HTTP/1.1 429 Too Many Requests Retry-After: 42 Content-Type: application/json { "error": { "code": "RATE_LIMITED", "message": "Too many requests. Each key may make 100 requests a minute." } } ``` ## Staying under the limit - Use webhooks to learn about payments instead of polling the list endpoints. - Page with the largest `limit` you need (up to 100) and filter on the server. - Give each integration its own key: each key has its own limit, and you can revoke one without the others. - On `429`, wait `Retry-After` seconds before retrying; don't retry in a tight loop. ## Other limits | Limit | Answer | | --- | --- | | An invoice can be emailed at most once every 24 hours (Send invoice) | `429 TOO_SOON` with `Retry-After` | | A payment link's price is $1.00 to $10,000.00 | `400 INVALID_INPUT` | | An invoice totals $1.00 to $100,000.00 | `400 INVALID_INPUT` | --- # Webhooks > Get Anytime Capital Payments events by webhook: payments, refunds, disputes, invoices and subscriptions, signed with HMAC-SHA256 and retried for 24 hours. Source: https://anytime-capital.com/docs/webhooks · Markdown: https://anytime-capital.com/docs/webhooks.md · All docs: https://anytime-capital.com/docs/llms-full.txt Add an endpoint in **Settings → Developers** in your dashboard and we POST a JSON event to it as things happen. Each event wraps the same object the API returns, in `data`, so one parser handles both. `livemode` is `false` for events from [test mode](https://anytime-capital.com/docs/test-mode.md), which only ever go to endpoints added in test mode. **An event** ```json { "id": "evt_3f9a1c2b7d4e6f8a0b1c2d3e4f5a6b7c", "type": "payment.succeeded", "created": "2026-10-14T16:02:16.000Z", "livemode": true, "data": { "object": "payment", "livemode": true, "id": "pay_7Hq2LmX9cR4tVb", "status": "paid", "amountCents": 12500, "amountRefundedCents": 0, "netCents": 12088, "currency": "usd", "description": "Consulting — October", "source": { "type": "payment_link", "id": "pl_4kTz8QwN2xYb6RcD1mPa" }, "paymentLinkId": "pl_4kTz8QwN2xYb6RcD1mPa", "customer": { "name": "Jordan Lee", "email": "jordan@example.com", "country": "US" }, "card": { "brand": "visa", "last4": "4242" }, "paymentMethod": { "type": "card", "label": "Visa •••• 4242" }, "clientReference": "order_1042", "metadata": { "campaign": "fall-launch" }, "amountDetails": { "subtotalCents": 12500, "discountCents": 0, "shippingCents": 0, "taxCents": 0, "customerFeeCents": 0 }, "dispute": null, "failureMessage": null, "createdAt": "2026-10-14T16:02:11.000Z", "paidAt": "2026-10-14T16:02:15.000Z" } } ``` ## Add an endpoint 1. In **Settings → Developers**, choose **Add endpoint**. 2. Enter an `https://` URL on a public domain name (not an IP address or `localhost`). 3. Choose the events to receive, or every event, including types added later. 4. Copy the endpoint's signing secret (`whsec_…`), shown when you add the endpoint, into a server-side environment variable such as `ANYTIME_WEBHOOK_SECRET`. Owners can **Reveal** it again later. 5. Choose **Send test event** to check your endpoint answers `2xx`. Pick any event type to receive a realistic sample of it. A business can have up to 10 endpoints, each with its own secret and event selection. Owners and admins manage them. To add and remove endpoints from your own code, REST-hook style, use the [webhook endpoints API](https://anytime-capital.com/docs/webhook-endpoints.md). ## Event types | Type | `data` is a | When | | --- | --- | --- | | `payment.succeeded` | payment | A payment went through: card, bank or stablecoin. Sent once per payment. | | `payment.failed` | payment | A card payment was declined or blocked. | | `payment.refunded` | payment | All or part of a payment was refunded. Sent for each refund. | | `refund.created` | refund | A refund was made on a payment, from the dashboard, the API or an approved request. | | `refund.succeeded` | refund | A refund reached the customer's card or account. | | `refund.failed` | refund | A refund failed or was canceled; nothing was returned. | | `dispute.opened` | payment | A customer disputed a payment with their bank. | | `dispute.updated` | dispute | A dispute moved on: evidence was submitted or its status changed. | | `dispute.closed` | payment | A dispute was decided: won, lost or closed. | | `invoice.created` | invoice | An invoice was created, including each copy of a recurring invoice. | | `invoice.sent` | invoice | An invoice was emailed to its customer for the first time. | | `invoice.paid` | invoice | An invoice was paid in full by card. | | `invoice.overdue` | invoice | An unpaid invoice passed its due date. Sent once per invoice. | | `invoice.voided` | invoice | An invoice was voided and can no longer be paid. | | `subscription.created` | subscription | A customer subscribed to one of your plans. | | `subscription.updated` | subscription | A subscription changed: renewed, set to cancel, paused, resumed or past due. | | `subscription.paused` | subscription | Payments on a subscription were paused. | | `subscription.resumed` | subscription | A paused subscription started collecting payments again. | | `subscription.past_due` | subscription | A renewal payment failed and the subscription is past due. | | `subscription.canceled` | subscription | A subscription ended: canceled, expired or completed. | | `payment.review_required` | payment | A card payment was authorized and is waiting for your approval before it is captured. | | `customer.created` | customer | A customer was added, by hand, from the API or when they first paid. | | `customer.updated` | customer | A customer's details were changed. | | `payout.paid` | payout | A payout to your bank account was sent. | | `payout.failed` | payout | A payout failed, was denied or came back. | | `webhook.test` | test | Sent by “Send test event”, whatever the endpoint subscribes to. | **Send test event** can also send any type above with realistic sample data, whatever the endpoint subscribes to. Samples carry `"test": true` at the top level and ids containing `_test_` (such as `pay_test_7Hq2LmX9cR4tVb`), they never appear in the Events API, and they never count towards turning an endpoint off. Never fulfil an order from an event with `test: true`. Payment events and `dispute.opened` / `dispute.closed` carry the [payment object](https://anytime-capital.com/docs/payments.md#the-payment-object) (with `paymentLinkId`, the link's `metadata` and the customer's email); `refund.*` the [refund object](https://anytime-capital.com/docs/refunds.md#the-refund-object) and `dispute.updated` the [dispute](https://anytime-capital.com/docs/disputes.md#the-dispute-object); invoice events the [invoice object](https://anytime-capital.com/docs/invoices.md#the-invoice-object); subscription events the [subscription object](https://anytime-capital.com/docs/subscriptions.md#the-subscription-object); customer events the [customer object](https://anytime-capital.com/docs/customers.md#the-customer-object); and payout events the [payout object](https://anytime-capital.com/docs/payouts-api.md#the-payout-object). ## Delivery and retries - Respond with any `2xx` status within 10 seconds to acknowledge an event; do slow work after responding. - Anything else (another status, a redirect, a timeout) is retried after 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours and 24 hours, then marked failed. - If several events in a row fail every retry, we turn the endpoint off and email your owners and admins. Fix it, then choose **Test and turn on**: we send a test event and turn the endpoint back on only if it answers `2xx`. - Delivery is at least once and events can arrive out of order: use `id` (also in the `Anytime-Event-Id` header, the same on every retry and resend) to skip events you've handled, and re-read the object from the API when order matters. | Header | Value | | --- | --- | | `Anytime-Signature` | `t=,v1=`, with a second `v1=` during a secret rotation's overlap | | `Anytime-Event-Id` | The event's `id`: the same on every retry and resend. | | `Anytime-Event-Type` | The event's `type`. | | `Anytime-Delivery-Attempt` | Which attempt this is at delivering the event to this endpoint: `1`, then `2`, `3`… on retries and resends. | | `Content-Type` | `application/json` | ## Delivery log, resends and replays - Each endpoint's page in **Settings → Developers** shows its health (success rate and latency over 24 hours and 7 days, and its last failure) and its delivery log. Filter by status, event type and time, or search by event id or the id of the object the event is about (`pay_…`, `re_…`, `inv_…`). - Open a delivery to see every attempt: the headers we sent, the request body, the response status and the start of the response body (truncated, with anything that looks like a key or token removed), the latency and when the next retry goes out. - **Resend** sends one event again now. From **Events**, any event of the last 30 days can be sent to any of your endpoints, including one that never got it. - **Resend failed** sends every failed event in a time range again; **Replay since…** sends every event since a time again, including ones the endpoint never got because it was off. Use it after an outage. Each run queues up to 1,000 events (run it again for more), which go out oldest first within minutes. Event ids stay the same, so a handler that skips ids it has seen handles each event once. - Resends and replays are rate-limited and recorded in your activity log. ## Rotating the signing secret Owners and admins can replace an endpoint's secret at any time with **Rotate secret**, choosing how long the old one keeps working: 1 hour, 24 hours, 3 days, 7 days, or not at all. During the overlap every event is signed with both secrets, as two `v1=` signatures (the new secret's first), so your endpoint keeps verifying events whichever secret it has. Deploy the new secret before the overlap ends; after that only the new one signs. Rotating again during an overlap drops the oldest secret at once. The Node SDK, the WooCommerce plugin and the Zapier app accept any matching `v1`. ## Verifying signatures Every event carries an `Anytime-Signature` header such as `t=1760630536,v1=5257a869…`. `v1` is the hex HMAC-SHA256, keyed with your endpoint's signing secret (the whole `whsec_…` string), of the timestamp, a period and the raw request body. Compute it yourself, compare in constant time against every `v1` in the header (there are two during a secret rotation), accept the event if any matches, and reject timestamps more than 5 minutes old so a captured request can't be replayed. > **Use the raw body:** The signature covers the exact bytes we sent. Verify before parsing, and don't verify a body your framework has already parsed and re-serialised: whitespace and key order would change. **Node.js (Express)** ```javascript import crypto from "node:crypto"; import express from "express"; const app = express(); const secret = process.env.ANYTIME_WEBHOOK_SECRET; // whsec_… // Read the raw body: the signature covers the exact bytes we sent. app.post("/webhooks/anytime", express.raw({ type: "application/json" }), (req, res) => { const header = req.get("Anytime-Signature") ?? ""; // t=, then one v1= per signing secret (two during a rotation). const pairs = header.split(",").map((part) => part.trim().split("=")); const t = pairs.find(([key]) => key === "t")?.[1] ?? ""; const signatures = pairs.filter(([key]) => key === "v1").map(([, value]) => value); const body = req.body.toString("utf8"); const expected = Buffer.from( crypto.createHmac("sha256", secret).update(`${t}.${body}`).digest("hex"), ); const signed = signatures.some( (signature) => signature.length === expected.length && crypto.timingSafeEqual(Buffer.from(signature), expected), ); const fresh = Math.abs(Date.now() / 1000 - Number(t)) <= 300; if (!signed || !fresh) return res.status(400).send("Invalid signature"); const event = JSON.parse(body); // The same event can arrive more than once: skip ids you've already handled. switch (event.type) { case "payment.succeeded": // Fulfil the order for event.data.id break; case "payment.refunded": // event.data.amountRefundedCents is the total refunded so far break; } res.sendStatus(200); }); ``` The same check for Next.js, Flask, FastAPI and PHP, and how to send yourself a signed test event, is on [Verify webhooks](https://anytime-capital.com/docs/webhook-signatures.md). ## Events API Every event we emit for your business is kept for 30 days, whether or not an endpoint took it, and can be read with the `events` permission (read only). Use it to reconcile, or to catch up after your endpoint missed events: list what happened since your last processed event and handle what you haven't seen. Events are recorded once your business has a webhook endpoint or an API key. Each is the same envelope a webhook delivery carries, plus `object` and `pendingWebhooks` (endpoints still retrying it). Endpoints and parameters: [Events API reference](https://anytime-capital.com/docs/events.md). ## Handling events well - Make your handler idempotent: record each `id` you process (for example in a table with `id` as its primary key, inserted in the same transaction as the work) and skip repeats. Retries, resends and replays all carry the same `id`. - Don't rely on order: a retry can arrive after a later event. Compare the object's own state (its `status`, timestamps or `amountRefundedCents`) with what you have, or re-read it from the API, before acting. - Acknowledge fast and work after: return `2xx` once the event is safely stored, then process it in the background. - Catch up after downtime with the Events API (`GET /events?createdFrom=…`) or **Replay since…**, and let your id check drop anything you'd already handled. `Anytime-Delivery-Attempt` above `1` tells you an event is a retry. - Skip events with `test: true`: they are samples from **Send test event**. - Fulfil orders on `payment.succeeded`, never on the customer reaching your success page: a bank account payment can take days, and a customer can close the tab. - Match a payment to your order with `data.paymentLinkId` and `data.metadata` (from the link), or `data.clientReference` (from `?ref=`); `data.customer.email` is the buyer. - In Node.js, `constructEvent` in the [Node SDK](https://anytime-capital.com/docs/node-sdk.md#webhooks) verifies the signature and parses the event in one call. - On `payment.refunded`, `data.amountRefundedCents` is the total refunded so far, not this refund alone. - On `dispute.opened`, respond from **Disputes** in the dashboard before the deadline. --- # Verify webhook signatures in Node.js, Python and PHP > Copy-paste webhook handlers that verify the Anytime-Signature header on the raw body: Express, Next.js, Flask, FastAPI and PHP, and how to test them locally. Source: https://anytime-capital.com/docs/webhook-signatures · Markdown: https://anytime-capital.com/docs/webhook-signatures.md · All docs: https://anytime-capital.com/docs/llms-full.txt Every event we send carries an `Anytime-Signature` header such as `t=1760630536,v1=5257a869…`. `v1` is the hex HMAC-SHA256 of the timestamp, a period and the raw request body, keyed with your endpoint's whole signing secret (`whsec_…`). Verify it before you trust the event, in constant time, and reject timestamps more than 5 minutes old so a captured request can't be replayed. While a signing secret is being [rotated with an overlap](https://anytime-capital.com/docs/webhooks.md#rotating-the-signing-secret), the header carries two `v1` values, one per secret, newest first. Accept the event if any `v1` matches: every handler below does, so you can deploy the new secret at any point in the overlap. Code that keeps only one `v1` (for example by turning the header into a map) fails during a rotation. > **Use the raw body:** The signature covers the exact bytes we sent. Read the body before any JSON parser touches it: a body that was parsed and serialised again has different whitespace and key order, and won't verify. That's the usual reason a correct secret fails. - Keep the secret in a server-side environment variable such as `ANYTIME_WEBHOOK_SECRET`. Each endpoint has its own. - Answer any `2xx` within 10 seconds, then do slow work; anything else is retried. See [Webhooks](https://anytime-capital.com/docs/webhooks.md#delivery-and-retries). - Events can arrive more than once and out of order: store each `id` you've handled and skip repeats. ## Node.js (Express) `express.raw` keeps the body as a Buffer for this route; register it before any `express.json()` that would claim the same path. **server.mjs** ```javascript // server.mjs — npm install express; run: node server.mjs import crypto from "node:crypto"; import express from "express"; const app = express(); const secret = process.env.ANYTIME_WEBHOOK_SECRET; // whsec_… // Read the raw body: the signature covers the exact bytes we sent. app.post("/webhooks/anytime", express.raw({ type: "application/json" }), (req, res) => { const header = req.get("Anytime-Signature") ?? ""; // t=, then one v1= per signing secret (two during a rotation). const pairs = header.split(",").map((part) => part.trim().split("=")); const t = pairs.find(([key]) => key === "t")?.[1] ?? ""; const signatures = pairs.filter(([key]) => key === "v1").map(([, value]) => value); const body = req.body.toString("utf8"); const expected = Buffer.from( crypto.createHmac("sha256", secret).update(`${t}.${body}`).digest("hex"), ); const signed = signatures.some( (signature) => signature.length === expected.length && crypto.timingSafeEqual(Buffer.from(signature), expected), ); const fresh = Math.abs(Date.now() / 1000 - Number(t)) <= 300; if (!signed || !fresh) return res.status(400).send("Invalid signature"); const event = JSON.parse(body); // The same event can arrive more than once: skip ids you've already handled. switch (event.type) { case "payment.succeeded": // Fulfil the order for event.data.id break; case "payment.refunded": // event.data.amountRefundedCents is the total refunded so far break; } res.sendStatus(200); }); app.listen(3000); ``` ## Next.js (App Router) **app/api/webhooks/anytime/route.ts** ```typescript // app/api/webhooks/anytime/route.ts (Next.js App Router) import crypto from "node:crypto"; export const runtime = "nodejs"; export async function POST(request: Request) { const secret = process.env.ANYTIME_WEBHOOK_SECRET!; // whsec_… const body = await request.text(); // the raw body, exactly as sent const header = request.headers.get("Anytime-Signature") ?? ""; // t=, then one v1= per signing secret (two while a secret is rotated). const pairs = header.split(",").map((part) => part.trim().split("=")); const t = pairs.find(([key]) => key === "t")?.[1] ?? ""; const signatures = pairs.filter(([key]) => key === "v1").map(([, value]) => value ?? ""); const expected = Buffer.from(crypto.createHmac("sha256", secret).update(`${t}.${body}`).digest("hex")); const signed = signatures.some( (signature) => signature.length === expected.length && crypto.timingSafeEqual(Buffer.from(signature), expected), ); const fresh = Math.abs(Date.now() / 1000 - Number(t)) <= 300; if (!signed || !fresh) return new Response("Invalid signature", { status: 400 }); const event = JSON.parse(body); // Skip event.id if you've handled it before, then act on event.type. if (event.type === "payment.succeeded") { // Mark the order in event.data.metadata (or event.data.clientReference) paid. } return new Response(null, { status: 200 }); } ``` ## Python (Flask) **app.py** ```python # app.py — pip install flask; run: flask --app app run import hashlib import hmac import json import os import time from flask import Flask, abort, request app = Flask(__name__) SECRET = os.environ["ANYTIME_WEBHOOK_SECRET"] # whsec_… @app.post("/webhooks/anytime") def anytime_webhook(): body = request.get_data() # the raw bytes, exactly as sent header = request.headers.get("Anytime-Signature", "") # t=, then one v1= per signing secret (two while a secret is rotated). pairs = [part.strip().split("=", 1) for part in header.split(",") if "=" in part] timestamp = next((value for key, value in pairs if key == "t"), "") signatures = [value for key, value in pairs if key == "v1"] if not timestamp.isdigit() or abs(time.time() - int(timestamp)) > 300: abort(400) expected = hmac.new(SECRET.encode(), timestamp.encode() + b"." + body, hashlib.sha256).hexdigest() if not any(hmac.compare_digest(expected.encode(), signature.encode()) for signature in signatures): abort(400) event = json.loads(body) # Skip event["id"] if you've handled it before, then act on event["type"]. if event["type"] == "payment.succeeded": pass # Mark the order in event["data"]["metadata"] paid. return "", 200 ``` ## Python (FastAPI) **main.py** ```python # main.py — pip install fastapi uvicorn; run: uvicorn main:app import hashlib import hmac import json import os import time from fastapi import FastAPI, HTTPException, Request app = FastAPI() SECRET = os.environ["ANYTIME_WEBHOOK_SECRET"] # whsec_… @app.post("/webhooks/anytime") async def anytime_webhook(request: Request): body = await request.body() # the raw bytes: don't take a parsed model here header = request.headers.get("Anytime-Signature", "") # t=, then one v1= per signing secret (two while a secret is rotated). pairs = [part.strip().split("=", 1) for part in header.split(",") if "=" in part] timestamp = next((value for key, value in pairs if key == "t"), "") signatures = [value for key, value in pairs if key == "v1"] if not timestamp.isdigit() or abs(time.time() - int(timestamp)) > 300: raise HTTPException(status_code=400, detail="Invalid signature") expected = hmac.new(SECRET.encode(), timestamp.encode() + b"." + body, hashlib.sha256).hexdigest() if not any(hmac.compare_digest(expected.encode(), signature.encode()) for signature in signatures): raise HTTPException(status_code=400, detail="Invalid signature") event = json.loads(body) # Skip event["id"] if you've handled it before, then act on event["type"]. return {"received": True} ``` ## PHP **webhook.php** ```php , then one v1= per signing secret (two while a secret is rotated). $timestamp = ''; $signatures = []; foreach (explode(',', $header) as $part) { [$key, $value] = array_pad(explode('=', trim($part), 2), 2, ''); if ($key === 't') { $timestamp = $value; } elseif ($key === 'v1') { $signatures[] = $value; } } $expected = hash_hmac('sha256', $timestamp . '.' . $payload, $secret); $signed = false; foreach ($signatures as $signature) { $signed = $signed || hash_equals($expected, $signature); } if (!ctype_digit($timestamp) || abs(time() - (int) $timestamp) > 300 || !$signed) { http_response_code(400); exit('Invalid signature'); } $event = json_decode($payload, true); // Skip $event['id'] if you've handled it before, then act on $event['type']. if ($event['type'] === 'payment.succeeded') { // Mark the order in $event['data']['metadata'] paid. } http_response_code(200); ``` In Laravel, read the raw body with `$request->getContent()` and the header with `$request->header('Anytime-Signature')`, and exclude the route from CSRF protection; the check is the same. ## Node SDK With the [Node SDK](https://anytime-capital.com/docs/node-sdk.md#webhooks) (preview), `AnytimeCapital.webhooks.constructEvent(rawBody, header, secret)` verifies and parses in one call. ## Test your handler - **Send test event** in **Settings → Developers** sends a signed `webhook.test` event to a deployed endpoint, whatever events it subscribes to. - On your own machine, sign a body yourself with your secret and post it to your local server: **Sign and send a test event** ```bash SECRET="whsec_…" # your endpoint's signing secret BODY='{"id":"evt_local_1","type":"webhook.test","created":"2026-10-24T12:00:00.000Z","data":{}}' T=$(date +%s) SIG=$(printf '%s.%s' "$T" "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -hex | sed 's/^.* //') curl -X POST http://localhost:3000/webhooks/anytime \ -H "Content-Type: application/json" \ -H "Anytime-Signature: t=$T,v1=$SIG" \ -d "$BODY" ``` The URL above is the Express handler's. For the others use `http://localhost:3000/api/webhooks/anytime` (Next.js), `http://localhost:5000/webhooks/anytime` (Flask) or `http://localhost:8000/webhooks/anytime` (uvicorn), or wherever your PHP script is served. Change one character of the body after signing and your handler should answer `400`. --- # Node.js SDK (preview) > The @anytimecapital/node SDK preview: typed resources, auto-pagination, Idempotency-Key retries, typed errors and webhook verification for Node.js 18+. Source: https://anytime-capital.com/docs/node-sdk · Markdown: https://anytime-capital.com/docs/node-sdk.md · All docs: https://anytime-capital.com/docs/llms-full.txt > **Not on npm yet:** `@anytimecapital/node` is a preview: it isn't published, so you can't install it yet. Until it is, call the API with `fetch` (every [reference page](https://anytime-capital.com/docs/payments.md) shows the Node.js version of each request) or generate a client from the [OpenAPI document](https://anytime-capital.com/docs/openapi.md). This page documents its interface, which the reference's “Node SDK” samples use. The SDK wraps the REST API for Node.js 18 and later with no runtime dependencies: a typed client per resource, `for await` over every page of a list, an Idempotency-Key on every write so its own retries never act twice, typed errors, and webhook signature verification. ## Create a client ```typescript import { AnytimeCapital } from "@anytimecapital/node"; const client = new AnytimeCapital({ apiKey: process.env.ANYTIME_API_KEY!, // ak_live_…, on your server only maxRetries: 2, // retries after the first attempt, on 429, 5xx and network errors timeoutMs: 30_000, // per attempt baseUrl: "https://anytime-capital.com/api/v1", }); ``` | Resource | Methods | | --- | --- | | `client.payments` | `list`, `retrieve` | | `client.refunds` | `list`, `create`, `retrieve` | | `client.paymentLinks` | `list`, `create`, `retrieve`, `update`, `deactivate` | | `client.invoices` | `list`, `create`, `retrieve`, `send` | | `client.customers` | `list`, `create`, `retrieve`, `update` | | `client.products` | `list`, `create`, `retrieve`, `update`, `archive` | | `client.subscriptionPlans` | `list`, `create`, `retrieve` | | `client.subscriptions` | `list`, `retrieve`, `cancel`, `pause`, `resume` | | `client.discountCodes` | `list`, `create`, `retrieve`, `deactivate` | | `client.payouts` | `list`, `retrieve` | | `client.balance` | `retrieve` | | `client.disputes` | `list`, `retrieve` | | `client.webhookEndpoints` | `list`, `create`, `retrieve`, `update`, `del` | | `client.events` | `list`, `retrieve` | | `client.testHelpers.events` | `create` (test keys only: [Test helpers](https://anytime-capital.com/docs/test-helpers.md)) | Meters and usage events aren't in the SDK yet: send them with `fetch` as on [Meters & usage events](https://anytime-capital.com/docs/meters.md). ## Lists and pages ```typescript // One page… const page = await client.payments.list({ status: "paid", limit: 50 }); // …or every payment, each page fetched as you reach it. for await (const payment of client.payments.list({ createdFrom: "2026-10-01" })) { console.log(payment.id, payment.amountCents, payment.metadata.orderId); } // Or collect them (stops at 10,000 by default). const all = await client.refunds.list({ paymentId: "pay_7Hq2LmX9cR4tVb" }).toArray(); ``` ## Writes and retries Every write sends an `Idempotency-Key`: yours when you pass one, otherwise one made per call. The SDK retries `429`, `5xx` and network errors with backoff that honours `Retry-After`, always with the same key, so a retry never acts twice. Pass your own key, stored with your order, to stay safe across process restarts too. ```typescript const refund = await client.refunds.create( { paymentId: "pay_7Hq2LmX9cR4tVb", amountCents: 2500, reason: "requested_by_customer" }, { idempotencyKey: `refund-${orderId}` }, ); if (refund.status === "pending_approval") { // Over your refund approval limit: an owner approves it in the dashboard. } ``` ## Errors | Class | When | | --- | --- | | `InvalidRequestError` | `400` or `422`: fix the request; the message names the field. | | `AuthenticationError` | `401`: missing, unknown or revoked key. | | `PermissionError` | `403`: `PERMISSION_DENIED` or `NOT_ENROLLED`. | | `NotFoundError` | `404`. | | `ConflictError` | `409`: the object's state doesn't allow it. | | `RateLimitError` | `429`, after its retries. | | `ApiError` | `5xx`, after its retries. | | `ConnectionError` | No answer, after its retries (`TIMEOUT` or `CONNECTION_ERROR`). | | `SignatureVerificationError` | A webhook that didn't verify. | All extend `AnytimeCapitalError`, with `code`, `message`, `status` and `retryAfterSeconds`. Branch on `instanceof` or on `code`; every code is in the [error code catalogue](https://anytime-capital.com/docs/error-codes.md). ## Webhooks ```typescript import express from "express"; import { AnytimeCapital } from "@anytimecapital/node"; const app = express(); app.post("/webhooks/anytime", express.raw({ type: "application/json" }), (req, res) => { let event; try { event = AnytimeCapital.webhooks.constructEvent( req.body, // the raw Buffer, not parsed JSON req.header("Anytime-Signature"), process.env.ANYTIME_WEBHOOK_SECRET!, ); } catch { return res.sendStatus(400); } if (event.type === "payment.succeeded") { // event.data is the payment: match your order with data.paymentLinkId or data.metadata. } res.sendStatus(200); }); ``` `constructEvent` rejects signatures older than 5 minutes (`{ toleranceSeconds }` changes it), and `generateTestHeader(body, secret)` signs a body for your own tests. Without the SDK, verify as on [Verify webhooks](https://anytime-capital.com/docs/webhook-signatures.md). --- # Anytime CLI (preview) > The anytime CLI preview: forward test webhook events to localhost, signed like real deliveries, and trigger payment, refund and dispute test events. Source: https://anytime-capital.com/docs/cli · Markdown: https://anytime-capital.com/docs/cli.md · All docs: https://anytime-capital.com/docs/llms-full.txt > **Not on npm yet:** `@anytimecapital/cli` is a preview: it isn't published, so you can't install it yet. This page documents how it works. Until it's out, send events to a deployed endpoint with **Send test event** in test mode, or sign a test event yourself as in [Test your handler](https://anytime-capital.com/docs/webhook-signatures.md#test-your-handler). The `anytime` command line tool makes webhook work local. `anytime listen` forwards your business's events to a server on your own machine, signed exactly as our webhooks sign them, so the verification code you ship is the code you test. `anytime trigger` sends a test event on demand. It needs Node.js 18 or later and nothing else. ## Install **Once it's published** ```bash npm install -g @anytimecapital/cli anytime --help ``` ## Log in with a test key Turn on test mode in the dashboard, create a key in **Settings → Developers** and paste it when `anytime login` asks. The CLI checks it with the API and saves it in your config directory, readable only by you (`0600`). It saves test keys only: a live key is refused, so one never sits on disk. ```bash anytime login # or, from a secret manager: echo "$ANYTIME_TEST_KEY" | anytime login ``` | System | Config file | | --- | --- | | macOS and Linux | `~/.config/anytime/config.json` (or `$XDG_CONFIG_HOME/anytime/`) | | Windows | `%APPDATA%\anytime\config.json` | | Anywhere | `$ANYTIME_CONFIG_DIR/config.json` when it's set | The key needs **Events (read)** for `listen` and **Webhook endpoints (write)** for `trigger`; a Full access test key has both. `anytime logout` forgets the key; `anytime logout --all` deletes the file. ## Forward events to localhost Webhook endpoints must be public `https://` URLs, so a laptop can't be one. `anytime listen` closes that gap from your side: it reads the [Events API](https://anytime-capital.com/docs/events.md) every two seconds and POSTs each new event to the URL you give it, printing the status your server answered. ```text $ anytime listen --forward-to http://localhost:3000/api/webhooks Ready. Forwarding test events to http://localhost:3000/api/webhooks Signing secret: whsec_… Set it as your app's webhook secret (e.g. ANYTIME_WEBHOOK_SECRET); every request carries Anytime-Signature. Events: all. Checking every 2s with ak_test_Nw4x…9QzA. Ctrl+C to stop. 17:00:20 payment.succeeded evt_b88d883077c9592700664bd6ffe508e0 → 200 OK (7 ms) 17:00:20 refund.created evt_a4c3cf027345ff32992cea8804aba126 → 200 OK (3 ms) 17:00:24 invoice.paid evt_817e0b1fe9b62b9626450752e9f44650 → 500 Internal Server Error (12 ms) ^C Stopped. 2 delivered, 1 failed. ``` - **The same request as a real delivery:** a JSON POST of the event (`id`, `type`, `created`, `livemode`, `data`) with the `Anytime-Signature`, `Anytime-Event-Id`, `Anytime-Event-Type` and `Anytime-Delivery-Attempt` headers. A `2xx` within 10 seconds counts as delivered; redirects aren't followed. - **Signed with a local secret:** `listen` makes a `whsec_…` secret the first time and keeps it in your config, so it stays the same between runs. Set it as your app's webhook secret while you develop: `export ANYTIME_WEBHOOK_SECRET=$(anytime listen --print-secret)`. - **Only new events:** anything that happened before `listen` started is skipped. Each event is forwarded once; a failed one isn't retried, so fix your handler and trigger another. - **Only what you ask for:** `--events payment.succeeded,refund.*` takes event types and families, as the Events API's `type` filter does. - **Only your events, never your key:** the key goes to the API; your server gets the event and nothing else. - **Test mode by default:** a live key is refused unless you pass `--live` with it (`anytime listen --live --api-key ak_live_…`). Live events then go only to an `https://` URL or one on your own machine. - **Light on your rate limit:** one request every two seconds is 30 of your key's 100 a minute. When the API asks it to slow down it waits the `Retry-After` it gives, and it backs off while the network is down. Without `--forward-to`, `listen` just prints each event as it happens. With `--json` it prints one JSON object per line, for scripts. ## Trigger test events `anytime trigger ` sends one test event through the [test helpers](https://anytime-capital.com/docs/test-helpers.md), built from your own test data exactly as the real event would be, with `livemode: false`. It goes to your test webhook endpoints and the Events API, so a running `anytime listen` forwards it too. Nothing else changes and no money moves. ```text $ anytime trigger payment.succeeded ✓ Sent payment.succeeded evt_9b2c4d6e8f0a1b3c5d7e9f1a2b4c6d8e about pay_7Hq2LmX9cR4tVb. Queued for 1 test webhook endpoint; a running `anytime listen` forwards it too. ``` | Event | What it carries | | --- | --- | | `payment.succeeded` | Payment succeeded | | `payment.failed` | Payment failed | | `refund.created` | Refund created | | `dispute.opened` | Dispute opened | | `invoice.paid` | Invoice paid | | `subscription.updated` | Subscription updated | Each is about your newest test object that fits, or the one `--object pay_…` names. With nothing to send it about yet, it says so: pay a test link with a [test card](https://anytime-capital.com/docs/test-mode.md#test-cards) or load sample data first. ## Check your key ```text $ anytime whoami Key: ak_test_Nw4x…9QzA (test mode) From: /home/you/.config/anytime/config.json API: https://anytime-capital.com/api/v1 Signing secret: whsec_…rEAL Status: works, and can read events ``` ## Open the dashboard and docs | Command | Opens | | --- | --- | | `anytime open dashboard` | Your business dashboard | | `anytime open developers` | API keys and webhook endpoints | | `anytime open events` | The event log, with each delivery | | `anytime open logs` | The API request log | | `anytime open docs` | These docs | | `anytime open cli` | This page | | `anytime open postman` | The [Postman collection](https://anytime-capital.com/docs/postman.md) | | `anytime open test-mode` | [Test mode](https://anytime-capital.com/docs/test-mode.md) | ## Commands and options | Command | Does | | --- | --- | | `anytime login` | Checks a test key and saves it. `--api-key`, `--base-url`, `--no-verify`. | | `anytime logout` | Forgets the key; `--all` deletes the config file. | | `anytime whoami` | The key in use (masked), where it comes from, and whether it works. | | `anytime listen` | `--forward-to `, `--events `, `--interval `, `--live`, `--print-secret`. | | `anytime trigger ` | `--object `; `--list` lists the events. | | `anytime open ` | `--print` prints the URL instead. | - Every command takes `--help`, `--json` (one JSON object per line) and `--no-color` (or `NO_COLOR=1`). - `--api-key` or `ANYTIME_CLI_API_KEY` use a key without saving it; `ANYTIME_BASE_URL` points the CLI at another copy of the site. - Exit codes: `0` done, `1` failed (the message says why, with the API's request id), `2` typed wrong. ## What it doesn't do yet - **Stream the request log.** The API request log can't be read with a key yet; see it in **Settings → Developers → Request log** (`anytime open logs`). - **Retry a failed forward.** Trigger the event again, or resend it to a deployed endpoint from the event log. - **Send events that need a real payment.** `trigger` offers only the events the test helpers can build; the rest come from using test mode, such as paying a link with a test card. - [Verify signatures](https://anytime-capital.com/docs/webhook-signatures.md): The handler `anytime listen` forwards to, in Node.js, Python and PHP. - [Test mode](https://anytime-capital.com/docs/test-mode.md): Test keys, test cards, sample data and test events. - [Events API](https://anytime-capital.com/docs/events.md): What `anytime listen` reads: every event from the last 30 days. --- # Postman collection > Import every Anytime Capital Payments API endpoint into Postman, Insomnia or Bruno from one link, with your test key as a variable and example bodies. Source: https://anytime-capital.com/docs/postman · Markdown: https://anytime-capital.com/docs/postman.md · All docs: https://anytime-capital.com/docs/llms-full.txt Every endpoint of the API, 53 requests in all, as a Postman collection (v2.1) at `https://anytime-capital.com/api/docs/postman.json`. It's built from the [OpenAPI document](https://anytime-capital.com/docs/openapi.md) with the reference's worked examples, so it always matches the API: real example ids, query parameters and bodies, and a saved example answer for each request. ## Import it into Postman 1. In Postman, choose **Import** and paste the collection's link: `https://anytime-capital.com/api/docs/postman.json`. 2. Open the collection **Anytime Capital Payments API**, then its **Variables** tab. 3. Set `apiKey` to a test key (`ak_test_…`) from **Settings → Developers** with test mode on, and save. 4. Open **Payments → List payments** and send it. There's no **Run in Postman** button yet: importing by link gives you the same collection. Import again whenever you want the newest endpoints; your variables live in the copy you edit, so set the key again on a fresh import. ## Insomnia, Bruno and others Insomnia and Bruno import Postman collections: use their import from a URL (or download the file and import that), then set the bearer token to your test key. Clients that read OpenAPI can import the [OpenAPI document](https://anytime-capital.com/docs/openapi.md) directly instead. ## What's in it | Part | What it does | | --- | --- | | Folders | One per area of the API: Payments, Refunds, Payment links, Invoices and the rest, as in the reference. | | `{{apiKey}}` | Sent as `Authorization: Bearer …` on every request but the public OpenAPI document. | | `{{baseUrl}}` | `https://anytime-capital.com/api/v1`, the same in test and live mode: your key decides which. | | `Idempotency-Key` | `{{$guid}}` on every write that takes one, a new UUID per send. Set your own value to see a retry within 24 hours return the first answer. | | Query parameters | Every one the endpoint takes; the example's are switched on, the rest are there to switch on. | | Examples | Each request's example answer, saved beside it. | | Version | Built from OpenAPI document `2026-10-24`. See the [Changelog](https://anytime-capital.com/docs/changelog.md) for what changed. | > **Start in test mode:** With a test key, every request reaches your test data only and no real money moves. Switch `apiKey` to a live key only when you mean to act on real payments, and keep live keys out of shared workspaces. - [OpenAPI document](https://anytime-capital.com/docs/openapi.md): The same API as OpenAPI 3.1, for code generators. - [Test mode](https://anytime-capital.com/docs/test-mode.md): Test keys, test cards and test events. - [Anytime CLI](https://anytime-capital.com/docs/cli.md): Forward test webhook events to your own machine. --- # Use Anytime Capital with AI agents > Connect Claude, Cursor, VS Code and your own agents to Anytime Capital Payments through the MCP server, give them the docs as Markdown, and keep them safe. Source: https://anytime-capital.com/docs/build-with-ai · Markdown: https://anytime-capital.com/docs/build-with-ai.md · All docs: https://anytime-capital.com/docs/llms-full.txt AI assistants work with Anytime Capital Payments in two ways. **Coding assistants** read these docs as Markdown and write your integration. **Agents** connect to the MCP server and act on your account directly (create payment links, draft and send invoices, look up payments, refunds and payouts) with exactly the access the API key you give them allows. - [Safe AI agents](https://anytime-capital.com/docs/ai-agent-safety.md): Restricted keys per use case, approval limits and what agents can never do. - [Prompt library](https://anytime-capital.com/docs/ai-prompt-library.md): Tested prompts for invoicing, reconciling, refunds and subscriptions. - [Build a custom agent](https://anytime-capital.com/docs/mcp-custom-agents.md): The MCP server from your own code: TypeScript, Python and the Claude API. - [Integration prompt](https://anytime-capital.com/docs/build-with-ai.md#the-integration-prompt): One prompt that has a coding assistant build the whole integration. ## Which client, which setup | Client | Connects to the MCP server | How | | --- | --- | --- | | Claude Code | Yes, over HTTP with your key | [One command](#claude-code) | | Claude Desktop | Yes, through the `mcp-remote` bridge | [Config file](#claude-desktop) | | Cursor and other remote-HTTP clients | Yes, over HTTP with your key | [mcp.json](#cursor-and-other-clients-with-remote-http-servers) | | VS Code (GitHub Copilot agent mode) | Yes, over HTTP, key asked for once | [.vscode/mcp.json](#vs-code) | | ChatGPT | Not yet | [Use the docs instead](#chatgpt) | | Your own agent | Yes: any MCP client, or the Claude API's MCP connector | [Custom agents](https://anytime-capital.com/docs/mcp-custom-agents.md) | ## Give your assistant the docs | URL | What it is | | --- | --- | | [https://anytime-capital.com/docs/llms-full.txt](https://anytime-capital.com/docs/llms-full.txt) | Every docs page in one Markdown file. The best single link to hand an assistant. | | [https://anytime-capital.com/docs.md](https://anytime-capital.com/docs.md) | The docs overview, with links to every page. | | `https://anytime-capital.com/docs/.md` | Any page on its own: add `.md` to its URL, e.g. [payment-links.md](https://anytime-capital.com/docs/payment-links.md). | | [https://anytime-capital.com/api/v1/openapi.json](https://anytime-capital.com/api/v1/openapi.json) | The API as OpenAPI 3.1, for exact request and response shapes. See [OpenAPI](https://anytime-capital.com/docs/openapi.md). | | [https://anytime-capital.com/llms.txt](https://anytime-capital.com/llms.txt) | The map of the whole Anytime Capital site for AI tools. | Every docs page also has **Copy page** (its Markdown), **View as Markdown**, **Copy prompt for AI** (a prompt for that page's task, pointing at its Markdown) and **Open in Claude** (the same prompt in a new Claude conversation). ## The integration prompt Paste this into Claude Code, Cursor or any coding assistant, in your project. It works with any language or framework. **Integrate Anytime Capital Payments into my app** ```text Integrate Anytime Capital Payments into this app so it can take payments. First read the documentation: https://anytime-capital.com/docs/llms-full.txt (Each page is also available on its own as Markdown, for example https://anytime-capital.com/docs/payment-links.md and https://anytime-capital.com/docs/webhooks.md.) Requirements: 1. Work out the stack from this repository (language, framework, where server code runs, how secrets and environment variables are handled) and follow its existing patterns. 2. Read the secret API key from a server-side environment variable named ANYTIME_API_KEY (it starts with ak_live_). Never send it to the browser or a mobile app, never log it, never commit it. 3. Call the REST API at https://anytime-capital.com/api/v1 from server code only, with the header "Authorization: Bearer ". Amounts are integer cents in USD. 4. To take a payment for an order, create a payment link (POST /api/v1/payment-links) with "singleUse": true and the order id in "metadata", save the link id on the order, and send the customer to the link's "url". To bill a known customer with line items and a due date, create an invoice (POST /api/v1/invoices) instead. 5. Send an Idempotency-Key header on every POST: generate it once per operation (a UUID), store it with the order, and reuse it on retries so a retry never creates a second link or invoice. 6. Add a webhook endpoint that reads the raw request body and verifies the Anytime-Signature header: v1 is the hex HMAC-SHA256 of "." keyed with the whsec_ signing secret from ANYTIME_WEBHOOK_SECRET; compare in constant time and reject timestamps more than 300 seconds old. Skip event ids it has already processed. On payment.succeeded, mark the order in data.metadata paid; also handle payment.failed, payment.refunded and dispute.opened. 7. Handle API errors by error.code (never by message text), show error.message where a person can fix it, and wait for Retry-After on 429. 8. Use only endpoints and fields the docs describe. If something I need isn't in the docs, tell me instead of guessing. When you're done, list the environment variables I must set, the webhook URL to register in https://anytime-capital.com/dashboard/business/settings/developers (Settings → Developers → Add endpoint, event payment.succeeded and the others you handle), and how to test the flow end to end in test mode (an ak_test_ key, a test card, and the event simulator at POST /api/v1/test-helpers/events) before switching to a live key. ``` ## Connect the MCP server The MCP server at `https://anytime-capital.com/api/mcp` lets an assistant call the Payments API as tools. It speaks MCP over Streamable HTTP and takes the same API key as the REST API, in the same `Authorization` header, so the assistant has exactly the access the key gives: same validation, same [rate limit](https://anytime-capital.com/docs/rate-limits.md), same [idempotency](https://anytime-capital.com/docs/idempotency.md), same activity log. > **Make a dedicated, restricted key:** Create a key just for your assistant in **Settings → Developers** (name it “Claude”, say), with only the access its job needs: **Read only** for questions and reports. Then you can revoke it on its own. Keep config files that contain the key out of version control. Presets per use case are on [Safe AI agents](https://anytime-capital.com/docs/ai-agent-safety.md). ### Claude Code ```bash claude mcp add --transport http anytime-capital https://anytime-capital.com/api/mcp --header "Authorization: Bearer ak_live_..." ``` Add `--scope user` to use it in every project. Run `claude mcp list` to check it's connected, or `/mcp` inside a session. To share the server with a team without sharing a key, commit a `.mcp.json` that reads the key from each person's environment: **.mcp.json** ```json { "mcpServers": { "anytime-capital": { "type": "http", "url": "https://anytime-capital.com/api/mcp", "headers": { "Authorization": "Bearer ${ANYTIME_API_KEY}" } } } } ``` ### Claude Desktop Claude Desktop's custom connectors don't send an API key header, which this server needs, so connect it through the open-source `mcp-remote` bridge, which needs Node.js. Open **Settings → Developer → Edit Config**, add the server, and restart Claude Desktop: **claude_desktop_config.json** ```json { "mcpServers": { "anytime-capital": { "command": "npx", "args": [ "-y", "mcp-remote", "https://anytime-capital.com/api/mcp", "--header", "Authorization:${AUTH_HEADER}" ], "env": { "AUTH_HEADER": "Bearer ak_live_..." } } } } ``` ### Cursor and other clients with remote HTTP servers Clients that connect to remote MCP servers over HTTP with custom headers take a config like this (in Cursor, `~/.cursor/mcp.json`, or `.cursor/mcp.json` in a project): **mcp.json** ```json { "mcpServers": { "anytime-capital": { "url": "https://anytime-capital.com/api/mcp", "headers": { "Authorization": "Bearer ak_live_..." } } } } ``` ### VS Code In VS Code with GitHub Copilot, add the server to `.vscode/mcp.json` in your project. VS Code asks for the key the first time and stores it securely, so the file holds no secret and can be committed. Use the tools from Copilot Chat in **Agent** mode. **.vscode/mcp.json** ```json { "inputs": [ { "type": "promptString", "id": "anytime-api-key", "description": "Anytime Capital API key (ak_live_…)", "password": true } ], "servers": { "anytime-capital": { "type": "http", "url": "https://anytime-capital.com/api/mcp", "headers": { "Authorization": "Bearer ${input:anytime-api-key}" } } } } ``` ### ChatGPT As of October 2026, ChatGPT can't connect to this MCP server. Its developer mode (a beta) adds custom MCP servers, but signs in to them with OAuth or not at all, and has no way to send the API key this server needs. We don't list an app in ChatGPT's app directory either: OpenAI's app guidelines don't allow apps that move money, so a listed app would be limited to reading and drafting. - **Write code with it:** give ChatGPT [llms-full.txt](https://anytime-capital.com/docs/llms-full.txt) and the integration prompt above. - **Draft invoices with it:** ask it to draft line items, tax and due dates, then create them in the dashboard or with a connected agent. The [prompt library](https://anytime-capital.com/docs/ai-prompt-library.md#draft-without-connecting) has prompts for this. - **Act on your account:** use Claude Code, Claude Desktop, Cursor, VS Code or [your own agent](https://anytime-capital.com/docs/mcp-custom-agents.md). ## Tools 48 tools, one per REST endpoint except usage billing's (meters and usage events, which your own code sends over [REST](https://anytime-capital.com/docs/meters.md)) and the OpenAPI document, generated from the server's own tool table. `send_test_event` is the test-mode [event simulator](https://anytime-capital.com/docs/test-helpers.md): it works only with an `ak_test_` key, and a live key gets `403 TEST_MODE_ONLY`. Each tool's arguments are the endpoint's fields (see the [API reference](https://anytime-capital.com/docs/payments.md)), plus `id` for one object and `idempotencyKey` on tools that change something. Results are the API's JSON objects; errors come back as `{ status, error: { code, message } }` with `isError: true`, so the assistant can read the message and fix the call. | Tool | What it does | REST endpoint | Key permission | Effect | | --- | --- | --- | --- | --- | | `list_payments` | List the business's payments (cards, card wallets and bank debits on payment links, invoices and subscriptions), newest first. Filter by status and by creation time. | `GET /api/v1/payments` | `payments:read` | Reads | | `get_payment` | Retrieve one payment by its id (pay_…): status, amounts in cents, net after fees, customer, payment method, metadata and any dispute. | `GET /api/v1/payments/{id}` | `payments:read` | Reads | | `list_payment_links` | List the business's payment links, newest first, each with its pay page url and the choices it offers buyers. Links behind invoices and subscription plans are not listed. | `GET /api/v1/payment-links` | `payment_links:read` | Reads | | `create_payment_link` | Create a hosted payment page for a single price and get back its url to share by text, email, DM or QR code. Customers pay by card, Apple Pay or Google Pay where available. | `POST /api/v1/payment-links` | `payment_links:write` | Changes | | `get_payment_link` | Retrieve one payment link by its id (pl_…), with its url, price, settings and buyer choices. | `GET /api/v1/payment-links/{id}` | `payment_links:read` | Reads | | `update_payment_link` | Change a payment link's title, description, what customers see after paying, terms consent, metadata, checkout questions, tips, quantity or shipping address. Send only what changes. The price can't change: create a new link instead. | `POST /api/v1/payment-links/{id}` | `payment_links:write` | Changes | | `deactivate_payment_link` | Stop a payment link taking payments. Anyone who opens it sees that it's closed; payments already made are kept. There is no API to reactivate it. Deactivating a link that is already off returns it unchanged. | `POST /api/v1/payment-links/{id}/deactivate` | `payment_links:write` | Changes, can't be undone | | `list_invoices` | List the business's invoices, newest first, with totals, what is paid and still owed, due date and pay page url. | `GET /api/v1/invoices` | `invoices:read` | Reads | | `create_invoice` | Create an invoice from line items; totals are calculated for you. By default it is emailed to the customer straight away (send: false to hold it). The customer pays by card at the invoice's url. | `POST /api/v1/invoices` | `invoices:write` | Changes; reaches people outside your business | | `get_invoice` | Retrieve one invoice by its id (inv_…): status, overdue, line items, totals, amount paid, balance and pay page url. | `GET /api/v1/invoices/{id}` | `invoices:read` | Reads | | `send_invoice` | Email an invoice to its customer: the invoice itself the first time, a reminder after that. At most once every 24 hours per invoice, and only while something is still owed. | `POST /api/v1/invoices/{id}/send` | `invoices:write` | Changes; reaches people outside your business | | `list_customers` | List the business's saved customers, newest first, or find one by email address. | `GET /api/v1/customers` | `customers:read` | Reads | | `create_customer` | Add a customer: name, and optionally email, phone, company, notes, billing address and tax ID. One customer per email. | `POST /api/v1/customers` | `customers:write` | Changes | | `get_customer` | Retrieve one saved customer by id (cus_…). | `GET /api/v1/customers/{id}` | `customers:read` | Reads | | `update_customer` | Change a customer's details. Send only what changes; null clears an optional field. | `PATCH /api/v1/customers/{id}` | `customers:write` | Changes | | `list_refunds` | List refunds, newest first, optionally for one payment or by status. status=pending_approval lists refunds waiting for an owner's approval. | `GET /api/v1/refunds` | `refunds:read` | Reads | | `get_refund` | Retrieve one refund (re_…) or one that needed an owner's approval (re_appr_…). | `GET /api/v1/refunds/{id}` | `refunds:read` | Reads | | `create_refund` | Refund all or part of a payment (leave amountCents out to refund everything left). The money goes back to the customer's card and can't be taken back. Over the business's approval limit nothing is refunded: the result has status pending_approval until an owner approves it in the dashboard. | `POST /api/v1/refunds` | `refunds:write` | Changes, can't be undone; reaches people outside your business | | `list_products` | List the product catalog, newest first; active=false for archived products. | `GET /api/v1/products` | `products:read` | Reads | | `create_product` | Add a product to the catalog: name and price in cents, optionally description, SKU and whether it ships. | `POST /api/v1/products` | `products:write` | Changes | | `get_product` | Retrieve one product by id (prod_…). | `GET /api/v1/products/{id}` | `products:read` | Reads | | `update_product` | Change a product's name, description, price, SKU or physical flag. Links already selling it keep their price. | `PATCH /api/v1/products/{id}` | `products:write` | Changes | | `archive_product` | Archive a product: it leaves the catalog. Nothing is deleted; it can be restored in the dashboard. | `POST /api/v1/products/{id}/archive` | `products:write` | Changes, can't be undone | | `list_subscription_plans` | List subscription plans, newest first; active=false for archived plans. | `GET /api/v1/subscription-plans` | `subscriptions:read` | Reads | | `create_subscription_plan` | Create a subscription plan: title, price in cents and interval (week, month, quarter, year), optionally a free trial. Customers subscribe at its url. | `POST /api/v1/subscription-plans` | `subscriptions:write` | Changes | | `get_subscription_plan` | Retrieve one subscription plan by id (sp_…). | `GET /api/v1/subscription-plans/{id}` | `subscriptions:read` | Reads | | `list_subscriptions` | List subscribers, newest first, by status, plan or customer email. | `GET /api/v1/subscriptions` | `subscriptions:read` | Reads | | `get_subscription` | Retrieve one subscription by id (sub_…). | `GET /api/v1/subscriptions/{id}` | `subscriptions:read` | Reads | | `cancel_subscription` | Cancel a subscription at the end of the period paid for (default) or now (atPeriodEnd: false). The subscriber is emailed. | `POST /api/v1/subscriptions/{id}/cancel` | `subscriptions:write` | Changes, can't be undone; reaches people outside your business | | `pause_subscription` | Pause payments on an active or trialing subscription until it is resumed. | `POST /api/v1/subscriptions/{id}/pause` | `subscriptions:write` | Changes, can't be undone | | `resume_subscription` | Resume a paused subscription, or undo a cancellation set for the period's end. | `POST /api/v1/subscriptions/{id}/resume` | `subscriptions:write` | Changes | | `list_discount_codes` | List discount codes, newest first, with whether each is active, expired or used up. | `GET /api/v1/discount-codes` | `discount_codes:read` | Reads | | `create_discount_code` | Create a discount code: a percentage (basis points) or a fixed amount in cents off, optionally with a use limit and an expiry. | `POST /api/v1/discount-codes` | `discount_codes:write` | Changes | | `get_discount_code` | Retrieve one discount code by id (dc_…). | `GET /api/v1/discount-codes/{id}` | `discount_codes:read` | Reads | | `deactivate_discount_code` | Stop a discount code being accepted at checkout. It can be brought back in the dashboard. | `POST /api/v1/discount-codes/{id}/deactivate` | `discount_codes:write` | Changes, can't be undone | | `list_payouts` | List payouts to the business's bank, newest first. | `GET /api/v1/payouts` | `payouts:read` | Reads | | `get_payout` | Retrieve one payout by id (wdrl_…). | `GET /api/v1/payouts/{id}` | `payouts:read` | Reads | | `get_balance` | The business's balance in cents: available to pay out, pending and in reserve. | `GET /api/v1/balance` | `payouts:read` | Reads | | `list_disputes` | List disputes on the business's payments, newest first, optionally by status. Each carries its evidence pack as `evidence` (status, strength, whether we advise accepting, whether the PDF is attached, when it was or will be submitted), or null. Responding happens in the dashboard. | `GET /api/v1/disputes` | `disputes:read` | Reads | | `get_dispute` | Retrieve one dispute by id (dspt_…), with its evidence pack's status as `evidence` (null when there is none). | `GET /api/v1/disputes/{id}` | `disputes:read` | Reads | | `list_webhook_endpoints` | List the business's webhook endpoints. | `GET /api/v1/webhook-endpoints` | `webhook_endpoints:read` | Reads | | `create_webhook_endpoint` | Send events (payment.succeeded, refund.created…) to an https URL. Returns the signing secret once. | `POST /api/v1/webhook-endpoints` | `webhook_endpoints:write` | Changes; reaches people outside your business | | `get_webhook_endpoint` | Retrieve one webhook endpoint by id (we_…). | `GET /api/v1/webhook-endpoints/{id}` | `webhook_endpoints:read` | Reads | | `update_webhook_endpoint` | Change a webhook endpoint's url, events or description, or switch it off and on. | `PATCH /api/v1/webhook-endpoints/{id}` | `webhook_endpoints:write` | Changes; reaches people outside your business | | `delete_webhook_endpoint` | Delete a webhook endpoint and its delivery log. Events stop at once. | `DELETE /api/v1/webhook-endpoints/{id}` | `webhook_endpoints:write` | Changes, can't be undone | | `list_events` | List the business's events from the last 30 days (payment.succeeded, refund.created, invoice.paid…), newest first: by type (payment.* for a family), time window or the object they're about (objectId). Use it to reconcile, or to catch up on events a webhook endpoint missed. | `GET /api/v1/events` | `events:read` | Reads | | `get_event` | Retrieve one event by id (evt_…), the id webhook deliveries carry. | `GET /api/v1/events/{id}` | `events:read` | Reads | | `send_test_event` | Test mode only: with a test-mode key (ak_test_…), send the business's test webhook endpoints one event (payment.succeeded, payment.failed, refund.created, dispute.opened, invoice.paid or subscription.updated) marked livemode: false, built from one of its test objects (objectId, or the newest that fits). Nothing else changes and no money moves. A live key gets 403 TEST_MODE_ONLY. | `POST /api/v1/test-helpers/events` | `webhook_endpoints:write` | Changes; reaches people outside your business | > **What an assistant can't do:** No tool moves money out of your balance: payouts and the balance can only be read, and payouts are made in the dashboard. Responding to disputes, changing a subscriber's plan, creating meters, and settings stay in the dashboard too. A refund over your approval limit waits for an owner. Tools that change data are marked as such (`readOnlyHint: false`, and `destructiveHint` on refunds, cancellations, archiving and deleting), so clients like Claude can ask you before running them. ## Protocol details | Detail | Value | | --- | --- | | Endpoint | `POST https://anytime-capital.com/api/mcp` | | Transport | Streamable HTTP, stateless: each POST carries one JSON-RPC 2.0 message or a batch, and the answer is `application/json`. No sessions and no server-sent events; `GET` and `DELETE` answer `405`. | | Protocol versions | `2025-06-18`, `2025-03-26`, `2024-11-05`. `initialize` answers with your version when it's one of these, otherwise `2025-06-18`. | | Server name | `anytime-capital-payments` | | Methods | `initialize`, `ping`, `tools/list`, `tools/call`. Notifications are accepted with `202` and no body. | | Authentication | `Authorization: Bearer ak_live_…` on every request. `initialize` and `tools/list` work without a key so clients can discover the tools; `tools/call` without a valid key returns the API's `401` as a tool error. OAuth isn't offered. | | Permissions | `tools/list` names each tool's permission in its description and in `_meta["anytime-capital.com/permission"]`; a key without it gets `403 PERMISSION_DENIED` as a tool error. | **Calling a tool directly** ```bash curl https://anytime-capital.com/api/mcp \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -H "Authorization: Bearer $ANYTIME_API_KEY" \ -d '{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "create_payment_link", "arguments": { "title": "Website audit", "amountCents": 25000, "idempotencyKey": "audit-2026-10" } } }' ``` **Result** ```json { "jsonrpc": "2.0", "id": 1, "result": { "content": [ { "type": "text", "text": "{\n \"object\": \"payment_link\",\n \"id\": \"pl_…\",\n \"url\": \"https://anytime-capital.com/pay/business/pl_…\",\n …\n}" } ], "structuredContent": { "object": "payment_link", "id": "pl_…", "url": "https://anytime-capital.com/pay/business/pl_…", "…": "…" }, "isError": false } } ``` --- # Safe AI agents: restricted keys, approvals and limits > Give AI agents least-privilege access to Anytime Capital Payments: a restricted key per agent, presets per use case, refund approval limits and idempotency. Source: https://anytime-capital.com/docs/ai-agent-safety · Markdown: https://anytime-capital.com/docs/ai-agent-safety.md · All docs: https://anytime-capital.com/docs/llms-full.txt An agent acts with the API key you give it, so the key is the boundary: give each agent its own key with only the access its job needs, and let the account's own controls (approval limits, the activity log, read-only money movement) do the rest. Everything below applies the same to the MCP server and to the REST API. ## What an agent can never do - Move money out of your balance: payouts and the balance can only be read, by any key. Payouts are made in the dashboard by owners and admins. - Refund past your approval limit: a refund over it waits for an owner (below). - Respond to disputes, change your settings, team or keys, or create other keys. - Act after you revoke its key: revoking takes effect on the next request. ## Presets per use case In **Settings → Developers → Create key**, choose **Read only**, or **Custom** and set each resource to none, read or write as below (write includes read). A key's access is fixed when it's made: for different access, make a new key and revoke the old one. | Use case | Job | Key access | MCP tools it can use | | --- | --- | --- | --- | | **Bookkeeping and reporting** | Answers questions about sales, refunds, payouts and disputes; reconciles a month. | **Read only** | `list_payments`, `get_payment`, `list_payment_links`, `get_payment_link`, `list_invoices`, `get_invoice`, `list_customers`, `get_customer`, `list_refunds`, `get_refund`, `list_products`, `get_product`, `list_subscription_plans`, `get_subscription_plan`, `list_subscriptions`, `get_subscription`, `list_discount_codes`, `get_discount_code`, `list_payouts`, `get_payout`, `get_balance`, `list_disputes`, `get_dispute`, `list_webhook_endpoints`, `get_webhook_endpoint`, `list_events`, `get_event` | | **Invoicing assistant** | Drafts, sends and chases invoices; adds the customers it invoices. | `invoices` write, `customers` write, `payments` read | `list_payments`, `get_payment`, `list_invoices`, `create_invoice`, `get_invoice`, `send_invoice`, `list_customers`, `create_customer`, `get_customer`, `update_customer` | | **Sales assistant** | Makes payment links and discount codes for deals, and checks who paid. | `payment_links` write, `discount_codes` write, `products` read, `payments` read | `list_payments`, `get_payment`, `list_payment_links`, `create_payment_link`, `get_payment_link`, `update_payment_link`, `deactivate_payment_link`, `list_products`, `get_product`, `list_discount_codes`, `create_discount_code`, `get_discount_code`, `deactivate_discount_code` | | **Customer support** | Looks up a customer's payments and subscription, refunds within your approval limit, cancels or pauses on request. | `payments` read, `refunds` write, `customers` read, `subscriptions` write, `disputes` read | `list_payments`, `get_payment`, `list_customers`, `get_customer`, `list_refunds`, `get_refund`, `create_refund`, `list_subscription_plans`, `create_subscription_plan`, `get_subscription_plan`, `list_subscriptions`, `get_subscription`, `cancel_subscription`, `pause_subscription`, `resume_subscription`, `list_disputes`, `get_dispute` | | **Catalog upkeep** | Keeps products and prices in step with your inventory. | `products` write | `list_products`, `create_product`, `get_product`, `update_product`, `archive_product` | | **Developer assistant** | Writes and tests your integration's code; sets up its webhook endpoint. | `webhook_endpoints` write, `payment_links` read, `payments` read | `list_payments`, `get_payment`, `list_payment_links`, `get_payment_link`, `list_webhook_endpoints`, `create_webhook_endpoint`, `get_webhook_endpoint`, `update_webhook_endpoint`, `delete_webhook_endpoint`, `send_test_event` | Resources a key can be given: `payments` (Payments), `refunds` (Refunds), `payment_links` (Payment links), `invoices` (Invoices), `customers` (Customers), `products` (Products), `subscriptions` (Subscriptions), `discount_codes` (Discount codes), `payouts` (Payouts), `disputes` (Disputes), `webhook_endpoints` (Webhook endpoints), `events` (Events). The full list of what each one allows is on [Authentication](https://anytime-capital.com/docs/authentication.md#restricted-keys-and-permissions). ## Approval limits for refunds An API key refunds like an admin. Set a refund approval limit in **Settings → Team & approvals** and any refund over it, by an admin, a key or an agent, waits for an owner: the agent gets `status: "pending_approval"` and nothing is refunded until an owner approves it in the dashboard. Set the limit to $0 and every refund an agent makes waits for you (admins' refunds too). ## Keep a person in the loop - Tools that change data are marked `readOnlyHint: false`; refunds, cancellations, archiving, deactivating and deleting are also `destructiveHint: true`, and tools that email customers are `openWorldHint: true`. Clients that ask before acting, such as Claude, use these: keep those confirmations on for keys that can write. - Ask agents to draft first: `create_invoice` with `send: false` makes the invoice without emailing it, so you can review it and send it with `send_invoice`. - Every change an agent makes is in your activity log under its key's name. ## Retries never act twice Agents retry. Every tool that changes something takes an `idempotencyKey` argument, sent as the [Idempotency-Key](https://anytime-capital.com/docs/idempotency.md) header: the same key within 24 hours returns the first result instead of acting again. Tell your agent to make one per action (“use refund-order-1042 as the idempotency key”). ## Untrusted text Customer names, invoice notes, checkout answers and metadata are written by other people. An agent that reads them can meet text that looks like instructions. Give agents that read customer data a **Read only** key unless they truly need to write, and keep confirmations on for every write. ## Checklist 1. One key per agent, named after it, with the smallest preset that does the job. 2. A refund approval limit, if the agent can refund. 3. The key in the client's secret store or an environment variable, never in a shared file or a chat. 4. Confirmations on in the client for tools that change data. 5. Review the activity log; revoke the key the day the agent is retired. --- # Build a custom agent on the MCP server > Connect your own agent to Anytime Capital Payments over MCP with an API key: raw JSON-RPC, the TypeScript and Python MCP SDKs, and the Claude API MCP connector. Source: https://anytime-capital.com/docs/mcp-custom-agents · Markdown: https://anytime-capital.com/docs/mcp-custom-agents.md · All docs: https://anytime-capital.com/docs/llms-full.txt The MCP server at `https://anytime-capital.com/api/mcp` is plain HTTP: any MCP client can use it, and so can your own code. Authenticate every call with an API key as `Authorization: Bearer ak_live_…`; the agent can do exactly what that key allows. Make the key for this agent alone, with the least access its job needs: see [Safe AI agents](https://anytime-capital.com/docs/ai-agent-safety.md). | Detail | Value | | --- | --- | | Transport | Streamable HTTP, stateless: POST one JSON-RPC message (or a batch), get `application/json` back. No session id; `GET` answers `405`, which MCP clients take to mean there's no event stream. | | Methods | `initialize`, `ping`, `tools/list`, `tools/call` | | Tool results | `structuredContent` is the API's JSON object; on an error `isError` is true and it's `{ status, error: { code, message } }`, with `retryAfterSeconds` when the API asked to wait. | | Limits | Each call counts against the key's [rate limit](https://anytime-capital.com/docs/rate-limits.md), as a REST request would. | | Retries | Pass `idempotencyKey` on tools that change something: a retry with the same key returns the first result. | ## Raw JSON-RPC ```bash # 1. Discover the tools (no key needed for initialize and tools/list) curl https://anytime-capital.com/api/mcp \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' # 2. Call one with your key curl https://anytime-capital.com/api/mcp \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -H "Authorization: Bearer $ANYTIME_API_KEY" \ -H "MCP-Protocol-Version: 2025-06-18" \ -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"list_invoices","arguments":{"status":"open","limit":10}}}' ``` ## TypeScript (MCP SDK) With the official MCP TypeScript SDK your code is the client: it lists the tools, and you decide which calls run. Check each tool's annotations and ask a person before anything that isn't read-only. **agent.mts** ```typescript // agent.mts — npm install @modelcontextprotocol/sdk import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; const transport = new StreamableHTTPClientTransport(new URL("https://anytime-capital.com/api/mcp"), { requestInit: { headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}` } }, }); const client = new Client({ name: "my-payments-agent", version: "1.0.0" }); await client.connect(transport); const { tools } = await client.listTools(); console.log(tools.map((tool) => tool.name)); // Your code decides what runs: ask a person before any tool that isn't read-only. const tool = tools.find((candidate) => candidate.name === "list_invoices"); if (tool?.annotations?.readOnlyHint !== true) throw new Error("Needs a person's OK first"); const result = await client.callTool({ name: "list_invoices", arguments: { status: "open", limit: 10 } }); if (result.isError) console.error(result.structuredContent); // { status, error: { code, message } } else console.log(result.structuredContent); // { object: "list", data: [...], hasMore, nextCursor } await client.close(); ``` ## Python (MCP SDK) **agent.py** ```python # agent.py — pip install "mcp>=1.10" import asyncio import os from mcp import ClientSession from mcp.client.streamable_http import streamablehttp_client URL = "https://anytime-capital.com/api/mcp" HEADERS = {"Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}"} async def main(): async with streamablehttp_client(URL, headers=HEADERS) as (read, write, _): async with ClientSession(read, write) as session: await session.initialize() tools = await session.list_tools() print([tool.name for tool in tools.tools]) result = await session.call_tool("list_invoices", {"status": "open", "limit": 10}) print(result.structuredContent) # the API's JSON, or { status, error } when result.isError asyncio.run(main()) ``` ## Claude API (MCP connector) Claude's Messages API can connect to the server for you: pass it in `mcp_servers` with your API key as `authorization_token`, and Claude calls the tools on Anthropic's side during the request. It runs the tools it chooses without asking you, so allowlist the ones the agent may use with `default_config` and `configs` (above: read-only tools only), and give it a key with no more access than that. The connector is in beta; see Anthropic's MCP connector documentation for its current limits. **claude-agent.mts** ```typescript // claude-agent.mts — npm install @anthropic-ai/sdk import Anthropic from "@anthropic-ai/sdk"; const anthropic = new Anthropic(); // reads ANTHROPIC_API_KEY const response = await anthropic.beta.messages.create({ model: "claude-opus-5-5", max_tokens: 16000, betas: ["mcp-client-2025-11-20", "server-side-fallback-2026-07-01"], fallbacks: "default", mcp_servers: [ { type: "url", url: "https://anytime-capital.com/api/mcp", name: "anytime-capital", authorization_token: process.env.ANYTIME_API_KEY, // sent as "Authorization: Bearer …" }, ], tools: [ { type: "mcp_toolset", mcp_server_name: "anytime-capital", // Allowlist: only these tools exist for this agent, whatever the key allows. default_config: { enabled: false }, configs: { list_invoices: { enabled: true }, get_invoice: { enabled: true }, list_payments: { enabled: true }, }, }, ], messages: [{ role: "user", content: "Which invoices are past due, and how much is owed in total?" }], }); if (response.stop_reason === "refusal") { console.error("Claude declined this request; rephrase it and try again."); } else { for (const block of response.content) { if (block.type === "text") console.log(block.text); } } ``` **claude_agent.py** ```python # claude_agent.py — pip install anthropic import os import anthropic client = anthropic.Anthropic() # reads ANTHROPIC_API_KEY response = client.beta.messages.create( model="claude-opus-5-5", max_tokens=16000, betas=["mcp-client-2025-11-20", "server-side-fallback-2026-07-01"], fallbacks="default", mcp_servers=[ { "type": "url", "url": "https://anytime-capital.com/api/mcp", "name": "anytime-capital", "authorization_token": os.environ["ANYTIME_API_KEY"], } ], tools=[ { "type": "mcp_toolset", "mcp_server_name": "anytime-capital", "default_config": {"enabled": False}, "configs": { "list_invoices": {"enabled": True}, "get_invoice": {"enabled": True}, "list_payments": {"enabled": True}, }, } ], messages=[{"role": "user", "content": "Which invoices are past due, and how much is owed in total?"}], ) if response.stop_reason == "refusal": print("Claude declined this request; rephrase it and try again.") else: for block in response.content: if block.type == "text": print(block.text) ``` > **Two keys, two places:** `ANTHROPIC_API_KEY` is your Claude API key; `ANYTIME_API_KEY` is your Anytime Capital key, which the connector sends to our server on each tool call. Keep both on your server. ## Patterns that work - **Read, then propose, then act:** let the agent read freely with a Read only key, have it propose changes, and run them with a second, narrower key after a person approves. - **Drafts:** have it create invoices with `send: false` and send them with `send_invoice` once checked. - **One key per agent:** revoking one never breaks another, and the activity log shows which agent did what. - **Webhooks for the rest:** an agent that needs to know when something happens should be woken by your [webhook](https://anytime-capital.com/docs/webhooks.md) handler rather than polling lists. --- # Prompt library for payments agents > Prompts for AI agents connected to Anytime Capital Payments: invoice customers, chase overdue invoices, reconcile a month, refund safely and win customers back. Source: https://anytime-capital.com/docs/ai-prompt-library · Markdown: https://anytime-capital.com/docs/ai-prompt-library.md · All docs: https://anytime-capital.com/docs/llms-full.txt Prompts that work well with an assistant connected to the [MCP server](https://anytime-capital.com/docs/build-with-ai.md#connect-the-mcp-server). Each lists the tools it leads to and the key permissions those need, so you can give the agent a key that allows exactly that (see [Safe AI agents](https://anytime-capital.com/docs/ai-agent-safety.md)). Amounts are in dollars in the prompt; the agent sends cents. > **Three habits that make agents safe:** Ask for a list before anything is sent or changed. Give an idempotency key for every action (“using idempotency key …”), so a retry never acts twice. Ask for drafts (`send: false`) and send them yourself or in a second step. ## Getting paid Links and invoices, made and chased for you. | Prompt | Tools | Key needs | | --- | --- | --- | | **A link for a quote:** “Create a single-use payment link for $1,850 titled “Kitchen remodel deposit — Rivera” with orderId Q-2291 in metadata, using idempotency key quote-Q-2291. Give me the link.” | `create_payment_link` | `payment_links:write` | | **Invoice every customer who…:** “Find every customer who paid the “Monthly bookkeeping” payment link in September but not in October. For each one, draft an invoice (send: false) for $400, “Bookkeeping — October”, due in 14 days, using idempotency key inv-oct-{their email}. Show me the list before sending any of them.” | `list_payments`, `list_payment_links`, `create_invoice` | `payments:read`, `payment_links:read`, `invoices:write` | | **Send the drafts:** “Send the invoices you just drafted, one by one, and tell me any that failed and why.” | `send_invoice` | `invoices:write` | | **Chase what's overdue:** “List open invoices that are past due, oldest first, with the customer, amount owed and days overdue. Then send a reminder for each one that hasn't been emailed in the last day.” | `list_invoices`, `send_invoice` | `invoices:read`, `invoices:write` | ## Reconcile and report Read only: a Read only key is enough. | Prompt | Tools | Key needs | | --- | --- | --- | | **Reconcile last month:** “Reconcile last month (UTC): total paid payments (gross and net), total refunds, and every payout with its status, net amount and trace code. Flag payouts that failed or came back, payments without a net amount yet, and refunds that failed. End with a short table I can paste into my books.” | `list_payments`, `list_refunds`, `list_payouts`, `get_balance` | `payments:read`, `refunds:read`, `payouts:read` | | **This week by card:** “How much did we take this week, by payment method, and how much of it was refunded? Compare with last week.” | `list_payments`, `list_refunds` | `payments:read`, `refunds:read` | | **Disputes to answer:** “List disputes that need a response, with the payment, amount, reason and how many days are left before evidence is due.” | `list_disputes`, `get_payment` | `disputes:read`, `payments:read` | | **Cash position:** “What's our available, pending and reserved balance, and when is the latest payout expected at the bank?” | `get_balance`, `list_payouts` | `payouts:read` | ## Customers and subscriptions Support and retention. | Prompt | Tools | Key needs | | --- | --- | --- | | **What happened with this customer:** “Look up jordan@example.com: their saved details, their last five payments and any refunds, and their subscription status.” | `list_customers`, `list_payments`, `list_refunds`, `list_subscriptions` | `customers:read`, `payments:read`, `refunds:read`, `subscriptions:read` | | **Refund an order:** “Refund $25.00 of payment pay_7Hq2LmX9cR4tVb as requested_by_customer, using idempotency key refund-1042-partial. Tell me whether it went through or is waiting for an owner's approval.” | `create_refund` | `refunds:write` | | **Past-due subscribers:** “List subscriptions that are past due, with each customer's email, plan and when the period ended. Draft a short, friendly note I can send each one about updating their card.” | `list_subscriptions` | `subscriptions:read` | | **Thank-you codes:** “Find customers who paid us more than three times in the last 90 days. For each, create a one-use discount code THANKS-{first name} for 15% off that expires in 30 days, using the code as the idempotency key. List the codes with their customer so I can email them.” | `list_payments`, `create_discount_code` | `payments:read`, `discount_codes:write` | ## Catalog and setup Products, links and webhooks. | Prompt | Tools | Key needs | | --- | --- | --- | | **Price change:** “Raise the price of every active product whose SKU starts with MUG- by $2.00, and list what changed. Payment links already selling them keep their price, so also tell me which links I should replace.” | `list_products`, `update_product`, `list_payment_links` | `products:read`, `products:write`, `payment_links:read` | | **Retire a link:** “Deactivate the “Summer sale” payment link and confirm it's closed.” | `list_payment_links`, `deactivate_payment_link` | `payment_links:read`, `payment_links:write` | | **Wire up webhooks:** “Add a webhook endpoint for https://example.com/webhooks/anytime with payment.succeeded and refund.created, using idempotency key endpoint-orders, and show me the signing secret once so I can store it.” | `create_webhook_endpoint` | `webhook_endpoints:write` | ## Draft without connecting Assistants that can't connect to your account, ChatGPT included, can still do the thinking: draft an invoice or a set of payment links as JSON, which you check and then create in the dashboard or with a connected agent. **Draft an invoice** ```text Draft an invoice for Northwind LLC (ap@northwind.example) for a website redesign: discovery workshop 1 × $1,200, design 1 × $2,400, build 1 × $3,900, 8.25% sales tax, due in 30 days, with a 25% deposit. Read https://anytime-capital.com/docs/invoices.md first, then give me the request body for POST /api/v1/invoices as JSON (amounts in cents, tax in basis points), plus the subtotal, tax and total you expect. Don't add fields that page doesn't list. ``` --- # Take payments on Wix, Squarespace, Webflow and other site builders > Add a Pay button, the checkout or a payment link to Wix, Squarespace, Webflow, WordPress, Framer, GoDaddy, Carrd, Ghost, Linktree or plain HTML, step by step. Source: https://anytime-capital.com/docs/website-builders · Markdown: https://anytime-capital.com/docs/website-builders.md · All docs: https://anytime-capital.com/docs/llms-full.txt Every way onto your website runs on a payment link: our Pay button opens the checkout in a window over your page, the checkout can sit inside a page, and any builder's own button or link can point at the checkout page. Card details are always entered on Anytime Capital's secure checkout, never on your site. > **Let the dashboard write it:** In your dashboard, **Checkout tools → Add to your website** asks which builder you use, fills in your payment link and brand colour, shows only the options your builder allows, and has a **Test it** button and a prompt for your AI assistant. ## Which option each builder allows | Builder | Pay button | Checkout in the page | Button or link | Plan needed for code | | --- | --- | --- | --- | --- | | [Wix](https://anytime-capital.com/docs/website-builders.md#wix) | — | Yes | Yes (best) | Any plan; code runs in a box | | [Squarespace](https://anytime-capital.com/docs/website-builders.md#squarespace) | Yes (best) | Yes | Yes | Core, Plus or Advanced | | [Webflow](https://anytime-capital.com/docs/website-builders.md#webflow) | Yes (best) | Yes | Yes | A paid Site or Workspace plan | | [WordPress](https://anytime-capital.com/docs/website-builders.md#wordpress) | Yes (best) | Yes | Yes | WordPress.com Business or Commerce; any self-hosted site | | [Framer](https://anytime-capital.com/docs/website-builders.md#framer) | — | Yes | Yes (best) | Code runs in a box | | [GoDaddy Website Builder](https://anytime-capital.com/docs/website-builders.md#godaddy) | — | Yes | Yes (best) | Code runs in a box | | [Carrd](https://anytime-capital.com/docs/website-builders.md#carrd) | Yes (best) | Yes | Yes | Pro Standard or Pro Plus | | [Ghost](https://anytime-capital.com/docs/website-builders.md#ghost) | Yes (best) | Yes | Yes | Any | | [Linktree and link in bio](https://anytime-capital.com/docs/website-builders.md#linktree) | — | — | Yes (best) | No custom code: link only | | [Your own HTML](https://anytime-capital.com/docs/website-builders.md#html) | Yes (best) | Yes | Yes | Any | ## Before you start - Make a **reusable** payment link under **Payment Links** (a one-time link closes after its first payment). Its price, quantity choices, discount codes and shipping all carry over. - Try everything in **test mode** first: switch the dashboard to test mode, make a test link and paste its code. Test links take test cards only. - Pick the option your plan allows. Every option works on phones. ## The code Checkout tools fills these in for your link and colour. They're shown here for a sample link. **Pay button (opens the checkout over your page)** ```html Pay $125.00 ``` **Checkout in the page** ```html ``` **Pay button with no script** ```html Pay $125.00 ``` **Payment link, for a builder's own button** ```text https://anytime-capital.com/pay/business/pl_4kTz8QwN2xYb6RcD1mPa ``` ## Wix > **Note:** Wix runs custom code in a box of its own, so a window over your page isn't possible there: use a Wix button, or the checkout in the page. ### Wix: button or link Your own button, linked to your secure checkout page. Works on every plan. 1. In the Wix Editor, choose Add Elements (+) → Button and place a button on your page. 2. Choose Change Text and type what it says, such as "Pay now". 3. Choose the Link icon → Web Address and paste your payment link. 4. Choose to open it in a new tab if the builder offers it, so your page stays open. 5. Publish (or update) the page. ### Wix: checkout in the page The checkout itself sits in your page and grows with its content. 1. In the Wix Editor, choose Add Elements (+) → Embed Code → Embed HTML. 2. Choose Enter Code, select Code, paste the code and choose Update. 3. Drag the box's bottom edge until it's at least 720 pixels tall. 4. Publish (or update) the page. This builder shows custom code in a box of its own, so the checkout can't grow by itself: make the box at least 720 pixels tall. Apple Pay and Google Pay may not appear inside the box; cards always work there, and the checkout page a button opens shows every way to pay. **AI prompt for Wix** ```text I want to take payments on my Wix website with Anytime Capital, a fintech company, not a bank. Help me add a button or link that opens this payment link: Walk me through it one click at a time. Use one of Wix's own buttons or links, set it to open this address (in a new tab if Wix offers it), and tell me where to find each setting. Good to know: Wix runs custom code in a box of its own, so a window over your page isn't possible there: use a Wix button, or the checkout in the page. Don't change the code or the link, and don't ask me for card details or passwords. The full guide is at https://anytime-capital.com/docs/website-builders#wix ``` ## Squarespace > **Note:** Code blocks with scripts need a Squarespace Core, Plus or Advanced plan (or a Business or Commerce plan on older pricing). On a Basic or Personal plan, use a Button block. ### Squarespace: pay button Customers pay in a secure window over your page, without leaving your site. 1. Edit the page, choose where the button goes and choose Add Block → Code. 2. Paste the code, make sure the block is set to HTML, then click outside the block. 3. Save the page. While you edit, Squarespace may show "Script disabled": the button works on the published page. ### Squarespace: checkout in the page The checkout itself sits in your page and grows with its content. 1. Edit the page and choose Add Block → Code where the checkout should go. 2. Paste the code, make sure the block is set to HTML, then click outside the block. 3. Save the page and open the live page to see it. ### Squarespace: button or link Your own button, linked to your secure checkout page. Works on every plan. 1. Edit the page and choose Add Block → Button. 2. Type the button text, then paste your payment link as the button's link. 3. Choose to open it in a new tab if the builder offers it, so your page stays open. 4. Save the page. **AI prompt for Squarespace** ```text I want to take payments on my Squarespace website with Anytime Capital, a fintech company, not a bank. Help me add a Pay button that opens a secure checkout over my page. Here is the code from Anytime Capital: Walk me through it one click at a time. Check whether my Squarespace plan allows custom code first. If it doesn't, show me how to point one of Squarespace's own buttons at this link instead: Good to know: Code blocks with scripts need a Squarespace Core, Plus or Advanced plan (or a Business or Commerce plan on older pricing). On a Basic or Personal plan, use a Button block. Don't change the code or the link, and don't ask me for card details or passwords. The full guide is at https://anytime-capital.com/docs/website-builders#squarespace ``` ## Webflow > **Note:** Custom code needs a paid Webflow Site plan or a paid Workspace plan to publish. On a free plan, link a Webflow button instead. ### Webflow: pay button Customers pay in a secure window over your page, without leaving your site. 1. In the Designer, open the Add panel and drag a Code Embed where the button goes. 2. Paste the code and choose Save & Close. 3. Publish the site. Scripts run on the published site, not inside the Designer. ### Webflow: checkout in the page The checkout itself sits in your page and grows with its content. 1. In the Designer, open the Add panel and drag a Code Embed where the checkout goes. 2. Paste the code and choose Save & Close. 3. Publish the site and open the published page to see it. ### Webflow: button or link Your own button, linked to your secure checkout page. Works on every plan. 1. Add a Button from the Add panel and type its text. 2. In Element settings, under Link settings, choose URL and paste your payment link. 3. Choose to open it in a new tab if the builder offers it, so your page stays open. 4. Publish the site. **AI prompt for Webflow** ```text I want to take payments on my Webflow website with Anytime Capital, a fintech company, not a bank. Help me add a Pay button that opens a secure checkout over my page. Here is the code from Anytime Capital: Walk me through it one click at a time. Check whether my Webflow plan allows custom code first. If it doesn't, show me how to point one of Webflow's own buttons at this link instead: Good to know: Custom code needs a paid Webflow Site plan or a paid Workspace plan to publish. On a free plan, link a Webflow button instead. Don't change the code or the link, and don't ask me for card details or passwords. The full guide is at https://anytime-capital.com/docs/website-builders#webflow ``` ## WordPress > **Note:** On WordPress.com, scripts in a Custom HTML block need a Business or Commerce plan; on lower plans the script is removed and the code stays a plain link to your checkout. Self-hosted WordPress: administrators can add it on any site. Selling with WooCommerce? Use our plugin instead. ### WordPress: pay button Customers pay in a secure window over your page, without leaving your site. 1. Edit the page or post, choose + (Add block) and pick Custom HTML. 2. Paste the code into the block. 3. Choose Update or Publish. ### WordPress: checkout in the page The checkout itself sits in your page and grows with its content. 1. Edit the page, choose + (Add block) and pick Custom HTML where the checkout goes. 2. Paste the code into the block. 3. Choose Update or Publish, then open the live page. ### WordPress: button or link Your own button, linked to your secure checkout page. Works on every plan. 1. Edit the page, choose + (Add block) and pick Buttons. 2. Type the button text, choose the link icon and paste your payment link. 3. Turn on Open in new tab. 4. Choose Update or Publish. **AI prompt for WordPress** ```text I want to take payments on my WordPress website with Anytime Capital, a fintech company, not a bank. Help me add a Pay button that opens a secure checkout over my page. Here is the code from Anytime Capital: Walk me through it one click at a time. Check whether my WordPress plan allows custom code first. If it doesn't, show me how to point one of WordPress's own buttons at this link instead: Good to know: On WordPress.com, scripts in a Custom HTML block need a Business or Commerce plan; on lower plans the script is removed and the code stays a plain link to your checkout. Self-hosted WordPress: administrators can add it on any site. Selling with WooCommerce? Use our plugin instead. Don't change the code or the link, and don't ask me for card details or passwords. The full guide is at https://anytime-capital.com/docs/website-builders#wordpress ``` ## Framer > **Note:** Framer shows embedded code in a box of its own, so a window over your page isn't possible there: link a Framer button, or use the checkout in the page. ### Framer: button or link Your own button, linked to your secure checkout page. Works on every plan. 1. Select the button or text layer customers will click. 2. In the right panel, under Link, paste your payment link. 3. Turn on New Tab. 4. Publish (or update) the page. ### Framer: checkout in the page The checkout itself sits in your page and grows with its content. 1. Choose Insert → Embed and place it where the checkout goes. 2. In the Embed's properties choose HTML and paste the code. 3. Set the Embed's height to at least 720 pixels. 4. Publish (or update) the page. This builder shows custom code in a box of its own, so the checkout can't grow by itself: make the box at least 720 pixels tall. Apple Pay and Google Pay may not appear inside the box; cards always work there, and the checkout page a button opens shows every way to pay. **AI prompt for Framer** ```text I want to take payments on my Framer website with Anytime Capital, a fintech company, not a bank. Help me add a button or link that opens this payment link: Walk me through it one click at a time. Use one of Framer's own buttons or links, set it to open this address (in a new tab if Framer offers it), and tell me where to find each setting. Good to know: Framer shows embedded code in a box of its own, so a window over your page isn't possible there: link a Framer button, or use the checkout in the page. Don't change the code or the link, and don't ask me for card details or passwords. The full guide is at https://anytime-capital.com/docs/website-builders#framer ``` ## GoDaddy Website Builder > **Note:** GoDaddy runs custom code in a box of its own, so a window over your page isn't possible there: use a GoDaddy button, or the checkout in the page. ### GoDaddy Website Builder: button or link Your own button, linked to your secure checkout page. Works on every plan. 1. Choose Edit Website, then select a section with a button (or add one). 2. Select the button, type its label and set its link to a web address. 3. Paste your payment link. 4. Choose to open it in a new tab if the builder offers it, so your page stays open. 5. Publish (or update) the page. ### GoDaddy Website Builder: checkout in the page The checkout itself sits in your page and grows with its content. 1. Choose Edit Website → Add Section, search for HTML and add the HTML section. 2. Paste the code into Custom Code and choose Done. 3. If the section has a height setting, make it at least 720 pixels. 4. Publish (or update) the page. This builder shows custom code in a box of its own, so the checkout can't grow by itself: make the box at least 720 pixels tall. Apple Pay and Google Pay may not appear inside the box; cards always work there, and the checkout page a button opens shows every way to pay. **AI prompt for GoDaddy Website Builder** ```text I want to take payments on my GoDaddy Website Builder website with Anytime Capital, a fintech company, not a bank. Help me add a button or link that opens this payment link: Walk me through it one click at a time. Use one of GoDaddy Website Builder's own buttons or links, set it to open this address (in a new tab if GoDaddy Website Builder offers it), and tell me where to find each setting. Good to know: GoDaddy runs custom code in a box of its own, so a window over your page isn't possible there: use a GoDaddy button, or the checkout in the page. Don't change the code or the link, and don't ask me for card details or passwords. The full guide is at https://anytime-capital.com/docs/website-builders#godaddy ``` ## Carrd > **Note:** Embed elements need Carrd Pro Standard or Pro Plus. On the free plan or Pro Lite, use a Carrd button. ### Carrd: pay button Customers pay in a secure window over your page, without leaving your site. 1. Choose + (Add element) → Embed where the button goes. 2. Set Type to Code and Style to Inline, paste the code and choose Done. 3. Publish the site. ### Carrd: checkout in the page The checkout itself sits in your page and grows with its content. 1. Choose + (Add element) → Embed where the checkout goes. 2. Set Type to Code and Style to Inline, paste the code and choose Done. 3. Publish the site. ### Carrd: button or link Your own button, linked to your secure checkout page. Works on every plan. 1. Choose + (Add element) → Buttons. 2. Type the button's label and paste your payment link as its URL. 3. Publish the site. **AI prompt for Carrd** ```text I want to take payments on my Carrd website with Anytime Capital, a fintech company, not a bank. Help me add a Pay button that opens a secure checkout over my page. Here is the code from Anytime Capital: Walk me through it one click at a time. Check whether my Carrd plan allows custom code first. If it doesn't, show me how to point one of Carrd's own buttons at this link instead: Good to know: Embed elements need Carrd Pro Standard or Pro Plus. On the free plan or Pro Lite, use a Carrd button. Don't change the code or the link, and don't ask me for card details or passwords. The full guide is at https://anytime-capital.com/docs/website-builders#carrd ``` ## Ghost ### Ghost: pay button Customers pay in a secure window over your page, without leaving your site. 1. In the post or page editor, start a new line, type / and choose HTML. 2. Paste the code into the HTML card. 3. Publish or Update. ### Ghost: checkout in the page The checkout itself sits in your page and grows with its content. 1. In the editor, start a new line where the checkout goes, type / and choose HTML. 2. Paste the code into the HTML card. 3. Publish or Update. ### Ghost: button or link Your own button, linked to your secure checkout page. Works on every plan. 1. In the editor, start a new line, type / and choose Button. 2. Type the button text and paste your payment link as its URL. 3. Publish or Update. **AI prompt for Ghost** ```text I want to take payments on my Ghost website with Anytime Capital, a fintech company, not a bank. Help me add a Pay button that opens a secure checkout over my page. Here is the code from Anytime Capital: Walk me through it one click at a time. Check whether my Ghost plan allows custom code first. If it doesn't, show me how to point one of Ghost's own buttons at this link instead: Don't change the code or the link, and don't ask me for card details or passwords. The full guide is at https://anytime-capital.com/docs/website-builders#ghost ``` ## Linktree and link in bio > **Note:** Link-in-bio pages don't take custom code, so add your payment link as one of your links. It opens your secure checkout. ### Linktree and link in bio: button or link A link on your page that opens your secure checkout. Works on every plan. 1. In Linktree (or Beacons, Stan, Koji and the like) choose Add, then add a link. 2. Paste your payment link as the URL. 3. Give it a title customers understand, such as "Pay for your order" or "Book and pay". 4. It's live as soon as you save it. The same link works in an Instagram, TikTok or X bio. **AI prompt for Linktree and link in bio** ```text I want to take payments on my link-in-bio page with Anytime Capital, a fintech company, not a bank. Help me add a button or link that opens this payment link: Walk me through it one click at a time. Use one of Linktree and link in bio's own buttons or links, set it to open this address (in a new tab if Linktree and link in bio offers it), and tell me where to find each setting. Good to know: Link-in-bio pages don't take custom code, so add your payment link as one of your links. It opens your secure checkout. Don't change the code or the link, and don't ask me for card details or passwords. The full guide is at https://anytime-capital.com/docs/website-builders#linktree ``` ## Your own HTML ### Your own HTML: pay button Customers pay in a secure window over your page, without leaving your site. 1. Paste the code inside where the button should appear. 2. With several buttons on one page, the script line is only needed once. 3. Deploy the page. ### Your own HTML: checkout in the page The checkout itself sits in your page and grows with its content. 1. Paste the code inside where the checkout should appear. 2. It fits the width of its container: put it in a column about 480 pixels wide or more. 3. Deploy the page. ### Your own HTML: pay button (no script) A styled link to your secure checkout page, with no script at all. 1. Paste the code where the button should appear. 2. Deploy the page. ### Your own HTML: button or link The checkout page's address, for emails, messages and anything else. 1. Use the link anywhere a web address goes, or send customers to it from your own code. **AI prompt for Your own HTML** ```text I want to take payments on my website with Anytime Capital, a fintech company, not a bank. Help me add a Pay button that opens a secure checkout over my page. Here is the code from Anytime Capital: Walk me through it one click at a time. Check whether my Your own HTML plan allows custom code first. If it doesn't, show me how to point one of Your own HTML's own buttons at this link instead: Don't change the code or the link, and don't ask me for card details or passwords. The full guide is at https://anytime-capital.com/docs/website-builders#html ``` ## Test it before customers do 1. Switch your dashboard to test mode and make a test payment link (or load sample data). 2. In **Checkout tools → Add to your website**, choose your builder and the test link, and paste the code or link where the steps say. 3. Open your published page and pay with the test card 4242 4242 4242 4242. Use any future expiry date and any three-digit security code. 4. Check the payment under **Orders** in test mode, then come back to live mode, choose your live link and replace the test code or link with it. Checkout tools' **Test it** does the same without your site: in test mode it opens the Pay button over the dashboard, the checkout or the link exactly as customers will see them. See [Test mode](https://anytime-capital.com/docs/test-mode.md). ## For developers: what the embed needs - The script loads from https://anytime-capital.com/embed/anytime-pay.js and places the checkout in a frame from https://anytime-capital.com/embed/pay/pl_…. If your site sends a Content-Security-Policy, add https://anytime-capital.com to script-src and frame-src. - Our embedded checkout may be framed by any website; nothing to set up on our side. - The frame asks for the Payment Request API (allow="payment *") so Apple Pay and Google Pay can show. A page whose Permissions-Policy turns payment off still takes cards. - The inline checkout sizes itself from messages it accepts only from https://anytime-capital.com. - Where a builder strips scripts, the code is still a plain link to your checkout page. - Builders that put custom code in a box of their own (Wix, GoDaddy, Framer) keep the checkout inside that box: use their button, or the checkout in the page with a fixed height. Prefill, your own order reference and the `anytime-pay:succeeded` event are covered in [Hosted checkout, Pay button and branding](https://anytime-capital.com/docs/checkout.md). ### The button opens the checkout page instead of a window over my site. Why? Your builder removed the script, which some plans do. The button then works as a plain link to your checkout page, so customers can still pay. To get the window, use a plan that allows custom code, or keep the link. ### Nothing happens when I click the button in the editor. Most builders don't run scripts while you edit. Publish the page and try the live page, or use **Test it** in Checkout tools. ### The checkout is cut off on Wix, GoDaddy or Framer. Those builders show code in a box of their own that can't grow. Make the box at least 720 pixels tall, or use their own button linked to your payment link. ### Apple Pay or Google Pay doesn't show on my site. Wallets need the browser's Payment Request API, which a builder's own box or your site's Permissions-Policy can block. Cards always work; the Pay button's window and the checkout page show every way to pay. ### Can I sell several products from one page? Yes: make a payment link per product and add a button for each. With our Pay button, the script line is only needed once per page. - [Hosted checkout and Pay button](https://anytime-capital.com/docs/checkout.md): Every option, prefill, events, your own checkout domain and branding. - [Payment links](https://anytime-capital.com/docs/payment-links.md): Make links in the dashboard or with the API. - [Shopify orders paid by invoice](https://anytime-capital.com/docs/shopify-order-invoices.md): Selling on Shopify? Take payment on your orders today. - [WooCommerce plugin](https://anytime-capital.com/docs/woocommerce.md): Selling with WooCommerce on WordPress. --- # Shopify > Take card payments at your Shopify checkout with the Anytime Capital Payments app: install, link your business, activate it, and refund from Shopify. Source: https://anytime-capital.com/docs/shopify · Markdown: https://anytime-capital.com/docs/shopify.md · All docs: https://anytime-capital.com/docs/llms-full.txt > **Waiting for Shopify's approval:** Shopify reviews every payments app before it can take payments on live stores. Anytime Capital Payments for Shopify isn't approved yet, so it isn't offered in your dashboard; this page shows how it works once it is. Until then, take your Shopify orders through Anytime Capital with [Shopify orders paid by invoice](https://anytime-capital.com/docs/shopify-order-invoices.md). The Anytime Capital Payments app adds card payments to your Shopify checkout. Your customer chooses Anytime Capital Payments, pays the exact order total on Anytime Capital's secure checkout, and returns to your store's order confirmation. Shopify marks the order paid as soon as the payment goes through. ## Before you start - An Anytime Capital business account with card payments switched on (**Payments** in your dashboard). - Owner or admin access to that business, and to the Shopify store. - A store that sells in US dollars. Checkouts in other currencies are declined with a clear reason, so keep another payment method for them. - Orders from $1.00 to $10,000.00. - Payment capture set to automatic in Shopify (**Settings → Payments → Payment capture method**). The app charges customers when they pay; it doesn't authorize now and capture later. - Sales tax off in Anytime Capital (**Settings → Payments**): Shopify already adds tax to the order total, and the app won't take payments while both would charge it. ## Install and link your business 1. Install **Anytime Capital Payments** from the Shopify App Store (or choose **Add to Shopify** in your Anytime Capital dashboard) and approve the payment permissions it asks for. 2. Shopify sends you to Anytime Capital. Sign in if you aren't already. 3. Choose the business your store's checkouts pay into and choose **Link store and continue to Shopify**. 4. You land on the app's page in Shopify's payment settings. Activate it. To link the store to a different business later, open the app from **Apps** in your Shopify admin and choose again. ## Switching from Shopify Payments or another gateway 1. In Shopify, go to **Settings → Payments** and activate Anytime Capital Payments. 2. Put the provider in Shopify test mode and place a test order with a [test card](https://anytime-capital.com/docs/test-mode.md#test-cards): it is paid on your Anytime Capital test mode and no real money moves. Then turn Shopify test mode off for real orders. 3. Then deactivate the provider you're replacing in the same **Settings → Payments** page, so customers only see one card option. Orders paid with the old provider stay with it: refund those through that provider. New orders pay into your Anytime Capital balance and show in **Orders** in your dashboard. ## Refunds Refund an order in Shopify as usual. The refund goes back to the customer's card through Anytime Capital and Shopify shows it as refunded. If your business requires an owner's approval for refunds over a limit (**Settings → Team & approvals**), Shopify can't send refunds over that limit: refund those from **Orders** in your Anytime Capital dashboard. ## Good to know - Each checkout gets its own one-time payment link for the exact amount, so a customer can't pay twice or pay a different amount. - A customer who leaves our checkout goes back to Shopify and can choose again. A checkout nobody pays within a day is closed. - Bank account payments show as pending in Shopify until the bank confirms them. - If a payment ever goes through after Shopify has closed the checkout, your owners and admins get a **Store payment needs attention** alert to refund it. - Uninstalling the app closes its open payment links. Shopify asks every app to erase the store's data 48 hours later, and we do. - [Payment methods](https://anytime-capital.com/docs/payment-methods.md): What customers can pay with on Anytime Capital's checkout. - [Pricing](https://anytime-capital.com/docs/pricing.md): What a card payment costs. - [Support](https://anytime-capital.com/docs/support.md): How to reach us. --- # Shopify orders paid by invoice > Take card payments on Shopify today: a manual payment method, your store's own app, and Anytime Capital invoices that mark orders paid in Shopify. Source: https://anytime-capital.com/docs/shopify-order-invoices · Markdown: https://anytime-capital.com/docs/shopify-order-invoices.md · All docs: https://anytime-capital.com/docs/llms-full.txt > **Until our Shopify app is approved:** Our [Shopify payments app](https://anytime-capital.com/docs/shopify.md) puts Anytime Capital right in Shopify's checkout once Shopify approves it. Until then, this gets your Shopify orders paid through Anytime Capital today, with no app to install from us. You add a manual payment method in Shopify (for example "Pay by card — Anytime Capital") and connect your store to Anytime Capital with your own Shopify app. Then: 1. Your customer checks out on Shopify and chooses your Anytime Capital payment method. Shopify creates the order, waiting for payment. 2. Within a minute we email them an invoice from Anytime Capital, with your logo, for exactly what Shopify charged: items, discounts, shipping and tax. 3. They pay by card, Apple Pay or Google Pay on our secure checkout. We record a payment on the Shopify order, so it shows as Paid and you can fulfil it as usual. 4. Refund from Anytime Capital → Orders: the refund is recorded on the Shopify order too. 5. Cancel an unpaid order in Shopify and we void its invoice, so it can't be paid. ## Before you start - Card payments switched on for your Anytime Capital business, and owner or admin access to it. - Owner access to the Shopify store (or a staff account that can manage apps and payment settings). - A store that sells in US dollars. Orders in other currencies aren't billed; keep another payment method for them. - Orders from $1.00 to $100,000.00. - Sales tax off in Anytime Capital (**Settings → Payments**): Shopify already adds tax to the order total. ## 1. Add the payment method in Shopify 1. In your Shopify admin, go to Settings → Payments → Manual payment methods and choose Create custom payment method. 2. Name it what customers should see at checkout, for example "Pay by card — Anytime Capital". You'll type the same name in Anytime Capital. 3. Under Payment instructions, write something like: "We'll email you a secure payment link from Anytime Capital within a few minutes. Your order ships once it's paid." 4. Activate it. Keep your other payment methods for anything we can't bill (orders in other currencies, say). ## 2. Give us access with your store's own app We need an app on your store with the Admin API scopes `read_orders` and `write_orders`: reading your new and cancelled orders, and recording payments and refunds on them. Shopify stopped creating new custom apps in the Shopify admin on January 1, 2026, so new apps are made in Shopify's Dev Dashboard; a custom app you already made in the admin works too. ### A Dev Dashboard app 1. In your Shopify admin, go to Settings → Apps → Develop apps and open the Dev Dashboard (dev.shopify.com). Create an app, for example "Anytime Capital orders". 2. Create a version of the app with the Admin API scopes read_orders and write_orders, and release it. 3. Install the app on your store from the Dev Dashboard. 4. If Shopify asks which protected customer data the app uses, allow the customer's name and email: without them, orders arrive with no email to send the invoice to. 5. Open the app's Settings and copy the Client ID and Client secret. We use the client ID and secret to ask Shopify for a short-lived token (24 hours) whenever we need one. Shopify only grants it to an app made by the organization that owns the store. ### A custom app made in the Shopify admin 1. In your Shopify admin, go to Settings → Apps → Develop apps and open the custom app you made there. 2. Under Configuration, give it the Admin API scopes read_orders and write_orders, save, and install (or reinstall) it. 3. Under API credentials, copy the Admin API access token (Shopify shows it once) and the API secret key. ## 3. Connect the store 1. In Anytime Capital, go to Checkout tools → Shopify store (owners and admins). 2. Enter your store's myshopify.com address, the payment method's exact name and your app's credentials, then choose Connect store. 3. We check the credentials with Shopify (the two scopes, and a store that sells in US dollars), keep them encrypted, and subscribe to your store's new and cancelled orders. ## 4. Test it 1. Switch your dashboard to test mode and connect the store there first. 2. Place an order on your store with the new payment method. The invoice goes to your team, marked [TEST], instead of the customer. 3. Pay it with the test card 4242 4242 4242 4242, any future expiry and any three-digit code. The order turns Paid in Shopify within a minute or two. 4. Cancel that test order in Shopify, disconnect the store in test mode, then switch back and connect it to your live business. ## What the invoice says The invoice is the order exactly as Shopify priced it, never recalculated. It's due in three days, with your usual invoice reminders, and its note names the order ("Order #1001 from your store"). | On the Shopify order | On the invoice | | --- | --- | | Each item, its variant, quantity and unit price | One line per item (free items are named in the note) | | Discounts on items and the order | The invoice's discount | | Each shipping line, after shipping discounts | A shipping line | | Tax lines (when prices don't already include tax) | One line per tax, with its rate | | Tax included in prices | A note saying how much tax the prices include | | Tip, duties and import taxes | Their own lines | | The customer's name, email and billing address | Who the invoice is for | The lines must add up to exactly what the order still owes. If they don't (a gift card paid part of it, say), the order isn't billed and you're told why. ## Orders we don't bill Only orders placed with your connected payment method and still waiting for payment are ours. Anything we can't bill shows as **Not billed** on **Checkout tools → Shopify store** with the reason, and owners and admins get a **Store order not billed** alert. Collect those another way. | Reason | What to do | | --- | --- | | The order isn't in US dollars | Collect it with another payment method. | | The amount is outside $1.00 to $100,000.00 | Collect it another way, or split the order. | | Shopify sent no customer email | Give your Dev Dashboard app access to the customer's email (protected customer data), or send an invoice yourself. | | The total can't be itemized (a gift card, say) | Send an invoice or payment link for what's owed. | | Card payments are off, or sales tax is on in Anytime Capital | Fix the setting, then choose **Try again** on the order. | ## When the customer pays We read the order from Shopify again, and if it still owes exactly what the customer paid and wasn't cancelled, we record the payment on it, so Shopify shows it as Paid. If the order changed or was cancelled in the meantime, nothing is written to Shopify and owners and admins get a **Store payment needs attention** alert to sort it out. Invoices you mark paid yourself (cash or a bank transfer) are picked up within a few minutes too. ## Refunds and cancellations - Refund from **Orders** in Anytime Capital, as for any payment. The refund is recorded on the Shopify order against the payment we recorded; Shopify moves no money for a manual payment method, so the customer is refunded once, by us. - Don't refund these orders in Shopify: Shopify would only record it, and no money would go back to the customer. - Cancel an unpaid order in Shopify and we void its invoice, so it can't be paid. - Cancel an order that was already paid and we don't refund it for you: owners and admins are told, and you refund from **Orders** if you meant to. ## Security and your data - Your app's credentials are encrypted at rest, never shown again, and deleted when you disconnect. - We subscribe to two order notifications at an address only your connection knows, and ignore any notification whose signature doesn't match your app's secret. - We keep an order's number, total and customer email, and the invoice itself; nothing else from your store. - Disconnect any time from **Checkout tools → Shopify store**: we remove our notifications from Shopify and delete the credentials. Invoices already sent can still be paid. Delete the app in Shopify too if you no longer need it. ## Shopify API details For developers and AI assistants. Admin API version `2026-10`, GraphQL at `https://{store}.myshopify.com/admin/api/2026-10/graphql.json`. | When | Call | | --- | --- | | Getting a token (Dev Dashboard app) | `POST /admin/oauth/access_token` with `grant_type=client_credentials` | | Connecting | `shop { name currencyCode }`, `currentAppInstallation { accessScopes }`, then `webhookSubscriptionCreate` for `ORDERS_CREATE` and `ORDERS_CANCELLED` | | An order arrives | The `orders/create` webhook, checked with `X-Shopify-Hmac-Sha256` (base64 HMAC-SHA256 of the body, keyed with your app's secret) | | The invoice is paid | `order { displayFinancialStatus cancelledAt totalOutstandingSet }`, then `orderMarkAsPaid` | | A refund | `refundCreate` with a `REFUND` transaction against the order's sale, sent with `@idempotent` | | Disconnecting | `webhookSubscriptionDelete` | - [Shopify payments app](https://anytime-capital.com/docs/shopify.md): Anytime Capital in Shopify's own checkout, once Shopify approves it. - [Invoices](https://anytime-capital.com/docs/invoices.md): How invoices, reminders and payments work. - [Refunds](https://anytime-capital.com/docs/refunds.md): Refunding a payment. - [Test mode](https://anytime-capital.com/docs/test-mode.md): Test cards and test data. --- # WooCommerce plugin > Add Anytime Capital Payments to WooCommerce: install the plugin, connect your API key and a signed webhook, and take card payments with refunds from orders. Source: https://anytime-capital.com/docs/woocommerce · Markdown: https://anytime-capital.com/docs/woocommerce.md · All docs: https://anytime-capital.com/docs/llms-full.txt The Anytime Capital Payments plugin adds card payments to WooCommerce. At checkout the order total becomes a one-time payment link, the customer pays on Anytime Capital's secure checkout, and a signed webhook marks the order paid. It works with the classic checkout and the Cart and Checkout blocks, and with WooCommerce's order storage (HPOS). - [Download the plugin](https://anytime-capital.com/api/integrations/woocommerce/download): anytime-capital-payments.zip, ready for Plugins → Add New → Upload Plugin. ## Requirements - WordPress 6.2 or later, WooCommerce 8.0 or later, PHP 7.4 or later. - Your store's currency set to US dollars. The method doesn't show for other currencies. - Orders from $1.00 to $10,000.00. - An Anytime Capital business account with card payments switched on. - Your store served over https, so customers return to your order confirmation after paying. ## Set it up 1. In WordPress, go to **Plugins → Add New → Upload Plugin**, upload `anytime-capital-payments.zip` and activate it. 2. In your Anytime Capital dashboard, open **Settings → Developers** and create an API key. Copy it (`ak_live_…`). 3. Still in **Settings → Developers**, add a webhook endpoint for `https://your-store.example/wp-json/anytime-capital/v1/webhook` (the plugin settings show your exact URL) with the events `payment.succeeded`, `payment.failed` and `payment.refunded`. Copy its signing secret (`whsec_…`). 4. In WordPress, go to **WooCommerce → Settings → Payments → Anytime Capital Payments**. Paste the API key and the signing secret, check the title and description customers see, and enable it. 5. Check the whole flow in test mode first (below), then place a small real order and refund it from the order screen. ## Test mode 1. In your Anytime Capital dashboard, turn on **Test mode** in the header. In **Settings → Developers**, create a test key (`ak_test_…`) and a webhook endpoint for the same URL and events, and copy its signing secret. 2. In the plugin settings, tick **Test mode** and paste the test key and the test signing secret. Your live key and secret stay saved. 3. Place an order and pay with a [test card](https://anytime-capital.com/docs/test-mode.md#test-cards). No real money moves; the order's notes say it was a test. 4. Untick **Test mode** to take real payments with your live key. A live key never works in test mode and a test key never works in live mode: the method is hidden at checkout until the key matches. A test webhook only ever changes test orders and a live one only live orders, so a live order still pending when you switch is completed as usual. Refund a test order with Test mode on. ## How an order is paid 1. The customer chooses the method and places the order; it waits as **Pending payment**. 2. The plugin creates a one-time payment link for the order total, with the order id, order key and your site in its `metadata`, and your order confirmation page as its `successUrl`. 3. The customer pays on Anytime Capital's checkout and returns to your store. 4. We send `payment.succeeded` to your webhook. The plugin checks the `Anytime-Signature` header (HMAC-SHA256 with your signing secret, at most 5 minutes old, compared in constant time), matches the order by id and key, checks the amount, and marks it paid with the payment's id as the transaction id. The order is marked paid only by the webhook, never by the customer reaching the confirmation page, and only once however many times the event arrives. A declined card marks a pending order **Failed**; the customer can still pay on the same link, and a later success completes the order. ## Refunds - Refund from the order screen with **Refund via Anytime Capital Payments**, in full or in part. - If your business requires an owner's approval for refunds over a limit, the refund waits for that approval and is added to the order once it's sent. - Refunds made in your Anytime Capital dashboard are added to the order by the `payment.refunded` webhook, up to the payment's refunded total, so nothing is counted twice. ## Good to know - Your store calculates tax and shipping and we charge the order total. If you collect sales tax in Anytime Capital, turn it off for your store's payments so customers aren't taxed twice. - One Anytime Capital business can serve several stores: each store only acts on its own orders. - Deleting the plugin removes its settings, including your API key and signing secret. Order notes and transaction ids stay on your orders. - Turn on **Debug log** in the plugin settings to see each step under **WooCommerce → Status → Logs**. Keys and secrets are never logged. - [Webhooks](https://anytime-capital.com/docs/webhooks.md): Events, retries and signature verification. - [Payment links API](https://anytime-capital.com/docs/payment-links.md): The payment links the plugin creates. - [Authentication](https://anytime-capital.com/docs/authentication.md): API keys and how to keep them safe. --- # Zapier > Connect Anytime Capital Payments to your other apps with Zapier: start Zaps on payments, refunds and invoices, and create payment links, invoices and refunds. Source: https://anytime-capital.com/docs/zapier · Markdown: https://anytime-capital.com/docs/zapier.md · All docs: https://anytime-capital.com/docs/llms-full.txt The Anytime Capital Payments app for Zapier connects your payments to the other tools you use, without code. Zaps start the moment something happens in your account, and can create payment links, invoices, customers and refunds for you. > **Coming soon:** The app isn't in Zapier's app directory yet. This page shows what it will do; we'll let you know in your dashboard when it's available. ## Connect your account 1. In your Anytime Capital dashboard, open **Settings → Developers** and create an API key named **Zapier**. 2. In Zapier, add an Anytime Capital Payments step and choose **Sign in**. Paste the key (`ak_live_…`). 3. To build and try your Zaps without real money, connect with a test key (`ak_test_…`) made with **Test mode** on: the Zaps see your test data and test events only, and the [event simulator](https://anytime-capital.com/docs/test-mode.md#test-your-integration) fires **New Payment** and **Invoice Paid** on demand. Reconnect with a live key to go live. 4. To disconnect, revoke the key in **Settings → Developers**. ## Triggers | Trigger | Starts when | | --- | --- | | New Payment | A customer pays you by card (`payment.succeeded`). | | Payment Refunded | All or part of a payment is refunded (`payment.refunded`). `amountRefundedCents` is the total so far. | | Invoice Paid | An invoice is paid in full (`invoice.paid`). | | Subscription Created | A customer subscribes to a plan (`subscription.created`). | | Subscription Canceled | A subscription ends (`subscription.canceled`). | | New Customer | A customer is added to your customers. Checked every few minutes. | Every trigger but New Customer is instant: turning the Zap on adds a webhook endpoint for its event in **Settings → Developers** (named "Zapier: …"), and turning it off removes it. Zapier checks each event's signature before using it. A business can have up to 10 webhook endpoints, and each instant Zap uses one. ## Actions and searches | Step | What it does | | --- | --- | | Create Payment Link | A payment link for an amount in US dollars, one-time or reusable, with an optional success URL and metadata. | | Create & Send Invoice | An invoice with line items, tax and a due date, emailed to the customer to pay by card. | | Create Customer | Adds a customer to your customers. | | Create Refund | Refunds a payment in full, or the amount you enter. | | Find Customer | Finds a customer by email; with **Find or Create**, adds them if there's none. | | Find Payment | Finds a payment by its id (`pay_…`). | Amounts are entered in dollars, like `125.50`, and sent to the API in cents. Refunds over your business's approval limit wait for an owner's approval, as they do from the API. ## Ideas - New Payment → add a row to a spreadsheet, or a sale to your accounting app. - A form submission → Create & Send Invoice. - New deal won in your CRM → Create Payment Link → email the link. - Payment Refunded → notify your team in chat. - [Webhooks](https://anytime-capital.com/docs/webhooks.md): The events behind the instant triggers. - [Authentication](https://anytime-capital.com/docs/authentication.md): API keys and how to keep them safe. - [Customers API](https://anytime-capital.com/docs/customers.md): What Create Customer and Find Customer use. --- # Sale Alerts in Slack, Discord and Microsoft Teams > Post new payments, refunds, disputes, payouts, subscriptions, invoices and a daily summary to your team's Slack, Discord or Microsoft Teams channel. Source: https://anytime-capital.com/docs/team-chat-alerts · Markdown: https://anytime-capital.com/docs/team-chat-alerts.md · All docs: https://anytime-capital.com/docs/llms-full.txt Your team hears about sales where it already talks. Connect a Slack, Discord or Microsoft Teams channel in **Settings → Notifications → Team chat alerts**, choose which events post there, and each one arrives as a short card with the amount, the customer, what they bought and a link into your dashboard. No code: each channel's own incoming webhook does the posting. ## What can post to a channel | Alert | Posts when | | --- | --- | | New payment | A customer pays: amount, customer, what they bought and how they paid. | | Refund issued | Money goes back to a customer, full or partial. | | Dispute opened / response due soon | A customer disputes a payment, and again as the deadline to respond gets close. | | Payout sent / failed | A payout reaches, or doesn't reach, your bank account. | | New subscription / subscription canceled | A customer subscribes, cancels or their subscription ends. | | Invoice paid / overdue | An invoice is paid, or passes its due date unpaid. | | Payment held for review | A payment is waiting for you to approve or reject it. | | Daily summary | About 8am in your business's time zone: yesterday's gross and net volume, payments, refunds, failed payments, disputes and top sources. Quiet days are skipped. | Every other team alert in **Settings → Notifications** (failed payments, plan changes, quotes accepted, team changes and more) can post too. Alerts only owners can receive, like refunds waiting for an owner's approval, never post to a channel, since anyone in the channel can read it. ## Connect a channel 1. Create an incoming webhook in your chat app (the steps for each app are below) and copy its URL. 2. In your dashboard, open **Settings → Notifications** and choose **Add channel** under Team chat alerts. Owners and admins can add channels. 3. Pick the app, name the channel (for example `#sales`), paste the URL and choose the alerts. 4. Choose **Test** to post a test message, and check it arrived. ### Slack Create a Slack app for your workspace with **Incoming Webhooks** turned on, add a webhook to the channel, and copy its URL. It starts with `https://hooks.slack.com/services/`. Slack Workflow Builder links (`/triggers/…`) don't accept formatted messages, so use an incoming webhook. ### Discord Open the channel's **Edit Channel → Integrations → Webhooks**, create a webhook and copy its URL. It starts with `https://discord.com/api/webhooks/`. Messages never mention or ping anyone, whatever a customer's name says. ### Microsoft Teams In the channel, open **Workflows** and choose the template **Send webhook alerts to a channel**, then copy the URL from its **When a Teams webhook request is received** step. Workflow URLs are on `api.powerplatform.com` (older ones on `logic.azure.com`) and end in `sig=…`; paste the whole URL. Microsoft retired the old Office 365 connector webhooks (`webhook.office.com`) in May 2026, so those aren't accepted. ## Privacy Turn on **Hide customer email addresses** for a channel and every email address is left out of its messages; names and amounts still show. Each message links to the payment, invoice or subscription in your dashboard, where the details are behind your team's own sign-in. ## Delivery and failures - Each event posts once per channel, even if it is seen more than once. - A message that can't be delivered is retried after 1, 5 and 30 minutes. A deleted or revoked webhook isn't retried. - After 5 messages in a row fail, the channel pauses itself and your business's owners get an email. Fix the webhook (or paste a new URL), send a test and choose **Resume**. - Each channel shows its status, the last message sent, the last error and how many messages were sent and failed this week. - A business can connect up to 10 channels. > **Your webhook URL is a password:** Anyone with a channel's webhook URL can post to it. We store it encrypted, only ever send messages to the app's own webhook address, and never show the full URL again after you save it. If it leaks, delete the webhook in your chat app and paste a new one. For your own systems rather than a chat channel, use signed [webhooks](https://anytime-capital.com/docs/webhooks.md), or connect other apps with [Zapier](https://anytime-capital.com/docs/zapier.md). --- # Payments API > List and retrieve payments with the Anytime Capital Payments API: status, amounts, net after fees, method, customer, metadata and disputes, filtered by date. Source: https://anytime-capital.com/docs/payments · Markdown: https://anytime-capital.com/docs/payments.md · All docs: https://anytime-capital.com/docs/llms-full.txt A payment is one charge (by card, card wallet, bank account or stablecoin) on a payment link, an invoice or a subscription. Customers create payments at checkout; the API reads them. To give money back, see [Refunds](https://anytime-capital.com/docs/refunds.md); to follow chargebacks, [Disputes](https://anytime-capital.com/docs/disputes.md). | Topic | Detail | | --- | --- | | Endpoints | [`GET /api/v1/payments`](#list-payments), [`GET /api/v1/payments/{id}`](#get-payment) | | Key permissions | `payments:read` | | Samples | curl; Node.js 18+ with the built-in `fetch` (in an ES module); the [Node SDK](https://anytime-capital.com/docs/node-sdk.md) (preview: not on npm yet); Python with `requests`. | | Every request can also answer | 401 `UNAUTHORIZED`, 401 `API_KEY_EXPIRED`, 403 `NOT_ENROLLED`, 403 `PERMISSION_DENIED`, 403 `IP_NOT_ALLOWED`, 429 `RATE_LIMITED`, 500 `INTERNAL_ERROR`. See [Error codes](https://anytime-capital.com/docs/error-codes.md). | ## The payment object ```json { "object": "payment", "livemode": true, "id": "pay_7Hq2LmX9cR4tVb", "status": "paid", "amountCents": 12500, "amountRefundedCents": 0, "netCents": 12088, "currency": "usd", "description": "Consulting — October", "source": { "type": "payment_link", "id": "pl_4kTz8QwN2xYb6RcD1mPa" }, "paymentLinkId": "pl_4kTz8QwN2xYb6RcD1mPa", "customer": { "name": "Jordan Lee", "email": "jordan@example.com", "country": "US" }, "card": { "brand": "visa", "last4": "4242" }, "paymentMethod": { "type": "card", "label": "Visa •••• 4242" }, "clientReference": "order_1042", "metadata": { "campaign": "fall-launch" }, "amountDetails": { "subtotalCents": 12500, "discountCents": 0, "shippingCents": 0, "taxCents": 0, "customerFeeCents": 0 }, "dispute": null, "failureMessage": null, "createdAt": "2026-10-14T16:02:11.000Z", "paidAt": "2026-10-14T16:02:15.000Z" } ``` | Field | Type | Description | | --- | --- | --- | | `object` | string | The type of object. Always `payment`. | | `livemode` | boolean | False for test-mode data (an `ak_test_` key); true for live data. | | `id` | string | `pay_…` | | `status` | string | `paid`, `pending`, `failed`, `canceled`, `refunded`, `partially_refunded`, `disputed`: the payment's state now, as **Orders** shows it. | | `amountCents` | integer | What the customer paid. Integer cents. | | `amountRefundedCents` | integer | Refunded so far. Integer cents. | | `netCents` | integer or null | What you keep after fees, once known; null until then. | | `currency` | string | Lower-case ISO 4217, e.g. usd. | | `description` | string | The link's title or `Invoice INV-0001`. | | `source` | object | | | `source.type` | string | One of `payment_link`, `invoice`, `subscription`, `manual`. | | `source.id` | string or null | The payment link, invoice or subscription id. | | `paymentLinkId` | string or null | The payment link it was paid on; null for invoices, subscriptions and manual charges. With `metadata`, how a shop matches a payment to its order. | | `customer` | object | | | `customer.name` | string or null | Billing name. | | `customer.email` | string | Customer email. | | `customer.country` | string or null | Billing country, ISO 3166-1 alpha-2. | | `paymentMethod` | object or null | | | `paymentMethod.type` | string | `card`, `apple_pay`, `google_pay`, `us_bank_account` or `stablecoin`; more may be added. | | `paymentMethod.label` | string | Visa •••• 4242. | | `card` | object or null | | | `card.brand` | string or null | Card network. | | `card.last4` | string or null | Last four digits. | | `clientReference` | string or null | Your own reference, from `?ref=` on the link or `data-ref` on your website's Pay button. | | `metadata` | object | The payment link's metadata when the payment was made; `{}` when it had none. | | `amountDetails` | object | | | `amountDetails.subtotalCents` | integer or null | Before discount, shipping and fees. Integer cents. | | `amountDetails.discountCents` | integer | Discount code savings. Integer cents. | | `amountDetails.shippingCents` | integer | Shipping. Integer cents. | | `amountDetails.taxCents` | integer | Sales tax. Integer cents. | | `amountDetails.customerFeeCents` | integer | A surcharge the customer paid. Integer cents. | | `dispute` | object or null | | | `dispute.status` | string | One of `needs_response`, `under_review`, `won`, `lost`, `closed`. | | `failureMessage` | string or null | Why the latest attempt failed. | | `createdAt` | string (date-time) | When the payment was started. | | `paidAt` | string (date-time) or null | When it went through. | > **Some payments take days:** A bank account payment (`us_bank_account`) stays `pending` for 3–5 business days before it is `paid` or `failed`. Fulfil on the `payment.succeeded` [webhook](https://anytime-capital.com/docs/webhooks.md), not when checkout finishes. ## Endpoints ### List payments `GET /api/v1/payments` Permission: `payments:read`. Card, wallet and bank payments on links, invoices and subscriptions, newest first. **Query** | Name | Type | Required | Description | | --- | --- | --- | --- | | `status` | string | no | Only payments with this status. One of `paid`, `pending`, `failed`, `canceled`, `refunded`, `partially_refunded`, `disputed`. | | `createdFrom` | string | no | Created at or after: a date such as `2026-10-01` (midnight UTC) or an ISO 8601 date-time with its offset, such as `2026-10-01T12:00:00Z`. | | `createdTo` | string | no | Created before (exclusive), in the same format. Must be after `createdFrom`. | | `limit` | integer | no | How many objects per page, 1–100. Default 25. See [Pagination](https://anytime-capital.com/docs/pagination.md). | | `cursor` | string | no | The previous page's `nextCursor`, for the page after it. | **Request (curl)** ```bash curl "https://anytime-capital.com/api/v1/payments?status=paid&createdFrom=2026-10-01&createdTo=2026-11-01" \ -H "Authorization: Bearer $ANYTIME_API_KEY" ``` **Request (Node.js)** ```javascript const res = await fetch("https://anytime-capital.com/api/v1/payments?status=paid&createdFrom=2026-10-01&createdTo=2026-11-01", { headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}` }, }); const page = await res.json(); if (!res.ok) throw new Error(`${page.error.code}: ${page.error.message}`); ``` **Request (Node SDK)** ```typescript import { AnytimeCapital } from "@anytimecapital/node"; const client = new AnytimeCapital({ apiKey: process.env.ANYTIME_API_KEY! }); const page = await client.payments.list( { status: "paid", createdFrom: "2026-10-01", createdTo: "2026-11-01" }, ); ``` **Request (Python)** ```python import os import requests res = requests.get( "https://anytime-capital.com/api/v1/payments", headers={"Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}"}, params={ "status": "paid", "createdFrom": "2026-10-01", "createdTo": "2026-11-01", }, timeout=30, ) page = res.json() if not res.ok: raise RuntimeError(f"{page['error']['code']}: {page['error']['message']}") ``` **Response** (200) ```json { "object": "list", "data": [ { "object": "payment", "livemode": true, "id": "pay_7Hq2LmX9cR4tVb", "status": "paid", "amountCents": 12500, "amountRefundedCents": 0, "netCents": 12088, "currency": "usd", "description": "Consulting — October", "source": { "type": "payment_link", "id": "pl_4kTz8QwN2xYb6RcD1mPa" }, "paymentLinkId": "pl_4kTz8QwN2xYb6RcD1mPa", "customer": { "name": "Jordan Lee", "email": "jordan@example.com", "country": "US" }, "card": { "brand": "visa", "last4": "4242" }, "paymentMethod": { "type": "card", "label": "Visa •••• 4242" }, "clientReference": "order_1042", "metadata": { "campaign": "fall-launch" }, "amountDetails": { "subtotalCents": 12500, "discountCents": 0, "shippingCents": 0, "taxCents": 0, "customerFeeCents": 0 }, "dispute": null, "failureMessage": null, "createdAt": "2026-10-14T16:02:11.000Z", "paidAt": "2026-10-14T16:02:15.000Z" } ], "hasMore": true, "nextCursor": "pay_7Hq2LmX9cR4tVb" } ``` **Errors** | Status | Code | When | | --- | --- | --- | | 400 | `INVALID_INPUT` | A field, filter, `limit` or `cursor` is missing or invalid. The message starts with the field's name, as in `amountCents: …`, or reads `Invalid cursor.` Fix the named field. Don't retry the same request unchanged. | ### Get a payment `GET /api/v1/payments/{id}` Permission: `payments:read`. One payment by id. **Path** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | The payment's id, `pay_…`. | **Request (curl)** ```bash curl https://anytime-capital.com/api/v1/payments/pay_7Hq2LmX9cR4tVb \ -H "Authorization: Bearer $ANYTIME_API_KEY" ``` **Request (Node.js)** ```javascript const res = await fetch("https://anytime-capital.com/api/v1/payments/pay_7Hq2LmX9cR4tVb", { headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}` }, }); const payment = await res.json(); if (!res.ok) throw new Error(`${payment.error.code}: ${payment.error.message}`); ``` **Request (Node SDK)** ```typescript import { AnytimeCapital } from "@anytimecapital/node"; const client = new AnytimeCapital({ apiKey: process.env.ANYTIME_API_KEY! }); const payment = await client.payments.retrieve("pay_7Hq2LmX9cR4tVb"); ``` **Request (Python)** ```python import os import requests res = requests.get( "https://anytime-capital.com/api/v1/payments/pay_7Hq2LmX9cR4tVb", headers={"Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}"}, timeout=30, ) payment = res.json() if not res.ok: raise RuntimeError(f"{payment['error']['code']}: {payment['error']['message']}") ``` **Response** (200) ```json { "object": "payment", "livemode": true, "id": "pay_7Hq2LmX9cR4tVb", "status": "paid", "amountCents": 12500, "amountRefundedCents": 0, "netCents": 12088, "currency": "usd", "description": "Consulting — October", "source": { "type": "payment_link", "id": "pl_4kTz8QwN2xYb6RcD1mPa" }, "paymentLinkId": "pl_4kTz8QwN2xYb6RcD1mPa", "customer": { "name": "Jordan Lee", "email": "jordan@example.com", "country": "US" }, "card": { "brand": "visa", "last4": "4242" }, "paymentMethod": { "type": "card", "label": "Visa •••• 4242" }, "clientReference": "order_1042", "metadata": { "campaign": "fall-launch" }, "amountDetails": { "subtotalCents": 12500, "discountCents": 0, "shippingCents": 0, "taxCents": 0, "customerFeeCents": 0 }, "dispute": null, "failureMessage": null, "createdAt": "2026-10-14T16:02:11.000Z", "paidAt": "2026-10-14T16:02:15.000Z" } ``` **Errors** | Status | Code | When | | --- | --- | --- | | 404 | `NOT_FOUND` | No such object on your business (an id from another business reads the same), or no such endpoint under `/api/v1`, which still answers in JSON. Check the id and the path. Ids are case-sensitive. | ## Related - [Accept a payment in Next.js](https://anytime-capital.com/docs/nextjs-checkout.md): A payment link per order and a verified webhook, end to end. - [Reconcile payouts](https://anytime-capital.com/docs/reconcile-payouts.md): A nightly job over payments, refunds and payouts. - [Webhooks](https://anytime-capital.com/docs/webhooks.md): payment.succeeded, payment.failed, payment.refunded and more. --- # Refunds API > Refund a payment in full or in part with the Anytime Capital Payments API, safely retried with an Idempotency-Key, within your refund approval limit. Source: https://anytime-capital.com/docs/refunds · Markdown: https://anytime-capital.com/docs/refunds.md · All docs: https://anytime-capital.com/docs/llms-full.txt Refund all or part of a payment, never more than is left. Every refund is listed here however it was made (the dashboard, the API, an approved request), each with its own id (`re_…`). The money goes back to the card or account that paid. For the full pattern with retries and approvals, see [Issue refunds safely](https://anytime-capital.com/docs/refund-safely.md). | Topic | Detail | | --- | --- | | Endpoints | [`POST /api/v1/refunds`](#create-refund), [`GET /api/v1/refunds`](#list-refunds), [`GET /api/v1/refunds/{id}`](#get-refund) | | Key permissions | `refunds:write`, `refunds:read` | | Samples | curl; Node.js 18+ with the built-in `fetch` (in an ES module); the [Node SDK](https://anytime-capital.com/docs/node-sdk.md) (preview: not on npm yet); Python with `requests`. | | Every request can also answer | 401 `UNAUTHORIZED`, 401 `API_KEY_EXPIRED`, 403 `NOT_ENROLLED`, 403 `PERMISSION_DENIED`, 403 `IP_NOT_ALLOWED`, 429 `RATE_LIMITED`, 500 `INTERNAL_ERROR`. See [Error codes](https://anytime-capital.com/docs/error-codes.md). | > **Refund approval limits:** An API key refunds like an admin. If your business has a refund approval limit (**Settings → Team & approvals**) and a refund is over it, nothing is refunded: the answer is `202` with `status: "pending_approval"` and an id starting `re_appr_`, and your owners are asked to approve it. Retrieve that id later: once approved, `refundId` names the refund it became; if declined, `status` is `failed` with `failureReason: "approval_declined"`. > **Stablecoin payments:** Payments made in stablecoins (ids starting `pay_sc`) went straight to your own wallet, so they're refunded by sending the tokens back from it, never through this endpoint. See [Stablecoin checkout](https://anytime-capital.com/docs/stablecoin-checkout.md#refunds). ## The refund object ```json { "object": "refund", "livemode": true, "id": "re_8812", "paymentId": "pay_7Hq2LmX9cR4tVb", "amountCents": 2500, "currency": "usd", "reason": "Requested by customer", "status": "succeeded", "failureReason": null, "refundId": null, "createdAt": "2026-10-20T09:12:44.000Z" } ``` | Field | Type | Description | | --- | --- | --- | | `object` | string | The type of object. Always `refund`. | | `livemode` | boolean | False for test-mode data (an `ak_test_` key); true for live data. | | `id` | string | `re_…` for a refund; `re_appr_…` for one that needed an owner's approval. | | `paymentId` | string | The refunded payment, `pay_…`. | | `amountCents` | integer | Refunded (or to be refunded). Integer cents. | | `currency` | string | usd. | | `reason` | string or null | Why, as recorded: a preset (`requested_by_customer`, `duplicate`, `fraudulent`, `other`) shown as words, your own note, or both. | | `status` | string | `succeeded`; `pending` (sent, not settled yet); `pending_approval` (waiting for an owner: nothing refunded yet); or `failed`. | | `failureReason` | string or null | One of `approval_declined`, `failed`, `canceled`. | | `refundId` | string or null | For an approved `re_appr_…`: the refund it became, `re_…`. | | `createdAt` | string (date-time) | When it was made or asked for. | ## Endpoints ### Refund a payment `POST /api/v1/refunds` Permission: `refunds:write`. Supports `Idempotency-Key`. Refund all or part of a payment, never more than is left. Over the business's refund approval limit nothing is refunded: the answer is 202 with status pending_approval while an owner decides. Answers `201` with the refund, or `202` with `status: "pending_approval"` and an id starting `re_appr_` when it is over your business's refund approval limit. The Idempotency-Key also keys the refund at the payments network, so a retry after a lost response never refunds twice. **Headers** | Header | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | Recommended. A unique key such as a UUID: a retry with the same key within 24 hours gets the first response back instead of acting twice. See [Idempotency](https://anytime-capital.com/docs/idempotency.md). | **Body** | Name | Type | Required | Description | | --- | --- | --- | --- | | `paymentId` | string | yes | The payment to refund, `pay_…`. | | `amountCents` | integer | no | How much, in cents. Leave out to refund everything still left. At least 1 ($0.01). | | `reason` | string | no | requested_by_customer, duplicate, fraudulent, other; or your own words. Up to 500 characters. | | `note` | string | no | Added to a preset reason. Up to 500 characters. | **Request (curl)** ```bash curl -X POST https://anytime-capital.com/api/v1/refunds \ -H "Authorization: Bearer $ANYTIME_API_KEY" \ -H "Idempotency-Key: refund-order-1042" \ -H "Content-Type: application/json" \ -d '{ "paymentId": "pay_7Hq2LmX9cR4tVb", "amountCents": 2500, "reason": "requested_by_customer" }' ``` **Request (Node.js)** ```javascript const res = await fetch("https://anytime-capital.com/api/v1/refunds", { method: "POST", headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}`, "Content-Type": "application/json", "Idempotency-Key": "refund-order-1042", }, body: JSON.stringify({ paymentId: "pay_7Hq2LmX9cR4tVb", amountCents: 2500, reason: "requested_by_customer", }), }); const refund = await res.json(); if (!res.ok) throw new Error(`${refund.error.code}: ${refund.error.message}`); ``` **Request (Node SDK)** ```typescript import { AnytimeCapital } from "@anytimecapital/node"; const client = new AnytimeCapital({ apiKey: process.env.ANYTIME_API_KEY! }); const refund = await client.refunds.create( { paymentId: "pay_7Hq2LmX9cR4tVb", amountCents: 2500, reason: "requested_by_customer", }, { idempotencyKey: "refund-order-1042" }, ); ``` **Request (Python)** ```python import os import requests res = requests.post( "https://anytime-capital.com/api/v1/refunds", headers={ "Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}", "Idempotency-Key": "refund-order-1042", }, json={ "paymentId": "pay_7Hq2LmX9cR4tVb", "amountCents": 2500, "reason": "requested_by_customer", }, timeout=30, ) refund = res.json() if not res.ok: raise RuntimeError(f"{refund['error']['code']}: {refund['error']['message']}") ``` **Response** (201) ```json { "object": "refund", "livemode": true, "id": "re_8812", "paymentId": "pay_7Hq2LmX9cR4tVb", "amountCents": 2500, "currency": "usd", "reason": "Requested by customer", "status": "succeeded", "failureReason": null, "refundId": null, "createdAt": "2026-10-20T09:12:44.000Z" } ``` **Errors** | Status | Code | When | | --- | --- | --- | | 400 | `INVALID_JSON` | The body isn't valid JSON, or couldn't be read. An empty body reads as `{}`. Send `Content-Type: application/json` and a JSON object. | | 400 | `INVALID_INPUT` | A field, filter, `limit` or `cursor` is missing or invalid. The message starts with the field's name, as in `amountCents: …`, or reads `Invalid cursor.` Fix the named field. Don't retry the same request unchanged. | | 400 | `INVALID_IDEMPOTENCY_KEY` | The `Idempotency-Key` header is empty, longer than 255 characters, or has characters other than printable ASCII. Use a UUID. See [Idempotency](https://anytime-capital.com/docs/idempotency.md). | | 404 | `NOT_FOUND` | No such object on your business (an id from another business reads the same), or no such endpoint under `/api/v1`, which still answers in JSON. Check the id and the path. Ids are case-sensitive. | | 409 | `NOT_REFUNDABLE` | The payment can't be refunded in its current state, or it was paid in stablecoins, which settle straight to your wallet. Check the payment's `status` and `amountRefundedCents` with `GET /api/v1/payments/{id}`. Refund a stablecoin payment by sending the tokens back from your wallet. | | 409 | `REFUND_PENDING_APPROVAL` | Another refund on this payment is already waiting for an owner's approval. Wait for the owner's decision: `GET /api/v1/refunds?paymentId=…&status=pending_approval` shows it. | | 422 | `IDEMPOTENCY_KEY_REUSED` | The `Idempotency-Key` was used in the last 24 hours with a different body or endpoint. Use a new key for a new request; reuse a key only to retry the same request. | | 500 | `REFUND_NOT_RECORDED` | The refund was sent, but our record of it isn't there yet. Don't refund again. List `GET /api/v1/refunds?paymentId=…` in a minute to find it. | | 502 | `PROCESSOR_ERROR` | The payments network didn't answer in time. Retry with the same `Idempotency-Key`. A refund is keyed at the network by that key too, so a retry never refunds twice. | | 503 | `NOT_CONFIGURED` | The part of Anytime Capital Payments this needs isn't available right now. Retry later, with the same `Idempotency-Key` on a write. | ### List refunds `GET /api/v1/refunds` Permission: `refunds:read`. Refunds however they were made, newest first. `status=pending_approval` lists refunds waiting for an owner's approval instead. **Query** | Name | Type | Required | Description | | --- | --- | --- | --- | | `paymentId` | string | no | Only refunds of this payment. | | `status` | string | no | `succeeded`, `pending` or `failed`; or `pending_approval` for the refunds waiting for an owner's approval (a separate list: its cursor works only with the same filter). | | `limit` | integer | no | How many objects per page, 1–100. Default 25. See [Pagination](https://anytime-capital.com/docs/pagination.md). | | `cursor` | string | no | The previous page's `nextCursor`, for the page after it. | **Request (curl)** ```bash curl "https://anytime-capital.com/api/v1/refunds?paymentId=pay_7Hq2LmX9cR4tVb" \ -H "Authorization: Bearer $ANYTIME_API_KEY" ``` **Request (Node.js)** ```javascript const res = await fetch("https://anytime-capital.com/api/v1/refunds?paymentId=pay_7Hq2LmX9cR4tVb", { headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}` }, }); const page = await res.json(); if (!res.ok) throw new Error(`${page.error.code}: ${page.error.message}`); ``` **Request (Node SDK)** ```typescript import { AnytimeCapital } from "@anytimecapital/node"; const client = new AnytimeCapital({ apiKey: process.env.ANYTIME_API_KEY! }); const page = await client.refunds.list({ paymentId: "pay_7Hq2LmX9cR4tVb" }); ``` **Request (Python)** ```python import os import requests res = requests.get( "https://anytime-capital.com/api/v1/refunds", headers={"Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}"}, params={"paymentId": "pay_7Hq2LmX9cR4tVb"}, timeout=30, ) page = res.json() if not res.ok: raise RuntimeError(f"{page['error']['code']}: {page['error']['message']}") ``` **Response** (200) ```json { "object": "list", "data": [ { "object": "refund", "livemode": true, "id": "re_8812", "paymentId": "pay_7Hq2LmX9cR4tVb", "amountCents": 2500, "currency": "usd", "reason": "Requested by customer", "status": "succeeded", "failureReason": null, "refundId": null, "createdAt": "2026-10-20T09:12:44.000Z" } ], "hasMore": false, "nextCursor": null } ``` **Errors** | Status | Code | When | | --- | --- | --- | | 400 | `INVALID_INPUT` | A field, filter, `limit` or `cursor` is missing or invalid. The message starts with the field's name, as in `amountCents: …`, or reads `Invalid cursor.` Fix the named field. Don't retry the same request unchanged. | ### Get a refund `GET /api/v1/refunds/{id}` Permission: `refunds:read`. One refund (`re_…`) or one that needed approval (`re_appr_…`). For a `re_appr_…` id: `refundId` names the refund it became once an owner approved it; a declined one is `failed` with `failureReason: "approval_declined"`. **Path** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | `re_…` or `re_appr_…`. | **Request (curl)** ```bash curl https://anytime-capital.com/api/v1/refunds/re_8812 \ -H "Authorization: Bearer $ANYTIME_API_KEY" ``` **Request (Node.js)** ```javascript const res = await fetch("https://anytime-capital.com/api/v1/refunds/re_8812", { headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}` }, }); const refund = await res.json(); if (!res.ok) throw new Error(`${refund.error.code}: ${refund.error.message}`); ``` **Request (Node SDK)** ```typescript import { AnytimeCapital } from "@anytimecapital/node"; const client = new AnytimeCapital({ apiKey: process.env.ANYTIME_API_KEY! }); const refund = await client.refunds.retrieve("re_8812"); ``` **Request (Python)** ```python import os import requests res = requests.get( "https://anytime-capital.com/api/v1/refunds/re_8812", headers={"Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}"}, timeout=30, ) refund = res.json() if not res.ok: raise RuntimeError(f"{refund['error']['code']}: {refund['error']['message']}") ``` **Response** (200) ```json { "object": "refund", "livemode": true, "id": "re_8812", "paymentId": "pay_7Hq2LmX9cR4tVb", "amountCents": 2500, "currency": "usd", "reason": "Requested by customer", "status": "succeeded", "failureReason": null, "refundId": null, "createdAt": "2026-10-20T09:12:44.000Z" } ``` **Errors** | Status | Code | When | | --- | --- | --- | | 404 | `NOT_FOUND` | No such object on your business (an id from another business reads the same), or no such endpoint under `/api/v1`, which still answers in JSON. Check the id and the path. Ids are case-sensitive. | **Over the approval limit (202)** ```json { "object": "refund", "livemode": true, "id": "re_appr_57", "paymentId": "pay_7Hq2LmX9cR4tVb", "amountCents": 75000, "currency": "usd", "reason": "Requested by customer", "status": "pending_approval", "failureReason": null, "refundId": null, "createdAt": "2026-10-20T09:12:44.000Z" } ``` ## Events `refund.created` when a refund is made, then `refund.succeeded` or `refund.failed`; the payment's own `payment.refunded` carries `amountRefundedCents`, the total refunded so far. See [Webhooks](https://anytime-capital.com/docs/webhooks.md). --- # Disputes API > Read the disputes and chargebacks on your payments with the Anytime Capital Payments API: status, reason, amount, evidence deadline and evidence pack. Source: https://anytime-capital.com/docs/disputes · Markdown: https://anytime-capital.com/docs/disputes.md · All docs: https://anytime-capital.com/docs/llms-full.txt Disputes on your payments, as the **Disputes** page lists them. The API reads them; respond with evidence from **Disputes** in the dashboard before `evidenceDueAt`. The list's `nextCursor` is opaque: pass it back exactly as it came. Disputes can only be read, so a key's `disputes` access stops at read. Each dispute carries its automatic evidence pack as `evidence`: whether it is built, how strong we read the case, whether we advise accepting, whether its PDF is attached to the response, and when it was or will be submitted. It is `null` when there is no pack. See [Dispute evidence](https://anytime-capital.com/docs/dispute-evidence.md) for what goes into a pack. | Topic | Detail | | --- | --- | | Endpoints | [`GET /api/v1/disputes`](#list-disputes), [`GET /api/v1/disputes/{id}`](#get-dispute) | | Key permissions | `disputes:read` | | Samples | curl; Node.js 18+ with the built-in `fetch` (in an ES module); the [Node SDK](https://anytime-capital.com/docs/node-sdk.md) (preview: not on npm yet); Python with `requests`. | | Every request can also answer | 401 `UNAUTHORIZED`, 401 `API_KEY_EXPIRED`, 403 `NOT_ENROLLED`, 403 `PERMISSION_DENIED`, 403 `IP_NOT_ALLOWED`, 429 `RATE_LIMITED`, 500 `INTERNAL_ERROR`. See [Error codes](https://anytime-capital.com/docs/error-codes.md). | ## The dispute object ```json { "object": "dispute", "livemode": true, "id": "dspt_7Hq2LmX9cR4t", "paymentId": "pay_7Hq2LmX9cR4tVb", "amountCents": 12500, "currency": "usd", "status": "needs_response", "reason": "product_not_received", "inquiry": false, "customer": { "name": "Jordan Lee", "email": "jordan@example.com" }, "card": { "brand": "visa", "last4": "4242" }, "evidenceDueAt": "2026-11-01T23:59:59.000Z", "evidenceSubmittedAt": null, "createdAt": "2026-10-18T08:30:00.000Z", "evidence": { "status": "ready", "strength": "strong", "recommendAccept": false, "documentReady": true, "builtAt": "2026-10-18T08:31:12.000Z", "submittedAt": null, "autoSubmitAt": "2026-10-30T23:59:59.000Z" } } ``` | Field | Type | Description | | --- | --- | --- | | `object` | string | The type of object. Always `dispute`. | | `livemode` | boolean | False for test-mode data (an `ak_test_` key); true for live data. | | `id` | string | `dspt_…` | | `paymentId` | string | `pay_…` | | `amountCents` | integer | Disputed. Integer cents. | | `currency` | string | usd. | | `status` | string | `needs_response` (respond before `evidenceDueAt`), `under_review`, then `won`, `lost` or `closed`. | | `reason` | string | What the customer's bank gave as the reason: `fraudulent`, `unrecognized`, `declined_authorization`, `product_not_received`, `product_unacceptable`, `subscription_canceled`, `credit_not_processed`, `duplicate`, `processing_error`, `documentation_request`, `bank_cannot_process`, `other`. | | `inquiry` | boolean | An inquiry or early warning rather than a chargeback. | | `customer` | object | | | `customer.name` | string or null | Name. | | `customer.email` | string or null | Email. | | `card` | object or null | | | `card.brand` | string or null | Network. | | `card.last4` | string or null | Last four. | | `evidenceDueAt` | string (date-time) or null | Respond in the dashboard before this. | | `evidenceSubmittedAt` | string (date-time) or null | Evidence submitted. | | `createdAt` | string (date-time) | Opened. | | `evidence` | object or null | The automatic evidence pack; null while there is none. | | `evidence.status` | string | `building` (just opened, or rebuilding), `ready`, or `failed` (rebuild it from **Disputes**). | | `evidence.strength` | string or null | `strong`, `fair` or `weak`: our read of the case once built, for you only. Never sent to the bank. | | `evidence.recommendAccept` | boolean | Your own records back the customer: we advise accepting, and never submit it automatically. | | `evidence.documentReady` | boolean | The pack's PDF, with the customer's receipt, is attached to the dispute response. | | `evidence.builtAt` | string (date-time) or null | Built. | | `evidence.submittedAt` | string (date-time) or null | Submitted with the pack, by your team or automatically. | | `evidence.autoSubmitAt` | string (date-time) or null | When we'll submit the response for you: automatic submission is on, nobody has submitted, we don't advise accepting, and the deadline is still ahead. Otherwise `null`. | ## Endpoints ### List disputes `GET /api/v1/disputes` Permission: `disputes:read`. Disputes on the business's payments, newest first. Respond in the dashboard. The cursor is opaque. Each dispute carries its automatic evidence pack as `evidence`: `building` right after the dispute opens, then `ready` (or `failed`), and `null` when there is none (automatic building is off and nobody has built one). See [Dispute evidence](https://anytime-capital.com/docs/dispute-evidence.md). **Query** | Name | Type | Required | Description | | --- | --- | --- | --- | | `status` | string | no | Only disputes with this status. One of `needs_response`, `under_review`, `won`, `lost`, `closed`. | | `limit` | integer | no | How many objects per page, 1–100. Default 25. See [Pagination](https://anytime-capital.com/docs/pagination.md). | | `cursor` | string | no | The previous page's `nextCursor`, for the page after it. | **Request (curl)** ```bash curl "https://anytime-capital.com/api/v1/disputes?status=needs_response" \ -H "Authorization: Bearer $ANYTIME_API_KEY" ``` **Request (Node.js)** ```javascript const res = await fetch("https://anytime-capital.com/api/v1/disputes?status=needs_response", { headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}` }, }); const page = await res.json(); if (!res.ok) throw new Error(`${page.error.code}: ${page.error.message}`); ``` **Request (Node SDK)** ```typescript import { AnytimeCapital } from "@anytimecapital/node"; const client = new AnytimeCapital({ apiKey: process.env.ANYTIME_API_KEY! }); const page = await client.disputes.list({ status: "needs_response" }); ``` **Request (Python)** ```python import os import requests res = requests.get( "https://anytime-capital.com/api/v1/disputes", headers={"Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}"}, params={"status": "needs_response"}, timeout=30, ) page = res.json() if not res.ok: raise RuntimeError(f"{page['error']['code']}: {page['error']['message']}") ``` **Response** (200) ```json { "object": "list", "data": [ { "object": "dispute", "livemode": true, "id": "dspt_3Kw9PzR6tY2m", "paymentId": "pay_3Kw9PzR6tY2mQa", "amountCents": 4900, "currency": "usd", "status": "needs_response", "reason": "fraudulent", "inquiry": false, "customer": { "name": "Sam Ortiz", "email": "sam@example.com" }, "card": { "brand": "mastercard", "last4": "4444" }, "evidenceDueAt": "2026-11-08T23:59:59.000Z", "evidenceSubmittedAt": null, "createdAt": "2026-10-24T14:02:00.000Z", "evidence": { "status": "building", "strength": null, "recommendAccept": false, "documentReady": false, "builtAt": null, "submittedAt": null, "autoSubmitAt": null } }, { "object": "dispute", "livemode": true, "id": "dspt_7Hq2LmX9cR4t", "paymentId": "pay_7Hq2LmX9cR4tVb", "amountCents": 12500, "currency": "usd", "status": "needs_response", "reason": "product_not_received", "inquiry": false, "customer": { "name": "Jordan Lee", "email": "jordan@example.com" }, "card": { "brand": "visa", "last4": "4242" }, "evidenceDueAt": "2026-11-01T23:59:59.000Z", "evidenceSubmittedAt": null, "createdAt": "2026-10-18T08:30:00.000Z", "evidence": { "status": "ready", "strength": "strong", "recommendAccept": false, "documentReady": true, "builtAt": "2026-10-18T08:31:12.000Z", "submittedAt": null, "autoSubmitAt": "2026-10-30T23:59:59.000Z" } } ], "hasMore": false, "nextCursor": null } ``` **Errors** | Status | Code | When | | --- | --- | --- | | 400 | `INVALID_INPUT` | A field, filter, `limit` or `cursor` is missing or invalid. The message starts with the field's name, as in `amountCents: …`, or reads `Invalid cursor.` Fix the named field. Don't retry the same request unchanged. | | 502 | `PROCESSOR_ERROR` | The payments network didn't answer in time. Retry with the same `Idempotency-Key`. A refund is keyed at the network by that key too, so a retry never refunds twice. | | 503 | `NOT_CONFIGURED` | The part of Anytime Capital Payments this needs isn't available right now. Retry later, with the same `Idempotency-Key` on a write. | ### Get a dispute `GET /api/v1/disputes/{id}` Permission: `disputes:read`. One dispute. `evidence.documentReady` is true once the pack's PDF is attached to the response, and `evidence.autoSubmitAt` says when we'll submit it for you (automatic submission on, nobody has submitted, and we don't advise accepting). `strength` and `recommendAccept` are our read of the case; neither is ever sent to the bank. **Path** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | `dspt_…` | **Request (curl)** ```bash curl https://anytime-capital.com/api/v1/disputes/dspt_7Hq2LmX9cR4t \ -H "Authorization: Bearer $ANYTIME_API_KEY" ``` **Request (Node.js)** ```javascript const res = await fetch("https://anytime-capital.com/api/v1/disputes/dspt_7Hq2LmX9cR4t", { headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}` }, }); const dispute = await res.json(); if (!res.ok) throw new Error(`${dispute.error.code}: ${dispute.error.message}`); ``` **Request (Node SDK)** ```typescript import { AnytimeCapital } from "@anytimecapital/node"; const client = new AnytimeCapital({ apiKey: process.env.ANYTIME_API_KEY! }); const dispute = await client.disputes.retrieve("dspt_7Hq2LmX9cR4t"); ``` **Request (Python)** ```python import os import requests res = requests.get( "https://anytime-capital.com/api/v1/disputes/dspt_7Hq2LmX9cR4t", headers={"Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}"}, timeout=30, ) dispute = res.json() if not res.ok: raise RuntimeError(f"{dispute['error']['code']}: {dispute['error']['message']}") ``` **Response** (200) ```json { "object": "dispute", "livemode": true, "id": "dspt_7Hq2LmX9cR4t", "paymentId": "pay_7Hq2LmX9cR4tVb", "amountCents": 12500, "currency": "usd", "status": "needs_response", "reason": "product_not_received", "inquiry": false, "customer": { "name": "Jordan Lee", "email": "jordan@example.com" }, "card": { "brand": "visa", "last4": "4242" }, "evidenceDueAt": "2026-11-01T23:59:59.000Z", "evidenceSubmittedAt": null, "createdAt": "2026-10-18T08:30:00.000Z", "evidence": { "status": "ready", "strength": "strong", "recommendAccept": false, "documentReady": true, "builtAt": "2026-10-18T08:31:12.000Z", "submittedAt": null, "autoSubmitAt": "2026-10-30T23:59:59.000Z" } } ``` **Errors** | Status | Code | When | | --- | --- | --- | | 404 | `NOT_FOUND` | No such object on your business (an id from another business reads the same), or no such endpoint under `/api/v1`, which still answers in JSON. Check the id and the path. Ids are case-sensitive. | | 502 | `PROCESSOR_ERROR` | The payments network didn't answer in time. Retry with the same `Idempotency-Key`. A refund is keyed at the network by that key too, so a retry never refunds twice. | | 503 | `NOT_CONFIGURED` | The part of Anytime Capital Payments this needs isn't available right now. Retry later, with the same `Idempotency-Key` on a write. | ## Events `dispute.opened` and `dispute.closed` carry the disputed [payment](https://anytime-capital.com/docs/payments.md) (its `dispute.status` says where it stands); `dispute.updated` carries the dispute itself when evidence is submitted or its status changes. See [Webhooks](https://anytime-capital.com/docs/webhooks.md) and [Fraud rules & 3D Secure](https://anytime-capital.com/docs/risk-and-3ds.md) for preventing them. --- # Payment links API > Create, update and deactivate hosted payment links with the Anytime Capital Payments API: one per order or reusable, with metadata, and prefilled checkout. Source: https://anytime-capital.com/docs/payment-links · Markdown: https://anytime-capital.com/docs/payment-links.md · All docs: https://anytime-capital.com/docs/llms-full.txt A payment link is a hosted checkout page for one price. Share its `url` by text, email, DM or QR code, put it behind a button on your site, or create one per order from your backend and send the customer straight to it. Customers pay by card, Apple Pay or Google Pay, and by bank account or stablecoin where you have turned them on. Links made for invoices and subscription plans are managed with those and aren't listed here. | Topic | Detail | | --- | --- | | Endpoints | [`POST /api/v1/payment-links`](#create-payment-link), [`GET /api/v1/payment-links`](#list-payment-links), [`GET /api/v1/payment-links/{id}`](#get-payment-link), [`POST /api/v1/payment-links/{id}`](#update-payment-link), [`POST /api/v1/payment-links/{id}/deactivate`](#deactivate-payment-link) | | Key permissions | `payment_links:write`, `payment_links:read` | | Samples | curl; Node.js 18+ with the built-in `fetch` (in an ES module); the [Node SDK](https://anytime-capital.com/docs/node-sdk.md) (preview: not on npm yet); Python with `requests`. | | Every request can also answer | 401 `UNAUTHORIZED`, 401 `API_KEY_EXPIRED`, 403 `NOT_ENROLLED`, 403 `PERMISSION_DENIED`, 403 `IP_NOT_ALLOWED`, 429 `RATE_LIMITED`, 500 `INTERNAL_ERROR`. See [Error codes](https://anytime-capital.com/docs/error-codes.md). | ## The payment link object ```json { "object": "payment_link", "livemode": true, "id": "pl_4kTz8QwN2xYb6RcD1mPa", "url": "https://anytime-capital.com/pay/business/pl_4kTz8QwN2xYb6RcD1mPa", "title": "Consulting — October", "description": "Two strategy sessions", "amountCents": 12500, "currency": "usd", "active": true, "singleUse": false, "shippingCents": 0, "collectShipping": false, "allowDiscountCodes": false, "successUrl": null, "confirmationMessage": "Thanks! We'll email your session times within a day.", "requireTermsConsent": false, "metadata": { "campaign": "fall-launch" }, "items": [], "createdAt": "2026-10-01T09:30:00.000Z", "deactivatedAt": null, "allowQuantity": false, "maxQuantity": null, "customFields": [], "tipsEnabled": false, "payWhatYouWant": false, "minAmountCents": null } ``` | Field | Type | Description | | --- | --- | --- | | `object` | string | The type of object. Always `payment_link`. | | `livemode` | boolean | False for test-mode data (an `ak_test_` key); true for live data. | | `id` | string | `pl_` followed by 20 letters and digits. | | `url` | string (uri) | The pay page. Share it, redirect to it or embed it. | | `title` | string | What the customer pays for. | | `description` | string or null | Shown under the title. | | `amountCents` | integer | The price in cents. With `payWhatYouWant`, the suggested amount. | | `currency` | string | usd. | | `active` | boolean | Taking payments. | | `singleUse` | boolean | Closes after its first payment. | | `shippingCents` | integer | Flat shipping. Integer cents. | | `collectShipping` | boolean | Asks for a shipping address. | | `allowDiscountCodes` | boolean | Accepts discount codes. | | `successUrl` | string or null | Where customers go after paying. A link has at most one of `successUrl` and `confirmationMessage`. | | `requireTermsConsent` | boolean | Customers must agree to your terms. | | `confirmationMessage` | string or null | Shown after paying. | | `metadata` | object | Your own key/value data. | | `items` | object[] | Catalog products, for links made in the dashboard from products. Empty for single-price links. | | `createdAt` | string (date-time) | Created. | | `deactivatedAt` | string (date-time) or null | Deactivated. | | `allowQuantity` | boolean | Customers choose how many. | | `maxQuantity` | integer or null | Most per payment. | | `customFields` | object[] | | | `customFields[].id` | string | Your id. | | `customFields[].label` | string | The question. | | `customFields[].type` | string | `text`, `long_text`, `phone`, `dropdown`. | | `customFields[].options` | string[] | | | `customFields[].required` | boolean | Must be answered. | | `tipsEnabled` | boolean | Offers a tip. | | `payWhatYouWant` | boolean | Customers choose the amount. | | `minAmountCents` | integer or null | Smallest amount with payWhatYouWant. Integer cents. | ## Endpoints ### Create a payment link `POST /api/v1/payment-links` Permission: `payment_links:write`. Supports `Idempotency-Key`. A hosted payment page for one price. Catalog products (`items`) can't be sent through the API yet: send `amountCents` for a single price. Links built from products in the dashboard still list and retrieve with their `items`. **Headers** | Header | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | Recommended. A unique key such as a UUID: a retry with the same key within 24 hours gets the first response back instead of acting twice. See [Idempotency](https://anytime-capital.com/docs/idempotency.md). | **Body** | Name | Type | Required | Description | | --- | --- | --- | --- | | `title` | string | yes | What the customer is paying for. Up to 120 characters. | | `amountCents` | integer | yes | The price in cents. With payWhatYouWant, the suggested amount. From 100 ($1.00) to 1,000,000 ($10,000.00). | | `description` | string | no | Shown under the title. Up to 500 characters. | | `singleUse` | boolean | no | Close the link after its first successful payment. Default false. | | `shippingCents` | integer | no | Flat shipping added to each payment. Default 0. From 0 ($0.00) to 100,000 ($1,000.00). | | `collectShipping` | boolean | no | Ask for a shipping address. Default false. | | `allowDiscountCodes` | boolean | no | Let customers enter one of your discount codes. Default false. | | `successUrl` | string (uri) | no | An https:// page on your site customers go to after paying. Not with confirmationMessage. Up to 500 characters. | | `confirmationMessage` | string | no | Shown on the confirmation page after paying, instead of a successUrl. Up to 500 characters. | | `requireTermsConsent` | boolean | no | Customers must tick “I agree to the terms” before paying. Needs a terms link in **Settings → Payments** (`409 TERMS_URL_REQUIRED` otherwise). Default `false`. | | `metadata` | object | no | Up to 20 of your own key/value pairs, e.g. `{"orderId": "1042"}`: keys up to 40 letters, numbers, `-` and `_`; values text up to 500 characters. Copied onto each payment made on the link; customers never see it. | | `allowQuantity` | boolean | no | Let customers choose how many. Default false. | | `maxQuantity` | integer | no | With allowQuantity: the most per payment (default 10). From 1 to 99. | | `tipsEnabled` | boolean | no | Offer a tip. Default false. | | `payWhatYouWant` | boolean | no | Customers choose the amount. Not with quantity, tips or discount codes. Default false. | | `minAmountCents` | integer | no | With payWhatYouWant: the smallest amount. From 100 ($1.00) to 1,000,000 ($10,000.00). | | `customFields` | object[] | no | Up to 3 questions asked at checkout. Up to 3 items. | | `customFields[].id` | string | yes | Your id for the question; answers are keyed by it. | | `customFields[].label` | string | yes | Up to 60 characters. | | `customFields[].type` | string | yes | One of `text`, `long_text`, `phone`, `dropdown`. | | `customFields[].options` | string[] | no | Dropdown only: 2–10 different choices. Up to 10 items. | | `customFields[].required` | boolean | no | | **Request (curl)** ```bash curl -X POST https://anytime-capital.com/api/v1/payment-links \ -H "Authorization: Bearer $ANYTIME_API_KEY" \ -H "Idempotency-Key: order-1042-link" \ -H "Content-Type: application/json" \ -d '{ "title": "Consulting — October", "description": "Two strategy sessions", "amountCents": 12500, "singleUse": true, "metadata": { "orderId": "1042" } }' ``` **Request (Node.js)** ```javascript const res = await fetch("https://anytime-capital.com/api/v1/payment-links", { method: "POST", headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}`, "Content-Type": "application/json", "Idempotency-Key": "order-1042-link", }, body: JSON.stringify({ title: "Consulting — October", description: "Two strategy sessions", amountCents: 12500, singleUse: true, metadata: { orderId: "1042" }, }), }); const link = await res.json(); if (!res.ok) throw new Error(`${link.error.code}: ${link.error.message}`); ``` **Request (Node SDK)** ```typescript import { AnytimeCapital } from "@anytimecapital/node"; const client = new AnytimeCapital({ apiKey: process.env.ANYTIME_API_KEY! }); const link = await client.paymentLinks.create( { title: "Consulting — October", description: "Two strategy sessions", amountCents: 12500, singleUse: true, metadata: { orderId: "1042" }, }, { idempotencyKey: "order-1042-link" }, ); ``` **Request (Python)** ```python import os import requests res = requests.post( "https://anytime-capital.com/api/v1/payment-links", headers={ "Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}", "Idempotency-Key": "order-1042-link", }, json={ "title": "Consulting — October", "description": "Two strategy sessions", "amountCents": 12500, "singleUse": True, "metadata": {"orderId": "1042"}, }, timeout=30, ) link = res.json() if not res.ok: raise RuntimeError(f"{link['error']['code']}: {link['error']['message']}") ``` **Response** (201) ```json { "object": "payment_link", "livemode": true, "id": "pl_4kTz8QwN2xYb6RcD1mPa", "url": "https://anytime-capital.com/pay/business/pl_4kTz8QwN2xYb6RcD1mPa", "title": "Consulting — October", "description": "Two strategy sessions", "amountCents": 12500, "currency": "usd", "active": true, "singleUse": true, "shippingCents": 0, "collectShipping": false, "allowDiscountCodes": false, "successUrl": null, "confirmationMessage": null, "requireTermsConsent": false, "metadata": { "orderId": "1042" }, "items": [], "createdAt": "2026-10-01T09:30:00.000Z", "deactivatedAt": null, "allowQuantity": false, "maxQuantity": null, "customFields": [], "tipsEnabled": false, "payWhatYouWant": false, "minAmountCents": null } ``` **Errors** | Status | Code | When | | --- | --- | --- | | 400 | `INVALID_JSON` | The body isn't valid JSON, or couldn't be read. An empty body reads as `{}`. Send `Content-Type: application/json` and a JSON object. | | 400 | `INVALID_INPUT` | A field, filter, `limit` or `cursor` is missing or invalid. The message starts with the field's name, as in `amountCents: …`, or reads `Invalid cursor.` Fix the named field. Don't retry the same request unchanged. | | 400 | `INVALID_IDEMPOTENCY_KEY` | The `Idempotency-Key` header is empty, longer than 255 characters, or has characters other than printable ASCII. Use a UUID. See [Idempotency](https://anytime-capital.com/docs/idempotency.md). | | 409 | `TERMS_URL_REQUIRED` | `requireTermsConsent` needs a link to your terms, and the business hasn't set one. Add your terms link in **Settings → Payments**, then retry. | | 422 | `IDEMPOTENCY_KEY_REUSED` | The `Idempotency-Key` was used in the last 24 hours with a different body or endpoint. Use a new key for a new request; reuse a key only to retry the same request. | ### List payment links `GET /api/v1/payment-links` Permission: `payment_links:read`. Payment links with their pay page url and buyer choices. Links behind invoices and plans aren't listed. **Query** | Name | Type | Required | Description | | --- | --- | --- | --- | | `active` | boolean | no | true for live objects, false for deactivated or archived ones. | | `limit` | integer | no | How many objects per page, 1–100. Default 25. See [Pagination](https://anytime-capital.com/docs/pagination.md). | | `cursor` | string | no | The previous page's `nextCursor`, for the page after it. | **Request (curl)** ```bash curl "https://anytime-capital.com/api/v1/payment-links?active=true&limit=10" \ -H "Authorization: Bearer $ANYTIME_API_KEY" ``` **Request (Node.js)** ```javascript const res = await fetch("https://anytime-capital.com/api/v1/payment-links?active=true&limit=10", { headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}` }, }); const page = await res.json(); if (!res.ok) throw new Error(`${page.error.code}: ${page.error.message}`); ``` **Request (Node SDK)** ```typescript import { AnytimeCapital } from "@anytimecapital/node"; const client = new AnytimeCapital({ apiKey: process.env.ANYTIME_API_KEY! }); const page = await client.paymentLinks.list({ active: true, limit: 10 }); ``` **Request (Python)** ```python import os import requests res = requests.get( "https://anytime-capital.com/api/v1/payment-links", headers={"Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}"}, params={"active": "true", "limit": "10"}, timeout=30, ) page = res.json() if not res.ok: raise RuntimeError(f"{page['error']['code']}: {page['error']['message']}") ``` **Response** (200) ```json { "object": "list", "data": [ { "object": "payment_link", "livemode": true, "id": "pl_4kTz8QwN2xYb6RcD1mPa", "url": "https://anytime-capital.com/pay/business/pl_4kTz8QwN2xYb6RcD1mPa", "title": "Consulting — October", "description": "Two strategy sessions", "amountCents": 12500, "currency": "usd", "active": true, "singleUse": false, "shippingCents": 0, "collectShipping": false, "allowDiscountCodes": false, "successUrl": null, "confirmationMessage": "Thanks! We'll email your session times within a day.", "requireTermsConsent": false, "metadata": { "campaign": "fall-launch" }, "items": [], "createdAt": "2026-10-01T09:30:00.000Z", "deactivatedAt": null, "allowQuantity": false, "maxQuantity": null, "customFields": [], "tipsEnabled": false, "payWhatYouWant": false, "minAmountCents": null } ], "hasMore": false, "nextCursor": null } ``` **Errors** | Status | Code | When | | --- | --- | --- | | 400 | `INVALID_INPUT` | A field, filter, `limit` or `cursor` is missing or invalid. The message starts with the field's name, as in `amountCents: …`, or reads `Invalid cursor.` Fix the named field. Don't retry the same request unchanged. | ### Get a payment link `GET /api/v1/payment-links/{id}` Permission: `payment_links:read`. One payment link by id. **Path** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | `pl_…` | **Request (curl)** ```bash curl https://anytime-capital.com/api/v1/payment-links/pl_4kTz8QwN2xYb6RcD1mPa \ -H "Authorization: Bearer $ANYTIME_API_KEY" ``` **Request (Node.js)** ```javascript const res = await fetch("https://anytime-capital.com/api/v1/payment-links/pl_4kTz8QwN2xYb6RcD1mPa", { headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}` }, }); const link = await res.json(); if (!res.ok) throw new Error(`${link.error.code}: ${link.error.message}`); ``` **Request (Node SDK)** ```typescript import { AnytimeCapital } from "@anytimecapital/node"; const client = new AnytimeCapital({ apiKey: process.env.ANYTIME_API_KEY! }); const link = await client.paymentLinks.retrieve("pl_4kTz8QwN2xYb6RcD1mPa"); ``` **Request (Python)** ```python import os import requests res = requests.get( "https://anytime-capital.com/api/v1/payment-links/pl_4kTz8QwN2xYb6RcD1mPa", headers={"Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}"}, timeout=30, ) link = res.json() if not res.ok: raise RuntimeError(f"{link['error']['code']}: {link['error']['message']}") ``` **Response** (200) ```json { "object": "payment_link", "livemode": true, "id": "pl_4kTz8QwN2xYb6RcD1mPa", "url": "https://anytime-capital.com/pay/business/pl_4kTz8QwN2xYb6RcD1mPa", "title": "Consulting — October", "description": "Two strategy sessions", "amountCents": 12500, "currency": "usd", "active": true, "singleUse": false, "shippingCents": 0, "collectShipping": false, "allowDiscountCodes": false, "successUrl": null, "confirmationMessage": "Thanks! We'll email your session times within a day.", "requireTermsConsent": false, "metadata": { "campaign": "fall-launch" }, "items": [], "createdAt": "2026-10-01T09:30:00.000Z", "deactivatedAt": null, "allowQuantity": false, "maxQuantity": null, "customFields": [], "tipsEnabled": false, "payWhatYouWant": false, "minAmountCents": null } ``` **Errors** | Status | Code | When | | --- | --- | --- | | 404 | `NOT_FOUND` | No such object on your business (an id from another business reads the same), or no such endpoint under `/api/v1`, which still answers in JSON. Check the id and the path. Ids are case-sensitive. | ### Update a payment link `POST /api/v1/payment-links/{id}` Permission: `payment_links:write`. Supports `Idempotency-Key`. Send only what changes. The price is fixed: 400 NOT_EDITABLE names a field that can't change. Changes apply straight away; payments already made keep what they were charged. The price and how it is priced are fixed: `amountCents`, `items`, `payWhatYouWant`, `minAmountCents`, `shippingCents`, `allowDiscountCodes` and `singleUse` answer `400 NOT_EDITABLE`. **Path** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | `pl_…` | **Headers** | Header | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | Recommended. A unique key such as a UUID: a retry with the same key within 24 hours gets the first response back instead of acting twice. See [Idempotency](https://anytime-capital.com/docs/idempotency.md). | **Body** | Name | Type | Required | Description | | --- | --- | --- | --- | | `title` | string | no | Up to 120 characters. | | `description` | string or null | no | New description; null removes it. Up to 500 characters. | | `successUrl` | string (uri) or null | no | An https:// page to send customers to after paying; setting it clears confirmationMessage, null clears it. Up to 500 characters. | | `confirmationMessage` | string or null | no | Message after paying; setting it clears successUrl, null clears it. Up to 500 characters. | | `requireTermsConsent` | boolean | no | | | `metadata` | object or null | no | Replaces all of the link's metadata; null clears it. Up to 20 entries. | | `customFields` | object[] | no | Up to 3 questions asked at checkout. Up to 3 items. | | `customFields[].id` | string | yes | Your id for the question; answers are keyed by it. | | `customFields[].label` | string | yes | Up to 60 characters. | | `customFields[].type` | string | yes | One of `text`, `long_text`, `phone`, `dropdown`. | | `customFields[].options` | string[] | no | Dropdown only: 2–10 different choices. Up to 10 items. | | `customFields[].required` | boolean | no | | | `tipsEnabled` | boolean | no | | | `allowQuantity` | boolean | no | | | `maxQuantity` | integer or null | no | With allowQuantity: the most per payment. From 1 to 99. | | `collectShipping` | boolean | no | | **Request (curl)** ```bash curl -X POST https://anytime-capital.com/api/v1/payment-links/pl_4kTz8QwN2xYb6RcD1mPa \ -H "Authorization: Bearer $ANYTIME_API_KEY" \ -H "Idempotency-Key: update-pl_4kTz8Q-title" \ -H "Content-Type: application/json" \ -d '{ "title": "Consulting — November", "metadata": { "campaign": "winter" } }' ``` **Request (Node.js)** ```javascript const res = await fetch("https://anytime-capital.com/api/v1/payment-links/pl_4kTz8QwN2xYb6RcD1mPa", { method: "POST", headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}`, "Content-Type": "application/json", "Idempotency-Key": "update-pl_4kTz8Q-title", }, body: JSON.stringify({ title: "Consulting — November", metadata: { campaign: "winter" } }), }); const link = await res.json(); if (!res.ok) throw new Error(`${link.error.code}: ${link.error.message}`); ``` **Request (Node SDK)** ```typescript import { AnytimeCapital } from "@anytimecapital/node"; const client = new AnytimeCapital({ apiKey: process.env.ANYTIME_API_KEY! }); const link = await client.paymentLinks.update( "pl_4kTz8QwN2xYb6RcD1mPa", { title: "Consulting — November", metadata: { campaign: "winter" } }, { idempotencyKey: "update-pl_4kTz8Q-title" }, ); ``` **Request (Python)** ```python import os import requests res = requests.post( "https://anytime-capital.com/api/v1/payment-links/pl_4kTz8QwN2xYb6RcD1mPa", headers={ "Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}", "Idempotency-Key": "update-pl_4kTz8Q-title", }, json={"title": "Consulting — November", "metadata": {"campaign": "winter"}}, timeout=30, ) link = res.json() if not res.ok: raise RuntimeError(f"{link['error']['code']}: {link['error']['message']}") ``` **Response** (200) ```json { "object": "payment_link", "livemode": true, "id": "pl_4kTz8QwN2xYb6RcD1mPa", "url": "https://anytime-capital.com/pay/business/pl_4kTz8QwN2xYb6RcD1mPa", "title": "Consulting — November", "description": "Two strategy sessions", "amountCents": 12500, "currency": "usd", "active": true, "singleUse": false, "shippingCents": 0, "collectShipping": false, "allowDiscountCodes": false, "successUrl": null, "confirmationMessage": "Thanks! We'll email your session times within a day.", "requireTermsConsent": false, "metadata": { "campaign": "winter" }, "items": [], "createdAt": "2026-10-01T09:30:00.000Z", "deactivatedAt": null, "allowQuantity": false, "maxQuantity": null, "customFields": [], "tipsEnabled": false, "payWhatYouWant": false, "minAmountCents": null } ``` **Errors** | Status | Code | When | | --- | --- | --- | | 400 | `INVALID_JSON` | The body isn't valid JSON, or couldn't be read. An empty body reads as `{}`. Send `Content-Type: application/json` and a JSON object. | | 400 | `INVALID_INPUT` | A field, filter, `limit` or `cursor` is missing or invalid. The message starts with the field's name, as in `amountCents: …`, or reads `Invalid cursor.` Fix the named field. Don't retry the same request unchanged. | | 400 | `INVALID_IDEMPOTENCY_KEY` | The `Idempotency-Key` header is empty, longer than 255 characters, or has characters other than printable ASCII. Use a UUID. See [Idempotency](https://anytime-capital.com/docs/idempotency.md). | | 400 | `NOT_EDITABLE` | The update names a field that is fixed once a link exists: `amountCents`, `items`, `payWhatYouWant`, `minAmountCents`, `shippingCents`, `allowDiscountCodes` or `singleUse`. The message names it. Create a new link at the new price and deactivate the old one. | | 404 | `NOT_FOUND` | No such object on your business (an id from another business reads the same), or no such endpoint under `/api/v1`, which still answers in JSON. Check the id and the path. Ids are case-sensitive. | | 409 | `TERMS_URL_REQUIRED` | `requireTermsConsent` needs a link to your terms, and the business hasn't set one. Add your terms link in **Settings → Payments**, then retry. | | 422 | `IDEMPOTENCY_KEY_REUSED` | The `Idempotency-Key` was used in the last 24 hours with a different body or endpoint. Use a new key for a new request; reuse a key only to retry the same request. | ### Deactivate a payment link `POST /api/v1/payment-links/{id}/deactivate` Permission: `payment_links:write`. Supports `Idempotency-Key`. Stop a link taking payments. Safe to repeat. Anyone who opens the link sees that it's closed. There's no API to turn it back on. **Path** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | `pl_…` | **Headers** | Header | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | Recommended. A unique key such as a UUID: a retry with the same key within 24 hours gets the first response back instead of acting twice. See [Idempotency](https://anytime-capital.com/docs/idempotency.md). | **Request (curl)** ```bash curl -X POST https://anytime-capital.com/api/v1/payment-links/pl_4kTz8QwN2xYb6RcD1mPa/deactivate \ -H "Authorization: Bearer $ANYTIME_API_KEY" \ -H "Idempotency-Key: deactivate-pl_4kTz8Q" ``` **Request (Node.js)** ```javascript const res = await fetch("https://anytime-capital.com/api/v1/payment-links/pl_4kTz8QwN2xYb6RcD1mPa/deactivate", { method: "POST", headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}`, "Idempotency-Key": "deactivate-pl_4kTz8Q", }, }); const link = await res.json(); if (!res.ok) throw new Error(`${link.error.code}: ${link.error.message}`); ``` **Request (Node SDK)** ```typescript import { AnytimeCapital } from "@anytimecapital/node"; const client = new AnytimeCapital({ apiKey: process.env.ANYTIME_API_KEY! }); const link = await client.paymentLinks.deactivate( "pl_4kTz8QwN2xYb6RcD1mPa", { idempotencyKey: "deactivate-pl_4kTz8Q" }, ); ``` **Request (Python)** ```python import os import requests res = requests.post( "https://anytime-capital.com/api/v1/payment-links/pl_4kTz8QwN2xYb6RcD1mPa/deactivate", headers={ "Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}", "Idempotency-Key": "deactivate-pl_4kTz8Q", }, timeout=30, ) link = res.json() if not res.ok: raise RuntimeError(f"{link['error']['code']}: {link['error']['message']}") ``` **Response** (200) ```json { "object": "payment_link", "livemode": true, "id": "pl_4kTz8QwN2xYb6RcD1mPa", "url": "https://anytime-capital.com/pay/business/pl_4kTz8QwN2xYb6RcD1mPa", "title": "Consulting — October", "description": "Two strategy sessions", "amountCents": 12500, "currency": "usd", "active": false, "singleUse": false, "shippingCents": 0, "collectShipping": false, "allowDiscountCodes": false, "successUrl": null, "confirmationMessage": "Thanks! We'll email your session times within a day.", "requireTermsConsent": false, "metadata": { "campaign": "fall-launch" }, "items": [], "createdAt": "2026-10-01T09:30:00.000Z", "deactivatedAt": "2026-10-16T10:00:00.000Z", "allowQuantity": false, "maxQuantity": null, "customFields": [], "tipsEnabled": false, "payWhatYouWant": false, "minAmountCents": null } ``` **Errors** | Status | Code | When | | --- | --- | --- | | 400 | `INVALID_IDEMPOTENCY_KEY` | The `Idempotency-Key` header is empty, longer than 255 characters, or has characters other than printable ASCII. Use a UUID. See [Idempotency](https://anytime-capital.com/docs/idempotency.md). | | 404 | `NOT_FOUND` | No such object on your business (an id from another business reads the same), or no such endpoint under `/api/v1`, which still answers in JSON. Check the id and the path. Ids are case-sensitive. | | 422 | `IDEMPOTENCY_KEY_REUSED` | The `Idempotency-Key` was used in the last 24 hours with a different body or endpoint. Use a new key for a new request; reuse a key only to retry the same request. | ## Prefill checkout Fill in details for your customer by adding parameters to a link's `url`. A value that isn't valid is ignored, never an error. | Parameter | What it does | | --- | --- | | `prefilled_email` | The customer's email for their receipt. They can still change it. | | `code` | One of your discount codes, applied when checkout opens, on links that accept codes. | | `ref` | Your own reference for the payment, up to 200 letters, numbers, spaces and `_ . : / # @ + -`. Stored on the payment as `clientReference` and returned by the API and in webhooks. | ```text https://anytime-capital.com/pay/business/pl_4kTz8QwN2xYb6RcD1mPa?prefilled_email=jordan%40example.com&code=FALL20&ref=order_1042 ``` On your own website, the Pay button and the inline checkout take the same values as `data-email`, `data-code` and `data-ref`. See [Checkout](https://anytime-capital.com/docs/checkout.md). ## One link per order For a cart or an order whose total you work out yourself, create a `singleUse` link per order with the order id in `metadata`, send the customer to its `url`, and mark the order paid when the `payment.succeeded` webhook arrives: `data.metadata` carries your order id, `data.paymentLinkId` the link id and `data.customer.email` the buyer. Use a stored Idempotency-Key so a retry never makes a second link. For a fixed price you sell again and again, one reusable link with `?ref=` per customer does the same job. The [Next.js guide](https://anytime-capital.com/docs/nextjs-checkout.md) builds this end to end. - [Products](https://anytime-capital.com/docs/products.md): Your catalog: what links built in the dashboard sell. - [Discount codes](https://anytime-capital.com/docs/discount-codes.md): Codes customers enter on links that allow them. - [Checkout & website](https://anytime-capital.com/docs/checkout.md): The Pay button, inline checkout and your branding. --- # Invoices API > Create, list and send invoices with the Anytime Capital Payments API: line items, discount, tax and deposits, emailed in your branding and paid by card. Source: https://anytime-capital.com/docs/invoices · Markdown: https://anytime-capital.com/docs/invoices.md · All docs: https://anytime-capital.com/docs/llms-full.txt Invoices are emailed from Anytime Capital in your branding and paid at their `url`. Totals are calculated from the line items, an optional discount and tax. A customer can pay a deposit first, and automatic reminders go out around the due date unless you turn them off. Recurring invoices, bank transfer instructions and recording an offline payment are done in the dashboard. | Topic | Detail | | --- | --- | | Endpoints | [`POST /api/v1/invoices`](#create-invoice), [`GET /api/v1/invoices`](#list-invoices), [`GET /api/v1/invoices/{id}`](#get-invoice), [`POST /api/v1/invoices/{id}/send`](#send-invoice) | | Key permissions | `invoices:write`, `invoices:read` | | Samples | curl; Node.js 18+ with the built-in `fetch` (in an ES module); the [Node SDK](https://anytime-capital.com/docs/node-sdk.md) (preview: not on npm yet); Python with `requests`. | | Every request can also answer | 401 `UNAUTHORIZED`, 401 `API_KEY_EXPIRED`, 403 `NOT_ENROLLED`, 403 `PERMISSION_DENIED`, 403 `IP_NOT_ALLOWED`, 429 `RATE_LIMITED`, 500 `INTERNAL_ERROR`. See [Error codes](https://anytime-capital.com/docs/error-codes.md). | ## The invoice object ```json { "object": "invoice", "livemode": true, "id": "inv_9QmR2vX7pLk3TzW8cN4b", "number": "INV-0042", "url": "https://anytime-capital.com/pay/invoice/inv_9QmR2vX7pLk3TzW8cN4b", "status": "open", "overdue": false, "customer": { "name": "Northwind LLC", "email": "ap@northwind.example" }, "lineItems": [ { "description": "Website redesign", "quantity": 1, "unitCents": 450000 } ], "subtotalCents": 450000, "discountCents": 0, "taxBps": 825, "taxCents": 37125, "totalCents": 487125, "amountPaidCents": 0, "balanceCents": 487125, "depositCents": null, "currency": "usd", "dueDate": "2026-11-15", "notes": "Thank you for your business.", "paidVia": null, "paidAt": null, "paymentId": null, "sentAt": "2026-10-16T14:00:00.000Z", "voidedAt": null, "createdAt": "2026-10-16T14:00:00.000Z" } ``` | Field | Type | Description | | --- | --- | --- | | `object` | string | The type of object. Always `invoice`. | | `livemode` | boolean | False for test-mode data (an `ak_test_` key); true for live data. | | `id` | string | `inv_` followed by 20 letters and digits. | | `number` | string | INV-0001. | | `url` | string (uri) | Where the customer views and pays it. | | `status` | string | `open`, `partially_paid`, `paid`, `refunded`, `void`: `partially_paid` after a deposit or part payment, `refunded` when paid and then refunded in full. | | `overdue` | boolean | Still owed and past its due date. | | `customer` | object | | | `customer.name` | string | Name. | | `customer.email` | string | Email. | | `lineItems` | object[] | | | `lineItems[].description` | string | Line. | | `lineItems[].quantity` | integer | Quantity. | | `lineItems[].unitCents` | integer | Unit price. Integer cents. | | `subtotalCents` | integer | Before discount and tax. Integer cents. | | `discountCents` | integer | Discount. Integer cents. | | `taxBps` | integer | Tax in basis points (825 is 8.25%). | | `taxCents` | integer | Tax, applied after the discount. | | `totalCents` | integer | Total. Integer cents. | | `amountPaidCents` | integer | Paid so far. Integer cents. | | `balanceCents` | integer | Still owed. Integer cents. | | `depositCents` | integer or null | Deposit the customer may pay first. Integer cents. | | `currency` | string | usd. | | `dueDate` | string (date) | YYYY-MM-DD. | | `notes` | string or null | Printed on the invoice. | | `paidVia` | string or null | `card`, or how it was recorded as paid in the dashboard. | | `paidAt` | string (date-time) or null | Paid in full. | | `paymentId` | string or null | The card payment that paid it. | | `sentAt` | string (date-time) or null | First emailed. | | `voidedAt` | string (date-time) or null | Voided. | | `createdAt` | string (date-time) | Created. | ## Endpoints ### Create an invoice `POST /api/v1/invoices` Permission: `invoices:write`. Supports `Idempotency-Key`. Totals are calculated from the lines; emailed to the customer unless `send: false`. Totals are calculated from the lines (discount first, then tax) and run from $1.00 to $100,000.00. Recurring invoices are set up in the dashboard: sending `recurrence` answers `400 INVALID_INPUT`. If the email fails, the invoice is still created with `sentAt: null`; send it later. **Headers** | Header | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | Recommended. A unique key such as a UUID: a retry with the same key within 24 hours gets the first response back instead of acting twice. See [Idempotency](https://anytime-capital.com/docs/idempotency.md). | **Body** | Name | Type | Required | Description | | --- | --- | --- | --- | | `customerName` | string | yes | Who the invoice is for. Up to 200 characters. | | `customerEmail` | string (email) | yes | Where the invoice is emailed. Up to 255 characters. | | `lineItems` | object[] | yes | 1–50 items. | | `lineItems[].description` | string | yes | Up to 200 characters. | | `lineItems[].quantity` | integer | yes | From 1 to 10,000. | | `lineItems[].unitCents` | integer | yes | From 1 ($0.01) to 10,000,000 ($100,000.00). | | `dueDate` | string (date) | yes | YYYY-MM-DD. | | `discountCents` | integer | no | Taken off the subtotal before tax. | | `taxBps` | integer | no | Tax in basis points: 825 is 8.25%. From 0 to 3,000. | | `notes` | string | no | Printed on the invoice. Up to 2000 characters. | | `deposit` | object | no | What the customer may pay first: `{"type": "amount", "amountCents": 50000}` or `{"type": "percent", "percent": 25}`. At least $1.00, and less than the total with at least $1.00 left to pay. | | `remindersEnabled` | boolean | no | Automatic reminders around the due date. Default true. | | `send` | boolean | no | Email the invoice now. Default true. | | `saveCustomer` | boolean | no | Also add the customer to your customers (skipped when the email is already there). Default `false`. | **Request (curl)** ```bash curl -X POST https://anytime-capital.com/api/v1/invoices \ -H "Authorization: Bearer $ANYTIME_API_KEY" \ -H "Idempotency-Key: invoice-northwind-oct" \ -H "Content-Type: application/json" \ -d '{ "customerName": "Northwind LLC", "customerEmail": "ap@northwind.example", "lineItems": [ { "description": "Website redesign", "quantity": 1, "unitCents": 450000 } ], "taxBps": 825, "dueDate": "2026-11-15" }' ``` **Request (Node.js)** ```javascript const res = await fetch("https://anytime-capital.com/api/v1/invoices", { method: "POST", headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}`, "Content-Type": "application/json", "Idempotency-Key": "invoice-northwind-oct", }, body: JSON.stringify({ customerName: "Northwind LLC", customerEmail: "ap@northwind.example", lineItems: [{ description: "Website redesign", quantity: 1, unitCents: 450000 }], taxBps: 825, dueDate: "2026-11-15", }), }); const invoice = await res.json(); if (!res.ok) throw new Error(`${invoice.error.code}: ${invoice.error.message}`); ``` **Request (Node SDK)** ```typescript import { AnytimeCapital } from "@anytimecapital/node"; const client = new AnytimeCapital({ apiKey: process.env.ANYTIME_API_KEY! }); const invoice = await client.invoices.create( { customerName: "Northwind LLC", customerEmail: "ap@northwind.example", lineItems: [{ description: "Website redesign", quantity: 1, unitCents: 450000 }], taxBps: 825, dueDate: "2026-11-15", }, { idempotencyKey: "invoice-northwind-oct" }, ); ``` **Request (Python)** ```python import os import requests res = requests.post( "https://anytime-capital.com/api/v1/invoices", headers={ "Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}", "Idempotency-Key": "invoice-northwind-oct", }, json={ "customerName": "Northwind LLC", "customerEmail": "ap@northwind.example", "lineItems": [ {"description": "Website redesign", "quantity": 1, "unitCents": 450000}, ], "taxBps": 825, "dueDate": "2026-11-15", }, timeout=30, ) invoice = res.json() if not res.ok: raise RuntimeError(f"{invoice['error']['code']}: {invoice['error']['message']}") ``` **Response** (201) ```json { "object": "invoice", "livemode": true, "id": "inv_9QmR2vX7pLk3TzW8cN4b", "number": "INV-0042", "url": "https://anytime-capital.com/pay/invoice/inv_9QmR2vX7pLk3TzW8cN4b", "status": "open", "overdue": false, "customer": { "name": "Northwind LLC", "email": "ap@northwind.example" }, "lineItems": [ { "description": "Website redesign", "quantity": 1, "unitCents": 450000 } ], "subtotalCents": 450000, "discountCents": 0, "taxBps": 825, "taxCents": 37125, "totalCents": 487125, "amountPaidCents": 0, "balanceCents": 487125, "depositCents": null, "currency": "usd", "dueDate": "2026-11-15", "notes": "Thank you for your business.", "paidVia": null, "paidAt": null, "paymentId": null, "sentAt": "2026-10-16T14:00:00.000Z", "voidedAt": null, "createdAt": "2026-10-16T14:00:00.000Z" } ``` **Errors** | Status | Code | When | | --- | --- | --- | | 400 | `INVALID_JSON` | The body isn't valid JSON, or couldn't be read. An empty body reads as `{}`. Send `Content-Type: application/json` and a JSON object. | | 400 | `INVALID_INPUT` | A field, filter, `limit` or `cursor` is missing or invalid. The message starts with the field's name, as in `amountCents: …`, or reads `Invalid cursor.` Fix the named field. Don't retry the same request unchanged. | | 400 | `INVALID_IDEMPOTENCY_KEY` | The `Idempotency-Key` header is empty, longer than 255 characters, or has characters other than printable ASCII. Use a UUID. See [Idempotency](https://anytime-capital.com/docs/idempotency.md). | | 422 | `IDEMPOTENCY_KEY_REUSED` | The `Idempotency-Key` was used in the last 24 hours with a different body or endpoint. Use a new key for a new request; reuse a key only to retry the same request. | ### List invoices `GET /api/v1/invoices` Permission: `invoices:read`. Invoices with totals, what is paid and owed, and the pay page url. **Query** | Name | Type | Required | Description | | --- | --- | --- | --- | | `status` | string | no | Only invoices with this status. One of `open`, `partially_paid`, `paid`, `refunded`, `void`. | | `limit` | integer | no | How many objects per page, 1–100. Default 25. See [Pagination](https://anytime-capital.com/docs/pagination.md). | | `cursor` | string | no | The previous page's `nextCursor`, for the page after it. | **Request (curl)** ```bash curl "https://anytime-capital.com/api/v1/invoices?status=open" \ -H "Authorization: Bearer $ANYTIME_API_KEY" ``` **Request (Node.js)** ```javascript const res = await fetch("https://anytime-capital.com/api/v1/invoices?status=open", { headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}` }, }); const page = await res.json(); if (!res.ok) throw new Error(`${page.error.code}: ${page.error.message}`); ``` **Request (Node SDK)** ```typescript import { AnytimeCapital } from "@anytimecapital/node"; const client = new AnytimeCapital({ apiKey: process.env.ANYTIME_API_KEY! }); const page = await client.invoices.list({ status: "open" }); ``` **Request (Python)** ```python import os import requests res = requests.get( "https://anytime-capital.com/api/v1/invoices", headers={"Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}"}, params={"status": "open"}, timeout=30, ) page = res.json() if not res.ok: raise RuntimeError(f"{page['error']['code']}: {page['error']['message']}") ``` **Response** (200) ```json { "object": "list", "data": [ { "object": "invoice", "livemode": true, "id": "inv_9QmR2vX7pLk3TzW8cN4b", "number": "INV-0042", "url": "https://anytime-capital.com/pay/invoice/inv_9QmR2vX7pLk3TzW8cN4b", "status": "open", "overdue": false, "customer": { "name": "Northwind LLC", "email": "ap@northwind.example" }, "lineItems": [ { "description": "Website redesign", "quantity": 1, "unitCents": 450000 } ], "subtotalCents": 450000, "discountCents": 0, "taxBps": 825, "taxCents": 37125, "totalCents": 487125, "amountPaidCents": 0, "balanceCents": 487125, "depositCents": null, "currency": "usd", "dueDate": "2026-11-15", "notes": "Thank you for your business.", "paidVia": null, "paidAt": null, "paymentId": null, "sentAt": "2026-10-16T14:00:00.000Z", "voidedAt": null, "createdAt": "2026-10-16T14:00:00.000Z" } ], "hasMore": false, "nextCursor": null } ``` **Errors** | Status | Code | When | | --- | --- | --- | | 400 | `INVALID_INPUT` | A field, filter, `limit` or `cursor` is missing or invalid. The message starts with the field's name, as in `amountCents: …`, or reads `Invalid cursor.` Fix the named field. Don't retry the same request unchanged. | ### Get an invoice `GET /api/v1/invoices/{id}` Permission: `invoices:read`. One invoice by id. **Path** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | `inv_…` | **Request (curl)** ```bash curl https://anytime-capital.com/api/v1/invoices/inv_9QmR2vX7pLk3TzW8cN4b \ -H "Authorization: Bearer $ANYTIME_API_KEY" ``` **Request (Node.js)** ```javascript const res = await fetch("https://anytime-capital.com/api/v1/invoices/inv_9QmR2vX7pLk3TzW8cN4b", { headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}` }, }); const invoice = await res.json(); if (!res.ok) throw new Error(`${invoice.error.code}: ${invoice.error.message}`); ``` **Request (Node SDK)** ```typescript import { AnytimeCapital } from "@anytimecapital/node"; const client = new AnytimeCapital({ apiKey: process.env.ANYTIME_API_KEY! }); const invoice = await client.invoices.retrieve("inv_9QmR2vX7pLk3TzW8cN4b"); ``` **Request (Python)** ```python import os import requests res = requests.get( "https://anytime-capital.com/api/v1/invoices/inv_9QmR2vX7pLk3TzW8cN4b", headers={"Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}"}, timeout=30, ) invoice = res.json() if not res.ok: raise RuntimeError(f"{invoice['error']['code']}: {invoice['error']['message']}") ``` **Response** (200) ```json { "object": "invoice", "livemode": true, "id": "inv_9QmR2vX7pLk3TzW8cN4b", "number": "INV-0042", "url": "https://anytime-capital.com/pay/invoice/inv_9QmR2vX7pLk3TzW8cN4b", "status": "open", "overdue": false, "customer": { "name": "Northwind LLC", "email": "ap@northwind.example" }, "lineItems": [ { "description": "Website redesign", "quantity": 1, "unitCents": 450000 } ], "subtotalCents": 450000, "discountCents": 0, "taxBps": 825, "taxCents": 37125, "totalCents": 487125, "amountPaidCents": 0, "balanceCents": 487125, "depositCents": null, "currency": "usd", "dueDate": "2026-11-15", "notes": "Thank you for your business.", "paidVia": null, "paidAt": null, "paymentId": null, "sentAt": "2026-10-16T14:00:00.000Z", "voidedAt": null, "createdAt": "2026-10-16T14:00:00.000Z" } ``` **Errors** | Status | Code | When | | --- | --- | --- | | 404 | `NOT_FOUND` | No such object on your business (an id from another business reads the same), or no such endpoint under `/api/v1`, which still answers in JSON. Check the id and the path. Ids are case-sensitive. | ### Send an invoice `POST /api/v1/invoices/{id}/send` Permission: `invoices:write`. Supports `Idempotency-Key`. Email it (a reminder after the first time), at most once a day, while something is owed. The invoice itself the first time (sends `invoice.sent`), a reminder after that. **Path** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | `inv_…` | **Headers** | Header | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | Recommended. A unique key such as a UUID: a retry with the same key within 24 hours gets the first response back instead of acting twice. See [Idempotency](https://anytime-capital.com/docs/idempotency.md). | **Request (curl)** ```bash curl -X POST https://anytime-capital.com/api/v1/invoices/inv_9QmR2vX7pLk3TzW8cN4b/send \ -H "Authorization: Bearer $ANYTIME_API_KEY" \ -H "Idempotency-Key: send-inv-0042" ``` **Request (Node.js)** ```javascript const res = await fetch("https://anytime-capital.com/api/v1/invoices/inv_9QmR2vX7pLk3TzW8cN4b/send", { method: "POST", headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}`, "Idempotency-Key": "send-inv-0042", }, }); const invoice = await res.json(); if (!res.ok) throw new Error(`${invoice.error.code}: ${invoice.error.message}`); ``` **Request (Node SDK)** ```typescript import { AnytimeCapital } from "@anytimecapital/node"; const client = new AnytimeCapital({ apiKey: process.env.ANYTIME_API_KEY! }); const invoice = await client.invoices.send( "inv_9QmR2vX7pLk3TzW8cN4b", { idempotencyKey: "send-inv-0042" }, ); ``` **Request (Python)** ```python import os import requests res = requests.post( "https://anytime-capital.com/api/v1/invoices/inv_9QmR2vX7pLk3TzW8cN4b/send", headers={ "Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}", "Idempotency-Key": "send-inv-0042", }, timeout=30, ) invoice = res.json() if not res.ok: raise RuntimeError(f"{invoice['error']['code']}: {invoice['error']['message']}") ``` **Response** (200) ```json { "object": "invoice", "livemode": true, "id": "inv_9QmR2vX7pLk3TzW8cN4b", "number": "INV-0042", "url": "https://anytime-capital.com/pay/invoice/inv_9QmR2vX7pLk3TzW8cN4b", "status": "open", "overdue": false, "customer": { "name": "Northwind LLC", "email": "ap@northwind.example" }, "lineItems": [ { "description": "Website redesign", "quantity": 1, "unitCents": 450000 } ], "subtotalCents": 450000, "discountCents": 0, "taxBps": 825, "taxCents": 37125, "totalCents": 487125, "amountPaidCents": 0, "balanceCents": 487125, "depositCents": null, "currency": "usd", "dueDate": "2026-11-15", "notes": "Thank you for your business.", "paidVia": null, "paidAt": null, "paymentId": null, "sentAt": "2026-10-16T14:00:00.000Z", "voidedAt": null, "createdAt": "2026-10-16T14:00:00.000Z" } ``` **Errors** | Status | Code | When | | --- | --- | --- | | 400 | `INVALID_IDEMPOTENCY_KEY` | The `Idempotency-Key` header is empty, longer than 255 characters, or has characters other than printable ASCII. Use a UUID. See [Idempotency](https://anytime-capital.com/docs/idempotency.md). | | 404 | `NOT_FOUND` | No such object on your business (an id from another business reads the same), or no such endpoint under `/api/v1`, which still answers in JSON. Check the id and the path. Ids are case-sensitive. | | 409 | `INVOICE_CLOSED` | The invoice is already paid, refunded or void, so there's nothing to send. Read the invoice's `status`; nothing to do. | | 422 | `IDEMPOTENCY_KEY_REUSED` | The `Idempotency-Key` was used in the last 24 hours with a different body or endpoint. Use a new key for a new request; reuse a key only to retry the same request. | | 429 | `TOO_SOON` | The invoice was already emailed in the last 24 hours. Wait the seconds in `Retry-After`. | | 502 | `EMAIL_FAILED` | We couldn't send the email. Retry. Nothing was marked as sent. | ## Knowing when an invoice is paid Subscribe to the `invoice.paid` webhook, which carries the invoice object once it is paid in full. Each card payment on an invoice, deposits included, also sends `payment.succeeded` with `source.type` `invoice` and `source.id` the invoice id. `invoice.created`, `invoice.sent`, `invoice.overdue` and `invoice.voided` follow the rest of its life. See [Webhooks](https://anytime-capital.com/docs/webhooks.md). - [Bank transfers on invoices](https://anytime-capital.com/docs/invoice-bank-transfers.md): Show your ACH and wire details on invoices. - [Customers](https://anytime-capital.com/docs/customers.md): Save the people you invoice. - [Prompt library](https://anytime-capital.com/docs/ai-prompt-library.md): Have an AI assistant draft and send invoices for you. --- # Customers API > Create, update and look up your saved customers with the Anytime Capital Payments API, one per email address, to match payments and invoices to them. Source: https://anytime-capital.com/docs/customers · Markdown: https://anytime-capital.com/docs/customers.md · All docs: https://anytime-capital.com/docs/llms-full.txt Customers are the clients saved on your **Customers** page. The API lists them, finds one by email, adds them and updates them, with the same rules as the dashboard: one customer per email address, in any letter case. Card buyers are added automatically when they first pay, so look a customer up by email before you create one. | Topic | Detail | | --- | --- | | Endpoints | [`POST /api/v1/customers`](#create-customer), [`GET /api/v1/customers`](#list-customers), [`GET /api/v1/customers/{id}`](#get-customer), [`PATCH /api/v1/customers/{id}`](#update-customer) | | Key permissions | `customers:write`, `customers:read` | | Samples | curl; Node.js 18+ with the built-in `fetch` (in an ES module); the [Node SDK](https://anytime-capital.com/docs/node-sdk.md) (preview: not on npm yet); Python with `requests`. | | Every request can also answer | 401 `UNAUTHORIZED`, 401 `API_KEY_EXPIRED`, 403 `NOT_ENROLLED`, 403 `PERMISSION_DENIED`, 403 `IP_NOT_ALLOWED`, 429 `RATE_LIMITED`, 500 `INTERNAL_ERROR`. See [Error codes](https://anytime-capital.com/docs/error-codes.md). | ## The customer object ```json { "object": "customer", "livemode": true, "id": "cus_1042", "name": "Jordan Lee", "email": "jordan@example.com", "phone": "+1 555 0100", "company": "Lee Studio", "notes": null, "billingAddress": null, "taxId": null, "country": "US", "createdAt": "2026-09-02T11:20:00.000Z" } ``` | Field | Type | Description | | --- | --- | --- | | `object` | string | The type of object. Always `customer`. | | `livemode` | boolean | False for test-mode data (an `ak_test_` key); true for live data. | | `id` | string | `cus_` followed by digits. | | `name` | string | Name. | | `email` | string or null | Email. | | `phone` | string or null | Phone. | | `company` | string or null | Company. | | `notes` | string or null | Your notes; never shown to the customer. | | `billingAddress` | object or null | A postal address printed on invoices and quotes. | | `billingAddress.line1` | string | Street address. Up to 200 characters. | | `billingAddress.line2` | string or null | Apartment, suite, unit. Up to 200 characters. | | `billingAddress.city` | string | City. Up to 100 characters. | | `billingAddress.state` | string or null | State, province or region. Up to 100 characters. | | `billingAddress.postalCode` | string | Postal code. Up to 20 characters. | | `billingAddress.country` | string | ISO 3166-1 alpha-2 country code. | | `taxId` | string or null | VAT, EIN or other tax number. | | `country` | string or null | ISO 3166-1 alpha-2, from what you saved or their card's billing address. | | `createdAt` | string (date-time) | Added. | ## Endpoints ### Create a customer `POST /api/v1/customers` Permission: `customers:write`. Supports `Idempotency-Key`. One customer per email: 409 DUPLICATE_CUSTOMER otherwise. Card buyers are also added on their own when they first pay, so look up by email before creating. **Headers** | Header | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | Recommended. A unique key such as a UUID: a retry with the same key within 24 hours gets the first response back instead of acting twice. See [Idempotency](https://anytime-capital.com/docs/idempotency.md). | **Body** | Name | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | yes | The customer's name. Up to 255 characters. | | `email` | string (email) | no | One customer per email address. Up to 255 characters. | | `phone` | string | no | Phone. Up to 50 characters. | | `company` | string | no | Company. Up to 255 characters. | | `notes` | string | no | Your notes. Up to 2000 characters. | | `billingAddress` | object or null | no | A postal address printed on invoices and quotes. | | `billingAddress.line1` | string | yes | Street address. Up to 200 characters. | | `billingAddress.line2` | string or null | no | Apartment, suite, unit. Up to 200 characters. | | `billingAddress.city` | string | yes | City. Up to 100 characters. | | `billingAddress.state` | string or null | no | State, province or region. Up to 100 characters. | | `billingAddress.postalCode` | string | yes | Postal code. Up to 20 characters. | | `billingAddress.country` | string | yes | ISO 3166-1 alpha-2 country code. | | `taxId` | string or null | no | VAT, EIN or other tax number. Up to 64 characters. | **Request (curl)** ```bash curl -X POST https://anytime-capital.com/api/v1/customers \ -H "Authorization: Bearer $ANYTIME_API_KEY" \ -H "Idempotency-Key: customer-jordan" \ -H "Content-Type: application/json" \ -d '{ "name": "Jordan Lee", "email": "jordan@example.com", "company": "Lee Studio" }' ``` **Request (Node.js)** ```javascript const res = await fetch("https://anytime-capital.com/api/v1/customers", { method: "POST", headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}`, "Content-Type": "application/json", "Idempotency-Key": "customer-jordan", }, body: JSON.stringify({ name: "Jordan Lee", email: "jordan@example.com", company: "Lee Studio", }), }); const customer = await res.json(); if (!res.ok) throw new Error(`${customer.error.code}: ${customer.error.message}`); ``` **Request (Node SDK)** ```typescript import { AnytimeCapital } from "@anytimecapital/node"; const client = new AnytimeCapital({ apiKey: process.env.ANYTIME_API_KEY! }); const customer = await client.customers.create( { name: "Jordan Lee", email: "jordan@example.com", company: "Lee Studio", }, { idempotencyKey: "customer-jordan" }, ); ``` **Request (Python)** ```python import os import requests res = requests.post( "https://anytime-capital.com/api/v1/customers", headers={ "Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}", "Idempotency-Key": "customer-jordan", }, json={ "name": "Jordan Lee", "email": "jordan@example.com", "company": "Lee Studio", }, timeout=30, ) customer = res.json() if not res.ok: raise RuntimeError(f"{customer['error']['code']}: {customer['error']['message']}") ``` **Response** (201) ```json { "object": "customer", "livemode": true, "id": "cus_1042", "name": "Jordan Lee", "email": "jordan@example.com", "phone": "+1 555 0100", "company": "Lee Studio", "notes": null, "billingAddress": null, "taxId": null, "country": "US", "createdAt": "2026-09-02T11:20:00.000Z" } ``` **Errors** | Status | Code | When | | --- | --- | --- | | 400 | `INVALID_JSON` | The body isn't valid JSON, or couldn't be read. An empty body reads as `{}`. Send `Content-Type: application/json` and a JSON object. | | 400 | `INVALID_INPUT` | A field, filter, `limit` or `cursor` is missing or invalid. The message starts with the field's name, as in `amountCents: …`, or reads `Invalid cursor.` Fix the named field. Don't retry the same request unchanged. | | 400 | `INVALID_IDEMPOTENCY_KEY` | The `Idempotency-Key` header is empty, longer than 255 characters, or has characters other than printable ASCII. Use a UUID. See [Idempotency](https://anytime-capital.com/docs/idempotency.md). | | 409 | `DUPLICATE_CUSTOMER` | Another saved customer already has this email address, in any letter case. Find them with `GET /api/v1/customers?email=…` and update that customer instead. | | 422 | `IDEMPOTENCY_KEY_REUSED` | The `Idempotency-Key` was used in the last 24 hours with a different body or endpoint. Use a new key for a new request; reuse a key only to retry the same request. | ### List customers `GET /api/v1/customers` Permission: `customers:read`. Saved customers, newest first, or one by email. **Query** | Name | Type | Required | Description | | --- | --- | --- | --- | | `email` | string | no | Only customers with this email, any case. Up to 255 characters. | | `limit` | integer | no | How many objects per page, 1–100. Default 25. See [Pagination](https://anytime-capital.com/docs/pagination.md). | | `cursor` | string | no | The previous page's `nextCursor`, for the page after it. | **Request (curl)** ```bash curl "https://anytime-capital.com/api/v1/customers?email=jordan%40example.com" \ -H "Authorization: Bearer $ANYTIME_API_KEY" ``` **Request (Node.js)** ```javascript const res = await fetch("https://anytime-capital.com/api/v1/customers?email=jordan%40example.com", { headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}` }, }); const page = await res.json(); if (!res.ok) throw new Error(`${page.error.code}: ${page.error.message}`); ``` **Request (Node SDK)** ```typescript import { AnytimeCapital } from "@anytimecapital/node"; const client = new AnytimeCapital({ apiKey: process.env.ANYTIME_API_KEY! }); const page = await client.customers.list({ email: "jordan@example.com" }); ``` **Request (Python)** ```python import os import requests res = requests.get( "https://anytime-capital.com/api/v1/customers", headers={"Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}"}, params={"email": "jordan@example.com"}, timeout=30, ) page = res.json() if not res.ok: raise RuntimeError(f"{page['error']['code']}: {page['error']['message']}") ``` **Response** (200) ```json { "object": "list", "data": [ { "object": "customer", "livemode": true, "id": "cus_1042", "name": "Jordan Lee", "email": "jordan@example.com", "phone": "+1 555 0100", "company": "Lee Studio", "notes": null, "billingAddress": null, "taxId": null, "country": "US", "createdAt": "2026-09-02T11:20:00.000Z" } ], "hasMore": false, "nextCursor": null } ``` **Errors** | Status | Code | When | | --- | --- | --- | | 400 | `INVALID_INPUT` | A field, filter, `limit` or `cursor` is missing or invalid. The message starts with the field's name, as in `amountCents: …`, or reads `Invalid cursor.` Fix the named field. Don't retry the same request unchanged. | ### Get a customer `GET /api/v1/customers/{id}` Permission: `customers:read`. One saved customer. Customers removed in the dashboard answer `404`. **Path** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | `cus_…` | **Request (curl)** ```bash curl https://anytime-capital.com/api/v1/customers/cus_1042 \ -H "Authorization: Bearer $ANYTIME_API_KEY" ``` **Request (Node.js)** ```javascript const res = await fetch("https://anytime-capital.com/api/v1/customers/cus_1042", { headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}` }, }); const customer = await res.json(); if (!res.ok) throw new Error(`${customer.error.code}: ${customer.error.message}`); ``` **Request (Node SDK)** ```typescript import { AnytimeCapital } from "@anytimecapital/node"; const client = new AnytimeCapital({ apiKey: process.env.ANYTIME_API_KEY! }); const customer = await client.customers.retrieve("cus_1042"); ``` **Request (Python)** ```python import os import requests res = requests.get( "https://anytime-capital.com/api/v1/customers/cus_1042", headers={"Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}"}, timeout=30, ) customer = res.json() if not res.ok: raise RuntimeError(f"{customer['error']['code']}: {customer['error']['message']}") ``` **Response** (200) ```json { "object": "customer", "livemode": true, "id": "cus_1042", "name": "Jordan Lee", "email": "jordan@example.com", "phone": "+1 555 0100", "company": "Lee Studio", "notes": null, "billingAddress": null, "taxId": null, "country": "US", "createdAt": "2026-09-02T11:20:00.000Z" } ``` **Errors** | Status | Code | When | | --- | --- | --- | | 404 | `NOT_FOUND` | No such object on your business (an id from another business reads the same), or no such endpoint under `/api/v1`, which still answers in JSON. Check the id and the path. Ids are case-sensitive. | ### Update a customer `PATCH /api/v1/customers/{id}` Permission: `customers:write`. Supports `Idempotency-Key`. Send only what changes; null clears an optional field. **Path** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | `cus_…` | **Headers** | Header | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | Recommended. A unique key such as a UUID: a retry with the same key within 24 hours gets the first response back instead of acting twice. See [Idempotency](https://anytime-capital.com/docs/idempotency.md). | **Body** | Name | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | no | The customer's name. Up to 255 characters. | | `email` | string (email) or null | no | One customer per email address. Up to 255 characters. | | `phone` | string or null | no | Phone. Up to 50 characters. | | `company` | string or null | no | Company. Up to 255 characters. | | `notes` | string or null | no | Your notes. Up to 2000 characters. | | `billingAddress` | object or null | no | A postal address printed on invoices and quotes. | | `billingAddress.line1` | string | yes | Street address. Up to 200 characters. | | `billingAddress.line2` | string or null | no | Apartment, suite, unit. Up to 200 characters. | | `billingAddress.city` | string | yes | City. Up to 100 characters. | | `billingAddress.state` | string or null | no | State, province or region. Up to 100 characters. | | `billingAddress.postalCode` | string | yes | Postal code. Up to 20 characters. | | `billingAddress.country` | string | yes | ISO 3166-1 alpha-2 country code. | | `taxId` | string or null | no | VAT, EIN or other tax number. Up to 64 characters. | **Request (curl)** ```bash curl -X PATCH https://anytime-capital.com/api/v1/customers/cus_1042 \ -H "Authorization: Bearer $ANYTIME_API_KEY" \ -H "Idempotency-Key: customer-jordan-phone" \ -H "Content-Type: application/json" \ -d '{ "phone": "+1 555 0199", "notes": null }' ``` **Request (Node.js)** ```javascript const res = await fetch("https://anytime-capital.com/api/v1/customers/cus_1042", { method: "PATCH", headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}`, "Content-Type": "application/json", "Idempotency-Key": "customer-jordan-phone", }, body: JSON.stringify({ phone: "+1 555 0199", notes: null }), }); const customer = await res.json(); if (!res.ok) throw new Error(`${customer.error.code}: ${customer.error.message}`); ``` **Request (Node SDK)** ```typescript import { AnytimeCapital } from "@anytimecapital/node"; const client = new AnytimeCapital({ apiKey: process.env.ANYTIME_API_KEY! }); const customer = await client.customers.update( "cus_1042", { phone: "+1 555 0199", notes: null }, { idempotencyKey: "customer-jordan-phone" }, ); ``` **Request (Python)** ```python import os import requests res = requests.patch( "https://anytime-capital.com/api/v1/customers/cus_1042", headers={ "Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}", "Idempotency-Key": "customer-jordan-phone", }, json={"phone": "+1 555 0199", "notes": None}, timeout=30, ) customer = res.json() if not res.ok: raise RuntimeError(f"{customer['error']['code']}: {customer['error']['message']}") ``` **Response** (200) ```json { "object": "customer", "livemode": true, "id": "cus_1042", "name": "Jordan Lee", "email": "jordan@example.com", "phone": "+1 555 0199", "company": "Lee Studio", "notes": null, "billingAddress": null, "taxId": null, "country": "US", "createdAt": "2026-09-02T11:20:00.000Z" } ``` **Errors** | Status | Code | When | | --- | --- | --- | | 400 | `INVALID_JSON` | The body isn't valid JSON, or couldn't be read. An empty body reads as `{}`. Send `Content-Type: application/json` and a JSON object. | | 400 | `INVALID_INPUT` | A field, filter, `limit` or `cursor` is missing or invalid. The message starts with the field's name, as in `amountCents: …`, or reads `Invalid cursor.` Fix the named field. Don't retry the same request unchanged. | | 400 | `INVALID_IDEMPOTENCY_KEY` | The `Idempotency-Key` header is empty, longer than 255 characters, or has characters other than printable ASCII. Use a UUID. See [Idempotency](https://anytime-capital.com/docs/idempotency.md). | | 404 | `NOT_FOUND` | No such object on your business (an id from another business reads the same), or no such endpoint under `/api/v1`, which still answers in JSON. Check the id and the path. Ids are case-sensitive. | | 409 | `DUPLICATE_CUSTOMER` | Another saved customer already has this email address, in any letter case. Find them with `GET /api/v1/customers?email=…` and update that customer instead. | | 422 | `IDEMPOTENCY_KEY_REUSED` | The `Idempotency-Key` was used in the last 24 hours with a different body or endpoint. Use a new key for a new request; reuse a key only to retry the same request. | ## Find or create To keep one record per person, look the email up first and create only when nothing comes back. Two processes doing this at once can race: the second create answers `409 DUPLICATE_CUSTOMER`, so treat that as “found” and list by email again. **Node.js 18+** ```javascript const API = "https://anytime-capital.com/api/v1"; const headers = { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}` }; export async function findOrCreateCustomer({ name, email }) { const find = async () => { const res = await fetch(`${API}/customers?email=${encodeURIComponent(email)}`, { headers }); const page = await res.json(); if (!res.ok) throw new Error(`${page.error.code}: ${page.error.message}`); return page.data[0] ?? null; }; const existing = await find(); if (existing) return existing; const res = await fetch(`${API}/customers`, { method: "POST", headers: { ...headers, "Content-Type": "application/json" }, body: JSON.stringify({ name, email }), }); const customer = await res.json(); // Someone else created them first, or our earlier attempt went through: // one customer per email makes the retry safe without an Idempotency-Key. if (res.status === 409 && customer.error.code === "DUPLICATE_CUSTOMER") return find(); if (!res.ok) throw new Error(`${customer.error.code}: ${customer.error.message}`); return customer; } ``` `customer.created` and `customer.updated` webhooks carry this object whenever a customer is added or changed, by hand, by the API or when they first pay. --- # Products API > Manage your product catalog with the Anytime Capital Payments API: create products with a price, SKU and shipping flag, update them and archive them. Source: https://anytime-capital.com/docs/products · Markdown: https://anytime-capital.com/docs/products.md · All docs: https://anytime-capital.com/docs/llms-full.txt Your product catalog (the **Products** page): what you sell, with a price, an optional SKU and whether it ships. Products are archived, never deleted, so payment links and receipts that used one keep its name. Images are added in the dashboard, and links that sell products are built there too: the API's payment links take a single price. | Topic | Detail | | --- | --- | | Endpoints | [`POST /api/v1/products`](#create-product), [`GET /api/v1/products`](#list-products), [`GET /api/v1/products/{id}`](#get-product), [`PATCH /api/v1/products/{id}`](#update-product), [`POST /api/v1/products/{id}/archive`](#archive-product) | | Key permissions | `products:write`, `products:read` | | Samples | curl; Node.js 18+ with the built-in `fetch` (in an ES module); the [Node SDK](https://anytime-capital.com/docs/node-sdk.md) (preview: not on npm yet); Python with `requests`. | | Every request can also answer | 401 `UNAUTHORIZED`, 401 `API_KEY_EXPIRED`, 403 `NOT_ENROLLED`, 403 `PERMISSION_DENIED`, 403 `IP_NOT_ALLOWED`, 429 `RATE_LIMITED`, 500 `INTERNAL_ERROR`. See [Error codes](https://anytime-capital.com/docs/error-codes.md). | ## The product object ```json { "object": "product", "livemode": true, "id": "prod_Lm3Qx8Rt2Vb7Nc4Zp9Wk", "name": "Ceramic mug", "description": "Hand-glazed, 12 oz.", "priceCents": 1800, "currency": "usd", "sku": "MUG-12", "physical": true, "imageUrl": null, "active": true, "createdAt": "2026-10-02T10:00:00.000Z", "archivedAt": null } ``` | Field | Type | Description | | --- | --- | --- | | `object` | string | The type of object. Always `product`. | | `livemode` | boolean | False for test-mode data (an `ak_test_` key); true for live data. | | `id` | string | `prod_` followed by 20 letters and digits. | | `name` | string | Name. | | `description` | string or null | Description. | | `priceCents` | integer | Price. Integer cents. | | `currency` | string | usd. | | `sku` | string or null | SKU. | | `physical` | boolean | Ships: checkout asks for a shipping address. | | `imageUrl` | string (uri) or null | Image, while it has one and isn't archived. | | `active` | boolean | Not archived. | | `createdAt` | string (date-time) | Added. | | `archivedAt` | string (date-time) or null | Archived. | ## Endpoints ### Create a product `POST /api/v1/products` Permission: `products:write`. Supports `Idempotency-Key`. Add a product to the catalog. Images are added in the dashboard. **Headers** | Header | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | Recommended. A unique key such as a UUID: a retry with the same key within 24 hours gets the first response back instead of acting twice. See [Idempotency](https://anytime-capital.com/docs/idempotency.md). | **Body** | Name | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | yes | Name. Up to 120 characters. | | `description` | string or null | no | Description. Up to 1000 characters. | | `priceCents` | integer | yes | Price in cents. From 1 ($0.01) to 10,000,000 ($100,000.00). | | `sku` | string or null | no | SKU: letters, numbers, spaces and - _ . / #. Up to 64 characters. | | `physical` | boolean | no | Ships: checkout asks for a shipping address. Default false. | **Request (curl)** ```bash curl -X POST https://anytime-capital.com/api/v1/products \ -H "Authorization: Bearer $ANYTIME_API_KEY" \ -H "Idempotency-Key: product-mug-12" \ -H "Content-Type: application/json" \ -d '{ "name": "Ceramic mug", "priceCents": 1800, "sku": "MUG-12", "physical": true }' ``` **Request (Node.js)** ```javascript const res = await fetch("https://anytime-capital.com/api/v1/products", { method: "POST", headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}`, "Content-Type": "application/json", "Idempotency-Key": "product-mug-12", }, body: JSON.stringify({ name: "Ceramic mug", priceCents: 1800, sku: "MUG-12", physical: true }), }); const product = await res.json(); if (!res.ok) throw new Error(`${product.error.code}: ${product.error.message}`); ``` **Request (Node SDK)** ```typescript import { AnytimeCapital } from "@anytimecapital/node"; const client = new AnytimeCapital({ apiKey: process.env.ANYTIME_API_KEY! }); const product = await client.products.create( { name: "Ceramic mug", priceCents: 1800, sku: "MUG-12", physical: true }, { idempotencyKey: "product-mug-12" }, ); ``` **Request (Python)** ```python import os import requests res = requests.post( "https://anytime-capital.com/api/v1/products", headers={ "Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}", "Idempotency-Key": "product-mug-12", }, json={ "name": "Ceramic mug", "priceCents": 1800, "sku": "MUG-12", "physical": True, }, timeout=30, ) product = res.json() if not res.ok: raise RuntimeError(f"{product['error']['code']}: {product['error']['message']}") ``` **Response** (201) ```json { "object": "product", "livemode": true, "id": "prod_Lm3Qx8Rt2Vb7Nc4Zp9Wk", "name": "Ceramic mug", "description": null, "priceCents": 1800, "currency": "usd", "sku": "MUG-12", "physical": true, "imageUrl": null, "active": true, "createdAt": "2026-10-02T10:00:00.000Z", "archivedAt": null } ``` **Errors** | Status | Code | When | | --- | --- | --- | | 400 | `INVALID_JSON` | The body isn't valid JSON, or couldn't be read. An empty body reads as `{}`. Send `Content-Type: application/json` and a JSON object. | | 400 | `INVALID_INPUT` | A field, filter, `limit` or `cursor` is missing or invalid. The message starts with the field's name, as in `amountCents: …`, or reads `Invalid cursor.` Fix the named field. Don't retry the same request unchanged. | | 400 | `INVALID_IDEMPOTENCY_KEY` | The `Idempotency-Key` header is empty, longer than 255 characters, or has characters other than printable ASCII. Use a UUID. See [Idempotency](https://anytime-capital.com/docs/idempotency.md). | | 422 | `IDEMPOTENCY_KEY_REUSED` | The `Idempotency-Key` was used in the last 24 hours with a different body or endpoint. Use a new key for a new request; reuse a key only to retry the same request. | ### List products `GET /api/v1/products` Permission: `products:read`. The product catalog, newest first. **Query** | Name | Type | Required | Description | | --- | --- | --- | --- | | `active` | boolean | no | true for live objects, false for deactivated or archived ones. | | `limit` | integer | no | How many objects per page, 1–100. Default 25. See [Pagination](https://anytime-capital.com/docs/pagination.md). | | `cursor` | string | no | The previous page's `nextCursor`, for the page after it. | **Request (curl)** ```bash curl "https://anytime-capital.com/api/v1/products?active=true" \ -H "Authorization: Bearer $ANYTIME_API_KEY" ``` **Request (Node.js)** ```javascript const res = await fetch("https://anytime-capital.com/api/v1/products?active=true", { headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}` }, }); const page = await res.json(); if (!res.ok) throw new Error(`${page.error.code}: ${page.error.message}`); ``` **Request (Node SDK)** ```typescript import { AnytimeCapital } from "@anytimecapital/node"; const client = new AnytimeCapital({ apiKey: process.env.ANYTIME_API_KEY! }); const page = await client.products.list({ active: true }); ``` **Request (Python)** ```python import os import requests res = requests.get( "https://anytime-capital.com/api/v1/products", headers={"Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}"}, params={"active": "true"}, timeout=30, ) page = res.json() if not res.ok: raise RuntimeError(f"{page['error']['code']}: {page['error']['message']}") ``` **Response** (200) ```json { "object": "list", "data": [ { "object": "product", "livemode": true, "id": "prod_Lm3Qx8Rt2Vb7Nc4Zp9Wk", "name": "Ceramic mug", "description": "Hand-glazed, 12 oz.", "priceCents": 1800, "currency": "usd", "sku": "MUG-12", "physical": true, "imageUrl": null, "active": true, "createdAt": "2026-10-02T10:00:00.000Z", "archivedAt": null } ], "hasMore": false, "nextCursor": null } ``` **Errors** | Status | Code | When | | --- | --- | --- | | 400 | `INVALID_INPUT` | A field, filter, `limit` or `cursor` is missing or invalid. The message starts with the field's name, as in `amountCents: …`, or reads `Invalid cursor.` Fix the named field. Don't retry the same request unchanged. | ### Get a product `GET /api/v1/products/{id}` Permission: `products:read`. One product. **Path** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | `prod_…` | **Request (curl)** ```bash curl https://anytime-capital.com/api/v1/products/prod_Lm3Qx8Rt2Vb7Nc4Zp9Wk \ -H "Authorization: Bearer $ANYTIME_API_KEY" ``` **Request (Node.js)** ```javascript const res = await fetch("https://anytime-capital.com/api/v1/products/prod_Lm3Qx8Rt2Vb7Nc4Zp9Wk", { headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}` }, }); const product = await res.json(); if (!res.ok) throw new Error(`${product.error.code}: ${product.error.message}`); ``` **Request (Node SDK)** ```typescript import { AnytimeCapital } from "@anytimecapital/node"; const client = new AnytimeCapital({ apiKey: process.env.ANYTIME_API_KEY! }); const product = await client.products.retrieve("prod_Lm3Qx8Rt2Vb7Nc4Zp9Wk"); ``` **Request (Python)** ```python import os import requests res = requests.get( "https://anytime-capital.com/api/v1/products/prod_Lm3Qx8Rt2Vb7Nc4Zp9Wk", headers={"Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}"}, timeout=30, ) product = res.json() if not res.ok: raise RuntimeError(f"{product['error']['code']}: {product['error']['message']}") ``` **Response** (200) ```json { "object": "product", "livemode": true, "id": "prod_Lm3Qx8Rt2Vb7Nc4Zp9Wk", "name": "Ceramic mug", "description": "Hand-glazed, 12 oz.", "priceCents": 1800, "currency": "usd", "sku": "MUG-12", "physical": true, "imageUrl": null, "active": true, "createdAt": "2026-10-02T10:00:00.000Z", "archivedAt": null } ``` **Errors** | Status | Code | When | | --- | --- | --- | | 404 | `NOT_FOUND` | No such object on your business (an id from another business reads the same), or no such endpoint under `/api/v1`, which still answers in JSON. Check the id and the path. Ids are case-sensitive. | ### Update a product `PATCH /api/v1/products/{id}` Permission: `products:write`. Supports `Idempotency-Key`. Change name, description, price, SKU or physical. Payment links already selling it keep the name and price they were made with. **Path** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | `prod_…` | **Headers** | Header | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | Recommended. A unique key such as a UUID: a retry with the same key within 24 hours gets the first response back instead of acting twice. See [Idempotency](https://anytime-capital.com/docs/idempotency.md). | **Body** | Name | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | no | Name. Up to 120 characters. | | `description` | string or null | no | Description. Up to 1000 characters. | | `priceCents` | integer | no | Price in cents. From 1 ($0.01) to 10,000,000 ($100,000.00). | | `sku` | string or null | no | SKU: letters, numbers, spaces and - _ . / #. Up to 64 characters. | | `physical` | boolean | no | Ships: checkout asks for a shipping address. Default false. | **Request (curl)** ```bash curl -X PATCH https://anytime-capital.com/api/v1/products/prod_Lm3Qx8Rt2Vb7Nc4Zp9Wk \ -H "Authorization: Bearer $ANYTIME_API_KEY" \ -H "Idempotency-Key: product-mug-price-2000" \ -H "Content-Type: application/json" \ -d '{ "priceCents": 2000 }' ``` **Request (Node.js)** ```javascript const res = await fetch("https://anytime-capital.com/api/v1/products/prod_Lm3Qx8Rt2Vb7Nc4Zp9Wk", { method: "PATCH", headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}`, "Content-Type": "application/json", "Idempotency-Key": "product-mug-price-2000", }, body: JSON.stringify({ priceCents: 2000 }), }); const product = await res.json(); if (!res.ok) throw new Error(`${product.error.code}: ${product.error.message}`); ``` **Request (Node SDK)** ```typescript import { AnytimeCapital } from "@anytimecapital/node"; const client = new AnytimeCapital({ apiKey: process.env.ANYTIME_API_KEY! }); const product = await client.products.update( "prod_Lm3Qx8Rt2Vb7Nc4Zp9Wk", { priceCents: 2000 }, { idempotencyKey: "product-mug-price-2000" }, ); ``` **Request (Python)** ```python import os import requests res = requests.patch( "https://anytime-capital.com/api/v1/products/prod_Lm3Qx8Rt2Vb7Nc4Zp9Wk", headers={ "Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}", "Idempotency-Key": "product-mug-price-2000", }, json={"priceCents": 2000}, timeout=30, ) product = res.json() if not res.ok: raise RuntimeError(f"{product['error']['code']}: {product['error']['message']}") ``` **Response** (200) ```json { "object": "product", "livemode": true, "id": "prod_Lm3Qx8Rt2Vb7Nc4Zp9Wk", "name": "Ceramic mug", "description": "Hand-glazed, 12 oz.", "priceCents": 2000, "currency": "usd", "sku": "MUG-12", "physical": true, "imageUrl": null, "active": true, "createdAt": "2026-10-02T10:00:00.000Z", "archivedAt": null } ``` **Errors** | Status | Code | When | | --- | --- | --- | | 400 | `INVALID_JSON` | The body isn't valid JSON, or couldn't be read. An empty body reads as `{}`. Send `Content-Type: application/json` and a JSON object. | | 400 | `INVALID_INPUT` | A field, filter, `limit` or `cursor` is missing or invalid. The message starts with the field's name, as in `amountCents: …`, or reads `Invalid cursor.` Fix the named field. Don't retry the same request unchanged. | | 400 | `INVALID_IDEMPOTENCY_KEY` | The `Idempotency-Key` header is empty, longer than 255 characters, or has characters other than printable ASCII. Use a UUID. See [Idempotency](https://anytime-capital.com/docs/idempotency.md). | | 404 | `NOT_FOUND` | No such object on your business (an id from another business reads the same), or no such endpoint under `/api/v1`, which still answers in JSON. Check the id and the path. Ids are case-sensitive. | | 422 | `IDEMPOTENCY_KEY_REUSED` | The `Idempotency-Key` was used in the last 24 hours with a different body or endpoint. Use a new key for a new request; reuse a key only to retry the same request. | ### Archive a product `POST /api/v1/products/{id}/archive` Permission: `products:write`. Supports `Idempotency-Key`. Take it out of the catalog. Safe to repeat. Nothing is deleted; restore it from **Products** in the dashboard. **Path** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | `prod_…` | **Headers** | Header | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | Recommended. A unique key such as a UUID: a retry with the same key within 24 hours gets the first response back instead of acting twice. See [Idempotency](https://anytime-capital.com/docs/idempotency.md). | **Request (curl)** ```bash curl -X POST https://anytime-capital.com/api/v1/products/prod_Lm3Qx8Rt2Vb7Nc4Zp9Wk/archive \ -H "Authorization: Bearer $ANYTIME_API_KEY" \ -H "Idempotency-Key: product-mug-archive" ``` **Request (Node.js)** ```javascript const res = await fetch("https://anytime-capital.com/api/v1/products/prod_Lm3Qx8Rt2Vb7Nc4Zp9Wk/archive", { method: "POST", headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}`, "Idempotency-Key": "product-mug-archive", }, }); const product = await res.json(); if (!res.ok) throw new Error(`${product.error.code}: ${product.error.message}`); ``` **Request (Node SDK)** ```typescript import { AnytimeCapital } from "@anytimecapital/node"; const client = new AnytimeCapital({ apiKey: process.env.ANYTIME_API_KEY! }); const product = await client.products.archive( "prod_Lm3Qx8Rt2Vb7Nc4Zp9Wk", { idempotencyKey: "product-mug-archive" }, ); ``` **Request (Python)** ```python import os import requests res = requests.post( "https://anytime-capital.com/api/v1/products/prod_Lm3Qx8Rt2Vb7Nc4Zp9Wk/archive", headers={ "Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}", "Idempotency-Key": "product-mug-archive", }, timeout=30, ) product = res.json() if not res.ok: raise RuntimeError(f"{product['error']['code']}: {product['error']['message']}") ``` **Response** (200) ```json { "object": "product", "livemode": true, "id": "prod_Lm3Qx8Rt2Vb7Nc4Zp9Wk", "name": "Ceramic mug", "description": "Hand-glazed, 12 oz.", "priceCents": 1800, "currency": "usd", "sku": "MUG-12", "physical": true, "imageUrl": null, "active": false, "createdAt": "2026-10-02T10:00:00.000Z", "archivedAt": "2026-10-24T12:00:00.000Z" } ``` **Errors** | Status | Code | When | | --- | --- | --- | | 400 | `INVALID_IDEMPOTENCY_KEY` | The `Idempotency-Key` header is empty, longer than 255 characters, or has characters other than printable ASCII. Use a UUID. See [Idempotency](https://anytime-capital.com/docs/idempotency.md). | | 404 | `NOT_FOUND` | No such object on your business (an id from another business reads the same), or no such endpoint under `/api/v1`, which still answers in JSON. Check the id and the path. Ids are case-sensitive. | | 422 | `IDEMPOTENCY_KEY_REUSED` | The `Idempotency-Key` was used in the last 24 hours with a different body or endpoint. Use a new key for a new request; reuse a key only to retry the same request. | --- # Subscription plans and subscriptions API > Create subscription plans, list subscribers, and cancel, pause or resume subscriptions with the Anytime Capital Payments API, with trials and webhooks. Source: https://anytime-capital.com/docs/subscriptions · Markdown: https://anytime-capital.com/docs/subscriptions.md · All docs: https://anytime-capital.com/docs/llms-full.txt A **plan** is a recurring price customers subscribe to at its `url`, billed every `week`, `month`, `quarter` or `year`, with an optional free trial. A **subscription** is one customer's subscription to a plan. Customers subscribe themselves at checkout; the API creates plans, reads subscribers and cancels, pauses or resumes them, following the dashboard's rules and sending the same emails. Both need the `subscriptions` permission. | Topic | Detail | | --- | --- | | Endpoints | [`POST /api/v1/subscription-plans`](#create-subscription-plan), [`GET /api/v1/subscription-plans`](#list-subscription-plans), [`GET /api/v1/subscription-plans/{id}`](#get-subscription-plan), [`GET /api/v1/subscriptions`](#list-subscriptions), [`GET /api/v1/subscriptions/{id}`](#get-subscription), [`POST /api/v1/subscriptions/{id}/cancel`](#cancel-subscription), [`POST /api/v1/subscriptions/{id}/pause`](#pause-subscription), [`POST /api/v1/subscriptions/{id}/resume`](#resume-subscription) | | Key permissions | `subscriptions:write`, `subscriptions:read` | | Samples | curl; Node.js 18+ with the built-in `fetch` (in an ES module); the [Node SDK](https://anytime-capital.com/docs/node-sdk.md) (preview: not on npm yet); Python with `requests`. | | Every request can also answer | 401 `UNAUTHORIZED`, 401 `API_KEY_EXPIRED`, 403 `NOT_ENROLLED`, 403 `PERMISSION_DENIED`, 403 `IP_NOT_ALLOWED`, 429 `RATE_LIMITED`, 500 `INTERNAL_ERROR`. See [Error codes](https://anytime-capital.com/docs/error-codes.md). | > **Done in the dashboard:** Changing a subscriber's plan (with proration), discount codes on subscriptions, usage meters on a plan and archiving a plan are set up on the **Subscriptions** page. See [Subscription discounts and plan changes](https://anytime-capital.com/docs/subscription-discounts.md) and [Usage billing](https://anytime-capital.com/docs/usage-billing.md). A plan's price and interval are fixed once it exists. ## The subscription plan object ```json { "object": "subscription_plan", "livemode": true, "id": "sp_Vb7Nc4Zp9WkLm3Qx8Rt2", "title": "Studio membership", "description": "Unlimited sessions each month", "amountCents": 4900, "currency": "usd", "interval": "month", "trialDays": 14, "url": "https://anytime-capital.com/pay/business/pl_Qx8Rt2Vb7Nc4Zp9WkLm3", "active": true, "createdAt": "2026-09-15T12:00:00.000Z", "archivedAt": null } ``` | Field | Type | Description | | --- | --- | --- | | `object` | string | The type of object. Always `subscription_plan`. | | `livemode` | boolean | False for test-mode data (an `ak_test_` key); true for live data. | | `id` | string | `sp_` followed by 20 letters and digits. | | `title` | string | Name. | | `description` | string or null | Description. | | `amountCents` | integer | Price per interval. Integer cents. | | `currency` | string | usd. | | `interval` | string | One of `week`, `month`, `quarter`, `year`. | | `trialDays` | integer | Free trial length, in days. | | `url` | string (uri) or null | Where customers subscribe; null once archived. | | `active` | boolean | Taking new subscribers. | | `createdAt` | string (date-time) | Created. | | `archivedAt` | string (date-time) or null | Archived. | ## Plan endpoints ### Create a subscription plan `POST /api/v1/subscription-plans` Permission: `subscriptions:write`. Supports `Idempotency-Key`. A recurring price customers subscribe to at its url. Price and interval are fixed once made. Share the plan's `url`: customers subscribe there. **Headers** | Header | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | Recommended. A unique key such as a UUID: a retry with the same key within 24 hours gets the first response back instead of acting twice. See [Idempotency](https://anytime-capital.com/docs/idempotency.md). | **Body** | Name | Type | Required | Description | | --- | --- | --- | --- | | `title` | string | yes | Name customers recognise. Up to 120 characters. | | `description` | string or null | no | Description. Up to 500 characters. | | `amountCents` | integer | yes | Price per interval, in cents. At least 1 ($0.01). | | `interval` | string | yes | How often it bills. One of `week`, `month`, `quarter`, `year`. | | `trialDays` | integer | no | Free trial days. Default 0. From 0 to 90. | **Request (curl)** ```bash curl -X POST https://anytime-capital.com/api/v1/subscription-plans \ -H "Authorization: Bearer $ANYTIME_API_KEY" \ -H "Idempotency-Key: plan-studio-monthly" \ -H "Content-Type: application/json" \ -d '{ "title": "Studio membership", "description": "Unlimited sessions each month", "amountCents": 4900, "interval": "month", "trialDays": 14 }' ``` **Request (Node.js)** ```javascript const res = await fetch("https://anytime-capital.com/api/v1/subscription-plans", { method: "POST", headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}`, "Content-Type": "application/json", "Idempotency-Key": "plan-studio-monthly", }, body: JSON.stringify({ title: "Studio membership", description: "Unlimited sessions each month", amountCents: 4900, interval: "month", trialDays: 14, }), }); const plan = await res.json(); if (!res.ok) throw new Error(`${plan.error.code}: ${plan.error.message}`); ``` **Request (Node SDK)** ```typescript import { AnytimeCapital } from "@anytimecapital/node"; const client = new AnytimeCapital({ apiKey: process.env.ANYTIME_API_KEY! }); const plan = await client.subscriptionPlans.create( { title: "Studio membership", description: "Unlimited sessions each month", amountCents: 4900, interval: "month", trialDays: 14, }, { idempotencyKey: "plan-studio-monthly" }, ); ``` **Request (Python)** ```python import os import requests res = requests.post( "https://anytime-capital.com/api/v1/subscription-plans", headers={ "Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}", "Idempotency-Key": "plan-studio-monthly", }, json={ "title": "Studio membership", "description": "Unlimited sessions each month", "amountCents": 4900, "interval": "month", "trialDays": 14, }, timeout=30, ) plan = res.json() if not res.ok: raise RuntimeError(f"{plan['error']['code']}: {plan['error']['message']}") ``` **Response** (201) ```json { "object": "subscription_plan", "livemode": true, "id": "sp_Vb7Nc4Zp9WkLm3Qx8Rt2", "title": "Studio membership", "description": "Unlimited sessions each month", "amountCents": 4900, "currency": "usd", "interval": "month", "trialDays": 14, "url": "https://anytime-capital.com/pay/business/pl_Qx8Rt2Vb7Nc4Zp9WkLm3", "active": true, "createdAt": "2026-09-15T12:00:00.000Z", "archivedAt": null } ``` **Errors** | Status | Code | When | | --- | --- | --- | | 400 | `INVALID_JSON` | The body isn't valid JSON, or couldn't be read. An empty body reads as `{}`. Send `Content-Type: application/json` and a JSON object. | | 400 | `INVALID_INPUT` | A field, filter, `limit` or `cursor` is missing or invalid. The message starts with the field's name, as in `amountCents: …`, or reads `Invalid cursor.` Fix the named field. Don't retry the same request unchanged. | | 400 | `INVALID_IDEMPOTENCY_KEY` | The `Idempotency-Key` header is empty, longer than 255 characters, or has characters other than printable ASCII. Use a UUID. See [Idempotency](https://anytime-capital.com/docs/idempotency.md). | | 422 | `IDEMPOTENCY_KEY_REUSED` | The `Idempotency-Key` was used in the last 24 hours with a different body or endpoint. Use a new key for a new request; reuse a key only to retry the same request. | ### List subscription plans `GET /api/v1/subscription-plans` Permission: `subscriptions:read`. Plans, newest first. **Query** | Name | Type | Required | Description | | --- | --- | --- | --- | | `active` | boolean | no | true for live objects, false for deactivated or archived ones. | | `limit` | integer | no | How many objects per page, 1–100. Default 25. See [Pagination](https://anytime-capital.com/docs/pagination.md). | | `cursor` | string | no | The previous page's `nextCursor`, for the page after it. | **Request (curl)** ```bash curl "https://anytime-capital.com/api/v1/subscription-plans?active=true" \ -H "Authorization: Bearer $ANYTIME_API_KEY" ``` **Request (Node.js)** ```javascript const res = await fetch("https://anytime-capital.com/api/v1/subscription-plans?active=true", { headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}` }, }); const page = await res.json(); if (!res.ok) throw new Error(`${page.error.code}: ${page.error.message}`); ``` **Request (Node SDK)** ```typescript import { AnytimeCapital } from "@anytimecapital/node"; const client = new AnytimeCapital({ apiKey: process.env.ANYTIME_API_KEY! }); const page = await client.subscriptionPlans.list({ active: true }); ``` **Request (Python)** ```python import os import requests res = requests.get( "https://anytime-capital.com/api/v1/subscription-plans", headers={"Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}"}, params={"active": "true"}, timeout=30, ) page = res.json() if not res.ok: raise RuntimeError(f"{page['error']['code']}: {page['error']['message']}") ``` **Response** (200) ```json { "object": "list", "data": [ { "object": "subscription_plan", "livemode": true, "id": "sp_Vb7Nc4Zp9WkLm3Qx8Rt2", "title": "Studio membership", "description": "Unlimited sessions each month", "amountCents": 4900, "currency": "usd", "interval": "month", "trialDays": 14, "url": "https://anytime-capital.com/pay/business/pl_Qx8Rt2Vb7Nc4Zp9WkLm3", "active": true, "createdAt": "2026-09-15T12:00:00.000Z", "archivedAt": null } ], "hasMore": false, "nextCursor": null } ``` **Errors** | Status | Code | When | | --- | --- | --- | | 400 | `INVALID_INPUT` | A field, filter, `limit` or `cursor` is missing or invalid. The message starts with the field's name, as in `amountCents: …`, or reads `Invalid cursor.` Fix the named field. Don't retry the same request unchanged. | ### Get a subscription plan `GET /api/v1/subscription-plans/{id}` Permission: `subscriptions:read`. One plan. **Path** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | `sp_…` | **Request (curl)** ```bash curl https://anytime-capital.com/api/v1/subscription-plans/sp_Vb7Nc4Zp9WkLm3Qx8Rt2 \ -H "Authorization: Bearer $ANYTIME_API_KEY" ``` **Request (Node.js)** ```javascript const res = await fetch("https://anytime-capital.com/api/v1/subscription-plans/sp_Vb7Nc4Zp9WkLm3Qx8Rt2", { headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}` }, }); const plan = await res.json(); if (!res.ok) throw new Error(`${plan.error.code}: ${plan.error.message}`); ``` **Request (Node SDK)** ```typescript import { AnytimeCapital } from "@anytimecapital/node"; const client = new AnytimeCapital({ apiKey: process.env.ANYTIME_API_KEY! }); const plan = await client.subscriptionPlans.retrieve("sp_Vb7Nc4Zp9WkLm3Qx8Rt2"); ``` **Request (Python)** ```python import os import requests res = requests.get( "https://anytime-capital.com/api/v1/subscription-plans/sp_Vb7Nc4Zp9WkLm3Qx8Rt2", headers={"Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}"}, timeout=30, ) plan = res.json() if not res.ok: raise RuntimeError(f"{plan['error']['code']}: {plan['error']['message']}") ``` **Response** (200) ```json { "object": "subscription_plan", "livemode": true, "id": "sp_Vb7Nc4Zp9WkLm3Qx8Rt2", "title": "Studio membership", "description": "Unlimited sessions each month", "amountCents": 4900, "currency": "usd", "interval": "month", "trialDays": 14, "url": "https://anytime-capital.com/pay/business/pl_Qx8Rt2Vb7Nc4Zp9WkLm3", "active": true, "createdAt": "2026-09-15T12:00:00.000Z", "archivedAt": null } ``` **Errors** | Status | Code | When | | --- | --- | --- | | 404 | `NOT_FOUND` | No such object on your business (an id from another business reads the same), or no such endpoint under `/api/v1`, which still answers in JSON. Check the id and the path. Ids are case-sensitive. | ## The subscription object ```json { "object": "subscription", "livemode": true, "id": "sub_2208", "status": "active", "plan": { "id": "sp_Vb7Nc4Zp9WkLm3Qx8Rt2", "title": "Studio membership", "amountCents": 4900, "currency": "usd", "interval": "month" }, "customer": { "name": "Jordan Lee", "email": "jordan@example.com" }, "currentPeriodEnd": "2026-11-15T12:00:00.000Z", "cancelAtPeriodEnd": false, "canceledAt": null, "createdAt": "2026-09-15T12:05:00.000Z" } ``` | Field | Type | Description | | --- | --- | --- | | `object` | string | The type of object. Always `subscription`. | | `livemode` | boolean | False for test-mode data (an `ak_test_` key); true for live data. | | `id` | string | `sub_…` | | `status` | string | `trialing`, `active`, `past_due` (a renewal failed and is being retried), `paused`, `canceling`, or `canceled`, `expired` or `completed` once it has ended. | | `plan` | object | | | `plan.id` | string | `sp_…` | | `plan.title` | string | Name. | | `plan.amountCents` | integer | Price. Integer cents. | | `plan.currency` | string | usd. | | `plan.interval` | string | week, month, quarter or year. | | `customer` | object | | | `customer.name` | string or null | Name. | | `customer.email` | string | Email. | | `currentPeriodEnd` | string (date-time) or null | When the period paid for ends (the next renewal). | | `cancelAtPeriodEnd` | boolean | Set to cancel when the period ends. | | `canceledAt` | string (date-time) or null | When it ended. | | `createdAt` | string (date-time) | Subscribed. | ## Subscription endpoints ### List subscriptions `GET /api/v1/subscriptions` Permission: `subscriptions:read`. Subscribers, newest first. **Query** | Name | Type | Required | Description | | --- | --- | --- | --- | | `status` | string | no | As the Subscriptions page groups them. One of `active`, `trialing`, `past_due`, `paused`, `canceling`, `ended`. | | `planId` | string | no | Only subscribers of this plan, `sp_…`. | | `customerEmail` | string | no | Only this customer's, any case. Up to 255 characters. | | `limit` | integer | no | How many objects per page, 1–100. Default 25. See [Pagination](https://anytime-capital.com/docs/pagination.md). | | `cursor` | string | no | The previous page's `nextCursor`, for the page after it. | **Request (curl)** ```bash curl "https://anytime-capital.com/api/v1/subscriptions?status=active&planId=sp_Vb7Nc4Zp9WkLm3Qx8Rt2" \ -H "Authorization: Bearer $ANYTIME_API_KEY" ``` **Request (Node.js)** ```javascript const res = await fetch("https://anytime-capital.com/api/v1/subscriptions?status=active&planId=sp_Vb7Nc4Zp9WkLm3Qx8Rt2", { headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}` }, }); const page = await res.json(); if (!res.ok) throw new Error(`${page.error.code}: ${page.error.message}`); ``` **Request (Node SDK)** ```typescript import { AnytimeCapital } from "@anytimecapital/node"; const client = new AnytimeCapital({ apiKey: process.env.ANYTIME_API_KEY! }); const page = await client.subscriptions.list( { status: "active", planId: "sp_Vb7Nc4Zp9WkLm3Qx8Rt2" }, ); ``` **Request (Python)** ```python import os import requests res = requests.get( "https://anytime-capital.com/api/v1/subscriptions", headers={"Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}"}, params={"status": "active", "planId": "sp_Vb7Nc4Zp9WkLm3Qx8Rt2"}, timeout=30, ) page = res.json() if not res.ok: raise RuntimeError(f"{page['error']['code']}: {page['error']['message']}") ``` **Response** (200) ```json { "object": "list", "data": [ { "object": "subscription", "livemode": true, "id": "sub_2208", "status": "active", "plan": { "id": "sp_Vb7Nc4Zp9WkLm3Qx8Rt2", "title": "Studio membership", "amountCents": 4900, "currency": "usd", "interval": "month" }, "customer": { "name": "Jordan Lee", "email": "jordan@example.com" }, "currentPeriodEnd": "2026-11-15T12:00:00.000Z", "cancelAtPeriodEnd": false, "canceledAt": null, "createdAt": "2026-09-15T12:05:00.000Z" } ], "hasMore": false, "nextCursor": null } ``` **Errors** | Status | Code | When | | --- | --- | --- | | 400 | `INVALID_INPUT` | A field, filter, `limit` or `cursor` is missing or invalid. The message starts with the field's name, as in `amountCents: …`, or reads `Invalid cursor.` Fix the named field. Don't retry the same request unchanged. | ### Get a subscription `GET /api/v1/subscriptions/{id}` Permission: `subscriptions:read`. One subscriber. **Path** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | `sub_…` | **Request (curl)** ```bash curl https://anytime-capital.com/api/v1/subscriptions/sub_2208 \ -H "Authorization: Bearer $ANYTIME_API_KEY" ``` **Request (Node.js)** ```javascript const res = await fetch("https://anytime-capital.com/api/v1/subscriptions/sub_2208", { headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}` }, }); const subscription = await res.json(); if (!res.ok) throw new Error(`${subscription.error.code}: ${subscription.error.message}`); ``` **Request (Node SDK)** ```typescript import { AnytimeCapital } from "@anytimecapital/node"; const client = new AnytimeCapital({ apiKey: process.env.ANYTIME_API_KEY! }); const subscription = await client.subscriptions.retrieve("sub_2208"); ``` **Request (Python)** ```python import os import requests res = requests.get( "https://anytime-capital.com/api/v1/subscriptions/sub_2208", headers={"Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}"}, timeout=30, ) subscription = res.json() if not res.ok: raise RuntimeError(f"{subscription['error']['code']}: {subscription['error']['message']}") ``` **Response** (200) ```json { "object": "subscription", "livemode": true, "id": "sub_2208", "status": "active", "plan": { "id": "sp_Vb7Nc4Zp9WkLm3Qx8Rt2", "title": "Studio membership", "amountCents": 4900, "currency": "usd", "interval": "month" }, "customer": { "name": "Jordan Lee", "email": "jordan@example.com" }, "currentPeriodEnd": "2026-11-15T12:00:00.000Z", "cancelAtPeriodEnd": false, "canceledAt": null, "createdAt": "2026-09-15T12:05:00.000Z" } ``` **Errors** | Status | Code | When | | --- | --- | --- | | 404 | `NOT_FOUND` | No such object on your business (an id from another business reads the same), or no such endpoint under `/api/v1`, which still answers in JSON. Check the id and the path. Ids are case-sensitive. | ### Cancel a subscription `POST /api/v1/subscriptions/{id}/cancel` Permission: `subscriptions:write`. Supports `Idempotency-Key`. At the period's end (default) or now. The subscriber is emailed. At the period's end, `cancelAtPeriodEnd` is `true` until then; `atPeriodEnd: false` ends it now. The subscriber gets the same email as when you cancel from the dashboard. **Path** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | `sub_…` | **Headers** | Header | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | Recommended. A unique key such as a UUID: a retry with the same key within 24 hours gets the first response back instead of acting twice. See [Idempotency](https://anytime-capital.com/docs/idempotency.md). | **Body** | Name | Type | Required | Description | | --- | --- | --- | --- | | `atPeriodEnd` | boolean | no | true (default): at the end of the period paid for. false: now. | **Request (curl)** ```bash curl -X POST https://anytime-capital.com/api/v1/subscriptions/sub_2208/cancel \ -H "Authorization: Bearer $ANYTIME_API_KEY" \ -H "Idempotency-Key: cancel-sub_2208" \ -H "Content-Type: application/json" \ -d '{ "atPeriodEnd": true }' ``` **Request (Node.js)** ```javascript const res = await fetch("https://anytime-capital.com/api/v1/subscriptions/sub_2208/cancel", { method: "POST", headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}`, "Content-Type": "application/json", "Idempotency-Key": "cancel-sub_2208", }, body: JSON.stringify({ atPeriodEnd: true }), }); const subscription = await res.json(); if (!res.ok) throw new Error(`${subscription.error.code}: ${subscription.error.message}`); ``` **Request (Node SDK)** ```typescript import { AnytimeCapital } from "@anytimecapital/node"; const client = new AnytimeCapital({ apiKey: process.env.ANYTIME_API_KEY! }); const subscription = await client.subscriptions.cancel( "sub_2208", { atPeriodEnd: true }, { idempotencyKey: "cancel-sub_2208" }, ); ``` **Request (Python)** ```python import os import requests res = requests.post( "https://anytime-capital.com/api/v1/subscriptions/sub_2208/cancel", headers={ "Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}", "Idempotency-Key": "cancel-sub_2208", }, json={"atPeriodEnd": True}, timeout=30, ) subscription = res.json() if not res.ok: raise RuntimeError(f"{subscription['error']['code']}: {subscription['error']['message']}") ``` **Response** (200) ```json { "object": "subscription", "livemode": true, "id": "sub_2208", "status": "active", "plan": { "id": "sp_Vb7Nc4Zp9WkLm3Qx8Rt2", "title": "Studio membership", "amountCents": 4900, "currency": "usd", "interval": "month" }, "customer": { "name": "Jordan Lee", "email": "jordan@example.com" }, "currentPeriodEnd": "2026-11-15T12:00:00.000Z", "cancelAtPeriodEnd": true, "canceledAt": null, "createdAt": "2026-09-15T12:05:00.000Z" } ``` **Errors** | Status | Code | When | | --- | --- | --- | | 400 | `INVALID_JSON` | The body isn't valid JSON, or couldn't be read. An empty body reads as `{}`. Send `Content-Type: application/json` and a JSON object. | | 400 | `INVALID_INPUT` | A field, filter, `limit` or `cursor` is missing or invalid. The message starts with the field's name, as in `amountCents: …`, or reads `Invalid cursor.` Fix the named field. Don't retry the same request unchanged. | | 400 | `INVALID_IDEMPOTENCY_KEY` | The `Idempotency-Key` header is empty, longer than 255 characters, or has characters other than printable ASCII. Use a UUID. See [Idempotency](https://anytime-capital.com/docs/idempotency.md). | | 404 | `NOT_FOUND` | No such object on your business (an id from another business reads the same), or no such endpoint under `/api/v1`, which still answers in JSON. Check the id and the path. Ids are case-sensitive. | | 409 | `SUBSCRIPTION_ENDED` | The subscription has already ended (canceled, expired or completed). Nothing to do; a customer subscribes again at the plan's `url`. | | 409 | `ALREADY_CANCELING` | The subscription is already set to cancel at the end of its period. Send `atPeriodEnd: false` to end it now, or `POST …/resume` to keep it. | | 409 | `CHANGE_IN_PROGRESS` | The subscription is switching plans right now. Retry in a minute. | | 422 | `IDEMPOTENCY_KEY_REUSED` | The `Idempotency-Key` was used in the last 24 hours with a different body or endpoint. Use a new key for a new request; reuse a key only to retry the same request. | | 502 | `PROCESSOR_ERROR` | The payments network didn't answer in time. Retry with the same `Idempotency-Key`. A refund is keyed at the network by that key too, so a retry never refunds twice. | | 503 | `NOT_CONFIGURED` | The part of Anytime Capital Payments this needs isn't available right now. Retry later, with the same `Idempotency-Key` on a write. | ### Pause a subscription `POST /api/v1/subscriptions/{id}/pause` Permission: `subscriptions:write`. Supports `Idempotency-Key`. Stop collecting payments on an active or trialing subscription. **Path** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | `sub_…` | **Headers** | Header | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | Recommended. A unique key such as a UUID: a retry with the same key within 24 hours gets the first response back instead of acting twice. See [Idempotency](https://anytime-capital.com/docs/idempotency.md). | **Request (curl)** ```bash curl -X POST https://anytime-capital.com/api/v1/subscriptions/sub_2208/pause \ -H "Authorization: Bearer $ANYTIME_API_KEY" \ -H "Idempotency-Key: pause-sub_2208" ``` **Request (Node.js)** ```javascript const res = await fetch("https://anytime-capital.com/api/v1/subscriptions/sub_2208/pause", { method: "POST", headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}`, "Idempotency-Key": "pause-sub_2208", }, }); const subscription = await res.json(); if (!res.ok) throw new Error(`${subscription.error.code}: ${subscription.error.message}`); ``` **Request (Node SDK)** ```typescript import { AnytimeCapital } from "@anytimecapital/node"; const client = new AnytimeCapital({ apiKey: process.env.ANYTIME_API_KEY! }); const subscription = await client.subscriptions.pause( "sub_2208", { idempotencyKey: "pause-sub_2208" }, ); ``` **Request (Python)** ```python import os import requests res = requests.post( "https://anytime-capital.com/api/v1/subscriptions/sub_2208/pause", headers={ "Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}", "Idempotency-Key": "pause-sub_2208", }, timeout=30, ) subscription = res.json() if not res.ok: raise RuntimeError(f"{subscription['error']['code']}: {subscription['error']['message']}") ``` **Response** (200) ```json { "object": "subscription", "livemode": true, "id": "sub_2208", "status": "paused", "plan": { "id": "sp_Vb7Nc4Zp9WkLm3Qx8Rt2", "title": "Studio membership", "amountCents": 4900, "currency": "usd", "interval": "month" }, "customer": { "name": "Jordan Lee", "email": "jordan@example.com" }, "currentPeriodEnd": "2026-11-15T12:00:00.000Z", "cancelAtPeriodEnd": false, "canceledAt": null, "createdAt": "2026-09-15T12:05:00.000Z" } ``` **Errors** | Status | Code | When | | --- | --- | --- | | 400 | `INVALID_IDEMPOTENCY_KEY` | The `Idempotency-Key` header is empty, longer than 255 characters, or has characters other than printable ASCII. Use a UUID. See [Idempotency](https://anytime-capital.com/docs/idempotency.md). | | 404 | `NOT_FOUND` | No such object on your business (an id from another business reads the same), or no such endpoint under `/api/v1`, which still answers in JSON. Check the id and the path. Ids are case-sensitive. | | 409 | `SUBSCRIPTION_ENDED` | The subscription has already ended (canceled, expired or completed). Nothing to do; a customer subscribes again at the plan's `url`. | | 409 | `CANNOT_PAUSE` | Only an active or trialing subscription that renews can be paused: this one is paused, past due or set to cancel. Read the subscription's `status` and `cancelAtPeriodEnd` first. | | 409 | `CHANGE_IN_PROGRESS` | The subscription is switching plans right now. Retry in a minute. | | 409 | `SWITCH_SCHEDULED` | The subscription switches plans at its next renewal, so it can't be paused or resumed. Undo the switch from the subscriber's details on the **Subscriptions** page first. | | 422 | `IDEMPOTENCY_KEY_REUSED` | The `Idempotency-Key` was used in the last 24 hours with a different body or endpoint. Use a new key for a new request; reuse a key only to retry the same request. | | 502 | `PROCESSOR_ERROR` | The payments network didn't answer in time. Retry with the same `Idempotency-Key`. A refund is keyed at the network by that key too, so a retry never refunds twice. | | 503 | `NOT_CONFIGURED` | The part of Anytime Capital Payments this needs isn't available right now. Retry later, with the same `Idempotency-Key` on a write. | ### Resume a subscription `POST /api/v1/subscriptions/{id}/resume` Permission: `subscriptions:write`. Supports `Idempotency-Key`. Undo a pause, or a cancellation set for the period's end. **Path** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | `sub_…` | **Headers** | Header | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | Recommended. A unique key such as a UUID: a retry with the same key within 24 hours gets the first response back instead of acting twice. See [Idempotency](https://anytime-capital.com/docs/idempotency.md). | **Request (curl)** ```bash curl -X POST https://anytime-capital.com/api/v1/subscriptions/sub_2208/resume \ -H "Authorization: Bearer $ANYTIME_API_KEY" \ -H "Idempotency-Key: resume-sub_2208" ``` **Request (Node.js)** ```javascript const res = await fetch("https://anytime-capital.com/api/v1/subscriptions/sub_2208/resume", { method: "POST", headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}`, "Idempotency-Key": "resume-sub_2208", }, }); const subscription = await res.json(); if (!res.ok) throw new Error(`${subscription.error.code}: ${subscription.error.message}`); ``` **Request (Node SDK)** ```typescript import { AnytimeCapital } from "@anytimecapital/node"; const client = new AnytimeCapital({ apiKey: process.env.ANYTIME_API_KEY! }); const subscription = await client.subscriptions.resume( "sub_2208", { idempotencyKey: "resume-sub_2208" }, ); ``` **Request (Python)** ```python import os import requests res = requests.post( "https://anytime-capital.com/api/v1/subscriptions/sub_2208/resume", headers={ "Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}", "Idempotency-Key": "resume-sub_2208", }, timeout=30, ) subscription = res.json() if not res.ok: raise RuntimeError(f"{subscription['error']['code']}: {subscription['error']['message']}") ``` **Response** (200) ```json { "object": "subscription", "livemode": true, "id": "sub_2208", "status": "active", "plan": { "id": "sp_Vb7Nc4Zp9WkLm3Qx8Rt2", "title": "Studio membership", "amountCents": 4900, "currency": "usd", "interval": "month" }, "customer": { "name": "Jordan Lee", "email": "jordan@example.com" }, "currentPeriodEnd": "2026-11-15T12:00:00.000Z", "cancelAtPeriodEnd": false, "canceledAt": null, "createdAt": "2026-09-15T12:05:00.000Z" } ``` **Errors** | Status | Code | When | | --- | --- | --- | | 400 | `INVALID_IDEMPOTENCY_KEY` | The `Idempotency-Key` header is empty, longer than 255 characters, or has characters other than printable ASCII. Use a UUID. See [Idempotency](https://anytime-capital.com/docs/idempotency.md). | | 404 | `NOT_FOUND` | No such object on your business (an id from another business reads the same), or no such endpoint under `/api/v1`, which still answers in JSON. Check the id and the path. Ids are case-sensitive. | | 409 | `SUBSCRIPTION_ENDED` | The subscription has already ended (canceled, expired or completed). Nothing to do; a customer subscribes again at the plan's `url`. | | 409 | `NOT_PAUSED` | The subscription is neither paused nor set to cancel, so there's nothing to resume. Nothing to do. | | 409 | `CHANGE_IN_PROGRESS` | The subscription is switching plans right now. Retry in a minute. | | 409 | `SWITCH_SCHEDULED` | The subscription switches plans at its next renewal, so it can't be paused or resumed. Undo the switch from the subscriber's details on the **Subscriptions** page first. | | 422 | `IDEMPOTENCY_KEY_REUSED` | The `Idempotency-Key` was used in the last 24 hours with a different body or endpoint. Use a new key for a new request; reuse a key only to retry the same request. | | 502 | `PROCESSOR_ERROR` | The payments network didn't answer in time. Retry with the same `Idempotency-Key`. A refund is keyed at the network by that key too, so a retry never refunds twice. | | 503 | `NOT_CONFIGURED` | The part of Anytime Capital Payments this needs isn't available right now. Retry later, with the same `Idempotency-Key` on a write. | ## Events `subscription.created`, `subscription.updated`, `subscription.paused`, `subscription.resumed`, `subscription.past_due` and `subscription.canceled` carry the subscription object; each renewal's charge sends `payment.succeeded` with `source.type` `subscription`. The [subscriptions guide](https://anytime-capital.com/docs/sell-subscriptions.md) turns these into access in your app. --- # Discount codes API > Create and deactivate discount codes with the Anytime Capital Payments API: a percentage or amount off, with an optional use limit and expiry date. Source: https://anytime-capital.com/docs/discount-codes · Markdown: https://anytime-capital.com/docs/discount-codes.md · All docs: https://anytime-capital.com/docs/llms-full.txt Codes customers enter at checkout on payment links that allow them (`allowDiscountCodes`): a percentage or a fixed amount off the subtotal, with an optional limit on uses and an expiry. Codes are stored upper case and are unique per business. Add `?code=FALL15` to a link's `url` to apply one when checkout opens. | Topic | Detail | | --- | --- | | Endpoints | [`POST /api/v1/discount-codes`](#create-discount-code), [`GET /api/v1/discount-codes`](#list-discount-codes), [`GET /api/v1/discount-codes/{id}`](#get-discount-code), [`POST /api/v1/discount-codes/{id}/deactivate`](#deactivate-discount-code) | | Key permissions | `discount_codes:write`, `discount_codes:read` | | Samples | curl; Node.js 18+ with the built-in `fetch` (in an ES module); the [Node SDK](https://anytime-capital.com/docs/node-sdk.md) (preview: not on npm yet); Python with `requests`. | | Every request can also answer | 401 `UNAUTHORIZED`, 401 `API_KEY_EXPIRED`, 403 `NOT_ENROLLED`, 403 `PERMISSION_DENIED`, 403 `IP_NOT_ALLOWED`, 429 `RATE_LIMITED`, 500 `INTERNAL_ERROR`. See [Error codes](https://anytime-capital.com/docs/error-codes.md). | > **On subscriptions:** Whether a code also works on subscription plans, for how many payments, and for new customers only, is set in the dashboard on the **Discounts** page. See [Subscription discounts](https://anytime-capital.com/docs/subscription-discounts.md). ## The discount code object ```json { "object": "discount_code", "livemode": true, "id": "dc_311", "code": "FALL15", "percentOffBps": 1500, "amountOffCents": null, "maxRedemptions": 100, "redemptions": 12, "expiresAt": "2026-11-30T23:59:59.000Z", "status": "active", "active": true, "createdAt": "2026-10-01T08:00:00.000Z", "archivedAt": null } ``` | Field | Type | Description | | --- | --- | --- | | `object` | string | The type of object. Always `discount_code`. | | `livemode` | boolean | False for test-mode data (an `ak_test_` key); true for live data. | | `id` | string | `dc_…` | | `code` | string | What customers type, upper case. | | `percentOffBps` | integer or null | Percent off in basis points (1500 is 15%). | | `amountOffCents` | integer or null | Amount off. Integer cents. | | `maxRedemptions` | integer or null | Most uses. | | `redemptions` | integer | Uses so far. | | `expiresAt` | string (date-time) or null | Expires. | | `status` | string | `active`, `expired` (past `expiresAt`), `used_up` (reached `maxRedemptions`) or `archived` (deactivated). | | `active` | boolean | Accepted at checkout now: not expired, not used up and not deactivated. | | `createdAt` | string (date-time) | Created. | | `archivedAt` | string (date-time) or null | Deactivated. | ## Endpoints ### Create a discount code `POST /api/v1/discount-codes` Permission: `discount_codes:write`. Supports `Idempotency-Key`. A percentage or fixed amount off. Unique per business: 409 CODE_TAKEN otherwise. Codes made here apply to payment links that allow them. To use a code on subscription plans too, turn on **Use on subscriptions** for it in the dashboard (see [Subscription discounts](https://anytime-capital.com/docs/subscription-discounts.md)). **Headers** | Header | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | Recommended. A unique key such as a UUID: a retry with the same key within 24 hours gets the first response back instead of acting twice. See [Idempotency](https://anytime-capital.com/docs/idempotency.md). | **Body** | Name | Type | Required | Description | | --- | --- | --- | --- | | `code` | string | yes | 3–40 letters, numbers, dashes or underscores; stored upper case. | | `percentOffBps` | integer or null | no | Percent off in basis points: 1500 is 15%. From 1 to 10,000. | | `amountOffCents` | integer or null | no | Amount off, in cents. From 1 ($0.01) to 1,000,000 ($10,000.00). | | `maxRedemptions` | integer or null | no | Most uses. From 1 to 1,000,000. | | `expiresAt` | string (date-time) or null | no | When it stops working: in the future, within five years. | **Request (curl)** ```bash curl -X POST https://anytime-capital.com/api/v1/discount-codes \ -H "Authorization: Bearer $ANYTIME_API_KEY" \ -H "Idempotency-Key: discount-fall15" \ -H "Content-Type: application/json" \ -d '{ "code": "FALL15", "percentOffBps": 1500, "maxRedemptions": 100 }' ``` **Request (Node.js)** ```javascript const res = await fetch("https://anytime-capital.com/api/v1/discount-codes", { method: "POST", headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}`, "Content-Type": "application/json", "Idempotency-Key": "discount-fall15", }, body: JSON.stringify({ code: "FALL15", percentOffBps: 1500, maxRedemptions: 100 }), }); const discountCode = await res.json(); if (!res.ok) throw new Error(`${discountCode.error.code}: ${discountCode.error.message}`); ``` **Request (Node SDK)** ```typescript import { AnytimeCapital } from "@anytimecapital/node"; const client = new AnytimeCapital({ apiKey: process.env.ANYTIME_API_KEY! }); const discountCode = await client.discountCodes.create( { code: "FALL15", percentOffBps: 1500, maxRedemptions: 100 }, { idempotencyKey: "discount-fall15" }, ); ``` **Request (Python)** ```python import os import requests res = requests.post( "https://anytime-capital.com/api/v1/discount-codes", headers={ "Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}", "Idempotency-Key": "discount-fall15", }, json={"code": "FALL15", "percentOffBps": 1500, "maxRedemptions": 100}, timeout=30, ) discountCode = res.json() if not res.ok: raise RuntimeError(f"{discountCode['error']['code']}: {discountCode['error']['message']}") ``` **Response** (201) ```json { "object": "discount_code", "livemode": true, "id": "dc_311", "code": "FALL15", "percentOffBps": 1500, "amountOffCents": null, "maxRedemptions": 100, "redemptions": 0, "expiresAt": null, "status": "active", "active": true, "createdAt": "2026-10-01T08:00:00.000Z", "archivedAt": null } ``` **Errors** | Status | Code | When | | --- | --- | --- | | 400 | `INVALID_JSON` | The body isn't valid JSON, or couldn't be read. An empty body reads as `{}`. Send `Content-Type: application/json` and a JSON object. | | 400 | `INVALID_INPUT` | A field, filter, `limit` or `cursor` is missing or invalid. The message starts with the field's name, as in `amountCents: …`, or reads `Invalid cursor.` Fix the named field. Don't retry the same request unchanged. | | 400 | `INVALID_IDEMPOTENCY_KEY` | The `Idempotency-Key` header is empty, longer than 255 characters, or has characters other than printable ASCII. Use a UUID. See [Idempotency](https://anytime-capital.com/docs/idempotency.md). | | 409 | `CODE_TAKEN` | The business already has a discount code with this code. Pick another code, or use the existing one. | | 422 | `IDEMPOTENCY_KEY_REUSED` | The `Idempotency-Key` was used in the last 24 hours with a different body or endpoint. Use a new key for a new request; reuse a key only to retry the same request. | ### List discount codes `GET /api/v1/discount-codes` Permission: `discount_codes:read`. Discount codes, newest first. **Query** | Name | Type | Required | Description | | --- | --- | --- | --- | | `active` | boolean | no | `true` for codes not deactivated (they may still have expired or run out: see `status`), `false` for deactivated ones. | | `limit` | integer | no | How many objects per page, 1–100. Default 25. See [Pagination](https://anytime-capital.com/docs/pagination.md). | | `cursor` | string | no | The previous page's `nextCursor`, for the page after it. | **Request (curl)** ```bash curl "https://anytime-capital.com/api/v1/discount-codes?active=true" \ -H "Authorization: Bearer $ANYTIME_API_KEY" ``` **Request (Node.js)** ```javascript const res = await fetch("https://anytime-capital.com/api/v1/discount-codes?active=true", { headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}` }, }); const page = await res.json(); if (!res.ok) throw new Error(`${page.error.code}: ${page.error.message}`); ``` **Request (Node SDK)** ```typescript import { AnytimeCapital } from "@anytimecapital/node"; const client = new AnytimeCapital({ apiKey: process.env.ANYTIME_API_KEY! }); const page = await client.discountCodes.list({ active: true }); ``` **Request (Python)** ```python import os import requests res = requests.get( "https://anytime-capital.com/api/v1/discount-codes", headers={"Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}"}, params={"active": "true"}, timeout=30, ) page = res.json() if not res.ok: raise RuntimeError(f"{page['error']['code']}: {page['error']['message']}") ``` **Response** (200) ```json { "object": "list", "data": [ { "object": "discount_code", "livemode": true, "id": "dc_311", "code": "FALL15", "percentOffBps": 1500, "amountOffCents": null, "maxRedemptions": 100, "redemptions": 12, "expiresAt": "2026-11-30T23:59:59.000Z", "status": "active", "active": true, "createdAt": "2026-10-01T08:00:00.000Z", "archivedAt": null } ], "hasMore": false, "nextCursor": null } ``` **Errors** | Status | Code | When | | --- | --- | --- | | 400 | `INVALID_INPUT` | A field, filter, `limit` or `cursor` is missing or invalid. The message starts with the field's name, as in `amountCents: …`, or reads `Invalid cursor.` Fix the named field. Don't retry the same request unchanged. | ### Get a discount code `GET /api/v1/discount-codes/{id}` Permission: `discount_codes:read`. One discount code. **Path** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | `dc_…` | **Request (curl)** ```bash curl https://anytime-capital.com/api/v1/discount-codes/dc_311 \ -H "Authorization: Bearer $ANYTIME_API_KEY" ``` **Request (Node.js)** ```javascript const res = await fetch("https://anytime-capital.com/api/v1/discount-codes/dc_311", { headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}` }, }); const discountCode = await res.json(); if (!res.ok) throw new Error(`${discountCode.error.code}: ${discountCode.error.message}`); ``` **Request (Node SDK)** ```typescript import { AnytimeCapital } from "@anytimecapital/node"; const client = new AnytimeCapital({ apiKey: process.env.ANYTIME_API_KEY! }); const discountCode = await client.discountCodes.retrieve("dc_311"); ``` **Request (Python)** ```python import os import requests res = requests.get( "https://anytime-capital.com/api/v1/discount-codes/dc_311", headers={"Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}"}, timeout=30, ) discountCode = res.json() if not res.ok: raise RuntimeError(f"{discountCode['error']['code']}: {discountCode['error']['message']}") ``` **Response** (200) ```json { "object": "discount_code", "livemode": true, "id": "dc_311", "code": "FALL15", "percentOffBps": 1500, "amountOffCents": null, "maxRedemptions": 100, "redemptions": 12, "expiresAt": "2026-11-30T23:59:59.000Z", "status": "active", "active": true, "createdAt": "2026-10-01T08:00:00.000Z", "archivedAt": null } ``` **Errors** | Status | Code | When | | --- | --- | --- | | 404 | `NOT_FOUND` | No such object on your business (an id from another business reads the same), or no such endpoint under `/api/v1`, which still answers in JSON. Check the id and the path. Ids are case-sensitive. | ### Deactivate a discount code `POST /api/v1/discount-codes/{id}/deactivate` Permission: `discount_codes:write`. Supports `Idempotency-Key`. Stop it being accepted at checkout. Safe to repeat. Payments that used it keep it; bring it back from **Discounts** in the dashboard. **Path** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | `dc_…` | **Headers** | Header | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | Recommended. A unique key such as a UUID: a retry with the same key within 24 hours gets the first response back instead of acting twice. See [Idempotency](https://anytime-capital.com/docs/idempotency.md). | **Request (curl)** ```bash curl -X POST https://anytime-capital.com/api/v1/discount-codes/dc_311/deactivate \ -H "Authorization: Bearer $ANYTIME_API_KEY" \ -H "Idempotency-Key: discount-fall15-off" ``` **Request (Node.js)** ```javascript const res = await fetch("https://anytime-capital.com/api/v1/discount-codes/dc_311/deactivate", { method: "POST", headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}`, "Idempotency-Key": "discount-fall15-off", }, }); const discountCode = await res.json(); if (!res.ok) throw new Error(`${discountCode.error.code}: ${discountCode.error.message}`); ``` **Request (Node SDK)** ```typescript import { AnytimeCapital } from "@anytimecapital/node"; const client = new AnytimeCapital({ apiKey: process.env.ANYTIME_API_KEY! }); const discountCode = await client.discountCodes.deactivate( "dc_311", { idempotencyKey: "discount-fall15-off" }, ); ``` **Request (Python)** ```python import os import requests res = requests.post( "https://anytime-capital.com/api/v1/discount-codes/dc_311/deactivate", headers={ "Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}", "Idempotency-Key": "discount-fall15-off", }, timeout=30, ) discountCode = res.json() if not res.ok: raise RuntimeError(f"{discountCode['error']['code']}: {discountCode['error']['message']}") ``` **Response** (200) ```json { "object": "discount_code", "livemode": true, "id": "dc_311", "code": "FALL15", "percentOffBps": 1500, "amountOffCents": null, "maxRedemptions": 100, "redemptions": 12, "expiresAt": "2026-11-30T23:59:59.000Z", "status": "archived", "active": false, "createdAt": "2026-10-01T08:00:00.000Z", "archivedAt": "2026-10-24T12:00:00.000Z" } ``` **Errors** | Status | Code | When | | --- | --- | --- | | 400 | `INVALID_IDEMPOTENCY_KEY` | The `Idempotency-Key` header is empty, longer than 255 characters, or has characters other than printable ASCII. Use a UUID. See [Idempotency](https://anytime-capital.com/docs/idempotency.md). | | 404 | `NOT_FOUND` | No such object on your business (an id from another business reads the same), or no such endpoint under `/api/v1`, which still answers in JSON. Check the id and the path. Ids are case-sensitive. | | 422 | `IDEMPOTENCY_KEY_REUSED` | The `Idempotency-Key` was used in the last 24 hours with a different body or endpoint. Use a new key for a new request; reuse a key only to retry the same request. | --- # Meters and usage events API > Send usage events to Anytime Capital meters one at a time or in batches of 100, list your meters and read a customer's usage, for usage-based billing. Source: https://anytime-capital.com/docs/meters · Markdown: https://anytime-capital.com/docs/meters.md · All docs: https://anytime-capital.com/docs/llms-full.txt Meters count what customers use (API calls, seats, gigabytes) and bill it each period. Make meters on the **Meters** tab of **Subscriptions** in the dashboard; this API sends usage to them and reads it back. How usage turns into invoices is on [Usage billing](https://anytime-capital.com/docs/usage-billing.md); a complete sender is in the [usage recipe](https://anytime-capital.com/docs/bill-usage-with-meters.md). Reading needs `subscriptions:read`, sending `subscriptions:write`. | Topic | Detail | | --- | --- | | Endpoints | [`POST /api/v1/meter-events`](#create-meter-event), [`POST /api/v1/meter-events/batch`](#create-meter-event-batch), [`GET /api/v1/meters`](#list-meters), [`GET /api/v1/meters/{id}/usage`](#get-meter-usage) | | Key permissions | `subscriptions:write`, `subscriptions:read` | | Samples | curl; Node.js 18+ with the built-in `fetch` (in an ES module); the [Node SDK](https://anytime-capital.com/docs/node-sdk.md) (preview: not on npm yet); Python with `requests`. | | Every request can also answer | 401 `UNAUTHORIZED`, 401 `API_KEY_EXPIRED`, 403 `NOT_ENROLLED`, 403 `PERMISSION_DENIED`, 403 `IP_NOT_ALLOWED`, 429 `RATE_LIMITED`, 500 `INTERNAL_ERROR`. See [Error codes](https://anytime-capital.com/docs/error-codes.md). | > **Idempotency is per event:** Usage events don't use the `Idempotency-Key` header. Each event carries its own `idempotencyKey` in the body (your request id, say), remembered for good: sending the same event again answers `200` with the event already recorded, so retries never count twice. ## The meter object ```json { "object": "meter", "livemode": true, "id": "mtr_8fKq2LmNpR4sT6vW9xYz", "name": "API calls", "eventName": "api_call", "aggregation": "count", "unitLabel": "API call", "unitPriceCents": "0.25", "includedUnits": "1000", "status": "active", "createdAt": "2026-10-01T15:04:05.000Z" } ``` | Field | Type | Description | | --- | --- | --- | | `object` | string | The type of object. Always `meter`. | | `livemode` | boolean | False for test-mode data (an `ak_test_` key); true for live data. | | `id` | string | `mtr_` followed by 20 letters and digits. | | `name` | string | Shown on invoices. | | `eventName` | string | What your events name to reach this meter. Unique per business. | | `aggregation` | string | How a period's events add up: `sum` adds values, `count` counts events, `max` takes the highest value, `last` the latest. | | `unitLabel` | string | The unit, e.g. "API call". | | `unitPriceCents` | string | Price per unit in cents, as a decimal string (fractions of a cent allowed). | | `includedUnits` | string | Units each period before billing starts, as a decimal string. | | `status` | string | `active`, or `archived` (it stops taking events and billing). | | `createdAt` | string (date-time) | Created. | ## The meter event object ```json { "object": "meter_event", "livemode": true, "id": "mev_48213", "meter": "mtr_8fKq2LmNpR4sT6vW9xYz", "eventName": "api_call", "customer": "cus_1042", "customerEmail": "jordan@example.com", "value": "1", "timestamp": "2026-10-24T09:30:00.000Z", "idempotencyKey": "req_7f3a9c", "created": "2026-10-24T09:30:01.214Z" } ``` | Field | Type | Description | | --- | --- | --- | | `object` | string | The type of object. Always `meter_event`. | | `livemode` | boolean | False for test-mode data (an `ak_test_` key); true for live data. | | `id` | string | `mev_…` | | `meter` | string | `mtr_…` | | `eventName` | string | The meter's event name. | | `customer` | string or null | `cus_…` when the customer is saved. | | `customerEmail` | string | Lower-cased. | | `value` | string | The usage, as a decimal string. | | `timestamp` | string (date-time) | When the usage happened. | | `idempotencyKey` | string | Your key for this event. | | `created` | string (date-time) | Recorded. | ## The usage object ```json { "object": "meter_usage", "livemode": true, "meter": "mtr_8fKq2LmNpR4sT6vW9xYz", "customer": "cus_1042", "customerEmail": "jordan@example.com", "from": "2026-10-01T00:00:00.000Z", "to": "2026-10-03T00:00:00.000Z", "aggregation": "count", "quantity": "1210", "eventCount": 1210, "daily": [ { "date": "2026-10-01", "quantity": "480" }, { "date": "2026-10-02", "quantity": "730" } ] } ``` | Field | Type | Description | | --- | --- | --- | | `object` | string | The type of object. Always `meter_usage`. | | `livemode` | boolean | False for test-mode data (an `ak_test_` key); true for live data. | | `meter` | string | `mtr_…` | | `customer` | string or null | `cus_…` when the customer is saved. | | `customerEmail` | string | Lower-cased. | | `from` | string (date-time) | Window start. | | `to` | string (date-time) | Window end. | | `aggregation` | string | One of `sum`, `count`, `max`, `last`. | | `quantity` | string | Usage over the window, as a decimal string. | | `eventCount` | integer | Events in the window. | | `daily` | object[] | | | `daily[].date` | string | The UTC day, YYYY-MM-DD. | | `daily[].quantity` | string | Usage that day, as a decimal string. | ## Endpoints ### Record usage `POST /api/v1/meter-events` Permission: `subscriptions:write`. Idempotent by each event's `idempotencyKey`. Record one usage event against a meter, by the meter's event name. Each event carries its own idempotencyKey in the body (the Idempotency-Key header isn't used here): repeating it with the same event answers 200 with Idempotent-Replayed: true; with a different event, 409 IDEMPOTENCY_KEY_REUSED. 422 for a timestamp more than 5 minutes ahead, older than 400 days, or in a period already billed. **Body** | Name | Type | Required | Description | | --- | --- | --- | --- | | `eventName` | string | yes | The meter's event name. | | `customerId` | string | no | `cus_…`. Send this or `customerEmail`. | | `customerEmail` | string | no | The customer's email. Send this or `customerId`. | | `value` | number or string | no | The usage, at least 0 with up to 6 decimals. Defaults to 1. | | `timestamp` | string (date-time) | no | ISO 8601 with an offset. Defaults to now; at most 5 minutes ahead. | | `idempotencyKey` | string | yes | Required. Your unique key for this event, 1–255 printable characters, such as your own request id. Sending it again with the same event answers `200` with the event already recorded; nothing is counted twice. | **Request (curl)** ```bash curl -X POST https://anytime-capital.com/api/v1/meter-events \ -H "Authorization: Bearer $ANYTIME_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "eventName": "api_call", "customerId": "cus_1042", "value": 1, "idempotencyKey": "req_7f3a9c" }' ``` **Request (Node.js)** ```javascript const res = await fetch("https://anytime-capital.com/api/v1/meter-events", { method: "POST", headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ eventName: "api_call", customerId: "cus_1042", value: 1, idempotencyKey: "req_7f3a9c", }), }); const event = await res.json(); if (!res.ok) throw new Error(`${event.error.code}: ${event.error.message}`); ``` **Request (Python)** ```python import os import requests res = requests.post( "https://anytime-capital.com/api/v1/meter-events", headers={"Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}"}, json={ "eventName": "api_call", "customerId": "cus_1042", "value": 1, "idempotencyKey": "req_7f3a9c", }, timeout=30, ) event = res.json() if not res.ok: raise RuntimeError(f"{event['error']['code']}: {event['error']['message']}") ``` **Response** (201) ```json { "object": "meter_event", "livemode": true, "id": "mev_48213", "meter": "mtr_8fKq2LmNpR4sT6vW9xYz", "eventName": "api_call", "customer": "cus_1042", "customerEmail": "jordan@example.com", "value": "1", "timestamp": "2026-10-24T09:30:00.000Z", "idempotencyKey": "req_7f3a9c", "created": "2026-10-24T09:30:01.214Z" } ``` **Errors** | Status | Code | When | | --- | --- | --- | | 400 | `INVALID_JSON` | The body isn't valid JSON, or couldn't be read. An empty body reads as `{}`. Send `Content-Type: application/json` and a JSON object. | | 400 | `INVALID_INPUT` | A field, filter, `limit` or `cursor` is missing or invalid. The message starts with the field's name, as in `amountCents: …`, or reads `Invalid cursor.` Fix the named field. Don't retry the same request unchanged. | | 400 | `CUSTOMER_MISMATCH` | `customerId` and `customerEmail` name different customers. Send one of them. | | 404 | `METER_NOT_FOUND` | No active meter has this `eventName`. Check the meter's event name on the **Meters** tab of **Subscriptions**, or with `GET /api/v1/meters`. | | 404 | `CUSTOMER_NOT_FOUND` | No saved customer has this `customerId`. Send the customer's `cus_…` id, or `customerEmail` instead. | | 409 | `IDEMPOTENCY_KEY_REUSED` | The usage event's `idempotencyKey` was already used for a different event. Send a new `idempotencyKey` for each event; repeat a key only to resend the same event. | | 422 | `CUSTOMER_HAS_NO_EMAIL` | The customer has no email address, and usage is matched to subscriptions by email. Add an email to the customer in the dashboard, or send `customerEmail`. | | 422 | `TIMESTAMP_IN_FUTURE` | The event's `timestamp` is more than five minutes ahead of our clock. Send the time the usage happened, or leave `timestamp` out for now. | | 422 | `TIMESTAMP_TOO_OLD` | The event's `timestamp` is more than 400 days ago. Usage that old can't be billed; drop the event. | | 422 | `PERIOD_CLOSED` | The `timestamp` falls in a period already billed for this customer and meter. Send usage from the end of the billed period on. The message says when that is. | ### Record usage in a batch `POST /api/v1/meter-events/batch` Permission: `subscriptions:write`. Idempotent by each event's `idempotencyKey`. Up to 100 usage events at once, each with its own idempotencyKey. Each is answered by its index: created, duplicate or refused with its own error. The HTTP status is `200` whenever the batch itself is valid: read each event's `status`. A refused event carries the code a single event would get (`METER_NOT_FOUND`, `PERIOD_CLOSED`, `IDEMPOTENCY_KEY_REUSED`…). **Body** | Name | Type | Required | Description | | --- | --- | --- | --- | | `events` | object[] | yes | 1–100 items. | | `events[].eventName` | string | yes | The meter's event name. | | `events[].customerId` | string | no | `cus_…`. Send this or `customerEmail`. | | `events[].customerEmail` | string | no | The customer's email. Send this or `customerId`. | | `events[].value` | number or string | no | The usage, at least 0 with up to 6 decimals. Defaults to 1. | | `events[].timestamp` | string (date-time) | no | ISO 8601 with an offset. Defaults to now; at most 5 minutes ahead. | | `events[].idempotencyKey` | string | yes | Your unique key for this event, 1–255 printable characters. | **Request (curl)** ```bash curl -X POST https://anytime-capital.com/api/v1/meter-events/batch \ -H "Authorization: Bearer $ANYTIME_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "events": [ { "eventName": "api_call", "customerId": "cus_1042", "idempotencyKey": "req_7f3a9d" }, { "eventName": "api_call", "customerEmail": "jordan@example.com", "value": 3, "idempotencyKey": "req_7f3a9e" } ] }' ``` **Request (Node.js)** ```javascript const res = await fetch("https://anytime-capital.com/api/v1/meter-events/batch", { method: "POST", headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ events: [ { eventName: "api_call", customerId: "cus_1042", idempotencyKey: "req_7f3a9d", }, { eventName: "api_call", customerEmail: "jordan@example.com", value: 3, idempotencyKey: "req_7f3a9e", }, ], }), }); const result = await res.json(); if (!res.ok) throw new Error(`${result.error.code}: ${result.error.message}`); ``` **Request (Python)** ```python import os import requests res = requests.post( "https://anytime-capital.com/api/v1/meter-events/batch", headers={"Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}"}, json={ "events": [ { "eventName": "api_call", "customerId": "cus_1042", "idempotencyKey": "req_7f3a9d", }, { "eventName": "api_call", "customerEmail": "jordan@example.com", "value": 3, "idempotencyKey": "req_7f3a9e", }, ], }, timeout=30, ) result = res.json() if not res.ok: raise RuntimeError(f"{result['error']['code']}: {result['error']['message']}") ``` **Response** (200) ```json { "object": "list", "data": [ { "index": 0, "status": "created", "event": { "object": "meter_event", "livemode": true, "id": "mev_48214", "meter": "mtr_8fKq2LmNpR4sT6vW9xYz", "eventName": "api_call", "customer": "cus_1042", "customerEmail": "jordan@example.com", "value": "1", "timestamp": "2026-10-24T09:30:00.000Z", "idempotencyKey": "req_7f3a9d", "created": "2026-10-24T09:30:01.214Z" } }, { "index": 1, "status": "refused", "error": { "code": "PERIOD_CLOSED", "message": "This customer's usage on API calls is already billed up to 2026-10-24T00:00:00.000Z. Send events from then on." } } ], "created": 1, "duplicates": 0, "refused": 1 } ``` **Errors** | Status | Code | When | | --- | --- | --- | | 400 | `INVALID_JSON` | The body isn't valid JSON, or couldn't be read. An empty body reads as `{}`. Send `Content-Type: application/json` and a JSON object. | | 400 | `INVALID_INPUT` | A field, filter, `limit` or `cursor` is missing or invalid. The message starts with the field's name, as in `amountCents: …`, or reads `Invalid cursor.` Fix the named field. Don't retry the same request unchanged. | ### List meters `GET /api/v1/meters` Permission: `subscriptions:read`. The business's usage meters, newest first. Meters are made in the dashboard, on the **Meters** tab of **Subscriptions**. **Query** | Name | Type | Required | Description | | --- | --- | --- | --- | | `limit` | integer | no | How many objects per page, 1–100. Default 25. See [Pagination](https://anytime-capital.com/docs/pagination.md). | | `cursor` | string | no | The previous page's `nextCursor`, for the page after it. | **Request (curl)** ```bash curl https://anytime-capital.com/api/v1/meters \ -H "Authorization: Bearer $ANYTIME_API_KEY" ``` **Request (Node.js)** ```javascript const res = await fetch("https://anytime-capital.com/api/v1/meters", { headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}` }, }); const page = await res.json(); if (!res.ok) throw new Error(`${page.error.code}: ${page.error.message}`); ``` **Request (Python)** ```python import os import requests res = requests.get( "https://anytime-capital.com/api/v1/meters", headers={"Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}"}, timeout=30, ) page = res.json() if not res.ok: raise RuntimeError(f"{page['error']['code']}: {page['error']['message']}") ``` **Response** (200) ```json { "object": "list", "data": [ { "object": "meter", "livemode": true, "id": "mtr_8fKq2LmNpR4sT6vW9xYz", "name": "API calls", "eventName": "api_call", "aggregation": "count", "unitLabel": "API call", "unitPriceCents": "0.25", "includedUnits": "1000", "status": "active", "createdAt": "2026-10-01T15:04:05.000Z" } ], "hasMore": false, "nextCursor": null } ``` **Errors** | Status | Code | When | | --- | --- | --- | | 400 | `INVALID_INPUT` | A field, filter, `limit` or `cursor` is missing or invalid. The message starts with the field's name, as in `amountCents: …`, or reads `Invalid cursor.` Fix the named field. Don't retry the same request unchanged. | ### Get a customer's usage `GET /api/v1/meters/{id}/usage` Permission: `subscriptions:read`. One customer's usage on a meter over a window (default the last 30 days, at most 366), with a daily breakdown. 422 CUSTOMER_HAS_NO_EMAIL when the customer has no email to match events by. Usage in [`from`, `to`) is added up the meter's way (`sum`, `count`, `max` or `last`), with one point per UTC day. **Path** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | `mtr_…` | **Query** | Name | Type | Required | Description | | --- | --- | --- | --- | | `customerId` | string | no | `cus_…`. Send this or `customerEmail`. | | `customerEmail` | string | no | The customer's email. | | `from` | string | no | Window start: a date (`2026-10-01`, midnight UTC) or an ISO 8601 date-time. Default: 30 days before `to`. | | `to` | string | no | Window end (exclusive), same format. Default: now. The window spans at most 366 days. | **Request (curl)** ```bash curl "https://anytime-capital.com/api/v1/meters/mtr_8fKq2LmNpR4sT6vW9xYz/usage?customerId=cus_1042&from=2026-10-01&to=2026-10-03" \ -H "Authorization: Bearer $ANYTIME_API_KEY" ``` **Request (Node.js)** ```javascript const res = await fetch("https://anytime-capital.com/api/v1/meters/mtr_8fKq2LmNpR4sT6vW9xYz/usage?customerId=cus_1042&from=2026-10-01&to=2026-10-03", { headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}` }, }); const usage = await res.json(); if (!res.ok) throw new Error(`${usage.error.code}: ${usage.error.message}`); ``` **Request (Python)** ```python import os import requests res = requests.get( "https://anytime-capital.com/api/v1/meters/mtr_8fKq2LmNpR4sT6vW9xYz/usage", headers={"Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}"}, params={"customerId": "cus_1042", "from": "2026-10-01", "to": "2026-10-03"}, timeout=30, ) usage = res.json() if not res.ok: raise RuntimeError(f"{usage['error']['code']}: {usage['error']['message']}") ``` **Response** (200) ```json { "object": "meter_usage", "livemode": true, "meter": "mtr_8fKq2LmNpR4sT6vW9xYz", "customer": "cus_1042", "customerEmail": "jordan@example.com", "from": "2026-10-01T00:00:00.000Z", "to": "2026-10-03T00:00:00.000Z", "aggregation": "count", "quantity": "1210", "eventCount": 1210, "daily": [ { "date": "2026-10-01", "quantity": "480" }, { "date": "2026-10-02", "quantity": "730" } ] } ``` **Errors** | Status | Code | When | | --- | --- | --- | | 400 | `INVALID_INPUT` | A field, filter, `limit` or `cursor` is missing or invalid. The message starts with the field's name, as in `amountCents: …`, or reads `Invalid cursor.` Fix the named field. Don't retry the same request unchanged. | | 400 | `CUSTOMER_MISMATCH` | `customerId` and `customerEmail` name different customers. Send one of them. | | 404 | `NOT_FOUND` | No such object on your business (an id from another business reads the same), or no such endpoint under `/api/v1`, which still answers in JSON. Check the id and the path. Ids are case-sensitive. | | 422 | `CUSTOMER_HAS_NO_EMAIL` | The customer has no email address, and usage is matched to subscriptions by email. Add an email to the customer in the dashboard, or send `customerEmail`. | --- # Payouts and balance API > Read your payouts and your payments balance with the Anytime Capital Payments API: status, amount, bank trace number and expected arrival, read only. Source: https://anytime-capital.com/docs/payouts-api · Markdown: https://anytime-capital.com/docs/payouts-api.md · All docs: https://anytime-capital.com/docs/llms-full.txt Your payouts to your bank, and the balance behind them, for reconciling deposits and forecasting cash. These endpoints only read: payouts are made from **Payouts** in the dashboard by owners and admins, and no API key or AI assistant can move money out of your balance. The list's `nextCursor` is opaque: pass it back exactly as it came. For every payout option and speed, see [Payout options](https://anytime-capital.com/docs/payouts.md). | Topic | Detail | | --- | --- | | Endpoints | [`GET /api/v1/payouts`](#list-payouts), [`GET /api/v1/payouts/{id}`](#get-payout), [`GET /api/v1/balance`](#get-balance) | | Key permissions | `payouts:read` | | Samples | curl; Node.js 18+ with the built-in `fetch` (in an ES module); the [Node SDK](https://anytime-capital.com/docs/node-sdk.md) (preview: not on npm yet); Python with `requests`. | | Every request can also answer | 401 `UNAUTHORIZED`, 401 `API_KEY_EXPIRED`, 403 `NOT_ENROLLED`, 403 `PERMISSION_DENIED`, 403 `IP_NOT_ALLOWED`, 429 `RATE_LIMITED`, 500 `INTERNAL_ERROR`. See [Error codes](https://anytime-capital.com/docs/error-codes.md). | ## The payout object ```json { "object": "payout", "livemode": true, "id": "wdrl_5Tz8QwN2xYb6RcD1", "status": "completed", "amountCents": 482500, "netCents": 482500, "currency": "usd", "method": "Chase checking", "speed": "standard", "automatic": true, "estimatedArrival": "2026-10-22T16:00:00.000Z", "traceCode": "021000021234567", "failureMessage": null, "createdAt": "2026-10-20T16:00:00.000Z" } ``` | Field | Type | Description | | --- | --- | --- | | `object` | string | The type of object. Always `payout`. | | `livemode` | boolean | False for test-mode data (an `ak_test_` key); true for live data. | | `id` | string | `wdrl_…` | | `status` | string | `requested`, `in_review`, `processing`, `completed`, `reversed`, `canceled`, `failed`, `denied`, `unknown`. `completed` means it was sent; `reversed` that it came back. | | `amountCents` | integer or null | Taken from the balance. Integer cents. | | `netCents` | integer or null | What reaches the bank. Integer cents. | | `currency` | string | usd. | | `method` | string or null | Where it went, e.g. “Chase checking”. | | `speed` | string or null | One of `standard`, `instant`. | | `automatic` | boolean or null | Sent on the automatic schedule. | | `estimatedArrival` | string (date-time) or null | Expected at the bank. | | `traceCode` | string or null | The trace number your bank can find the deposit by. Match it, with `netCents`, against your bank feed. | | `failureMessage` | string or null | Why it failed or came back. | | `createdAt` | string (date-time) | Created. | ## The balance object ```json { "object": "balance", "livemode": true, "currency": "usd", "availableCents": 128400, "pendingCents": 54200, "reserveCents": 0 } ``` | Field | Type | Description | | --- | --- | --- | | `object` | string | The type of object. Always `balance`. | | `livemode` | boolean | False for test-mode data (an `ak_test_` key); true for live data. | | `currency` | string | usd. | | `availableCents` | integer | Ready to pay out. Integer cents. | | `pendingCents` | integer | Not settled yet. Integer cents. | | `reserveCents` | integer | Held in reserve. Integer cents. | ## Endpoints ### List payouts `GET /api/v1/payouts` Permission: `payouts:read`. Payouts to the business's bank, newest first. The cursor is opaque. **Query** | Name | Type | Required | Description | | --- | --- | --- | --- | | `limit` | integer | no | How many objects per page, 1–100. Default 25. See [Pagination](https://anytime-capital.com/docs/pagination.md). | | `cursor` | string | no | The previous page's `nextCursor`, for the page after it. | **Request (curl)** ```bash curl "https://anytime-capital.com/api/v1/payouts?limit=10" \ -H "Authorization: Bearer $ANYTIME_API_KEY" ``` **Request (Node.js)** ```javascript const res = await fetch("https://anytime-capital.com/api/v1/payouts?limit=10", { headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}` }, }); const page = await res.json(); if (!res.ok) throw new Error(`${page.error.code}: ${page.error.message}`); ``` **Request (Node SDK)** ```typescript import { AnytimeCapital } from "@anytimecapital/node"; const client = new AnytimeCapital({ apiKey: process.env.ANYTIME_API_KEY! }); const page = await client.payouts.list({ limit: 10 }); ``` **Request (Python)** ```python import os import requests res = requests.get( "https://anytime-capital.com/api/v1/payouts", headers={"Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}"}, params={"limit": "10"}, timeout=30, ) page = res.json() if not res.ok: raise RuntimeError(f"{page['error']['code']}: {page['error']['message']}") ``` **Response** (200) ```json { "object": "list", "data": [ { "object": "payout", "livemode": true, "id": "wdrl_5Tz8QwN2xYb6RcD1", "status": "completed", "amountCents": 482500, "netCents": 482500, "currency": "usd", "method": "Chase checking", "speed": "standard", "automatic": true, "estimatedArrival": "2026-10-22T16:00:00.000Z", "traceCode": "021000021234567", "failureMessage": null, "createdAt": "2026-10-20T16:00:00.000Z" } ], "hasMore": false, "nextCursor": null } ``` **Errors** | Status | Code | When | | --- | --- | --- | | 400 | `INVALID_INPUT` | A field, filter, `limit` or `cursor` is missing or invalid. The message starts with the field's name, as in `amountCents: …`, or reads `Invalid cursor.` Fix the named field. Don't retry the same request unchanged. | | 502 | `PROCESSOR_ERROR` | The payments network didn't answer in time. Retry with the same `Idempotency-Key`. A refund is keyed at the network by that key too, so a retry never refunds twice. | | 503 | `NOT_CONFIGURED` | The part of Anytime Capital Payments this needs isn't available right now. Retry later, with the same `Idempotency-Key` on a write. | ### Get a payout `GET /api/v1/payouts/{id}` Permission: `payouts:read`. One payout. **Path** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | `wdrl_…` | **Request (curl)** ```bash curl https://anytime-capital.com/api/v1/payouts/wdrl_5Tz8QwN2xYb6RcD1 \ -H "Authorization: Bearer $ANYTIME_API_KEY" ``` **Request (Node.js)** ```javascript const res = await fetch("https://anytime-capital.com/api/v1/payouts/wdrl_5Tz8QwN2xYb6RcD1", { headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}` }, }); const payout = await res.json(); if (!res.ok) throw new Error(`${payout.error.code}: ${payout.error.message}`); ``` **Request (Node SDK)** ```typescript import { AnytimeCapital } from "@anytimecapital/node"; const client = new AnytimeCapital({ apiKey: process.env.ANYTIME_API_KEY! }); const payout = await client.payouts.retrieve("wdrl_5Tz8QwN2xYb6RcD1"); ``` **Request (Python)** ```python import os import requests res = requests.get( "https://anytime-capital.com/api/v1/payouts/wdrl_5Tz8QwN2xYb6RcD1", headers={"Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}"}, timeout=30, ) payout = res.json() if not res.ok: raise RuntimeError(f"{payout['error']['code']}: {payout['error']['message']}") ``` **Response** (200) ```json { "object": "payout", "livemode": true, "id": "wdrl_5Tz8QwN2xYb6RcD1", "status": "completed", "amountCents": 482500, "netCents": 482500, "currency": "usd", "method": "Chase checking", "speed": "standard", "automatic": true, "estimatedArrival": "2026-10-22T16:00:00.000Z", "traceCode": "021000021234567", "failureMessage": null, "createdAt": "2026-10-20T16:00:00.000Z" } ``` **Errors** | Status | Code | When | | --- | --- | --- | | 404 | `NOT_FOUND` | No such object on your business (an id from another business reads the same), or no such endpoint under `/api/v1`, which still answers in JSON. Check the id and the path. Ids are case-sensitive. | | 502 | `PROCESSOR_ERROR` | The payments network didn't answer in time. Retry with the same `Idempotency-Key`. A refund is keyed at the network by that key too, so a retry never refunds twice. | | 503 | `NOT_CONFIGURED` | The part of Anytime Capital Payments this needs isn't available right now. Retry later, with the same `Idempotency-Key` on a write. | ### Get the balance `GET /api/v1/balance` Permission: `payouts:read`. Available, pending and reserved, in USD cents. All zero before your first payment settles. **Request (curl)** ```bash curl https://anytime-capital.com/api/v1/balance \ -H "Authorization: Bearer $ANYTIME_API_KEY" ``` **Request (Node.js)** ```javascript const res = await fetch("https://anytime-capital.com/api/v1/balance", { headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}` }, }); const balance = await res.json(); if (!res.ok) throw new Error(`${balance.error.code}: ${balance.error.message}`); ``` **Request (Node SDK)** ```typescript import { AnytimeCapital } from "@anytimecapital/node"; const client = new AnytimeCapital({ apiKey: process.env.ANYTIME_API_KEY! }); const balance = await client.balance.retrieve(); ``` **Request (Python)** ```python import os import requests res = requests.get( "https://anytime-capital.com/api/v1/balance", headers={"Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}"}, timeout=30, ) balance = res.json() if not res.ok: raise RuntimeError(f"{balance['error']['code']}: {balance['error']['message']}") ``` **Response** (200) ```json { "object": "balance", "livemode": true, "currency": "usd", "availableCents": 128400, "pendingCents": 54200, "reserveCents": 0 } ``` **Errors** | Status | Code | When | | --- | --- | --- | | 502 | `PROCESSOR_ERROR` | The payments network didn't answer in time. Retry with the same `Idempotency-Key`. A refund is keyed at the network by that key too, so a retry never refunds twice. | | 503 | `NOT_CONFIGURED` | The part of Anytime Capital Payments this needs isn't available right now. Retry later, with the same `Idempotency-Key` on a write. | ## Events `payout.paid` when a payout to your bank is sent and `payout.failed` when one fails, is denied or comes back, each with the payout object. The [reconciliation guide](https://anytime-capital.com/docs/reconcile-payouts.md) puts these together in a nightly job. --- # Webhook endpoints API > Subscribe and unsubscribe webhook URLs from your own code with the Anytime Capital Payments API, REST-hook style, and get each endpoint's signing secret. Source: https://anytime-capital.com/docs/webhook-endpoints · Markdown: https://anytime-capital.com/docs/webhook-endpoints.md · All docs: https://anytime-capital.com/docs/llms-full.txt Where we send your [events](https://anytime-capital.com/docs/webhooks.md), managed from your own code: subscribe with `POST`, unsubscribe with `DELETE`, the REST-hook style automation tools use. The rules are the dashboard's: an `https://` URL on a public domain name (no IP addresses or `localhost`), known event types or `["*"]` for every event including ones added later, and at most 10 endpoints per business. Endpoints made here also show in **Settings → Developers**. | Topic | Detail | | --- | --- | | Endpoints | [`POST /api/v1/webhook-endpoints`](#create-webhook-endpoint), [`GET /api/v1/webhook-endpoints`](#list-webhook-endpoints), [`GET /api/v1/webhook-endpoints/{id}`](#get-webhook-endpoint), [`PATCH /api/v1/webhook-endpoints/{id}`](#update-webhook-endpoint), [`DELETE /api/v1/webhook-endpoints/{id}`](#delete-webhook-endpoint) | | Key permissions | `webhook_endpoints:write`, `webhook_endpoints:read` | | Samples | curl; Node.js 18+ with the built-in `fetch` (in an ES module); the [Node SDK](https://anytime-capital.com/docs/node-sdk.md) (preview: not on npm yet); Python with `requests`. | | Every request can also answer | 401 `UNAUTHORIZED`, 401 `API_KEY_EXPIRED`, 403 `NOT_ENROLLED`, 403 `PERMISSION_DENIED`, 403 `IP_NOT_ALLOWED`, 429 `RATE_LIMITED`, 500 `INTERNAL_ERROR`. See [Error codes](https://anytime-capital.com/docs/error-codes.md). | > **Store the secret when you create it:** The signing secret (`whsec_…`) is in the create response only: read it from `secret`, keep it in a server-side secret store, and use it to [verify every event](https://anytime-capital.com/docs/webhook-signatures.md). A retry of the create with the same Idempotency-Key returns the endpoint again with its secret. ## The webhook endpoint object ```json { "object": "webhook_endpoint", "livemode": true, "id": "we_Rt2Vb7Nc4Zp9WkLm3Qx8", "url": "https://example.com/webhooks/anytime", "events": [ "payment.succeeded", "refund.created" ], "description": "Order fulfilment", "enabled": true, "createdAt": "2026-10-24T10:00:00.000Z" } ``` | Field | Type | Description | | --- | --- | --- | | `object` | string | The type of object. Always `webhook_endpoint`. | | `livemode` | boolean | False for test-mode data (an `ak_test_` key); true for live data. | | `id` | string | `we_` followed by 20 letters and digits. | | `url` | string (uri) | Where events are sent. | | `events` | string[] | Event types, or ["*"] for every event. | | `description` | string or null | Your note. | | `enabled` | boolean | Receiving events. | | `createdAt` | string (date-time) | Created. | ## Endpoints ### Create a webhook endpoint `POST /api/v1/webhook-endpoints` Permission: `webhook_endpoints:write`. Supports `Idempotency-Key`. Subscribe a URL to events (REST hooks subscribe here). The signing secret is in this response only. Store `secret` now: it's in this response only. A retry with the same Idempotency-Key returns the endpoint with its current secret. **Headers** | Header | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | Recommended. A unique key such as a UUID: a retry with the same key within 24 hours gets the first response back instead of acting twice. See [Idempotency](https://anytime-capital.com/docs/idempotency.md). | **Body** | Name | Type | Required | Description | | --- | --- | --- | --- | | `url` | string (uri) | yes | An https:// URL on a public domain name (no IP addresses or localhost). Up to 500 characters. | | `events` | string[] | yes | Event types to receive, or ["*"] for every event including ones added later. 1–26 items. | | `description` | string or null | no | Your note. Up to 200 characters. | **Request (curl)** ```bash curl -X POST https://anytime-capital.com/api/v1/webhook-endpoints \ -H "Authorization: Bearer $ANYTIME_API_KEY" \ -H "Idempotency-Key: endpoint-orders" \ -H "Content-Type: application/json" \ -d '{ "url": "https://example.com/webhooks/anytime", "events": [ "payment.succeeded", "refund.created" ], "description": "Order fulfilment" }' ``` **Request (Node.js)** ```javascript const res = await fetch("https://anytime-capital.com/api/v1/webhook-endpoints", { method: "POST", headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}`, "Content-Type": "application/json", "Idempotency-Key": "endpoint-orders", }, body: JSON.stringify({ url: "https://example.com/webhooks/anytime", events: ["payment.succeeded", "refund.created"], description: "Order fulfilment", }), }); const endpoint = await res.json(); if (!res.ok) throw new Error(`${endpoint.error.code}: ${endpoint.error.message}`); ``` **Request (Node SDK)** ```typescript import { AnytimeCapital } from "@anytimecapital/node"; const client = new AnytimeCapital({ apiKey: process.env.ANYTIME_API_KEY! }); const endpoint = await client.webhookEndpoints.create( { url: "https://example.com/webhooks/anytime", events: ["payment.succeeded", "refund.created"], description: "Order fulfilment", }, { idempotencyKey: "endpoint-orders" }, ); ``` **Request (Python)** ```python import os import requests res = requests.post( "https://anytime-capital.com/api/v1/webhook-endpoints", headers={ "Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}", "Idempotency-Key": "endpoint-orders", }, json={ "url": "https://example.com/webhooks/anytime", "events": ["payment.succeeded", "refund.created"], "description": "Order fulfilment", }, timeout=30, ) endpoint = res.json() if not res.ok: raise RuntimeError(f"{endpoint['error']['code']}: {endpoint['error']['message']}") ``` **Response** (201) ```json { "object": "webhook_endpoint", "livemode": true, "id": "we_Rt2Vb7Nc4Zp9WkLm3Qx8", "url": "https://example.com/webhooks/anytime", "events": [ "payment.succeeded", "refund.created" ], "description": "Order fulfilment", "enabled": true, "createdAt": "2026-10-24T10:00:00.000Z", "secret": "whsec_…" } ``` **Errors** | Status | Code | When | | --- | --- | --- | | 400 | `INVALID_JSON` | The body isn't valid JSON, or couldn't be read. An empty body reads as `{}`. Send `Content-Type: application/json` and a JSON object. | | 400 | `INVALID_INPUT` | A field, filter, `limit` or `cursor` is missing or invalid. The message starts with the field's name, as in `amountCents: …`, or reads `Invalid cursor.` Fix the named field. Don't retry the same request unchanged. | | 400 | `INVALID_IDEMPOTENCY_KEY` | The `Idempotency-Key` header is empty, longer than 255 characters, or has characters other than printable ASCII. Use a UUID. See [Idempotency](https://anytime-capital.com/docs/idempotency.md). | | 409 | `LIMIT_REACHED` | The business already has 10 webhook endpoints. Delete one you no longer use with `DELETE /api/v1/webhook-endpoints/{id}`. | | 422 | `IDEMPOTENCY_KEY_REUSED` | The `Idempotency-Key` was used in the last 24 hours with a different body or endpoint. Use a new key for a new request; reuse a key only to retry the same request. | | 503 | `NOT_CONFIGURED` | The part of Anytime Capital Payments this needs isn't available right now. Retry later, with the same `Idempotency-Key` on a write. | ### List webhook endpoints `GET /api/v1/webhook-endpoints` Permission: `webhook_endpoints:read`. Every endpoint (at most 10), oldest first, in one page. **Request (curl)** ```bash curl https://anytime-capital.com/api/v1/webhook-endpoints \ -H "Authorization: Bearer $ANYTIME_API_KEY" ``` **Request (Node.js)** ```javascript const res = await fetch("https://anytime-capital.com/api/v1/webhook-endpoints", { headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}` }, }); const page = await res.json(); if (!res.ok) throw new Error(`${page.error.code}: ${page.error.message}`); ``` **Request (Node SDK)** ```typescript import { AnytimeCapital } from "@anytimecapital/node"; const client = new AnytimeCapital({ apiKey: process.env.ANYTIME_API_KEY! }); const page = await client.webhookEndpoints.list(); ``` **Request (Python)** ```python import os import requests res = requests.get( "https://anytime-capital.com/api/v1/webhook-endpoints", headers={"Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}"}, timeout=30, ) page = res.json() if not res.ok: raise RuntimeError(f"{page['error']['code']}: {page['error']['message']}") ``` **Response** (200) ```json { "object": "list", "data": [ { "object": "webhook_endpoint", "livemode": true, "id": "we_Rt2Vb7Nc4Zp9WkLm3Qx8", "url": "https://example.com/webhooks/anytime", "events": [ "payment.succeeded", "refund.created" ], "description": "Order fulfilment", "enabled": true, "createdAt": "2026-10-24T10:00:00.000Z" } ], "hasMore": false, "nextCursor": null } ``` ### Get a webhook endpoint `GET /api/v1/webhook-endpoints/{id}` Permission: `webhook_endpoints:read`. One endpoint (never its secret). **Path** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | `we_…` | **Request (curl)** ```bash curl https://anytime-capital.com/api/v1/webhook-endpoints/we_Rt2Vb7Nc4Zp9WkLm3Qx8 \ -H "Authorization: Bearer $ANYTIME_API_KEY" ``` **Request (Node.js)** ```javascript const res = await fetch("https://anytime-capital.com/api/v1/webhook-endpoints/we_Rt2Vb7Nc4Zp9WkLm3Qx8", { headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}` }, }); const endpoint = await res.json(); if (!res.ok) throw new Error(`${endpoint.error.code}: ${endpoint.error.message}`); ``` **Request (Node SDK)** ```typescript import { AnytimeCapital } from "@anytimecapital/node"; const client = new AnytimeCapital({ apiKey: process.env.ANYTIME_API_KEY! }); const endpoint = await client.webhookEndpoints.retrieve("we_Rt2Vb7Nc4Zp9WkLm3Qx8"); ``` **Request (Python)** ```python import os import requests res = requests.get( "https://anytime-capital.com/api/v1/webhook-endpoints/we_Rt2Vb7Nc4Zp9WkLm3Qx8", headers={"Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}"}, timeout=30, ) endpoint = res.json() if not res.ok: raise RuntimeError(f"{endpoint['error']['code']}: {endpoint['error']['message']}") ``` **Response** (200) ```json { "object": "webhook_endpoint", "livemode": true, "id": "we_Rt2Vb7Nc4Zp9WkLm3Qx8", "url": "https://example.com/webhooks/anytime", "events": [ "payment.succeeded", "refund.created" ], "description": "Order fulfilment", "enabled": true, "createdAt": "2026-10-24T10:00:00.000Z" } ``` **Errors** | Status | Code | When | | --- | --- | --- | | 404 | `NOT_FOUND` | No such object on your business (an id from another business reads the same), or no such endpoint under `/api/v1`, which still answers in JSON. Check the id and the path. Ids are case-sensitive. | ### Update a webhook endpoint `PATCH /api/v1/webhook-endpoints/{id}` Permission: `webhook_endpoints:write`. Supports `Idempotency-Key`. Change url, events or description, or switch it off and on. **Path** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | `we_…` | **Headers** | Header | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | Recommended. A unique key such as a UUID: a retry with the same key within 24 hours gets the first response back instead of acting twice. See [Idempotency](https://anytime-capital.com/docs/idempotency.md). | **Body** | Name | Type | Required | Description | | --- | --- | --- | --- | | `url` | string (uri) | no | An https:// URL on a public domain name (no IP addresses or localhost). Up to 500 characters. | | `events` | string[] | no | Event types to receive, or ["*"] for every event including ones added later. 1–26 items. | | `description` | string or null | no | Your note; null clears it. Up to 200 characters. | | `enabled` | boolean | no | false switches it off (events waiting are dropped); true back on (new events only). | **Request (curl)** ```bash curl -X PATCH https://anytime-capital.com/api/v1/webhook-endpoints/we_Rt2Vb7Nc4Zp9WkLm3Qx8 \ -H "Authorization: Bearer $ANYTIME_API_KEY" \ -H "Idempotency-Key: endpoint-orders-all-events" \ -H "Content-Type: application/json" \ -d '{ "events": [ "*" ] }' ``` **Request (Node.js)** ```javascript const res = await fetch("https://anytime-capital.com/api/v1/webhook-endpoints/we_Rt2Vb7Nc4Zp9WkLm3Qx8", { method: "PATCH", headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}`, "Content-Type": "application/json", "Idempotency-Key": "endpoint-orders-all-events", }, body: JSON.stringify({ events: ["*"] }), }); const endpoint = await res.json(); if (!res.ok) throw new Error(`${endpoint.error.code}: ${endpoint.error.message}`); ``` **Request (Node SDK)** ```typescript import { AnytimeCapital } from "@anytimecapital/node"; const client = new AnytimeCapital({ apiKey: process.env.ANYTIME_API_KEY! }); const endpoint = await client.webhookEndpoints.update( "we_Rt2Vb7Nc4Zp9WkLm3Qx8", { events: ["*"] }, { idempotencyKey: "endpoint-orders-all-events" }, ); ``` **Request (Python)** ```python import os import requests res = requests.patch( "https://anytime-capital.com/api/v1/webhook-endpoints/we_Rt2Vb7Nc4Zp9WkLm3Qx8", headers={ "Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}", "Idempotency-Key": "endpoint-orders-all-events", }, json={"events": ["*"]}, timeout=30, ) endpoint = res.json() if not res.ok: raise RuntimeError(f"{endpoint['error']['code']}: {endpoint['error']['message']}") ``` **Response** (200) ```json { "object": "webhook_endpoint", "livemode": true, "id": "we_Rt2Vb7Nc4Zp9WkLm3Qx8", "url": "https://example.com/webhooks/anytime", "events": [ "*" ], "description": "Order fulfilment", "enabled": true, "createdAt": "2026-10-24T10:00:00.000Z" } ``` **Errors** | Status | Code | When | | --- | --- | --- | | 400 | `INVALID_JSON` | The body isn't valid JSON, or couldn't be read. An empty body reads as `{}`. Send `Content-Type: application/json` and a JSON object. | | 400 | `INVALID_INPUT` | A field, filter, `limit` or `cursor` is missing or invalid. The message starts with the field's name, as in `amountCents: …`, or reads `Invalid cursor.` Fix the named field. Don't retry the same request unchanged. | | 400 | `INVALID_IDEMPOTENCY_KEY` | The `Idempotency-Key` header is empty, longer than 255 characters, or has characters other than printable ASCII. Use a UUID. See [Idempotency](https://anytime-capital.com/docs/idempotency.md). | | 404 | `NOT_FOUND` | No such object on your business (an id from another business reads the same), or no such endpoint under `/api/v1`, which still answers in JSON. Check the id and the path. Ids are case-sensitive. | | 422 | `IDEMPOTENCY_KEY_REUSED` | The `Idempotency-Key` was used in the last 24 hours with a different body or endpoint. Use a new key for a new request; reuse a key only to retry the same request. | ### Delete a webhook endpoint `DELETE /api/v1/webhook-endpoints/{id}` Permission: `webhook_endpoints:write`. Supports `Idempotency-Key`. Remove it and its delivery log (REST hooks unsubscribe here). Events stop at once. Without an Idempotency-Key, deleting it again answers `404`. **Path** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | `we_…` | **Headers** | Header | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | Recommended. A unique key such as a UUID: a retry with the same key within 24 hours gets the first response back instead of acting twice. See [Idempotency](https://anytime-capital.com/docs/idempotency.md). | **Request (curl)** ```bash curl -X DELETE https://anytime-capital.com/api/v1/webhook-endpoints/we_Rt2Vb7Nc4Zp9WkLm3Qx8 \ -H "Authorization: Bearer $ANYTIME_API_KEY" \ -H "Idempotency-Key: endpoint-orders-delete" ``` **Request (Node.js)** ```javascript const res = await fetch("https://anytime-capital.com/api/v1/webhook-endpoints/we_Rt2Vb7Nc4Zp9WkLm3Qx8", { method: "DELETE", headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}`, "Idempotency-Key": "endpoint-orders-delete", }, }); const deleted = await res.json(); if (!res.ok) throw new Error(`${deleted.error.code}: ${deleted.error.message}`); ``` **Request (Node SDK)** ```typescript import { AnytimeCapital } from "@anytimecapital/node"; const client = new AnytimeCapital({ apiKey: process.env.ANYTIME_API_KEY! }); const deleted = await client.webhookEndpoints.del( "we_Rt2Vb7Nc4Zp9WkLm3Qx8", { idempotencyKey: "endpoint-orders-delete" }, ); ``` **Request (Python)** ```python import os import requests res = requests.delete( "https://anytime-capital.com/api/v1/webhook-endpoints/we_Rt2Vb7Nc4Zp9WkLm3Qx8", headers={ "Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}", "Idempotency-Key": "endpoint-orders-delete", }, timeout=30, ) deleted = res.json() if not res.ok: raise RuntimeError(f"{deleted['error']['code']}: {deleted['error']['message']}") ``` **Response** (200) ```json { "object": "webhook_endpoint", "id": "we_Rt2Vb7Nc4Zp9WkLm3Qx8", "deleted": true } ``` **Errors** | Status | Code | When | | --- | --- | --- | | 400 | `INVALID_IDEMPOTENCY_KEY` | The `Idempotency-Key` header is empty, longer than 255 characters, or has characters other than printable ASCII. Use a UUID. See [Idempotency](https://anytime-capital.com/docs/idempotency.md). | | 404 | `NOT_FOUND` | No such object on your business (an id from another business reads the same), or no such endpoint under `/api/v1`, which still answers in JSON. Check the id and the path. Ids are case-sensitive. | | 422 | `IDEMPOTENCY_KEY_REUSED` | The `Idempotency-Key` was used in the last 24 hours with a different body or endpoint. Use a new key for a new request; reuse a key only to retry the same request. | --- # Events API > Read every event Anytime Capital Payments emitted for your business in the last 30 days, to reconcile or catch up after missed webhook deliveries. Source: https://anytime-capital.com/docs/events · Markdown: https://anytime-capital.com/docs/events.md · All docs: https://anytime-capital.com/docs/llms-full.txt Every [webhook event](https://anytime-capital.com/docs/webhooks.md) we emit for your business, kept for 30 days whether or not an endpoint took it. Use it to reconcile, or to catch up after your endpoint was down: list what happened since the last event you handled and process what you haven't seen. Events are recorded once your business has a webhook endpoint or an API key, and need a key with the `events` permission (read only). Test events from **Send test event** never appear here. | Topic | Detail | | --- | --- | | Endpoints | [`GET /api/v1/events`](#list-events), [`GET /api/v1/events/{id}`](#get-event) | | Key permissions | `events:read` | | Samples | curl; Node.js 18+ with the built-in `fetch` (in an ES module); the [Node SDK](https://anytime-capital.com/docs/node-sdk.md) (preview: not on npm yet); Python with `requests`. | | Every request can also answer | 401 `UNAUTHORIZED`, 401 `API_KEY_EXPIRED`, 403 `NOT_ENROLLED`, 403 `PERMISSION_DENIED`, 403 `IP_NOT_ALLOWED`, 429 `RATE_LIMITED`, 500 `INTERNAL_ERROR`. See [Error codes](https://anytime-capital.com/docs/error-codes.md). | ## The event object The same envelope a webhook delivery carries, plus `object` and `pendingWebhooks`: how many of your endpoints are still retrying it. ```json { "object": "event", "id": "evt_3f9a1c2b7d4e6f8a0b1c2d3e4f5a6b7c", "type": "payment.succeeded", "created": "2026-10-14T16:02:16.000Z", "livemode": true, "data": { "object": "payment", "livemode": true, "id": "pay_7Hq2LmX9cR4tVb", "status": "paid", "amountCents": 12500, "amountRefundedCents": 0, "netCents": 12088, "currency": "usd", "description": "Consulting — October", "source": { "type": "payment_link", "id": "pl_4kTz8QwN2xYb6RcD1mPa" }, "paymentLinkId": "pl_4kTz8QwN2xYb6RcD1mPa", "customer": { "name": "Jordan Lee", "email": "jordan@example.com", "country": "US" }, "card": { "brand": "visa", "last4": "4242" }, "paymentMethod": { "type": "card", "label": "Visa •••• 4242" }, "clientReference": "order_1042", "metadata": { "campaign": "fall-launch" }, "amountDetails": { "subtotalCents": 12500, "discountCents": 0, "shippingCents": 0, "taxCents": 0, "customerFeeCents": 0 }, "dispute": null, "failureMessage": null, "createdAt": "2026-10-14T16:02:11.000Z", "paidAt": "2026-10-14T16:02:15.000Z" }, "pendingWebhooks": 0 } ``` | Field | Type | Description | | --- | --- | --- | | `object` | string | The type of object. Always `event`. | | `id` | string | `evt_…`: the same id as the delivery's `Anytime-Event-Id` header. | | `type` | string | One of `payment.succeeded`, `payment.failed`, `payment.refunded`, `refund.created`, `refund.succeeded`, `refund.failed`, `dispute.opened`, `dispute.updated`, `dispute.closed`, `invoice.created`, `invoice.sent`, `invoice.paid`, `invoice.overdue`, `invoice.voided`, `subscription.created`, `subscription.updated`, `subscription.paused`, `subscription.resumed`, `subscription.past_due`, `subscription.canceled`, `payment.review_required`, `customer.created`, `customer.updated`, `payout.paid`, `payout.failed`. | | `created` | string (date-time) | When it happened. | | `livemode` | boolean | False for test-mode data (an `ak_test_` key); true for live data. | | `data` | object | The API object the event is about, as it was then. | | `pendingWebhooks` | integer | Endpoints still retrying it. | ## Endpoints ### List events `GET /api/v1/events` Permission: `events:read`. Every event emitted for the business in the last 30 days, newest first, whether or not a webhook endpoint took it: for reconciliation and catching up on missed deliveries. Each is the envelope a webhook delivery carries, plus pendingWebhooks. To catch up after downtime, list from your last handled event's `created` and skip ids you've already handled. **Query** | Name | Type | Required | Description | | --- | --- | --- | --- | | `type` | string | no | An event type (payment.succeeded), a family (payment.*), or up to 10 of either, comma-separated. | | `createdFrom` | string | no | Created at or after: 2026-10-01 or an ISO 8601 date-time. | | `createdTo` | string | no | Created before (exclusive), same format. | | `objectId` | string | no | Only events about this object: `pay_…`, `re_…`, `inv_…`, `sub_…`, `cus_…`. | | `limit` | integer | no | How many objects per page, 1–100. Default 25. See [Pagination](https://anytime-capital.com/docs/pagination.md). | | `cursor` | string | no | The previous page's `nextCursor`, for the page after it. | **Request (curl)** ```bash curl "https://anytime-capital.com/api/v1/events?type=payment.*&createdFrom=2026-10-14" \ -H "Authorization: Bearer $ANYTIME_API_KEY" ``` **Request (Node.js)** ```javascript const res = await fetch("https://anytime-capital.com/api/v1/events?type=payment.*&createdFrom=2026-10-14", { headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}` }, }); const page = await res.json(); if (!res.ok) throw new Error(`${page.error.code}: ${page.error.message}`); ``` **Request (Python)** ```python import os import requests res = requests.get( "https://anytime-capital.com/api/v1/events", headers={"Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}"}, params={"type": "payment.*", "createdFrom": "2026-10-14"}, timeout=30, ) page = res.json() if not res.ok: raise RuntimeError(f"{page['error']['code']}: {page['error']['message']}") ``` **Response** (200) ```json { "object": "list", "data": [ { "object": "event", "id": "evt_3f9a1c2b7d4e6f8a0b1c2d3e4f5a6b7c", "type": "payment.succeeded", "created": "2026-10-14T16:02:16.000Z", "livemode": true, "data": { "object": "payment", "livemode": true, "id": "pay_7Hq2LmX9cR4tVb", "status": "paid", "amountCents": 12500, "amountRefundedCents": 0, "netCents": 12088, "currency": "usd", "description": "Consulting — October", "source": { "type": "payment_link", "id": "pl_4kTz8QwN2xYb6RcD1mPa" }, "paymentLinkId": "pl_4kTz8QwN2xYb6RcD1mPa", "customer": { "name": "Jordan Lee", "email": "jordan@example.com", "country": "US" }, "card": { "brand": "visa", "last4": "4242" }, "paymentMethod": { "type": "card", "label": "Visa •••• 4242" }, "clientReference": "order_1042", "metadata": { "campaign": "fall-launch" }, "amountDetails": { "subtotalCents": 12500, "discountCents": 0, "shippingCents": 0, "taxCents": 0, "customerFeeCents": 0 }, "dispute": null, "failureMessage": null, "createdAt": "2026-10-14T16:02:11.000Z", "paidAt": "2026-10-14T16:02:15.000Z" }, "pendingWebhooks": 0 } ], "hasMore": false, "nextCursor": null } ``` **Errors** | Status | Code | When | | --- | --- | --- | | 400 | `INVALID_INPUT` | A field, filter, `limit` or `cursor` is missing or invalid. The message starts with the field's name, as in `amountCents: …`, or reads `Invalid cursor.` Fix the named field. Don't retry the same request unchanged. | ### Get an event `GET /api/v1/events/{id}` Permission: `events:read`. One event from the last 30 days, by the id a webhook delivery carries (Anytime-Event-Id). **Path** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | `evt_…` | **Request (curl)** ```bash curl https://anytime-capital.com/api/v1/events/evt_3f9a1c2b7d4e6f8a0b1c2d3e4f5a6b7c \ -H "Authorization: Bearer $ANYTIME_API_KEY" ``` **Request (Node.js)** ```javascript const res = await fetch("https://anytime-capital.com/api/v1/events/evt_3f9a1c2b7d4e6f8a0b1c2d3e4f5a6b7c", { headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}` }, }); const event = await res.json(); if (!res.ok) throw new Error(`${event.error.code}: ${event.error.message}`); ``` **Request (Python)** ```python import os import requests res = requests.get( "https://anytime-capital.com/api/v1/events/evt_3f9a1c2b7d4e6f8a0b1c2d3e4f5a6b7c", headers={"Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}"}, timeout=30, ) event = res.json() if not res.ok: raise RuntimeError(f"{event['error']['code']}: {event['error']['message']}") ``` **Response** (200) ```json { "object": "event", "id": "evt_3f9a1c2b7d4e6f8a0b1c2d3e4f5a6b7c", "type": "payment.succeeded", "created": "2026-10-14T16:02:16.000Z", "livemode": true, "data": { "object": "payment", "livemode": true, "id": "pay_7Hq2LmX9cR4tVb", "status": "paid", "amountCents": 12500, "amountRefundedCents": 0, "netCents": 12088, "currency": "usd", "description": "Consulting — October", "source": { "type": "payment_link", "id": "pl_4kTz8QwN2xYb6RcD1mPa" }, "paymentLinkId": "pl_4kTz8QwN2xYb6RcD1mPa", "customer": { "name": "Jordan Lee", "email": "jordan@example.com", "country": "US" }, "card": { "brand": "visa", "last4": "4242" }, "paymentMethod": { "type": "card", "label": "Visa •••• 4242" }, "clientReference": "order_1042", "metadata": { "campaign": "fall-launch" }, "amountDetails": { "subtotalCents": 12500, "discountCents": 0, "shippingCents": 0, "taxCents": 0, "customerFeeCents": 0 }, "dispute": null, "failureMessage": null, "createdAt": "2026-10-14T16:02:11.000Z", "paidAt": "2026-10-14T16:02:15.000Z" }, "pendingWebhooks": 0 } ``` **Errors** | Status | Code | When | | --- | --- | --- | | 404 | `NOT_FOUND` | No such object on your business (an id from another business reads the same), or no such endpoint under `/api/v1`, which still answers in JSON. Check the id and the path. Ids are case-sensitive. | --- # Test helpers API > Send your test webhook endpoints a payment, refund, dispute, invoice or subscription event on demand, with an ak_test_ key. Never with a live key. Source: https://anytime-capital.com/docs/test-helpers · Markdown: https://anytime-capital.com/docs/test-helpers.md · All docs: https://anytime-capital.com/docs/llms-full.txt Test mode only. With a test key (`ak_test_…`), send your test webhook endpoints one event of a type you choose, built from your own test data with the same shape, signature and delivery as the real event, and `livemode: false`. Nothing else changes and no money moves. A live key gets `403 TEST_MODE_ONLY` and nothing is read. Types: `payment.succeeded`, `payment.failed`, `refund.created`, `dispute.opened`, `invoice.paid`, `subscription.updated`. See [Test mode](https://anytime-capital.com/docs/test-mode.md#test-your-integration) for an example of each. AI assistants can send one too, with the [MCP server](https://anytime-capital.com/docs/build-with-ai.md)'s `send_test_event` tool and the same test key. | Topic | Detail | | --- | --- | | Endpoints | [`POST /api/v1/test-helpers/events`](#create-test-event) | | Key permissions | `webhook_endpoints:write` | | Samples | curl; Node.js 18+ with the built-in `fetch` (in an ES module); the [Node SDK](https://anytime-capital.com/docs/node-sdk.md) (preview: not on npm yet); Python with `requests`. | | Every request can also answer | 401 `UNAUTHORIZED`, 401 `API_KEY_EXPIRED`, 403 `NOT_ENROLLED`, 403 `PERMISSION_DENIED`, 403 `IP_NOT_ALLOWED`, 429 `RATE_LIMITED`, 500 `INTERNAL_ERROR`. See [Error codes](https://anytime-capital.com/docs/error-codes.md). | ## The test event object What the simulator sent: the event's id (the same `evt_…` your endpoints receive), its type, the test object it is about and how many of your endpoints it was queued for. ```json { "object": "test_event", "id": "evt_9b2c4d6e8f0a1b3c5d7e9f1a2b4c6d8e", "type": "payment.succeeded", "livemode": false, "objectId": "pay_7Hq2LmX9cR4tVb", "deliveries": 1, "sample": false } ``` | Field | Type | Description | | --- | --- | --- | | `object` | string | The type of object. Always `test_event`. | | `id` | string | The event's id, `evt_…`, as your endpoints receive it. | | `type` | string | The event sent. One of `payment.succeeded`, `payment.failed`, `refund.created`, `dispute.opened`, `invoice.paid`, `subscription.updated`. | | `livemode` | boolean | Always false: test events never carry live data. Always `false`. | | `objectId` | string | The test object the event carries as `data`. | | `deliveries` | integer | Deliveries queued: one per enabled endpoint that takes this event (0 when none does). | | `sample` | boolean | True when the object is from **Load sample data** (ids `pay_sample…`). | ## Endpoints ### Send a test webhook event `POST /api/v1/test-helpers/events` Permission: `webhook_endpoints:write`. Supports `Idempotency-Key`. Test mode only: needs an `ak_test_` key (a live key gets 403 TEST_MODE_ONLY). Sends your test webhook endpoints one event — payment.succeeded, payment.failed, refund.created, dispute.opened, invoice.paid or subscription.updated — marked `livemode: false`, built from one of your test objects (`objectId`, or the newest that fits) exactly as the real event carries it. Nothing else changes and no money moves. 409 NOTHING_TO_SEND when you have no test object of that kind yet. With an `ak_test_` key only. Leave out `objectId` to use your newest test object that fits. **Headers** | Header | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | Recommended. A unique key such as a UUID: a retry with the same key within 24 hours gets the first response back instead of acting twice. See [Idempotency](https://anytime-capital.com/docs/idempotency.md). | **Body** | Name | Type | Required | Description | | --- | --- | --- | --- | | `type` | string | yes | The event to send. One of `payment.succeeded`, `payment.failed`, `refund.created`, `dispute.opened`, `invoice.paid`, `subscription.updated`. | | `objectId` | string | no | The test object it is about (`pay_…`, `re_…`, `inv_…`, `sub_…`); left out, the newest that fits. Up to 64 characters. | **Request (curl)** ```bash curl -X POST https://anytime-capital.com/api/v1/test-helpers/events \ -H "Authorization: Bearer $ANYTIME_API_KEY" \ -H "Idempotency-Key: test-payment-succeeded-1" \ -H "Content-Type: application/json" \ -d '{ "type": "payment.succeeded", "objectId": "pay_7Hq2LmX9cR4tVb" }' ``` **Request (Node.js)** ```javascript const res = await fetch("https://anytime-capital.com/api/v1/test-helpers/events", { method: "POST", headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}`, "Content-Type": "application/json", "Idempotency-Key": "test-payment-succeeded-1", }, body: JSON.stringify({ type: "payment.succeeded", objectId: "pay_7Hq2LmX9cR4tVb" }), }); const testEvent = await res.json(); if (!res.ok) throw new Error(`${testEvent.error.code}: ${testEvent.error.message}`); ``` **Request (Python)** ```python import os import requests res = requests.post( "https://anytime-capital.com/api/v1/test-helpers/events", headers={ "Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}", "Idempotency-Key": "test-payment-succeeded-1", }, json={"type": "payment.succeeded", "objectId": "pay_7Hq2LmX9cR4tVb"}, timeout=30, ) testEvent = res.json() if not res.ok: raise RuntimeError(f"{testEvent['error']['code']}: {testEvent['error']['message']}") ``` **Response** (201) ```json { "object": "test_event", "id": "evt_9b2c4d6e8f0a1b3c5d7e9f1a2b4c6d8e", "type": "payment.succeeded", "livemode": false, "objectId": "pay_7Hq2LmX9cR4tVb", "deliveries": 1, "sample": false } ``` **Errors** | Status | Code | When | | --- | --- | --- | | 400 | `INVALID_JSON` | The body isn't valid JSON, or couldn't be read. An empty body reads as `{}`. Send `Content-Type: application/json` and a JSON object. | | 400 | `INVALID_INPUT` | A field, filter, `limit` or `cursor` is missing or invalid. The message starts with the field's name, as in `amountCents: …`, or reads `Invalid cursor.` Fix the named field. Don't retry the same request unchanged. | | 400 | `INVALID_IDEMPOTENCY_KEY` | The `Idempotency-Key` header is empty, longer than 255 characters, or has characters other than printable ASCII. Use a UUID. See [Idempotency](https://anytime-capital.com/docs/idempotency.md). | | 403 | `TEST_MODE_ONLY` | A test helper was called with a live key. Test helpers never touch live data. Use a test key (`ak_test_…`): turn on **Test mode** in the dashboard and create one in **Settings → Developers**. | | 404 | `NOT_FOUND` | No such object on your business (an id from another business reads the same), or no such endpoint under `/api/v1`, which still answers in JSON. Check the id and the path. Ids are case-sensitive. | | 409 | `NOTHING_TO_SEND` | You have no test object of the kind this event is about yet (no paid test payment for `payment.succeeded`, no test invoice for `invoice.paid`…). Make one in test mode first (pay a test link with a test card), or choose **Load sample data** in the test mode bar. | | 422 | `IDEMPOTENCY_KEY_REUSED` | The `Idempotency-Key` was used in the last 24 hours with a different body or endpoint. Use a new key for a new request; reuse a key only to retry the same request. | | 503 | `NOT_CONFIGURED` | The part of Anytime Capital Payments this needs isn't available right now. Retry later, with the same `Idempotency-Key` on a write. | --- # OpenAPI 3.1 document > The whole Anytime Capital Payments API as an OpenAPI 3.1 document: every endpoint, schema, permission and error, public, for code generators and AI tools. Source: https://anytime-capital.com/docs/openapi · Markdown: https://anytime-capital.com/docs/openapi.md · All docs: https://anytime-capital.com/docs/llms-full.txt Every endpoint of the API is described in one OpenAPI 3.1 document at `https://anytime-capital.com/api/v1/openapi.json`: its parameters, request bodies, responses, errors, the API key permission it needs and whether it takes an Idempotency-Key. It is public (no key), served with `Access-Control-Allow-Origin: *`, and built from the same tables as this reference, so the two always agree. | Detail | Value | | --- | --- | | URL | [https://anytime-capital.com/api/v1/openapi.json](https://anytime-capital.com/api/v1/openapi.json) | | Format | OpenAPI 3.1 (JSON Schema 2020-12), JSON | | Version | `info.version` is `2026-10-24`; it changes when the contract does. See the [Changelog](https://anytime-capital.com/docs/changelog.md). | | Operations | 53, each with an `operationId` such as `createPaymentLink` | | Authentication | `bearerAuth`: `Authorization: Bearer ak_live_…` | | `x-anytime-permission` | On each operation: the key permission it needs, such as `refunds:write` | | `x-anytime-idempotent` | On each operation: whether it honours the `Idempotency-Key` header | ## Tags | Tag | What it covers | Reference | | --- | --- | --- | | Payments | Card, wallet and bank payments. | [payments](https://anytime-capital.com/docs/payments.md) | | Refunds | Refunds, including those waiting for an owner's approval. | [refunds](https://anytime-capital.com/docs/refunds.md) | | Payment links | Hosted payment pages. | [payment-links](https://anytime-capital.com/docs/payment-links.md) | | Invoices | Invoices emailed to customers and paid by card. | [invoices](https://anytime-capital.com/docs/invoices.md) | | Customers | The business's saved customers. | [customers](https://anytime-capital.com/docs/customers.md) | | Products | The product catalog. | [products](https://anytime-capital.com/docs/products.md) | | Subscriptions | Subscription plans and subscribers. | [subscriptions](https://anytime-capital.com/docs/subscriptions.md), [meters](https://anytime-capital.com/docs/meters.md) | | Discount codes | Codes customers enter at checkout. | [discount-codes](https://anytime-capital.com/docs/discount-codes.md) | | Payouts | Payouts to the business's bank, and its balance. | [payouts-api](https://anytime-capital.com/docs/payouts-api.md) | | Disputes | Disputes on the business's payments. | [disputes](https://anytime-capital.com/docs/disputes.md) | | Webhook endpoints | Where events are sent (REST hooks). | [webhook-endpoints](https://anytime-capital.com/docs/webhook-endpoints.md) | | Events | Every event from the last 30 days, for reconciliation and missed deliveries. | [events](https://anytime-capital.com/docs/events.md) | | Test helpers | Test mode only (`ak_test_` keys): simulate events without real money. | [test-helpers](https://anytime-capital.com/docs/test-helpers.md) | | Meta | About the API itself. | [openapi](https://anytime-capital.com/docs/openapi.md) | ## Use it - **Typed TypeScript:** `npx openapi-typescript https://anytime-capital.com/api/v1/openapi.json -o anytime-capital.d.ts` gives you the request and response types to use with `fetch`. - **API clients:** import the URL into Postman, Insomnia or Bruno to get every request ready to send; set the bearer token to your key. Or import the ready-made [Postman collection](https://anytime-capital.com/docs/postman.md), with example bodies and your key as a variable. - **Other languages:** any generator that reads OpenAPI 3.1 can build a client from it. Check the generated code sends `Idempotency-Key` on writes. - **AI tools:** give an assistant the URL alongside [llms-full.txt](https://anytime-capital.com/docs/llms-full.txt) so it writes calls against the real schemas. See the [AI agents hub](https://anytime-capital.com/docs/build-with-ai.md). ## Endpoint ### Get this OpenAPI document `GET /api/v1/openapi.json` Public: no API key needed. The API described in OpenAPI 3.1. Public; no key needed. Served with `Access-Control-Allow-Origin: *` and cached for an hour, so browsers, API clients and code generators can load it directly. **Request (curl)** ```bash curl https://anytime-capital.com/api/v1/openapi.json ``` **Request (Node.js)** ```javascript const res = await fetch("https://anytime-capital.com/api/v1/openapi.json"); const spec = await res.json(); ``` **Request (Python)** ```python import requests res = requests.get( "https://anytime-capital.com/api/v1/openapi.json", timeout=30, ) spec = res.json() ``` **Response** (200) ```json { "openapi": "3.1.0", "info": { "title": "Anytime Capital Payments API", "version": "2026-10-24", "…": "…" }, "servers": [ { "url": "https://anytime-capital.com/api/v1", "description": "Production" } ], "paths": { "/payments": { "get": { "operationId": "listPayments", "x-anytime-permission": "payments:read", "…": "…" } }, "…": "…" }, "components": { "…": "…" } } ``` --- # API error code catalogue > Every error code the Anytime Capital Payments API returns: its HTTP status, what it means, how to fix it, whether to retry, and which endpoints return it. Source: https://anytime-capital.com/docs/error-codes · Markdown: https://anytime-capital.com/docs/error-codes.md · All docs: https://anytime-capital.com/docs/llms-full.txt Every error from `/api/v1` is `{ "error": { "code", "message" } }`. Branch on `code`, which never changes; show `message`, which is written for people and may be reworded. This page lists every code the API can return, taken from the API's own source; how to handle them in general is on [Errors and retries](https://anytime-capital.com/docs/errors.md). ## By status | Status | Codes | | --- | --- | | 400 | `INVALID_JSON`, `INVALID_INPUT`, `INVALID_IDEMPOTENCY_KEY`, `NOT_EDITABLE`, `CUSTOMER_MISMATCH` | | 401 | `UNAUTHORIZED`, `API_KEY_EXPIRED` | | 403 | `NOT_ENROLLED`, `PERMISSION_DENIED`, `IP_NOT_ALLOWED`, `TEST_MODE_ONLY` | | 404 | `NOT_FOUND`, `METER_NOT_FOUND`, `CUSTOMER_NOT_FOUND` | | 409 | `IDEMPOTENCY_KEY_REUSED`, `NOTHING_TO_SEND`, `NOT_REFUNDABLE`, `REFUND_PENDING_APPROVAL`, `TERMS_URL_REQUIRED`, `INVOICE_CLOSED`, `DUPLICATE_CUSTOMER`, `CODE_TAKEN`, `LIMIT_REACHED`, `SUBSCRIPTION_ENDED`, `ALREADY_CANCELING`, `CANNOT_PAUSE`, `NOT_PAUSED`, `CHANGE_IN_PROGRESS`, `SWITCH_SCHEDULED` | | 422 | `IDEMPOTENCY_KEY_REUSED`, `CUSTOMER_HAS_NO_EMAIL`, `TIMESTAMP_IN_FUTURE`, `TIMESTAMP_TOO_OLD`, `PERIOD_CLOSED` | | 429 | `RATE_LIMITED`, `TOO_SOON` | | 500 | `INTERNAL_ERROR`, `REFUND_NOT_RECORDED` | | 502 | `PROCESSOR_ERROR`, `EMAIL_FAILED` | | 503 | `NOT_CONFIGURED` | ## Every code | Code | Status | Meaning | Fix | Retry | Returned by | | --- | --- | --- | --- | --- | --- | | `UNAUTHORIZED` | 401 | The `Authorization` header is missing, or the key is malformed, unknown or revoked. A revoked key reads exactly like an unknown one. | Send `Authorization: Bearer ak_live_…` with an active key from **Settings → Developers**. | No: fix the request | Every request with a key | | `API_KEY_EXPIRED` | 401 | The key passed its expiry date, or the overlap after it was rolled ended. The message says when it expired. | Use the key that replaced it, or create a new one in **Settings → Developers**. See [Authentication](https://anytime-capital.com/docs/authentication.md). | No: fix the request | Every request with a key | | `NOT_ENROLLED` | 403 | The key is valid, but the business isn't verified and set up for card payments yet. | Finish verification in **Settings → Verification**; the API opens once card payments are set up. | No: fix the request | Every request with a key | | `PERMISSION_DENIED` | 403 | The key doesn't have the permission this request needs. The message names it, such as `refunds:write`. | Use a key that has it: an owner or admin creates one in **Settings → Developers**. See [Authentication](https://anytime-capital.com/docs/authentication.md#restricted-keys-and-permissions). | No: fix the request | Every request with a key | | `IP_NOT_ALLOWED` | 403 | The key has an IP allowlist and this request came from another address, which the message names. | Call from a server on the allowlist, or an owner or admin edits the key's allowed IPs in **Settings → Developers**. | No: fix the request | Every request with a key | | `RATE_LIMITED` | 429 | The key made more than 100 requests this minute. | Wait the seconds in the `Retry-After` header, then retry. See [Rate limits](https://anytime-capital.com/docs/rate-limits.md). | After `Retry-After` | Every request with a key | | `INTERNAL_ERROR` | 500 | Something failed on our side. | Retry with the same `Idempotency-Key`; you get the first result if the first attempt went through. Contact [Support](https://anytime-capital.com/docs/support.md) if it keeps happening. | Yes, same Idempotency-Key | Every request with a key | | `INVALID_JSON` | 400 | The body isn't valid JSON, or couldn't be read. An empty body reads as `{}`. | Send `Content-Type: application/json` and a JSON object. | No: fix the request | Every request with a body | | `INVALID_INPUT` | 400 | A field, filter, `limit` or `cursor` is missing or invalid. The message starts with the field's name, as in `amountCents: …`, or reads `Invalid cursor.` | Fix the named field. Don't retry the same request unchanged. | No: fix the request | Every list, every request with a body, and [`GET /meters/{id}/usage`](https://anytime-capital.com/docs/meters.md#get-meter-usage) | | `INVALID_IDEMPOTENCY_KEY` | 400 | The `Idempotency-Key` header is empty, longer than 255 characters, or has characters other than printable ASCII. | Use a UUID. See [Idempotency](https://anytime-capital.com/docs/idempotency.md). | No: fix the request | Every request with an Idempotency-Key | | `IDEMPOTENCY_KEY_REUSED` | 422 or 409 | The `Idempotency-Key` was used in the last 24 hours with a different body or endpoint. Or: The usage event's `idempotencyKey` was already used for a different event. | Use a new key for a new request; reuse a key only to retry the same request. Send a new `idempotencyKey` for each event; repeat a key only to resend the same event. | No: fix the request | Every request with an Idempotency-Key, and [`POST /meter-events`](https://anytime-capital.com/docs/meters.md#create-meter-event) | | `NOT_FOUND` | 404 | No such object on your business (an id from another business reads the same), or no such endpoint under `/api/v1`, which still answers in JSON. | Check the id and the path. Ids are case-sensitive. | No: fix the request | Every request with an id in the path, and [`POST /refunds`](https://anytime-capital.com/docs/refunds.md#create-refund), [`POST /test-helpers/events`](https://anytime-capital.com/docs/test-helpers.md#create-test-event) | | `TEST_MODE_ONLY` | 403 | A test helper was called with a live key. Test helpers never touch live data. | Use a test key (`ak_test_…`): turn on **Test mode** in the dashboard and create one in **Settings → Developers**. | No: fix the request | [`POST /test-helpers/events`](https://anytime-capital.com/docs/test-helpers.md#create-test-event) | | `NOTHING_TO_SEND` | 409 | You have no test object of the kind this event is about yet (no paid test payment for `payment.succeeded`, no test invoice for `invoice.paid`…). | Make one in test mode first (pay a test link with a test card), or choose **Load sample data** in the test mode bar. | No: fix the request | [`POST /test-helpers/events`](https://anytime-capital.com/docs/test-helpers.md#create-test-event) | | `NOT_CONFIGURED` | 503 | The part of Anytime Capital Payments this needs isn't available right now. | Retry later, with the same `Idempotency-Key` on a write. | Yes, same Idempotency-Key | [`POST /refunds`](https://anytime-capital.com/docs/refunds.md#create-refund), [`POST /subscriptions/{id}/cancel`](https://anytime-capital.com/docs/subscriptions.md#cancel-subscription), [`POST /subscriptions/{id}/pause`](https://anytime-capital.com/docs/subscriptions.md#pause-subscription), [`POST /subscriptions/{id}/resume`](https://anytime-capital.com/docs/subscriptions.md#resume-subscription), [`GET /payouts`](https://anytime-capital.com/docs/payouts-api.md#list-payouts), [`GET /payouts/{id}`](https://anytime-capital.com/docs/payouts-api.md#get-payout), [`GET /balance`](https://anytime-capital.com/docs/payouts-api.md#get-balance), [`GET /disputes`](https://anytime-capital.com/docs/disputes.md#list-disputes), [`GET /disputes/{id}`](https://anytime-capital.com/docs/disputes.md#get-dispute), [`POST /webhook-endpoints`](https://anytime-capital.com/docs/webhook-endpoints.md#create-webhook-endpoint), [`POST /test-helpers/events`](https://anytime-capital.com/docs/test-helpers.md#create-test-event) | | `PROCESSOR_ERROR` | 502 | The payments network didn't answer in time. | Retry with the same `Idempotency-Key`. A refund is keyed at the network by that key too, so a retry never refunds twice. | Yes, same Idempotency-Key | [`POST /refunds`](https://anytime-capital.com/docs/refunds.md#create-refund), [`POST /subscriptions/{id}/cancel`](https://anytime-capital.com/docs/subscriptions.md#cancel-subscription), [`POST /subscriptions/{id}/pause`](https://anytime-capital.com/docs/subscriptions.md#pause-subscription), [`POST /subscriptions/{id}/resume`](https://anytime-capital.com/docs/subscriptions.md#resume-subscription), [`GET /payouts`](https://anytime-capital.com/docs/payouts-api.md#list-payouts), [`GET /payouts/{id}`](https://anytime-capital.com/docs/payouts-api.md#get-payout), [`GET /balance`](https://anytime-capital.com/docs/payouts-api.md#get-balance), [`GET /disputes`](https://anytime-capital.com/docs/disputes.md#list-disputes), [`GET /disputes/{id}`](https://anytime-capital.com/docs/disputes.md#get-dispute) | | `NOT_REFUNDABLE` | 409 | The payment can't be refunded in its current state, or it was paid in stablecoins, which settle straight to your wallet. | Check the payment's `status` and `amountRefundedCents` with `GET /api/v1/payments/{id}`. Refund a stablecoin payment by sending the tokens back from your wallet. | No: fix the request | [`POST /refunds`](https://anytime-capital.com/docs/refunds.md#create-refund) | | `REFUND_PENDING_APPROVAL` | 409 | Another refund on this payment is already waiting for an owner's approval. | Wait for the owner's decision: `GET /api/v1/refunds?paymentId=…&status=pending_approval` shows it. | No: fix the request | [`POST /refunds`](https://anytime-capital.com/docs/refunds.md#create-refund) | | `REFUND_NOT_RECORDED` | 500 | The refund was sent, but our record of it isn't there yet. | Don't refund again. List `GET /api/v1/refunds?paymentId=…` in a minute to find it. | No: fix the request | [`POST /refunds`](https://anytime-capital.com/docs/refunds.md#create-refund) | | `NOT_EDITABLE` | 400 | The update names a field that is fixed once a link exists: `amountCents`, `items`, `payWhatYouWant`, `minAmountCents`, `shippingCents`, `allowDiscountCodes` or `singleUse`. The message names it. | Create a new link at the new price and deactivate the old one. | No: fix the request | [`POST /payment-links/{id}`](https://anytime-capital.com/docs/payment-links.md#update-payment-link) | | `TERMS_URL_REQUIRED` | 409 | `requireTermsConsent` needs a link to your terms, and the business hasn't set one. | Add your terms link in **Settings → Payments**, then retry. | No: fix the request | [`POST /payment-links`](https://anytime-capital.com/docs/payment-links.md#create-payment-link), [`POST /payment-links/{id}`](https://anytime-capital.com/docs/payment-links.md#update-payment-link) | | `INVOICE_CLOSED` | 409 | The invoice is already paid, refunded or void, so there's nothing to send. | Read the invoice's `status`; nothing to do. | No: fix the request | [`POST /invoices/{id}/send`](https://anytime-capital.com/docs/invoices.md#send-invoice) | | `TOO_SOON` | 429 | The invoice was already emailed in the last 24 hours. | Wait the seconds in `Retry-After`. | After `Retry-After` | [`POST /invoices/{id}/send`](https://anytime-capital.com/docs/invoices.md#send-invoice) | | `EMAIL_FAILED` | 502 | We couldn't send the email. | Retry. Nothing was marked as sent. | Yes, same Idempotency-Key | [`POST /invoices/{id}/send`](https://anytime-capital.com/docs/invoices.md#send-invoice) | | `DUPLICATE_CUSTOMER` | 409 | Another saved customer already has this email address, in any letter case. | Find them with `GET /api/v1/customers?email=…` and update that customer instead. | No: fix the request | [`POST /customers`](https://anytime-capital.com/docs/customers.md#create-customer), [`PATCH /customers/{id}`](https://anytime-capital.com/docs/customers.md#update-customer) | | `CODE_TAKEN` | 409 | The business already has a discount code with this code. | Pick another code, or use the existing one. | No: fix the request | [`POST /discount-codes`](https://anytime-capital.com/docs/discount-codes.md#create-discount-code) | | `LIMIT_REACHED` | 409 | The business already has 10 webhook endpoints. | Delete one you no longer use with `DELETE /api/v1/webhook-endpoints/{id}`. | No: fix the request | [`POST /webhook-endpoints`](https://anytime-capital.com/docs/webhook-endpoints.md#create-webhook-endpoint) | | `SUBSCRIPTION_ENDED` | 409 | The subscription has already ended (canceled, expired or completed). | Nothing to do; a customer subscribes again at the plan's `url`. | No: fix the request | [`POST /subscriptions/{id}/cancel`](https://anytime-capital.com/docs/subscriptions.md#cancel-subscription), [`POST /subscriptions/{id}/pause`](https://anytime-capital.com/docs/subscriptions.md#pause-subscription), [`POST /subscriptions/{id}/resume`](https://anytime-capital.com/docs/subscriptions.md#resume-subscription) | | `ALREADY_CANCELING` | 409 | The subscription is already set to cancel at the end of its period. | Send `atPeriodEnd: false` to end it now, or `POST …/resume` to keep it. | No: fix the request | [`POST /subscriptions/{id}/cancel`](https://anytime-capital.com/docs/subscriptions.md#cancel-subscription) | | `CANNOT_PAUSE` | 409 | Only an active or trialing subscription that renews can be paused: this one is paused, past due or set to cancel. | Read the subscription's `status` and `cancelAtPeriodEnd` first. | No: fix the request | [`POST /subscriptions/{id}/pause`](https://anytime-capital.com/docs/subscriptions.md#pause-subscription) | | `NOT_PAUSED` | 409 | The subscription is neither paused nor set to cancel, so there's nothing to resume. | Nothing to do. | No: fix the request | [`POST /subscriptions/{id}/resume`](https://anytime-capital.com/docs/subscriptions.md#resume-subscription) | | `CHANGE_IN_PROGRESS` | 409 | The subscription is switching plans right now. | Retry in a minute. | Yes, same Idempotency-Key | [`POST /subscriptions/{id}/cancel`](https://anytime-capital.com/docs/subscriptions.md#cancel-subscription), [`POST /subscriptions/{id}/pause`](https://anytime-capital.com/docs/subscriptions.md#pause-subscription), [`POST /subscriptions/{id}/resume`](https://anytime-capital.com/docs/subscriptions.md#resume-subscription) | | `SWITCH_SCHEDULED` | 409 | The subscription switches plans at its next renewal, so it can't be paused or resumed. | Undo the switch from the subscriber's details on the **Subscriptions** page first. | No: fix the request | [`POST /subscriptions/{id}/pause`](https://anytime-capital.com/docs/subscriptions.md#pause-subscription), [`POST /subscriptions/{id}/resume`](https://anytime-capital.com/docs/subscriptions.md#resume-subscription) | | `METER_NOT_FOUND` | 404 | No active meter has this `eventName`. | Check the meter's event name on the **Meters** tab of **Subscriptions**, or with `GET /api/v1/meters`. | No: fix the request | [`POST /meter-events`](https://anytime-capital.com/docs/meters.md#create-meter-event) | | `CUSTOMER_NOT_FOUND` | 404 | No saved customer has this `customerId`. | Send the customer's `cus_…` id, or `customerEmail` instead. | No: fix the request | [`POST /meter-events`](https://anytime-capital.com/docs/meters.md#create-meter-event) | | `CUSTOMER_HAS_NO_EMAIL` | 422 | The customer has no email address, and usage is matched to subscriptions by email. | Add an email to the customer in the dashboard, or send `customerEmail`. | No: fix the request | [`GET /meters/{id}/usage`](https://anytime-capital.com/docs/meters.md#get-meter-usage), [`POST /meter-events`](https://anytime-capital.com/docs/meters.md#create-meter-event) | | `CUSTOMER_MISMATCH` | 400 | `customerId` and `customerEmail` name different customers. | Send one of them. | No: fix the request | [`GET /meters/{id}/usage`](https://anytime-capital.com/docs/meters.md#get-meter-usage), [`POST /meter-events`](https://anytime-capital.com/docs/meters.md#create-meter-event) | | `TIMESTAMP_IN_FUTURE` | 422 | The event's `timestamp` is more than five minutes ahead of our clock. | Send the time the usage happened, or leave `timestamp` out for now. | No: fix the request | [`POST /meter-events`](https://anytime-capital.com/docs/meters.md#create-meter-event) | | `TIMESTAMP_TOO_OLD` | 422 | The event's `timestamp` is more than 400 days ago. | Usage that old can't be billed; drop the event. | No: fix the request | [`POST /meter-events`](https://anytime-capital.com/docs/meters.md#create-meter-event) | | `PERIOD_CLOSED` | 422 | The `timestamp` falls in a period already billed for this customer and meter. | Send usage from the end of the billed period on. The message says when that is. | No: fix the request | [`POST /meter-events`](https://anytime-capital.com/docs/meters.md#create-meter-event) | ## Usage events in a batch `POST /api/v1/meter-events/batch` answers `200` when the batch itself is valid, and reports each refused event in its result with the code a single event would get: `INVALID_INPUT`, `METER_NOT_FOUND`, `CUSTOMER_NOT_FOUND`, `CUSTOMER_HAS_NO_EMAIL`, `CUSTOMER_MISMATCH`, `TIMESTAMP_IN_FUTURE`, `TIMESTAMP_TOO_OLD`, `PERIOD_CLOSED` or `IDEMPOTENCY_KEY_REUSED`. See [Meters](https://anytime-capital.com/docs/meters.md#create-meter-event-batch). ## MCP server errors A tool call that reaches the API returns the API's error as the tool result, with `isError: true`: `{ status, error: { code, message } }`, plus `retryAfterSeconds` when the API asked to wait. `UNEXPECTED_RESPONSE` means the API answered without a JSON body. Problems with the JSON-RPC message itself answer as JSON-RPC errors: | Code | Meaning | | --- | --- | | -32700 | The body isn't valid JSON. | | -32600 | Not a JSON-RPC 2.0 request, an empty batch, or an unsupported `MCP-Protocol-Version` header (HTTP `400`). `GET` and `DELETE` answer `405` with this code. | | -32601 | A method other than `initialize`, `ping`, `tools/list` or `tools/call`. | | -32602 | An unknown tool, no `params.name`, or arguments that aren't an object (or a missing `id`). | | -32603 | Something failed on our side; try again. | ## Node SDK errors The [Node SDK](https://anytime-capital.com/docs/node-sdk.md) throws a class per kind of error, each with the API's `code`, `message`, `status` and `retryAfterSeconds`, and adds codes of its own: `TIMEOUT` and `CONNECTION_ERROR` (no answer, after its retries), `UNEXPECTED_RESPONSE` (an answer without the error envelope) and `SIGNATURE_VERIFICATION_FAILED` (a webhook that didn't verify). --- # Changelog > What's new in Anytime Capital Payments, newest first: API endpoints, webhook events, the MCP server, the Node SDK, checkout, billing and integrations. Source: https://anytime-capital.com/docs/changelog · Markdown: https://anytime-capital.com/docs/changelog.md · All docs: https://anytime-capital.com/docs/llms-full.txt What shipped, newest first. Changes to the API's contract also change the [OpenAPI document](https://anytime-capital.com/docs/openapi.md)'s `info.version`. ## 2026-10-25: Developer tools for switching - A [Postman collection](https://anytime-capital.com/docs/postman.md) of every endpoint, built from the OpenAPI document, to import by link. - The [API mapping guide](https://anytime-capital.com/docs/stripe-api-mapping.md) for code moving from Stripe: objects, calls and webhook events side by side, and what has no equivalent yet. - The [Anytime CLI](https://anytime-capital.com/docs/cli.md) preview, documented ahead of its npm release: forward test events to localhost and trigger test events. - A **Developer tools** card in **Settings → Developers** linking all of these. ## 2026-10-24: API reference, guides and AI agents hub - A reference page per API resource, built from the OpenAPI document: every endpoint with its parameters, permission, Idempotency-Key support, errors, and samples in curl, Node.js, the Node SDK and Python. Start at [Payments](https://anytime-capital.com/docs/payments.md). - Guides you can copy and run: [Next.js checkout](https://anytime-capital.com/docs/nextjs-checkout.md), [webhook verification](https://anytime-capital.com/docs/webhook-signatures.md) in Node.js, Python and PHP, [safe refunds](https://anytime-capital.com/docs/refund-safely.md), [nightly payout reconciliation](https://anytime-capital.com/docs/reconcile-payouts.md), [subscriptions](https://anytime-capital.com/docs/sell-subscriptions.md), [usage billing](https://anytime-capital.com/docs/bill-usage-with-meters.md) and [stablecoins](https://anytime-capital.com/docs/accept-stablecoins.md). - The [AI agents hub](https://anytime-capital.com/docs/build-with-ai.md): connecting the MCP server in each client, [least-privilege keys for agents](https://anytime-capital.com/docs/ai-agent-safety.md), [custom agents](https://anytime-capital.com/docs/mcp-custom-agents.md) and a [prompt library](https://anytime-capital.com/docs/ai-prompt-library.md). - An [error code catalogue](https://anytime-capital.com/docs/error-codes.md), a [glossary](https://anytime-capital.com/docs/glossary.md) and the [OpenAPI page](https://anytime-capital.com/docs/openapi.md). - OpenAPI document `2026-10-24`: usage events are marked idempotent by their own `idempotencyKey` rather than the header, writes that take an Idempotency-Key list their `400`, and the usage object's daily points are `{ date, quantity }`. ## 2026-10-24: Restricted keys and a much larger API - [Restricted API keys](https://anytime-capital.com/docs/authentication.md#restricted-keys-and-permissions): none, read or write per resource, with Full access and Read only presets. A request the key can't make answers `403 PERMISSION_DENIED` naming the permission. - New endpoints, 53 in all: [refunds](https://anytime-capital.com/docs/refunds.md) (within your approval limit), [customers](https://anytime-capital.com/docs/customers.md) create and update, [products](https://anytime-capital.com/docs/products.md), [subscription plans and subscriptions](https://anytime-capital.com/docs/subscriptions.md) with cancel, pause and resume, [discount codes](https://anytime-capital.com/docs/discount-codes.md), [payouts and balance](https://anytime-capital.com/docs/payouts-api.md) and [disputes](https://anytime-capital.com/docs/disputes.md) (read only), and [webhook endpoints](https://anytime-capital.com/docs/webhook-endpoints.md). - The API described in [OpenAPI 3.1](https://anytime-capital.com/docs/openapi.md), with each operation's permission. - A [Node SDK](https://anytime-capital.com/docs/node-sdk.md) preview with typed resources, auto-pagination, safe retries and webhook verification. It isn't on npm yet. - The [MCP server](https://anytime-capital.com/docs/build-with-ai.md) grew to 48 tools covering every resource above but usage events, each naming the permission it needs. - 25 [webhook event types](https://anytime-capital.com/docs/webhooks.md), covering payments, refunds, disputes, invoices, subscriptions, customers, payouts and payments held for review. ## 2026-10-24: Checkout, risk and billing - One-click checkout: customers who opt in pay with a saved card after a code by email. See [Fraud rules & 3D Secure](https://anytime-capital.com/docs/risk-and-3ds.md#one-click-checkout). - Checkout risk rules and a manual review queue for held card payments. See [manual review](https://anytime-capital.com/docs/risk-and-3ds.md#manual-review). - [Stablecoin checkout](https://anytime-capital.com/docs/stablecoin-checkout.md): customers pay links and invoices in USDC or USDT straight to your business wallet. - [Subscription discount codes and plan changes](https://anytime-capital.com/docs/subscription-discounts.md) with proration, and [usage-based billing](https://anytime-capital.com/docs/usage-billing.md) with meters and the [usage events API](https://anytime-capital.com/docs/meters.md). ## 2026-10-24: Money movement and integrations - [QuickBooks Online and Xero sync](https://anytime-capital.com/docs/accounting-sync.md): payments, card processing, refunds and payouts posted through a clearing account. - [Bank transfer instructions on invoices](https://anytime-capital.com/docs/invoice-bank-transfers.md), your payout schedule in **Settings → Payouts**, and Form 1099-K requests from **Reports → Year-end**. - [WooCommerce plugin](https://anytime-capital.com/docs/woocommerce.md). The [Shopify app](https://anytime-capital.com/docs/shopify.md) is waiting for Shopify's approval, and the [Zapier app](https://anytime-capital.com/docs/zapier.md) isn't in Zapier's directory yet: both pages show how they'll work. - [Switch from Stripe](https://anytime-capital.com/docs/switch-from-stripe.md): an importer for customers, products, plans, discount codes, links and open invoices, and invitations that move subscribers without charging twice. ## 2026-10-23: Anytime Capital Payments launches - Payment links, invoices, a Pay button and inline checkout for your website, and [in-person QR payments](https://anytime-capital.com/docs/in-person.md). - The REST API: [payment links](https://anytime-capital.com/docs/payment-links.md), [invoices](https://anytime-capital.com/docs/invoices.md), [payments](https://anytime-capital.com/docs/payments.md) and customers, with [idempotency](https://anytime-capital.com/docs/idempotency.md), [pagination](https://anytime-capital.com/docs/pagination.md) and signed [webhooks](https://anytime-capital.com/docs/webhooks.md). - The MCP server for AI assistants, and these docs as Markdown with [llms-full.txt](https://anytime-capital.com/docs/llms-full.txt). --- # Glossary > The words used across the Anytime Capital Payments docs and API, from approval limit and clearing account to idempotency key, meter and signing secret. Source: https://anytime-capital.com/docs/glossary · Markdown: https://anytime-capital.com/docs/glossary.md · All docs: https://anytime-capital.com/docs/llms-full.txt The words these docs use, and what they mean at Anytime Capital. Ids show their prefix, so you can tell what an id is by looking at it. | Term | Meaning | | --- | --- | | **3D Secure** | An extra check by the customer's bank at card checkout. You can require it above an amount: see [Fraud rules & 3D Secure](https://anytime-capital.com/docs/risk-and-3ds.md). | | **Activity log** | Your business's record of who did what. Every change an API key or AI agent makes is written there under the key's name. | | **Aggregation** | How a [meter](https://anytime-capital.com/docs/meters.md) adds up a period's usage events: `sum`, `count`, `max` or `last`. | | **Amounts** | Always integer cents in USD: `12500` is $125.00. Fields that hold money end in `Cents`. | | **Anytime Capital** | A fintech company, not a bank. Banking services are provided by our licensed partners. | | **API key** | A secret key starting `ak_live_`, sent as `Authorization: Bearer …`. Made in **Settings → Developers**; keep it on your server. See [Authentication](https://anytime-capital.com/docs/authentication.md). | | **Approval limit** | A refund amount above which refunds by admins, API keys and AI agents wait for an owner's approval (**Settings → Team & approvals**). See [Refunds](https://anytime-capital.com/docs/refunds.md). | | **Balance** | Your Anytime Capital Payments money: `availableCents` to pay out, `pendingCents` not settled yet and `reserveCents` held. See [Payouts & balance](https://anytime-capital.com/docs/payouts-api.md). | | **Clearing account** | The account in QuickBooks Online or Xero that stands for your payments balance when you [sync your books](https://anytime-capital.com/docs/accounting-sync.md). | | **Cursor** | The `nextCursor` of one page of a list, passed back as `cursor` for the next. See [Pagination](https://anytime-capital.com/docs/pagination.md). | | **Customer** | Someone saved on your **Customers** page, one per email address. See [Customers](https://anytime-capital.com/docs/customers.md). | | **Deposit** | Part of an [invoice](https://anytime-capital.com/docs/invoices.md) the customer may pay first, as an amount or a percentage. | | **Discount code** | A code customers enter at checkout for a percentage or amount off. See [Discount codes](https://anytime-capital.com/docs/discount-codes.md). | | **Dispute** | A customer's challenge of a payment through their bank (a chargeback, or an inquiry first). Respond from **Disputes** in the dashboard. See [Disputes](https://anytime-capital.com/docs/disputes.md). | | **Event** | What we POST to your webhook endpoint when something happens, such as `payment.succeeded`: an `id`, a `type` and the API object in `data`. See [Webhooks](https://anytime-capital.com/docs/webhooks.md). | | **Idempotency-Key** | A header you send on writes so a retry never acts twice: the same key within 24 hours gets the first response back. See [Idempotency](https://anytime-capital.com/docs/idempotency.md). | | **Invoice** | A bill with line items, tax and a due date, emailed in your branding and paid at its `url`. See [Invoices](https://anytime-capital.com/docs/invoices.md). | | **llms-full.txt** | Every docs page as one Markdown file, for AI assistants: [/docs/llms-full.txt](https://anytime-capital.com/docs/llms-full.txt). | | **MCP server** | The Model Context Protocol endpoint at `/api/mcp` that lets AI assistants call the API as tools, with your API key. See [AI agents](https://anytime-capital.com/docs/build-with-ai.md). | | **Metadata** | Your own key/value pairs on a payment link, copied onto each payment made on it, so you can match a payment to your order. | | **Meter** | Something customers use that you bill by the unit, such as API calls. Usage events add up on it each period. See [Usage billing](https://anytime-capital.com/docs/usage-billing.md). | | **Net** | What you keep from a payment after fees (`netCents`), or what reaches your bank from a payout. | | **Payment** | One charge on a payment link, invoice or subscription, by card, wallet, bank account or stablecoin. Ids start `pay_`. See [Payments](https://anytime-capital.com/docs/payments.md). | | **Payment link** | A hosted checkout page for one price, shared by its `url`. Ids start `pl_`. See [Payment links](https://anytime-capital.com/docs/payment-links.md). | | **Payout** | Money sent from your balance to your bank. Ids start `wdrl_`. Made in the dashboard only. See [Payout options](https://anytime-capital.com/docs/payouts.md). | | **Permission** | What a restricted key may do with one resource: none, read or write, such as `refunds:write`. See [Authentication](https://anytime-capital.com/docs/authentication.md#restricted-keys-and-permissions). | | **Plan** | A recurring price customers subscribe to, billed weekly, monthly, quarterly or yearly. Ids start `sp_`. See [Subscriptions](https://anytime-capital.com/docs/subscriptions.md). | | **Proration** | Refunding the unused part of a period when a subscriber switches plan now. See [Subscription discounts and plan changes](https://anytime-capital.com/docs/subscription-discounts.md). | | **Rate limit** | 100 requests a minute per API key; over it, `429` with `Retry-After`. See [Rate limits](https://anytime-capital.com/docs/rate-limits.md). | | **Refund** | Money returned on a payment, in full or in part. Ids start `re_`, or `re_appr_` for one waiting for approval. See [Refunds](https://anytime-capital.com/docs/refunds.md). | | **Restricted key** | An API key with only the permissions it needs, per resource. The safest kind to give an AI agent: see [Safe AI agents](https://anytime-capital.com/docs/ai-agent-safety.md). | | **Signing secret** | A webhook endpoint's `whsec_…` secret, used to verify the `Anytime-Signature` header on every event. See [Verify webhooks](https://anytime-capital.com/docs/webhook-signatures.md). | | **Stablecoin** | A token worth one US dollar, here USDC or USDT. Customers can pay in them straight to your wallet: see [Stablecoin checkout](https://anytime-capital.com/docs/stablecoin-checkout.md). | | **Subscription** | One customer's subscription to a plan. Ids start `sub_`. See [Subscriptions](https://anytime-capital.com/docs/subscriptions.md). | | **Test mode** | A copy of your business where no real money moves: `ak_test_` keys, test cards, test webhooks (`livemode: false`) and simulated events. See [Test mode](https://anytime-capital.com/docs/test-mode.md). | | **Trace code** | The number your bank can find a payout's deposit by (`traceCode`). | | **Usage event** | One record of usage sent to a meter, with its own `idempotencyKey`. See [Meters & usage events](https://anytime-capital.com/docs/meters.md). | | **Webhook endpoint** | An `https://` URL we send events to, with its own signing secret and event list. Ids start `we_`. See [Webhook endpoints](https://anytime-capital.com/docs/webhook-endpoints.md). | --- Anytime Capital is a fintech company, not a bank. Payment processing, banking and payout services are provided by Anytime Capital's licensed partners. Rates shown are for US card payments; international cards, currency conversion and disputes may carry additional fees shown in your dashboard before you accept a payment.