> For the complete documentation index, see [llms.txt](https://run.emailchaser.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://run.emailchaser.com/api-reference/credits.md).

# Credits

## Get the credit balance

> Returns the workspace's credit wallet. A wallet is created empty on first use, so a new workspace sees zeros rather than an error. unlimited is true when Emailchaser has made this workspace's credits free: credit actions then never spend available and are never refused for lack of credits, so there is no need to check the balance or buy credits.

```json
{"openapi":"3.1.1","info":{"title":"Emailchaser API","version":"1.0"},"servers":[{"url":"https://api.emailchaser.com/r"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","name":"Authorization","in":"header"}},"schemas":{"models.CreditBalanceResponse":{"type":"object","properties":{"available":{"type":"integer"},"lifetimeGranted":{"type":"integer"},"lifetimeUsed":{"type":"integer"},"monthlyGrant":{"type":"integer"},"reserved":{"type":"integer"},"unlimited":{"description":"Unlimited is true when this workspace's credits are free: every credit\naction goes through without spending available, nothing is refused for\nlack of credits, and POST /credits/purchase is refused (code\ncredits_free) because there is nothing to buy.","type":"boolean"}}},"models.ErrorUnauthorized":{"type":"object","properties":{"error":{"type":"string"}}},"models.ErrorResponse":{"type":"object","properties":{"error":{"type":"string"}}}}},"paths":{"/credits/balance":{"get":{"description":"Returns the workspace's credit wallet. A wallet is created empty on first use, so a new workspace sees zeros rather than an error. unlimited is true when Emailchaser has made this workspace's credits free: credit actions then never spend available and are never refused for lack of credits, so there is no need to check the balance or buy credits.","tags":["Credits"],"summary":"Get the credit balance","responses":{"200":{"description":"The wallet snapshot","content":{"application/json":{"schema":{"$ref":"#/components/schemas/models.CreditBalanceResponse"}}}},"401":{"description":"Unauthorized - invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/models.ErrorUnauthorized"}}}},"500":{"description":"Failed to load the credit balance","content":{"application/json":{"schema":{"$ref":"#/components/schemas/models.ErrorResponse"}}}}}}}}}
```

## Buy credits (charges real money)

> CHARGES REAL MONEY: this immediately charges the workspace's saved default payment method (the card behind the active subscription), off-session, with no confirmation step beyond this call. Buys between 1000 and 10000 prospect credits at the tiered list price ($33 per 1000 credits, $20 per 1000 from 5000 credits) and grants them to the wallet on success. Responds with the credits bought, the exact amount charged in USD and the new available balance. Errors carry a machine-readable code: billing\_required (402, no active subscription or saved card - fix billing in the app first), payment\_failed (402, the charge was refused - no money moved), credits\_free (409, this workspace's credits are free, so nothing is charged and there is nothing to buy), invalid\_request (400), temporarily\_unavailable (503, the purchase stopped before any charge - safe to retry), purchase\_incomplete (500, charged but not credited, support already notified - do NOT retry) and purchase\_unconfirmed (500, check /credits/transactions before retrying). The grant is idempotent on the Stripe invoice, so one charge can never double-credit - but every successful call is a NEW charge.

```json
{"openapi":"3.1.1","info":{"title":"Emailchaser API","version":"1.0"},"servers":[{"url":"https://api.emailchaser.com/r"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","name":"Authorization","in":"header"}},"schemas":{"models.PurchaseCreditsResponse":{"type":"object","properties":{"amountUsd":{"description":"AmountUsd is what the saved default payment method was charged, in\nUS dollars.","type":"number"},"creditsPurchased":{"description":"CreditsPurchased is how many credits were bought and granted.","type":"integer"},"newBalance":{"description":"NewBalance is the available credit balance after the grant.","type":"integer"}}},"models.PurchaseCreditsErrorResponse":{"type":"object","properties":{"code":{"description":"Code is machine-readable:\n  - invalid_request: malformed body or credits outside 1000..10000; fix the request. No charge was made.\n  - billing_required: the workspace has no active subscription with a saved default card; fix billing in the Emailchaser app, then retry. No charge was made.\n  - payment_failed: the charge was attempted and refused (declined, expired, insufficient funds); fix the card, then retry. No money moved.\n  - credits_free: this workspace's credits are free, so there is nothing to buy. No charge was made.\n  - temporarily_unavailable: the purchase stopped before any charge because a check could not be read. No charge was made; retry shortly.\n  - purchase_incomplete: the card WAS charged but crediting failed; support is already notified. Do NOT retry - a retry charges again.\n  - purchase_unconfirmed: the outcome is unknown, or the purchase completed but the new balance could not be read; check /credits/transactions for a stripe_topup entry before retrying.","type":"string"},"error":{"type":"string"}}},"models.ErrorUnauthorized":{"type":"object","properties":{"error":{"type":"string"}}},"models.PurchaseCreditsRequest":{"type":"object","required":["credits"],"properties":{"credits":{"description":"Credits is how many prospect credits to buy. Minimum 1000, maximum\n10000 per call.","type":"integer"}}}}},"paths":{"/credits/purchase":{"post":{"description":"CHARGES REAL MONEY: this immediately charges the workspace's saved default payment method (the card behind the active subscription), off-session, with no confirmation step beyond this call. Buys between 1000 and 10000 prospect credits at the tiered list price ($33 per 1000 credits, $20 per 1000 from 5000 credits) and grants them to the wallet on success. Responds with the credits bought, the exact amount charged in USD and the new available balance. Errors carry a machine-readable code: billing_required (402, no active subscription or saved card - fix billing in the app first), payment_failed (402, the charge was refused - no money moved), credits_free (409, this workspace's credits are free, so nothing is charged and there is nothing to buy), invalid_request (400), temporarily_unavailable (503, the purchase stopped before any charge - safe to retry), purchase_incomplete (500, charged but not credited, support already notified - do NOT retry) and purchase_unconfirmed (500, check /credits/transactions before retrying). The grant is idempotent on the Stripe invoice, so one charge can never double-credit - but every successful call is a NEW charge.","tags":["Credits"],"summary":"Buy credits (charges real money)","responses":{"200":{"description":"The completed purchase","content":{"application/json":{"schema":{"$ref":"#/components/schemas/models.PurchaseCreditsResponse"}}}},"400":{"description":"Malformed body or credits out of bounds (code invalid_request)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/models.PurchaseCreditsErrorResponse"}}}},"401":{"description":"Unauthorized - invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/models.ErrorUnauthorized"}}}},"402":{"description":"No usable billing (code billing_required) or the charge was refused (code payment_failed)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/models.PurchaseCreditsErrorResponse"}}}},"409":{"description":"Credits are free for this workspace, nothing was charged (code credits_free)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/models.PurchaseCreditsErrorResponse"}}}},"500":{"description":"Charged but not credited (code purchase_incomplete, do not retry) or outcome unknown (code purchase_unconfirmed)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/models.PurchaseCreditsErrorResponse"}}}},"503":{"description":"Stopped before any charge, safe to retry (code temporarily_unavailable)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/models.PurchaseCreditsErrorResponse"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/models.PurchaseCreditsRequest"}}},"description":"How many credits to buy (1000..10000)","required":true}}}}}
```

## List credit transactions

> Lists the workspace's credit ledger entries, newest first. Optional filters: kind (movement type), reason (what the credits were for), and since/until on the entry time (both inclusive, RFC3339 or YYYY-MM-DD where a bare date means midnight UTC at the start of that day). Without filters the full ledger is returned as before. An entry with free=true was written while the workspace's credits were free: its amount is what the action would have cost, and no credits moved.

```json
{"openapi":"3.1.1","info":{"title":"Emailchaser API","version":"1.0"},"servers":[{"url":"https://api.emailchaser.com/r"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","name":"Authorization","in":"header"}},"schemas":{"models.ListCreditTransactionsResponse":{"type":"object","properties":{"total":{"type":"integer"},"transactions":{"type":"array","items":{"$ref":"#/components/schemas/models.CreditTransactionItem"}}}},"models.CreditTransactionItem":{"type":"object","properties":{"amount":{"description":"Amount is the signed credit delta: negative for spend, positive for\ngrants and refunds.","type":"integer"},"balanceAfter":{"description":"BalanceAfter is the available balance immediately after this entry.","type":"integer"},"createdAt":{"type":"string"},"description":{"type":"string"},"free":{"description":"Free is true for an entry written while this workspace's credits were\nfree. Its amount is what the action would have cost, no credits moved,\nand balanceAfter is the balance it found.","type":"boolean"},"id":{"type":"integer"},"kind":{"description":"Kind is the movement type: grant, topup, reserve, commit, refund,\nexpire or adjustment.","type":"string"},"reason":{"description":"Reason is what the credits were spent on or granted for.","type":"string"},"reference":{"description":"Reference correlates the entry to what caused it, e.g. 'prospect_search:123'.","type":"string"}}},"models.ErrorResponse":{"type":"object","properties":{"error":{"type":"string"}}},"models.ErrorUnauthorized":{"type":"object","properties":{"error":{"type":"string"}}}}},"paths":{"/credits/transactions":{"get":{"description":"Lists the workspace's credit ledger entries, newest first. Optional filters: kind (movement type), reason (what the credits were for), and since/until on the entry time (both inclusive, RFC3339 or YYYY-MM-DD where a bare date means midnight UTC at the start of that day). Without filters the full ledger is returned as before. An entry with free=true was written while the workspace's credits were free: its amount is what the action would have cost, and no credits moved.","tags":["Credits"],"summary":"List credit transactions","parameters":[{"schema":{"type":"integer"},"description":"Page size (default 50, max 200)","name":"limit","in":"query"},{"schema":{"type":"integer"},"description":"Page number, 1-based (default 1)","name":"page","in":"query"},{"schema":{"type":"string"},"description":"Filter by movement type: grant, topup, reserve, commit, refund, expire or adjustment","name":"kind","in":"query"},{"schema":{"type":"string"},"description":"Filter by reason: monthly_grant, prospect_reveal, ai_icp, ai_sequence, ai_reply, stripe_topup or manual_adjustment","name":"reason","in":"query"},{"schema":{"type":"string"},"description":"Only entries at or after this time, RFC3339 or YYYY-MM-DD","name":"since","in":"query"},{"schema":{"type":"string"},"description":"Only entries at or before this time, RFC3339 or YYYY-MM-DD (a bare date means midnight UTC at the start of that day)","name":"until","in":"query"}],"responses":{"200":{"description":"Ledger entries and total count (both reflect the filters)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/models.ListCreditTransactionsResponse"}}}},"400":{"description":"Invalid filter value","content":{"application/json":{"schema":{"$ref":"#/components/schemas/models.ErrorResponse"}}}},"401":{"description":"Unauthorized - invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/models.ErrorUnauthorized"}}}},"500":{"description":"Failed to retrieve credit transactions","content":{"application/json":{"schema":{"$ref":"#/components/schemas/models.ErrorResponse"}}}}}}}}}
```
