Birinchishop Partner API
REST API, webhooks, S2S postbacks and an MCP server for partners, ad trackers and AI agents. Version 2026-10-23.
Authentication
Create a key in the partner panel (Dashboard → API). The full key is shown once. Send it on every request:
Authorization: Bearer bsk_live_XXXXXXXXXXXX_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Keys are scoped — grant only what the integration needs — and can be restricted to IP addresses and given an expiry date. Never put a key in a browser, mobile app or public repository; if a key leaks, revoke it in the panel immediately. Keys in URLs are not accepted.
| Scope | Allows |
|---|---|
account:read | Read profile and balance |
offers:read | Read the offers catalog |
links:read | Read tracking links |
links:write | Create tracking links |
orders:read | Read orders and their statuses |
orders:write | Submit orders (leads) |
stats:read | Read statistics |
events:read | Read the event feed (order status changes) |
webhooks:manage | Manage webhook/postback endpoints |
insights:read | Read AI manager recommendations and order sale chance |
ads:read | Read Meta ad campaigns and their results |
ads:write | Pause ad campaigns; resume/budget changes require the partner's confirmation |
Quick start
1. Find good offers
curl -s "https://birinchishop.uz/api/v1/offers?sort=best_selling&limit=5" \
-H "Authorization: Bearer $BSK_KEY"
2. Create a tracking link
curl -s -X POST "https://birinchishop.uz/api/v1/links" \
-H "Authorization: Bearer $BSK_KEY" -H "Content-Type: application/json" \
-d '{"offer_id": 123, "name": "Instagram reels"}'
Use data.url in your ads. To track conversions in your tracker, use data.tracking_url_template — e.g. https://birinchishop.uz/l/123?click_id={clickid}&sub1={campaign}. Orders placed on that page carry your click_id/sub1..5.
3. Or send orders from your own landing page
curl -s -X POST "https://birinchishop.uz/api/v1/orders" \
-H "Authorization: Bearer $BSK_KEY" -H "Content-Type: application/json" \
-H "Idempotency-Key: my-order-98765" \
-d '{"link_id": 456, "phone": "+998901234567", "name": "Aziz", "region": "Toshkent", "click_id": "abc123", "external_id": "98765"}'
Every order is real: a call center phones the customer. Requires the orders:write scope and an account with at least 3 delivered orders (or admin approval).
4. Track statuses
# incremental sync
curl -s "https://birinchishop.uz/api/v1/events?after=0&include_order=true" -H "Authorization: Bearer $BSK_KEY"
# then call again with after=<meta.next_cursor>
Business rules
- Customers pay on delivery (cash on delivery). A delivery fee (typically 30,000 UZS) is added to customer_price by the delivery partner.
- Payout is credited only when an order reaches "delivered"; if the status is later reverted, the payout is reversed.
- One phone number cannot order the same offer twice within 24 hours, and at most 3 different offers per 24 hours.
- Customer phone numbers are always masked for partners.
- Submitting orders via API (orders:write) requires at least 3 delivered orders on the account, or admin approval.
- Fake or test orders are forbidden and lead to account blocking.
Endpoints
Base URL: https://birinchishop.uz/api/v1. All responses are JSON: {"data": …, "meta": …, "request_id": "…"}. Money is integer UZS; date filters use Asia/Tashkent; timestamps are ISO 8601 UTC.
| Path | Description | Scope | |
|---|---|---|---|
| GET | /me | Account, balance and key info | account:read |
| GET | /offers | Search active offers | offers:read |
| GET | /offers/{offer_id} | Offer details with variants and your links | offers:read |
| GET | /links | List your tracking links | links:read |
| POST | /links | Create a tracking link | links:write |
| GET | /links/{link_id} | One tracking link | links:read |
| GET | /orders | List your orders | orders:read |
| POST | /orders | Submit a real customer order (lead) Creates a REAL order; a call center will phone the customer. Never send test or fake orders. Send an `Idempotency-Key` header so that retries never create duplicates (a replay returns 200 with header `Idempotent-Replayed: true`). | orders:write |
| GET | /orders/{order_id} | One order | orders:read |
| GET | /stats | Aggregated statistics | stats:read |
| GET | /events | Order events feed (cursor-based) Poll this instead of (or in addition to) webhooks. Store meta.next_cursor and pass it as `after` next time. Events are kept for 90 days. | events:read |
| GET | /orders/{order_id}/sale-chance | Probability that the order gets delivered Estimate 0-100% from the offer sale rate, the order stage, the customer's previous orders across the network, duplicates and rejected leads. The latest value is also in Order.sale_chance_percent. | insights:read |
| GET | /insights | AI manager: today's overview and recommendations Yesterday's results, last 7 days vs previous week, promoted offers with sale rates and stock, Meta campaigns, suggested offers and prioritized recommendations (type + hint). Also the morning brief text. | insights:read |
| GET | /ads/status | Meta ad account connection | ads:read |
| GET | /ads/campaigns | Meta campaigns with results | ads:read |
| POST | /ads/campaigns/{campaign_id}/pause | Pause a campaign (immediate) | ads:write |
| POST | /ads/campaigns/{campaign_id}/resume | Request to resume (needs partner confirmation) | ads:write |
| POST | /ads/campaigns/{campaign_id}/budget | Request a daily budget change (needs partner confirmation) | ads:write |
| GET | /actions/{action_id} | Status of a confirmation request | ads:read |
| GET | /webhooks | List webhooks/postbacks | webhooks:manage |
| POST | /webhooks | Add a webhook or S2S postback kind=json: we POST a signed JSON event. kind=postback: we GET your URL after replacing macros like {click_id}, {status}, {conversion_status}, {payout}. Only public http(s) URLs on ports 80/443/8080/8443. The signing secret is returned ONCE in this response. | webhooks:manage |
| PATCH | /webhooks/{webhook_id} | Update / re-enable a webhook | webhooks:manage |
| DELETE | /webhooks/{webhook_id} | Delete a webhook | webhooks:manage |
| POST | /webhooks/{webhook_id}/test | Send a sample event now | webhooks:manage |
| GET | /webhooks/{webhook_id}/deliveries | Recent delivery attempts | webhooks:manage |
| POST | /mcp | MCP endpoint for AI agents (JSON-RPC 2.0) Model Context Protocol, Streamable HTTP transport, stateless (JSON responses, no SSE). Tools are filtered by the key scopes. | — |
| GET | /reference | Enums, scopes, statuses and business rules | — |
Order statuses
| status | Meaning |
|---|---|
new | Order received, waiting for the call center. |
processing | Call center is processing the order. |
callback | Customer asked to be called back later. |
confirmed | Customer confirmed the order by phone. |
packed | Order is packed at the warehouse. |
shipping | Order is on the way to the customer. |
delivered | Customer received and paid for the product. Payout is earned. |
returned | Customer refused the product at delivery; it was returned. |
cancelled | Order was cancelled (not confirmed, duplicate, unreachable customer, etc.). No payout. |
conversion_status (for trackers)
| value | Meaning |
|---|---|
lead | Order received, not yet confirmed (new, processing, callback). |
approved | Confirmed by the customer, not yet delivered (confirmed, packed, shipping). |
sale | Delivered and paid by the customer. Payout earned. |
rejected | Returned or cancelled. No payout. |
Webhooks (signed JSON)
Add a webhook in the panel or via POST /webhooks with "kind": "json". We send order.created and order.status_changed events as a POST. Reply with any 2xx within 10 s; otherwise we retry (1m, 5m, 15m, 1h, 3h, 6h, 12h, 24h). After many consecutive failures the webhook is disabled and you are notified. Events can arrive twice or out of order — deduplicate by id.
POST /your/endpoint
X-Birinchishop-Event: order.status_changed
X-Birinchishop-Event-Id: evt_1042
X-Birinchishop-Timestamp: 1761130000
X-Birinchishop-Signature: t=1761130000,v1=5f1c…
{"id":"evt_1042","type":"order.status_changed","api_version":"2026-10-23",
"created_at":"2026-10-22T10:00:00.000Z",
"data":{"id":1042,"order_id":12345,"status":"delivered","previous_status":"shipping",
"conversion_status":"sale","order":{"id":12345,"payout":50000,"payout_status":"earned", "...": "..."}}}
Verify the signature (Node.js)
const crypto = require('crypto');
function verify(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(',').map(p => p.split('=')));
if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false; // replay protection
const expected = crypto.createHmac('sha256', secret).update(parts.t + '.' + rawBody).digest('hex');
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1 || ''));
}
Python
import hmac, hashlib, time
def verify(raw_body: bytes, header: str, secret: str) -> bool:
parts = dict(p.split('=', 1) for p in header.split(','))
if abs(time.time() - int(parts['t'])) > 300:
return False
expected = hmac.new(secret.encode(), parts['t'].encode() + b'.' + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts.get('v1', ''))
S2S postbacks (Keitaro, Binom, Voluum, …)
Add a webhook with "kind": "postback". We call your URL with GET on each event, replacing macros (URL-encoded):
{click_id} {sub1} {sub2} {sub3} {sub4} {sub5} {order_id} {external_id} {status} {conversion_status} {payout} {customer_price} {currency} {offer_id} {link_id} {event} {event_id} {timestamp}
https://tracker.example.com/postback?subid={click_id}&status={conversion_status}&payout={payout}¤cy={currency}
Map conversion_status in your tracker: lead → lead, approved → approved/hold, sale → sale, rejected → rejected. Payout is earned only on sale.
AI agents (MCP)
An MCP server lets AI assistants (Claude, ChatGPT and others) work with your account in natural language: “find 3 best-selling offers under 300 000 som, create links and report yesterday's results”. Transport: Streamable HTTP (stateless), endpoint https://birinchishop.uz/api/v1/mcp. The agent only sees tools allowed by the granted scopes — grant read-only access unless it must create links or orders.
Claude.ai, ChatGPT and other apps (OAuth — no key needed)
Claude.ai: Settings → Connectors → Add custom connector. ChatGPT: turn on Settings → Apps & Connectors → Advanced settings → Developer mode, then Create with Authentication: OAuth. Use the URL https://birinchishop.uz/api/v1/mcp and press Connect (paid plans of these apps). You will be sent to the Birinchi Shop panel: sign in, choose permissions and confirm with your password (or a Telegram code). The connection appears in Panel → API, where you can revoke it at any time; it is also revoked automatically when your password changes. OAuth 2.1 with PKCE (S256), dynamic client registration and client ID metadata documents are supported; metadata: protected resource, authorization server. OAuth tokens work only on the MCP endpoint (use an API key for REST), access tokens live 1 hour, refresh tokens rotate, a connection lasts up to 180 days.
Claude Code (API key)
claude mcp add --transport http birinchishop https://birinchishop.uz/api/v1/mcp \
--header "Authorization: Bearer $BSK_KEY"
Generic MCP client config
{
"mcpServers": {
"birinchishop": {
"type": "http",
"url": "https://birinchishop.uz/api/v1/mcp",
"headers": {
"Authorization": "Bearer ${BSK_KEY}"
}
}
}
}
| Tool | Description | Scope |
|---|---|---|
get_account | Get the partner account: name, status, current balance (UZS), frozen balance, minimum withdrawal and the API key scopes. | account:read |
search_offers | Search the catalog of active offers (products the partner can promote). Returns customer_price, payout, lead_bonus, stock and sale_rate_percent for each offer. Use sort=best_selling to find offers that actually get delivered. | offers:read |
get_offer | Get full details of one offer: description, all images, video, variants (sizes/colors with their own price and payout) and the partner's existing links for this offer (my_links). | offers:read |
list_links | List the partner's tracking links (streams) with clicks, orders and delivered counts. Each link has a public url to share in ads. | links:read |
create_link | Create a new tracking link for an offer. Returns the public url (landing page with order form) and a tracking_url_template that passes click_id/sub1..sub5 for postbacks. Check get_offer.my_links first to avoid duplicates. | links:write |
list_orders | List the partner's orders with status, payout and payout_status. Filter by status, conversion_status, dates, link, offer or tracking click_id. Phone numbers are masked. | orders:read |
get_order | Get one order with its current status, call-center (operator) and courier notes, expected delivery_date and tracking values. | orders:read |
create_order | Submit a REAL customer order (lead) for one of the partner's links. A call center will phone this customer. ONLY call this with a real customer's explicit request and their correct Uzbekistan phone number. Never use for testing. The same phone cannot order the same offer twice within 24 hours. Pass idempotency_key to make retries safe. | orders:write |
get_stats | Aggregated performance: orders, approved, delivered, rejected, approval/delivery rates, earned and potential earnings (UZS), grouped by day, offer or link. Default range: last 30 days. | stats:read |
list_events | Feed of order events (order.created, order.status_changed) in chronological order. Pass the returned meta.next_cursor as `after` to get only new events since the last call. | events:read |
get_insights | The partner's personal AI manager view: yesterday's orders/approved/delivered/earnings, last 7 days vs previous week, each promoted offer with sale rate and stock, Meta campaigns (7 days), suggested better-selling offers the partner has not tried, and prioritized recommendations (losing_campaign, stock_out, low_sale_offer, try_offers, slowdown, can_withdraw...). Also returns this morning's brief text. | insights:read |
get_order_sale_chance | Estimate (0-100%) that one order will be delivered, with the factors behind it: offer sale rate, current stage, the customer's previous orders across the whole network, duplicates, rejected leads. | insights:read |
get_ads_status | Whether the partner's Meta (Facebook/Instagram) ad account is connected, its currency and campaign counts by status. | ads:read |
list_campaigns | The partner's Meta ad campaigns with daily budget, delivery status/problems and results for the period: spend, orders, approved, delivered, cost per order, net profit and likely revenue (UZS). | ads:read |
pause_campaign | Pause one of the partner's Meta campaigns right away (stops spending). The partner is notified. Use when a campaign is clearly losing money or its product is out of stock. | ads:write |
resume_campaign | Ask the partner to resume a paused campaign. Does NOT resume by itself: sends a confirmation card to the partner's Telegram bot and panel and returns an action (status pending_confirmation). Check later with get_action. | ads:write |
change_campaign_budget | Ask the partner to change a campaign's daily budget. Does NOT change it by itself: sends a confirmation card (Telegram + panel) and returns an action with status pending_confirmation. unit=UZS means the amount is in so'm; ACCOUNT means the ad account currency (often USD). | ads:write |
get_action | Status of a confirmation card created by resume_campaign or change_campaign_budget: pending, running, done, failed, cancelled or expired, with the result message. | ads:read |
Human in the loop
Agents can read Meta ad results and pause a campaign immediately (ads:write). Anything that can increase spending — resuming a campaign or changing its budget — is never executed by the agent: it creates a confirmation card that the partner approves in the Telegram bot or the panel (/dashboard/ai); the agent checks the result with get_action.
Prompts and resources
Ready-made prompts appear in the client's prompt menu: daily_review (yesterday's results and today's plan), find_offer, check_campaigns, order_problems. Resources: birinchishop://guide/business (business rules) and birinchishop://account/insights (AI manager overview).
Errors & limits
{"error": {"code": "duplicate_order", "message": "This phone number already has an order for this offer in the last 24 hours"},
"request_id": "req_Q2x…"}
Branch on error.code. Common codes: invalid_request, unauthorized, key_revoked, key_expired, insufficient_scope, ip_not_allowed, not_found, offer_inactive, out_of_stock, duplicate_order, customer_order_limit, orders_write_not_allowed, daily_order_limit, rate_limited, internal_error. Include request_id when contacting support.
Limits: 120 requests/minute per key; 30 orders/minute per account; 30 failed attempts per 15 minutes per IP. Responses include RateLimit-* headers; on 429 wait and retry.
Machine-readable: /api/v1/openapi.json · /api/v1/reference