{"openapi":"3.1.0","info":{"title":"Novix SMS API","version":"1.0.0","description":"SMS OTP reseller platform API (customer + admin).\n\n**Conventions**\n- All responses are enveloped: `{ ok: true, data, meta? }` or `{ ok: false, error: { code, message, correlationId } }`.\n- Every response carries `x-correlation-id`; pass it in support requests.\n- Money is exchanged as integer minor units (`amountMinor`) plus a display string; the server is authoritative.\n- Purchases are idempotent per user via `idempotencyKey`; provider timeouts become `UNCERTAIN` orders that reconciliation resolves — never silent double charges or automatic refunds.\n- Rate limits are per IP + per account; `429` includes `Retry-After`.\n\n**Branding note** — this platform is an independent reseller product. Upstream SMSPool APIs are used through a server-side adapter and are never exposed to clients."},"servers":[{"url":"https://novix-sms-next-js.vercel.app","description":"Production (Vercel)"},{"url":"http://localhost:3000","description":"Local development"}],"tags":[{"name":"System","description":"Health, provider status and the contract itself"},{"name":"Auth","description":"Registration, sign-in, rotating sessions, password reset"},{"name":"Account","description":"Profile, password, notifications"},{"name":"Catalog","description":"Countries and services available upstream"},{"name":"Pricing","description":"Margin-adjusted, server-computed prices"},{"name":"Orders","description":"Purchases, OTP retrieval, cancellation, live status"},{"name":"Wallet","description":"Balances and the immutable ledger"},{"name":"Deposits","description":"Manual bKash/Nagad top-ups with admin verification"},{"name":"Payments","description":"Signed gateway webhooks"},{"name":"Support","description":"Customer tickets"},{"name":"Admin: Users","description":"User administration"},{"name":"Admin: Orders","description":"Order operations and reconciliation"},{"name":"Admin: Deposits","description":"Deposit approval queue"},{"name":"Admin: Pricing","description":"Margin rules and catalog sync"},{"name":"Admin: Tickets","description":"Support queue for staff"},{"name":"Admin: Insights","description":"Metrics and audit trail"}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Access token from POST /api/v1/auth/login. Mobile clients keep it in secure storage (Keychain/Keystore); it expires in minutes and is refreshed with the rotating refresh token."},"cookieAuth":{"type":"apiKey","in":"cookie","name":"vp_access","description":"HttpOnly access cookie used by the web app. Same-origin requests only; CSRF-protected by origin checks."}},"schemas":{"Money":{"type":["object","null"],"properties":{"amount":{"type":"string","description":"Decimal string — display only","example":"1.13"},"amountMinor":{"type":"string","description":"Integer minor units — authoritative","example":"113"},"currency":{"type":"string","example":"USD"}},"required":["amount","amountMinor","currency"]},"ProviderHealth":{"type":"object","properties":{"mode":{"type":"string","enum":["LIVE","MOCK"]},"configured":{"type":"boolean"},"reachable":{"type":"boolean"},"balance":{"$ref":"#/components/schemas/Money"},"latencyMs":{"type":"integer"},"detail":{"type":"string"},"checkedAt":{"type":"string","format":"date-time"}},"required":["mode","configured","reachable","checkedAt"]},"ErrorBody":{"type":"object","properties":{"code":{"type":"string","example":"VALIDATION_ERROR"},"message":{"type":"string","example":"countryId: Invalid uuid"},"details":{},"correlationId":{"type":"string","example":"b1f2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d"}},"required":["code","message","correlationId"]},"ErrorEnvelope":{"type":"object","properties":{"ok":{"type":"boolean","enum":[false]},"error":{"$ref":"#/components/schemas/ErrorBody"}},"required":["ok","error"]},"Me":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"email":{"type":"string","format":"email"},"displayName":{"type":["string","null"]},"status":{"type":"string","enum":["ACTIVE","SUSPENDED","CLOSED"]},"roles":{"type":"array","items":{"type":"string"},"example":["CUSTOMER"]},"permissions":{"type":"array","items":{"type":"string"}},"purchaseLimitDaily":{"type":["integer","null"]},"notes":{"type":["string","null"]},"createdAt":{"type":"string","format":"date-time"}},"required":["id","email","displayName","status","roles","permissions","createdAt"]},"Notification":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"type":{"type":"string","example":"ORDER_COMPLETED"},"title":{"type":"string"},"body":{"type":"string"},"readAt":{"type":["string","null"],"format":"date-time"},"createdAt":{"type":"string","format":"date-time"}},"required":["id","type","title","body","readAt","createdAt"]},"Pagination":{"type":"object","properties":{"page":{"type":"integer","example":1},"pageSize":{"type":"integer","example":20},"total":{"type":"integer","example":140},"totalPages":{"type":"integer","example":7}},"required":["page","pageSize","total","totalPages"]},"Meta":{"type":"object","properties":{"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["pagination"]},"Country":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string","example":"United States"},"shortName":{"type":"string","example":"US"},"flag":{"type":"string","example":"🇺🇸"},"providerId":{"type":"integer"}},"required":["id","name","shortName"]},"Service":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string","example":"WhatsApp"},"providerId":{"type":"integer"}},"required":["id","name"]},"PricingRow":{"type":"object","properties":{"countryId":{"type":"string","format":"uuid"},"serviceId":{"type":"string","format":"uuid"},"countryProviderId":{"type":"integer"},"serviceProviderId":{"type":"integer"},"countryName":{"type":"string"},"serviceName":{"type":"string"},"shortName":{"type":"string"},"flag":{"type":"string"},"sellPrice":{"allOf":[{"$ref":"#/components/schemas/Money"},{"type":"object"}]},"successRate":{"type":["number","null"],"description":"Provider success rate 0..100 when known"}},"required":["countryId","serviceId","countryProviderId","serviceProviderId","countryName","serviceName","shortName","flag","sellPrice","successRate"]},"Quote":{"type":"object","properties":{"countryId":{"type":"string","format":"uuid"},"serviceId":{"type":"string","format":"uuid"},"countryName":{"type":"string"},"serviceName":{"type":"string"},"shortName":{"type":"string"},"countryProviderId":{"type":"integer"},"serviceProviderId":{"type":"integer"},"poolProviderId":{"type":"integer"},"upstreamPrice":{"allOf":[{"$ref":"#/components/schemas/Money"},{"type":"object"}]},"sellPrice":{"allOf":[{"$ref":"#/components/schemas/Money"},{"type":"object"}]},"margin":{"allOf":[{"$ref":"#/components/schemas/Money"},{"type":"object"}]},"available":{"type":"boolean"},"stock":{"type":["integer","null"]},"successRate":{"type":["number","null"]},"pricingRule":{"type":["object","null"],"properties":{"scope":{"type":"string"},"ruleType":{"type":"string"},"value":{"type":"string"},"priority":{"type":"integer"}},"required":["scope","ruleType","value","priority"]},"providerMode":{"type":"string","enum":["LIVE","MOCK"]},"warnings":{"type":"array","items":{"type":"string"},"example":["Mock provider: no real SMS is delivered"]}},"required":["countryId","serviceId","countryName","serviceName","shortName","countryProviderId","serviceProviderId","poolProviderId","upstreamPrice","sellPrice","margin","available","stock","successRate","pricingRule","providerMode","warnings"]},"OrderStatus":{"type":"string","enum":["PENDING","WAITING_FOR_SMS","CODE_RECEIVED","COMPLETED","CANCELLED","REFUNDED","EXPIRED","FAILED","UNCERTAIN"]},"OrderStatusEvent":{"type":"object","properties":{"fromStatus":{"type":["string","null"]},"toStatus":{"type":"string"},"source":{"type":"string","enum":["SYSTEM","USER","ADMIN","PROVIDER_POLL","PROVIDER_ON_DEMAND","JOB"]},"reason":{"type":["string","null"]},"createdAt":{"type":"string","format":"date-time"}},"required":["fromStatus","toStatus","source","reason","createdAt"]},"Order":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"publicRef":{"type":"string","example":"NX-4JA6YA"},"status":{"$ref":"#/components/schemas/OrderStatus"},"country":{"$ref":"#/components/schemas/Country"},"service":{"$ref":"#/components/schemas/Service"},"phoneNumber":{"type":["string","null"],"description":"List view masks all but the last 4 digits"},"phoneCc":{"type":["string","null"]},"sellPrice":{"allOf":[{"$ref":"#/components/schemas/Money"},{"type":"object"}]},"expiresAt":{"type":["string","null"],"format":"date-time"},"createdAt":{"type":"string","format":"date-time"},"completedAt":{"type":["string","null"],"format":"date-time"},"providerMode":{"type":"string","enum":["LIVE","MOCK"]},"otpCode":{"type":["string","null"],"description":"Owner/support only; never logged, never in exports"},"otpFullText":{"type":["string","null"]},"statusHistory":{"type":"array","items":{"$ref":"#/components/schemas/OrderStatusEvent"}},"refund":{"type":["object","null"],"properties":{"amount":{"allOf":[{"$ref":"#/components/schemas/Money"},{"type":"object"}]},"status":{"type":"string"},"reason":{"type":["string","null"]}},"required":["amount","status","reason"]}},"required":["id","publicRef","status","country","service","phoneNumber","phoneCc","sellPrice","expiresAt","createdAt","completedAt","providerMode","otpCode","otpFullText","statusHistory","refund"]},"Wallet":{"type":"object","properties":{"currency":{"type":"string","example":"USD"},"available":{"allOf":[{"$ref":"#/components/schemas/Money"},{"type":"object"}]},"reserved":{"allOf":[{"$ref":"#/components/schemas/Money"},{"type":"object"}]},"total":{"allOf":[{"$ref":"#/components/schemas/Money"},{"type":"object"}]},"updatedAt":{"type":"string","format":"date-time"}},"required":["currency","available","reserved","total","updatedAt"]},"WalletTransaction":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"ref":{"type":"string","example":"WT-20261013-PUR-FBBCA4"},"category":{"type":"string","enum":["DEPOSIT_CREDIT","PURCHASE_RESERVE","PURCHASE_DEBIT","PURCHASE_RELEASE","REFUND_CREDIT","ADJUSTMENT"]},"amount":{"type":"string"},"amountMinor":{"type":"string"},"availableDelta":{"type":"string"},"reservedDelta":{"type":"string"},"balanceAfterAvailable":{"type":"string"},"currency":{"type":"string"},"orderId":{"type":["string","null"],"format":"uuid"},"depositId":{"type":["string","null"],"format":"uuid"},"reason":{"type":["string","null"]},"createdAt":{"type":"string","format":"date-time"}},"required":["id","ref","category","amount","amountMinor","availableDelta","reservedDelta","balanceAfterAvailable","currency","orderId","depositId","reason","createdAt"]},"DepositMethods":{"type":"object","properties":{"currency":{"type":"string"},"limits":{"type":"object","properties":{"min":{"type":"string"},"max":{"type":"string"},"minMinor":{"type":"string"},"maxMinor":{"type":"string"}},"required":["min","max","minMinor","maxMinor"]},"expiryHours":{"type":"integer"},"manualMethods":{"type":"array","items":{"type":"object","properties":{"method":{"type":"string","enum":["MANUAL_BKASH","MANUAL_NAGAD"]},"label":{"type":"string"},"accountNumber":{"type":"string","description":"Merchant number the customer sends money to"},"accountType":{"type":"string"},"steps":{"type":"array","items":{"type":"string"}}},"required":["method","label","accountNumber","accountType","steps"]}},"gateway":{"type":"object","properties":{"enabled":{"type":"boolean"},"id":{"type":"string"},"reason":{"type":"string"}},"required":["enabled","id"]}},"required":["currency","limits","expiryHours","manualMethods"]},"Deposit":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"ref":{"type":"string","example":"DP-WBKTHU"},"method":{"type":"string","enum":["MANUAL_BKASH","MANUAL_NAGAD","GATEWAY"]},"amount":{"type":"string"},"amountMinor":{"type":"string"},"currency":{"type":"string"},"status":{"type":"string","enum":["PENDING","APPROVED","REJECTED","EXPIRED"]},"senderRef":{"type":["string","null"]},"reviewNote":{"type":["string","null"]},"createdAt":{"type":"string","format":"date-time"},"expiresAt":{"type":["string","null"],"format":"date-time"},"paidAt":{"type":["string","null"],"format":"date-time"}},"required":["id","ref","method","amount","amountMinor","currency","status","senderRef","reviewNote","createdAt","expiresAt","paidAt"]},"Ticket":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"ref":{"type":["string","null"]},"subject":{"type":"string"},"category":{"type":"string","enum":["ORDER","PAYMENT","ACCOUNT","OTHER"]},"priority":{"type":"string","enum":["LOW","NORMAL","HIGH","URGENT"]},"status":{"type":"string","enum":["OPEN","PENDING_CUSTOMER","ASSIGNED","RESOLVED","CLOSED"]},"orderId":{"type":["string","null"],"format":"uuid"},"assignedToId":{"type":["string","null"],"format":"uuid"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"messages":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"authorType":{"type":"string","enum":["CUSTOMER","STAFF","SYSTEM"]},"authorId":{"type":["string","null"],"format":"uuid"},"body":{"type":"string"},"createdAt":{"type":"string","format":"date-time"}},"required":["id","authorType","body","createdAt"]}}},"required":["id","subject","category","priority","status","createdAt"]},"AdminUserRow":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"email":{"type":"string","format":"email"},"displayName":{"type":["string","null"]},"status":{"type":"string","enum":["ACTIVE","SUSPENDED","CLOSED"]},"roles":{"type":"array","items":{"type":"string"}},"purchaseLimitDaily":{"type":["integer","null"]},"wallet":{"type":"object","properties":{"available":{"allOf":[{"$ref":"#/components/schemas/Money"},{"type":"object"}]},"reserved":{"allOf":[{"$ref":"#/components/schemas/Money"},{"type":"object"}]}},"required":["available","reserved"]},"orders":{"type":"integer"},"createdAt":{"type":"string","format":"date-time"}},"required":["id","email","displayName","status","roles","purchaseLimitDaily","createdAt"]},"AdminOrderRow":{"allOf":[{"$ref":"#/components/schemas/Order"},{"type":"object","properties":{"upstreamOrderId":{"type":["string","null"],"description":"Provider order id — masked in list views"},"userId":{"type":"string","format":"uuid"},"userEmail":{"type":"string","format":"email"},"upstreamPrice":{"allOf":[{"$ref":"#/components/schemas/Money"},{"type":"object"}]}},"required":["upstreamOrderId","userId"]}]},"ReconcileResult":{"type":"object","properties":{"resolved":{"type":"string","enum":["REFRESHED","COMPLETED","REFUNDED","RELEASED","NEEDS_REVIEW","NOOP"]},"status":{"$ref":"#/components/schemas/OrderStatus"},"detail":{"type":"string"}},"required":["resolved","status","detail"]},"AdminDepositRow":{"allOf":[{"$ref":"#/components/schemas/Deposit"},{"type":"object","properties":{"userId":{"type":"string","format":"uuid"},"userEmail":{"type":"string","format":"email"},"reviewedById":{"type":["string","null"],"format":"uuid"}},"required":["userId"]}]},"PricingRule":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"scope":{"type":"string","enum":["GLOBAL","COUNTRY","SERVICE","COUNTRY_SERVICE"]},"countryId":{"type":["string","null"],"format":"uuid"},"serviceId":{"type":["string","null"],"format":"uuid"},"ruleType":{"type":"string","enum":["PERCENT_BPS","FIXED_MINOR"]},"value":{"type":"string"},"minSellPriceMinor":{"type":["string","null"]},"minMarginBps":{"type":["integer","null"]},"priority":{"type":"integer"},"active":{"type":"boolean"},"createdAt":{"type":"string","format":"date-time"}},"required":["id","scope","countryId","serviceId","ruleType","value","minSellPriceMinor","minMarginBps","priority","active","createdAt"]},"AdminTicketRow":{"allOf":[{"$ref":"#/components/schemas/Ticket"},{"type":"object","properties":{"userEmail":{"type":"string","format":"email"}}}]},"AdminDashboard":{"type":"object","properties":{"users":{"type":"object","properties":{"total":{"type":"integer"},"active":{"type":"integer"},"suspended":{"type":"integer"}},"required":["total","active","suspended"]},"orders":{"type":"object","properties":{"total":{"type":"integer"},"last24h":{"type":"integer"},"byStatus":{"type":"object","additionalProperties":{"type":"integer"}},"uncertain":{"type":"integer"}},"required":["total","last24h","byStatus","uncertain"]},"finance":{"type":"object","properties":{"currency":{"type":"string"},"walletLiability":{"allOf":[{"$ref":"#/components/schemas/Money"},{"type":"object"}]},"reserved":{"allOf":[{"$ref":"#/components/schemas/Money"},{"type":"object"}]},"depositsPending":{"type":"integer"},"revenueMinor":{"type":"string"},"costMinor":{"type":"string"},"marginMinor":{"type":"string"}},"required":["currency","walletLiability","reserved","depositsPending","revenueMinor","costMinor","marginMinor"]},"provider":{"type":"object","properties":{"mode":{"type":"string","enum":["LIVE","MOCK"]},"lastCheck":{"type":["string","null"],"format":"date-time"}},"required":["mode","lastCheck"]}},"required":["users","orders","finance","provider"]},"AuditLogRow":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"action":{"type":"string"},"actorId":{"type":["string","null"],"format":"uuid"},"actorEmail":{"type":["string","null"]},"targetType":{"type":["string","null"]},"targetId":{"type":["string","null"]},"reason":{"type":["string","null"]},"redactedMeta":{"type":["string","null"]},"correlationId":{"type":["string","null"]},"ip":{"type":["string","null"]},"createdAt":{"type":"string","format":"date-time"}},"required":["id","action","actorId","targetType","targetId","reason","redactedMeta","correlationId","ip","createdAt"]}},"parameters":{}},"paths":{"/api/v1/health":{"get":{"tags":["System"],"summary":"Liveness/readiness probe","description":"Returns `ok` or `degraded` plus database/provider checks. Safe to wire to a container healthcheck or uptime monitor. Public.","security":[],"responses":{"200":{"description":"Service status","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"status":{"type":"string","enum":["ok","degraded"]},"checks":{"type":"object","additionalProperties":{"type":"string"}},"time":{"type":"string","format":"date-time"}},"required":["status","time"]}},"required":["ok","data"]}}}}}}},"/api/v1/openapi":{"get":{"tags":["System"],"summary":"This OpenAPI document","description":"Machine-readable contract generated from the live zod validation schemas. Import into Postman/Insomnia/Stoplight.","security":[],"responses":{"200":{"description":"OpenAPI 3.1 document","content":{"application/json":{"schema":{"type":"object","additionalProperties":{}}}}}}}},"/api/v1/provider/health":{"get":{"tags":["System"],"summary":"Upstream provider health","description":"Performs a live balance call against SMSPool when credentials are configured, otherwise reports the mock provider. Never returns the API key.","security":[{"cookieAuth":[]},{"bearerAuth":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/ProviderHealth"}},"required":["ok","data"]}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/auth/register":{"post":{"tags":["Auth"],"summary":"Create a customer account","description":"Creates the user plus a zero-balance wallet, sets session cookies (web) and returns an access token + rotating refresh token (mobile). Rate limited per IP.","security":[],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","maxLength":254,"format":"email"},"password":{"type":"string","minLength":10,"maxLength":128},"displayName":{"type":"string","minLength":1,"maxLength":80},"referralCode":{"type":"string","maxLength":40}},"required":["email","password"]}}}},"responses":{"201":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"user":{"$ref":"#/components/schemas/Me"},"accessToken":{"type":"string"},"refreshToken":{"type":"string"}},"required":["user","accessToken","refreshToken"]}},"required":["ok","data"]}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/auth/login":{"post":{"tags":["Auth"],"summary":"Sign in","description":"argon2id verification, per-account + per-IP throttling. Returns tokens; the web client receives them as HttpOnly cookies and never sees the refresh token in JS.","security":[],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","maxLength":254,"format":"email"},"password":{"type":"string","minLength":1,"maxLength":128}},"required":["email","password"]}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"user":{"$ref":"#/components/schemas/Me"},"accessToken":{"type":"string"},"refreshToken":{"type":"string"}},"required":["user","accessToken","refreshToken"]}},"required":["ok","data"]}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/auth/refresh":{"post":{"tags":["Auth"],"summary":"Rotate the session","description":"Single-use rotating refresh token. Reuse of an already-rotated token revokes the whole token family and returns 401. Web sends the cookie; mobile sends `refreshToken` in the body.","security":[{"cookieAuth":[]},{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"refreshToken":{"type":"string","minLength":20,"maxLength":200}}}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"accessToken":{"type":"string"},"refreshToken":{"type":"string"}},"required":["accessToken","refreshToken"]}},"required":["ok","data"]}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/auth/logout":{"post":{"tags":["Auth"],"summary":"Sign out","description":"Revokes the presented refresh token (and clears cookies).","security":[{"cookieAuth":[]},{"bearerAuth":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"ok":{"type":"boolean"}},"required":["ok"]}},"required":["ok","data"]}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/auth/forgot-password":{"post":{"tags":["Auth"],"summary":"Request a password reset link","description":"Always returns 200 to avoid account enumeration. In DEMO_MODE with the log email transport the response includes `devToken` for local testing; production returns `{ requested: true }` only.","security":[],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","maxLength":254,"format":"email"}},"required":["email"]}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"requested":{"type":"boolean","enum":[true]},"devToken":{"type":"string"}},"required":["requested"]}},"required":["ok","data"]}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/auth/reset-password":{"post":{"tags":["Auth"],"summary":"Complete a password reset","description":"Consumes a single-use reset token, sets the new argon2id hash and revokes every active session for that user.","security":[],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string"},"newPassword":{"type":"string","minLength":10,"maxLength":128}},"required":["token","newPassword"]}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"reset":{"type":"boolean"}},"required":["reset"]}},"required":["ok","data"]}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/me":{"get":{"tags":["Account"],"summary":"Current profile","security":[{"cookieAuth":[]},{"bearerAuth":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"user":{"$ref":"#/components/schemas/Me"}},"required":["user"]}},"required":["ok","data"]}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"patch":{"tags":["Account"],"summary":"Update profile","description":"Update display name and/or email address.","security":[{"cookieAuth":[]},{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"displayName":{"type":"string","maxLength":80},"email":{"type":"string","format":"email"}}}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"user":{"$ref":"#/components/schemas/Me"},"message":{"type":"string"}},"required":["user"]}},"required":["ok","data"]}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/me/password":{"post":{"tags":["Account"],"summary":"Change password","description":"Requires the current password; revokes other sessions after the change.","security":[{"cookieAuth":[]},{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"currentPassword":{"type":"string","minLength":1,"maxLength":128},"newPassword":{"type":"string","minLength":10,"maxLength":128}},"required":["currentPassword","newPassword"]}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"ok":{"type":"boolean"}},"required":["ok"]}},"required":["ok","data"]}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/notifications":{"get":{"tags":["Account"],"summary":"List notifications","security":[{"cookieAuth":[]},{"bearerAuth":[]}],"parameters":[{"schema":{"type":"integer","minimum":1},"required":false,"name":"page","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100},"required":false,"name":"pageSize","in":"query"},{"schema":{"type":"string","enum":["true","false"]},"required":false,"name":"unreadOnly","in":"query"}],"responses":{"200":{"description":"Paged success","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"array","items":{"$ref":"#/components/schemas/Notification"}},"meta":{"$ref":"#/components/schemas/Meta"}},"required":["ok","data","meta"]}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/notifications/{id}/read":{"post":{"tags":["Account"],"summary":"Mark one notification read","security":[{"cookieAuth":[]},{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Internal UUID **or** public ref (NX-…, DP-…) where noted"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"ok":{"type":"boolean"}},"required":["ok"]}},"required":["ok","data"]}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/notifications/read-all":{"post":{"tags":["Account"],"summary":"Mark all notifications read","security":[{"cookieAuth":[]},{"bearerAuth":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"updated":{"type":"integer"}},"required":["updated"]}},"required":["ok","data"]}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/countries":{"get":{"tags":["Catalog"],"summary":"List serviceable countries (public)","description":"Served from the latest catalog snapshot. Availability is populated by the catalog-refresh job via the provider adapter.","security":[],"parameters":[{"schema":{"type":"string","maxLength":80},"required":false,"name":"search","in":"query"}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"array","items":{"$ref":"#/components/schemas/Country"}}},"required":["ok","data"]}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/services":{"get":{"tags":["Catalog"],"summary":"List services (public)","security":[],"parameters":[{"schema":{"type":"integer","exclusiveMinimum":0},"required":false,"name":"countryProviderId","in":"query"},{"schema":{"type":"string","maxLength":80},"required":false,"name":"search","in":"query"}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"array","items":{"$ref":"#/components/schemas/Service"}}},"required":["ok","data"]}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/pricing":{"get":{"tags":["Pricing"],"summary":"Marketplace price list","description":"Server-computed sell price for every country/service pair (integer math only). Prices already include the configured margin rules; the client must never compute prices.","security":[{"cookieAuth":[]},{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid"},"required":false,"name":"countryId","in":"query"},{"schema":{"type":"string","maxLength":80},"required":false,"name":"search","in":"query"},{"schema":{"type":"string","enum":["true","false"]},"required":false,"name":"availableOnly","in":"query"},{"schema":{"type":"string","enum":["price_asc","price_desc","country","service"]},"required":false,"name":"sort","in":"query"},{"schema":{"type":"integer","minimum":1},"required":false,"name":"page","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100},"required":false,"name":"pageSize","in":"query"}],"responses":{"200":{"description":"Paged success","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"array","items":{"$ref":"#/components/schemas/PricingRow"}},"meta":{"$ref":"#/components/schemas/Meta"}},"required":["ok","data","meta"]}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/pricing/quote":{"get":{"tags":["Pricing"],"summary":"Price quote for one pair","description":"Live cost/stock lookup plus local margin rules. `warnings` carries operator-visible caveats such as demo/mock mode. The quote is advisory — the authoritative charge is re-computed inside the purchase transaction.","security":[{"cookieAuth":[]},{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"countryId","in":"query"},{"schema":{"type":"string","format":"uuid"},"required":true,"name":"serviceId","in":"query"}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/Quote"}},"required":["ok","data"]}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/orders":{"get":{"tags":["Orders"],"summary":"List your orders","description":"Ownership-scoped. Phone numbers are masked; OTP codes are never included in list responses.","security":[{"cookieAuth":[]},{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","maxLength":200,"description":"Comma-separated order statuses","example":"WAITING_FOR_SMS,UNCERTAIN"},"required":false,"name":"status","in":"query"},{"schema":{"type":"string","maxLength":80},"required":false,"name":"search","in":"query"},{"schema":{"type":"string","format":"date-time"},"required":false,"name":"from","in":"query"},{"schema":{"type":"string","format":"date-time"},"required":false,"name":"to","in":"query"},{"schema":{"type":"integer","minimum":1},"required":false,"name":"page","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100},"required":false,"name":"pageSize","in":"query"}],"responses":{"200":{"description":"Paged success","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"array","items":{"$ref":"#/components/schemas/Order"}},"meta":{"$ref":"#/components/schemas/Meta"}},"required":["ok","data","meta"]}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"post":{"tags":["Orders"],"summary":"Buy a number / request an OTP","description":"Idempotent: `idempotencyKey` is unique per user, so a double-click or a retried request returns the original order instead of charging twice. Funds are reserved first, then the provider purchase is attempted exactly once. A provider timeout is NOT treated as a failure — the order becomes UNCERTAIN and reconciliation decides (refund only after the provider confirms).","security":[{"cookieAuth":[]},{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"countryId":{"type":"string","format":"uuid"},"serviceId":{"type":"string","format":"uuid"},"idempotencyKey":{"type":"string","minLength":8,"maxLength":64},"poolProviderId":{"type":["integer","null"],"minimum":0}},"required":["countryId","serviceId","idempotencyKey"]}}}},"responses":{"200":{"description":"Duplicate idempotency key — the original order is returned","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/Order"}},"required":["ok","data"]}}}},"201":{"description":"Order created (or the existing order for this idempotency key)","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/Order"}},"required":["ok","data"]}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/orders/export":{"get":{"tags":["Orders"],"summary":"Export order history as CSV","description":"Streams CSV of your own orders. OTP codes are intentionally excluded from exports.","security":[{"cookieAuth":[]},{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"date-time"},"required":false,"name":"from","in":"query"},{"schema":{"type":"string","format":"date-time"},"required":false,"name":"to","in":"query"}],"responses":{"200":{"description":"CSV file","content":{"text/csv":{"schema":{"type":"string"}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/orders/{id}":{"get":{"tags":["Orders"],"summary":"Order detail (by id or public ref)","description":"Owner-scoped. Includes the OTP code, the full phone number and the status timeline once available.","security":[{"cookieAuth":[]},{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","example":"NX-4JA6YA"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/Order"}},"required":["ok","data"]}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/orders/{id}/cancel":{"post":{"tags":["Orders"],"summary":"Cancel an active order","description":"Only pre-SMS states can be cancelled. The refund is applied **only** when the provider confirms the cancellation; otherwise the order is released back to its previous state or becomes UNCERTAIN for reconciliation.","security":[{"cookieAuth":[]},{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Internal UUID **or** public ref (NX-…, DP-…) where noted"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/Order"}},"required":["ok","data"]}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/orders/{id}/events":{"get":{"tags":["Orders"],"summary":"Live order status stream (SSE)","description":"Server-Sent Events stream of status changes for one order, owner-scoped. Clients fall back to polling `GET /api/v1/orders/{id}` when SSE is unavailable.","security":[{"cookieAuth":[]},{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Internal UUID **or** public ref (NX-…, DP-…) where noted"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"text/event-stream of `{ \"status\": …, \"at\": … }` frames","content":{"text/event-stream":{"schema":{"type":"string"}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/wallet":{"get":{"tags":["Wallet"],"summary":"Wallet balances","description":"Available/reserved/total in integer minor units. Balances are server-authoritative; no endpoint accepts a client-supplied balance.","security":[{"cookieAuth":[]},{"bearerAuth":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/Wallet"}},"required":["ok","data"]}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/wallet/transactions":{"get":{"tags":["Wallet"],"summary":"Ledger entries","description":"Append-only double-entry-style ledger rows for the caller, newest first.","security":[{"cookieAuth":[]},{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","enum":["DEPOSIT_CREDIT","PURCHASE_RESERVE","PURCHASE_DEBIT","PURCHASE_RELEASE","REFUND_CREDIT","ADJUSTMENT"]},"required":false,"name":"category","in":"query"},{"schema":{"type":"integer","minimum":1},"required":false,"name":"page","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100},"required":false,"name":"pageSize","in":"query"}],"responses":{"200":{"description":"Paged success","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"array","items":{"$ref":"#/components/schemas/WalletTransaction"}},"meta":{"$ref":"#/components/schemas/Meta"}},"required":["ok","data","meta"]}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/wallet/statement":{"get":{"tags":["Wallet"],"summary":"Download a statement (CSV)","security":[{"cookieAuth":[]},{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"date-time"},"required":false,"name":"from","in":"query"},{"schema":{"type":"string","format":"date-time"},"required":false,"name":"to","in":"query"}],"responses":{"200":{"description":"CSV file","content":{"text/csv":{"schema":{"type":"string"}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/deposits/methods":{"get":{"tags":["Deposits"],"summary":"Deposit methods + instructions","description":"Returns the operator-configured bKash/Nagad merchant numbers and step-by-step instructions. Manual deposits always require human verification before funds are credited.","security":[{"cookieAuth":[]},{"bearerAuth":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/DepositMethods"}},"required":["ok","data"]}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/deposits":{"get":{"tags":["Deposits"],"summary":"List your deposits","security":[{"cookieAuth":[]},{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","enum":["PENDING","APPROVED","REJECTED","EXPIRED"]},"required":false,"name":"status","in":"query"},{"schema":{"type":"integer","minimum":1},"required":false,"name":"page","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100},"required":false,"name":"pageSize","in":"query"}],"responses":{"200":{"description":"Paged success","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"array","items":{"$ref":"#/components/schemas/Deposit"}},"meta":{"$ref":"#/components/schemas/Meta"}},"required":["ok","data","meta"]}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"post":{"tags":["Deposits"],"summary":"Declare a deposit","description":"Creates a PENDING deposit with a unique reference. For Stripe, generates a checkout session URL. For manual methods, is credited upon admin approval.","security":[{"cookieAuth":[]},{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"method":{"type":"string","enum":["MANUAL_BKASH","MANUAL_NAGAD","GATEWAY","STRIPE","COINGATE"]},"amount":{"type":"string","pattern":"^\\d+(\\.\\d{1,2})?$"},"senderRef":{"type":"string","minLength":4,"maxLength":100},"senderAccount":{"type":"string","minLength":4,"maxLength":30},"successUrl":{"type":"string","format":"uri"},"cancelUrl":{"type":"string","format":"uri"}},"required":["method","amount"]}}}},"responses":{"201":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/Deposit"}},"required":["ok","data"]}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/deposits/stripe/verify":{"post":{"tags":["Deposits"],"summary":"Verify Stripe checkout session","description":"Queries the Stripe API to verify completion of a checkout session and immediately credits the user wallet.","security":[{"cookieAuth":[]},{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"sessionId":{"type":"string"},"depositId":{"type":"string"}},"required":["sessionId"]}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"success":{"type":"boolean"},"message":{"type":"string"}},"required":["success","message"]}},"required":["ok","data"]}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/payments/webhook/{gatewayId}":{"post":{"tags":["Payments"],"summary":"Payment gateway webhook (server-to-server)","description":"HMAC-SHA256 signed webhook ingress (`x-signature`, optional `sha256=` prefix, `x-event-id` for idempotency). Disabled unless configured. Signature verified against the raw body before any credit; duplicate events are ignored; amounts/currencies are re-verified server-side.","security":[],"parameters":[{"schema":{"type":"string","example":"sandboxpay"},"required":true,"name":"gatewayId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":{}}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"received":{"type":"boolean"}},"required":["received"]}},"required":["ok","data"]}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/deposits/stripe/payment-intent":{"post":{"tags":["Deposits"],"summary":"Create Stripe in-app payment intent","description":"Initializes a Stripe PaymentIntent for in-app mobile or web checkout with optional instant card confirmation.","security":[{"cookieAuth":[]},{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"amount":{"type":"string"},"card":{"type":"object","properties":{"number":{"type":"string"},"expMonth":{"type":"number"},"expYear":{"type":"number"},"cvc":{"type":"string"},"name":{"type":"string"}},"required":["number","expMonth","expYear","cvc"]}},"required":["amount"]}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"depositId":{"type":"string"},"clientSecret":{"type":"string"}},"required":["depositId","clientSecret"]}},"required":["ok","data"]}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/deposits/coingate/verify":{"post":{"tags":["Deposits"],"summary":"Verify CoinGate crypto payment","description":"Queries the CoinGate API to verify payment of a cryptocurrency order and immediately credits the user wallet.","security":[{"cookieAuth":[]},{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"orderId":{"anyOf":[{"type":"number"},{"type":"string"}]},"depositId":{"type":"string"}},"required":["orderId"]}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"success":{"type":"boolean"},"message":{"type":"string"}},"required":["success","message"]}},"required":["ok","data"]}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/payments/webhook/coingate":{"post":{"tags":["Payments"],"summary":"CoinGate cryptocurrency webhook ingress (server-to-server)","description":"Processes status: paid CoinGate webhook events to verify and credit wallet deposits.","security":[],"requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":{}}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"received":{"type":"boolean"}},"required":["received"]}},"required":["ok","data"]}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/payments/webhook/stripe":{"post":{"tags":["Payments"],"summary":"Stripe webhook ingress (server-to-server)","description":"Processes checkout.session.completed and payment_intent.succeeded Stripe webhook events to verify and credit wallet deposits.","security":[],"requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":{}}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"received":{"type":"boolean"}},"required":["received"]}},"required":["ok","data"]}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/support/tickets":{"get":{"tags":["Support"],"summary":"List your tickets","security":[{"cookieAuth":[]},{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string"},"required":false,"name":"status","in":"query"},{"schema":{"type":"integer","minimum":1},"required":false,"name":"page","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100},"required":false,"name":"pageSize","in":"query"}],"responses":{"200":{"description":"Paged success","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"array","items":{"$ref":"#/components/schemas/Ticket"}},"meta":{"$ref":"#/components/schemas/Meta"}},"required":["ok","data","meta"]}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"post":{"tags":["Support"],"summary":"Open a ticket","security":[{"cookieAuth":[]},{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"subject":{"type":"string","minLength":3,"maxLength":140},"category":{"type":"string","enum":["ORDER","PAYMENT","ACCOUNT","OTHER"]},"priority":{"type":"string","enum":["LOW","NORMAL","HIGH","URGENT"]},"body":{"type":"string","minLength":3,"maxLength":4000}},"required":["subject","category","body"]}}}},"responses":{"201":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/Ticket"}},"required":["ok","data"]}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/support/tickets/{id}":{"get":{"tags":["Support"],"summary":"Ticket thread","security":[{"cookieAuth":[]},{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Internal UUID **or** public ref (NX-…, DP-…) where noted"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/Ticket"}},"required":["ok","data"]}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/support/tickets/{id}/reply":{"post":{"tags":["Support"],"summary":"Reply to a ticket","security":[{"cookieAuth":[]},{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Internal UUID **or** public ref (NX-…, DP-…) where noted"},"required":true,"name":"id","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"body":{"type":"string","minLength":1,"maxLength":4000}},"required":["body"]}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/Ticket"}},"required":["ok","data"]}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/admin/users":{"get":{"tags":["Admin: Users"],"summary":"Search users","description":"Requires the `users:read` permission.","security":[{"cookieAuth":[]},{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","maxLength":120},"required":false,"name":"search","in":"query"},{"schema":{"type":"string","enum":["ACTIVE","SUSPENDED","CLOSED"]},"required":false,"name":"status","in":"query"},{"schema":{"type":"integer","minimum":1},"required":false,"name":"page","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100},"required":false,"name":"pageSize","in":"query"}],"responses":{"200":{"description":"Paged success","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminUserRow"}},"meta":{"$ref":"#/components/schemas/Meta"}},"required":["ok","data","meta"]}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/admin/users/{id}":{"get":{"tags":["Admin: Users"],"summary":"User detail (wallet, orders, notes)","security":[{"cookieAuth":[]},{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Internal UUID **or** public ref (NX-…, DP-…) where noted"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"allOf":[{"$ref":"#/components/schemas/AdminUserRow"},{"type":"object","properties":{"notes":{"type":["string","null"]},"recentOrders":{"type":"array","items":{"$ref":"#/components/schemas/Order"}}}}]}},"required":["ok","data"]}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/admin/users/{id}/status":{"patch":{"tags":["Admin: Users"],"summary":"Suspend / reactivate a user","description":"Audited. Suspension blocks authentication and new purchases but never touches balances.","security":[{"cookieAuth":[]},{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Internal UUID **or** public ref (NX-…, DP-…) where noted"},"required":true,"name":"id","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["ACTIVE","SUSPENDED"]},"reason":{"type":"string","minLength":3,"maxLength":500}},"required":["status","reason"]}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"ok":{"type":"boolean"}},"required":["ok"]}},"required":["ok","data"]}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/admin/users/{id}/limits":{"patch":{"tags":["Admin: Users"],"summary":"Set the daily purchase limit","security":[{"cookieAuth":[]},{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Internal UUID **or** public ref (NX-…, DP-…) where noted"},"required":true,"name":"id","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"purchaseLimitDaily":{"type":"integer","minimum":0,"maximum":100000},"reason":{"type":"string","minLength":3,"maxLength":500}},"required":["purchaseLimitDaily"]}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"ok":{"type":"boolean"}},"required":["ok"]}},"required":["ok","data"]}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/admin/users/{id}/notes":{"patch":{"tags":["Admin: Users"],"summary":"Attach internal notes","security":[{"cookieAuth":[]},{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Internal UUID **or** public ref (NX-…, DP-…) where noted"},"required":true,"name":"id","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"notes":{"type":"string","maxLength":2000}},"required":["notes"]}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"ok":{"type":"boolean"}},"required":["ok"]}},"required":["ok","data"]}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/admin/users/{id}/wallet-adjust":{"post":{"tags":["Admin: Users"],"summary":"Manual wallet adjustment (compensating entry)","description":"Writes an ADJUSTMENT ledger row — the only supported way to correct a balance, since ledger rows are immutable. Requires a reason and the `wallet:adjust` permission; audited. Step-up authentication is enforced on the web UI.","security":[{"cookieAuth":[]},{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Internal UUID **or** public ref (NX-…, DP-…) where noted"},"required":true,"name":"id","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"amount":{"type":"string","pattern":"^-?\\d+(\\.\\d{1,2})?$"},"reason":{"type":"string","minLength":5,"maxLength":500}},"required":["amount","reason"]}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"adjusted":{"type":"boolean"},"balanceAfterAvailable":{"type":"string"}},"required":["adjusted","balanceAfterAvailable"]}},"required":["ok","data"]}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/admin/orders":{"get":{"tags":["Admin: Orders"],"summary":"Search orders (all users)","security":[{"cookieAuth":[]},{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","maxLength":200,"description":"Comma-separated order statuses","example":"WAITING_FOR_SMS,UNCERTAIN"},"required":false,"name":"status","in":"query"},{"schema":{"type":"string","maxLength":120},"required":false,"name":"search","in":"query"},{"schema":{"type":"string","enum":["LIVE","MOCK"]},"required":false,"name":"providerMode","in":"query"},{"schema":{"type":"string","enum":["true","false"]},"required":false,"name":"uncertainOnly","in":"query"},{"schema":{"type":"integer","minimum":1},"required":false,"name":"page","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100},"required":false,"name":"pageSize","in":"query"}],"responses":{"200":{"description":"Paged success","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminOrderRow"}},"meta":{"$ref":"#/components/schemas/Meta"}},"required":["ok","data","meta"]}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/admin/orders/{id}":{"get":{"tags":["Admin: Orders"],"summary":"Order detail (all users)","security":[{"cookieAuth":[]},{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Internal UUID **or** public ref (NX-…, DP-…) where noted"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/AdminOrderRow"}},"required":["ok","data"]}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/admin/orders/{id}/reconcile":{"post":{"tags":["Admin: Orders"],"summary":"Reconcile an order against the provider","description":"Re-checks the upstream order. When the provider has no record, the order is marked UNCERTAIN, flagged for review (audit + event) and funds stay committed — they are never released automatically. Second invocation escalates to NEEDS_REVIEW.","security":[{"cookieAuth":[]},{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Internal UUID **or** public ref (NX-…, DP-…) where noted"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/ReconcileResult"}},"required":["ok","data"]}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/admin/deposits":{"get":{"tags":["Admin: Deposits"],"summary":"Deposit queue","description":"Manual deposits awaiting verification, oldest first, plus history.","security":[{"cookieAuth":[]},{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","enum":["PENDING","APPROVED","REJECTED","EXPIRED"]},"required":false,"name":"status","in":"query"},{"schema":{"type":"integer","minimum":1},"required":false,"name":"page","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100},"required":false,"name":"pageSize","in":"query"}],"responses":{"200":{"description":"Paged success","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminDepositRow"}},"meta":{"$ref":"#/components/schemas/Meta"}},"required":["ok","data","meta"]}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/admin/deposits/{id}/approve":{"post":{"tags":["Admin: Deposits"],"summary":"Approve a manual deposit (credits the wallet)","description":"The single place where manual money enters the ledger: validates the reference, credits DEPOSIT_CREDIT, marks the deposit APPROVED and records the reviewer. Requires `deposits:review`.","security":[{"cookieAuth":[]},{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Internal UUID **or** public ref (NX-…, DP-…) where noted"},"required":true,"name":"id","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"note":{"type":"string","minLength":3,"maxLength":500}},"required":["note"]}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/AdminDepositRow"}},"required":["ok","data"]}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/admin/deposits/{id}/reject":{"post":{"tags":["Admin: Deposits"],"summary":"Reject a manual deposit","description":"No ledger effect — a rejected deposit never credits anything.","security":[{"cookieAuth":[]},{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Internal UUID **or** public ref (NX-…, DP-…) where noted"},"required":true,"name":"id","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"note":{"type":"string","minLength":3,"maxLength":500}},"required":["note"]}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/AdminDepositRow"}},"required":["ok","data"]}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/admin/pricing-rules":{"get":{"tags":["Admin: Pricing"],"summary":"List pricing rules","security":[{"cookieAuth":[]},{"bearerAuth":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"array","items":{"$ref":"#/components/schemas/PricingRule"}}},"required":["ok","data"]}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"post":{"tags":["Admin: Pricing"],"summary":"Create a pricing rule","description":"Rules are integer-only: PERCENT_BPS (basis points, 2500 = 25%) or FIXED_MINOR (minor units), with optional minimum sell price and minimum margin. Most specific scope wins, then priority.","security":[{"cookieAuth":[]},{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"scope":{"type":"string","enum":["GLOBAL","COUNTRY","SERVICE","COUNTRY_SERVICE"]},"countryId":{"type":["string","null"],"format":"uuid"},"serviceId":{"type":["string","null"],"format":"uuid"},"ruleType":{"type":"string","enum":["PERCENT_BPS","FIXED_MINOR"]},"value":{"type":"string","pattern":"^\\d+$"},"minSellPriceMinor":{"type":["string","null"],"pattern":"^\\d+$"},"minMarginBps":{"type":["integer","null"],"minimum":0,"maximum":100000},"priority":{"type":"integer","minimum":0,"maximum":100000},"active":{"type":"boolean"}},"required":["scope","ruleType","value"]}}}},"responses":{"201":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/PricingRule"}},"required":["ok","data"]}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/admin/pricing-rules/{id}":{"patch":{"tags":["Admin: Pricing"],"summary":"Update a pricing rule","security":[{"cookieAuth":[]},{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Internal UUID **or** public ref (NX-…, DP-…) where noted"},"required":true,"name":"id","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"scope":{"type":"string","enum":["GLOBAL","COUNTRY","SERVICE","COUNTRY_SERVICE"]},"countryId":{"type":["string","null"],"format":"uuid"},"serviceId":{"type":["string","null"],"format":"uuid"},"ruleType":{"type":"string","enum":["PERCENT_BPS","FIXED_MINOR"]},"value":{"type":"string","pattern":"^\\d+$"},"minSellPriceMinor":{"type":["string","null"],"pattern":"^\\d+$"},"minMarginBps":{"type":["integer","null"],"minimum":0,"maximum":100000},"priority":{"type":"integer","minimum":0,"maximum":100000},"active":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/PricingRule"}},"required":["ok","data"]}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/admin/pricing-preview":{"get":{"tags":["Admin: Pricing"],"summary":"Preview a rule against the live catalog","description":"Shows the resulting sell price/margin for a country/service pair before saving a rule.","security":[{"cookieAuth":[]},{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid"},"required":false,"name":"countryId","in":"query"},{"schema":{"type":"string","format":"uuid"},"required":false,"name":"serviceId","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":200},"required":false,"name":"limit","in":"query"}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"array","items":{"$ref":"#/components/schemas/PricingRow"}}},"required":["ok","data"]}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/admin/catalog/sync":{"post":{"tags":["Admin: Pricing"],"summary":"Force a catalog refresh","description":"Re-pulls countries/services/stock from the provider adapter and snapshots the result.","security":[{"cookieAuth":[]},{"bearerAuth":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"countries":{"type":"integer"},"services":{"type":"integer"},"mode":{"type":"string","enum":["LIVE","MOCK"]}},"required":["countries","services","mode"]}},"required":["ok","data"]}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/admin/tickets":{"get":{"tags":["Admin: Tickets"],"summary":"Support queue","security":[{"cookieAuth":[]},{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string"},"required":false,"name":"status","in":"query"},{"schema":{"type":"string"},"required":false,"name":"priority","in":"query"},{"schema":{"type":"integer","minimum":1},"required":false,"name":"page","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100},"required":false,"name":"pageSize","in":"query"}],"responses":{"200":{"description":"Paged success","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminTicketRow"}},"meta":{"$ref":"#/components/schemas/Meta"}},"required":["ok","data","meta"]}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/admin/tickets/{id}":{"get":{"tags":["Admin: Tickets"],"summary":"Ticket thread (staff view)","security":[{"cookieAuth":[]},{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Internal UUID **or** public ref (NX-…, DP-…) where noted"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/AdminTicketRow"}},"required":["ok","data"]}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/admin/tickets/{id}/reply":{"post":{"tags":["Admin: Tickets"],"summary":"Staff reply","security":[{"cookieAuth":[]},{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Internal UUID **or** public ref (NX-…, DP-…) where noted"},"required":true,"name":"id","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"body":{"type":"string","minLength":1,"maxLength":4000}},"required":["body"]}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/AdminTicketRow"}},"required":["ok","data"]}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/admin/tickets/{id}/status":{"patch":{"tags":["Admin: Tickets"],"summary":"Change ticket status / assign","security":[{"cookieAuth":[]},{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Internal UUID **or** public ref (NX-…, DP-…) where noted"},"required":true,"name":"id","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["OPEN","PENDING_CUSTOMER","ASSIGNED","RESOLVED","CLOSED"]},"assignToSelf":{"type":"boolean"}},"required":["status"]}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/AdminTicketRow"}},"required":["ok","data"]}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/admin/dashboard":{"get":{"tags":["Admin: Insights"],"summary":"Operations dashboard metrics","description":"User/order counters, uncertain-order count, wallet liability and margin for the current period.","security":[{"cookieAuth":[]},{"bearerAuth":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/AdminDashboard"}},"required":["ok","data"]}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/admin/metrics/timeseries":{"get":{"tags":["Admin: Insights"],"summary":"Time series for charts","security":[{"cookieAuth":[]},{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","enum":["orders","revenue","margin","deposits"]},"required":false,"name":"metric","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":90},"required":false,"name":"days","in":"query"}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"array","items":{"type":"object","properties":{"date":{"type":"string"},"value":{"type":"string"},"count":{"type":"integer"}},"required":["date","value"]}}},"required":["ok","data"]}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/admin/audit-logs":{"get":{"tags":["Admin: Insights"],"summary":"Immutable audit trail","description":"Every privileged action lands here. `redactedMeta` is sanitised before storage — OTPs, tokens, API keys and passwords never appear.","security":[{"cookieAuth":[]},{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","maxLength":80},"required":false,"name":"action","in":"query"},{"schema":{"type":"string","maxLength":80},"required":false,"name":"targetType","in":"query"},{"schema":{"type":"string","format":"uuid"},"required":false,"name":"actorId","in":"query"},{"schema":{"type":"integer","minimum":1},"required":false,"name":"page","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100},"required":false,"name":"pageSize","in":"query"}],"responses":{"200":{"description":"Paged success","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"type":"array","items":{"$ref":"#/components/schemas/AuditLogRow"}},"meta":{"$ref":"#/components/schemas/Meta"}},"required":["ok","data","meta"]}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Not authenticated / token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — missing permission, suspended account, or CSRF/origin rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or not owned by the caller)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict — duplicate idempotency key or illegal state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Upstream provider or gateway unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}}},"webhooks":{}}