Sign your customers into their billing portal from your own app or dashboard.
There is no public sign-in page and no self-service link request: a customer reaches the portal only through you. Your app authenticates the customer, asks Kontier for a sign-in link with your API key, and sends the browser there at once.
The flow
- Authenticate the customer in your own app, as you do today.
- Find their Kontier customer:
GET /v1/customers?email=jane@example.com(or keep the customeridyou stored when you created them). - Issue a link:
POST /v1/customers/{id}/portal-linkswith a shortttl_secondsand, if you want, areturn_path. - Redirect the browser to the returned
urlat once. The customer lands signed in, onreturn_pathif you gave one.
Code
Code
Every POST needs an Idempotency-Key. Use a new one for each sign-in: a retry with the same key replays the first answer, which is the same single-use link.
Security contract
- Authenticate your customer first. Whoever holds the URL is signed in as that customer.
- Redirect, do not send. Never email, log or store the link unless delivering it is what you mean to do.
- Links are single-use and short-lived. A link signs in once; opening it again lands on the portal's expired page, exactly as an expired link does. It lives for
ttl_seconds(60 up to the deployment's link TTL, 30 minutes unless configured otherwise). Ask for 60 to 120 seconds when you redirect at once. Links issued before single-use links were introduced carry no single-use id and are refused: issue a new one. - Scope your key. The endpoint needs the
portal.links.issuepermission (area Portal Sign-in in the key's custom access). Give the key that signs customers in that permission and only what else it needs. Keys that heldcustomer.writebefore this permission existed were granted it once, so existing integrations keep working.
return_path must be one of the portal's own pages, such as /portal/invoices, /portal/invoices/{id}/pay, /portal/quote/{id} or /portal/subscriptions/{id}. Anything else (another host, a query, //) is refused with 400.
Each link you issue is recorded in the audit log as customer.portal_access_issued: who issued it, for which customer, the landing page and the expiry. The link itself is never stored.
Issue a portal sign-in link for a customer
Signs a single-use sign-in link into the customer's portal and returns the URL. This is how a customer reaches the portal: there is no public sign-in page. Integrate it into your own app or dashboard: authenticate your customer there, find them (GET /v1/customers?email=...), call this with your API key, and redirect the browser to url at once.
Security contract. Whoever holds the URL is signed in as this customer, so: authenticate your customer before you issue a link for them; never email the link unless that is what you mean to do, and never log it; redirect at once and ask for a short ttl_seconds (60 to 120) when you do. A link signs in once: a second use is refused exactly as an expired link is. It lives for ttl_seconds, at most the deployment's link TTL (30 minutes unless configured otherwise).
return_path lands the customer on one portal page after sign-in, e.g. /portal/invoices/{id}/pay or /portal/quote/{id}. Only the portal's own pages are accepted.
No email is sent. Issuing is audited: a customer.portal_access_issued event names who issued it, for which contact, the scope and landing page, and the expiry - never the link. Needs the portal.links.issue permission; an API key holds it only when its scopes include it.
Required scope: portal.links.issue (Issue portal sign-in links).
path Parameters
idCustomer UUID
Headers
Idempotency-KeyUnique key that makes this POST safe to retry: repeats with the same key replay the first response instead of re-executing. Replays are scoped to the retrying principal (same API key / user) and kept for 24 hours. Required on every POST.
Client-generated idempotency key (e.g. a UUID).
X-Workspace-IdSelects the workspace this request operates in, by workspace UUID or slug (e.g. a sandbox workspace for test integrations). Omitted: the organization's default live workspace. Unknown workspace: 404; workspace outside your organization: 403. Discover workspaces via GET /workspaces.
Workspace UUID or slug.
Issue a portal sign-in link for a customer › Request Body
return_path^/portal(/[A-Za-z0-9…Optional portal page the customer lands on after sign-in, e.g. /portal/invoices, /portal/quote/
scopeOptional task the link is for: add_payment_method, pay_invoice, topup_wallet, manage_subscription, checkout_subscription or quote:
ttl_secondsOptional link lifetime in seconds: at least 60, at most the deployment's link TTL (30 minutes unless configured otherwise), which is also the default. An integrator that redirects the customer at once should ask for 60 to 120.
Issue a portal sign-in link for a customer › Responses
Created
Create a session for an embedded payment page
Mints the session an embedded payment page (Kontier.mount(target, {kind, session})) runs on, for one customer and one task: payment_method (add a payment method), invoice (pay the customer's open invoices) or topup (top up a wallet). It is valid for 30 minutes and can make only that task's requests (anything else answers 403 PORTAL_SCOPE_FORBIDDEN).
Security contract. Create it on your server with your secret API key, for the customer you have authenticated, and hand only the token to your page. Keep it in memory; never put it in a URL, cookie or log. The page's complete event is a UI signal, not proof of payment: confirm through a webhook or the API.
Required scope: portal.links.issue (Issue portal sign-in links).
Headers
Idempotency-KeyUnique key that makes this POST safe to retry: repeats with the same key replay the first response instead of re-executing. Replays are scoped to the retrying principal (same API key / user) and kept for 24 hours. Required on every POST.
Client-generated idempotency key (e.g. a UUID).
X-Workspace-IdSelects the workspace this request operates in, by workspace UUID or slug (e.g. a sandbox workspace for test integrations). Omitted: the organization's default live workspace. Unknown workspace: 404; workspace outside your organization: 403. Discover workspaces via GET /workspaces.
Workspace UUID or slug.
Create a session for an embedded payment page › Request Body
customer_idThe customer the embedded page acts for.
kindThe task: payment_method (add or replace a payment method), invoice (pay an open invoice), topup (top up a wallet).
invoice_idkind invoice: the invoice the page opens on.
Create a session for an embedded payment page › Responses
Created