API: Merchant (self-service)
MeerPartners business self-service API: offers, escrow balance and deposit, integration keys, analytics, publishers and grants, profile, postback test.
The /api/v1/merchant/* group is the business cabinet via API. All endpoints require a merchant JWT. The tenant_id is resolved only from membership in the database (anti-IDOR), never from the request body.
The X-Tenant-Id header
If your account owns multiple businesses, specify the active one on each request via the X-Tenant-Id: <id> header. The value is validated against your memberships. If you have a single business, the header is not needed. If you have multiple businesses and the header is not passed — 400 TENANT_HEADER_REQUIRED with the list of your tenants in details.tenants.
Roles: merchant_owner — full access (offer CRUD, keys, deposit, grants); merchant_member — read-only (GET). Revealing a key secret is for the owner only. Insufficient permission → 403 FORBIDDEN. List responses here have the shape { "data": [...], "total": N }.
Offers
GET /merchant/offers
/api/v1/merchant/offers🔒 Bearer JWTA list of the business's offers. Query: skip (0+), limit (1–200, default 50). Response — { "data": [...], "total": N }.
GET /merchant/offers/{offer_id}
/api/v1/merchant/offers/{offer_id}🔒 Bearer JWTA single offer card (see the response shape below).
POST /merchant/offers
/api/v1/merchant/offers🔒 Bearer JWTCreate an offer. A new offer goes to the pending_review status (platform moderation). merchant_owner only.
namestringrequiredmonetization_modelstringrequiredcpl, cpa, revshare or hybrid.descriptionstringoptionaldestination_urlstringoptionalgeoarrayoptionalcategorystringoptionalvisibilitystringoptionalpublic or private. Defaults to public.currencystringoptionalRUB.cpa_amountstringoptionalcpa/hybrid.cpl_amountstringoptionalcpl.revshare_percentstringoptionalrevshare/hybrid.hold_daysintegeroptionalattribution_window_hoursintegeroptionalmultilevel_enabledbooleanoptionalfalse.levels_enabledintegeroptionallevel_ratesarrayoptionalmax_total_commission_percentstringoptionalmax_total_commission_amountstringoptional{
"id": 501,
"tenant_id": 17,
"name": "Acme Premium",
"monetization_model": "cpa",
"currency": "RUB",
"visibility": "public",
"status": "pending_review",
"hold_days": 14,
"geo": ["RU", "KZ"],
"multilevel_enabled": true,
"active_tariff": {
"id": 700,
"monetization_model": "cpa",
"cpa_amount": "750.00",
"cpl_amount": null,
"revshare_percent": null,
"currency": "RUB",
"hold_days": 14,
"max_levels": 3,
"level_rates": { "1": "10", "2": "5", "3": "2" }
},
"rejection_reason": null,
"terms": {},
"created_at": "2026-06-14T12:00:00Z",
"updated_at": null
}PUT /merchant/offers/{offer_id}
/api/v1/merchant/offers/{offer_id}🔒 Bearer JWTEdit an offer. merchant_owner only. The body — the same fields as in creation, but all optional (pass only the ones you change). If the offer was rejected, rejection_reason in the response contains the reason.
Balance and deposit
GET /merchant/balance
/api/v1/merchant/balance🔒 Bearer JWTThe business's escrow balance with a ledger of movements. merchant_owner only. Query: currency (ISO code, default RUB).
currencystringrequiredavailablestringrequiredreservedstringrequiredtotal_depositedstringrequiredtotal_spentstringrequiredaccruals_pausedbooleanrequiredaccruals_paused_reasonstringoptionalledgerarrayoptional{
"currency": "RUB",
"available": "84000.00",
"reserved": "12000.00",
"total_deposited": "200000.00",
"total_spent": "104000.00",
"accruals_paused": false,
"accruals_paused_reason": null,
"ledger": [
{
"id": 9100,
"entry_type": "deposit",
"amount": "100000.00",
"balance_after": "184000.00",
"currency": "RUB",
"ref_type": "deposit_intent",
"ref_id": 55,
"created_at": "2026-06-10T10:00:00Z"
}
]
}POST /merchant/deposit
/api/v1/merchant/deposit🔒 Bearer JWTInitiate a top-up. Returns a pending intent — the actual crediting arrives via the provider's webhook. merchant_owner only. Success — 202 Accepted.
amountstringrequiredcurrencystringoptionalRUB.providerstringoptionalcard / crypto / manual / stub. Defaults to manual.{
"deposit_intent_id": 55,
"status": "pending",
"amount": "100000.00",
"currency": "RUB",
"provider": "card",
"provider_ref": "yk_2f9...",
"redirect_url": "https://pay.example.com/...",
"instructions": null
}Crediting happens via webhook
POST /merchant/deposit creates an intent but does not credit the money. The real top-up happens when the provider calls the platform's S2S webhook (POST /api/v1/merchant/deposit/webhook, signature X-Provider-Signature + X-Timestamp, dedup by provider_tx_id). This webhook is called by the provider, not by your integration.
Integration keys
The tenant_api_key is needed for S2S: signing the postback and token-exchange. Scopes: postback, sso, readonly.
GET /merchant/integration/keys
/api/v1/merchant/integration/keys🔒 Bearer JWTA list of keys. merchant_owner only. The secret is masked — only the tail secret_tail is visible.
{
"data": [
{
"id": 12,
"key_id": "ak_live_8d3f1a",
"name": "prod-postback",
"scopes": ["postback"],
"is_active": true,
"secret_tail": "9c1e",
"created_at": "2026-05-01T10:00:00Z",
"last_used_at": "2026-06-14T11:55:00Z",
"revoked_at": null
}
]
}POST /merchant/integration/keys
/api/v1/merchant/integration/keys🔒 Bearer JWTIssue a key. The secret is shown once in the response. merchant_owner only.
namestringrequiredscopesarrayrequiredpostback, sso, readonly.{
"id": 12,
"key_id": "ak_live_8d3f1a",
"name": "prod-postback",
"scopes": ["postback"],
"secret": "sk_live_a1b2c3d4e5f6...9c1e",
"secret_tail": "9c1e",
"warning": "Save the secret — it won't be shown again",
"created_at": "2026-06-14T12:00:00Z"
}POST /merchant/integration/keys/{key_id}/reveal
/api/v1/merchant/integration/keys/{key_id}/reveal🔒 Bearer JWTReveal the secret of an existing key. merchant_owner only.
{ "key_id": "ak_live_8d3f1a", "secret": "sk_live_a1b2c3...9c1e", "secret_tail": "9c1e" }POST /merchant/integration/keys/{key_id}/revoke
/api/v1/merchant/integration/keys/{key_id}/revoke🔒 Bearer JWTRevoke a key. merchant_owner only.
{ "status": "revoked" }POST /merchant/integration/test-postback
/api/v1/merchant/integration/test-postback🔒 Bearer JWTA test postback (a dry-run through the real flow) — verify the integration without a live conversion. merchant_owner only.
key_idstringrequiredkey_id of a key from integration/keys.external_order_idstringrequiredoffer_idintegerrequiredamountstringoptional1.00.currencystringoptionalRUB.event_typestringoptionalsale or lead. Defaults to sale.{
"accepted": true,
"duplicate": false,
"conversion_id": 778,
"error_code": null,
"error_message": null
}Analytics
GET /merchant/analytics
/api/v1/merchant/analytics🔒 Bearer JWTBusiness analytics: clicks, conversions, spend — in total and by offer. Query: date_from, date_to, currency (ISO code, default RUB).
total_clicksintegerrequiredtotal_conversionsintegerrequiredtotal_spentstringrequiredcurrencystringrequiredby_offerarrayoptional{
"date_from": "2026-06-01",
"date_to": "2026-06-14",
"total_clicks": 53200,
"total_conversions": 1180,
"total_spent": "884000.00",
"currency": "RUB",
"by_offer": [
{
"offer_id": 501,
"offer_name": "Acme Premium",
"clicks": 21000,
"conversions": 540,
"cr": "2.57",
"spent": "405000.00",
"epc": "19.29",
"currency": "RUB"
}
]
}Publishers and grants
GET /merchant/affiliates
/api/v1/merchant/affiliates🔒 Bearer JWTInfluencers working with the business's offers. Query: skip, limit (1–200, default 50).
{
"data": [
{
"affiliate_id": 88,
"display_name": "Anya",
"email": "creator@example.com",
"referral_code": "ANYA7K",
"private_access": [
{ "offer_id": 502, "offer_name": "Acme VIP", "status": "approved" }
],
"total_conversions": 96,
"total_clicks": 4210
}
],
"total": 34
}POST /merchant/affiliates/{affiliate_id}/grant
/api/v1/merchant/affiliates/{affiliate_id}/grant🔒 Bearer JWTGrant an influencer access to a private offer. merchant_owner only.
offer_idintegerrequired{ "status": "granted" }POST /merchant/affiliates/{affiliate_id}/revoke
/api/v1/merchant/affiliates/{affiliate_id}/revoke🔒 Bearer JWTRevoke access to a private offer. merchant_owner only. The body — { "offer_id": <int> }.
{ "status": "revoked" }Profile
GET /merchant/profile
/api/v1/merchant/profile🔒 Bearer JWTThe business profile: company and contacts.
company_namestringoptionalcontact_emailstringoptionalwebsite_urlstringoptional{ "company_name": "Acme LLC", "contact_email": "ops@acme.com", "website_url": "https://acme.com" }PUT /merchant/profile
/api/v1/merchant/profile🔒 Bearer JWTUpdate the business profile. merchant_owner only.
company_namestringoptionalcontact_emailstringoptionalwebsite_urlstringoptionalReturns the updated profile.
Team and auto top-up
This same group includes team management (/merchant/team, invites /merchant/team/invites) and auto top-up settings (/merchant/deposit/auto-settings). Auto top-up is marked as "coming soon" and does not yet trigger real payments — see the cabinet dashboard. Team management is described in the Profile and team guide.