{"openapi":"3.1.0","info":{"title":"Tatsupay API","version":"1.0.0","description":"REST API for accepting QRIS payments in Indonesia. Create a transaction, show the QR to your customer, and receive a webhook when it settles.","contact":{"name":"Tatsupay Support","email":"support@tatsupay.com","url":"https://tatsupay.com/docs"}},"servers":[{"url":"https://api.tatsupay.com","description":"Production"}],"security":[{"bearerAuth":[]}],"tags":[{"name":"Transactions","description":"Create and inspect payments"},{"name":"Payment methods","description":"Methods enabled for your account"},{"name":"Pricing","description":"Public pricing, no authentication required"}],"paths":{"/v1/transactions":{"post":{"tags":["Transactions"],"summary":"Create a transaction","description":"Creates a transaction and returns QRIS data ready to display to the customer.","operationId":"createTransaction","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTransaction"}}}},"responses":{"200":{"description":"Transaction created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Transaction"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}},"get":{"tags":["Transactions"],"summary":"List transactions","operationId":"listTransactions","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}}],"responses":{"200":{"description":"A list of transactions, most recent first","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Transaction"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/v1/transactions/{id}":{"get":{"tags":["Transactions"],"summary":"Retrieve a transaction","description":"Returns the current state of one transaction. Use as a fallback when a webhook does not arrive.","operationId":"getTransaction","parameters":[{"$ref":"#/components/parameters/TransactionId"}],"responses":{"200":{"description":"The transaction","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Transaction"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/v1/transactions/{id}/simulate":{"post":{"tags":["Transactions"],"summary":"Simulate payment (test mode only)","description":"Marks a test-mode transaction as paid so the settlement and webhook path can be exercised without real money. Rejected when called with a live key.","operationId":"simulateTransaction","parameters":[{"$ref":"#/components/parameters/TransactionId"}],"responses":{"200":{"description":"Transaction marked as paid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Transaction"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/v1/transactions/{id}/qris.png":{"get":{"tags":["Transactions"],"summary":"QR code image","description":"Returns the transaction QR rendered as a PNG image.","operationId":"getTransactionQRImage","parameters":[{"$ref":"#/components/parameters/TransactionId"}],"responses":{"200":{"description":"PNG image","content":{"image/png":{"schema":{"type":"string","format":"binary"}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/v1/payment-methods":{"get":{"tags":["Payment methods"],"summary":"List enabled payment methods","description":"Returns the methods enabled for the authenticated merchant, including the fee that applies to that merchant.","operationId":"listPaymentMethods","responses":{"200":{"description":"Enabled methods","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/PaymentMethod"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/v1/public/pricing":{"get":{"tags":["Pricing"],"summary":"Public pricing","description":"Returns default transaction fees, the payout fee, and active subscription plans. No authentication required. Never includes merchant-specific negotiated rates.","operationId":"getPublicPricing","security":[],"responses":{"200":{"description":"Current public pricing","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Pricing"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"API key as a bearer token. Test keys are prefixed sk_test_, live keys sk_live_. Keep keys server-side only."}},"parameters":{"TransactionId":{"name":"id","in":"path","required":true,"description":"Transaction identifier, prefixed trx_.","schema":{"type":"string","example":"trx_01HQ8Z3XK2M9V4N7P1R5T8W2Y6"}}},"schemas":{"CreateTransaction":{"type":"object","required":["amount","payment_method"],"properties":{"amount":{"type":"integer","minimum":1,"description":"Amount in whole rupiah, not in cents.","example":150000},"payment_method":{"type":"string","description":"Payment method code.","example":"qris"},"reference":{"type":"string","description":"Your own order identifier. Optional, but it is what makes reconciliation possible.","example":"ORDER-1042"}}},"Transaction":{"type":"object","properties":{"id":{"type":"string","example":"trx_01HQ8Z3XK2M9V4N7P1R5T8W2Y6"},"status":{"$ref":"#/components/schemas/TransactionStatus"},"amount":{"type":"integer","example":150000},"payment_method":{"type":"string","example":"qris"},"reference":{"type":"string","example":"ORDER-1042"},"qr_string":{"type":"string","description":"Raw QRIS payload. Render it yourself for full control over styling."},"url_image_qris":{"type":"string","format":"uri","description":"Ready-made PNG of the same QR."},"expires_at":{"type":"string","format":"date-time"},"paid_at":{"type":"string","format":"date-time","nullable":true},"created_at":{"type":"string","format":"date-time"}}},"TransactionStatus":{"type":"string","enum":["pending","paid","expired","failed"],"description":"Only \"paid\" means money has been received. Treat every other value as not yet paid."},"PaymentMethod":{"type":"object","properties":{"code":{"type":"string","example":"qris"},"name":{"type":"string","example":"QRIS"},"category":{"type":"string","example":"qris"},"enabled":{"type":"boolean"},"fee_bps":{"type":"integer","description":"Fee in basis points. 70 bps equals 0.7%.","example":70},"fee_flat":{"type":"integer","example":0},"min_amount":{"type":"integer","example":500},"max_amount":{"type":"integer","example":10000000}}},"Pricing":{"type":"object","properties":{"currency":{"type":"string","example":"IDR"},"payment_methods":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string"},"name":{"type":"string"},"category":{"type":"string"},"fee_bps":{"type":"integer"},"fee_flat":{"type":"integer"},"fee_percent":{"type":"number","example":0.7},"min_amount":{"type":"integer"},"max_amount":{"type":"integer"},"subscription":{"type":"boolean","description":"True when the method is billed via subscription rather than per transaction. Without this flag a zero fee would read as free."}}}},"withdrawal_fee":{"type":"integer","example":5000},"subscription_plans":{"type":"array","items":{"$ref":"#/components/schemas/SubscriptionPlan"}},"generated_at":{"type":"string","format":"date-time"}}},"SubscriptionPlan":{"type":"object","properties":{"code":{"type":"string","example":"qris_custom_1m"},"product":{"type":"string","example":"qris_custom"},"name":{"type":"string","example":"1 Bulan"},"description":{"type":"string"},"duration_days":{"type":"integer","example":30},"price_amount":{"type":"integer","example":99000},"currency":{"type":"string","example":"IDR"},"price_per_month":{"type":"integer","example":99000}}},"Error":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Stable machine-readable code. Branch on this rather than on message text.","example":"VALIDATION_ERROR"},"field":{"type":"string","description":"The offending field, when the error relates to one.","example":"amount"}}}}},"WebhookEvent":{"type":"object","description":"Sent to your registered endpoint when a transaction settles. Your handler must be idempotent, because retries can deliver the same event more than once.","properties":{"event":{"type":"string","enum":["transaction.paid"]},"data":{"$ref":"#/components/schemas/Transaction"}}}},"responses":{"BadRequest":{"description":"The request was malformed or failed validation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unauthorized":{"description":"The API key is missing or invalid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Not found, or not owned by this account","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Rate limit exceeded. Honour the Retry-After header.","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"webhooks":{"transaction.paid":{"post":{"summary":"Payment settled","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"200":{"description":"Acknowledged. Any non-2xx response causes the delivery to be retried with increasing delays."}}}}},"externalDocs":{"description":"Tatsupay documentation","url":"https://tatsupay.com/docs"}}