Base URL and conventions
Everything lives under two hosts. The sandbox mirrors production exactly — same endpoints, same objects, same failure modes — so you can build against it with confidence and switch by changing one key.
| Environment | Base URL | Key prefix |
| Sandbox | https://api.sandbox.bearmerchant.com | sk_test_… |
| Production | https://api.bearmerchant.com | sk_live_… |
- All requests must be made over HTTPS. Plain HTTP is rejected, not redirected.
- Request bodies are JSON. Send
Content-Type: application/json. - Amounts are integers in the currency's smallest unit —
4280 is $42.80. - Timestamps are RFC 3339 in UTC, e.g.
2026-07-27T09:14:22Z. - Every object has a prefixed id (
ch_, cus_, po_) so you always know what you are holding.
first-request.sh
bash
# Create a charge in the sandbox
curl https://api.sandbox.bearmerchant.com/v1/charges \
-u sk_test_51H8xQ2p: \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 8f14e45f-ea01-4f2b-9c39-1b7d2a6c0e11" \
-d '{
"amount": 4280,
"currency": "usd",
"payment_method": "pm_card_visa",
"description": "Order #1042"
}'
Core endpoints
The resources most integrations touch. Every collection endpoint supports the same pagination and filtering parameters described below.
Charges
| Method | Path | Description |
| POST |
/v1/charges |
Create and authorise a charge |
| GET |
/v1/charges/:id |
Retrieve a single charge |
| GET |
/v1/charges |
List charges, newest first |
| POST |
/v1/charges/:id/capture |
Capture a previously authorised charge |
| POST |
/v1/charges/:id/refund |
Refund all or part of a charge |
Customers and payment methods
| Method | Path | Description |
| POST |
/v1/customers |
Create a customer |
| GET |
/v1/customers/:id |
Retrieve a customer |
| PATCH |
/v1/customers/:id |
Update a customer |
| DELETE |
/v1/customers/:id |
Delete a customer and its tokens |
| POST |
/v1/payment_methods |
Tokenise a card or bank account |
| POST |
/v1/payment_methods/:id/attach |
Attach a payment method to a customer |
Subscriptions and payouts
| Method | Path | Description |
| POST |
/v1/subscriptions |
Start a subscription |
| PATCH |
/v1/subscriptions/:id |
Change plan, quantity or billing anchor |
| DELETE |
/v1/subscriptions/:id |
Cancel, immediately or at period end |
| GET |
/v1/payouts |
List settlements to your bank account |
| GET |
/v1/balance |
Current available and pending balance |
Disputes
| Method | Path | Description |
| GET |
/v1/disputes |
List chargebacks and enquiries |
| GET |
/v1/disputes/:id |
Retrieve a dispute with its deadline |
| POST |
/v1/disputes/:id/evidence |
Submit representment evidence |
| POST |
/v1/disputes/:id/accept |
Accept liability and close the case |
The charge object
Charges are the object you will handle most. A successful response looks like this:
charge.json
json
{
"id": "ch_3PkX9a2eZvKYlo2C",
"object": "charge",
"amount": 4280,
"amount_refunded": 0,
"currency": "usd",
"status": "succeeded",
"captured": true,
"description": "Order #1042",
"customer": "cus_9Fq2LmTd",
"payment_method": {
"id": "pm_1KdY7x",
"brand": "visa",
"last4": "4242",
"exp_month": 8,
"exp_year": 2027
},
"outcome": {
"network_status": "approved_by_network",
"risk_level": "normal",
"risk_score": 8,
"three_d_secure": "authenticated"
},
"metadata": { "order_id": "1042" },
"created": "2026-07-27T09:14:22Z"
}
Create charge parameters
| Parameter | Type | Required | Description |
amount |
integer |
Required |
Amount in the smallest currency unit. Must be positive. |
currency |
string |
Required |
Three-letter ISO code, lowercase — usd, eur, gbp. |
payment_method |
string |
Required |
A tokenised payment method id, or a customer's default when customer is set. |
customer |
string |
Optional |
Attach the charge to an existing customer. |
capture |
boolean |
Optional |
Defaults to true. Set false to authorise now and capture later. |
three_d_secure |
string |
Optional |
automatic (default), any or never. Exemptions are applied where they qualify. |
description |
string |
Optional |
Shown on your dashboard and, where supported, on the cardholder statement. |
statement_descriptor |
string |
Optional |
Up to 22 characters. Use something the cardholder will recognise — vague descriptors drive disputes. |
metadata |
object |
Optional |
Up to 40 key/value pairs of your own data. Returned on every read and every webhook. |
Idempotency
Any POST can be retried safely by sending an Idempotency-Key header. If a request with that key has already succeeded, the original response is replayed instead of creating a second charge.
retry.sh
bash
curl https://api.bearmerchant.com/v1/charges \
-u sk_live_…: \
-H "Idempotency-Key: 8f14e45f-ea01-4f2b-9c39-1b7d2a6c0e11" \
-d '{ "amount": 4280, "currency": "usd", "payment_method": "pm_1KdY7x" }'
# Send the identical request again — you get the same charge back,
# not a second one, and the response carries Idempotent-Replayed: true
Keys are scoped to your account and retained for 24 hours. Generate a fresh UUID per logical operation — reusing one key for a genuinely different request returns an error rather than silently overwriting.
Errors
Errors use conventional HTTP status codes and always return a body describing what went wrong and, where the problem is fixable, which parameter caused it.
error.json
json
{
"error": {
"type": "card_error",
"code": "insufficient_funds",
"message": "The card has insufficient funds to complete this charge.",
"decline_code": "51",
"charge": "ch_3PkX9a2eZvKYlo2C",
"doc_url": "https://bearmerchant.com/api-reference#errors"
}
}
| Status | Type | What it means |
400 | invalid_request_error | A parameter is missing or malformed. Fix the request. |
401 | authentication_error | The key is missing, revoked or wrong for this environment. |
402 | card_error | The card was declined. decline_code tells you whether a retry is worth attempting. |
403 | permission_error | The key is valid but lacks the scope for this endpoint. |
404 | not_found_error | No object with that id, or it belongs to another account. |
409 | idempotency_error | The idempotency key was reused with different parameters. |
429 | rate_limit_error | Too many requests. Back off and retry — see below. |
5xx | api_error | Something failed on our side. Safe to retry with the same idempotency key. |
Never branch on the human-readable message — it can change. Branch on type and code, which are stable contract.
Rate limits
Limits are applied per account, not per key, and are generous enough that normal traffic never touches them. Every response carries your current position so you can pace yourself before being throttled.
| Header | Meaning |
RateLimit-Limit | Requests permitted in the current window |
RateLimit-Remaining | Requests left in the current window |
RateLimit-Reset | Seconds until the window resets |
Retry-After | Sent with a 429 — wait this many seconds |
- Read endpoints: 100 requests per second.
- Write endpoints: 50 requests per second.
- Bulk exports and reporting: 10 requests per second.
- Retry a 429 with exponential backoff and jitter — a tight retry loop will keep you throttled.