AmiliPay API Introduction
This is the API documentation for managing the AmiliPay service.Endpoint#
We only allow HTTPS connections. Regular HTTP connections will be redirected to HTTPS. We have two environments:Build and test your integration against the testing environment first, then switch to production once you're ready to go live. The two are separate accounts/data stores - a customer, invoice, or token created in testing does not exist in production and vice versa.Allowed HTTP requests#
GET: Get a resource or list of resources
DELETE: Delete a resource
Send these headers with every request:Content-Type: application/json (for requests with a body)
Authentication#
Authenticate with a partner API key. Create and manage keys under Settings > Partner API Tokens (up to 10 active keys per account, each with its own scopes). Send the key on every request using one of:Authorization: ApiKey {token}
Authorization: Bearer {token} (recognized only when the token itself is prefixed apk_)
A key is not gated on the onboarding progress of any merchant you manage - it works regardless of how far along a given merchant's onboarding is. It is gated on the lifecycle status of the key's own owning account: a key belonging to a Suspended or Deactivated account stops authenticating.To act on behalf of a specific merchant you've onboarded (e.g. from your own backend), you can also mint a merchant-scoped token via POST /customers/{account_id}/tokens - see Reference > Merchant.Rate Limiting#
You can make 450 requests every minute per API key (per IP if unauthenticated). Responses include X-RateLimit-Limit and X-RateLimit-Remaining headers; exceeding the limit returns 429 with a Retry-After header.Integration flow#
1. Onboard the merchant#
A merchant must complete onboarding and accept the current agreement(s) before you can create invoices for them. Call POST /customers to create the merchant account - this automatically creates an onboarding record and returns an account_id plus an onboarding_token.Hosted onboarding (recommended): hand the customer the onboarding_token and embed our onboarding widget - it walks them through agreement approval and KYC data collection.
Self-hosted onboarding: collect agreement approval and KYC data yourself and submit it against the onboarding record that was already created for you - PUT /onboardings/{id} (do not POST a new one, that creates a duplicate) plus POST /onboardings/{id}/documents - then call POST /onboardings/{id}/process to submit it for review.
Track progress with the ONBOARDING_STATUS_UPDATED webhook; a merchant can accept payments once its status is COMPLETED.2. Create a payment request#
Create an invoice with POST /invoices. A merchant token creates the invoice under its own account; a partner token must supply account_id to target one of its direct-child merchants - a partner cannot create an invoice without specifying which child merchant it belongs to. Required fields: amount, invoice_id, customer_id (your own reference for the payer - unrelated to the merchant's account_id), name, currency, due_date. Optionally supply customer_reference - your own free-text reference for the payer, up to 255 characters, stored and returned as-is. Set notify_customer: true (default) together with email/phone to have us notify the payer directly, or use the redirect_url returned in the response to send them to the payment page yourself.3. Handle webhooks#
Register a webhook endpoint from the merchant/partner dashboard under Settings > Webhooks - enter your callback_url, choose which events to subscribe to, and save; this returns a signing secret used to verify deliveries. There is no partner-API endpoint for registering or managing webhook endpoints today - it's dashboard-only, not something you can automate from your own backend. We POST every event to your callback_url; your endpoint must return a 2xx status, or we retry after 1, then 5, 15, and 60 minutes (4 attempts total).See the Webhooks reference for the full, current list of event types - it includes invoice events (INVOICE_PAYMENT_AUTHORIZED, INVOICE_PAYMENT_REFUSED, INVOICE_REFUNDED, INVOICE_CHARGEBACK, ...), onboarding events (ONBOARDING_STATUS_UPDATED, ONBOARDING_PROCESSING_FAILED, ...), and payout report events. The same list is shown on the dashboard's webhook settings page when choosing which events to subscribe to.{
"data": {
"unique_id": "i0n00sdQKSgyshUpV5CWYtID2k1judsrgGBl1lYxssycEBN1d1",
"account_id": 41,
"invoice_id": "RE-21243",
"amount": 10000,
"customer_id": "9131542",
"customer_reference": "PO-88231",
"currency": "NOK",
"payment_url": "https://pay.amili.no/pay-invoice/i0n00sdQKSgyshUpV5CWYtID2k1judsrgGBl1lYxssycEBN1d1"
},
"event": "INVOICE_PAYMENT_AUTHORIZED",
"message": "Invoice payment is authorized."
}
Every request is signed - verify the X-AmiliPay-Signature header, computed as hash_hmac('sha256', payload, secret), where payload is the raw JSON request body (the timestamp is not part of the signed string). The X-AmiliPay-Timestamp header carries the delivery time separately; as a general precaution, reject deliveries whose timestamp is more than 5 minutes old.4. Recurring payments#
Recurring payments must be enabled by customer service for the merchant. To enable it, set the is_recurring flag when creating the invoice. From then on, reference the original invoice via recurring_invoice_id when creating new invoices for the same customer.The first time you pass is_recurring: true without recurring_invoice_id, you'll receive an INVOICE_RECURRING_INITIATED webhook - redirect the customer to the payment window to store their payment details.
After a successful payment you'll receive INVOICE_PAYMENT_AUTHORIZED with is_recurring: true and a unique_id - store that unique_id as the reference for this customer's stored payment details.
If a recurring charge fails, you'll receive INVOICE_PAYMENT_REFUSED - ask the customer to pay the invoice manually via the payment window.
If automatic payment fails 10 times with the same token, the token is deleted and you'll receive INVOICE_RECURRING_TOKEN_DELETED.
Payouts#
Payouts to a merchant's bank account are swept automatically - daily by default, configurable to weekly or monthly per merchant. List and track them with GET /payouts and GET /payouts/{payout_id} (with GET /payouts/{payout_id}/details for the underlying transactions). Status is one of Pending, Processing, Completed, Failed, or Returned (a bank-side failure that reverses a previously completed payout).Testing payments#
Payments are processed through Adyen under the hood, so sandbox testing uses Adyen's test environment and test cards. To test different authorisation outcomes, change the name on the card to one of the values below.| Type | Card number | Expiry Date | Security Code (CVC/CVV/CID) |
|---|
| 3D Secure | 4917 6100 0000 0000 | 03/30 | 737 |
| Visa | 4111 1111 1111 1111 | 03/30 | 737 |
Response codes#
| # | holderName | refusalReason | resultCode |
|---|
| 0 | UNKNOWN | Unknown | Error |
| 1 | APPROVED | - | Authorised |
| 2 | DECLINED | Refused | Refused |
| 3 | REFERRAL | Referral | Refused |
| 4 | ERROR | Acquirer Error | Error |
| 5 | BLOCK_CARD | Blocked Card | Refused |
| 6 | CARD_EXPIRED | Expired Card | Refused |
| 7 | INVALID_AMOUNT | Invalid Amount | Refused |
| 8 | INVALID_CARD_NUMBER | Invalid Card Number | Refused |
| 9 | ISSUER_UNAVAILABLE | Issuer Unavailable | Refused |
| 10 | NOT_SUPPORTED | Not supported | Refused |
| 11 | NOT_3D_AUTHENTICATED | 3D Not Authenticated | Refused |
| 12 | NOT_ENOUGH_BALANCE | Not enough balance | Refused |
| 13 | PENDING | - | Received |
| 14 | ACQUIRER_FRAUD | Acquirer Fraud | Refused |
| 15 | CANCELLED | Cancelled | Refused |
| 16 | SHOPPER_CANCELLED | Shopper Cancelled | Refused |
| 17 | INVALID_PIN | Invalid Pin | Refused |
| 18 | PIN_TRIES_EXCEEDED | Pin tries exceeded | Refused |
| 19 | PIN_VALIDATION_NOT_POSSIBLE | Pin validation not possible | Refused |
| 20 | FRAUD | FRAUD | Refused |
| 21 | NOT_SUBMITTED | Not Submitted | Refused |
| 22 | FRAUD_CANCELLED | FRAUD-CANCELLED | Cancelled |
| 23 | TRANSACTION_NOT_PERMITTED | Transaction Not Permitted | Refused |
| 24 | CVC_DECLINED | CVC Declined | Refused |
| 25 | RESTRICTED_CARD | Restricted Card | Refused |
| 26 | REVOCATION_OF_AUTH | Revocation Of Auth | Refused |
| 27 | DECLINED_NON_GENERIC | Declined Non Generic | Refused |
| 28 | WITHDRAWAL_AMOUNT_EXCEEDED | Withdrawal amount exceeded | Refused |
| 29 | WITHDRAWAL_COUNT_EXCEEDED | Withdrawal count exceeded | Refused |
| 30 | PARTIALLY_APPROVED | - | Authorised |
| 31 | ISSUER_SUSPECTED_FRAUD | Issuer Suspected Fraud | Refused |
| 32 | AVS_DECLINED | AVS Declined | Refused |
| 33 | PIN_REQUIRED | Card requires online pin | Refused |
| 34 | NO_CHECKING_ACCOUNT_AVAILABLE_ON_CARD | No checking account available on Card | Refused |
| 35 | NO_SAVINGS_ACCOUNT_AVAILABLE_ON_CARD | No savings account available on Card | Refused |
| 36 | MOBILE_PIN_REQUIRED | Mobile PIN required | Refused |
| 37 | CONTACTLESS_FALLBACK | Contactless fallback | Refused |
| 38 | AUTHENTICATION_REQUIRED | Authentication required | Refused |
| 39 | RREQ_NOT_RECEIVED | RReq not received from DS | Refused |
Card numbers and outcome codes above are Adyen's own test-environment values - confirm with your integration contact if you hit a mismatch.Creating customer account#
"Customer" here means the merchant account you're onboarding (account_id) - not the customer_id field on an invoice, which is just your own free-text reference for the invoice's payer. There is no separate customer entity: account_id is the one durable identifier used throughout the rest of the API. customer_id and customer_reference are both your own free-text values on an invoice - use customer_id for the payer's identifier in your system and customer_reference for any additional reference (e.g. a PO number) you want echoed back on the invoice and its webhooks.There are two ways to complete onboarding once you've called POST /customers (see Integration flow, step 1):Hosted onboarding: use the onboarding_token from the response with the embedded widget - the customer approves the agreement and fills in their own KYC details.
Self-hosted onboarding: submit agreement approval and KYC data yourself against the onboarding record already created for you, then add onboarding documents for the company and its personas, then submit for review.
Register a webhook for onboarding events so you know when a merchant is COMPLETED and ready to accept payments.
To fully onboard a customer you must provide all required fields on both the onboarding and onboarding-documents endpoints.
It's recommended to do a full onboarding via the hosted flow first and inspect the resulting onboarding record to see how the endpoints fit together, or book a call with your integration contact for guidance.
Managing dashboard users#
Partner and admin API keys can also manage the dashboard login accounts ("users") within their account hierarchy via GET/POST /users and GET/PUT/DELETE /users/{user_id}. These are team-member logins for your own staff to access the dashboard - not customers or invoice payers. See the OpenAPI reference for the full request/response contract.The embedded widget gives a customer access to onboarding without leaving your platform. Create a customer via the API to get an onboarding_token, then load the widget script and pass the token to it:<zettr-widget token="{onboarding_token}"></zettr-widget>
<script type="text/javascript" src="https://pay.amili.no/widget/widget.js"></script>
(The <zettr-widget> element name is legacy naming from the widget's original build - it's correct as shown above, just not renamed yet.) The token does not expire, but treat it as a secret - anyone holding it can access that onboarding session.Embedding domain: by default the widget only loads in a frame on its own origin (self). To embed it on your own site, set your account's WIDGET_DOMAIN setting (via PUT /settings/widget-domain) to your site's domain(s) - a comma-separated list, e.g. partner.com,app.partner.com. Requests from an origin not on that list are rejected. Modified at 2026-09-25 07:16:01