Birinchishop Partner API

REST API, webhooks, S2S postbacks and an MCP server for partners, ad trackers and AI agents. Version 2026-10-23.

O'zbekcha: bu sahifa reklamachilar va dasturchilar uchun. API kalitini panelda Dashboard → API bo'limida yarating. Kalitni hech kimga bermang va sayt/ilova kodiga yozmang — faqat serveringizda saqlang. Savollar: @birinchishop_support.

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.

ScopeAllows
account:readRead profile and balance
offers:readRead the offers catalog
links:readRead tracking links
links:writeCreate tracking links
orders:readRead orders and their statuses
orders:writeSubmit orders (leads)
stats:readRead statistics
events:readRead the event feed (order status changes)
webhooks:manageManage webhook/postback endpoints
insights:readRead AI manager recommendations and order sale chance
ads:readRead Meta ad campaigns and their results
ads:writePause 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

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.

PathDescriptionScope
GET/meAccount, balance and key infoaccount:read
GET/offersSearch active offersoffers:read
GET/offers/{offer_id}Offer details with variants and your linksoffers:read
GET/linksList your tracking linkslinks:read
POST/linksCreate a tracking linklinks:write
GET/links/{link_id}One tracking linklinks:read
GET/ordersList your ordersorders:read
POST/ordersSubmit 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 orderorders:read
GET/statsAggregated statisticsstats:read
GET/eventsOrder 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-chanceProbability 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/insightsAI 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/statusMeta ad account connectionads:read
GET/ads/campaignsMeta campaigns with resultsads:read
POST/ads/campaigns/{campaign_id}/pausePause a campaign (immediate)ads:write
POST/ads/campaigns/{campaign_id}/resumeRequest to resume (needs partner confirmation)ads:write
POST/ads/campaigns/{campaign_id}/budgetRequest a daily budget change (needs partner confirmation)ads:write
GET/actions/{action_id}Status of a confirmation requestads:read
GET/webhooksList webhooks/postbackswebhooks:manage
POST/webhooksAdd 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 webhookwebhooks:manage
DELETE/webhooks/{webhook_id}Delete a webhookwebhooks:manage
POST/webhooks/{webhook_id}/testSend a sample event nowwebhooks:manage
GET/webhooks/{webhook_id}/deliveriesRecent delivery attemptswebhooks:manage
POST/mcpMCP 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/referenceEnums, scopes, statuses and business rules—

Order statuses

statusMeaning
newOrder received, waiting for the call center.
processingCall center is processing the order.
callbackCustomer asked to be called back later.
confirmedCustomer confirmed the order by phone.
packedOrder is packed at the warehouse.
shippingOrder is on the way to the customer.
deliveredCustomer received and paid for the product. Payout is earned.
returnedCustomer refused the product at delivery; it was returned.
cancelledOrder was cancelled (not confirmed, duplicate, unreachable customer, etc.). No payout.

conversion_status (for trackers)

valueMeaning
leadOrder received, not yet confirmed (new, processing, callback).
approvedConfirmed by the customer, not yet delivered (confirmed, packed, shipping).
saleDelivered and paid by the customer. Payout earned.
rejectedReturned 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}&currency={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}"
      }
    }
  }
}
ToolDescriptionScope
get_accountGet the partner account: name, status, current balance (UZS), frozen balance, minimum withdrawal and the API key scopes.account:read
search_offersSearch 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_offerGet 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_linksList 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_linkCreate 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_ordersList 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_orderGet one order with its current status, call-center (operator) and courier notes, expected delivery_date and tracking values.orders:read
create_orderSubmit 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_statsAggregated 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_eventsFeed 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_insightsThe 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_chanceEstimate (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_statusWhether the partner's Meta (Facebook/Instagram) ad account is connected, its currency and campaign counts by status.ads:read
list_campaignsThe 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_campaignPause 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_campaignAsk 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_budgetAsk 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_actionStatus 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