openapi: 3.1.0 info: title: Sithril API version: "0.5.0" description: | Sithril is a stablecoin merchant acquirer. Use this API for agentic checkout on Solana (mainnet only): an agent turns a url and prompt into a Checkout Intent, you confirm it, and Sithril drives the merchant checkout and charges the card. ## Products - **Checkout Intents** — agentic checkout. An agent supplies a url and product, you confirm the quote, and Sithril drives the merchant checkout and charges a vaulted card. ## Conventions - Amounts are integer cents — `2500` is $25.00. Currency is always lowercase `usdc`. - Every object has a prefixed id (e.g. `ci_…`) and a `created` Unix timestamp in seconds. ## Authentication Most endpoints are open today. Authenticated routes accept either a dashboard session cookie or an API key. Create a key from the dashboard, or with `POST /v1/api_keys` from an existing session, then send it as `Authorization: Bearer sk_live_…`. The plaintext key is shown once. contact: name: Sithril url: https://sithril.com servers: - url: https://api.sithril.com description: Production tags: - name: API Keys description: Long-lived bearer credentials for server-to-server calls. Exchange a dashboard session for an sk_live_ key and send it in the Authorization header as a Bearer token. - name: Checkout Intents description: Agentic checkout driver — an agent supplies a url and product, and Sithril drives the merchant checkout and charges the card. components: securitySchemes: ApiKeyAuth: type: http scheme: bearer description: API key sent as a bearer token in the Authorization header (Bearer sk_live_…). Create one via `POST /v1/api_keys`. SessionCookie: type: apiKey in: cookie name: sithril_session description: Clerk-managed browser session cookie used by the hosted dashboard to authenticate requests. schemas: Product: type: object required: [id, object, active, created, metadata, name, type, amount, currency] properties: id: { type: string, example: prod_1Yt3vD8wJpX } object: { type: string, enum: [product] } active: { type: boolean, default: true } created: type: integer description: Unix timestamp (seconds). example: 1778667320 description: { type: string, nullable: true } images: type: array items: { type: string } metadata: type: object additionalProperties: { type: string } name: { type: string, example: T-Shirt } type: { type: string, enum: [good, service], default: service } unit_label: { type: string, nullable: true } updated: { type: integer } url: { type: string, nullable: true } default_price: type: string nullable: true description: | Set when the Product was created with `amount`/`default_price_data`. Null when the Product is bare. example: price_1Wh2sP6lFqM amount: type: integer description: | Convenience snapshot of `default_price.unit_amount` (0 when no default_price exists). example: 2500 currency: type: string enum: [usdc] example: usdc Price: type: object description: | A Price is a child of a Product. Prices are created with their Product (`POST /v1/products`) or inline via `price_data` on a line item; they are read-only otherwise and archived (`active: false`) via the Product. properties: id: { type: string, example: price_1Wh2sP6lFqM } object: { type: string, enum: [price] } active: { type: boolean } billing_scheme: { type: string, enum: [per_unit] } created: { type: integer } currency: { type: string, enum: [usdc], example: usdc } metadata: { type: object, additionalProperties: { type: string } } product: { type: string, example: prod_1Yt3vD8wJpX } type: { type: string, enum: [one_time] } unit_amount: { type: integer, example: 2500 } unit_amount_decimal: { type: string } CheckoutLinkLineItemInput: type: object description: | A line item. Must supply **exactly one** of `price`, `product`, or `product_data`. Mixing two or more is an `invalid_request_error`. required: [quantity] properties: quantity: type: integer minimum: 1 default: 1 oneOf: - title: Inline-create Product + Price required: [product_data] properties: product_data: type: object required: [name, price_data] description: Create a new Product and its default Price as part of this request. properties: name: { type: string, example: T-Shirt } price_data: type: object required: [unit_amount] properties: unit_amount: type: integer description: Minor units (cents). E.g. 2500 = $25.00 USDC. example: 2500 currency: type: string enum: [usdc] default: usdc - title: By Product id (uses default_price) required: [product] properties: product: type: string description: An existing Product token; the line is priced by its `default_price`. example: prod_1Yt3vD8wJpX - title: By Price id required: [price] properties: price: type: string description: An existing Price token. example: price_1Wh2sP6lFqM LineItem: type: object properties: id: { type: string, example: li_1Xk9mR2tNwP_0 } object: { type: string, enum: [item] } amount_discount: { type: integer, default: 0 } amount_subtotal: { type: integer } amount_tax: { type: integer, default: 0 } amount_total: { type: integer } currency: { type: string, enum: [usdc] } description: { type: string } price: allOf: [{ $ref: "#/components/schemas/Price" }] nullable: true quantity: { type: integer, minimum: 1 } product: type: string description: Direct product reference. LineItemList: type: object properties: object: { type: string, enum: [list] } data: type: array items: { $ref: "#/components/schemas/LineItem" } has_more: { type: boolean, default: false } url: { type: string } CheckoutLink: type: object required: [id, object, canceled_by, currency, url, line_items, metadata, created, recipient_address] properties: id: { type: string, example: cl_1Bq7zH4mKnR } object: { type: string, enum: [checkout_link] } canceled_by: type: string enum: [user] nullable: true description: | Null while the link is live. Set to `user` once a merchant cancels it (POST `/cancel`); a canceled link no longer creates new CheckoutSessions. currency: { type: string, enum: [usdc] } url: type: string description: | Public URL for this CL. Hitting it creates a new CheckoutSession every time. example: https://checkout.sithril.com/cl_1Bq7zH4mKnR line_items: { $ref: "#/components/schemas/LineItemList" } metadata: { type: object, additionalProperties: { type: string } } created: { type: integer, description: Unix seconds. } recipient_address: type: string description: Solana wallet that receives USDC for sessions created from this link. expires_at: type: integer nullable: true description: | Reserved for Free-vs-Premium expiry split. Not enforced yet. account_id: type: string nullable: true example: acct_1Mr8tH3wLnY CheckoutSession: type: object required: [id, object, amount_subtotal, amount_total, created, currency, line_items, metadata, payment_status, status, canceled_by, url, recipient_address, reference] properties: id: { type: string, example: cs_1Xk9mR2tNwP } object: { type: string, enum: [checkout.session] } amount_subtotal: { type: integer, example: 2500 } amount_total: { type: integer, example: 2500 } created: { type: integer, description: Unix seconds. } currency: { type: string, enum: [usdc] } line_items: { $ref: "#/components/schemas/LineItemList" } metadata: { type: object, additionalProperties: { type: string } } payment_status: { type: string, enum: [paid, unpaid, no_payment_required] } status: type: string enum: [open, complete, canceled] description: | Public wire enum. Maps from the internal state machine `open / completed / canceled` — `completed` renders as `complete` here. There is no time-based expiry today. canceled_by: type: string enum: [user] nullable: true description: | Null unless the session was canceled. `user` when the merchant canceled it (POST `/cancel`). url: type: string description: Hosted checkout page. Send this to the buyer. example: https://sithril.com/c/cs_1Xk9mR2tNwP recipient_address: type: string description: Solana wallet that receives the USDC. reference: type: string description: | The on-chain Solana Pay reference key the server polls to find the matching payment. Unique per CS. solana_pay_uri: type: string description: The `solana:` URI encoded in the QR. qr_data_url: type: string description: | Base64-encoded PNG QR for `solana_pay_uri`. Present on POST responses only. account_id: type: string nullable: true example: acct_1Mr8tH3wLnY checkout_link: type: string nullable: true description: The Checkout Link that created this session, if any. example: cl_1Bq7zH4mKnR TransactionIntent: type: object description: | The push-payment primitive. Born `requires_action` (payable immediately); settles to `succeeded` via the on-channel `/pay` door or an off-channel broadcast to `reference`. Both converge on one Transaction. A failed attempt is non-terminal — it stays `requires_action` with a `last_payment_error`. required: [id, object, status, amount, fee_amount, net_amount, currency, recipient_address, reference, payer_address, transaction, last_payment_error, next_action, metadata, canceled_by, canceled_at, created] properties: id: { type: string, example: ti_1Xk9mR2tNwP } object: { type: string, enum: [transaction_intent] } status: type: string enum: [requires_action, processing, succeeded, canceled] description: | `requires_action` → `processing` → `succeeded`; `requires_action` → `canceled` (merchant). A failed attempt stays non-terminal. amount: { type: integer, description: "Gross cents the payer is debited.", example: 2500 } fee_amount: { type: integer, description: "Sithril's fee, in cents.", example: 5 } net_amount: { type: integer, description: "amount − fee_amount. Goes to the merchant.", example: 2495 } currency: { type: string, enum: [usdc] } recipient_address: { type: string, description: Solana wallet that receives net_amount., example: FNNnGtRDnaBMEnGLVDkM5Zp2J5Tnk4QPMjtJt6hcLoq5 } reference: type: string description: On-chain Solana Pay reference that ties a payment to this TransactionIntent. Unique per TI. payer_address: type: string nullable: true description: Learned from the signature at settlement. transaction: type: string nullable: true description: The txn_ produced on settlement. Only present once `succeeded`; `null` otherwise. last_payment_error: type: object nullable: true description: Most recent failed attempt; cleared on success. properties: code: { type: string } message: { type: string } next_action: type: object nullable: true description: Present while `requires_action` — how to pay. properties: type: { type: string, enum: [solana_payment] } solana_payment: type: object properties: payable: type: object description: Structured payable (== the MPP `request` blob). Amounts in on-chain base units. solana_pay_uri: { type: string } qr_data_url: { type: string, description: SVG data URL — the solana_pay_uri rendered as a QR code. } hosted_url: { type: string } metadata: { type: object, additionalProperties: { type: string } } canceled_by: { type: string, enum: [user], nullable: true } canceled_at: { type: integer, nullable: true, description: Unix seconds. } created: { type: integer, description: Unix seconds. } example: id: ti_1Xk9mR2tNwP object: transaction_intent status: requires_action amount: 2500 fee_amount: 5 net_amount: 2495 currency: usdc recipient_address: FNNnGtRDnaBMEnGLVDkM5Zp2J5Tnk4QPMjtJt6hcLoq5 reference: 9WzkSxV4rTq2mNb7pYd3Ko payer_address: null transaction: null last_payment_error: null next_action: type: solana_payment solana_payment: solana_pay_uri: "solana:FNNnGtRDnaBMEnGLVDkM5Zp2J5Tnk4QPMjtJt6hcLoq5?amount=25.00&spl-token=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v&reference=9WzkSxV4rTq2mNb7pYd3Ko" qr_data_url: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0i…" hosted_url: "https://pay.sithril.com/i/ti_1Xk9mR2tNwP" metadata: {} canceled_by: null canceled_at: null created: 1780000000 Transaction: type: object description: | A recorded on-chain value movement. Bidirectional, so it carries an explicit `sender_address` / `recipient_address`. Server-created only; there is no `POST /v1/transactions`. Server-created by the product lifecycles: a gift produces a `gift_fund` transaction when funded and a `gift_release` when released; a completed Checkout Session produces a `checkout_session`. No `status` (a recorded transaction is succeeded) and no `metadata` (it lives on the source object). required: [id, object, type, source, signature, sender_address, recipient_address, amount, currency, account_id, created] properties: id: { type: string, example: txn_1Kp4nG7vQzS } object: { type: string, enum: [transaction] } type: type: string enum: [gift_fund, gift_release, checkout_session, transaction_intent] description: The product event that produced this transaction. source: type: string nullable: true description: The originating object (a gift, or a Checkout Session) — retrieve it for metadata and full detail. example: gift_1Xk9mR2tNwP signature: type: string description: Solana transaction signature (base58). Unique across all Transactions. sender_address: type: string description: The payer — the gift sender, or the buyer for a Checkout Session. recipient_address: type: string nullable: true description: The payee. Null for `gift_fund` (the recipient is unknown until release). amount: { type: integer, description: "Integer cents (usdc, 2dp).", example: 2500 } currency: { type: string, enum: [usdc] } account_id: type: string nullable: true description: The account that owns the source object. created: type: integer description: Unix seconds. On-chain confirmation time. Account: type: object required: [id, object, created, business_profile, username, metadata, wallet_address] properties: id: { type: string, example: acct_1Mr8tH3wLnY } object: { type: string, enum: [account] } created: { type: integer } business_profile: type: object properties: name: { type: string, example: "Ajax Inc." } username: type: string nullable: true maxLength: 32 description: | Free-form display label set by the account owner on the dashboard's settings page. Shown in the sidebar in place of the raw `acct_…` id; `null` until set. example: jsmith metadata: { type: object, additionalProperties: { type: string } } wallet_address: type: string description: Solana wallet owned by this account. Session: type: object required: [account] properties: account: { $ref: "#/components/schemas/Account" } ApiKey: type: object required: [id, object, name, last4, created, last_used_at, account_id] properties: id: { type: string, example: ak_1Mr8tH3wLnY } object: { type: string, enum: [api_key] } name: { type: string, example: prod server } last4: type: string description: Last 4 characters of the secret, for masked display (sk_live_…wQ2x). example: wQ2x created: { type: integer, description: Unix seconds. } last_used_at: type: integer nullable: true description: Unix seconds of the most recent successful auth with this key; null if never used. account_id: { type: string, example: acct_1Mr8tH3wLnY } secret: type: string description: Plaintext key, returned ONLY in the create response — never on list or retrieve. Store it securely; it cannot be recovered. example: sk_live_4xKf9mR2tNwPqZ7vB3cH8jL5nD6sG1aY0eW4uT2x ApiKeyList: allOf: - $ref: "#/components/schemas/List" - properties: data: type: array items: { $ref: "#/components/schemas/ApiKey" } Error: type: object required: [error] properties: error: type: object required: [type, message] properties: type: type: string enum: - api_error - invalid_request_error - idempotency_error - rate_limit_error - authentication_error code: { type: string, example: parameter_missing } message: { type: string } param: { type: string } doc_url: { type: string } List: type: object properties: object: { type: string, enum: [list] } data: { type: array, items: {} } has_more: { type: boolean, default: false } url: { type: string } Gift: type: object required: [id, object, status, amount, currency, sender_address, release_verification_key, created, metadata] properties: id: { type: string, example: gift_1Xk9mR2tNwP } object: { type: string, enum: [gift] } status: { type: string, enum: [requires_action, open, released, reclaimed] } next_action: description: | Present (with the unsigned fund tx) while `requires_action`. Response-only on release/reclaim. Null otherwise. oneOf: - $ref: "#/components/schemas/SubmitTransactionAction" - type: "null" amount: { type: integer, description: "Integer cents (usdc, 2dp).", example: 2500 } currency: { type: string, enum: [usdc], example: usdc } sender_address: { type: string } recipient_address: { type: string, nullable: true } release_verification_key: type: string description: base58 ed25519 public key the contract verifies releases against. funded_at: { type: integer, nullable: true, description: Unix seconds. } released_at: { type: integer, nullable: true, description: Unix seconds. } reclaimed_at: { type: integer, nullable: true, description: Unix seconds. } release_tx: { type: string, nullable: true, description: "On-chain release transaction signature, once released." } created: { type: integer, description: Unix seconds. } metadata: { type: object, additionalProperties: { type: string } } SubmitTransactionAction: type: object required: [type, transaction] properties: type: { type: string, enum: [submit_transaction] } transaction: type: object required: [serialized_transaction, signer_address, network_fee_lamports] properties: serialized_transaction: { type: string, description: base64-encoded unsigned transaction. } solana_pay_uri: { type: string } qr_data_url: { type: string } signer_address: { type: string } network_fee_lamports: { type: integer } CheckoutIntent: type: object required: [id, object, status, status_reason, next_action, url, prompt, options, merchant, max_amount, amount, currency, amount_breakdown, exceeds_ceiling, card, charge_id, created, metadata] properties: id: { type: string, example: ci_1Xk9mR2tNwP } object: { type: string, enum: [checkout_intent] } status: type: string enum: [requires_action, requires_confirmation, processing, succeeded, failed, canceled] description: | Drives one merchant checkout. Start in `requires_action` (an option is missing) or `requires_confirmation` (fully specified and quoted). `confirm` moves it through `processing` to `succeeded`/`failed`; `canceled` if abandoned before confirmation. status_reason: type: string nullable: true enum: [unsupported_checkout, card_declined, insufficient_balance, merchant_error, authentication_required, out_of_stock, user_canceled, sithril_canceled, expired] description: Cause of the current status, when there is one. next_action: description: What the agent must do next; null when none. oneOf: - $ref: "#/components/schemas/CheckoutIntentSelectOptions" - $ref: "#/components/schemas/CheckoutIntentPaymentAuthentication" - type: "null" url: { type: string, example: "https://acme.com", description: The checkout entry url. } prompt: { type: string, nullable: true, description: The natural-language product or action. } options: type: object additionalProperties: true description: Furnished option values, filled via the respond endpoint. merchant: { type: string, nullable: true, description: Resolved checkout-flow slug. } max_amount: { type: integer, nullable: true, description: "Agent spending ceiling, integer cents." } amount: { type: integer, nullable: true, description: "Quoted total, integer cents. Null until requires_confirmation." } currency: { type: string, enum: [usd, sgd], nullable: true, description: "Lowercase ISO 4217 (discovered from the merchant)." } amount_breakdown: type: object nullable: true additionalProperties: { type: integer } description: "Line items of the quote (integer cents)." exceeds_ceiling: { type: boolean, description: True when the quote exceeds max_amount. } card: { type: string, nullable: true, example: card_1Np6xK9rTbW, description: The funding card. } charge_id: { type: string, nullable: true, description: The settled charge / merchant order id, once succeeded. } created: { type: integer, description: Unix seconds. } metadata: { type: object, additionalProperties: { type: string } } CheckoutIntentSelectOptions: type: object required: [type, options_schema] properties: type: { type: string, enum: [select_options] } options_schema: type: object additionalProperties: true description: JSON Schema of the options still to supply; respond with matching values. CheckoutIntentPaymentAuthentication: type: object required: [type, payment_authentication] properties: type: { type: string, enum: [payment_authentication] } payment_authentication: type: object required: [url, return_url] properties: url: { type: string } return_url: { type: string } CheckoutIntentList: allOf: - $ref: "#/components/schemas/List" - properties: data: type: array items: { $ref: "#/components/schemas/CheckoutIntent" } responses: BadRequest: description: Invalid request. content: application/json: schema: { $ref: "#/components/schemas/Error" } NotFound: description: Resource not found. content: application/json: schema: { $ref: "#/components/schemas/Error" } Conflict: description: Resource is in a state that does not allow this action. content: application/json: schema: { $ref: "#/components/schemas/Error" } paths: /v1/api_keys: post: tags: [API Keys] summary: Create an API key security: - ApiKeyAuth: [] - SessionCookie: [] description: | Creates a long-lived bearer key. Authenticate the request with an existing session (the `sithril_session` dashboard cookie) or with an `Authorization` Bearer header carrying another key. The plaintext `secret` is returned exactly once, in this response. requestBody: required: false content: application/json: schema: type: object properties: name: { type: string, maxLength: 200, default: Secret key, description: Human label for the key. } responses: "200": description: Created. The body carries the one-time secret. content: application/json: schema: { $ref: "#/components/schemas/ApiKey" } "400": { $ref: "#/components/responses/BadRequest" } "401": { description: No valid session or API key. } get: tags: [API Keys] summary: List API keys security: - ApiKeyAuth: [] - SessionCookie: [] description: Lists the authenticated account's live (non-revoked) keys, newest first. Secrets are never included. responses: "200": description: OK. content: application/json: schema: { $ref: "#/components/schemas/ApiKeyList" } "401": { description: Not authenticated. } /v1/api_keys/{id}: delete: tags: [API Keys] summary: Revoke an API key security: - ApiKeyAuth: [] - SessionCookie: [] description: | Revokes a key by its `ak_…` id. Revocation is immediate — the key fails auth on the next request. Returns 404 for an unknown id, a key owned by another account, or one already revoked. parameters: - { name: id, in: path, required: true, schema: { type: string } } responses: "200": description: Revoked. content: application/json: schema: type: object properties: id: { type: string, example: ak_1Mr8tH3wLnY } object: { type: string, enum: [api_key] } deleted: { type: boolean, enum: [true] } "401": { description: Not authenticated. } "404": { $ref: "#/components/responses/NotFound" } /v1/checkout_intents: post: tags: [Checkout Intents] summary: Create a Checkout Intent security: - ApiKeyAuth: [] - SessionCookie: [] description: | Starts an agentic checkout at a `url` (resolved to a learned checkout flow) from a free-text `prompt` and/or structured `options`. Returns `requires_action` with a `select_options` when an option is missing, or `requires_confirmation` when fully specified and quoted. An unsupported url returns `failed` with `status_reason: unsupported_checkout`. The funding card is resolved server-side. Send an `Idempotency-Key` header to make retries safe. Also accepts form-encoded bodies. requestBody: required: true content: application/json: schema: type: object required: [url] properties: url: { type: string, example: "https://acme.com" } prompt: { type: string, example: "buy an ajax" } options: { type: object, additionalProperties: true, description: Pre-supplied option values. } max_amount: { type: integer, minimum: 1, description: "Spending ceiling, integer cents." } card: { type: string, description: A specific card_ id; defaults to the account's most-recent card. } responses: "200": description: Created. content: application/json: schema: { $ref: "#/components/schemas/CheckoutIntent" } "400": { $ref: "#/components/responses/BadRequest" } "401": { description: Not authenticated. } get: tags: [Checkout Intents] summary: List Checkout Intents security: - ApiKeyAuth: [] - SessionCookie: [] description: Lists the account's checkout intents, newest first. parameters: - { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 100, default: 25 } } - { name: starting_after, in: query, schema: { type: string } } - { name: ending_before, in: query, schema: { type: string } } responses: "200": description: OK. content: application/json: schema: { $ref: "#/components/schemas/CheckoutIntentList" } "401": { description: Not authenticated. } /v1/checkout_intents/{id}: get: tags: [Checkout Intents] summary: Retrieve a Checkout Intent security: - ApiKeyAuth: [] - SessionCookie: [] parameters: - { name: id, in: path, required: true, schema: { type: string } } responses: "200": description: OK. content: application/json: schema: { $ref: "#/components/schemas/CheckoutIntent" } "401": { description: Not authenticated. } "404": { $ref: "#/components/responses/NotFound" } /v1/checkout_intents/{id}/respond: post: tags: [Checkout Intents] summary: Respond to a select_options security: - ApiKeyAuth: [] - SessionCookie: [] description: | Answers the current `select_options` by merging the supplied `options` hash, then advances: stays `requires_action` while options remain, or moves to `requires_confirmation` once fully specified. Also accepts form-encoded bodies. parameters: - { name: id, in: path, required: true, schema: { type: string } } requestBody: required: true content: application/json: schema: type: object required: [options] properties: options: { type: object, additionalProperties: true } responses: "200": description: Updated. content: application/json: schema: { $ref: "#/components/schemas/CheckoutIntent" } "400": { $ref: "#/components/responses/BadRequest" } "401": { description: Not authenticated. } "404": { $ref: "#/components/responses/NotFound" } "409": { $ref: "#/components/responses/Conflict" } /v1/checkout_intents/{id}/confirm: post: tags: [Checkout Intents] summary: Confirm a Checkout Intent security: - ApiKeyAuth: [] - SessionCookie: [] description: | Confirms a `requires_confirmation` intent: drives the charge and resolves to `succeeded` (with `charge_id`) or `failed` (with `status_reason`). Send an `Idempotency-Key` header; retries are charge-safe. parameters: - { name: id, in: path, required: true, schema: { type: string } } responses: "200": description: Resolved. content: application/json: schema: { $ref: "#/components/schemas/CheckoutIntent" } "401": { description: Not authenticated. } "404": { $ref: "#/components/responses/NotFound" } "409": { $ref: "#/components/responses/Conflict" } /v1/checkout_intents/{id}/cancel: post: tags: [Checkout Intents] summary: Cancel a Checkout Intent security: - ApiKeyAuth: [] - SessionCookie: [] description: Abandons a checkout intent before it is confirmed. parameters: - { name: id, in: path, required: true, schema: { type: string } } responses: "200": description: Canceled. content: application/json: schema: { $ref: "#/components/schemas/CheckoutIntent" } "401": { description: Not authenticated. } "404": { $ref: "#/components/responses/NotFound" } "409": { $ref: "#/components/responses/Conflict" }