---
title: "Mint a one-time billing-portal link"
method: GET
path: "/v1/billing/portal"
tags: ["Billing"]
---

# Mint a one-time billing-portal link

`GET /v1/billing/portal`

## Request

- Base URL: `https://api.attestwire.com`
- URL: `https://api.attestwire.com/v1/billing/portal`
- Auth: HTTP bearer

Returns a ONE-TIME URL to the Paddle customer portal for the calling key: change plan, update the card, download every invoice, cancel.

TREAT THE URL AS A CREDENTIAL. Whoever opens it is treated as the account holder — there is no second factor behind it. Do not cache it, do not log it, do not put it in a shared inbox or a chat channel, and do not hand the same one to two people. It expires; request a fresh one instead of storing it. (Paddle documents that portal sessions expire but publishes no duration, so this API does not invent one — there is deliberately no `expires_at` field rather than a guessed one.)

GET and POST are identical. Consumes no documents; it is subject to the same per-second pace limit as everything else on the key.

A key with no subscription behind it — a free key, or a tier granted by hand — gets 409 `no_subscription`. Every receipt email Paddle sends also carries a portal link, which is the path for a customer who cannot reach this API.

## Response `200`

A fresh portal session. Redirect the customer, do not store the URL.

- object
  - `url` string, uri, required: One-time Paddle customer-portal URL. Expires. Not a page to bookmark.
  - `note` string, required: Prose safe to show a user or an agent verbatim.

## Other responses

- `401`: Missing or unknown API key.
- `409`: This key has no subscription behind it, so there is no portal to open. A 409 and not a 404: the route exists and the request was well-formed — the account state is what makes it inapplicable.
- `410`: The key presented was retired by an earlier rotation. Its replacement is the live credential; this one is permanently gone, so retrying with it cannot work. Every authenticated endpoint answers this, not just /v1/keys/rotate.
- `429`: `rate_limited` — too many requests per second on this key. Nothing was charged.
- `500`: Unhandled fault while opening the billing portal. Never expected; the body carries no detail on purpose (a fault message can quote the offending value). Retry, and email hello@attestwire.com if it persists.
- `502`: The payment provider refused to mint a session. Nothing changed; retry shortly.
- `503`: Billing is not enabled on this deployment.

---

[API](https://skmtc.dev/attestwire/apis/attestwire-api.md) · [All operations](https://skmtc.dev/attestwire/apis/attestwire-api/llms.txt) · [OpenAPI document](https://skmtc.dev/attestwire/apis/attestwire-api/revisions/0036f8e043b2?raw)
