{"openapi":"3.0.0","paths":{"/health":{"get":{"operationId":"HealthController_check","summary":"Liveness probe","description":"Returns 200 with { status: \"ok\", revision: \"<short commit sha>\" }.","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthResponseDto"}}}}},"tags":["Health"]}},"/v1/sessions/{id}":{"get":{"operationId":"SessionController_getSession","summary":"Get a payment session","description":"Public — returns the session with payment options and line items. Merchant metadata is never included; read it from GET /v1/merchants/me/sessions/:id or webhook events.","parameters":[{"name":"id","required":true,"in":"path","description":"Session id (UUID)","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionDto"}}}},"404":{"description":"Session not found"}},"tags":["Sessions"]}},"/v1/sessions/{id}/status":{"get":{"operationId":"SessionController_getSessionStatus","summary":"Poll session status","description":"Public — lightweight status + webhookDelivered flag for redirect gating.","parameters":[{"name":"id","required":true,"in":"path","description":"Session id (UUID)","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionStatusResponseDto"}}}},"404":{"description":"Session not found"}},"tags":["Sessions"]}},"/v1/sessions/{id}/cancel":{"post":{"operationId":"SessionController_cancel","summary":"Cancel an unpaid session","description":"Only before any deposit is detected. Repeated cancellation returns the existing outcome.","parameters":[{"name":"id","required":true,"in":"path","description":"Session id (UUID)","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CancelSessionResponseDto"}}}},"401":{"description":"Missing or invalid secret API key","content":{"application/json":{"schema":{"type":"object","properties":{"statusCode":{"type":"number"},"message":{"type":"string"},"error":{"type":"string"}},"required":["statusCode","message"]}}}},"404":{"description":"Session not found for this merchant"},"409":{"description":"A deposit was detected, the session expired, or settlement already started"}},"tags":["Sessions"],"security":[{"sk-api-key":[]}]}},"/v1/sessions":{"post":{"operationId":"SessionController_create","summary":"Create a payment session","description":"Server-to-server — requires an `sk_*` secret key in the Authorization header. When mode isolation is enabled, the key determines the mode and sandboxMode is ignored.","parameters":[{"name":"Idempotency-Key","in":"header","description":"Opaque key (per merchant and, after isolation activation, per mode) (at most 255 characters). A retried create with the same key and body returns the original session instead of creating a new one.","required":false,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSessionRequestDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSessionResponseDto"}}}},"400":{"description":"`session_idempotency_key_invalid` — The header is present but blank or longer than the limit\n\nBody-validation failures use the same envelope without `code`.","content":{"application/json":{"schema":{"type":"object","properties":{"statusCode":{"type":"number"},"message":{"type":"string"},"error":{"type":"string"},"code":{"type":"string","enum":["session_idempotency_key_invalid"]}},"required":["statusCode","message"]}}}},"401":{"description":"Missing or unknown `Authorization: Bearer sk_*` secret key","content":{"application/json":{"schema":{"type":"object","properties":{"statusCode":{"type":"number"},"message":{"type":"string"},"error":{"type":"string"}},"required":["statusCode","message"]}}}},"409":{"description":"`session_idempotency_conflict` — A key replays the original session only for an identical body; reuse with a different body is refused instead of silently returning an unrelated session","content":{"application/json":{"schema":{"type":"object","properties":{"statusCode":{"type":"number"},"message":{"type":"string"},"error":{"type":"string"},"code":{"type":"string","enum":["session_idempotency_conflict"]}},"required":["statusCode","message","code"]}}}},"422":{"description":"`session_processing_unpayable` — Processing is merchant-paid only and every supported option's current processing price exceeds the merchant's processing expense limit","content":{"application/json":{"schema":{"type":"object","properties":{"statusCode":{"type":"number"},"message":{"type":"string"},"error":{"type":"string"},"code":{"type":"string","enum":["session_processing_unpayable"]},"options":{"type":"array","items":{"type":"object","properties":{"symbol":{"type":"string"},"chainId":{"type":"number"},"networkName":{"type":"string"},"reason":{"type":"string","enum":["exceeds_expense_limit"]},"processingPrice":{"type":"string"},"expenseLimit":{"type":"string"}},"required":["symbol","chainId","networkName","reason","processingPrice","expenseLimit"]}}},"required":["statusCode","message","code"]}}}}},"tags":["Sessions"],"security":[{"sk-api-key":[]}]}},"/v1/paymaster/rpc":{"post":{"operationId":"PaymasterRpcController_handle","summary":"ERC-4337 paymaster JSON-RPC proxy","description":"ERC-4337 paymaster JSON-RPC for the publishable `pmc_*` client id in `X-Client-Id`.\n\n**Methods.** Every method takes `params = [userOp, entryPoint, chainId, context?]`: `entryPoint` is EntryPoint v0.8, v0.7 or v0.6; `chainId` is a `0x` hex string, a decimal string or a number; `context` is optional `{\"token\": \"0x…\"}`.\n\n- `pm_getPaymasterStubData` (ERC-7677) — paymaster data with a recoverable, non-authorizing 65-byte ECDSA placeholder for gas estimation; does not call the real signer or authorize payment, and reads the limits without consuming them.\n- `pm_getPaymasterData` (ERC-7677) — signed paymaster data to submit; needs `callGasLimit`, `verificationGasLimit`, `preVerificationGas` and `maxFeePerGas`, and consumes one unit of the per-sender limits. A request whose `verificationGasLimit` and `preVerificationGas` are both zero cannot be included on-chain, so it is answered like `pm_getPaymasterStubData`: a non-authorizing placeholder signature, limits read but not consumed.\n- `pm_sponsorUserOperation` — the same signed result for merchant-sponsored gas; a request with zero `verificationGasLimit` and `preVerificationGas` is answered like the stub the same way; a `context.token` is refused.\n\nV0.6 requests use unpacked `initCode` and `paymasterAndData`; split factory/paymaster gas fields and EIP-7702 are refused. V0.6 returns `{paymasterAndData}`. V0.7/v0.8 return `{paymaster, paymasterData, paymasterVerificationGasLimit, paymasterPostOpGasLimit}`.\n\n**What gets sponsored.**\n- A `pmc_live_*` key covers mainnets only and a `pmc_test_*` key testnets only; the chain must also be in the client's `allowedChainIds`.\n- No `context.token` is merchant-sponsored gas; a `context.token` is user-paid gas in that ERC-20. The client's `gasMode` (`sponsored`, `user_paid`, `both`) decides which are allowed.\n- Direct calls support `execute(address,uint256,bytes)` and MetaMask's single `execute((address,uint256,bytes))`. The target must be on the merchant contract allowlist and not blocked by a deny rule.\n- Direct batches support `executeBatch(address[],uint256[],bytes[])` (SimpleAccount v0.7 and thirdweb Account on supported EntryPoints, including thirdweb v0.6) and ERC-7579 `execute(bytes32,bytes)` with exactly the BatchDefault mode (`0x01` then 31 zero bytes) and an `abi.encode((address,uint256,bytes)[])` payload (MetaMask smart accounts). A batch holds 1 to 16 calls in at most 32 KiB of `callData`, every call has zero native value, the three `executeBatch` arrays have equal length (an empty value array is refused), and the encoding must be canonical. Try, delegatecall and every other mode, SimpleAccount v0.8 `executeBatch((address,uint256,bytes)[])` and a call targeting DelegationManager are refused.\n- Every call target of a batch must be on the merchant contract allowlist and none may be blocked by a deny rule; one refused call refuses the whole operation before it is signed or counted against limits, and the error names the call by its zero-based index. The allowlist matches contract addresses only; call arguments such as an `approve` spender are not checked.\n- A batch executes in the account in order and atomically: a reverting call reverts every call of the operation, while the included operation still consumes gas and is billed like any other operation. A batch is one operation for limits, billing and usage; `targets` on usage and activity rows lists every call target in order.\n- On all supported networks (Ethereum, Base, Arbitrum and their Sepolia testnets), MetaMask v1.3.0 Advanced Permissions (ERC-7715) support one ERC-20 allowance transfer through DelegationManager, wrapped in a supported single execute call or ERC-7579 `execute(bytes32,bytes)` with an all-zero SingleDefault mode. One root delegation must bind the sender and chain-specific EOA signature, token allowance, expiry, redeemer, payee, zero native value and nonce. Delegated batches, redelegation, other modes and unrecognised caveats are refused.\n- Delegated transfers check deny rules and per-account limits for both the session sender and original owner; the global budget is charged once. Usage keeps the actual UserOperation sender and records the owner in `reason`. Bundler execution simulation checks on-chain revocation and remaining allowance.\n- Sponsored ops on mainnets need a positive prepaid gas balance.\n- Per-sender limits are the lower of the platform and merchant values, see `GET /v1/paymaster/limits`.\n\n**Errors.**\n\n| Response | `message` starts with | Cause |\n| --- | --- | --- |\n| HTTP 401 (`/v1/paymaster/rpc`, `/v1/paymaster/bundler/{chainId}`) | `paymaster_unauthorized` | Client id missing (`X-Client-Id` header or `?clientId=`), unknown or revoked |\n| HTTP 403 (`/v1/paymaster/rpc`, `/v1/paymaster/bundler/{chainId}`) | `paymaster_origin_not_allowed` | The client has `allowedOrigins` and the `Origin` header is missing or not on the list |\n| JSON-RPC `-32700` | `Parse error` | The JSON body is not a JSON-RPC 2.0 request: `jsonrpc` is not `\"2.0\"`, `method` is not a string, `params` is not an array, or `id` is not a string or number |\n| JSON-RPC `-32601` | `Unknown method` | `method` is not `pm_sponsorUserOperation`, `pm_getPaymasterStubData` or `pm_getPaymasterData` |\n| JSON-RPC `-32602` | `Invalid params` | `params[0]` is not an object, `params[1]` is not a string starting with `0x`, `params[2]` is not a chainId (`0x` hex, decimal string or number), or `nonce` or a gas field cannot be read as an integer |\n| JSON-RPC `-32602` | `paymaster_token_invalid` | `params[3].token` is present but not an address |\n| JSON-RPC `-32602` | `paymaster_token_unsupported_method` | `context.token` sent with `pm_sponsorUserOperation`; user-paid gas uses the stub + data methods |\n| JSON-RPC `-32602` | `paymaster_gas_fields_missing` | `pm_getPaymasterData` / `pm_sponsorUserOperation` without `callGasLimit`, `verificationGasLimit`, `preVerificationGas` or `maxFeePerGas` |\n| HTTP 403 (`/v1/paymaster/rpc`, `/v1/paymaster/bundler/{chainId}`); JSON-RPC `-32602` | `Policy denied: paymaster_chain_not_allowed` (`paymaster_chain_not_allowed` on the bundler route) | chainId is not in the client's `allowedChainIds`; on `/v1/paymaster/rpc` the client-id check answers HTTP 403 first, the JSON-RPC code is the service's re-check |\n| HTTP 403 (`/v1/paymaster/bundler/{chainId}`); JSON-RPC `-32602` | `Policy denied: paymaster_key_network_class_mismatch` (`paymaster_key_network_class_mismatch` on the bundler route) | A `pmc_live_*` key on a testnet or a `pmc_test_*` key on a mainnet |\n| JSON-RPC `-32602` | `Policy denied: paymaster_entrypoint_not_supported` | `params[1]` is not a supported EntryPoint (v0.6, v0.7, v0.8) |\n| JSON-RPC `-32602` | `Policy denied: paymaster_target_not_allowed` | Unsupported execution, batch or bounded MetaMask delegation transfer, a batch call with native value or targeting DelegationManager, invalid delegation permission, or a call target is not in the merchant's contract allowlist for that chain |\n| HTTP 403 (`/v1/paymaster/bundler/{chainId}`); JSON-RPC `-32600` | `Policy denied: paymaster_gas_mode_not_allowed` (`paymaster_gas_mode_not_allowed` on the bundler route) | The client's `gasMode` does not allow this op (a `token` on a `sponsored` key, no token on a `user_paid` key) |\n| JSON-RPC `-32600` | `Policy denied: paymaster_blocked_by_merchant_rule` | A merchant deny rule matches the sender, the delegated owner or any call target on that chain; checked before the allowlist |\n| HTTP 402 (`/v1/paymaster/bundler/{chainId}`); JSON-RPC `-32600` | `Policy denied: paymaster_billing_balance_exhausted` (`paymaster_billing_balance_exhausted` on the bundler route) | Sponsored op on a mainnet while the merchant's prepaid gas balance is exhausted |\n| JSON-RPC `-32600` | `Policy denied: paymaster_merchant_tx_cap_unpriceable` | A merchant per-transaction cap is set and the op cannot be priced (no ETH/USD rate) |\n| JSON-RPC `-32600` | `Policy denied: paymaster_merchant_tx_cap_exceeded` | Estimated cost plus markup exceeds the merchant per-transaction cap |\n| JSON-RPC `-32600` | `Policy denied: paymaster_global_cap_exceeded` | Platform daily sponsorship budget reached; resets at 00:00 UTC |\n| JSON-RPC `-32600` | `Policy denied: paymaster_spend_cap_exceeded` | Per-sender daily USD cap reached; `(merchant-configured cap)` suffix when the merchant cap is the binding one |\n| JSON-RPC `-32600` | `Policy denied: paymaster_rate_limit_exceeded` | Per-sender hourly op limit reached; `(merchant-configured cap)` suffix when the merchant limit is the binding one |\n| JSON-RPC `-32600` | `Policy denied: paymaster_price_unavailable` | A dollar cap applies and no ETH/USD price is available; retry |\n| JSON-RPC `-32603` | `paymaster_upstream_error` | The signing service is unreachable, timed out, or answered with a non-2xx status or a non-JSON body |\n| JSON-RPC `-32603` | `paymaster_mode_mismatch` | A `token` was requested and the signing service did not return ERC-20 mode paymaster data |\n| HTTP 502 (`/v1/paymaster/bundler/{chainId}`) | `bundler upstream` | The platform bundler answered with a non-2xx status or a non-JSON body; a transport failure returns 502 with the transport error text instead |\n\n- HTTP 401 and 403 from the client-id check carry `{\"statusCode\":<status>,\"message\":\"...\",\"error\":\"...\"}`; the other HTTP errors of the bundler route carry `{\"error\":{\"message\":\"...\",\"type\":\"error\"}}`.\n- A `{chainId}` path segment that is not an integer is rejected with HTTP 400 `Validation failed (numeric string is expected)` right after the client-id check.\n- JSON-RPC errors arrive with HTTP 200 as `{\"jsonrpc\":\"2.0\",\"id\":<id>,\"error\":{\"code\":<code>,\"message\":\"...\"}}`; `id` is `null` only for `Parse error`.\n- A `Policy denied: ` prefix marks a policy decision: never fall back to another paymaster for that operation. The bundler route never adds the prefix.\n- Once every check passes, errors from the internal signing service (for example `Unsupported chain`, `Token … not whitelisted`, `Paymaster deposit too low`) are returned unchanged with code `-32602` or `-32000`.","parameters":[{"name":"Origin","in":"header","description":"Validated against the client's allowedOrigins when configured","required":false,"schema":{"type":"string"}},{"name":"X-Client-Id","in":"header","description":"Publishable paymaster client id (`pmc_live_*` / `pmc_test_*`)","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcRequestDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcResponseDto"}}}},"401":{"description":"`paymaster_unauthorized` — Client id missing (`X-Client-Id` header or `?clientId=`), unknown or revoked","content":{"application/json":{"schema":{"type":"object","properties":{"statusCode":{"type":"number"},"message":{"type":"string"},"error":{"type":"string"}},"required":["statusCode","message"]}}}},"403":{"description":"`paymaster_origin_not_allowed` — The client has `allowedOrigins` and the `Origin` header is missing or not on the list\n\n`paymaster_chain_not_allowed` — chainId is not in the client's `allowedChainIds`; on `/v1/paymaster/rpc` the client-id check answers HTTP 403 first, the JSON-RPC code is the service's re-check","content":{"application/json":{"schema":{"type":"object","properties":{"statusCode":{"type":"number"},"message":{"type":"string"},"error":{"type":"string"}},"required":["statusCode","message"]}}}}},"tags":["Paymaster RPC"],"security":[{"paymaster-client-id":[]}]}},"/v1/paymaster/bundler/{chainId}":{"post":{"operationId":"PaymasterBundlerController_proxy","summary":"Authenticated ERC-4337 bundler proxy","description":"Relays ERC-4337 bundler JSON-RPC (e.g. `eth_sendUserOperation`) to the platform bundler so the Pimlico key never reaches the browser. Authenticates via the publishable `pmc_*` client id in `X-Client-Id` (Origin validated against the client's allowlist); the `:chainId` path param is validated against the client's allowedChainIds.","parameters":[{"name":"chainId","required":true,"in":"path","schema":{"type":"number"}},{"name":"Origin","in":"header","description":"Validated against the client's allowedOrigins when configured","required":false,"schema":{"type":"string"}},{"name":"clientId","required":false,"in":"query","description":"Publishable client id, alternative to the `X-Client-Id` header (header wins).","schema":{}},{"name":"X-Client-Id","in":"header","description":"Publishable paymaster client id (`pmc_live_*` / `pmc_test_*`). May be supplied as the `clientId` query param instead — thirdweb's `bundlerUrl` can't send a header, so the bundler route accepts it in the URL.","required":false,"schema":{"type":"string"}}],"requestBody":{"required":true,"description":"ERC-4337 bundler JSON-RPC request, or a batch of them","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/BundlerJsonRpcRequestDto"},{"type":"array","items":{"$ref":"#/components/schemas/BundlerJsonRpcRequestDto"}}]}}}},"responses":{"200":{"description":"Raw bundler JSON-RPC response"},"400":{"description":"`Validation failed (numeric string is expected)` — the `{chainId}` path segment is not an integer","content":{"application/json":{"schema":{"type":"object","properties":{"statusCode":{"type":"number"},"message":{"type":"string"},"error":{"type":"string"}},"required":["statusCode","message"]}}}},"401":{"description":"`paymaster_unauthorized` — Client id missing (`X-Client-Id` header or `?clientId=`), unknown or revoked","content":{"application/json":{"schema":{"type":"object","properties":{"statusCode":{"type":"number"},"message":{"type":"string"},"error":{"type":"string"}},"required":["statusCode","message"]}}}},"402":{"description":"`paymaster_billing_balance_exhausted` — Sponsored op on a mainnet while the merchant's prepaid gas balance is exhausted","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"message":{"type":"string"},"type":{"type":"string","enum":["error"]}},"required":["message","type"]}},"required":["error"]}}}},"403":{"description":"`paymaster_origin_not_allowed` — The client has `allowedOrigins` and the `Origin` header is missing or not on the list\n\n`paymaster_chain_not_allowed` — chainId is not in the client's `allowedChainIds`; on `/v1/paymaster/rpc` the client-id check answers HTTP 403 first, the JSON-RPC code is the service's re-check\n\n`paymaster_key_network_class_mismatch` — A `pmc_live_*` key on a testnet or a `pmc_test_*` key on a mainnet\n\n`paymaster_gas_mode_not_allowed` — The client's `gasMode` does not allow this op (a `token` on a `sponsored` key, no token on a `user_paid` key)","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"error":{"type":"object","properties":{"message":{"type":"string"},"type":{"type":"string","enum":["error"]}},"required":["message","type"]}},"required":["error"]},{"type":"object","properties":{"statusCode":{"type":"number"},"message":{"type":"string"},"error":{"type":"string"}},"required":["statusCode","message"]}]}}}},"502":{"description":"`bundler upstream` — The platform bundler answered with a non-2xx status or a non-JSON body; a transport failure returns 502 with the transport error text instead","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"message":{"type":"string"},"type":{"type":"string","enum":["error"]}},"required":["message","type"]}},"required":["error"]}}}}},"tags":["Paymaster Bundler"],"security":[{"paymaster-client-id":[]}]}}},"info":{"title":"StablecoinX Public API","description":"Integrator-facing endpoints for StablecoinX — create and read payment sessions, poll session status, and proxy ERC-4337 paymaster JSON-RPC calls. Session creation uses an `sk_*` secret key; the paymaster RPC proxy uses a publishable `pmc_*` client id in the `X-Client-Id` header. For the full merchant + integrator spec see /docs/openapi.yaml.\n\n## Webhook delivery\n\nEndpoints and delivery history are managed under `/v1/webhooks` with an `sk_*` secret key — see the [Webhooks routes](https://api.dev.scx.easeflow.io/docs#/Webhooks) in the full OpenAPI ([JSON](https://api.dev.scx.easeflow.io/docs/openapi.json)).\n\n- **At-least-once.** Every recorded event is delivered at least once to each endpoint that was subscribed when the event was recorded, as long as the endpoint answers 2xx within the automatic retry window. A delivery whose endpoint is disabled, or whose URL fails the dispatch-time check, is marked `failed` without retry; a DNS resolution failure or a signing secret that cannot be decrypted is a failed attempt and follows the automatic retry schedule; a manual resend (`POST /v1/sessions/{id}/retry-webhook` or the dashboard) reaches only the session's latest event, sent to the endpoints enabled and subscribed to its type at resend time — an endpoint added later can receive an older event, and `POST /v1/webhooks/{id}/deliveries/{deliveryId}/resend` replays any retained delivery, its stored payload unchanged, to its original endpoint, which must still be enabled and subscribed to the event type. The same event can arrive more than once: a 2xx that did not reach StablecoinX (timeout, dropped connection), a worker failure after your 2xx, or a manual resend, which sends the event again even after a successful delivery.\n- **No ordering guarantee.** Events of one session can arrive in any order — a retried `session.confirming` can arrive after `session.completed`.\n- **Session status and resend.** Both select the same latest event. Events co-produced by one payment share a timestamp; tied events prefer types subscribed to by an enabled endpoint, then descending event id. Selection never falls back to an earlier timestamp. Use delivery history to inspect or replay other retained events.\n- **Stable event id.** Every event has an id `evt_*` in the body's `id`, copied to the `X-StablecoinX-Event-Id` header. It is identical on every delivery of that event and on every endpoint the event is sent to.\n- **Dedupe on the body `id` after verifying the signature.** `X-StablecoinX-Signature` covers `{timestamp}.{rawBody}` only; the `X-StablecoinX-Event-Id` header is unsigned.\n- **Recommended handler:** verify the signature → in one database transaction insert the event id into a table with a UNIQUE constraint together with your own state change (or a job row) → commit → respond 2xx → run external side effects (emails, outbound calls) from the job, idempotently. On a unique-key conflict respond 2xx without re-running anything. When several endpoints share one store, key the table on the consuming system and the event id. Respond within 10 seconds: a slower response times out and is retried.\n- **Ordering:** treat an event as a signal, not as the state. From the job, not the request path, re-read the session with `GET /v1/merchants/me/sessions/{id}` (`sk_*`, full OpenAPI) and act on its current `status` and `amountReceivedByChain`; do not order events by their bodies or `createdAt`. Never move an order back from `completed`; `session.expired` can still be followed by `session.completed`.\n- **Retention:** keep processed event ids for as long as you need to suppress repeated side effects — any retained delivery, including a balance event, can be manually replayed at any time.\n\n## floYsh / Yield as a Service\n\nfloYsh is StablecoinX's Yield as a Service (YaaS): a branded stablecoin-yield site built around Ethena USDe/sUSDe vaults. Merchants manage the site's deployment, branding and domains in Business UI; customers use the public site to connect a wallet and access the vault.\n\n- [Business UI](https://business.dev.scx.easeflow.io/) — sign in, open **Deployments → Yield** (`/deployments/yield`), and choose **New deployment** (or **Create deployment** for the first one). Open a deployment to edit its branding, manage **Domains**, and inspect **Contracts**.\n- [Business UI guide](https://business.dev.scx.easeflow.io/llms-full.txt) — product overview and navigation, including how to find a deployment's public site.\n- [Merchant OpenAPI](https://api.dev.scx.easeflow.io/docs#/yield-deployments) ([JSON](https://api.dev.scx.easeflow.io/docs/openapi.json)) — configuration routes under `/v1/merchants/me/yield-deployments`, authenticated with a merchant JWT; the list and read routes also accept an `sk_*` secret key. These routes are in the full merchant spec, not the integrator spec at `/openapi`.\n\nThe MCP server currently has no yield-deployment tools; guide users to Business UI for floYsh management.\n","version":"1.0","contact":{}},"tags":[{"name":"Health","description":"Liveness probe"},{"name":"Sessions","description":"Payment sessions — create (sk_*), read + poll status (public)"},{"name":"Paymaster RPC","description":"ERC-4337 paymaster JSON-RPC proxy (X-Client-Id)"},{"name":"Paymaster Bundler","description":"ERC-4337 bundler JSON-RPC proxy per chain (X-Client-Id or ?clientId=)"}],"servers":[{"url":"https://api.dev.scx.easeflow.io","description":"Dev"}],"components":{"securitySchemes":{"sk-api-key":{"type":"http","scheme":"bearer","description":"Server-to-server secret key as `Bearer sk_*`"},"paymaster-client-id":{"type":"apiKey","in":"header","name":"X-Client-Id","description":"Publishable paymaster client id (`pmc_live_*` / `pmc_test_*`)"}},"schemas":{"HealthResponseDto":{"type":"object","properties":{"status":{"type":"string","example":"ok"},"revision":{"type":"string","example":"9f8e7d6c5b4a","description":"Short sha of the commit this build was made from, or \"unknown\"."}},"required":["status","revision"]},"NetworkDto":{"type":"object","properties":{"name":{"type":"string","example":"Base Sepolia"},"chainId":{"type":"number","example":84532},"standard":{"type":"string","example":"ERC-20"}},"required":["name","chainId","standard"]},"PaymentOptionProcessingDto":{"type":"object","properties":{"price":{"type":"string","example":"0.42","description":"Processing price in the option's token (decimal string)"},"payer":{"type":"string","enum":["MERCHANT","PAYER"],"description":"Who pays the processing price"},"quoteExpiresAt":{"type":"string","format":"date-time","description":"The price is not valid as payment instructions after this time"}},"required":["price","payer","quoteExpiresAt"]},"PaymentOptionDto":{"type":"object","properties":{"id":{"type":"string"},"symbol":{"type":"string","enum":["USDe","USDT","USDC"]},"tokenAddress":{"type":"string"},"decimals":{"type":"number","example":6},"network":{"$ref":"#/components/schemas/NetworkDto"},"depositAddress":{"type":"string","description":"CREATE2-predicted Forwarder address the payer sends funds to"},"amountDue":{"type":"string","example":"100.42","description":"Amount the payer sends with this option: the order amount plus a payer-paid processing price and a payer-paid service fee (decimal string)"},"available":{"type":"boolean","description":"False when the option has no current processing price; it must not be used as payment instructions"},"unavailableReason":{"type":"string","enum":["price_unavailable","quote_expired",null],"nullable":true},"processing":{"description":"Null for sessions created without a processing price","anyOf":[{"$ref":"#/components/schemas/PaymentOptionProcessingDto"},{"type":"object","nullable":true,"enum":[null]}]}},"required":["id","symbol","tokenAddress","decimals","network","depositAddress","amountDue","available","unavailableReason","processing"]},"LineItemDto":{"type":"object","properties":{"name":{"type":"string","example":"Pro plan (annual)"},"quantity":{"type":"number","example":1},"unitPrice":{"type":"string","example":"100.00","description":"Decimal string"},"total":{"type":"string","example":"100.00","description":"Decimal string"}},"required":["name","quantity","unitPrice","total"]},"SessionServiceFeeDto":{"type":"object","properties":{"amount":{"type":"string","example":"1.00","description":"Service fee in the session currency (decimal string)"},"payer":{"type":"string","enum":["MERCHANT","PAYER"],"description":"Who pays the service fee"}},"required":["amount","payer"]},"SessionDto":{"type":"object","properties":{"mode":{"type":"string","enum":["live","test"]},"id":{"type":"string","format":"uuid"},"merchantName":{"type":"string"},"amount":{"type":"string","description":"Decimal string"},"currency":{"type":"string","example":"usd"},"status":{"type":"string","enum":["settling","pending","awaiting_payment","partial","confirming","bridging","completed","failed","expired","cancelled"]},"paymentOptions":{"type":"array","items":{"$ref":"#/components/schemas/PaymentOptionDto"}},"lineItems":{"type":"array","items":{"$ref":"#/components/schemas/LineItemDto"}},"expiresAt":{"type":"string","format":"date-time"},"createdAt":{"type":"string","format":"date-time"},"paidAt":{"type":"string","format":"date-time"},"txHash":{"type":"string","nullable":true},"sandboxMode":{"type":"boolean"},"serviceFee":{"description":"Service-fee terms fixed at creation, uniform across options; null when the session has no fee terms. A PAYER fee is already included in every option's amountDue.","anyOf":[{"$ref":"#/components/schemas/SessionServiceFeeDto"},{"type":"object","nullable":true,"enum":[null]}]},"successUrl":{"type":"string"}},"required":["id","merchantName","amount","currency","status","paymentOptions","lineItems","expiresAt","createdAt","sandboxMode","serviceFee"]},"ChainAmountReceivedDto":{"type":"object","properties":{"chainId":{"type":"number"},"amountReceived":{"type":"string","description":"Decimal string"},"currency":{"type":"string","example":"usd"}},"required":["chainId","amountReceived","currency"]},"SessionStatusResponseDto":{"type":"object","properties":{"mode":{"type":"string","enum":["live","test"]},"status":{"type":"string","enum":["settling","pending","awaiting_payment","partial","confirming","bridging","completed","failed","expired","cancelled"]},"webhookDelivered":{"type":"boolean","description":"True when the user-ui is clear to redirect the payer to successUrl"},"currency":{"type":"string","example":"usd","description":"Lowercase ISO 4217 currency code for the session amount fields"},"amountReceived":{"type":"string","nullable":true,"description":"Amount received on the accepted option's chain for a partial payment, otherwise on the chain with the largest balance. Decimal string; null until a deposit lands."},"amountRemaining":{"type":"string","nullable":true,"description":"Amount still owed on amountReceivedChainId under the accepted processing terms, clamped at \"0\". Always \"0\" once the session settled as paid (a tolerance-accepted shortfall is not owed). Null until a deposit lands."},"amountReceivedChainId":{"type":"number","nullable":true},"amountReceivedByChain":{"type":"array","items":{"$ref":"#/components/schemas/ChainAmountReceivedDto"}},"acceptedPaymentOptionId":{"type":"string","nullable":true,"description":"ID of the payment option (`paymentOptions[].id`) whose processing terms the first deposit fixed. The rest of a partial payment is owed under these terms in this option's token, so `amountRemaining` includes its payer-paid processing price. Null until a deposit lands."},"paymentReceivedAt":{"type":"string","format":"date-time","nullable":true,"description":"When the sufficient source deposit reached its 14-confirmation gate (payment received). Null while the deposit is unconfirmed, and again while a reorg or a recheck that could not read the chain reopens the gate before source execution; it is then set with the new release time."}},"required":["status","webhookDelivered","currency","amountReceived","amountRemaining","amountReceivedByChain","acceptedPaymentOptionId","paymentReceivedAt"]},"CancelSessionResponseDto":{"type":"object","properties":{"mode":{"type":"string","enum":["live","test"]},"id":{"type":"string"},"status":{"type":"string","enum":["cancelled"]},"cancelledAt":{"type":"string","format":"date-time"}},"required":["id","status","cancelledAt"]},"CreateSessionRequestDto":{"type":"object","properties":{"amount":{"type":"string","example":"100.00","description":"Total amount as a positive decimal string"},"merchantName":{"type":"string","example":"Acme Inc."},"lineItems":{"type":"array","items":{"$ref":"#/components/schemas/LineItemDto"}},"paymentOptionSymbols":{"type":"array","description":"Restrict offered payment currencies; defaults to all supported","items":{"type":"string","enum":["USDe","USDT","USDC"]}},"expiresInSec":{"type":"number","example":3600,"description":"Session lifetime in seconds (60..604800)"},"successUrl":{"type":"string","example":"https://shop.example.com/order/123/success","description":"Absolute https URL the payer is redirected to after settlement"},"sandboxMode":{"type":"boolean","description":"Compatibility flag during preparation: true selects testnets, false mainnets. Ignored when mode isolation is enabled; the credential/header selects the mode."},"payoutChainId":{"type":"number","nullable":true,"description":"Per-session normalize payout chain override (supported chain id)"},"payoutAddress":{"type":"string","nullable":true,"description":"Per-session normalize payout address override (EVM address)"},"stakingOverride":{"type":"string","nullable":true,"enum":["DEFAULT","STAKE","NORMALIZE","NO_STAKE",null],"default":"DEFAULT","description":"Per-session override of the merchant staking policy; DEFAULT or null inherits it"},"processingPaymentMode":{"type":"string","enum":["MERCHANT_ONLY","PAYER_ONLY","MERCHANT_THEN_PAYER"],"description":"Who pays each payment option's processing price for this session: MERCHANT_ONLY offers only options within the merchant's processing expense limit, PAYER_ONLY adds the price to the payer's amount, MERCHANT_THEN_PAYER lets the merchant pay within the limit and the payer above it. Omit to use the merchant's account setting."},"serviceFeePayer":{"type":"string","enum":["MERCHANT","PAYER"],"description":"Who pays this session's service fee: PAYER adds it to every option's amountDue, MERCHANT withholds it from the proceeds. Omit to use the merchant's account setting."},"metadata":{"type":"object","nullable":true,"maxProperties":20,"additionalProperties":{"type":"string","maxLength":500},"example":{"orderId":"order_8421","customerId":"cus_123"},"description":"Your correlation data, e.g. your order id under \"orderId\". Flat map of string values: up to 20 keys of 1-40 characters (A-Z, a-z, 0-9, \"_\", \".\", \"-\"; \"__proto__\", \"constructor\" and \"prototype\" are reserved), values up to 500 characters without control characters. Key order is not preserved. Returned on merchant session reads and in every webhook event; never shown to the payer or returned by the public session endpoints. Do not put secrets or personal data here."}},"required":["amount"]},"CreateSessionResponseDto":{"type":"object","properties":{"mode":{"type":"string","enum":["live","test"]},"id":{"type":"string","format":"uuid"},"paymentUrl":{"type":"string","example":"https://pay.stablecoinx.com/session/<id>"},"status":{"type":"string","enum":["settling","pending","awaiting_payment","partial","confirming","bridging","completed","failed","expired","cancelled"]},"expiresAt":{"type":"string","format":"date-time"},"createdAt":{"type":"string","format":"date-time"}},"required":["id","paymentUrl","status","expiresAt","createdAt"]},"JsonRpcRequestDto":{"type":"object","properties":{"jsonrpc":{"type":"string","example":"2.0","enum":["2.0"]},"id":{"description":"Request id echoed back in the response; null or missing is rejected with -32700","oneOf":[{"type":"number"},{"type":"string"}],"example":1},"method":{"type":"string","enum":["pm_sponsorUserOperation","pm_getPaymasterStubData","pm_getPaymasterData"],"example":"pm_sponsorUserOperation"},"params":{"type":"array","items":{},"description":"[userOp, entryPoint, chainId, context?] for every method — chainId as `0x` hex, decimal string or number; context is optional `{token}` for user-paid gas.","example":[{"sender":"0x…","nonce":"0x0","callData":"0xb61d27f6…"},"0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108","0x14a34"]}},"required":["jsonrpc","id","method","params"]},"JsonRpcResponseDto":{"type":"object","properties":{"jsonrpc":{"type":"string","example":"2.0"},"id":{"oneOf":[{"type":"number"},{"type":"string","nullable":true}],"example":1},"result":{"type":"object","description":"Present on success — method-specific result object","additionalProperties":true},"error":{"type":"object","description":"Present on error","properties":{"code":{"type":"number","example":-32601},"message":{"type":"string","example":"Unknown method: eth_chainId"}}}},"required":["jsonrpc","id"]},"BundlerJsonRpcRequestDto":{"type":"object","properties":{"jsonrpc":{"type":"string","enum":["2.0"],"example":"2.0"},"id":{"oneOf":[{"type":"number"},{"type":"string"}],"example":1},"method":{"type":"string","description":"ERC-4337 bundler method, forwarded unchanged; only `eth_sendUserOperation` is gated by sponsorship policy","example":"eth_sendUserOperation"},"params":{"type":"array","items":{},"description":"Method parameters as the bundler expects them, e.g. `[userOp, entryPoint]` for `eth_sendUserOperation`; omitted for parameterless methods such as `thirdweb_getUserOperationGasPrice`","example":[{"sender":"0x…","nonce":"0x0","callData":"0xb61d27f6…"},"0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108"]}},"required":["jsonrpc","id","method"]}}}}