S
docs.syncra.money
Account

Merchant profile and limits (My Merchant)

Merchant self-service profile: deal limits (min/max/daily), kill_switch, webhook URL — via the cabinet JWT

Merchant profile and limits (My Merchant)

A pair of self-service cabinet endpoints: the merchant reads and updates its own profile without admin rights.

Authentication. GET is dual-homed: the cabinet JWT (Authorization: Bearer <access_token> from a cabinet login) or the merchant's HMAC pair (X-Merchant-Token / X-Timestamp / X-Signature, scope — from the signed token). PATCH — cabinet JWT only (server-to-server profile mutations over HMAC are not supported). The path is outside the merchant prefix: /api/v1/p2p/my-merchant.


Retrieving the profile

GET /api/v1/p2p/my-merchant
Authorization: Bearer <access_token>

Response example (200 OK, live staging response)

{
  "merchant": {
    "id": "3e52c59c-3013-4efa-a2db-70cef2adc7ab",
    "name": "Merchant TC Sandbox",
    "email": "[email protected]",
    "status": "ACTIVE",
    "kill_switch": false,
    "balance_usdt": "95982",
    "escrow_usdt": "0",
    "trc20_address": "TCBYf3t6D2VshYVQLh55GGBDWTZvjQRoHd",
    "trc20_usdt_balance": "0",
    "trc20_trx_balance": "0",
    "project_url": "",
    "checkout_brand_name": "",
    "success_redirect_url": "",
    "fail_redirect_url": "",
    "finance_chat_id": "",
    "appeal_chat_id": "",
    "ip_whitelist": [],
    "api_token": "8102965ace9cbdcd88fb0fb857375c9b3d812327e8871a58d25a79a29d0cba7f",
    "withdrawal_address": "",
    "moderation_status": "APPROVED",
    "api_version": "v2",
    "webhook_url": "http://mock-merchant-e2e.syncra-stage.svc.cluster.local:18098/webhook",
    "created_at": "2026-08-22T17:12:02Z",
    "updated_at": "2026-08-23T18:58:54Z",
    "kill_switch_reason": ""
  },
  "auto_withdrawal_mode": "AUTO_UP_TO_LIMIT",
  "auto_withdrawal_effective_limit_usdt_cents": "10000000000"
}

Key fields

FieldTypeDescription
kill_switchboolEmergency block of merchant activity. Toggled by the platform operator (admin endpoints /disable//enable, unavailable to the merchant's HMAC/JWT sessions); the merchant sees the status in the cabinet.
kill_switch_reasonstringThe reason the platform kill switch is on — the cabinet banner shows it. Empty string when kill_switch=false or when the operator did not provide a reason. Read-only for the merchant.
deal_min_amountint64 (optional)The merchant's own minimum per deal (minor units, kopecks). Absent = no minimum.
deal_max_amountint64 (optional)The merchant's own maximum per deal. Absent = no maximum.
daily_deal_amount_limitint64 (optional)Daily (UTC) cap on combined PayIn+PayOut volume. Absent = no cap set.
api_tokenstringThe public API token (rotated via api-keys).
auto_withdrawal_modestringCrypto withdrawal policy: AUTO_UP_TO_LIMIT or MANUAL_ONLY (read-only).
auto_withdrawal_effective_limit_usdt_centsint64The auto-withdrawal trigger threshold in USDT cents (0 = everything manual).

The limit fields (deal_min_amount / deal_max_amount / daily_deal_amount_limit) appear in the response only when set (proto3 optional): once a limit is removed, the field disappears from the JSON instead of becoming zero.


Updating the profile and limits

PATCH /api/v1/p2p/my-merchant
Authorization: Bearer <access_token>
Content-Type: application/json

Partial update: pass only the fields you are changing. The mutable fields are name, webhook_url, withdrawal_address, success_redirect_url, fail_redirect_url, checkout_brand_name, finance_chat_id, appeal_chat_id, ip_whitelist (a JSON array as a string) and the three deal limits. Status, balances, and fees cannot be changed via self-service (admin-only).

Pointer semantics for limits

Limit values are int64 in minor units (kopecks):

Passed in PATCHEffect
field omittedthe limit is unchanged
0the limit is removed (NULL = no limit)
> 0the limit is set (for example, "100000" = 1,000.00 RUB)

The min/max pair stays consistent: you cannot set a maximum below the active minimum.

Request example (set a 1,000.00 minimum and a 100,000.00 daily cap)

{
  "deal_min_amount": "100000",
  "daily_deal_amount_limit": "10000000"
}

Response example (200 OK)

Returns the full merchant object (same structure as in GET); the set limits are visible in the deal_min_amount / daily_deal_amount_limit fields.


Deal creation errors on limit violations

Limits are checked on POST /deals/payin and POST /deals/payout (live staging responses; see Error codes):

400 INVALID_ARGUMENT — minimum/maximum

{
  "type": "about:blank",
  "title": "Bad Request",
  "status": 400,
  "detail": "{\"code\":3,\"message\":\"deal amount is below the merchant minimum: 50000 is below the configured minimum 100000\",\"details\":[]}",
  "instance": "/api/v1/p2p-engine"
}
{
  "type": "about:blank",
  "title": "Bad Request",
  "status": 400,
  "detail": "{\"code\":3,\"message\":\"deal amount is above the merchant maximum: 150000 is above the configured maximum 100000\",\"details\":[]}",
  "instance": "/api/v1/p2p-engine"
}

400 FAILED_PRECONDITION — daily volume

{
  "type": "about:blank",
  "title": "Bad Request",
  "status": 400,
  "detail": "{\"code\":9,\"message\":\"merchant daily deal volume limit exceeded: today's volume 793921 + requested 150000 exceeds the configured daily cap 100000\",\"details\":[]}",
  "instance": "/api/v1/p2p-engine"
}

The daily volume is computed over UTC from the same UNION set as turnover (without EXPIRED/SPAM_REJECTED/ FAILED).


  • Error codes — the 400 rule for all "wrong state" classes.
  • Deal turnover — how the daily volume is computed.
  • Callbacks — setting up webhook_url and webhook signatures.

On this page