External credits & billing
Stripe isn't the only way to bill your customers. If you invoice through your own system — a bank transfer flow, another PSP, a marketplace, or manual sales — you can grant (and deduct) prepaid credits over the API. MicroAuth takes care of the ledger, the balance, and enforcement; your system stays the source of truth for payments.
Authentication: workspace API key
The credits endpoint is part of the management API, so it authenticates with a workspace API key (mak_...), not the tenant secret:
Authorization: Bearer mak_...Create one in the MicroAuth app under Workspace settings → API keys. The key acts on behalf of the workspace and can manage every tenant in it; the full key is shown once at creation.
Don't confuse the key types
mas_ (tenant secret) is for the SDK API only. mak_ (workspace key) is for management endpoints like this one. See API keys at a glance.
The endpoint
POST https://api.microauth.com/v1/tenants/{tenantId}/customers/{customerId}/creditstenantId— the tenant's UUID (visible in the app URL and via the API).customerId— the customer's UUID. A "customer" is always a team (portal workspace), never an individual portal user — even a solo signup gets a single-member team behind the scenes, and the credit balance belongs to that team. Look customers up withGET /v1/tenants/{tenantId}/customers, or find the ID on the customer's detail page in the MicroAuth app.
Request body:
| Field | Type | Required | Meaning |
|---|---|---|---|
amount_micro | integer | yes | Credit amount in micro-USD (1,000,000 = $1.00). Positive to grant, negative to deduct. Must be non-zero. |
description | string (≤ 300) | no | Free-text shown in the ledger, e.g. "Invoice #2026-081" |
external_ref | string (≤ 120) | no | Idempotency key from your billing system — strongly recommended |
Response:
{ "credit_balance_micro": 22500000 }The new balance after the adjustment ($22.50 here).
Example: grant $10 for a paid invoice
curl -X POST \
"https://api.microauth.com/v1/tenants/$TENANT_ID/customers/$CUSTOMER_ID/credits" \
-H "Authorization: Bearer mak_..." \
-H "Content-Type: application/json" \
-d '{
"amount_micro": 10000000,
"description": "Invoice #2026-081 paid via wire",
"external_ref": "inv_2026_081"
}'The same in Python:
import requests
r = requests.post(
f"https://api.microauth.com/v1/tenants/{TENANT_ID}/customers/{CUSTOMER_ID}/credits",
headers={"Authorization": f"Bearer {WORKSPACE_KEY}"},
json={
"amount_micro": 10_000_000, # $10.00
"description": "Invoice #2026-081 paid via wire",
"external_ref": "inv_2026_081", # idempotency key
},
timeout=10,
)
r.raise_for_status()
print("new balance:", r.json()["credit_balance_micro"] / 1_000_000, "USD")To deduct (a refund or chargeback), send a negative amount with its own reference, e.g. "amount_micro": -10000000, "external_ref": "refund_inv_2026_081".
Idempotency with external_ref
Billing systems retry. Webhooks fire twice. Networks fail after the request was applied but before you saw the response. external_ref makes all of that safe:
- Each
external_refis unique per tenant. The first request with a given reference applies the adjustment and writes a ledger entry. - A replay with the same reference changes nothing — no duplicate ledger entry, no double credit — and returns
200with the current balance.
So the safe pattern is simply: give every adjustment a stable reference derived from your system (invoice ID, payment intent ID, refund ID), and retry freely on timeouts.
One reference = one adjustment
Because replays are no-ops, don't reuse a reference for a different amount — it would be silently ignored. A refund needs its own reference, not the original invoice's.
Reading the ledger
Every adjustment — external, Stripe, subscription grant or usage charge — lands in the customer's credit ledger:
curl "https://api.microauth.com/v1/tenants/$TENANT_ID/customers/$CUSTOMER_ID/ledger?limit=50&offset=0" \
-H "Authorization: Bearer mak_..."{
"total": 3,
"entries": [
{
"id": "5c1f...",
"amount_micro": 10000000,
"kind": "external_topup",
"description": "Invoice #2026-081 paid via wire",
"external_ref": "inv_2026_081",
"created_at": "2026-08-03T18:04:11Z"
}
]
}External adjustments have kind: "external_topup" (for grants and deductions alike); Stripe top-ups, plan credit grants and other movements have their own kinds. The same ledger is visible in the MicroAuth app on the customer's detail page, and to the customer on their portal's Billing page.
How it plays with enforcement
The balance you adjust here is the same prepaid balance the SDK enforces:
- SDKs pick up the new balance on their next snapshot refresh (~30s), so a customer who just paid is unblocked within moments.
- Usage charges deduct from this balance automatically as usage is reported — you only need to put credits in (or claw them back).
See also
- Custom integration — how usage gets charged against the balance.
- The API reference — full schemas under the Customers tag, including customer listing, custom limits and suspension endpoints you may want in your billing automation.