Portal tools
MCP tools for Portal: portal_account_get, portal_brand_get, portal_credit_balance_get, portal_credit_notes_list, portal_credit_packs_list, portal_export_get, portal_invoices_get, portal_invoices_list, portal_invoices_pay, portal_login_links_request, portal_payment_methods_delete, portal_payment_methods_list, portal_payment_methods_set_default, portal_payment_methods_setup, portal_payment_methods_use_for_subscriptions, portal_sessions_create, portal_sessions_redeem, portal_sessions_revoke, portal_subscriptions_apply_promotion_code, portal_subscriptions_cancel, portal_subscriptions_get, portal_subscriptions_list, portal_subscriptions_preview_change, portal_subscriptions_reactivate, portal_subscriptions_set_payment_method, portal_subscriptions_update, portal_tax_details_update, portal_tax_ids_add, portal_tax_ids_delete, portal_wallets_auto_reload_update, portal_wallets_list, portal_wallets_top_up, portal_wallets_transactions_list.
portal_account_get: Get your customer accountportal_brand_get: Get a brand's customer portalportal_credit_balance_get: Get your credit balanceportal_credit_notes_list: List your credit notesportal_credit_packs_list: List the credit packs you can buyportal_export_get: Export your dataportal_invoices_get: Get one of your invoicesportal_invoices_list: List your invoicesportal_invoices_pay: Pay one of your invoices by cardportal_login_links_request: Email a customer a portal sign-in linkportal_payment_methods_delete: Remove a payment methodportal_payment_methods_list: List your payment methodsportal_payment_methods_set_default: Make a payment method your defaultportal_payment_methods_setup: Add a payment methodportal_payment_methods_use_for_subscriptions: Charge your default card for all your subscriptionsportal_sessions_create: Generate a customer portal linkportal_sessions_redeem: Sign in to the customer portal with a linkportal_sessions_revoke: Sign out of the customer portalportal_subscriptions_apply_promotion_code: Apply a promotion code to your subscriptionportal_subscriptions_cancel: Cancel your subscriptionportal_subscriptions_get: Get one of your subscriptionsportal_subscriptions_list: List your subscriptionsportal_subscriptions_preview_change: Preview a plan or quantity changeportal_subscriptions_reactivate: Keep your subscriptionportal_subscriptions_set_payment_method: Choose the card a subscription chargesportal_subscriptions_update: Switch plan or change the quantityportal_tax_details_update: Update your billing detailsportal_tax_ids_add: Add a tax ID to your accountportal_tax_ids_delete: Remove a tax ID from your accountportal_wallets_auto_reload_update: Set up auto-reloadportal_wallets_list: List your wallets and balancesportal_wallets_top_up: Top up your creditsportal_wallets_transactions_list: List your wallet's history
portal_account_get
Get your customer account. Operation portal.account.get · Brand level · permission portal:read · risk tier read · since M9
Returns the signed-in customer's own account with the brand: email, name, business name, billing address and tax IDs with their latest check, and when they last signed in to the portal.
Customer portal only: the caller is the customer signed in to the brand's portal (portal:read).
Annotations: readOnlyHint: true · destructiveHint: false · idempotentHint: true · openWorldHint: false
Arguments
| Argument | Type | Required | Description |
|---|---|---|---|
entity_id | ID (ent_…) | no | Optional: the brand's legal entity (ent_…); brand_id alone is enough. |
brand_id | ID (brd_…) | no | The brand to act on (brd_…), inside your API key's reach. Needed with an organization or entity key; a brand key acts on its own brand. |
- Brand: pass
brand_idwith an organization or entity key; a brand key acts on its own brand. - Read-only: changes nothing;
idempotency_keyis ignored.
Errors
validation_failed: A field or pagination value is malformed.livemode_not_allowed: The portal session is in the other mode than the request.permission_denied: The caller isn't a customer signed in to this brand's portal (operators and API keys use the customer operations).
Example
Your account
Arguments:
{
"brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz"
}
Result output:
{
"object": "portal_account",
"customerId": "cus_01m4tx5nj8emwbyf7y9vcf48b1",
"livemode": false,
"email": "ana@example.com",
"name": "Ana Novak",
"businessName": "Alpen Apps GmbH",
"type": "business",
"locale": null,
"billingAddress": {
"line1": "Ring 1",
"line2": null,
"city": "Wien",
"postalCode": "1010",
"region": null,
"country": "AT"
},
"taxIds": [
{
"id": "ATU00000000",
"type": "eu_vat",
"value": "ATU00000000",
"country": "AT",
"status": "valid",
"checkedAt": "2026-10-20T09:00:00.000Z"
}
],
"portalLastLoginAt": "2026-10-20T09:00:00.000Z"
}
portal_brand_get
Get a brand's customer portal. Operation portal.brand.get · Brand level · permission portal:read · risk tier read · since M9
Returns what a brand's customer portal shows before sign-in: the brand's name, logo, color and support links,
the seller (legal name, address and VAT ID), and the mode the portal's sign-in works in (livemode: test mode
for a brand that is still test-only, live mode once it is live).
Public: no API key needed. Draft and suspended brands have no portal (resource_not_found).
Annotations: readOnlyHint: true · destructiveHint: false · idempotentHint: true · openWorldHint: false
Arguments
| Argument | Type | Required | Description |
|---|---|---|---|
brandSlug | string | yes | The brand's slug, as in /portal/{brandSlug}. (1–64 characters) |
entity_id | ID (ent_…) | no | Optional: the brand's legal entity (ent_…); brand_id alone is enough. |
brand_id | ID (brd_…) | no | The brand to act on (brd_…), inside your API key's reach. Needed with an organization or entity key; a brand key acts on its own brand. |
- Brand: pass
brand_idwith an organization or entity key; a brand key acts on its own brand. - Read-only: changes nothing;
idempotency_keyis ignored.
Errors
validation_failed:brandSlugisn't a slug.resource_not_found: No brand with this slug has a customer portal (draft and suspended brands have none).rate_limited: Too many requests from this address or client IP; waitretry_after_ms.
Example
Genno's portal (test-only brand)
Arguments:
{
"brandSlug": "genno",
"brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz"
}
Result output:
{
"object": "portal_brand",
"slug": "genno",
"name": "Genno",
"livemode": false,
"logoUrl": null,
"primaryColor": "#5b3df5",
"supportEmail": "help@genno.example",
"supportUrl": null,
"termsUrl": "https://genno.example/terms",
"privacyUrl": "https://genno.example/privacy",
"seller": {
"legalName": "Etna Labs AG",
"address": "Dammstrasse 16, 6300 Zug, Switzerland",
"taxId": "CHE-181.927.786 MWST"
},
"locales": ["en", "hr"]
}
portal_credit_balance_get
Get your credit balance. Operation portal.credit_balance.get · Brand level · permission portal:read · risk tier read · since M9
Returns the signed-in customer's invoice credit balance in every currency they have one (proration credits,
goodwill credits and overpayments), which is applied automatically to their next invoices, and its latest
changes. It isn't a usage wallet. Read-only; customer portal only (portal:read).
Annotations: readOnlyHint: true · destructiveHint: false · idempotentHint: true · openWorldHint: false
Arguments
| Argument | Type | Required | Description |
|---|---|---|---|
entity_id | ID (ent_…) | no | Optional: the brand's legal entity (ent_…); brand_id alone is enough. |
brand_id | ID (brd_…) | no | The brand to act on (brd_…), inside your API key's reach. Needed with an organization or entity key; a brand key acts on its own brand. |
- Brand: pass
brand_idwith an organization or entity key; a brand key acts on its own brand. - Read-only: changes nothing;
idempotency_keyis ignored.
Errors
validation_failed: A field or pagination value is malformed.livemode_not_allowed: The portal session is in the other mode than the request.permission_denied: The caller isn't a customer signed in to this brand's portal (operators and API keys use the customer operations).
Example
€50 of credit left
Arguments:
{
"brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz"
}
Result output:
{
"object": "portal_credit_balance",
"balances": [
{
"amount": "50000000",
"currency": "EUR"
}
],
"changes": [
{
"change": {
"amount": "-200000000",
"currency": "EUR"
},
"reason": "invoice_applied",
"invoiceId": "inv_01m4tx5nj8emwbyf7y9vcf48b3",
"effectiveAt": "2026-10-20T09:00:00.000Z"
}
]
}
portal_credit_notes_list
List your credit notes. Operation portal.credit_notes.list · Brand level · permission portal:read · risk tier read · since M9
Lists the signed-in customer's credit notes (issued for refunds), newest first, each with the invoice it
corrects and a download link of its PDF (pdfPath; null for a moment after issue).
Paginate with limit (default 20) and starting_after. Customer portal only (portal:read).
Annotations: readOnlyHint: true · destructiveHint: false · idempotentHint: true · openWorldHint: false
Arguments
| Argument | Type | Required | Description |
|---|---|---|---|
limit | integer | no | Page size, 1 to 100 (default 20). (1–100) |
starting_after | string | no | The next_cursor of the previous page. (1–64 characters) |
entity_id | ID (ent_…) | no | Optional: the brand's legal entity (ent_…); brand_id alone is enough. |
brand_id | ID (brd_…) | no | The brand to act on (brd_…), inside your API key's reach. Needed with an organization or entity key; a brand key acts on its own brand. |
- Brand: pass
brand_idwith an organization or entity key; a brand key acts on its own brand. - Read-only: changes nothing;
idempotency_keyis ignored.
Errors
validation_failed: A field or pagination value is malformed.livemode_not_allowed: The portal session is in the other mode than the request.permission_denied: The caller isn't a customer signed in to this brand's portal (operators and API keys use the customer operations).
Example
Your credit notes
Arguments:
{
"brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz"
}
Result output:
{
"object": "list",
"data": [
{
"id": "cn_01m4tx5nj8emwbyf7y9vcf48b4",
"object": "portal_credit_note",
"number": "GEN-CN-2026-000003",
"issueDate": "2026-10-22",
"currency": "EUR",
"total": {
"amount": "30000000",
"currency": "EUR"
},
"invoiceId": "inv_01m4tx5nj8emwbyf7y9vcf48b3",
"invoiceNumber": "GEN-2026-000042",
"pdfPath": "/api/v1/credit_notes/cn_01m4tx5nj8emwbyf7y9vcf48b4/pdf?token=eyJ…"
}
],
"has_more": false,
"next_cursor": null
}
portal_credit_packs_list
List the credit packs you can buy. Operation portal.credit_packs.list · Brand level · permission portal:read · risk tier read · since M9
Lists the brand's active credit packs in the portal's mode, newest first: fixed packs with their price, credits,
bonus credits and expiry, and custom-amount packs with their minimum, maximum and suggested amount. Top up with
portal.wallets.top_up. Customer portal only (portal:read).
Annotations: readOnlyHint: true · destructiveHint: false · idempotentHint: true · openWorldHint: false
Arguments
| Argument | Type | Required | Description |
|---|---|---|---|
limit | integer | no | Page size, 1 to 100 (default 20). (1–100) |
starting_after | string | no | The next_cursor of the previous page. (1–64 characters) |
entity_id | ID (ent_…) | no | Optional: the brand's legal entity (ent_…); brand_id alone is enough. |
brand_id | ID (brd_…) | no | The brand to act on (brd_…), inside your API key's reach. Needed with an organization or entity key; a brand key acts on its own brand. |
- Brand: pass
brand_idwith an organization or entity key; a brand key acts on its own brand. - Read-only: changes nothing;
idempotency_keyis ignored.
Errors
livemode_not_allowed: The portal session is in the other mode than the request.permission_denied: The caller isn't a customer signed in to this brand's portal (operators and API keys use the subscription and wallet operations).
Example
A €25 pack and any amount
Arguments:
{
"brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz"
}
Result output:
{
"object": "list",
"data": [
{
"priceId": "price_01m4tx5nj8emwbyf7y9vcf48c9",
"object": "portal_credit_pack",
"productName": "Genno credits",
"name": "€25 credits",
"currency": "EUR",
"amount": {
"amount": "25000000",
"currency": "EUR"
},
"customAmount": null,
"credits": {
"amount": "25000000",
"currency": "EUR"
},
"bonusCredits": null,
"expiresAfterDays": null,
"taxBehavior": "inclusive"
},
{
"priceId": "price_01m4tx5nj8emwbyf7y9vcf48ca",
"object": "portal_credit_pack",
"productName": "Genno credits",
"name": "Any amount",
"currency": "EUR",
"amount": null,
"customAmount": {
"min": {
"amount": "5000000",
"currency": "EUR"
},
"max": {
"amount": "5000000000",
"currency": "EUR"
},
"preset": {
"amount": "50000000",
"currency": "EUR"
}
},
"credits": null,
"bonusCredits": null,
"expiresAfterDays": null,
"taxBehavior": "inclusive"
}
],
"has_more": false,
"next_cursor": null
}
portal_export_get
Export your data. Operation portal.export.get · Brand level · permission portal:read · risk tier read · since M9
Returns everything the brand holds about the signed-in customer as one file: the account with its tax IDs, location evidence, invoices and credit notes with their lines, payments, payment methods, subscriptions, wallets and the credit balance (at most 1,000 records of each type).
json: one document grouped by record type.csv: one row per value (record_type,record_id,field,value), nested fields as dotted paths.
The portal offers it as a download. Customer portal only (portal:read).
Annotations: readOnlyHint: true · destructiveHint: false · idempotentHint: true · openWorldHint: false
Arguments
| Argument | Type | Required | Description |
|---|---|---|---|
format | one of json, csv | no | json (default) or csv. (default "json") |
entity_id | ID (ent_…) | no | Optional: the brand's legal entity (ent_…); brand_id alone is enough. |
brand_id | ID (brd_…) | no | The brand to act on (brd_…), inside your API key's reach. Needed with an organization or entity key; a brand key acts on its own brand. |
- Brand: pass
brand_idwith an organization or entity key; a brand key acts on its own brand. - Read-only: changes nothing;
idempotency_keyis ignored.
Errors
validation_failed: A field or pagination value is malformed.livemode_not_allowed: The portal session is in the other mode than the request.permission_denied: The caller isn't a customer signed in to this brand's portal (operators and API keys use the customer operations).
Example
CSV export
Arguments:
{
"format": "csv",
"brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz"
}
Result output:
{
"object": "portal_export",
"format": "csv",
"filename": "genno-cus_01m4tx5nj8emwbyf7y9vcf48b1-2026-10-20.csv",
"contentType": "text/csv; charset=utf-8",
"content": "record_type,record_id,field,value\r\ncustomer,cus_01m4tx5nj8emwbyf7y9vcf48b1,email,ana@example.com\r\n"
}
portal_invoices_get
Get one of your invoices. Operation portal.invoices.get · Brand level · permission portal:read · risk tier read · since M9
Returns one of the signed-in customer's issued invoices: its lines, amounts, due date, the PDF link, the bank
transfer details of a send_invoice invoice (the invoice number is the payment reference) and whether it can be
paid by card now (card). Another customer's invoice reads as missing.
Customer portal only (portal:read).
Annotations: readOnlyHint: true · destructiveHint: false · idempotentHint: true · openWorldHint: false
Arguments
| Argument | Type | Required | Description |
|---|---|---|---|
invoiceId | ID (inv_…) | yes | One of your invoices (inv_…). |
entity_id | ID (ent_…) | no | Optional: the brand's legal entity (ent_…); brand_id alone is enough. |
brand_id | ID (brd_…) | no | The brand to act on (brd_…), inside your API key's reach. Needed with an organization or entity key; a brand key acts on its own brand. |
- Brand: pass
brand_idwith an organization or entity key; a brand key acts on its own brand. - Read-only: changes nothing;
idempotency_keyis ignored.
Errors
validation_failed: The invoice's ID or another field is malformed.resource_not_found: You have no such invoice (another customer's reads the same).livemode_not_allowed: The portal session is in the other mode than the request.permission_denied: The caller isn't a customer signed in to this brand's portal (operators and API keys use the customer operations).
Example
An open invoice
Arguments:
{
"invoiceId": "inv_01m4tx5nj8emwbyf7y9vcf48b3",
"brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz"
}
Result output:
{
"id": "inv_01m4tx5nj8emwbyf7y9vcf48b3",
"object": "portal_invoice",
"number": "GEN-2026-000042",
"kind": "invoice",
"status": "open",
"issueDate": "2026-10-20",
"dueDate": "2026-11-03",
"currency": "EUR",
"total": {
"amount": "120000000",
"currency": "EUR"
},
"creditApplied": null,
"amountDue": {
"amount": "120000000",
"currency": "EUR"
},
"paidAt": null,
"payable": true,
"pdfPath": "/api/v1/invoices/inv_01m4tx5nj8emwbyf7y9vcf48b3/pdf?token=eyJ…",
"sellerName": "Etna Labs AG",
"lines": [
{
"description": "Genno Pro (monthly)",
"quantity": 1,
"amount": {
"amount": "120000000",
"currency": "EUR"
}
}
],
"bankTransfer": {
"accountHolder": "Etna Labs AG",
"iban": "CH9300762011623852957",
"bic": "EXMPCHZZXXX",
"bankName": "Example Bank",
"reference": "GEN-2026-000042"
},
"card": {
"available": true,
"unavailableReason": null,
"component": "fake",
"publishableKey": null
}
}
portal_invoices_list
List your invoices. Operation portal.invoices.list · Brand level · permission portal:read · risk tier read · since M9
Lists the signed-in customer's issued invoices and receipts, newest first, with the amount due and a download
link of each PDF (pdfPath; null for a moment after issue, while the PDF is prepared). Open invoices with
something due are payable by card (portal.invoices.pay). Drafts aren't listed.
Paginate with limit (default 20) and starting_after. Customer portal only (portal:read).
Annotations: readOnlyHint: true · destructiveHint: false · idempotentHint: true · openWorldHint: false
Arguments
| Argument | Type | Required | Description |
|---|---|---|---|
limit | integer | no | Page size, 1 to 100 (default 20). (1–100) |
starting_after | string | no | The next_cursor of the previous page. (1–64 characters) |
entity_id | ID (ent_…) | no | Optional: the brand's legal entity (ent_…); brand_id alone is enough. |
brand_id | ID (brd_…) | no | The brand to act on (brd_…), inside your API key's reach. Needed with an organization or entity key; a brand key acts on its own brand. |
- Brand: pass
brand_idwith an organization or entity key; a brand key acts on its own brand. - Read-only: changes nothing;
idempotency_keyis ignored.
Errors
validation_failed: A field or pagination value is malformed.livemode_not_allowed: The portal session is in the other mode than the request.permission_denied: The caller isn't a customer signed in to this brand's portal (operators and API keys use the customer operations).
Example
Your latest invoices
Arguments:
{
"brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz"
}
Result output:
{
"object": "list",
"data": [
{
"id": "inv_01m4tx5nj8emwbyf7y9vcf48b3",
"object": "portal_invoice",
"number": "GEN-2026-000042",
"kind": "invoice",
"status": "open",
"issueDate": "2026-10-20",
"dueDate": "2026-11-03",
"currency": "EUR",
"total": {
"amount": "120000000",
"currency": "EUR"
},
"creditApplied": null,
"amountDue": {
"amount": "120000000",
"currency": "EUR"
},
"paidAt": null,
"payable": true,
"pdfPath": "/api/v1/invoices/inv_01m4tx5nj8emwbyf7y9vcf48b3/pdf?token=eyJ…"
}
],
"has_more": false,
"next_cursor": null
}
portal_invoices_pay
Pay one of your invoices by card. Operation portal.invoices.pay · Brand level · permission portal:write · risk tier financial · since M9
Starts a card payment of one of the signed-in customer's open invoices, for its amount due, at the brand's
processor: a one-off payment (no card is saved). expectedAmount is the amount due the customer saw; if it
changed meanwhile, the payment is refused.
- fake processor: the payment is confirmed here with the chosen test card (
paymentMethod):succeeded,processing,requires_action(pay again to pass the fake 3-D Secure challenge) or a decline; - Stripe: the answer is
client_confirmwith the Payment Element'sclientSecret; the browser confirms; - Mollie: the answer is
redirectwithredirectUrl, Mollie's payment page; the customer comes back to the invoice's hosted page.
The invoice is marked paid when the processor's webhook confirms the payment: read it again with
portal.invoices.get. Customer portal only (portal:write).
Annotations: readOnlyHint: false · destructiveHint: false · idempotentHint: false · openWorldHint: true
Arguments
| Argument | Type | Required | Description |
|---|---|---|---|
invoiceId | ID (inv_…) | yes | One of your invoices (inv_…). |
expectedAmount | object | yes | The amount due you saw; if it changed meanwhile, the payment is refused (invoice_payment_unavailable). |
paymentMethod | string | no | Fake processor only (development, CI): the test card, e.g. pm_fake_visa. (1–255 characters) |
entity_id | ID (ent_…) | no | Optional: the brand's legal entity (ent_…); brand_id alone is enough. |
brand_id | ID (brd_…) | no | The brand to act on (brd_…), inside your API key's reach. Needed with an organization or entity key; a brand key acts on its own brand. |
idempotency_key | string | no | A unique string for this change. Retrying with the same key and arguments never acts twice. When left out, the server generates one and returns it in the result's idempotency_key. (1–255 characters) |
dry_run | boolean | no | true: check permissions and rules and compute the result, then roll everything back. Nothing is saved. (default false) |
- Brand: pass
brand_idwith an organization or entity key; a brand key acts on its own brand. - Idempotency: the result returns the
idempotency_keyused, yours or one the server generated. Retry with it: a call with the same key and arguments never acts twice (keys are kept for 24 hours). - Dry run: supported.
dry_run: truechecks permissions and rules and computes the result, then rolls everything back.
Errors
validation_failed: The invoice's ID or another field is malformed.resource_not_found: You have no such invoice (another customer's reads the same).invoice_payment_unavailable: The invoice is paid or voided, has nothing due, its amount due changed, or the brand has no processor.processor_payment_declined: The card was declined; try another card or a bank transfer.processor_request_failed: The processor couldn't create or confirm the payment; try again.livemode_not_allowed: The portal session is in the other mode than the request.permission_denied: The caller isn't a customer signed in to this brand's portal (operators and API keys use the customer operations).
Example
Pay with a fake test card
Arguments:
{
"invoiceId": "inv_01m4tx5nj8emwbyf7y9vcf48b3",
"expectedAmount": {
"amount": "1200000000",
"currency": "EUR"
},
"paymentMethod": "pm_fake_visa",
"brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz",
"idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}
Result output:
{
"object": "hosted_invoice_payment",
"invoiceId": "inv_01m4tx5nj8emwbyf7y9vcf48b3",
"outcome": "succeeded",
"amount": {
"amount": "1200000000",
"currency": "EUR"
},
"clientSecret": null,
"publishableKey": null,
"redirectUrl": null
}
portal_login_links_request
Email a customer a portal sign-in link. Operation portal.login_links.request · Brand level · permission portal:write · risk tier write · since M9
Emails a one-time sign-in link for the brand's customer portal to the address, if it belongs to a customer of the brand in the portal's mode. The link works once, for 15 minutes; only a hash of its token is stored.
The answer is the same whether or not the address belongs to a customer, so nobody can find out who the
brand's customers are. Requests are limited per address (5 per 15 minutes per brand) and per client IP (20 per
hour), whether or not the address is known (rate_limited).
Public: the portal's sign-in page calls it, no API key needed.
Annotations: readOnlyHint: false · destructiveHint: false · idempotentHint: false · openWorldHint: true
Arguments
| Argument | Type | Required | Description |
|---|---|---|---|
brandSlug | string | yes | The brand's slug, as in /portal/{brandSlug}. (1–64 characters) |
email | string | yes | The address the customer uses with the brand. (at most 254 characters) |
entity_id | ID (ent_…) | no | Optional: the brand's legal entity (ent_…); brand_id alone is enough. |
brand_id | ID (brd_…) | no | The brand to act on (brd_…), inside your API key's reach. Needed with an organization or entity key; a brand key acts on its own brand. |
idempotency_key | string | no | A unique string for this change. Retrying with the same key and arguments never acts twice. When left out, the server generates one and returns it in the result's idempotency_key. (1–255 characters) |
- Brand: pass
brand_idwith an organization or entity key; a brand key acts on its own brand. - Idempotency: the result returns the
idempotency_keyused, yours or one the server generated. Retry with it: a call with the same key and arguments never acts twice (keys are kept for 24 hours). - Dry run: not supported.
dry_run: truefails withdry_run_not_supported.
Errors
validation_failed:emailisn't an email address, orbrandSlugisn't a slug.resource_not_found: No brand with this slug has a customer portal (draft and suspended brands have none).rate_limited: Too many sign-in links were asked for this address (5 per 15 minutes per brand) or from this client IP (20 per hour); waitretry_after_ms.
Example
Ask for a sign-in link
Arguments:
{
"brandSlug": "genno",
"email": "ana@example.com",
"brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz",
"idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}
Result output:
{
"object": "portal_login_link_request",
"email": "ana@example.com",
"message": "If ana@example.com belongs to an account with Genno, we've sent it a sign-in link. The link works once, for 15 minutes."
}
portal_payment_methods_delete
Remove a payment method. Operation portal.payment_methods.delete · Brand level · permission portal:write · risk tier write · since M9
Removes one of the signed-in customer's saved payment methods: it is never charged again and stops being the
default (subscriptions then charge the new default, if any). The processor detaches it too, right after. The
record stays for the history of the payments made with it. Customer portal only (portal:write).
Annotations: readOnlyHint: false · destructiveHint: true · idempotentHint: true · openWorldHint: true
Arguments
| Argument | Type | Required | Description |
|---|---|---|---|
paymentMethodId | ID (pm_…) | yes | One of your saved payment methods (pm_…). |
entity_id | ID (ent_…) | no | Optional: the brand's legal entity (ent_…); brand_id alone is enough. |
brand_id | ID (brd_…) | no | The brand to act on (brd_…), inside your API key's reach. Needed with an organization or entity key; a brand key acts on its own brand. |
idempotency_key | string | no | A unique string for this change. Retrying with the same key and arguments never acts twice. When left out, the server generates one and returns it in the result's idempotency_key. (1–255 characters) |
- Brand: pass
brand_idwith an organization or entity key; a brand key acts on its own brand. - Idempotency: the result returns the
idempotency_keyused, yours or one the server generated. Retry with it: a call with the same key and arguments never acts twice (keys are kept for 24 hours). - Dry run: not supported.
dry_run: truefails withdry_run_not_supported.
Errors
validation_failed: The payment method's ID or another field is malformed.resource_not_found: You have no such payment method (another customer's reads the same).version_conflict: Your payment methods changed at the same moment; retry.livemode_not_allowed: The portal session is in the other mode than the request.permission_denied: The caller isn't a customer signed in to this brand's portal (operators and API keys use the customer operations).
Example
Remove an old card
Arguments:
{
"paymentMethodId": "pm_01m4tx5nj8emwbyf7y9vcf48b6",
"brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz",
"idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}
Result output:
{
"id": "pm_01m4tx5nj8emwbyf7y9vcf48b6",
"object": "portal_payment_method",
"type": "card",
"card": {
"brand": "visa",
"last4": "4242",
"expMonth": 12,
"expYear": 2029,
"country": "DE"
},
"isDefault": false,
"createdAt": "2026-10-20T09:00:00.000Z"
}
portal_payment_methods_list
List your payment methods. Operation portal.payment_methods.list · Brand level · permission portal:read · risk tier read · since M9
Lists the cards (and other methods) the signed-in customer saved with the brand, newest first: brand, last four
digits, expiry and country, and which one is the default that subscriptions charge. Removed methods aren't
listed. Paginate with limit (default 20) and starting_after. Customer portal only (portal:read).
Annotations: readOnlyHint: true · destructiveHint: false · idempotentHint: true · openWorldHint: false
Arguments
| Argument | Type | Required | Description |
|---|---|---|---|
limit | integer | no | Page size, 1 to 100 (default 20). (1–100) |
starting_after | string | no | The next_cursor of the previous page. (1–64 characters) |
entity_id | ID (ent_…) | no | Optional: the brand's legal entity (ent_…); brand_id alone is enough. |
brand_id | ID (brd_…) | no | The brand to act on (brd_…), inside your API key's reach. Needed with an organization or entity key; a brand key acts on its own brand. |
- Brand: pass
brand_idwith an organization or entity key; a brand key acts on its own brand. - Read-only: changes nothing;
idempotency_keyis ignored.
Errors
validation_failed: A field or pagination value is malformed.livemode_not_allowed: The portal session is in the other mode than the request.permission_denied: The caller isn't a customer signed in to this brand's portal (operators and API keys use the customer operations).
Example
Two saved cards
Arguments:
{
"brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz"
}
Result output:
{
"object": "list",
"data": [
{
"id": "pm_01m4tx5nj8emwbyf7y9vcf48b5",
"object": "portal_payment_method",
"type": "card",
"card": {
"brand": "visa",
"last4": "4242",
"expMonth": 12,
"expYear": 2029,
"country": "DE"
},
"isDefault": true,
"createdAt": "2026-10-20T09:00:00.000Z"
},
{
"id": "pm_01m4tx5nj8emwbyf7y9vcf48b6",
"object": "portal_payment_method",
"type": "card",
"card": {
"brand": "mastercard",
"last4": "4444",
"expMonth": 3,
"expYear": 2028,
"country": "AT"
},
"isDefault": false,
"createdAt": "2026-10-20T09:00:00.000Z"
}
],
"has_more": false,
"next_cursor": null
}
portal_payment_methods_set_default
Make a payment method your default. Operation portal.payment_methods.set_default · Brand level · permission portal:write · risk tier write · since M9
Makes one of the signed-in customer's saved payment methods the default: subscriptions that don't name their own
method charge it from now on, and a past-due invoice is retried with it at once. Customer portal only
(portal:write).
Annotations: readOnlyHint: false · destructiveHint: false · idempotentHint: true · openWorldHint: false
Arguments
| Argument | Type | Required | Description |
|---|---|---|---|
paymentMethodId | ID (pm_…) | yes | One of your saved payment methods (pm_…). |
entity_id | ID (ent_…) | no | Optional: the brand's legal entity (ent_…); brand_id alone is enough. |
brand_id | ID (brd_…) | no | The brand to act on (brd_…), inside your API key's reach. Needed with an organization or entity key; a brand key acts on its own brand. |
idempotency_key | string | no | A unique string for this change. Retrying with the same key and arguments never acts twice. When left out, the server generates one and returns it in the result's idempotency_key. (1–255 characters) |
- Brand: pass
brand_idwith an organization or entity key; a brand key acts on its own brand. - Idempotency: the result returns the
idempotency_keyused, yours or one the server generated. Retry with it: a call with the same key and arguments never acts twice (keys are kept for 24 hours). - Dry run: not supported.
dry_run: truefails withdry_run_not_supported.
Errors
validation_failed: The payment method's ID or another field is malformed.resource_not_found: You have no such payment method (another customer's reads the same).version_conflict: Your payment methods changed at the same moment; retry.livemode_not_allowed: The portal session is in the other mode than the request.permission_denied: The caller isn't a customer signed in to this brand's portal (operators and API keys use the customer operations).
Example
Use the new card
Arguments:
{
"paymentMethodId": "pm_01m4tx5nj8emwbyf7y9vcf48b6",
"brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz",
"idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}
Result output:
{
"id": "pm_01m4tx5nj8emwbyf7y9vcf48b6",
"object": "portal_payment_method",
"type": "card",
"card": {
"brand": "visa",
"last4": "4242",
"expMonth": 12,
"expYear": 2029,
"country": "DE"
},
"isDefault": true,
"createdAt": "2026-10-20T09:00:00.000Z"
}
portal_payment_methods_setup
Add a payment method. Operation portal.payment_methods.setup · Brand level · permission portal:write · risk tier write · since M9
Starts adding a card for the signed-in customer: returns the URL of a hosted page (a checkout in setup mode)
where the card is entered at the brand's processor (the Stripe Payment Element, or the fake processor's test
cards in development). Nothing is charged. The card is saved when the processor confirms it and becomes the
default; the page then returns to the portal's payment methods. The page works for 24 hours.
Customer portal only (portal:write).
Annotations: readOnlyHint: false · destructiveHint: false · idempotentHint: false · openWorldHint: false
Arguments
| Argument | Type | Required | Description |
|---|---|---|---|
entity_id | ID (ent_…) | no | Optional: the brand's legal entity (ent_…); brand_id alone is enough. |
brand_id | ID (brd_…) | no | The brand to act on (brd_…), inside your API key's reach. Needed with an organization or entity key; a brand key acts on its own brand. |
idempotency_key | string | no | A unique string for this change. Retrying with the same key and arguments never acts twice. When left out, the server generates one and returns it in the result's idempotency_key. (1–255 characters) |
- Brand: pass
brand_idwith an organization or entity key; a brand key acts on its own brand. - Idempotency: the result returns the
idempotency_keyused, yours or one the server generated. Retry with it: a call with the same key and arguments never acts twice (keys are kept for 24 hours). - Dry run: not supported.
dry_run: truefails withdry_run_not_supported.
Errors
validation_failed: The account's ID or another field is malformed.resource_not_found: You have no such account (another customer's reads the same).customer_deleted: Your account was deleted by the brand.livemode_not_allowed: The portal session is in the other mode than the request.permission_denied: The caller isn't a customer signed in to this brand's portal (operators and API keys use the customer operations).
Example
Add a card
Arguments:
{
"brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz",
"idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}
Result output:
{
"object": "portal_payment_method_setup",
"url": "https://ownrecord.io/c/genno/cs_01m4tx5nj8emwbyf7y9vcf48b8?key=Zm9yLWRvY3Mtb25seS4uLi4uLi4uLi4uLi4",
"expiresAt": "2026-10-21T09:00:00.000Z"
}
portal_payment_methods_use_for_subscriptions
Charge your default card for all your subscriptions. Operation portal.payment_methods.use_for_subscriptions · Brand level · permission portal:write · risk tier write · since M13
Switches every running, card-charged subscription of the signed-in customer that charges another card to their
default card (paymentMethodId, the default card they saw): from then on each charges the default card, also
after the default changes again. A past-due invoice is retried with it at once. Subscriptions paid by bank
transfer, ended ones and those the brand's portal settings don't let you change are left as they are. Returns the
subscriptions it switched (none when every subscription already charges the default card). Customer portal only
(portal:write).
Annotations: readOnlyHint: false · destructiveHint: false · idempotentHint: true · openWorldHint: true
Arguments
| Argument | Type | Required | Description |
|---|---|---|---|
paymentMethodId | ID (pm_…) | yes | Your default card (pm_…), as you saw it. |
entity_id | ID (ent_…) | no | Optional: the brand's legal entity (ent_…); brand_id alone is enough. |
brand_id | ID (brd_…) | no | The brand to act on (brd_…), inside your API key's reach. Needed with an organization or entity key; a brand key acts on its own brand. |
idempotency_key | string | no | A unique string for this change. Retrying with the same key and arguments never acts twice. When left out, the server generates one and returns it in the result's idempotency_key. (1–255 characters) |
- Brand: pass
brand_idwith an organization or entity key; a brand key acts on its own brand. - Idempotency: the result returns the
idempotency_keyused, yours or one the server generated. Retry with it: a call with the same key and arguments never acts twice (keys are kept for 24 hours). - Dry run: not supported.
dry_run: truefails withdry_run_not_supported.
Errors
validation_failed: The payment method's ID is malformed.subscription_payment_method_invalid: The card isn't your default card (any more), or it was removed: reload your payment methods.version_conflict: One of your subscriptions changed at the same moment; retry.livemode_not_allowed: The portal session is in the other mode than the request.permission_denied: The caller isn't a customer signed in to this brand's portal (operators and API keys use the subscription and wallet operations).
Example
Use the card you just added for your subscriptions
Arguments:
{
"paymentMethodId": "pm_01m4tx5nj8emwbyf7y9vcf48cb",
"brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz",
"idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}
Result output:
{
"object": "list",
"data": [
{
"id": "sub_01m4tx5nj8emwbyf7y9vcf48c0",
"object": "portal_subscription",
"status": "active",
"currency": "EUR",
"items": [
{
"priceId": "price_01m4tx5nj8emwbyf7y9vcf48c2",
"productId": "prod_01m4tx5nj8emwbyf7y9vcf48c4",
"productName": "Genno Plus",
"priceName": "Plus, monthly",
"interval": {
"interval": "month",
"intervalCount": 1
},
"usageType": "licensed",
"unitAmount": {
"amount": "9000000",
"currency": "EUR"
},
"taxBehavior": "inclusive",
"id": "si_01m4tx5nj8emwbyf7y9vcf48c1",
"quantity": 1,
"amount": {
"amount": "9000000",
"currency": "EUR"
}
}
],
"collectionMethod": "charge_automatically",
"paymentMethodId": null,
"currentPeriodStart": "2026-10-20T09:00:00.000Z",
"currentPeriodEnd": "2026-11-20T10:00:00.000Z",
"trialEnd": null,
"cancelAtPeriodEnd": false,
"cancelAt": null,
"canceledAt": null,
"endedAt": null,
"cancellation": null,
"discounts": [],
"scheduledChange": null,
"actions": {
"switch_plan": true,
"change_quantity": false,
"apply_promotion_code": true,
"cancel": true,
"reactivate": false,
"change_payment_method": true
},
"cancelBehavior": "period_end",
"collectCancelReason": true,
"version": 5,
"createdAt": "2026-09-20T09:00:00.000Z"
}
]
}
portal_sessions_create
Generate a customer portal link. Operation portal.sessions.create · Brand level · permission customers:write · risk tier security · since M9
Makes a one-time sign-in link to the brand's customer portal for one customer, in the request's mode: send the customer there (for example from your own app's billing page). The link works once, for 15 minutes; the customer's session then lasts 7 days.
Generating a link doesn't sign you in: whoever opens it is signed in as the customer, so give it only to them.
Generating it is recorded in the audit log with who asked. Only a hash of the link's token is stored. Needs
customers:write.
Annotations: readOnlyHint: false · destructiveHint: false · idempotentHint: false · openWorldHint: false
Arguments
| Argument | Type | Required | Description |
|---|---|---|---|
customerId | ID (cus_…) | yes | The customer the link signs in (cus_…). |
entity_id | ID (ent_…) | no | Optional: the brand's legal entity (ent_…); brand_id alone is enough. |
brand_id | ID (brd_…) | no | The brand to act on (brd_…), inside your API key's reach. Needed with an organization or entity key; a brand key acts on its own brand. |
idempotency_key | string | no | A unique string for this change. Retrying with the same key and arguments never acts twice. When left out, the server generates one and returns it in the result's idempotency_key. (1–255 characters) |
- Brand: pass
brand_idwith an organization or entity key; a brand key acts on its own brand. - Idempotency: the result returns the
idempotency_keyused, yours or one the server generated. Retry with it: a call with the same key and arguments never acts twice (keys are kept for 24 hours). - Dry run: not supported.
dry_run: truefails withdry_run_not_supported.
Errors
validation_failed:customerIdisn't a customer ID (cus_…).resource_not_found: This brand has no such customer in this mode (customers belong to one brand and one mode).customer_deleted: The customer was deleted.scope_required: The request names no brand (an org or entity key withoutOwnrecord-Brand).livemode_not_allowed: You have no live-mode access, so you can't act on live customers.permission_denied: You can't change this brand's customers (customers:write: owners, admins and support).
Example
A sign-in link for a customer
Arguments:
{
"customerId": "cus_01m4tx5nj8emwbyf7y9vcf48b1",
"brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz",
"idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}
Result output:
{
"object": "portal_session_link",
"id": "psess_01m4tx5nj8emwbyf7y9vcf48b2",
"customerId": "cus_01m4tx5nj8emwbyf7y9vcf48b1",
"livemode": false,
"url": "https://ownrecord.io/portal/genno/login/verify?session=psess_01m4tx5nj8emwbyf7y9vcf48b2&token=Q2hhbmdlTWVJbkV4YW1wbGVzT25seS4uLi4uLi4uLi4",
"expiresAt": "2026-10-20T09:15:00.000Z"
}
portal_sessions_redeem
Sign in to the customer portal with a link. Operation portal.sessions.redeem · Brand level · permission portal:write · risk tier security · since M9
Redeems a one-time sign-in link (from the portal's email, or an operator's portal.sessions.create): the link's
portal session becomes an active session for 7 days, and the response carries its sessionToken once. The
portal keeps it in an HttpOnly, Secure, SameSite=Lax cookie on its own host; only its hash is stored.
A link works once and for 15 minutes. A used, expired or signed-out link, or a wrong token, is
portal_link_invalid, all alike. The customer's portalLastLoginAt is set.
Public: the session's own brand and mode apply (a test-mode link signs in to the test-mode portal).
Annotations: readOnlyHint: false · destructiveHint: false · idempotentHint: false · openWorldHint: false
Arguments
| Argument | Type | Required | Description |
|---|---|---|---|
portalSessionId | ID (psess_…) | yes | The portal session's ID (psess_…). |
token | string | yes | The token of the sign-in link. |
entity_id | ID (ent_…) | no | Optional: the brand's legal entity (ent_…); brand_id alone is enough. |
brand_id | ID (brd_…) | no | The brand to act on (brd_…), inside your API key's reach. Needed with an organization or entity key; a brand key acts on its own brand. |
idempotency_key | string | no | A unique string for this change. Retrying with the same key and arguments never acts twice. When left out, the server generates one and returns it in the result's idempotency_key. (1–255 characters) |
- Brand: pass
brand_idwith an organization or entity key; a brand key acts on its own brand. - Idempotency: the result returns the
idempotency_keyused, yours or one the server generated. Retry with it: a call with the same key and arguments never acts twice (keys are kept for 24 hours). - Dry run: not supported.
dry_run: truefails withdry_run_not_supported.
Errors
validation_failed:portalSessionIdortokenis malformed.resource_not_found: No portal session has this ID.portal_link_invalid: The link was already used, expired (15 minutes), was signed out of, or the token is wrong.customer_deleted: The customer was deleted after the link was sent.rate_limited: Too many requests from this address or client IP; waitretry_after_ms.
Example
Redeem the link from the email
Arguments:
{
"portalSessionId": "psess_01m4tx5nj8emwbyf7y9vcf48b2",
"token": "Q2hhbmdlTWVJbkV4YW1wbGVzT25seS4uLi4uLi4uLi4",
"brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz",
"idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}
Result output:
{
"object": "portal_session",
"id": "psess_01m4tx5nj8emwbyf7y9vcf48b2",
"customerId": "cus_01m4tx5nj8emwbyf7y9vcf48b1",
"brandId": "brd_01m4tx5nj8emwbyf7y9vcf48b0",
"brandSlug": "genno",
"livemode": false,
"sessionToken": "U2Vzc2lvblRva2VuRm9yRG9jc09ubHkuLi4uLi4uLi4",
"expiresAt": "2026-10-27T09:01:00.000Z"
}
portal_sessions_revoke
Sign out of the customer portal. Operation portal.sessions.revoke · Brand level · permission portal:write · risk tier write · since M9
Ends the signed-in customer's portal session at once; the portal also clears its cookie. Other sessions of the customer (other browsers) keep working until they expire.
Customer portal only: the caller is the customer signed in to the brand's portal (portal:write).
Annotations: readOnlyHint: false · destructiveHint: true · idempotentHint: true · openWorldHint: false
Arguments
| Argument | Type | Required | Description |
|---|---|---|---|
entity_id | ID (ent_…) | no | Optional: the brand's legal entity (ent_…); brand_id alone is enough. |
brand_id | ID (brd_…) | no | The brand to act on (brd_…), inside your API key's reach. Needed with an organization or entity key; a brand key acts on its own brand. |
idempotency_key | string | no | A unique string for this change. Retrying with the same key and arguments never acts twice. When left out, the server generates one and returns it in the result's idempotency_key. (1–255 characters) |
- Brand: pass
brand_idwith an organization or entity key; a brand key acts on its own brand. - Idempotency: the result returns the
idempotency_keyused, yours or one the server generated. Retry with it: a call with the same key and arguments never acts twice (keys are kept for 24 hours). - Dry run: not supported.
dry_run: truefails withdry_run_not_supported.
Errors
livemode_not_allowed: The portal session is in the other mode than the request.permission_denied: The caller isn't a customer signed in to this brand's portal (operators and API keys use the customer operations).
Example
Sign out
Arguments:
{
"brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz",
"idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}
Result output:
{
"object": "portal_session",
"id": "psess_01m4tx5nj8emwbyf7y9vcf48b2",
"signedOut": true
}
portal_subscriptions_apply_promotion_code
Apply a promotion code. Operation portal.subscriptions.apply_promotion_code · Brand level · permission portal:write · risk tier financial · since M9
Applies a promotion code the brand published to the signed-in customer's subscription: its coupon becomes a
discount of the subscription from now, for the coupon's duration. The code's own rules apply: it must be active
and unexpired with redemptions left, and meet its restrictions (this customer only, first-time customers only, a
minimum amount). Not while a cancellation is scheduled. Customer portal only (portal:write).
Annotations: readOnlyHint: false · destructiveHint: false · idempotentHint: false · openWorldHint: false
Arguments
| Argument | Type | Required | Description |
|---|---|---|---|
subscriptionId | ID (sub_…) | yes | One of your subscriptions (sub_…). |
promotionCode | string | yes | A promotion code as the customer typed it (case doesn't matter). (1–50 characters) |
entity_id | ID (ent_…) | no | Optional: the brand's legal entity (ent_…); brand_id alone is enough. |
brand_id | ID (brd_…) | no | The brand to act on (brd_…), inside your API key's reach. Needed with an organization or entity key; a brand key acts on its own brand. |
idempotency_key | string | no | A unique string for this change. Retrying with the same key and arguments never acts twice. When left out, the server generates one and returns it in the result's idempotency_key. (1–255 characters) |
dry_run | boolean | no | true: check permissions and rules and compute the result, then roll everything back. Nothing is saved. (default false) |
- Brand: pass
brand_idwith an organization or entity key; a brand key acts on its own brand. - Idempotency: the result returns the
idempotency_keyused, yours or one the server generated. Retry with it: a call with the same key and arguments never acts twice (keys are kept for 24 hours). - Dry run: supported.
dry_run: truechecks permissions and rules and computes the result, then rolls everything back.
Errors
validation_failed: The code is empty or too long.resource_not_found: You have no such subscription (another customer's reads the same), or the brand has no such code (param: promotionCode).portal_action_not_allowed: The brand's portal settings or the subscription's state don't allow promotion codes now (a cancellation is scheduled, or the subscription can't change) (details.action).promotion_code_not_redeemable: The code is archived, has expired or is used up, is for another customer or first-time customers only, or the plan doesn't reach its minimum (details.reason).discount_already_applied: This code's coupon already applies to the subscription.subscription_status_invalid: The subscription can't change in its status.livemode_not_allowed: The portal session is in the other mode than the request.permission_denied: The caller isn't a customer signed in to this brand's portal (operators and API keys use the subscription and wallet operations).
Example
20% off for three months
Arguments:
{
"subscriptionId": "sub_01m4tx5nj8emwbyf7y9vcf48c0",
"promotionCode": "AUTUMN20",
"brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz",
"idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}
Result output:
{
"id": "sub_01m4tx5nj8emwbyf7y9vcf48c0",
"object": "portal_subscription",
"status": "active",
"currency": "EUR",
"items": [
{
"priceId": "price_01m4tx5nj8emwbyf7y9vcf48c2",
"productId": "prod_01m4tx5nj8emwbyf7y9vcf48c4",
"productName": "Genno Plus",
"priceName": "Plus, monthly",
"interval": {
"interval": "month",
"intervalCount": 1
},
"usageType": "licensed",
"unitAmount": {
"amount": "9000000",
"currency": "EUR"
},
"taxBehavior": "inclusive",
"id": "si_01m4tx5nj8emwbyf7y9vcf48c1",
"quantity": 1,
"amount": {
"amount": "9000000",
"currency": "EUR"
}
}
],
"collectionMethod": "charge_automatically",
"paymentMethodId": null,
"currentPeriodStart": "2026-10-20T09:00:00.000Z",
"currentPeriodEnd": "2026-11-20T10:00:00.000Z",
"trialEnd": null,
"cancelAtPeriodEnd": false,
"cancelAt": null,
"canceledAt": null,
"endedAt": null,
"cancellation": null,
"discounts": [
{
"id": "disc_01m4tx5nj8emwbyf7y9vcf48c6",
"name": "Autumn 20%",
"percentOffPpm": "200000",
"amountOff": null,
"start": "2026-11-05T09:30:00.000Z",
"end": "2027-02-20T10:00:00.000Z"
}
],
"scheduledChange": null,
"actions": {
"switch_plan": true,
"change_quantity": false,
"apply_promotion_code": true,
"cancel": true,
"reactivate": false,
"change_payment_method": true
},
"cancelBehavior": "period_end",
"collectCancelReason": true,
"version": 5,
"createdAt": "2026-09-20T09:00:00.000Z"
}
portal_subscriptions_cancel
Cancel your subscription. Operation portal.subscriptions.cancel · Brand level · permission portal:write · risk tier financial · since M9
Cancels the signed-in customer's subscription as the brand's portal settings say: at the end of the paid period
(cancelBehavior: period_end, the default; it can be reactivated until then), or at once (immediate: usage
and pending items so far are invoiced, the unused time isn't credited). When the brand asks why
(collectCancelReason), the customer may give a reason (feedback) and a comment; they're kept on the
subscription for the brand. Recorded as canceled by the customer. Emits subscription.cancel_scheduled or
subscription.canceled. Customer portal only (portal:write).
Annotations: readOnlyHint: false · destructiveHint: true · idempotentHint: true · openWorldHint: true
Arguments
| Argument | Type | Required | Description |
|---|---|---|---|
subscriptionId | ID (sub_…) | yes | One of your subscriptions (sub_…). |
expectedVersion | integer | no | The subscription's version you saw (the preview's); if it changed meanwhile, version_conflict. (1–9007199254740991) |
feedback | one of too_expensive, missing_features, switched_service, unused, customer_service, too_complex, low_quality, other | no | Why you're leaving, when the brand asks (collectCancelReason). |
comment | string | no | Anything you'd like to tell the brand, when it asks. (1–500 characters) |
entity_id | ID (ent_…) | no | Optional: the brand's legal entity (ent_…); brand_id alone is enough. |
brand_id | ID (brd_…) | no | The brand to act on (brd_…), inside your API key's reach. Needed with an organization or entity key; a brand key acts on its own brand. |
idempotency_key | string | no | A unique string for this change. Retrying with the same key and arguments never acts twice. When left out, the server generates one and returns it in the result's idempotency_key. (1–255 characters) |
dry_run | boolean | no | true: check permissions and rules and compute the result, then roll everything back. Nothing is saved. (default false) |
- Brand: pass
brand_idwith an organization or entity key; a brand key acts on its own brand. - Idempotency: the result returns the
idempotency_keyused, yours or one the server generated. Retry with it: a call with the same key and arguments never acts twice (keys are kept for 24 hours). - Dry run: supported.
dry_run: truechecks permissions and rules and computes the result, then rolls everything back.
Errors
validation_failed: A field is malformed, or a reason was sent to a brand that doesn't ask for one.resource_not_found: You have no such subscription (another customer's reads the same).portal_action_not_allowed: The brand's portal settings or the subscription's state don't allow cancelling: cancelling is off, a cancellation is already scheduled, or the status can't be canceled (details.action).subscription_status_invalid: The subscription can't be canceled in its status.version_conflict: The subscription changed since you saw it (expectedVersion); look again.jurisdiction_blocked: The brand can't sell in your country now (an invoice due now can't be issued).registration_required: The brand can't invoice your country yet (an invoice due now can't be issued).livemode_not_allowed: The portal session is in the other mode than the request.permission_denied: The caller isn't a customer signed in to this brand's portal (operators and API keys use the subscription and wallet operations).
Example
Cancel at the end of the period, too expensive
Arguments:
{
"subscriptionId": "sub_01m4tx5nj8emwbyf7y9vcf48c0",
"expectedVersion": 4,
"feedback": "too_expensive",
"comment": "Back when my project starts again.",
"brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz",
"idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}
Result output:
{
"id": "sub_01m4tx5nj8emwbyf7y9vcf48c0",
"object": "portal_subscription",
"status": "active",
"currency": "EUR",
"items": [
{
"priceId": "price_01m4tx5nj8emwbyf7y9vcf48c2",
"productId": "prod_01m4tx5nj8emwbyf7y9vcf48c4",
"productName": "Genno Plus",
"priceName": "Plus, monthly",
"interval": {
"interval": "month",
"intervalCount": 1
},
"usageType": "licensed",
"unitAmount": {
"amount": "9000000",
"currency": "EUR"
},
"taxBehavior": "inclusive",
"id": "si_01m4tx5nj8emwbyf7y9vcf48c1",
"quantity": 1,
"amount": {
"amount": "9000000",
"currency": "EUR"
}
}
],
"collectionMethod": "charge_automatically",
"paymentMethodId": null,
"currentPeriodStart": "2026-10-20T09:00:00.000Z",
"currentPeriodEnd": "2026-11-20T10:00:00.000Z",
"trialEnd": null,
"cancelAtPeriodEnd": true,
"cancelAt": "2026-11-20T10:00:00.000Z",
"canceledAt": null,
"endedAt": null,
"cancellation": {
"by": "customer",
"feedback": "too_expensive",
"comment": "Back when my project starts again."
},
"discounts": [],
"scheduledChange": null,
"actions": {
"switch_plan": false,
"change_quantity": false,
"apply_promotion_code": false,
"cancel": false,
"reactivate": true,
"change_payment_method": true
},
"cancelBehavior": "period_end",
"collectCancelReason": true,
"version": 5,
"createdAt": "2026-09-20T09:00:00.000Z"
}
portal_subscriptions_get
Get one of your subscriptions. Operation portal.subscriptions.get · Brand level · permission portal:read · risk tier read · since M9
Returns one of the signed-in customer's subscriptions as portal.subscriptions.list shows it, plus the plans
each item may switch to under the brand's plan-change matrix (planOptions) and the upcoming invoice as it
stands now (upcomingInvoice: the next renewal's lines, tax and amount due; null when nothing is billed next).
Customer portal only (portal:read).
Annotations: readOnlyHint: true · destructiveHint: false · idempotentHint: true · openWorldHint: false
Arguments
| Argument | Type | Required | Description |
|---|---|---|---|
subscriptionId | ID (sub_…) | yes | One of your subscriptions (sub_…). |
entity_id | ID (ent_…) | no | Optional: the brand's legal entity (ent_…); brand_id alone is enough. |
brand_id | ID (brd_…) | no | The brand to act on (brd_…), inside your API key's reach. Needed with an organization or entity key; a brand key acts on its own brand. |
- Brand: pass
brand_idwith an organization or entity key; a brand key acts on its own brand. - Read-only: changes nothing;
idempotency_keyis ignored.
Errors
validation_failed: The subscription's ID or a pagination value is malformed.resource_not_found: You have no such subscription (another customer's reads the same).livemode_not_allowed: The portal session is in the other mode than the request.permission_denied: The caller isn't a customer signed in to this brand's portal (operators and API keys use the subscription and wallet operations).
Example
A monthly plan that can move up to Pro
Arguments:
{
"subscriptionId": "sub_01m4tx5nj8emwbyf7y9vcf48c0",
"brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz"
}
Result output:
{
"id": "sub_01m4tx5nj8emwbyf7y9vcf48c0",
"object": "portal_subscription",
"status": "active",
"currency": "EUR",
"items": [
{
"priceId": "price_01m4tx5nj8emwbyf7y9vcf48c2",
"productId": "prod_01m4tx5nj8emwbyf7y9vcf48c4",
"productName": "Genno Plus",
"priceName": "Plus, monthly",
"interval": {
"interval": "month",
"intervalCount": 1
},
"usageType": "licensed",
"unitAmount": {
"amount": "9000000",
"currency": "EUR"
},
"taxBehavior": "inclusive",
"id": "si_01m4tx5nj8emwbyf7y9vcf48c1",
"quantity": 1,
"amount": {
"amount": "9000000",
"currency": "EUR"
}
}
],
"collectionMethod": "charge_automatically",
"paymentMethodId": null,
"currentPeriodStart": "2026-10-20T09:00:00.000Z",
"currentPeriodEnd": "2026-11-20T10:00:00.000Z",
"trialEnd": null,
"cancelAtPeriodEnd": false,
"cancelAt": null,
"canceledAt": null,
"endedAt": null,
"cancellation": null,
"discounts": [],
"scheduledChange": null,
"actions": {
"switch_plan": true,
"change_quantity": false,
"apply_promotion_code": true,
"cancel": true,
"reactivate": false,
"change_payment_method": true
},
"cancelBehavior": "period_end",
"collectCancelReason": true,
"version": 4,
"createdAt": "2026-09-20T09:00:00.000Z",
"planOptions": [
{
"itemId": "si_01m4tx5nj8emwbyf7y9vcf48c1",
"targets": [
{
"priceId": "price_01m4tx5nj8emwbyf7y9vcf48c3",
"productId": "prod_01m4tx5nj8emwbyf7y9vcf48c5",
"productName": "Genno Pro",
"priceName": "Pro, monthly",
"interval": {
"interval": "month",
"intervalCount": 1
},
"usageType": "licensed",
"unitAmount": {
"amount": "29000000",
"currency": "EUR"
},
"taxBehavior": "inclusive"
}
]
}
],
"upcomingInvoice": {
"currency": "EUR",
"lines": [
{
"description": "Genno Plus (Plus, monthly)",
"quantity": 1,
"amount": {
"amount": "9000000",
"currency": "EUR"
},
"proration": false,
"periodStart": "2026-11-20T10:00:00.000Z",
"periodEnd": "2026-12-20T10:00:00.000Z"
}
],
"subtotal": {
"amount": "7377049",
"currency": "EUR"
},
"tax": {
"amount": "1622951",
"currency": "EUR"
},
"total": {
"amount": "9000000",
"currency": "EUR"
},
"creditApplied": null,
"amountDue": {
"amount": "9000000",
"currency": "EUR"
}
}
}
portal_subscriptions_list
List your subscriptions. Operation portal.subscriptions.list · Brand level · permission portal:read · risk tier read · since M9
Lists the signed-in customer's subscriptions with this brand, newest first: status, plan and quantity, the
current period, discounts, a scheduled change or cancellation, and what the brand's portal settings let the
customer do with each (actions). Customer portal only (portal:read).
Annotations: readOnlyHint: true · destructiveHint: false · idempotentHint: true · openWorldHint: false
Arguments
| Argument | Type | Required | Description |
|---|---|---|---|
limit | integer | no | Page size, 1 to 100 (default 20). (1–100) |
starting_after | string | no | The next_cursor of the previous page. (1–64 characters) |
entity_id | ID (ent_…) | no | Optional: the brand's legal entity (ent_…); brand_id alone is enough. |
brand_id | ID (brd_…) | no | The brand to act on (brd_…), inside your API key's reach. Needed with an organization or entity key; a brand key acts on its own brand. |
- Brand: pass
brand_idwith an organization or entity key; a brand key acts on its own brand. - Read-only: changes nothing;
idempotency_keyis ignored.
Errors
validation_failed: A pagination value is malformed.livemode_not_allowed: The portal session is in the other mode than the request.permission_denied: The caller isn't a customer signed in to this brand's portal (operators and API keys use the subscription and wallet operations).
Example
One active plan
Arguments:
{
"brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz"
}
Result output:
{
"object": "list",
"data": [
{
"id": "sub_01m4tx5nj8emwbyf7y9vcf48c0",
"object": "portal_subscription",
"status": "active",
"currency": "EUR",
"items": [
{
"priceId": "price_01m4tx5nj8emwbyf7y9vcf48c2",
"productId": "prod_01m4tx5nj8emwbyf7y9vcf48c4",
"productName": "Genno Plus",
"priceName": "Plus, monthly",
"interval": {
"interval": "month",
"intervalCount": 1
},
"usageType": "licensed",
"unitAmount": {
"amount": "9000000",
"currency": "EUR"
},
"taxBehavior": "inclusive",
"id": "si_01m4tx5nj8emwbyf7y9vcf48c1",
"quantity": 1,
"amount": {
"amount": "9000000",
"currency": "EUR"
}
}
],
"collectionMethod": "charge_automatically",
"paymentMethodId": null,
"currentPeriodStart": "2026-10-20T09:00:00.000Z",
"currentPeriodEnd": "2026-11-20T10:00:00.000Z",
"trialEnd": null,
"cancelAtPeriodEnd": false,
"cancelAt": null,
"canceledAt": null,
"endedAt": null,
"cancellation": null,
"discounts": [],
"scheduledChange": null,
"actions": {
"switch_plan": true,
"change_quantity": false,
"apply_promotion_code": true,
"cancel": true,
"reactivate": false,
"change_payment_method": true
},
"cancelBehavior": "period_end",
"collectCancelReason": true,
"version": 4,
"createdAt": "2026-09-20T09:00:00.000Z"
}
],
"has_more": false,
"next_cursor": null
}
portal_subscriptions_preview_change
Preview a plan or quantity change. Operation portal.subscriptions.preview_change · Brand level · permission portal:read · risk tier read · since M9
Shows exactly what switching one item of the signed-in customer's subscription to another plan, or changing its
quantity, would do, without changing anything: an upgrade applies now and its invoice is issued and charged now
(invoice: the credit for the unused time, the charge for the time left, tax and the amount due); a downgrade
applies at the end of the period with nothing to pay now. The brand's portal settings decide what may change:
a plan switch only to a plan its plan-change matrix lists for the item's plan, a quantity change only when the
brand allows it. Confirm with portal.subscriptions.update and the preview's version.
Proration is exact to the second, so the real change may differ by the time that passed. Customer portal only
(portal:read).
Annotations: readOnlyHint: true · destructiveHint: false · idempotentHint: true · openWorldHint: true
Arguments
| Argument | Type | Required | Description |
|---|---|---|---|
subscriptionId | ID (sub_…) | yes | One of your subscriptions (sub_…). |
itemId | ID (si_…) | yes | The subscription item to change (si_…). |
priceId | ID (price_…) | no | Switch the item to this price: one the brand's plan-change matrix allows from its price. |
quantity | integer | no | The item's new quantity (seats), when the brand lets customers change it. (1–1000000) |
entity_id | ID (ent_…) | no | Optional: the brand's legal entity (ent_…); brand_id alone is enough. |
brand_id | ID (brd_…) | no | The brand to act on (brd_…), inside your API key's reach. Needed with an organization or entity key; a brand key acts on its own brand. |
- Brand: pass
brand_idwith an organization or entity key; a brand key acts on its own brand. - Read-only: changes nothing;
idempotency_keyis ignored.
Errors
validation_failed: A field is malformed, or neitherpriceIdnorquantitywas sent.resource_not_found: You have no such subscription (another customer's reads the same).subscription_item_not_found:itemIdisn't one of the subscription's items.portal_action_not_allowed: The brand's portal settings or the subscription's state don't allow this change: plan switches need the brand's plan-change matrix to list the target, quantity changes need the brand's permission and a licensed item (details.action).subscription_change_invalid: The change can't be made now (for example a change already waits for its invoice to be paid).subscription_status_invalid: The subscription can't change in its status.subscription_price_invalid: The new plan's price is archived.jurisdiction_blocked: The brand can't sell in your country now (an invoice due now can't be issued).registration_required: The brand can't invoice your country yet (an invoice due now can't be issued).livemode_not_allowed: The portal session is in the other mode than the request.permission_denied: The caller isn't a customer signed in to this brand's portal (operators and API keys use the subscription and wallet operations).
Example
Upgrading from Plus to Pro mid-period
Arguments:
{
"subscriptionId": "sub_01m4tx5nj8emwbyf7y9vcf48c0",
"itemId": "si_01m4tx5nj8emwbyf7y9vcf48c1",
"priceId": "price_01m4tx5nj8emwbyf7y9vcf48c3",
"brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz"
}
Result output:
{
"object": "portal_subscription_change_preview",
"subscriptionId": "sub_01m4tx5nj8emwbyf7y9vcf48c0",
"direction": "upgrade",
"effective": "now",
"effectiveAt": "2026-11-05T09:30:00.000Z",
"settlement": "invoice_now",
"invoice": {
"currency": "EUR",
"lines": [
{
"description": "Unused time on Genno Plus (Plus, monthly)",
"quantity": 1,
"amount": {
"amount": "-4500000",
"currency": "EUR"
},
"proration": true,
"periodStart": "2026-11-05T09:30:00.000Z",
"periodEnd": "2026-11-20T10:00:00.000Z"
},
{
"description": "Remaining time on Genno Pro (Pro, monthly)",
"quantity": 1,
"amount": {
"amount": "14500000",
"currency": "EUR"
},
"proration": true,
"periodStart": "2026-11-05T09:30:00.000Z",
"periodEnd": "2026-11-20T10:00:00.000Z"
}
],
"subtotal": {
"amount": "8196721",
"currency": "EUR"
},
"tax": {
"amount": "1803279",
"currency": "EUR"
},
"total": {
"amount": "10000000",
"currency": "EUR"
},
"creditApplied": null,
"amountDue": {
"amount": "10000000",
"currency": "EUR"
}
},
"pendingItems": [],
"credit": null,
"items": [
{
"priceId": "price_01m4tx5nj8emwbyf7y9vcf48c3",
"productId": "prod_01m4tx5nj8emwbyf7y9vcf48c5",
"productName": "Genno Pro",
"priceName": "Pro, monthly",
"interval": {
"interval": "month",
"intervalCount": 1
},
"usageType": "licensed",
"unitAmount": {
"amount": "29000000",
"currency": "EUR"
},
"taxBehavior": "inclusive",
"id": "si_01m4tx5nj8emwbyf7y9vcf48c1",
"quantity": 1,
"amount": {
"amount": "29000000",
"currency": "EUR"
}
}
],
"periodAmount": {
"amount": "29000000",
"currency": "EUR"
},
"version": 4
}
portal_subscriptions_reactivate
Keep your subscription. Operation portal.subscriptions.reactivate · Brand level · permission portal:write · risk tier write · since M9
Withdraws a cancellation the signed-in customer scheduled for the end of the period, before it ends: the
subscription renews as before. A cancellation the brand scheduled can't be withdrawn here. Emits
subscription.updated. Customer portal only (portal:write).
Annotations: readOnlyHint: false · destructiveHint: false · idempotentHint: true · openWorldHint: false
Arguments
| Argument | Type | Required | Description |
|---|---|---|---|
subscriptionId | ID (sub_…) | yes | One of your subscriptions (sub_…). |
expectedVersion | integer | no | The subscription's version you saw (the preview's); if it changed meanwhile, version_conflict. (1–9007199254740991) |
entity_id | ID (ent_…) | no | Optional: the brand's legal entity (ent_…); brand_id alone is enough. |
brand_id | ID (brd_…) | no | The brand to act on (brd_…), inside your API key's reach. Needed with an organization or entity key; a brand key acts on its own brand. |
idempotency_key | string | no | A unique string for this change. Retrying with the same key and arguments never acts twice. When left out, the server generates one and returns it in the result's idempotency_key. (1–255 characters) |
- Brand: pass
brand_idwith an organization or entity key; a brand key acts on its own brand. - Idempotency: the result returns the
idempotency_keyused, yours or one the server generated. Retry with it: a call with the same key and arguments never acts twice (keys are kept for 24 hours). - Dry run: not supported.
dry_run: truefails withdry_run_not_supported.
Errors
validation_failed: The subscription's ID is malformed.resource_not_found: You have no such subscription (another customer's reads the same).portal_action_not_allowed: The brand's portal settings or the subscription's state don't allow reactivating: no cancellation is scheduled, the brand scheduled it, or the period has already ended (details.action).version_conflict: The subscription changed since you saw it (expectedVersion); look again.livemode_not_allowed: The portal session is in the other mode than the request.permission_denied: The caller isn't a customer signed in to this brand's portal (operators and API keys use the subscription and wallet operations).
Example
Changed your mind
Arguments:
{
"subscriptionId": "sub_01m4tx5nj8emwbyf7y9vcf48c0",
"expectedVersion": 5,
"brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz",
"idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}
Result output:
{
"id": "sub_01m4tx5nj8emwbyf7y9vcf48c0",
"object": "portal_subscription",
"status": "active",
"currency": "EUR",
"items": [
{
"priceId": "price_01m4tx5nj8emwbyf7y9vcf48c2",
"productId": "prod_01m4tx5nj8emwbyf7y9vcf48c4",
"productName": "Genno Plus",
"priceName": "Plus, monthly",
"interval": {
"interval": "month",
"intervalCount": 1
},
"usageType": "licensed",
"unitAmount": {
"amount": "9000000",
"currency": "EUR"
},
"taxBehavior": "inclusive",
"id": "si_01m4tx5nj8emwbyf7y9vcf48c1",
"quantity": 1,
"amount": {
"amount": "9000000",
"currency": "EUR"
}
}
],
"collectionMethod": "charge_automatically",
"paymentMethodId": null,
"currentPeriodStart": "2026-10-20T09:00:00.000Z",
"currentPeriodEnd": "2026-11-20T10:00:00.000Z",
"trialEnd": null,
"cancelAtPeriodEnd": false,
"cancelAt": null,
"canceledAt": null,
"endedAt": null,
"cancellation": null,
"discounts": [],
"scheduledChange": null,
"actions": {
"switch_plan": true,
"change_quantity": false,
"apply_promotion_code": true,
"cancel": true,
"reactivate": false,
"change_payment_method": true
},
"cancelBehavior": "period_end",
"collectCancelReason": true,
"version": 6,
"createdAt": "2026-09-20T09:00:00.000Z"
}
portal_subscriptions_set_payment_method
Choose the card a subscription charges. Operation portal.subscriptions.set_payment_method · Brand level · permission portal:write · risk tier write · since M9
Sets which of the signed-in customer's saved cards a card-charged subscription charges (null: the customer's
default card). A past-due invoice of the subscription is retried with it at once. Add cards with
portal.payment_methods.setup. Customer portal only (portal:write).
Annotations: readOnlyHint: false · destructiveHint: false · idempotentHint: true · openWorldHint: true
Arguments
| Argument | Type | Required | Description |
|---|---|---|---|
subscriptionId | ID (sub_…) | yes | One of your subscriptions (sub_…). |
paymentMethodId | ID (pm_…) or null | yes | One of your saved payment methods (pm_…), or null to charge your default one. |
entity_id | ID (ent_…) | no | Optional: the brand's legal entity (ent_…); brand_id alone is enough. |
brand_id | ID (brd_…) | no | The brand to act on (brd_…), inside your API key's reach. Needed with an organization or entity key; a brand key acts on its own brand. |
idempotency_key | string | no | A unique string for this change. Retrying with the same key and arguments never acts twice. When left out, the server generates one and returns it in the result's idempotency_key. (1–255 characters) |
- Brand: pass
brand_idwith an organization or entity key; a brand key acts on its own brand. - Idempotency: the result returns the
idempotency_keyused, yours or one the server generated. Retry with it: a call with the same key and arguments never acts twice (keys are kept for 24 hours). - Dry run: not supported.
dry_run: truefails withdry_run_not_supported.
Errors
validation_failed: An ID is malformed.resource_not_found: You have no such subscription (another customer's reads the same).portal_action_not_allowed: The brand's portal settings or the subscription's state don't allow choosing a card: the subscription is paid by bank transfer or has ended (details.action).subscription_payment_method_invalid: The payment method isn't one of your saved, usable cards.livemode_not_allowed: The portal session is in the other mode than the request.permission_denied: The caller isn't a customer signed in to this brand's portal (operators and API keys use the subscription and wallet operations).
Example
Charge the new card
Arguments:
{
"subscriptionId": "sub_01m4tx5nj8emwbyf7y9vcf48c0",
"paymentMethodId": "pm_01m4tx5nj8emwbyf7y9vcf48cb",
"brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz",
"idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}
Result output:
{
"id": "sub_01m4tx5nj8emwbyf7y9vcf48c0",
"object": "portal_subscription",
"status": "active",
"currency": "EUR",
"items": [
{
"priceId": "price_01m4tx5nj8emwbyf7y9vcf48c2",
"productId": "prod_01m4tx5nj8emwbyf7y9vcf48c4",
"productName": "Genno Plus",
"priceName": "Plus, monthly",
"interval": {
"interval": "month",
"intervalCount": 1
},
"usageType": "licensed",
"unitAmount": {
"amount": "9000000",
"currency": "EUR"
},
"taxBehavior": "inclusive",
"id": "si_01m4tx5nj8emwbyf7y9vcf48c1",
"quantity": 1,
"amount": {
"amount": "9000000",
"currency": "EUR"
}
}
],
"collectionMethod": "charge_automatically",
"paymentMethodId": "pm_01m4tx5nj8emwbyf7y9vcf48cb",
"currentPeriodStart": "2026-10-20T09:00:00.000Z",
"currentPeriodEnd": "2026-11-20T10:00:00.000Z",
"trialEnd": null,
"cancelAtPeriodEnd": false,
"cancelAt": null,
"canceledAt": null,
"endedAt": null,
"cancellation": null,
"discounts": [],
"scheduledChange": null,
"actions": {
"switch_plan": true,
"change_quantity": false,
"apply_promotion_code": true,
"cancel": true,
"reactivate": false,
"change_payment_method": true
},
"cancelBehavior": "period_end",
"collectCancelReason": true,
"version": 5,
"createdAt": "2026-09-20T09:00:00.000Z"
}
portal_subscriptions_update
Switch plan or change the quantity. Operation portal.subscriptions.update · Brand level · permission portal:write · risk tier financial · since M9
Switches one item of the signed-in customer's subscription to another plan, or changes its quantity, as
portal.subscriptions.preview_change showed: an upgrade applies now, its prorated invoice is issued and charged
to the subscription's card (or sent, for a subscription paid by bank transfer); a downgrade is scheduled for the
end of the period (scheduledChange). The brand's portal settings decide what may change, and its billing
settings how a failed payment is handled. Send the preview's version as expectedVersion. Emits
subscription.updated. Customer portal only (portal:write).
Annotations: readOnlyHint: false · destructiveHint: false · idempotentHint: false · openWorldHint: true
Arguments
| Argument | Type | Required | Description |
|---|---|---|---|
subscriptionId | ID (sub_…) | yes | One of your subscriptions (sub_…). |
itemId | ID (si_…) | yes | The subscription item to change (si_…). |
priceId | ID (price_…) | no | Switch the item to this price: one the brand's plan-change matrix allows from its price. |
quantity | integer | no | The item's new quantity (seats), when the brand lets customers change it. (1–1000000) |
expectedVersion | integer | no | The subscription's version you saw (the preview's); if it changed meanwhile, version_conflict. (1–9007199254740991) |
entity_id | ID (ent_…) | no | Optional: the brand's legal entity (ent_…); brand_id alone is enough. |
brand_id | ID (brd_…) | no | The brand to act on (brd_…), inside your API key's reach. Needed with an organization or entity key; a brand key acts on its own brand. |
idempotency_key | string | no | A unique string for this change. Retrying with the same key and arguments never acts twice. When left out, the server generates one and returns it in the result's idempotency_key. (1–255 characters) |
dry_run | boolean | no | true: check permissions and rules and compute the result, then roll everything back. Nothing is saved. (default false) |
- Brand: pass
brand_idwith an organization or entity key; a brand key acts on its own brand. - Idempotency: the result returns the
idempotency_keyused, yours or one the server generated. Retry with it: a call with the same key and arguments never acts twice (keys are kept for 24 hours). - Dry run: supported.
dry_run: truechecks permissions and rules and computes the result, then rolls everything back.
Errors
validation_failed: A field is malformed, or neitherpriceIdnorquantitywas sent.resource_not_found: You have no such subscription (another customer's reads the same).subscription_item_not_found:itemIdisn't one of the subscription's items.portal_action_not_allowed: The brand's portal settings or the subscription's state don't allow this change: plan switches need the brand's plan-change matrix to list the target, quantity changes need the brand's permission and a licensed item (details.action).subscription_change_invalid: The change can't be made now (for example a change already waits for its invoice to be paid).subscription_status_invalid: The subscription can't change in its status.subscription_price_invalid: The new plan's price is archived.jurisdiction_blocked: The brand can't sell in your country now (an invoice due now can't be issued).registration_required: The brand can't invoice your country yet (an invoice due now can't be issued).version_conflict: The subscription changed since you saw it (expectedVersion); look again.livemode_not_allowed: The portal session is in the other mode than the request.permission_denied: The caller isn't a customer signed in to this brand's portal (operators and API keys use the subscription and wallet operations).
Example
Move up to Pro now
Arguments:
{
"subscriptionId": "sub_01m4tx5nj8emwbyf7y9vcf48c0",
"itemId": "si_01m4tx5nj8emwbyf7y9vcf48c1",
"priceId": "price_01m4tx5nj8emwbyf7y9vcf48c3",
"expectedVersion": 4,
"brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz",
"idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}
Result output:
{
"id": "sub_01m4tx5nj8emwbyf7y9vcf48c0",
"object": "portal_subscription",
"status": "active",
"currency": "EUR",
"items": [
{
"priceId": "price_01m4tx5nj8emwbyf7y9vcf48c3",
"productId": "prod_01m4tx5nj8emwbyf7y9vcf48c5",
"productName": "Genno Pro",
"priceName": "Pro, monthly",
"interval": {
"interval": "month",
"intervalCount": 1
},
"usageType": "licensed",
"unitAmount": {
"amount": "29000000",
"currency": "EUR"
},
"taxBehavior": "inclusive",
"id": "si_01m4tx5nj8emwbyf7y9vcf48c1",
"quantity": 1,
"amount": {
"amount": "29000000",
"currency": "EUR"
}
}
],
"collectionMethod": "charge_automatically",
"paymentMethodId": null,
"currentPeriodStart": "2026-10-20T09:00:00.000Z",
"currentPeriodEnd": "2026-11-20T10:00:00.000Z",
"trialEnd": null,
"cancelAtPeriodEnd": false,
"cancelAt": null,
"canceledAt": null,
"endedAt": null,
"cancellation": null,
"discounts": [],
"scheduledChange": null,
"actions": {
"switch_plan": true,
"change_quantity": false,
"apply_promotion_code": true,
"cancel": true,
"reactivate": false,
"change_payment_method": true
},
"cancelBehavior": "period_end",
"collectCancelReason": true,
"version": 5,
"createdAt": "2026-09-20T09:00:00.000Z"
}
portal_tax_details_update
Update your billing details. Operation portal.tax_details.update · Brand level · permission portal:write · risk tier write · since M9
Changes the signed-in customer's name, business name or billing address, as invoices show them. Send only the
fields to change; null removes an optional one, and billingAddress is replaced as a whole. The new address
is used for the next purchases and invoices; past sales stay taxed where they were.
Emits customer.updated to the brand's webhooks. Customer portal only (portal:write).
Annotations: readOnlyHint: false · destructiveHint: false · idempotentHint: true · openWorldHint: false
Arguments
| Argument | Type | Required | Description |
|---|---|---|---|
name | string or null | no | Your name; null removes it. |
businessName | string or null | no | Your business's legal name, for invoices; null removes it. |
billingAddress | object or null | no | Replaces the billing address; null removes it. |
entity_id | ID (ent_…) | no | Optional: the brand's legal entity (ent_…); brand_id alone is enough. |
brand_id | ID (brd_…) | no | The brand to act on (brd_…), inside your API key's reach. Needed with an organization or entity key; a brand key acts on its own brand. |
idempotency_key | string | no | A unique string for this change. Retrying with the same key and arguments never acts twice. When left out, the server generates one and returns it in the result's idempotency_key. (1–255 characters) |
- Brand: pass
brand_idwith an organization or entity key; a brand key acts on its own brand. - Idempotency: the result returns the
idempotency_keyused, yours or one the server generated. Retry with it: a call with the same key and arguments never acts twice (keys are kept for 24 hours). - Dry run: not supported.
dry_run: truefails withdry_run_not_supported.
Errors
validation_failed: Nothing to change, a field is malformed, or a business would be left without a business name or a tax ID.customer_deleted: Your account was deleted by the brand.version_conflict: Your details changed at the same moment; retry.livemode_not_allowed: The portal session is in the other mode than the request.permission_denied: The caller isn't a customer signed in to this brand's portal (operators and API keys use the customer operations).
Example
Set the billing address
Arguments:
{
"billingAddress": {
"line1": "Ring 1",
"city": "Wien",
"postalCode": "1010",
"country": "AT"
},
"brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz",
"idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}
Result output:
{
"object": "portal_account",
"customerId": "cus_01m4tx5nj8emwbyf7y9vcf48b1",
"livemode": false,
"email": "ana@example.com",
"name": "Ana Novak",
"businessName": "Alpen Apps GmbH",
"type": "business",
"locale": null,
"billingAddress": {
"line1": "Ring 1",
"line2": null,
"city": "Wien",
"postalCode": "1010",
"region": null,
"country": "AT"
},
"taxIds": [
{
"id": "ATU00000000",
"type": "eu_vat",
"value": "ATU00000000",
"country": "AT",
"status": "valid",
"checkedAt": "2026-10-20T09:00:00.000Z"
}
],
"portalLastLoginAt": "2026-10-20T09:00:00.000Z"
}
portal_tax_ids_add
Add a tax ID to your account. Operation portal.tax_ids.add · Brand level · permission portal:write · risk tier write · since M9
Adds a tax ID (for example an EU VAT number) to the signed-in customer's account after checking it with its
register (VIES for EU VAT IDs, HMRC for UK VAT numbers, the Swiss UID register; others by format). The answer is
stored with it: a number the register doesn't know is kept as invalid and doesn't make purchases
business-to-business. A malformed number is refused (tax_id_invalid). At most 5 per customer.
Emits customer.updated. Customer portal only (portal:write).
Annotations: readOnlyHint: false · destructiveHint: false · idempotentHint: false · openWorldHint: true
Arguments
| Argument | Type | Required | Description |
|---|---|---|---|
type | string | yes | The tax ID's type: eu_vat, gb_vat, ch_uid/ch_vat, no_org/no_vat or us_ein. Other types are kept but can't be checked (unverified). |
value | string | yes | The number as the customer gave it, with or without the country prefix and separators. (1–50 characters) |
country | string | yes | The country that issued the tax ID (ISO 3166-1 alpha-2; EL = Greece). |
entity_id | ID (ent_…) | no | Optional: the brand's legal entity (ent_…); brand_id alone is enough. |
brand_id | ID (brd_…) | no | The brand to act on (brd_…), inside your API key's reach. Needed with an organization or entity key; a brand key acts on its own brand. |
idempotency_key | string | no | A unique string for this change. Retrying with the same key and arguments never acts twice. When left out, the server generates one and returns it in the result's idempotency_key. (1–255 characters) |
- Brand: pass
brand_idwith an organization or entity key; a brand key acts on its own brand. - Idempotency: the result returns the
idempotency_keyused, yours or one the server generated. Retry with it: a call with the same key and arguments never acts twice (keys are kept for 24 hours). - Dry run: not supported.
dry_run: truefails withdry_run_not_supported.
Errors
validation_failed: A field is missing or malformed.tax_id_invalid: The number isn't valid for its type and country (format or checksum);details.reasonsays which. Nothing is stored.customer_deleted: Your account was deleted by the brand.customer_tax_id_exists: You already have this tax ID.customer_tax_id_limit: You already have 5 tax IDs.version_conflict: Your details changed at the same moment; retry.livemode_not_allowed: The portal session is in the other mode than the request.permission_denied: The caller isn't a customer signed in to this brand's portal (operators and API keys use the customer operations).
Example
Add an Austrian VAT ID
Arguments:
{
"type": "eu_vat",
"value": "ATU 0000 0000",
"country": "AT",
"brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz",
"idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}
Result output:
{
"object": "portal_account",
"customerId": "cus_01m4tx5nj8emwbyf7y9vcf48b1",
"livemode": false,
"email": "ana@example.com",
"name": "Ana Novak",
"businessName": "Alpen Apps GmbH",
"type": "business",
"locale": null,
"billingAddress": {
"line1": "Ring 1",
"line2": null,
"city": "Wien",
"postalCode": "1010",
"region": null,
"country": "AT"
},
"taxIds": [
{
"id": "ATU00000000",
"type": "eu_vat",
"value": "ATU00000000",
"country": "AT",
"status": "valid",
"checkedAt": "2026-10-20T09:00:00.000Z"
}
],
"portalLastLoginAt": "2026-10-20T09:00:00.000Z"
}
portal_tax_ids_delete
Remove a tax ID from your account. Operation portal.tax_ids.delete · Brand level · permission portal:write · risk tier write · since M9
Removes one of the signed-in customer's tax IDs (by its id, the canonical number). Later purchases are taxed
without it; issued invoices don't change. Emits customer.updated. Customer portal only (portal:write).
Annotations: readOnlyHint: false · destructiveHint: true · idempotentHint: false · openWorldHint: false
Arguments
| Argument | Type | Required | Description |
|---|---|---|---|
taxIdId | string | yes | The tax ID's id on the customer: its canonical number, e.g. ATU12345678. |
entity_id | ID (ent_…) | no | Optional: the brand's legal entity (ent_…); brand_id alone is enough. |
brand_id | ID (brd_…) | no | The brand to act on (brd_…), inside your API key's reach. Needed with an organization or entity key; a brand key acts on its own brand. |
idempotency_key | string | no | A unique string for this change. Retrying with the same key and arguments never acts twice. When left out, the server generates one and returns it in the result's idempotency_key. (1–255 characters) |
- Brand: pass
brand_idwith an organization or entity key; a brand key acts on its own brand. - Idempotency: the result returns the
idempotency_keyused, yours or one the server generated. Retry with it: a call with the same key and arguments never acts twice (keys are kept for 24 hours). - Dry run: not supported.
dry_run: truefails withdry_run_not_supported.
Errors
validation_failed: The tax ID's ID or another field is malformed.resource_not_found: You have no such tax ID (another customer's reads the same).customer_deleted: Your account was deleted by the brand.version_conflict: Your details changed at the same moment; retry.livemode_not_allowed: The portal session is in the other mode than the request.permission_denied: The caller isn't a customer signed in to this brand's portal (operators and API keys use the customer operations).
Example
Remove a VAT ID
Arguments:
{
"taxIdId": "ATU00000000",
"brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz",
"idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}
Result output:
{
"object": "portal_account",
"customerId": "cus_01m4tx5nj8emwbyf7y9vcf48b1",
"livemode": false,
"email": "ana@example.com",
"name": "Ana Novak",
"businessName": null,
"type": "individual",
"locale": null,
"billingAddress": {
"line1": "Ring 1",
"line2": null,
"city": "Wien",
"postalCode": "1010",
"region": null,
"country": "AT"
},
"taxIds": [],
"portalLastLoginAt": "2026-10-20T09:00:00.000Z"
}
portal_wallets_auto_reload_update
Set up auto-reload. Operation portal.wallets.auto_reload.update · Brand level · permission portal:write · risk tier write · since M9
Turns automatic reloading of one of the signed-in customer's wallets on or off. On: when the balance falls below
below, amount is bought and charged to the card (paymentMethodId, default: the customer's default card)
without the customer present. The amount must be one the brand sells as credits (a pack of that price, or within
a custom-amount pack's range) and the threshold at most the amount, so one reload restores the balance. Off keeps
the settings for next time. Auto-reload turns itself off after 2 declined reloads. Customer portal only
(portal:write).
Annotations: readOnlyHint: false · destructiveHint: false · idempotentHint: true · openWorldHint: false
Arguments
| Argument | Type | Required | Description |
|---|---|---|---|
walletId | ID (wal_…) | yes | One of your wallets (wal_…). |
enabled | boolean | yes | Turn auto-reload on or off. |
below | object | no | Reload when the balance falls below this (at most the reload amount). |
amount | object | no | How much each reload buys: an amount the brand sells as credits. |
paymentMethodId | ID (pm_…) | no | The card to charge (pm_…); default: your default payment method. |
entity_id | ID (ent_…) | no | Optional: the brand's legal entity (ent_…); brand_id alone is enough. |
brand_id | ID (brd_…) | no | The brand to act on (brd_…), inside your API key's reach. Needed with an organization or entity key; a brand key acts on its own brand. |
idempotency_key | string | no | A unique string for this change. Retrying with the same key and arguments never acts twice. When left out, the server generates one and returns it in the result's idempotency_key. (1–255 characters) |
- Brand: pass
brand_idwith an organization or entity key; a brand key acts on its own brand. - Idempotency: the result returns the
idempotency_keyused, yours or one the server generated. Retry with it: a call with the same key and arguments never acts twice (keys are kept for 24 hours). - Dry run: not supported.
dry_run: truefails withdry_run_not_supported.
Errors
validation_failed: A field is malformed, the threshold or amount is missing to turn it on, the amount isn't one the brand sells as credits, or the threshold is zero or above the amount (details.reason).resource_not_found: You have no such wallet (another customer's reads the same).wallet_currency_mismatch: An amount isn't in the wallet's currency.wallet_payment_method_invalid: You have no saved card to charge, or the card isn't yours or was removed.version_conflict: The wallet changed at the same moment; retry.livemode_not_allowed: The portal session is in the other mode than the request.permission_denied: The caller isn't a customer signed in to this brand's portal (operators and API keys use the subscription and wallet operations).
Example
Reload €25 below €5
Arguments:
{
"walletId": "wal_01m4tx5nj8emwbyf7y9vcf48c7",
"enabled": true,
"below": {
"amount": "5000000",
"currency": "EUR"
},
"amount": {
"amount": "25000000",
"currency": "EUR"
},
"brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz",
"idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}
Result output:
{
"id": "wal_01m4tx5nj8emwbyf7y9vcf48c7",
"object": "portal_wallet",
"currency": "EUR",
"status": "active",
"balance": {
"amount": "32500000",
"currency": "EUR"
},
"available": {
"amount": "25000000",
"currency": "EUR"
},
"promo": {
"amount": "5000000",
"currency": "EUR"
},
"reserved": {
"amount": "2500000",
"currency": "EUR"
},
"upcomingExpiries": [
{
"amount": {
"amount": "5000000",
"currency": "EUR"
},
"expiresAt": "2026-12-31T23:00:00.000Z"
}
],
"autoReload": {
"enabled": true,
"below": {
"amount": "5000000",
"currency": "EUR"
},
"amount": {
"amount": "25000000",
"currency": "EUR"
},
"paymentMethodId": "pm_01m4tx5nj8emwbyf7y9vcf48cb"
},
"createdAt": "2026-09-20T09:00:00.000Z"
}
portal_wallets_list
List your wallets. Operation portal.wallets.list · Brand level · permission portal:read · risk tier read · since M9
Lists the signed-in customer's prepaid credit wallets with this brand, one per currency, newest first: the
balance, what is available, promotional and held for usage in progress, the next credits to expire, and the
auto-reload settings. Amounts are integer micros. Customer portal only (portal:read).
Annotations: readOnlyHint: true · destructiveHint: false · idempotentHint: true · openWorldHint: false
Arguments
| Argument | Type | Required | Description |
|---|---|---|---|
limit | integer | no | Page size, 1 to 100 (default 20). (1–100) |
starting_after | string | no | The next_cursor of the previous page. (1–64 characters) |
entity_id | ID (ent_…) | no | Optional: the brand's legal entity (ent_…); brand_id alone is enough. |
brand_id | ID (brd_…) | no | The brand to act on (brd_…), inside your API key's reach. Needed with an organization or entity key; a brand key acts on its own brand. |
- Brand: pass
brand_idwith an organization or entity key; a brand key acts on its own brand. - Read-only: changes nothing;
idempotency_keyis ignored.
Errors
livemode_not_allowed: The portal session is in the other mode than the request.permission_denied: The caller isn't a customer signed in to this brand's portal (operators and API keys use the subscription and wallet operations).
Example
A euro wallet with promotional credits
Arguments:
{
"brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz"
}
Result output:
{
"object": "list",
"data": [
{
"id": "wal_01m4tx5nj8emwbyf7y9vcf48c7",
"object": "portal_wallet",
"currency": "EUR",
"status": "active",
"balance": {
"amount": "32500000",
"currency": "EUR"
},
"available": {
"amount": "25000000",
"currency": "EUR"
},
"promo": {
"amount": "5000000",
"currency": "EUR"
},
"reserved": {
"amount": "2500000",
"currency": "EUR"
},
"upcomingExpiries": [
{
"amount": {
"amount": "5000000",
"currency": "EUR"
},
"expiresAt": "2026-12-31T23:00:00.000Z"
}
],
"autoReload": null,
"createdAt": "2026-09-20T09:00:00.000Z"
}
],
"has_more": false,
"next_cursor": null
}
portal_wallets_top_up
Top up your credits. Operation portal.wallets.top_up · Brand level · permission portal:write · risk tier write · since M9
Starts a top-up of the signed-in customer's credits with one of the brand's credit packs: a hosted checkout in
topup mode for the customer (a custom-amount pack takes amount). The customer pays there; tax is quoted as
for any top-up and the brand's risk rules apply. The wallet in the pack's currency is credited when the payment
is confirmed (and created then, for a first top-up); the checkout returns to the portal's wallets page.
Customer portal only (portal:write).
Annotations: readOnlyHint: false · destructiveHint: false · idempotentHint: false · openWorldHint: false
Arguments
| Argument | Type | Required | Description |
|---|---|---|---|
priceId | ID (price_…) | yes | One of the brand's credit packs (portal.credit_packs.list). |
amount | object | no | A custom-amount pack: how much to top up, within the pack's minimum and maximum. |
entity_id | ID (ent_…) | no | Optional: the brand's legal entity (ent_…); brand_id alone is enough. |
brand_id | ID (brd_…) | no | The brand to act on (brd_…), inside your API key's reach. Needed with an organization or entity key; a brand key acts on its own brand. |
idempotency_key | string | no | A unique string for this change. Retrying with the same key and arguments never acts twice. When left out, the server generates one and returns it in the result's idempotency_key. (1–255 characters) |
- Brand: pass
brand_idwith an organization or entity key; a brand key acts on its own brand. - Idempotency: the result returns the
idempotency_keyused, yours or one the server generated. Retry with it: a call with the same key and arguments never acts twice (keys are kept for 24 hours). - Dry run: not supported.
dry_run: truefails withdry_run_not_supported.
Errors
validation_failed: A field is malformed.resource_not_found: The brand sells no such credit pack (or it was archived).checkout_amount_invalid: A custom amount is missing, outside the pack's minimum and maximum, or given for a fixed pack.checkout_currency_mismatch: The custom amount isn't in the pack's currency.customer_deleted: Your account was deleted by the brand.livemode_not_allowed: The portal session is in the other mode than the request.permission_denied: The caller isn't a customer signed in to this brand's portal (operators and API keys use the subscription and wallet operations).
Example
Buy the €25 pack
Arguments:
{
"priceId": "price_01m4tx5nj8emwbyf7y9vcf48c9",
"brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz",
"idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}
Result output:
{
"object": "portal_top_up",
"url": "https://ownrecord.io/c/genno/cs_01m4tx5nj8emwbyf7y9vcf48cc?key=ExampleOnlyNotARealSessionKey000",
"expiresAt": "2026-11-06T09:30:00.000Z"
}
portal_wallets_transactions_list
List your wallet's history. Operation portal.wallets.transactions.list · Brand level · permission portal:read · risk tier read · since M9
Lists what moved one of the signed-in customer's wallets, newest first, from the ledger: top-ups, included and
promotional credits, usage, holds and their release, expiries and refunds. Each entry has its category and the
signed change of the balance and of each part. Operators' notes and internal references aren't shown.
Paginate with limit and starting_after. Customer portal only (portal:read).
Annotations: readOnlyHint: true · destructiveHint: false · idempotentHint: true · openWorldHint: false
Arguments
| Argument | Type | Required | Description |
|---|---|---|---|
limit | integer | no | Page size, 1 to 100 (default 20). (1–100) |
starting_after | string | no | The next_cursor of the previous page. (1–64 characters) |
walletId | ID (wal_…) | yes | One of your wallets (wal_…). |
entity_id | ID (ent_…) | no | Optional: the brand's legal entity (ent_…); brand_id alone is enough. |
brand_id | ID (brd_…) | no | The brand to act on (brd_…), inside your API key's reach. Needed with an organization or entity key; a brand key acts on its own brand. |
- Brand: pass
brand_idwith an organization or entity key; a brand key acts on its own brand. - Read-only: changes nothing;
idempotency_keyis ignored.
Errors
validation_failed: The wallet's ID or a pagination value is malformed.resource_not_found: You have no such wallet (another customer's reads the same).livemode_not_allowed: The portal session is in the other mode than the request.permission_denied: The caller isn't a customer signed in to this brand's portal (operators and API keys use the subscription and wallet operations).
Example
A top-up
Arguments:
{
"walletId": "wal_01m4tx5nj8emwbyf7y9vcf48c7",
"brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz"
}
Result output:
{
"object": "list",
"data": [
{
"id": "txn_01m4tx5nj8emwbyf7y9vcf48c8",
"object": "portal_wallet_entry",
"walletId": "wal_01m4tx5nj8emwbyf7y9vcf48c7",
"type": "top_up",
"balance": {
"amount": "25000000",
"currency": "EUR"
},
"available": {
"amount": "25000000",
"currency": "EUR"
},
"promo": {
"amount": "0",
"currency": "EUR"
},
"reserved": {
"amount": "0",
"currency": "EUR"
},
"effectiveAt": "2026-11-05T09:30:00.000Z"
}
],
"has_more": false,
"next_cursor": null
}