Developer docs

Connecting to the BazaarPay API

The API is standard and stable. If you have used a similar API before, only change the base URL and the key.

Quick start

Base URL of every request:

https://gateway.bazaar-pay.ir
  1. Sign up and request the services you need.
  2. Once your account is approved, create a key in Panel › API keys. The key secret (secretKey) is shown only once.
  3. Use the key to get an access token and send it in the Authorization header.
  4. Call the paths and request bodies as described in the API reference.

Authentication and tokens

One apiKey / secretKey pair works for every product. For compatibility with existing clients, each product keeps its own token path and format, and a token issued by any of them is valid for all products (biometric authentication, alpha, has no token path and uses these same tokens).

  • Access token lifetime: 24 hours. Refresh token lifetime: 48 hours.
  • Send the token in the Authorization: Bearer <accessToken> header.
  • Before it expires, use the refresh token to get a new token pair.

Inquiry (ide)

Get a tokenPOST /ide/v1/tokens/generate
request
{"apiKey": "…", "secretKey": "…"}
200 response
{"accessToken": "…", "refreshToken": "…"}
Refresh the tokenPOST /ide/v1/tokens/refresh
request
{"refreshToken": "…"}

Response: same format as getting a token.

SMS (pulse)

Get a tokenPOST /pulse/api/v1/auth/authenticate
request
{"api_key": "…", "secret_key": "…"}
200 response
{"access_token": "…", "refresh_token": "…"}
Refresh the tokenPOST /pulse/api/v1/auth/refresh-token
request
{"access_token": "…", "refresh_token": "…"}

Response: same format as getting a token.

Contract and signature (mzahub)

Get a tokenPOST /mzahub/v1/auth/login
request
{"apiKey": "…", "secretKey": "…"}
200 response
{"accessToken": "…", "refreshToken": "…", "expiresIn": 86400, "refreshTokenExpiresIn": 172800, "tokenType": "Bearer"}
Refresh the tokenPOST /mzahub/v1/auth/refresh
request
{"refreshToken": "…"}

Response: same format as getting a token.

Request headers

HeaderRequiredDescription
AuthorizationRequiredBearer <accessToken>
X-TRACK-IDOptionalYour own tracking id; stored in the panel usage report and searchable.
X-REAL-IPOptionalRequired by the upstream contract API, but not needed for /mzahub paths here; the gateway sends its own registered server IP instead.
Content-TypeWith a bodyapplication/json or multipart/form-data, as each service requires.

IP restriction

For each API key you can define a list of allowed IPs. Then both getting a token and calling services are accepted only from those IPs; otherwise the error forbidden.ip is returned. An empty list means every IP is allowed. In production, always register your servers' IPs.

Test (sandbox) keys

When you create a key in the panel you can mark it as a "test key" (prefix bp_test_). Its requests go through the same checks as a normal key (active account, service access, IP, rate limit and resource ownership) but are not sent to the provider: a sample response with the same structure as the API reference comes back, nothing is charged, and the calls appear in the usage report labelled "test" (they are not counted in statistics, invoices or service status).

To try an error path, send the header X-Mock-Status: 503 or the query parameter ?X-Mock-Status=503 with a test key and that status code is returned.

Code samples

TOKEN=$(curl -s -X POST https://gateway.bazaar-pay.ir/ide/v1/tokens/generate \
  -H "Content-Type: application/json" \
  -d '{"apiKey":"YOUR_API_KEY","secretKey":"YOUR_SECRET_KEY"}' | jq -r .accessToken)

curl "https://gateway.bazaar-pay.ir/ide/v1/services/matching?nationalCode=0012345678&mobileNumber=09120000000" \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-TRACK-ID: order-1001"

Pricing and billing

  • Each service has a set price (in Rial) shown in the catalog and the panel; your account may have its own price.
  • Some requests use several services at once; for example a legal-entity inquiry with the ceo=true parameter is charged for both services.
  • Successful requests (status code below 400) are charged. Provider-side errors (5xx) and connection failures are never charged. For some services, 4xx responses (except 401, 403 and 429) are charged under the provider's rules.
  • The amount is reserved before the request is sent and settled when the response arrives; without enough credit the error credit.insufficient (code 402) is returned.
  • Prepaid: usage is deducted from the wallet balance. Postpaid: you use up to the agreed credit limit, and at the start of each month the invoice for the previous month is issued.

SMS billing

SMS is charged per segment and per recipient. A Persian message is one segment up to 70 characters (67 per segment in a multi-part message), and a Latin message up to 160 characters (153 multi-part). The final basis is the segment_count value in the provider's response.

Resource ownership

SMS messages and contracts created through your account belong to your account. SMS status inquiries, contract inquiry/cancel/download and the signing steps are answered only for your own resources; for other ids the error service.not_found is returned.

Rate limit and queue

Each account has a limit on requests per second (shown in the panel). A request over the limit is not rejected right away; it waits in a queue for its turn, at most for the account's "allowed wait" (3 seconds by default). If its turn does not come in that time, a 429 error with a Retry-After header is returned and nothing is charged.

Response time is always bounded: queue wait + provider timeout. The timeout is 15 seconds for inquiry and SMS and 100 seconds for contracts and video identity verification; after that the 504 error upstream.timeout is returned. Each request's wait time is in the X-Queue-Time response header (milliseconds).

Batch requests (asynchronous)

For large lists (for example 10,000 inquiries) submit all requests at once and get the results later. Each item runs and is charged exactly like a direct call (same path, body, access and price) and appears in the usage report with the tracking id batch-<id>-<seq>.

  • At most 10,000 items and 2 MB per request; authenticated with the same access token.
  • deadlineMinutes (default 30, max 1440): items not run by this deadline expire and cost nothing.
  • Temporary errors (429, 502, 503, 504) are retried automatically; other responses are stored as they are.
  • callbackUrl (optional, https only): when the batch finishes, a status summary is POSTed to this URL. It does not contain the results; fetch them from the API with your own token.
  • Results are kept for 30 days.
batch API
POST /api/v1/batches
Authorization: Bearer <accessToken>
Content-Type: application/json

{
  "items": [
    {"method": "GET", "path": "/ide/v1/cards?number=6037990000000000"},
    {"method": "POST", "path": "/pulse/api/v1/message/sms/send", "body": {"receptor": "0912…", "message": "…", "sender": "3000…"}}
  ],
  "deadlineMinutes": 30,
  "callbackUrl": "https://example.com/hooks/batch"
}

→ 202 {"batchId": 42, "status": "running", "total": 2, "deadline": "…"}

GET /api/v1/batches/42
→ {"batchId": 42, "status": "done", "total": 2, "pending": 0, "running": 0, "done": 2, "succeeded": 2, "expired": 0, …}

GET /api/v1/batches/42/items?after=-1&limit=500
→ {"items": [{"seq": 0, "status": "done", "httpStatus": 200, "response": {…}}, …]}

Webhooks

In Panel › Webhooks, register your https URLs and choose the events. Internal, loopback and private IP addresses are refused.

EventSent whendata
batch.completedA batch request finishes{batchId, status, total}
sms.mo.receivedAn SMS arrives on one of your dedicated lines (about a minute late; after an outage of up to 24 hours, the missed messages are still sent){id, sender, receptor, message, received_date_time}
webhook.testThe "send test" button in the panel{message}

SMS delivery status and contract status are not announced by the provider, so they have no events; keep querying them as before.

  • Body: {"id", "type", "createdAt", "data"} with the headers X-Webhook-Id (event id, the same across retries), X-Webhook-Event, X-Webhook-Timestamp (Unix seconds) and X-Webhook-Signature.
  • Signature: v1=hex(HMAC-SHA256(secret, timestamp + "." + body)). Check it against the raw body and reject events older than a few minutes. The panel shows the secret once; "New secret" replaces it, and from that moment every delivery is signed with the new one.
  • Any 2xx response means delivered. Otherwise it is resent after 30 seconds, 2, 8 and 32 minutes, about 2 and 8.5 hours, and 12 hours (8 attempts), then marked "failed". The delivery log stays in the panel for 30 days.

Error codes

Provider responses are returned unchanged. The gateway's own errors use the same product's format:

error format
// ide, pulse, mzahub
{"code": "credit.insufficient", "message": "…"}

// alpha
{"errorCode": "credit.insufficient", "errorMessage": "…", "data": null}

The message texts are in Persian.

HTTPCodeDescription
401unauthorizedThe token is missing, invalid or expired.
402credit.insufficientNot enough wallet balance (or postpaid credit limit).
402spend.limitThis API key has reached its monthly spending cap for the current Jalali month.
403forbidden.scopeThis API key has no access to the requested product or service (the key's scopes).
403forbidden.ipThe request came from an IP outside the key's allowed list.
403forbidden.serviceThis service is not enabled for your account; request it in the panel.
403customer.inactiveYour account has not been approved yet, or is suspended.
404service.not_foundThe path or service does not exist, or the resource (SMS/contract) does not belong to you.
429rate.limitedYour account's rate limit did not give the request a turn within the allowed wait (Retry-After header).
429queue.timeoutThe provider queue did not free up within the allowed wait; nothing is charged (Retry-After header).
502upstream.unavailableThe service provider is unreachable; nothing is charged.
504upstream.timeoutThe provider did not answer in time; nothing is charged.

Service status

The live status of every service is available without a token at GET /api/v1/public/status and is updated every 30 seconds. Status is computed from real requests in the last hour, and for each service the 24-hour availability, p95 latency and the availability of each hour (24 values, oldest first; null means no traffic) are returned. The same data is shown on the service status page.

status values: operational, degraded, down, unknown (no traffic in the last hour). Before retrying automatically on an upstream.unavailable error, you can check this path.

Full API reference

See the details of every path, parameter and response in the interactive reference. The OpenAPI file is also available for generating clients, and a Postman collection for quick testing.

In Postman, fill in the apiKey and secretKey variables and send a token request; the token is saved in accessToken and used by the other requests. The panel API (login, keys, usage, webhooks and batches) is described at /api/v1/public/control-openapi.json.