{"openapi":"3.0.3","info":{"title":"OutlinePay API","version":"1.0.0","description":"OutlinePay is a processor-agnostic payments API for merchant platforms looking to offer US payments. We provide headless capabilities for merchant management, checkout pages, payment links, refunds, disputes, and event webhooks.\n"},"servers":[{"url":"https://api.outlinepay.com","description":"Production"},{"url":"http://localhost:3000","description":"Local development"}],"tags":[{"name":"Merchants","x-object":"Merchant","description":"The businesses you accept payments for. A merchant becomes chargeable in three steps, in this order: create it, attach a processor to open its account, then have the merchant finish onboarding in an embedded component on your page. Charges are refused until the third step lands."},{"name":"Payments","x-object":"Payment","description":"A single charge. Creation returns a hosted checkout_url; the outcome arrives by webhook."},{"name":"Payment Links","x-object":"PaymentLink","description":"Reusable, shareable URLs that mint a fresh checkout session on every visit."},{"name":"Refunds","x-object":"Refund","description":"Return money for a succeeded payment, in full or in part."},{"name":"Disputes","x-object":"Dispute","description":"Chargebacks a cardholder opened with their bank. Read-only; evidence is submitted by the merchant in the processor's embedded component."},{"name":"Events","x-object":"Event","description":"The replayable log of everything OutlinePay has emitted for you."},{"name":"Webhook Endpoints","x-object":"WebhookEndpoint","description":"Where OutlinePay delivers signed events. The signing secret is shown once, at creation."}],"security":[{"bearerAuth":[]}],"paths":{"/v1/merchants":{"post":{"operationId":"merchants.create","x-conventions":["merchant-owned"],"tags":["Merchants"],"summary":"Create a merchant","description":"Creates a merchant and returns it with an empty `processors` list. This does not open a payment-processor account: that is a separate step, Attach a processor, so a merchant can exist before and independently of any processor.\n","x-notes":[{"label":"Prefill","body":"business is commercial detail you already hold. We store it and hand it to the processor later so onboarding stops asking the merchant for it. All of it is returned on the merchant."},{"label":"Not accepted here","body":"The forward-only tax_id and representative. You supply those when you attach a processor, because we never store them."},{"label":"Accuracy","body":"Prefilling is not attesting. The merchant, not you, is the party who confirms to the processor that these details are true, and every value you send stays editable during onboarding. Tell them which ones came from you so they can check each one."}],"x-emits":["merchant.created"],"x-idempotency":"optional","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MerchantCreateParams"},"example":{"name":"Acme Trading Co.","email":"owner@acme.example","country":"US","entity_type":"company","business":{"legal_name":"Acme Trading Co. LLC","structure":"llc","phone":"+12125550123","url":"https://acme.example","product_description":"Imported kitchenware and small appliances.","industry_code":"5722","statement_descriptor":"ACME TRADING","address":{"line1":"1600 Market St","city":"Philadelphia","state":"PA","postal_code":"19103","country":"US"}}}}}},"responses":{"200":{"description":"The created merchant, status \"created\", with no processor yet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Merchant"},"example":{"id":"mrc_9f2c8bAaZ1kQ","object":"merchant","name":"Acme Trading Co.","email":"owner@acme.example","status":"created","mode":"test","entity_type":"company","business":{"legal_name":"Acme Trading Co. LLC","structure":"llc","phone":"+12125550123","url":"https://acme.example","product_description":"Imported kitchenware and small appliances.","industry_code":"5722","statement_descriptor":"ACME TRADING","address":{"line1":"1600 Market St","city":"Philadelphia","state":"PA","postal_code":"19103","country":"US"}},"processors":[],"created_at":"2026-07-21T12:00:00Z"}}}}}},"get":{"operationId":"merchants.list","tags":["Merchants"],"summary":"List merchants","description":"Your merchants, newest first.","parameters":[{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/StartingAfter"}],"responses":{"200":{"description":"A page of merchants.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Merchant"}},"has_more":{"type":"boolean"}}}}}}}}},"/v1/merchants/{id}":{"get":{"operationId":"merchants.retrieve","tags":["Merchants"],"summary":"Retrieve a merchant","description":"The authoritative merchant record, including per-processor onboarding/verification state.","parameters":[{"name":"id","in":"path","required":true,"description":"The merchant id.","schema":{"type":"string"},"example":"mrc_9f2c8bAaZ1kQ"}],"responses":{"200":{"description":"The merchant.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Merchant"},"example":{"id":"mrc_9f2c8bAaZ1kQ","object":"merchant","name":"Acme Trading Co.","email":"owner@acme.example","status":"active","mode":"test","entity_type":"company","business":{"legal_name":"Acme Trading Co. LLC","structure":"llc","phone":"+12125550123","url":"https://acme.example","product_description":"Imported kitchenware and small appliances.","industry_code":"5722","statement_descriptor":"ACME TRADING","address":{"line1":"1600 Market St","city":"Philadelphia","state":"PA","postal_code":"19103","country":"US"}},"processors":[{"processor":"stripe","account_id":"acct_1AbCdEfGhIjKlMnO","onboarding_completed":true,"verified":true,"representative_prefilled":true}],"created_at":"2026-07-21T12:00:00Z"}}}},"404":{"description":"No merchant has this id in this mode. 404 for a path id, 400 for a passed one: https://outlinepay.com/docs#conventions-errors","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","details":[{"message":"No such merchant: \"mrc_9f2c8bAaZ1kQx\".","param":"id"}]}}}}}}},"patch":{"operationId":"merchants.update","x-conventions":["merchant-owned"],"tags":["Merchants"],"summary":"Update a merchant","description":"Updates the merchant's processor-agnostic business identity. Changes travel the other way too, with no call from you: when the merchant edits their details at the processor, we write them back here automatically.\n","x-notes":[{"label":"Merge semantics","body":"A field you omit is left unchanged, a field you send is set, and a field you send blank is cleared."},{"label":"Merchant-owned","body":"product_description, industry_code, statement_descriptor and phone are editable here only until a processor is attached. From then on the merchant owns them: they are edited in the account-management component and reach us through the inbound sync instead."},{"label":"Not accepted here","body":"The forward-only tax_id and representative, which belong to the attach endpoint."},{"label":"Propagation","body":"Stored here, then pushed to every attached processor account, matching fields best-effort: each processor receives the ones it models and anything it cannot model is still stored here, just not forwarded. Today legal_name, structure and address reach the processor only for a company, since an individual's identity is held as a person rather than a business."}],"x-emits":["merchant.updated"],"parameters":[{"name":"id","in":"path","required":true,"description":"The merchant id.","schema":{"type":"string"},"example":"mrc_9f2c8bAaZ1kQ"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MerchantUpdateParams"},"example":{"business":{"url":"https://acme.example","legal_name":"Acme Trading Co. LLC"}}}}},"responses":{"200":{"description":"The updated merchant.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Merchant"},"example":{"id":"mrc_9f2c8bAaZ1kQ","object":"merchant","name":"Acme Trading Co.","email":"owner@acme.example","status":"active","mode":"test","entity_type":"company","business":{"legal_name":"Acme Trading Co. LLC","structure":"llc","phone":"+12125550123","url":"https://acme.example","product_description":"Imported kitchenware and small appliances.","industry_code":"5722","statement_descriptor":"ACME TRADING","address":{"line1":"1600 Market St","city":"Philadelphia","state":"PA","postal_code":"19103","country":"US"}},"processors":[{"processor":"stripe","account_id":"acct_1AbCdEfGhIjKlMnO","onboarding_completed":true,"verified":true,"representative_prefilled":true}],"created_at":"2026-07-21T12:00:00Z"}}}},"400":{"description":"A forward-only field, an unknown field, or a bad value was sent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No merchant has this id in this mode. 404 for a path id, 400 for a passed one: https://outlinepay.com/docs#conventions-errors","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","details":[{"message":"No such merchant: \"mrc_9f2c8bAaZ1kQx\".","param":"id"}]}}}}}}}},"/v1/merchants/{merchant_id}/processors":{"post":{"operationId":"merchants.attach_processor","x-conventions":["forward-only","onboarding-required"],"tags":["Merchants"],"summary":"Attach a processor","description":"Opens the merchant's connected payment-processor account, the step that makes a merchant chargeable. Kept separate from Create a merchant so a merchant can exist before, and independently of, any processor.\n","x-notes":[{"label":"Forward-only identity","body":"This is where tax_id and the representative are supplied. We pass them to the processor and then discard them: never written down, never returned, so they cannot appear in a later GET, an event payload or a backup."},{"label":"Why it is worth sending","body":"Combined with the business stored at creation, it takes a US company's onboarding from 15 outstanding requirements down to 4. The merchant is left with its bank account, the last four digits of the representative's national ID, and the terms of service."},{"label":"What happens next","body":"The account is open but restricted, so this call does not make the merchant chargeable on its own: onboarding_completed and verified both come back false and status stays created. Create an account session next and render the onboarding component, which is where the processor collects what it still needs. Until that finishes, charges are refused with 409 merchant_not_onboarded."},{"label":"If the representative is rejected","body":"For a company, opening the account and registering the representative are two steps at the processor, so a representative it refuses can arrive after the account has opened. The account then stays attached: the 400 says so, and the merchant lists the processor with representative_prefilled false. Send the same request again with a corrected representative. The open account is reused, so retries never create a second one. The retry reads only the representative: the stored business went with the account when it opened, and a tax_id it did not carry is collected during onboarding."},{"label":"Already attached","body":"A merchant that already has an account for the given processor comes back as a 409, unless that account is still waiting for its representative, which resumes instead. The 409 names the state it found."},{"label":"Default rail","body":"The first processor a merchant attaches becomes its default_processor, used whenever a charge names none. It does not change on its own when you attach another."}],"x-emits":["merchant.updated"],"x-idempotency":"optional","parameters":[{"name":"merchant_id","in":"path","required":true,"description":"The merchant id.","schema":{"type":"string"},"example":"mrc_9f2c8bAaZ1kQ"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProcessorAttachParams"},"example":{"tax_id":"00-0000000","representative":{"first_name":"Dana","last_name":"Whitfield","email":"dana@acme.example","phone":"+12125550188","date_of_birth":"1985-04-27","address":{"line1":"220 Spruce St","city":"Philadelphia","state":"PA","postal_code":"19106","country":"US"}}}}}},"responses":{"200":{"description":"The merchant, now with a restricted processor account attached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Merchant"},"example":{"id":"mrc_9f2c8bAaZ1kQ","object":"merchant","name":"Acme Trading Co.","email":"owner@acme.example","status":"created","mode":"test","entity_type":"company","business":{"legal_name":"Acme Trading Co. LLC","structure":"llc","phone":"+12125550123","url":"https://acme.example","product_description":"Imported kitchenware and small appliances.","industry_code":"5722","statement_descriptor":"ACME TRADING","address":{"line1":"1600 Market St","city":"Philadelphia","state":"PA","postal_code":"19103","country":"US"}},"processors":[{"processor":"stripe","account_id":"acct_1AbCdEfGhIjKlMnO","onboarding_completed":false,"verified":false,"representative_prefilled":true}],"created_at":"2026-07-21T12:00:00Z"}}}},"400":{"description":"A bad value was sent, or the processor refused one when we forwarded it. The processor's own sentence then rides in processor_message. Fix the named field and resend: retrying unchanged cannot succeed. If the account had already opened when the rejection hit, the message says so, and resending finishes on that account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_request","details":[{"message":"The payment processor rejected representative.phone. Use E.164 format: '+', the country code, then the subscriber number, e.g. +12125550100, which is the US country code +1 followed by the 10-digit number. The stripe account was still opened and is attached to this merchant. Send this request again with a corrected representative to finish it on the same account. That retry reads only the representative.","param":"representative.phone","processor_message":"Phone number must follow the format of '+' sign, the country code, and the subscriber number.","doc_url":"https://outlinepay.com/docs#merchants-attach_processor"}]}}}}},"404":{"description":"No merchant has this id in this mode. 404 for a path id, 400 for a passed one: https://outlinepay.com/docs#conventions-errors","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","details":[{"message":"No such merchant: \"mrc_9f2c8bAaZ1kQx\".","param":"merchant_id"}]}}}}}}}},"/v1/merchants/{id}/account_session":{"post":{"operationId":"merchants.account_session","x-conventions":["onboarding-required","processor-agnostic"],"tags":["Merchants"],"summary":"Create an account session","description":"Returns a short-lived client_secret your page hands to the processor's embedded components, plus the publishable key and the merchant's locale. Your server is the authorization gate. It decides which logged-in merchant a session is for. See `components` below for the full set, named in OutlinePay's own vocabulary rather than the processor's.\n\nThis is the step that makes an attached merchant chargeable: the onboarding component is the only place a merchant completes its verification, so nothing can be charged until it has been rendered and finished. See the Quickstart for a worked example of both halves.\n","x-notes":[{"label":"Attach a processor first","body":"A session is scoped to the merchant's connected account, so it needs one to exist. Without it this returns 400 invalid_request, \"This merchant has no processor yet. Attach one first.\""},{"label":"What to do with the client_secret","body":"Hand it back to your own page and give it to the processor's SDK, then create the onboarding component. Two machines, two calls: your server makes this call so your secret key never reaches the browser, and your page asks an endpoint of yours for the secret."},{"label":"It expires, so mint a fresh one every time","body":"The session is short-lived and the SDK asks for a new secret by itself when it lapses mid-form, which is why it takes a function rather than a string. Point that function at an endpoint that calls this one afresh. Handing back a cached secret strands the merchant partway through onboarding."},{"label":"Do not send an Idempotency-Key","body":"Idempotency keys are ignored here: a replayed response would carry an expired client_secret, and creating a second session is harmless."},{"label":"How you know onboarding finished","body":"processors[].onboarding_completed turns true when the merchant submits, and processors[].verified turns true when the processor accepts. The merchant's status turns active at that moment and we emit merchant.updated. The component's own callbacks mean the form closed, not that the merchant was accepted, so read the merchant."},{"label":"Never refused for a merchant that has not onboarded","body":"Unlike the charge endpoints, this one is deliberately not gated. It is the way out of a 409 merchant_not_onboarded, so gating it would leave a merchant that has never onboarded unable to ever onboard."},{"label":"The onboarding component shows our merchant terms first","body":"When the response's terms.required is true, outlinepay.js frames terms.url ahead of the processor's form and the merchant accepts the OutlinePay merchant terms there, once, for every processor. You build nothing and record nothing. The merchant object's terms_acceptance and a merchant.updated event tell your systems it happened. If your Content-Security-Policy sets frame-src, allow the origin of terms.url."}],"parameters":[{"name":"id","in":"path","required":true,"description":"The merchant id.","schema":{"type":"string"},"example":"mrc_9f2c8bAaZ1kQ"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountSessionParams"},"example":{"components":["onboarding"]}}}},"responses":{"200":{"description":"The client secret and the components it is scoped to.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountSession"},"example":{"object":"account_session","processor":"stripe","client_secret":"accs_secret_9f2c8b...","publishable_key":"pk_test_51...","components":["onboarding"],"locale":"ko-KR","terms":{"required":true,"url":"https://outlinepay.com/embedded/merchant-terms?token=eyJfcmFpbHMi..."}}}}},"400":{"description":"The merchant has no processor attached yet, components is missing, or a component is unknown.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_request","details":[{"message":"This merchant has no processor yet. Attach one first."}]}}}}},"404":{"description":"No merchant has this id in this mode. 404 for a path id, 400 for a passed one: https://outlinepay.com/docs#conventions-errors","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","details":[{"message":"No such merchant: \"mrc_9f2c8bAaZ1kQx\".","param":"id"}]}}}}}}}},"/v1/payments":{"post":{"operationId":"payments.create","x-conventions":["onboarding-required","payment-methods","settlement-facts"],"tags":["Payments"],"summary":"Create a payment","description":"Creation is never a payment outcome. The response is status \"awaiting_payment\" plus an OutlinePay-owned checkout_url; whoever opens it completes the payment on the processor's hosted page. The terminal state (succeeded/failed/expired) arrives by webhook, with Retrieve a payment as the polling fallback. Point success_url and cancel_url at endpoints you control. The redirect is navigation, not proof of payment.\n","x-notes":[{"label":"Onboarding required","body":"The merchant must have finished onboarding before you can charge it. Until the processor verifies the account this returns 409 merchant_not_onboarded and no payment is created. Put the merchant through Create an account session first; its status turns active and we emit merchant.updated when it can take money."}],"x-idempotency":"optional","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentCreateParams"},"example":{"merchant_id":"mrc_9f2c8bAaZ1kQ","amount":10000,"currency":"usd","description":"Order","customer":{"email":"buyer@example.com"},"success_url":"https://pg.cookiepay.example/op/result","cancel_url":"https://pg.cookiepay.example/op/cancel"}}}},"responses":{"200":{"description":"The payment, awaiting_payment, with a checkout_url.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Payment"},"example":{"id":"pay_3Nk8Qh2Lmp7T","object":"payment","status":"awaiting_payment","mode":"test","merchant_id":"mrc_9f2c8bAaZ1kQ","amount":10000,"currency":"usd","description":"Order","customer":{"email":"buyer@example.com"},"fees":{"processor":null,"outline":50,"partner":100,"dispute":0,"invoice":null,"total":150},"merchant_net":9850,"amount_refunded":0,"amount_refundable":10000,"refunds":[],"disputes":[],"processor":"stripe","processor_payment_method":null,"selected_payment_method":null,"card":null,"bank_verification":null,"payment_link_id":null,"checkout_url":"https://checkout.outlinepay.com/c/pay_3Nk8Qh2Lmp7T","receipt_url":null,"invoice_url":null,"expires_at":"2026-07-22T12:45:00Z","processing_at":null,"succeeded_at":null,"failed_at":null,"expired_at":null,"failure_reason":null,"created_at":"2026-07-21T12:45:00Z"}}}},"409":{"description":"The merchant cannot be charged yet: merchant_not_onboarded until the processor has verified it, or merchant_terms_not_accepted if it was verified without ever seeing the OutlinePay merchant terms (the onboarding component shows them, so this only happens to integrations that bypass outlinepay.js). Both are fixed the same way, by putting the merchant through the onboarding component.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"merchant_not_onboarded","details":[{"message":"Merchant mrc_9f2c8bAaZ1kQ has not completed onboarding with stripe, so it cannot accept payments. Call POST /v1/merchants/mrc_9f2c8bAaZ1kQ/account_session and render the returned client_secret with the onboarding component, so the merchant can finish. The merchant becomes chargeable when its status turns active, which we announce with a merchant.updated event.","doc_url":"https://outlinepay.com/docs#merchants-account_session"}]}}}}},"400":{"description":"merchant_id names no merchant in this mode. 404 for a path id, 400 for a passed one: https://outlinepay.com/docs#conventions-errors","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","details":[{"message":"No such merchant: \"mrc_9f2c8bAaZ1kQx\".","param":"merchant_id"}]}}}}}}},"get":{"operationId":"payments.list","tags":["Payments"],"summary":"List payments","description":"Your payments, newest first. Filter by merchant_id and status.","parameters":[{"name":"merchant_id","in":"query","description":"Only payments for this merchant.","schema":{"type":"string"},"example":"mrc_9f2c8bAaZ1kQ"},{"name":"status","in":"query","description":"Only payments in this state.","schema":{"$ref":"#/components/schemas/PaymentStatus"}},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/StartingAfter"}],"responses":{"200":{"description":"A page of payments.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Payment"}},"has_more":{"type":"boolean"}}}}}},"400":{"description":"merchant_id names no merchant in this mode. 404 for a path id, 400 for a passed one: https://outlinepay.com/docs#conventions-errors","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","details":[{"message":"No such merchant: \"mrc_9f2c8bAaZ1kQx\".","param":"merchant_id"}]}}}}}}}},"/v1/payments/{id}":{"get":{"operationId":"payments.retrieve","x-conventions":["settlement-facts","disputes"],"tags":["Payments"],"summary":"Retrieve a payment","description":"The authoritative status of a payment, the polling fallback to the webhook.","parameters":[{"name":"id","in":"path","required":true,"description":"The payment id.","schema":{"type":"string"},"example":"pay_3Nk8Qh2Lmp7T"}],"responses":{"200":{"description":"The payment.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Payment"},"example":{"id":"pay_3Nk8Qh2Lmp7T","object":"payment","status":"awaiting_payment","mode":"test","merchant_id":"mrc_9f2c8bAaZ1kQ","amount":10000,"currency":"usd","description":"Order","customer":{"email":"buyer@example.com"},"fees":{"processor":null,"outline":50,"partner":100,"dispute":0,"invoice":null,"total":150},"merchant_net":9850,"amount_refunded":0,"amount_refundable":10000,"refunds":[],"disputes":[],"processor":"stripe","processor_payment_method":null,"selected_payment_method":null,"card":null,"bank_verification":null,"payment_link_id":null,"checkout_url":"https://checkout.outlinepay.com/c/pay_3Nk8Qh2Lmp7T","receipt_url":null,"invoice_url":null,"expires_at":"2026-07-22T12:45:00Z","processing_at":null,"succeeded_at":null,"failed_at":null,"expired_at":null,"failure_reason":null,"created_at":"2026-07-21T12:45:00Z"}}}},"404":{"description":"No payment has this id in this mode. 404 for a path id, 400 for a passed one: https://outlinepay.com/docs#conventions-errors","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","details":[{"message":"No such payment: \"pay_3Nk8Qh2Lmp7Tx\".","param":"id"}]}}}}}}}},"/v1/payment_links":{"post":{"operationId":"payment_links.create","x-conventions":["onboarding-required","payment-methods"],"tags":["Payment Links"],"summary":"Create a payment link","description":"Returns a permanent OutlinePay-owned URL. No processor session is minted here. The first click does that. Each completed payment appears as its own payment object with its own webhook.\n","x-notes":[{"label":"Onboarding required","body":"A link is only worth handing out if the merchant behind it can be paid, so this is gated exactly like Create a payment: until the processor verifies the account it returns 409 merchant_not_onboarded and no link is created. Otherwise the first customer to click would be the one to discover the merchant isn't ready."}],"x-emits":["payment_link.created"],"x-idempotency":"optional","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentLinkCreateParams"},"example":{"merchant_id":"mrc_9f2c8bAaZ1kQ","amount":4900,"currency":"usd","description":"Consulting session"}}}},"responses":{"200":{"description":"The active payment link.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentLink"},"example":{"id":"link_7Hq2Rf9wKc1V","object":"payment_link","url":"https://buy.outlinepay.com/l/link_7Hq2Rf9wKc1V","status":"active","mode":"test","merchant_id":"mrc_9f2c8bAaZ1kQ","amount":4900,"currency":"usd","description":"Consulting session","created_at":"2026-07-21T12:00:00Z"}}}},"409":{"description":"The merchant cannot be charged yet (merchant_not_onboarded or merchant_terms_not_accepted, as on Create a payment), so no link is created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"merchant_not_onboarded","details":[{"message":"Merchant mrc_9f2c8bAaZ1kQ has not completed onboarding with stripe, so it cannot accept payments. Call POST /v1/merchants/mrc_9f2c8bAaZ1kQ/account_session and render the returned client_secret with the onboarding component, so the merchant can finish. The merchant becomes chargeable when its status turns active, which we announce with a merchant.updated event.","doc_url":"https://outlinepay.com/docs#merchants-account_session"}]}}}}}}},"get":{"operationId":"payment_links.list","tags":["Payment Links"],"summary":"List payment links","description":"Your payment links, newest first. Filter by merchant_id.","parameters":[{"name":"merchant_id","in":"query","description":"Only links for this merchant.","schema":{"type":"string"},"example":"mrc_9f2c8bAaZ1kQ"},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/StartingAfter"}],"responses":{"200":{"description":"A page of payment links.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/PaymentLink"}},"has_more":{"type":"boolean"}}}}}}}}},"/v1/payment_links/{id}":{"get":{"operationId":"payment_links.retrieve","tags":["Payment Links"],"summary":"Retrieve a payment link","description":"The authoritative state of a link, including whether it is still active.","parameters":[{"name":"id","in":"path","required":true,"description":"The payment link id.","schema":{"type":"string"},"example":"link_7Hq2Rf9wKc1V"}],"responses":{"200":{"description":"The payment link.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentLink"},"example":{"id":"link_7Hq2Rf9wKc1V","object":"payment_link","url":"https://buy.outlinepay.com/l/link_7Hq2Rf9wKc1V","status":"active","mode":"test","merchant_id":"mrc_9f2c8bAaZ1kQ","amount":4900,"currency":"usd","description":"Consulting session","created_at":"2026-07-21T12:00:00Z"}}}},"404":{"description":"No payment link has this id in this mode. 404 for a path id, 400 for a passed one: https://outlinepay.com/docs#conventions-errors","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","details":[{"message":"No such payment link: \"link_7Hq2Rf9wKc1Vx\".","param":"id"}]}}}}}}}},"/v1/payment_links/{id}/deactivate":{"post":{"operationId":"payment_links.deactivate","tags":["Payment Links"],"summary":"Deactivate a payment link","description":"Kills a link already in the wild. Later clicks get a clean \"no longer active\" page.\n","x-emits":["payment_link.deactivated"],"x-idempotency":"optional","parameters":[{"name":"id","in":"path","required":true,"description":"The payment link id.","schema":{"type":"string"},"example":"link_7Hq2Rf9wKc1V"}],"responses":{"200":{"description":"The link, now deactivated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentLink"}}}},"404":{"description":"No payment link has this id in this mode. 404 for a path id, 400 for a passed one: https://outlinepay.com/docs#conventions-errors","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","details":[{"message":"No such payment link: \"link_7Hq2Rf9wKc1Vx\".","param":"id"}]}}}}}}}},"/v1/refunds":{"post":{"operationId":"refunds.create","x-conventions":["disputes"],"x-emits":["refund.completed"],"tags":["Refunds"],"summary":"Create a refund","description":"Refunds a succeeded payment. Usually pending here and resolved by the refund.completed / refund.failed webhook. The platform fee is not returned.\n","x-notes":[{"label":"Amount is in the smallest currency unit","body":"An integer, like every amount in this API: 2500 is $25.00 and 1 is one cent. Omit it to refund whatever is still owed, which is the payment's amount_refundable and not its amount. Several partial refunds are allowed as long as they add up to no more than that, so check amount_refundable rather than assuming a payment is still whole."},{"label":"Only succeeded payments","body":"A payment that never succeeded has nothing to return, so this refuses with 400 invalid_request rather than creating a refund that could not settle. A fully refunded payment has amount_refundable 0 and refuses the same way."}],"x-idempotency":"required","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefundCreateParams"},"example":{"payment_id":"pay_3Nk8Qh2Lmp7T","amount":2500}}}},"responses":{"200":{"description":"The refund.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Refund"},"example":{"id":"re_5Fp1Xy8nQd2W","object":"refund","status":"pending","mode":"test","payment_id":"pay_3Nk8Qh2Lmp7T","merchant_id":"mrc_9f2c8bAaZ1kQ","amount":2500,"created_at":"2026-07-21T12:10:00Z"}}}},"400":{"description":"payment_id names no payment in this mode. 404 for a path id, 400 for a passed one: https://outlinepay.com/docs#conventions-errors","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","details":[{"message":"No such payment: \"pay_3Nk8Qh2Lmp7Ta\".","param":"payment_id"}]}}}}}}},"get":{"operationId":"refunds.list","tags":["Refunds"],"summary":"List refunds","description":"Your refunds across every payment, newest first. A payment already carries its own refunds inline, so reach for this when you do not have a payment in hand: reconciling a day's returns, or reporting over a window. Filter by payment_id to narrow it to one payment.\n","x-notes":[{"label":"Two views of the same refund","body":"A payment's refunds[] array is the lean view (id, status, amount, metadata, created_at) because it is there to tell you what happened to that one payment. This endpoint returns the full Refund object, which additionally names its payment, merchant, mode and processor. The ids match, so either can be followed to the other."}],"parameters":[{"name":"payment_id","in":"query","description":"Only refunds against this payment.","schema":{"type":"string"},"example":"pay_3Nk8Qh2Lmp7T"},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/StartingAfter"}],"responses":{"200":{"description":"A page of refunds.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Refund"}},"has_more":{"type":"boolean"}}}}}},"400":{"description":"payment_id names no payment in this mode. 404 for a path id, 400 for a passed one: https://outlinepay.com/docs#conventions-errors","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","details":[{"message":"No such payment: \"pay_3Nk8Qh2Lmp7Ta\".","param":"payment_id"}]}}}}}}}},"/v1/refunds/{id}":{"get":{"operationId":"refunds.retrieve","tags":["Refunds"],"summary":"Retrieve a refund","description":"The authoritative state of a refund, the polling fallback to the webhook.","parameters":[{"name":"id","in":"path","required":true,"description":"The refund id.","schema":{"type":"string"},"example":"re_5Fp1Xy8nQd2W"}],"responses":{"200":{"description":"The refund.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Refund"},"example":{"id":"re_5Fp1Xy8nQd2W","object":"refund","status":"pending","mode":"test","payment_id":"pay_3Nk8Qh2Lmp7T","merchant_id":"mrc_9f2c8bAaZ1kQ","amount":2500,"created_at":"2026-07-21T12:10:00Z"}}}},"404":{"description":"No refund has this id in this mode. 404 for a path id, 400 for a passed one: https://outlinepay.com/docs#conventions-errors","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","details":[{"message":"No such refund: \"re_5Fp1Xy8nQd2Wx\".","param":"id"}]}}}}}}}},"/v1/disputes":{"get":{"operationId":"disputes.list","x-conventions":["disputes"],"tags":["Disputes"],"summary":"List disputes","description":"Your disputes, newest first. Usually empty: a dispute exists only when a cardholder has opened a chargeback.","parameters":[{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/StartingAfter"}],"responses":{"200":{"description":"A page of disputes.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Dispute"}},"has_more":{"type":"boolean"}}}}}}}}},"/v1/disputes/{id}":{"get":{"operationId":"disputes.retrieve","x-conventions":["disputes"],"tags":["Disputes"],"summary":"Retrieve a dispute","description":"A single dispute, including its evidence deadline. Read-only.","parameters":[{"name":"id","in":"path","required":true,"description":"The dispute id.","schema":{"type":"string"},"example":"dp_7Hq2Rf9wKc1V"}],"responses":{"200":{"description":"The dispute.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Dispute"},"example":{"id":"dp_7Hq2Rf9wKc1V","object":"dispute","status":"needs_response","mode":"test","payment_id":"pay_3Nk8Qh2Lmp7T","merchant_id":"mrc_9f2c8bAaZ1kQ","reason":"fraudulent","amount":10000,"fee":1500,"amount_withheld":10000,"evidence_due_at":"2026-08-04T23:59:59Z","created_at":"2026-07-21T13:00:00Z"}}}},"404":{"description":"No dispute has this id in this mode. 404 for a path id, 400 for a passed one: https://outlinepay.com/docs#conventions-errors","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","details":[{"message":"No such dispute: \"dp_7Hq2Rf9wKc1Vx\".","param":"id"}]}}}}}}}},"/v1/events":{"get":{"operationId":"events.list","x-conventions":["webhook-delivery"],"tags":["Events"],"summary":"List events","description":"Everything OutlinePay has emitted for you, newest first. Replay past events any time. Filter by type.","parameters":[{"name":"type","in":"query","description":"Only events of this type.","schema":{"$ref":"#/components/schemas/EventType"}},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/StartingAfter"}],"responses":{"200":{"description":"A page of events.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Event"}},"has_more":{"type":"boolean"}}}}}}}}},"/v1/events/{id}":{"get":{"operationId":"events.retrieve","x-conventions":["webhook-delivery"],"tags":["Events"],"summary":"Retrieve an event","description":"A single event exactly as it was emitted, for replaying or verifying a delivery.","parameters":[{"name":"id","in":"path","required":true,"description":"The event id.","schema":{"type":"string"},"example":"evt_9sK4Tb3vLm8P"}],"responses":{"200":{"description":"The event envelope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Event"},"example":{"id":"evt_9sK4Tb3vLm8P","object":"event","type":"payment.succeeded","mode":"test","created_at":"2026-07-21T13:02:11Z","data":{"id":"pay_3Nk8Qh2Lmp7T","object":"payment","status":"awaiting_payment","mode":"test","merchant_id":"mrc_9f2c8bAaZ1kQ","amount":10000,"currency":"usd","description":"Order","customer":{"email":"buyer@example.com"},"fees":{"processor":null,"outline":50,"partner":100,"dispute":0,"invoice":null,"total":150},"merchant_net":9850,"amount_refunded":0,"amount_refundable":10000,"refunds":[],"disputes":[],"processor":"stripe","processor_payment_method":null,"selected_payment_method":null,"card":null,"bank_verification":null,"payment_link_id":null,"checkout_url":"https://checkout.outlinepay.com/c/pay_3Nk8Qh2Lmp7T","receipt_url":null,"invoice_url":null,"expires_at":"2026-07-22T12:45:00Z","processing_at":null,"succeeded_at":null,"failed_at":null,"expired_at":null,"failure_reason":null,"created_at":"2026-07-21T12:45:00Z"}}}}},"404":{"description":"No event has this id in this mode. 404 for a path id, 400 for a passed one: https://outlinepay.com/docs#conventions-errors","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","details":[{"message":"No such event: \"evt_9sK4Tb3vLm8Px\".","param":"id"}]}}}}}}}},"/v1/webhook_endpoints":{"post":{"operationId":"webhook_endpoints.create","x-conventions":["webhook-delivery"],"tags":["Webhook Endpoints"],"summary":"Create a webhook endpoint","description":"Subscribes an HTTPS URL to your events. The response reveals the signing secret exactly once. Store it, and use it to verify the OutlinePay-Signature header on every delivery. Each mode allows up to 16 active endpoints; past that, creation fails with invalid_request until you delete one.\n","x-idempotency":"optional","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointCreateParams"},"example":{"url":"https://pg.cookiepay.example/outlinepay/webhook"}}}},"responses":{"200":{"description":"The endpoint, with its signing secret (shown only here).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpoint"},"example":{"id":"we_2Bd7Nq4pRs9K","object":"webhook_endpoint","url":"https://pg.cookiepay.example/outlinepay/webhook","status":"active","mode":"test","secret":"olp_whsec_YOUR_SIGNING_SECRET","created_at":"2026-07-21T12:00:00Z"}}}}}},"get":{"operationId":"webhook_endpoints.list","tags":["Webhook Endpoints"],"summary":"List webhook endpoints","description":"Your endpoints, newest first. The signing secret is never included here.","parameters":[{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/StartingAfter"}],"responses":{"200":{"description":"A page of endpoints.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/WebhookEndpoint"}},"has_more":{"type":"boolean"}}}}}}}}},"/v1/webhook_endpoints/{id}":{"delete":{"operationId":"webhook_endpoints.delete","tags":["Webhook Endpoints"],"summary":"Delete a webhook endpoint","description":"Deletes the endpoint. Deliveries stop immediately, it disappears from the list, its slot under the 16-endpoint cap frees up, and its id is gone: a repeat call answers 404. There is no undo; to start delivering to the same URL again, create a new endpoint (it will have a new signing secret).\n","parameters":[{"name":"id","in":"path","required":true,"description":"The webhook endpoint id.","schema":{"type":"string"},"example":"we_2Bd7Nq4pRs9K"}],"responses":{"200":{"description":"A deletion tombstone.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Deleted"},"example":{"id":"we_2Bd7Nq4pRs9K","object":"webhook_endpoint","deleted":true}}}},"404":{"description":"No webhook endpoint has this id in this mode, or it was already deleted. 404 for a path id, 400 for a passed one: https://outlinepay.com/docs#conventions-errors","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","details":[{"message":"No such webhook endpoint: \"we_2Bd7Nq4pRs9Kx\".","param":"id"}]}}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Pass your partner secret key as a Bearer token: Authorization: Bearer olp_sk_test_...  (test) or olp_sk_live_...  (live). Keys are shown once at creation and stored hashed.\n"}},"parameters":{"Limit":{"name":"limit","in":"query","description":"Page size, 1–100 (default 20).","schema":{"type":"integer","minimum":1,"maximum":100,"default":20},"example":20},"StartingAfter":{"name":"starting_after","in":"query","description":"A resource id; returns records after it (newest-first cursor).","schema":{"type":"string"},"example":"pay_3Nk8Qh2Lmp7T"}},"schemas":{"ErrorCode":{"type":"string","description":"Machine-readable error code.","enum":["invalid_request","authentication_error","not_found","merchant_not_onboarded","merchant_terms_not_accepted","idempotency_conflict","rate_limited","processor_error"]},"EventType":{"type":"string","description":"The outbound event catalog.\n\n`payment.updated` is the only type that is not a change of state. It fires a moment after a payment's outcome, carrying the facts that can only be read off the charge: the processor's own fee, the method that settled, and the card behind a card payment. All are still null on the outcome event itself, so record net from `payment.updated`, not from `payment.succeeded`. The full rule is under \"Fees arrive just after the outcome\" in Conventions.","enum":["merchant.created","merchant.updated","payment.processing","payment.succeeded","payment.failed","payment.expired","payment.updated","payment_link.created","payment_link.deactivated","refund.completed","refund.failed","dispute.created","dispute.updated","dispute.closed"]},"Mode":{"type":"string","description":"The world this object lives in, test (sandbox) or live. Matches your API key's prefix (olp_sk_test_ / olp_sk_live_).","enum":["test","live"]},"PaymentStatus":{"type":"string","description":"Where the payment is. Both lifecycles are drawn under The Payment object.\n\n`awaiting_payment`: the customer has not finished. A declined card does not end this state; the customer simply retries on the checkout page.\n\n`processing`: the customer has paid but the money is still moving. Only delayed methods (bank_debit, bank_transfer) reach it, it can last days, and nothing is decided yet, so do not fulfill on it.\n\n`failed`: reachable only from `processing`, so only on delayed methods. `failure_reason` says why.","enum":["awaiting_payment","processing","succeeded","failed","expired"]},"PaymentLinkStatus":{"type":"string","enum":["active","deactivated"]},"RefundStatus":{"type":"string","enum":["pending","succeeded","failed"]},"DisputeReason":{"type":"string","nullable":true,"description":"Why the cardholder disputed, in OutlinePay's vocabulary. Reason codes originate with the card networks but each processor normalises them differently, so we publish one set and fold theirs into it: bank_error covers the several ways a bank can decline to move the money, and anything we cannot place maps to other.","enum":["fraudulent","product_not_received","product_unacceptable","duplicate","credit_not_processed","subscription_canceled","unrecognized","customer_initiated","bank_error","other"]},"DisputeStatus":{"type":"string","description":"The chargeback's state, in OutlinePay's vocabulary and translated from whichever processor reported it. The `inquiry_*` set is the card networks' early retrieval request, raised before a formal chargeback; the rest is the real lifecycle, terminating in `won`/`lost`, or `refunded` when the merchant refunds instead of contesting.","enum":["inquiry_needs_response","inquiry_under_review","inquiry_closed","needs_response","under_review","won","lost","refunded"]},"BankVerification":{"type":"string","description":"How a customer's bank account must be proven to be theirs before it can be debited. Applies to `bank_debit` and is ignored on a checkout that offers no bank method.\n\n`instant_or_delayed` (default): the customer links their bank instantly, or, if they'd rather not or their bank can't be reached, enters the account number and confirms two small deposits instead. That second path takes 1-2 business days for the deposits to arrive and gives them 10 days to enter them, so the payment can sit in `processing` for a fortnight. Accepts the most customers.\n\n`instant`: instant linking only. You learn within seconds whether the account is good, which is what you want if you ship on payment. The cost is real: a customer whose bank can't be reached this way can't pay by bank at all, so they either use another method or don't buy.","enum":["instant_or_delayed","instant"]},"PaymentFailureReason":{"type":"string","description":"Why a payment failed, in OutlinePay's vocabulary, translated from the processor's own failure code at the boundary.\n\n`insufficient_funds`: the account didn't have the money. Retry later, the account itself is fine.\n\n`account_closed`: the account no longer exists. Ask for different details.\n\n`account_details_invalid`: the account or routing number was wrong, or a linked account's number was deactivated. Ask for correct details.\n\n`not_authorized`: there's no valid authorization to debit this account, usually because the customer disputed an earlier debit. Collect a fresh authorization before trying again.\n\n`account_blocked`: the customer's bank is refusing debits, frozen or restricted. The customer has to sort it out with their bank.\n\n`other`: the processor gave a reason we don't have a word for, or gave none. Treat it as a failure with no further guidance.","enum":["insufficient_funds","account_closed","account_details_invalid","not_authorized","account_blocked","other"]},"PaymentMethod":{"type":"string","description":"A payment-method category a partner may offer, in OutlinePay's vocabulary, translated to the processor's own methods at the boundary.\n\n`card` is debit and credit cards. The card-based wallets (Apple Pay, Google Pay, Link) ride along automatically and cannot be offered without cards. `wallet` is the standalone wallets (Cash App, Amazon Pay). `bnpl` is buy-now-pay-later (Klarna, Affirm, Afterpay). `bank_debit` is ACH Direct Debit, paid from a US bank account. `bank_transfer` is a push transfer: the customer is shown a virtual account number and sends the funds from their own bank.\n\nThe two bank rails behave differently from the rest. They settle after the customer finishes, so the payment sits in `processing` first, for days if the transfer is slow, and `bank_debit` can genuinely reach `failed`. Both OutlinePay's fee and yours are capped on them, so offering one beside an uncapped method sends the customer to a page asking which they intend to use before the price is fixed.\n\n`bank_transfer` additionally requires `customer.email` at creation (the virtual account number and any refund follow a named buyer, and the same buyer keeps the same account number across payments), cannot be offered on a payment link, and is available to US merchants only for now. See \"Payment methods\" under Conventions.\n\nProvide an array to offer exactly those categories, nothing else. Omit the field and you get the documented default of `[\"card\", \"wallet\"]`.","enum":["card","wallet","bnpl","bank_debit","bank_transfer"]},"MerchantStatus":{"type":"string","description":"Where the merchant stands with its processors: the field to read for whether it can charge today.\n\n`created`: no processor has verified the merchant yet. This one value covers both a merchant that has not started onboarding and one whose submission is still under review. To tell those apart, read processors[].onboarding_completed.\n\n`active`: a processor has verified the merchant and it can accept payments.\n\n`restricted`: a processor verified the merchant earlier but has since limited the account, usually because it now wants more information. Only the merchant can clear it, by answering the processor in the embedded components. A merchant that was never verified stays `created`, and processors[].verified never returns to false, so `restricted` is the only signal that a merchant which used to be chargeable no longer is.\n\nThe API does not carry the reason for a restriction. The processor delivers it to the merchant in the `notifications` component, which is also the only place the merchant can answer it. So when you see `restricted`, put the merchant in front of `notifications` and `account_management` (see Create an account session); we emit merchant.updated when the status returns to active.","enum":["created","active","restricted"]},"WebhookEndpointStatus":{"type":"string","description":"Always \"active\" today: a deleted endpoint leaves the API entirely rather than appearing as disabled.","enum":["active","disabled"]},"Error":{"type":"object","properties":{"error":{"type":"object","description":"`code` classifies the refusal, `details` describes it, and nothing else appears at this level.","properties":{"code":{"$ref":"#/components/schemas/ErrorCode"},"details":{"type":"array","minItems":1,"description":"One entry per problem found, never empty. Exactly one entry on every refusal except write-time request validation, where each field we refused is its own entry: unknown fields first, then the value checks, in this reference's field order, so the same request always answers with the same list. Every entry shares the top-level `code`. The list is every problem we found; the processor can still refuse a value when we forward one, and answers one field per call.","items":{"type":"object","properties":{"message":{"type":"string","description":"A human-readable sentence. Always present."},"param":{"type":"string","description":"Present when the entry is about exactly one request field: that field's name, in the API's spelling (`customer.email`, `amount`, `processor`), so you can map the refusal onto your own form without parsing `message`."},"processor_message":{"type":"string","description":"The payment processor's own explanation, passed through unchanged. Rides on processor_error, and on an invalid_request the processor raised by refusing a value you sent. `message` is always OutlinePay's; this is the processor's, so do not parse it as part of our contract."},"doc_url":{"type":"string","description":"Present when clearing the error means calling a different endpoint than the one that just failed: a link straight to the section of this reference that explains it."}}}}}}}},"Payment":{"type":"object","properties":{"id":{"type":"string"},"object":{"type":"string","description":"The literal string \"payment\", naming this resource's type. Every resource carries one, and it is the discriminator for an event's `data`."},"status":{"$ref":"#/components/schemas/PaymentStatus"},"mode":{"$ref":"#/components/schemas/Mode"},"merchant_id":{"type":"string"},"amount":{"type":"integer","description":"Minor units of `currency` (for usd, cents)."},"currency":{"type":"string","enum":["usd"],"description":"What the customer was charged in. `usd` is the only currency we support today."},"description":{"type":"string","nullable":true},"customer":{"type":"object","nullable":true,"description":"The customer details you sent at creation, echoed in the same shape. Null when you sent none.","properties":{"email":{"type":"string"}}},"fees":{"type":"object","description":"Fees charged on the gross amount, in minor units. Refunds do not return fees.\n\n`outline` and `partner` are frozen as soon as they are knowable, which for almost every payment is creation time. A later rate change does not touch them.\n\nThe exception is a checkout whose fee depends on which method the customer picks (a capped bank rail offered beside an uncapped one). There, `outline` and `partner` stay null until the customer answers on the hosted checkout page; `selected_payment_method` tells you whether they have. A payment that expires unanswered keeps null forever: no method was chosen and no money moved, so no fee was ever charged.","properties":{"processor":{"type":"integer","nullable":true,"description":"The processor's own cut, read from the charge's balance transactions (see the top-level `processor` field for which processor).\n\nNull until captured, a moment after the outcome. That means it is still null on the payment.succeeded or payment.failed event; the payment.updated event that follows carries it.\n\nSet on failed payments too. There it is the processor's flat charge for a returned bank debit, and the only fee borne: a failed payment collects nothing for OutlinePay or for you."},"outline":{"type":"integer","nullable":true,"description":"OutlinePay's fee, from the rate card. Same in test and live.\n\nNull while a method choice is still outstanding. 0 on a payment that failed or expired, because the processor returns this fee when a debit is returned."},"partner":{"type":"integer","nullable":true,"description":"Your fee, at your configured rate.\n\nNull while a method choice is still outstanding. 0 on a payment that failed or expired: nothing was collected, so nothing accrues to you."},"dispute":{"type":"integer","description":"What the disputes on this payment cost, summed: the same money that is itemised per-dispute on disputes[].fee. 0 when there are none."},"invoice":{"type":"integer","nullable":true,"description":"What the payment processor charged the merchant for issuing the invoice, on a payment created with `invoice.enabled`. Null on every payment that did not ask for one.\n\nLive mode only. It is also null on an invoiced payment until the figure lands, which is a day or so behind the payment, and it stays null forever in test mode: the processor's itemised fee reporting does not cover test data at all. We never estimate it from a published rate, because a computed number sitting beside measured ones would be indistinguishable from them."},"total":{"type":"integer","nullable":true,"description":"Every fee borne on this payment: the sum of the five fields above it (processor + outline + partner + dispute + invoice).\n\nCounts an uncaptured processor fee as 0. Null while a method choice is outstanding, since a figure there would read as a quote."}}},"merchant_net":{"type":"integer","nullable":true,"description":"The merchant's net: `amount − fees.total`.\n\nReads high until the processor's fee lands, a moment after the outcome, including on the payment.succeeded event itself. Record net from the payment.updated event that follows.\n\nNull while a method choice is outstanding, alongside fees.total.\n\nCan go negative. A full refund keeps all fees. On a payment that failed or expired there is no principal at all, so this is the negative of the processor's charge for the failure (its flat fee for a returned bank debit), and 0 until that charge has been read."},"amount_refunded":{"type":"integer","description":"Sum of non-failed refunds, in minor units."},"amount_refundable":{"type":"integer","description":"amount − amount_refunded: headroom for a further refund. Reaches 0 at a full refund, and is 0 on any payment that did not settle, since only a succeeded payment can be refunded. Also 0 while a dispute is holding the money (disputes[].amount_withheld above zero), which the processor refuses to refund whether the dispute is open or already lost; it becomes refundable again if the dispute is won."},"refunds":{"type":"array","description":"Refunds against this payment, oldest first.","items":{"type":"object","properties":{"id":{"type":"string"},"status":{"$ref":"#/components/schemas/RefundStatus"},"amount":{"type":"integer","description":"Minor units."},"metadata":{"$ref":"#/components/schemas/Metadata"},"created_at":{"type":"string","description":"ISO 8601 UTC."}}}},"disputes":{"type":"array","description":"Chargebacks against this payment, oldest first. Usually empty.","items":{"type":"object","properties":{"id":{"type":"string"},"status":{"$ref":"#/components/schemas/DisputeStatus"},"reason":{"$ref":"#/components/schemas/DisputeReason"},"amount":{"type":"integer","description":"Minor units."},"fee":{"type":"integer","nullable":true,"description":"What the processor charged the merchant to handle this dispute, in minor units (Stripe bills a flat 15.00 USD). Charged whether the dispute is won or lost. It is reported here rather than under the payment's fees because that is where the processor books it too, but the payment's fees.total and merchant_net both account for it."},"amount_withheld":{"type":"integer","nullable":true,"description":"How much of the payment the processor is currently holding back over this dispute, in minor units. Equal to amount from the moment a dispute opens, and 0 once the merchant wins. While it is above zero the payment cannot be refunded and amount_refundable reads 0."},"evidence_due_at":{"type":"string","nullable":true,"description":"Deadline to submit evidence, ISO 8601 UTC."},"created_at":{"type":"string","description":"ISO 8601 UTC."}}}},"processor":{"type":"string","description":"The underlying processor, e.g. \"stripe\"."},"processor_payment_method":{"type":"string","nullable":true,"description":"What actually settled, in the processor's own vocabulary (for example \"card\" or \"us_bank_account\"). Deliberately not one of OutlinePay's `payment_methods` categories: the `processor_` prefix marks a value as the processor's own and outside our portability contract, so read it for display or for reconciling against your processor, but do not branch product logic on it.\n\nNull until the charge exists, which for a delayed method such as ACH is days after the payment was created. It is read off the charge at the same moment as `fees.processor` and arrives on the same `payment.updated` event.\n\nOne value is less specific than it looks: `link` covers anything paid through the processor's wallet, including a bank account paid through it, so it does not tell you whether a card was involved. Use it for reconciliation against the processor, which is what it is for, rather than as a signal about how the customer funded the payment."},"selected_payment_method":{"allOf":[{"$ref":"#/components/schemas/PaymentMethod"}],"nullable":true,"description":"Which of the offered categories this payment will use. Set when the customer answered a choice OutlinePay put to them, which happens on the checkout where their answer is what fixes the fee (a capped bank rail offered alongside an uncapped method). Also set whenever the checkout offered exactly one category, since one option is already an answer.\n\nNull only when several categories that price alike were offered. The customer still picks one, but they pick it on the processor's page after the price is settled, so there is nothing for us to record. Read `processor_payment_method` for what the processor actually used."},"card":{"type":"object","nullable":true,"description":"The card that settled this payment, as the processor read it off the card networks. It arrives with the other settlement facts, a moment after the outcome: null on the payment.succeeded event itself, filled on the payment.updated event that follows, exactly like fees.processor. Null forever on a payment no card settled, such as a bank rail or a payment that expired unpaid.\n\nA wallet payment is a card underneath, so it appears here too, with `wallet` naming which wallet presented it. A payment whose `processor_payment_method` reads \"link\" stays null, because the processor does not reveal the card behind its own wallet.","properties":{"brand":{"type":"string","nullable":true,"description":"The card's scheme, as the networks name it: \"visa\", \"mastercard\", \"amex\", \"discover\", \"jcb\", \"unionpay\" and more. A string rather than an enum of ours, so a new scheme appears the moment the processor reports one."},"network":{"type":"string","nullable":true,"description":"The network the charge was actually routed over. Matches brand on almost every card. The two differ on a co-badged card, which carries two schemes and lets the processor pick the route."},"funding":{"type":"string","nullable":true,"description":"How the card draws money: \"credit\", \"debit\", \"prepaid\", or \"unknown\"."},"last4":{"type":"string","nullable":true,"description":"The last four digits of the card number. The full number never reaches OutlinePay or you, so this is as much of it as ever exists on either side."},"exp_month":{"type":"integer","nullable":true,"description":"Expiration month, 1 through 12."},"exp_year":{"type":"integer","nullable":true,"description":"Expiration year, four digits."},"country":{"type":"string","nullable":true,"description":"Two-letter ISO code of the country the card was issued in."},"authorization_code":{"type":"string","nullable":true,"description":"The approval number the issuer returned when it authorized the charge, the same code printed on a card receipt. Quote it when reconciling against card-company records. Null when the network did not return one."},"wallet":{"type":"string","nullable":true,"description":"Set when a digital wallet presented the card: \"apple_pay\", \"google_pay\", and so on. Null on a plain card payment."}}},"payment_link_id":{"type":"string","nullable":true,"description":"Set when the payment came from a link."},"checkout_url":{"type":"string","nullable":true,"description":"Hosted checkout URL. Present while the payment is not yet terminal, which includes `processing`: a delayed payment's page is where the customer sees what is still outstanding, and they may return to it days later. Null once the payment succeeds, fails or expires."},"receipt_url":{"type":"string","nullable":true,"description":"The customer-facing receipt: an OutlinePay-owned URL that resolves to the processor's hosted receipt, viewable in a browser and downloadable as a PDF. The document always shows the payment's latest state, refunds included. Appears when the payment succeeds, null before, and never expires. Share it with the customer or the merchant as proof of payment."},"invoice_url":{"type":"string","nullable":true,"description":"The invoice, on a payment created with `invoice.enabled`: an OutlinePay-owned URL that resolves to the processor's hosted invoice, viewable in a browser and downloadable as a PDF. It is the merchant's own document, issued under their business details, and it shows the payment's latest state including refunds.\n\nAppears when the payment succeeds, null before, and null forever on a payment that did not ask for one. A receipt is not a substitute: `receipt_url` is proof that money moved, while this is the itemised document a buyer's accounting asks for. What it cost the merchant is `fees.invoice`."},"expires_at":{"type":"string","nullable":true,"description":"ISO 8601 UTC. When the *checkout session* lapses, not the payment. The window is set by the processor when the session is minted; for Stripe-hosted checkout that is 24 hours after creation. A payment still `awaiting_payment` at that moment expires.\n\nA payment in `processing` has already been paid for, so this is routinely in the past while the payment is perfectly healthy."},"processing_at":{"type":"string","nullable":true,"description":"ISO 8601 UTC. When the customer finished and the payment entered processing. Only delayed methods reach this state."},"succeeded_at":{"type":"string","nullable":true},"failed_at":{"type":"string","nullable":true},"expired_at":{"type":"string","nullable":true},"failure_reason":{"allOf":[{"$ref":"#/components/schemas/PaymentFailureReason"}],"nullable":true,"description":"Why the payment failed, and null unless it did. Only bank rails reach it: a declined card doesn't end the checkout, the customer just tries again on the processor's page, so a card payment goes from `awaiting_payment` to `succeeded` or expires.\n\nGrouped by what to do next rather than by the processor's own code. Several of Stripe's ACH codes call for the same action and fold into one value here."},"payment_methods":{"type":"array","nullable":true,"description":"The method categories you restricted this payment to, echoed back. Null means you did not ask, which offers the documented default of `[\"card\", \"wallet\"]`, not every method the processor supports.","items":{"$ref":"#/components/schemas/PaymentMethod"}},"bank_verification":{"allOf":[{"$ref":"#/components/schemas/BankVerification"}],"nullable":true,"description":"Echoed as you sent it. Null means you did not ask, so instant_or_delayed applied."},"metadata":{"$ref":"#/components/schemas/Metadata"},"created_at":{"type":"string","description":"ISO 8601 UTC."}}},"PaymentLink":{"type":"object","properties":{"id":{"type":"string"},"object":{"type":"string","description":"The literal string \"payment_link\"."},"url":{"type":"string","description":"The permanent, OutlinePay-owned link URL."},"status":{"$ref":"#/components/schemas/PaymentLinkStatus"},"mode":{"$ref":"#/components/schemas/Mode"},"merchant_id":{"type":"string"},"amount":{"type":"integer"},"currency":{"type":"string","enum":["usd"],"description":"What the customer is charged in, fixed for the life of the link. `usd` is the only currency we support today."},"description":{"type":"string","nullable":true},"processor":{"type":"string","description":"The rail this link charges on, e.g. \"stripe\". Fixed at creation; every session and payment it produces uses it."},"payment_methods":{"type":"array","nullable":true,"description":"The method categories every session and payment this link mints is restricted to, echoed back. Fixed at creation, like the rail. Null means you did not ask, which offers the documented default of `[\"card\", \"wallet\"]`.","items":{"$ref":"#/components/schemas/PaymentMethod"}},"bank_verification":{"allOf":[{"$ref":"#/components/schemas/BankVerification"}],"nullable":true,"description":"Echoed as you sent it, and applied to every session this link mints. Null means you did not ask, so instant_or_delayed applied."},"invoice":{"type":"object","description":"Whether every payment this link mints asks the processor for an invoice. Always present, `{\"enabled\": false}` when you did not ask, so off and never-asked read the same.","properties":{"enabled":{"type":"boolean"}}},"metadata":{"$ref":"#/components/schemas/Metadata"},"created_at":{"type":"string"}}},"Refund":{"type":"object","properties":{"id":{"type":"string"},"object":{"type":"string","description":"The literal string \"refund\"."},"status":{"$ref":"#/components/schemas/RefundStatus"},"mode":{"$ref":"#/components/schemas/Mode"},"payment_id":{"type":"string"},"merchant_id":{"type":"string"},"processor":{"type":"string","description":"The rail this refund settled on, e.g. \"stripe\". Inherited from the payment."},"amount":{"type":"integer"},"metadata":{"$ref":"#/components/schemas/Metadata"},"created_at":{"type":"string"}}},"Dispute":{"type":"object","description":"A chargeback against a payment. Read-only: a cardholder opens it with their bank and we mirror the processor's state. Evidence is submitted through the processor's embedded disputes component, not the API.","properties":{"id":{"type":"string"},"object":{"type":"string","description":"The literal string \"dispute\"."},"status":{"$ref":"#/components/schemas/DisputeStatus"},"mode":{"$ref":"#/components/schemas/Mode"},"payment_id":{"type":"string"},"merchant_id":{"type":"string"},"processor":{"type":"string","description":"The rail the disputed charge is on, e.g. \"stripe\". Inherited from the payment."},"reason":{"$ref":"#/components/schemas/DisputeReason"},"amount":{"type":"integer","description":"Disputed amount, minor units."},"fee":{"type":"integer","nullable":true,"description":"What the processor charged the merchant to handle this dispute, in minor units (Stripe bills a flat 15.00 USD). Charged whether the dispute is won or lost, so winning still leaves the merchant down this much. The payment's fees.total and merchant_net account for it."},"amount_withheld":{"type":"integer","nullable":true,"description":"How much of the payment the processor is currently holding back over this dispute, in minor units. Equal to amount from the moment a dispute opens, and 0 once the merchant wins. While it is above zero the payment cannot be refunded."},"evidence_due_at":{"type":"string","nullable":true,"description":"Deadline to submit evidence, ISO 8601 UTC."},"created_at":{"type":"string"}}},"MerchantRequirementItem":{"type":"object","properties":{"code":{"type":"string","enum":["processor_terms","representative_identity","payout_account","business_category","business_details","other"],"description":"What the merchant has to do, in OutlinePay's vocabulary: accept the processor's agreement, supply the representative's identity, add a payout account, or correct the business category or details. Branch on this. `other` means the processor asked for something we have no word for yet, and processor_items is then the only description of it."},"blocks":{"type":"array","items":{"type":"string","enum":["charges","payouts"]},"description":"What this item holds up. A group's value covers every item in it, so an item blocking charges and another blocking payouts give a group that lists both. For the exact answer read blocks_charges and blocks_payouts on the object above."},"processor_items":{"type":"array","items":{"type":"string"},"description":"The processor's own names for the fields behind this item, verbatim. Useful for telling a merchant precisely what failed, and outside our compatibility contract like every processor_ field."}}},"MerchantRequirements":{"type":"object","properties":{"status":{"type":"string","enum":["overdue","due","upcoming"],"description":"How urgent the strictest outstanding item is. `overdue` is holding the account back now, `due` keeps it working, `upcoming` is wanted once the merchant's volume passes the processor's thresholds."},"blocks_charges":{"type":"boolean","description":"True when the merchant cannot take a payment until something here is resolved. This is the one that decides whether to show a checkout."},"blocks_payouts":{"type":"boolean","description":"True when the merchant cannot be paid out until something here is resolved."},"awaiting":{"type":"string","enum":["merchant","processor"],"description":"Whose move is next. `merchant` means the account session and onboarding component will collect what is missing. `processor` means it has what it needs and is reviewing, so there is nothing to do but wait."},"refreshed_at":{"type":"string","format":"date-time","description":"When we last read this from the processor."},"outstanding":{"type":"array","items":{"$ref":"#/components/schemas/MerchantRequirementItem"},"description":"One entry per thing the merchant has to do, the ones blocking charges first. Can be empty while the rest of this object is populated: the processor sometimes restricts an account before it says which fields it wants, and an empty list is not the same answer as a null requirements."}}},"MerchantProcessor":{"type":"object","properties":{"processor":{"type":"string"},"account_id":{"type":"string","nullable":true,"description":"The processor's own account identifier (for Stripe, the connected account id, e.g. \"acct_1AbC...\"). Null in the brief window before the account is opened."},"onboarding_completed":{"type":"boolean","description":"True once the merchant has submitted its details to the processor. It reports that the merchant did its part, not that the processor accepted them, so read it together with verified rather than instead of it. While either is false, requirements says what is still outstanding and whose move it is. It never returns to false once set."},"representative_prefilled":{"type":"boolean","description":"True once the representative supplied at attach reached the processor. False when none was supplied, or when it was rejected, in which case Attach a processor accepts the corrected one. A delivery flag only: the representative itself is forward-only and never returned."},"requirements":{"allOf":[{"$ref":"#/components/schemas/MerchantRequirements"}],"nullable":true,"description":"What the merchant still owes this processor, or null once it owes nothing. onboarding_completed and verified say a merchant is not through. This says what is left."},"verified":{"type":"boolean","description":"True once the processor has accepted the merchant and will take charges for it. This is the one that gates money: until it is true, creating a payment or a payment link is refused with 409 merchant_not_onboarded, whatever onboarding_completed says. It turns true at the same moment the merchant's status becomes active, and a merchant.updated event is emitted then, so you can wait on the event instead of polling. It never returns to false, so it answers \"has this merchant ever been accepted\" rather than \"can it charge right now\"; for the second question read status, which is where a later restriction shows up."}}},"Merchant":{"type":"object","properties":{"id":{"type":"string"},"object":{"type":"string","description":"The literal string \"merchant\"."},"name":{"type":"string"},"email":{"type":"string"},"status":{"$ref":"#/components/schemas/MerchantStatus"},"mode":{"$ref":"#/components/schemas/Mode"},"country":{"type":"string","description":"The merchant's legal-entity country, ISO 3166-1 alpha-2. Where the business is registered, not where its customers are. US today; other countries are on our roadmap."},"locale":{"type":"string","nullable":true,"description":"The language this merchant's onboarding renders in, or null if none was given (which onboards in English)."},"entity_type":{"type":"string","enum":["individual","company"],"nullable":true,"description":"The legal entity type, if any."},"default_processor":{"type":"string","nullable":true,"description":"The rail a charge uses when the request names none. Null until the first processor is attached, which becomes the default; it never changes on its own after that."},"business":{"allOf":[{"$ref":"#/components/schemas/MerchantBusiness"}],"nullable":true,"description":"Seeded by what you supply at creation, which is why it can change without a call from you: the merchant edits its details at the processor and the sync above brings them back here. Only business-level data is kept. Representative details are forwarded to the processor and never stored, so they are never returned here."},"processors":{"type":"array","items":{"$ref":"#/components/schemas/MerchantProcessor"}},"terms_acceptance":{"type":"object","nullable":true,"description":"The merchant's acceptance of the OutlinePay merchant terms, or null if they have not accepted yet. You never write this: the merchant agrees on the terms screen the onboarding component shows ahead of the processor's form, we record it, and merchant.updated announces the change. One acceptance covers every processor. A merchant without one cannot take payments (409 merchant_terms_not_accepted), except merchants created before 2026-08-16, who are exempt until they next pass through onboarding.","properties":{"version":{"type":"string","description":"The terms version accepted."},"accepted_at":{"type":"string"}}},"created_at":{"type":"string"}}},"MerchantBusiness":{"type":"object","description":"Commercial details about the merchant's business. Fields marked merchant-owned are the merchant's to edit once they have onboarded, so they only ever travel inward.","properties":{"legal_name":{"type":"string","description":"Registered legal name, if it differs from `name`."},"structure":{"type":"string","description":"The merchant's legal structure, in OutlinePay's processor-agnostic vocabulary; we translate it to your processor's own value. Applies to a company; an individual has none. These map to, in order: sole_proprietorship, single_member_llc, multi_member_llc, private_corporation, private_partnership.","enum":["sole_proprietor","llc","llc_multi","corporation","partnership"]},"phone":{"type":"string","x-merchant-owned":true,"description":"Business phone. Use E.164: '+', the country code, then the subscriber number, e.g. +12125550123 (US) or +821012345678 (KR). A phone no processor could parse is refused as a 400 invalid_request naming this field."},"url":{"type":"string","description":"Public website: a full http(s) URL with a domain name, e.g. https://hanui.example. The processor additionally refuses placeholder and shared domains (example.com and the like) when it is attached."},"product_description":{"type":"string","x-merchant-owned":true,"description":"One or two sentences on what the merchant sells."},"industry_code":{"type":"string","x-merchant-owned":true,"description":"Four-digit merchant category code (MCC) classifying what the business sells: the card networks' ISO 18245 standard, not ours. Common examples: 5399 general merchandise, 5812 restaurants, 5691 clothing, 5734 software. Omit it and the processor infers one from the website and product description. We accept the codes listed at https://outlinepay.com/docs/mccs and refuse any other value naming this field."},"statement_descriptor":{"type":"string","x-merchant-owned":true,"description":"What buyers see on their card statement. 5 to 22 characters, at least one Latin letter, no angle brackets. Keep it recognisable, or it drives chargebacks."},"address":{"$ref":"#/components/schemas/Address"}}},"Metadata":{"type":"object","description":"Your own key/value pairs, for correlating this object back to your records. We never interpret the contents: they are stored, returned on every read, carried on every event this object emits, and forwarded to the processor. At most 50 pairs; keys up to 40 characters, values up to 500. Values are stored as strings. Keys beginning outlinepay_, plus outline_fee and partner_fee, are reserved by OutlinePay and rejected. Because it reaches the processor, never put secrets here.","additionalProperties":{"type":"string"},"example":{"order_id":"A-1042"}},"Address":{"type":"object","properties":{"line1":{"type":"string"},"line2":{"type":"string"},"city":{"type":"string"},"state":{"type":"string","description":"Two-letter state or province code. For a US address, the USPS code. The processor cannot serve US insular areas (GU, VI, AS, MP)."},"postal_code":{"type":"string","description":"Postal code. For a US address, 5 digits or ZIP+4, e.g. 19104 or 19104-2617."},"country":{"type":"string","description":"Two-letter ISO country code, required whenever an address is sent. This is the country of *this address*: a business address must be in the merchant's own country (the top-level `country` on the merchant), while a representative's home address may be anywhere."}}},"WebhookEndpoint":{"type":"object","properties":{"id":{"type":"string"},"object":{"type":"string","description":"The literal string \"webhook_endpoint\"."},"url":{"type":"string"},"status":{"$ref":"#/components/schemas/WebhookEndpointStatus"},"mode":{"$ref":"#/components/schemas/Mode"},"secret":{"type":"string","nullable":true,"description":"Signing secret. Returned only on creation."},"created_at":{"type":"string"}}},"Deleted":{"type":"object","description":"The tombstone a successful DELETE answers with.","properties":{"id":{"type":"string"},"object":{"type":"string","description":"The type of the resource that was deleted."},"deleted":{"type":"boolean","description":"Always true."}}},"Event":{"type":"object","properties":{"id":{"type":"string"},"object":{"type":"string","description":"The literal string \"event\"."},"type":{"$ref":"#/components/schemas/EventType"},"mode":{"$ref":"#/components/schemas/Mode"},"created_at":{"type":"string"},"data":{"description":"The resource payload captured when the event was emitted: a Payment for payment.* events, a Merchant for merchant.*, and so on. Its `object` field names which, so a generic handler can branch on the payload rather than parsing the event type.","oneOf":[{"$ref":"#/components/schemas/Payment"},{"$ref":"#/components/schemas/Merchant"},{"$ref":"#/components/schemas/PaymentLink"},{"$ref":"#/components/schemas/Refund"},{"$ref":"#/components/schemas/Dispute"}],"discriminator":{"propertyName":"object","mapping":{"payment":"#/components/schemas/Payment","merchant":"#/components/schemas/Merchant","payment_link":"#/components/schemas/PaymentLink","refund":"#/components/schemas/Refund","dispute":"#/components/schemas/Dispute"}}}}},"AccountSession":{"type":"object","description":"The credentials your page hands to the processor's embedded-components SDK. `client_secret` and `publishable_key` are that processor's handshake rather than a shape every processor shares, so `processor` tells you which one you are holding.","properties":{"object":{"type":"string","description":"The literal string \"account_session\"."},"processor":{"type":"string","description":"The processor these credentials belong to, e.g. \"stripe\"."},"client_secret":{"type":"string"},"publishable_key":{"type":"string"},"components":{"type":"array","items":{"type":"string"}},"locale":{"type":"string","description":"The merchant's language, spelled the way this processor's SDK wants it. Pass it straight through when you initialise the components.\nIt is not the tag you sent us on the merchant: Stripe's embedded components accept only regional codes, so a merchant stored as `ko` comes back here as `ko-KR`, and a language the SDK cannot render falls back to `en-US` rather than failing. Setting the merchant's locale alone does not translate the form; the SDK has to be told."},"terms":{"type":"object","description":"Whether this merchant still has to accept the OutlinePay merchant terms, and where they are shown. outlinepay.js reads this on its own: when `required` is true, mounting the onboarding component shows the terms screen at `url` first, and the processor's form after the merchant agrees. Nothing to build on your side. Keep forwarding this response body verbatim. `url` is short-lived and scoped to this one merchant, minted fresh with every session.\nIf your Content-Security-Policy sets frame-src, allow the origin of `url`, the same way script-src already allows the processor's SDK host.","properties":{"required":{"type":"boolean","description":"True until the merchant has accepted the current terms version."},"url":{"type":"string","nullable":true,"description":"The terms screen to frame, or null when required is false."}}}}},"MerchantCreateParams":{"type":"object","required":["name","email","country"],"properties":{"name":{"type":"string"},"email":{"type":"string"},"country":{"type":"string","description":"The merchant's legal-entity country, ISO 3166-1 alpha-2. Where the business is registered, not where its customers are. Required, and never inferred, because it decides the onboarding requirements. `US` is the only country we onboard today: a merchant created with any other country is accepted here and then refused when you attach a processor, since that is where the entity is actually opened. Other countries are on our roadmap; see https://outlinepay.com/docs/countries and talk to us about yours."},"locale":{"type":"string","enum":["en","ko"],"description":"The language this merchant reads. It decides what language their onboarding form is rendered in and what language the payment processor writes to them in afterwards, so send it if you know it. Optional: leave it out and the merchant is onboarded in English.\n`en` and `ko` are what we support today. Anything else is rejected rather than quietly downgraded to English, so that a language we cannot serve yet fails where you can see it. Tell us which ones you need and we will add them."},"entity_type":{"type":"string","enum":["individual","company"],"description":"Whether the merchant is a registered business or a person trading under their own name."},"business":{"allOf":[{"$ref":"#/components/schemas/MerchantBusiness"}],"description":"The details you already hold. Stored and returned on the merchant in full. The forward-only `tax_id` and `representative` are not accepted here: you supply them when you attach a processor."}}},"MerchantEditableBusiness":{"type":"object","description":"The business fields a partial update may touch.","properties":{"legal_name":{"type":"string","description":"Registered legal name, if it differs from `name`."},"structure":{"type":"string","description":"The merchant's legal structure, in OutlinePay's processor-agnostic vocabulary (company only). See MerchantBusiness for what each maps to.","enum":["sole_proprietor","llc","llc_multi","corporation","partnership"]},"url":{"type":"string","description":"Public website: a full http(s) URL with a domain name, e.g. https://hanui.example. The processor additionally refuses placeholder and shared domains (example.com and the like) when it is attached."},"address":{"$ref":"#/components/schemas/Address"},"phone":{"type":"string","x-merchant-owned":true,"description":"Business phone. Use E.164: '+', the country code, then the subscriber number, e.g. +12125550123 (US) or +821012345678 (KR). A phone no processor could parse is refused as a 400 invalid_request naming this field."},"product_description":{"type":"string","x-merchant-owned":true,"description":"One or two sentences on what the merchant sells."},"industry_code":{"type":"string","x-merchant-owned":true,"description":"Four-digit merchant category code (MCC) classifying what the business sells: the card networks' ISO 18245 standard, not ours. Common examples: 5399 general merchandise, 5812 restaurants, 5691 clothing, 5734 software. Omit it and the processor infers one from the website and product description. We accept the codes listed at https://outlinepay.com/docs/mccs and refuse any other value naming this field."},"statement_descriptor":{"type":"string","x-merchant-owned":true,"description":"What buyers see on their card statement. 5 to 22 characters, at least one Latin letter, no angle brackets. Keep it recognisable, or it drives chargebacks."}}},"MerchantUpdateParams":{"type":"object","description":"A partial update to the business identity, merged field by field: omit a field to leave it unchanged, send a value to set it, send it blank to clear it.","properties":{"entity_type":{"type":"string","enum":["individual","company"],"description":"Set-only. Sending it blank does not clear it."},"locale":{"type":"string","enum":["en","ko"],"nullable":true,"description":"The language this merchant reads. Change it when you learn you had it wrong, or when the merchant asks: it was previously fixed at creation, and a merchant who signed up in the wrong language stayed in it.\n\nFollows this endpoint's merge rule. Omit it and the language is left alone; send `en` or `ko` to set it; send it blank to clear it, which returns the merchant to being onboarded in English.\n\nIt takes effect on the next account session you create. Components already on screen keep the locale they were initialised with, so by default the merchant sees the change on their next page load. If you want it sooner, the embedded components can be re-translated in place without re-mounting: call `stripeConnectInstance.update({locale})` with the `locale` from a freshly created account session. Nothing is sent to the processor when this changes, because the processor holds no copy of it."},"business":{"allOf":[{"$ref":"#/components/schemas/MerchantEditableBusiness"}],"description":"The identity fields to set or clear."}}},"ProcessorAttachParams":{"type":"object","description":"The forward-only identity handed to the processor when its account is opened, combined with the business stored at creation to prefill onboarding.","properties":{"processor":{"type":"string","default":"stripe","description":"Which processor to attach. Currently only `stripe`."},"tax_id":{"type":"string","x-forward-only":true,"description":"Business tax identifier: the EIN for a US business (nine digits, e.g. 12-3456789). Sent to the processor to satisfy its verification."},"representative":{"allOf":[{"$ref":"#/components/schemas/MerchantRepresentative"}],"x-forward-only":true}}},"MerchantRepresentative":{"type":"object","description":"The person who controls the business. Every field here is forward-only: we pass it to the processor so onboarding stops asking, and keep none of it. Do not send a national ID (SSN); the processor collects that from the merchant directly, and supplying it satisfies nothing.","properties":{"first_name":{"type":"string"},"last_name":{"type":"string"},"email":{"type":"string"},"phone":{"type":"string","description":"Use E.164: '+', the country code, then the subscriber number, e.g. +12125550123 (US) or +821012345678 (KR). A phone no processor could parse is refused as a 400 invalid_request naming this field."},"date_of_birth":{"type":"string","description":"ISO 8601 date, YYYY-MM-DD, e.g. 1985-04-27. Must be a past date at least 13 years back, the processor's minimum age for a representative."},"address":{"allOf":[{"$ref":"#/components/schemas/Address"}],"description":"The representative's home address."}}},"AccountSessionParams":{"type":"object","required":["components"],"properties":{"components":{"type":"array","minItems":1,"description":"Which components to scope the session to, in OutlinePay's processor-agnostic vocabulary. Required: a session grants exactly what you name here, so name only what the page mounts. Note: your browser still mounts each one with the processor's own SDK (for Stripe, Connect.js), which uses its own component ids.","items":{"type":"string","enum":["onboarding","account_management","notifications","disputes","balances","documents","payouts"]}}}},"PaymentCreateParams":{"type":"object","required":["merchant_id","amount","success_url"],"properties":{"merchant_id":{"type":"string"},"amount":{"type":"integer","minimum":100,"description":"Minor units of `currency` (for usd, cents). The minimum charge is 100 ($1.00): a lower amount is refused as a 400 invalid_request naming `amount`, before the processor is called."},"currency":{"type":"string","default":"usd","enum":["usd"],"description":"What the customer is charged in (the presentment currency), as distinct from what the merchant is paid in. `usd` is the only value we accept today, and it is the default, so you can leave it out. Contact us if you need another."},"description":{"type":"string"},"customer":{"type":"object","description":"Who is paying. Required (with `email`) whenever `payment_methods` includes `bank_transfer`; optional otherwise. The email keys the buyer's identity, so repeat buyers keep the same virtual account number.","properties":{"email":{"type":"string"}}},"success_url":{"type":"string","description":"Where the processor sends the browser after payment."},"cancel_url":{"type":"string","description":"Where the processor sends the browser on cancel."},"processor":{"type":"string","description":"Which rail to charge on. Defaults to the merchant's default processor."},"payment_methods":{"type":"array","description":"Restrict the checkout to these method categories (an allow-list). Omit for the documented default, `[\"card\", \"wallet\"]`. Naming `bank_transfer` requires `customer.email`.","items":{"$ref":"#/components/schemas/PaymentMethod"}},"bank_verification":{"allOf":[{"$ref":"#/components/schemas/BankVerification"}],"description":"Only meaningful when the checkout offers bank_debit. Omit it and you get instant_or_delayed."},"invoice":{"type":"object","description":"Ask the payment processor to issue an invoice once this payment succeeds. Off unless you send it, because the processor charges the merchant per invoice. It can only be decided here: the invoice's contents are fixed when the checkout is created and cannot be generated afterwards. The finished document is `invoice_url` on the payment, and what it cost the merchant is `fees.invoice`.","properties":{"enabled":{"type":"boolean","description":"Must be true or false. Anything else is refused rather than interpreted, so a stray value cannot bill the merchant for a document nobody asked for."}}},"metadata":{"$ref":"#/components/schemas/Metadata"}}},"PaymentLinkCreateParams":{"type":"object","required":["merchant_id","amount"],"properties":{"merchant_id":{"type":"string"},"amount":{"type":"integer","minimum":100,"description":"Minor units of `currency` (for usd, cents). Minimum 100 ($1.00), refused as a 400 invalid_request below it, as on Create a payment."},"currency":{"type":"string","default":"usd","enum":["usd"],"description":"What the customer is charged in (the presentment currency), fixed for the life of the link. `usd` is the only value we accept today, and it is the default."},"description":{"type":"string"},"processor":{"type":"string","description":"Which rail this link charges on. Defaults to the merchant's default processor, and is fixed for the life of the link."},"payment_methods":{"type":"array","description":"Restrict every session and payment this link mints to these method categories (an allow-list), fixed for the life of the link. Omit for the documented default, `[\"card\", \"wallet\"]`. `bank_transfer` is not accepted here: a virtual account number is issued to a known buyer, and a link has no buyer until someone opens it. Create payments instead.","items":{"$ref":"#/components/schemas/PaymentMethod"}},"bank_verification":{"allOf":[{"$ref":"#/components/schemas/BankVerification"}],"description":"Only meaningful when the link offers bank_debit. Fixed for the life of the link. Omit it and you get instant_or_delayed."},"invoice":{"type":"object","description":"Ask the payment processor to issue an invoice for every payment this link takes, once each one succeeds. The same option as on Create a payment, fixed for the life of the link like the rail and the methods. Off unless you send it, because the processor charges the merchant per invoice. Each payment the link mints then carries `invoice_url` and `fees.invoice`.","properties":{"enabled":{"type":"boolean","description":"Must be true or false. Anything else is refused rather than interpreted, so a stray value cannot bill the merchant for a document nobody asked for."}}},"metadata":{"$ref":"#/components/schemas/Metadata"}}},"RefundCreateParams":{"type":"object","required":["payment_id"],"properties":{"payment_id":{"type":"string"},"amount":{"type":"integer","description":"Omit to refund what is still refundable."},"metadata":{"$ref":"#/components/schemas/Metadata"}}},"WebhookEndpointCreateParams":{"type":"object","required":["url"],"properties":{"url":{"type":"string","description":"An HTTPS URL to receive events."}}}}}}