MMeerPartners docs

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

GET/api/v1/merchant/offers🔒 Bearer JWT

A list of the business's offers. Query: skip (0+), limit (1–200, default 50). Response — { "data": [...], "total": N }.

GET /merchant/offers/{offer_id}

GET/api/v1/merchant/offers/{offer_id}🔒 Bearer JWT

A single offer card (see the response shape below).

POST /merchant/offers

POST/api/v1/merchant/offers🔒 Bearer JWT

Create an offer. A new offer goes to the pending_review status (platform moderation). merchant_owner only.

namestringrequired
Name (1–200 characters).
monetization_modelstringrequired
Model: cpl, cpa, revshare or hybrid.
descriptionstringoptional
Description (up to 2000 characters).
destination_urlstringoptional
Destination URL (landing), up to 2048 characters.
geoarrayoptional
List of 2-letter country codes.
categorystringoptional
Category (up to 64 characters).
visibilitystringoptional
public or private. Defaults to public.
currencystringoptional
Offer currency (ISO code). Defaults to RUB.
cpa_amountstringoptional
CPA rate (NUMERIC string). For cpa/hybrid.
cpl_amountstringoptional
CPL rate (NUMERIC string). For cpl.
revshare_percentstringoptional
RevShare percentage (NUMERIC string). For revshare/hybrid.
hold_daysintegeroptional
Hold period in days (0–180, default 14).
attribution_window_hoursintegeroptional
Attribution window in hours (1–8760, default 720).
multilevel_enabledbooleanoptional
Enable multi-level. Defaults to false.
levels_enabledintegeroptional
Number of levels (1–7, default 3).
level_ratesarrayoptional
Per-level rates (a list of NUMERIC strings).
max_total_commission_percentstringoptional
CAP on total commission as a percentage.
max_total_commission_amountstringoptional
CAP on total commission as an absolute amount.
201 response
{
  "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}

PUT/api/v1/merchant/offers/{offer_id}🔒 Bearer JWT

Edit 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

GET/api/v1/merchant/balance🔒 Bearer JWT

The business's escrow balance with a ledger of movements. merchant_owner only. Query: currency (ISO code, default RUB).

currencystringrequired
Balance currency.
availablestringrequired
Available (what accruals are paid from).
reservedstringrequired
Reserved for hold accruals.
total_depositedstringrequired
Total deposited.
total_spentstringrequired
Total spent.
accruals_pausedbooleanrequired
Whether accruals are paused (low balance).
accruals_paused_reasonstringoptional
Reason accruals are paused.
ledgerarrayoptional
The latest balance movements.
200 response
{
  "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

POST/api/v1/merchant/deposit🔒 Bearer JWT

Initiate a top-up. Returns a pending intent — the actual crediting arrives via the provider's webhook. merchant_owner only. Success — 202 Accepted.

amountstringrequired
Amount (NUMERIC string, > 0).
currencystringoptional
Currency (ISO code). Defaults to RUB.
providerstringoptional
Provider: card / crypto / manual / stub. Defaults to manual.
202 response
{
  "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

GET/api/v1/merchant/integration/keys🔒 Bearer JWT

A list of keys. merchant_owner only. The secret is masked — only the tail secret_tail is visible.

200 response
{
  "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

POST/api/v1/merchant/integration/keys🔒 Bearer JWT

Issue a key. The secret is shown once in the response. merchant_owner only.

namestringrequired
Key name (1–100 characters).
scopesarrayrequired
Scopes: a non-empty list of postback, sso, readonly.
201 response
{
  "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

POST/api/v1/merchant/integration/keys/{key_id}/reveal🔒 Bearer JWT

Reveal the secret of an existing key. merchant_owner only.

200 response
{ "key_id": "ak_live_8d3f1a", "secret": "sk_live_a1b2c3...9c1e", "secret_tail": "9c1e" }

POST /merchant/integration/keys/{key_id}/revoke

POST/api/v1/merchant/integration/keys/{key_id}/revoke🔒 Bearer JWT

Revoke a key. merchant_owner only.

200 response
{ "status": "revoked" }

POST /merchant/integration/test-postback

POST/api/v1/merchant/integration/test-postback🔒 Bearer JWT

A test postback (a dry-run through the real flow) — verify the integration without a live conversion. merchant_owner only.

key_idstringrequired
The key_id of a key from integration/keys.
external_order_idstringrequired
A test order identifier (1–120 characters).
offer_idintegerrequired
Offer ID.
amountstringoptional
Amount. Defaults to 1.00.
currencystringoptional
Currency. Defaults to RUB.
event_typestringoptional
sale or lead. Defaults to sale.
200 response
{
  "accepted": true,
  "duplicate": false,
  "conversion_id": 778,
  "error_code": null,
  "error_message": null
}

Analytics

GET /merchant/analytics

GET/api/v1/merchant/analytics🔒 Bearer JWT

Business analytics: clicks, conversions, spend — in total and by offer. Query: date_from, date_to, currency (ISO code, default RUB).

total_clicksintegerrequired
Total clicks.
total_conversionsintegerrequired
Total conversions.
total_spentstringrequired
Total spend (NUMERIC).
currencystringrequired
Currency.
by_offerarrayoptional
Breakdown by offer (clicks, conversions, cr, spent, epc).
200 response
{
  "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

GET/api/v1/merchant/affiliates🔒 Bearer JWT

Influencers working with the business's offers. Query: skip, limit (1–200, default 50).

200 response
{
  "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

POST/api/v1/merchant/affiliates/{affiliate_id}/grant🔒 Bearer JWT

Grant an influencer access to a private offer. merchant_owner only.

offer_idintegerrequired
ID of the private offer.
200 response
{ "status": "granted" }

POST /merchant/affiliates/{affiliate_id}/revoke

POST/api/v1/merchant/affiliates/{affiliate_id}/revoke🔒 Bearer JWT

Revoke access to a private offer. merchant_owner only. The body — { "offer_id": <int> }.

200 response
{ "status": "revoked" }

Profile

GET /merchant/profile

GET/api/v1/merchant/profile🔒 Bearer JWT

The business profile: company and contacts.

company_namestringoptional
Company name.
contact_emailstringoptional
Contact email.
website_urlstringoptional
Website.
200 response
{ "company_name": "Acme LLC", "contact_email": "ops@acme.com", "website_url": "https://acme.com" }

PUT /merchant/profile

PUT/api/v1/merchant/profile🔒 Bearer JWT

Update the business profile. merchant_owner only.

company_namestringoptional
Company name (1–255 characters).
contact_emailstringoptional
Contact email (up to 255 characters).
website_urlstringoptional
Website (up to 2048 characters).

Returns 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.

What's next