Skip to content

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 account
  • portal_brand_get: Get a brand's customer portal
  • portal_credit_balance_get: Get your credit balance
  • portal_credit_notes_list: List your credit notes
  • portal_credit_packs_list: List the credit packs you can buy
  • portal_export_get: Export your data
  • portal_invoices_get: Get one of your invoices
  • portal_invoices_list: List your invoices
  • portal_invoices_pay: Pay one of your invoices by card
  • portal_login_links_request: Email a customer a portal sign-in link
  • portal_payment_methods_delete: Remove a payment method
  • portal_payment_methods_list: List your payment methods
  • portal_payment_methods_set_default: Make a payment method your default
  • portal_payment_methods_setup: Add a payment method
  • portal_payment_methods_use_for_subscriptions: Charge your default card for all your subscriptions
  • portal_sessions_create: Generate a customer portal link
  • portal_sessions_redeem: Sign in to the customer portal with a link
  • portal_sessions_revoke: Sign out of the customer portal
  • portal_subscriptions_apply_promotion_code: Apply a promotion code to your subscription
  • portal_subscriptions_cancel: Cancel your subscription
  • portal_subscriptions_get: Get one of your subscriptions
  • portal_subscriptions_list: List your subscriptions
  • portal_subscriptions_preview_change: Preview a plan or quantity change
  • portal_subscriptions_reactivate: Keep your subscription
  • portal_subscriptions_set_payment_method: Choose the card a subscription charges
  • portal_subscriptions_update: Switch plan or change the quantity
  • portal_tax_details_update: Update your billing details
  • portal_tax_ids_add: Add a tax ID to your account
  • portal_tax_ids_delete: Remove a tax ID from your account
  • portal_wallets_auto_reload_update: Set up auto-reload
  • portal_wallets_list: List your wallets and balances
  • portal_wallets_top_up: Top up your credits
  • portal_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

ArgumentTypeRequiredDescription
entity_idID (ent_…)noOptional: the brand's legal entity (ent_…); brand_id alone is enough.
brand_idID (brd_…)noThe 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_id with an organization or entity key; a brand key acts on its own brand.
  • Read-only: changes nothing; idempotency_key is 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:

JSON
{
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz"
}

Result output:

JSON
{
  "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

ArgumentTypeRequiredDescription
brandSlugstringyesThe brand's slug, as in /portal/{brandSlug}. (1–64 characters)
entity_idID (ent_…)noOptional: the brand's legal entity (ent_…); brand_id alone is enough.
brand_idID (brd_…)noThe 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_id with an organization or entity key; a brand key acts on its own brand.
  • Read-only: changes nothing; idempotency_key is ignored.

Errors

  • validation_failed: brandSlug isn'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; wait retry_after_ms.

Example

Genno's portal (test-only brand)

Arguments:

JSON
{
  "brandSlug": "genno",
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz"
}

Result output:

JSON
{
  "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

ArgumentTypeRequiredDescription
entity_idID (ent_…)noOptional: the brand's legal entity (ent_…); brand_id alone is enough.
brand_idID (brd_…)noThe 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_id with an organization or entity key; a brand key acts on its own brand.
  • Read-only: changes nothing; idempotency_key is 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:

JSON
{
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz"
}

Result output:

JSON
{
  "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

ArgumentTypeRequiredDescription
limitintegernoPage size, 1 to 100 (default 20). (1–100)
starting_afterstringnoThe next_cursor of the previous page. (1–64 characters)
entity_idID (ent_…)noOptional: the brand's legal entity (ent_…); brand_id alone is enough.
brand_idID (brd_…)noThe 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_id with an organization or entity key; a brand key acts on its own brand.
  • Read-only: changes nothing; idempotency_key is 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:

JSON
{
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz"
}

Result output:

JSON
{
  "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

ArgumentTypeRequiredDescription
limitintegernoPage size, 1 to 100 (default 20). (1–100)
starting_afterstringnoThe next_cursor of the previous page. (1–64 characters)
entity_idID (ent_…)noOptional: the brand's legal entity (ent_…); brand_id alone is enough.
brand_idID (brd_…)noThe 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_id with an organization or entity key; a brand key acts on its own brand.
  • Read-only: changes nothing; idempotency_key is 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:

JSON
{
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz"
}

Result output:

JSON
{
  "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

ArgumentTypeRequiredDescription
formatone of json, csvnojson (default) or csv. (default "json")
entity_idID (ent_…)noOptional: the brand's legal entity (ent_…); brand_id alone is enough.
brand_idID (brd_…)noThe 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_id with an organization or entity key; a brand key acts on its own brand.
  • Read-only: changes nothing; idempotency_key is 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:

JSON
{
  "format": "csv",
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz"
}

Result output:

JSON
{
  "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

ArgumentTypeRequiredDescription
invoiceIdID (inv_…)yesOne of your invoices (inv_…).
entity_idID (ent_…)noOptional: the brand's legal entity (ent_…); brand_id alone is enough.
brand_idID (brd_…)noThe 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_id with an organization or entity key; a brand key acts on its own brand.
  • Read-only: changes nothing; idempotency_key is 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:

JSON
{
  "invoiceId": "inv_01m4tx5nj8emwbyf7y9vcf48b3",
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz"
}

Result output:

JSON
{
  "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

ArgumentTypeRequiredDescription
limitintegernoPage size, 1 to 100 (default 20). (1–100)
starting_afterstringnoThe next_cursor of the previous page. (1–64 characters)
entity_idID (ent_…)noOptional: the brand's legal entity (ent_…); brand_id alone is enough.
brand_idID (brd_…)noThe 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_id with an organization or entity key; a brand key acts on its own brand.
  • Read-only: changes nothing; idempotency_key is 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:

JSON
{
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz"
}

Result output:

JSON
{
  "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_confirm with the Payment Element's clientSecret; the browser confirms;
  • Mollie: the answer is redirect with redirectUrl, 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

ArgumentTypeRequiredDescription
invoiceIdID (inv_…)yesOne of your invoices (inv_…).
expectedAmountobjectyesThe amount due you saw; if it changed meanwhile, the payment is refused (invoice_payment_unavailable).
paymentMethodstringnoFake processor only (development, CI): the test card, e.g. pm_fake_visa. (1–255 characters)
entity_idID (ent_…)noOptional: the brand's legal entity (ent_…); brand_id alone is enough.
brand_idID (brd_…)noThe 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_keystringnoA 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_runbooleannotrue: check permissions and rules and compute the result, then roll everything back. Nothing is saved. (default false)
  • Brand: pass brand_id with an organization or entity key; a brand key acts on its own brand.
  • Idempotency: the result returns the idempotency_key used, 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: true checks permissions and rules and computes the result, then rolls everything back.

Errors

Example

Pay with a fake test card

Arguments:

JSON
{
  "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:

JSON
{
  "object": "hosted_invoice_payment",
  "invoiceId": "inv_01m4tx5nj8emwbyf7y9vcf48b3",
  "outcome": "succeeded",
  "amount": {
    "amount": "1200000000",
    "currency": "EUR"
  },
  "clientSecret": null,
  "publishableKey": null,
  "redirectUrl": null
}

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

ArgumentTypeRequiredDescription
brandSlugstringyesThe brand's slug, as in /portal/{brandSlug}. (1–64 characters)
emailstringyesThe address the customer uses with the brand. (at most 254 characters)
entity_idID (ent_…)noOptional: the brand's legal entity (ent_…); brand_id alone is enough.
brand_idID (brd_…)noThe 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_keystringnoA 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_id with an organization or entity key; a brand key acts on its own brand.
  • Idempotency: the result returns the idempotency_key used, 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: true fails with dry_run_not_supported.

Errors

  • validation_failed: email isn't an email address, or brandSlug isn'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); wait retry_after_ms.

Example

Ask for a sign-in link

Arguments:

JSON
{
  "brandSlug": "genno",
  "email": "ana@example.com",
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz",
  "idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}

Result output:

JSON
{
  "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

ArgumentTypeRequiredDescription
paymentMethodIdID (pm_…)yesOne of your saved payment methods (pm_…).
entity_idID (ent_…)noOptional: the brand's legal entity (ent_…); brand_id alone is enough.
brand_idID (brd_…)noThe 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_keystringnoA 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_id with an organization or entity key; a brand key acts on its own brand.
  • Idempotency: the result returns the idempotency_key used, 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: true fails with dry_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:

JSON
{
  "paymentMethodId": "pm_01m4tx5nj8emwbyf7y9vcf48b6",
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz",
  "idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}

Result output:

JSON
{
  "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

ArgumentTypeRequiredDescription
limitintegernoPage size, 1 to 100 (default 20). (1–100)
starting_afterstringnoThe next_cursor of the previous page. (1–64 characters)
entity_idID (ent_…)noOptional: the brand's legal entity (ent_…); brand_id alone is enough.
brand_idID (brd_…)noThe 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_id with an organization or entity key; a brand key acts on its own brand.
  • Read-only: changes nothing; idempotency_key is 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:

JSON
{
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz"
}

Result output:

JSON
{
  "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

ArgumentTypeRequiredDescription
paymentMethodIdID (pm_…)yesOne of your saved payment methods (pm_…).
entity_idID (ent_…)noOptional: the brand's legal entity (ent_…); brand_id alone is enough.
brand_idID (brd_…)noThe 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_keystringnoA 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_id with an organization or entity key; a brand key acts on its own brand.
  • Idempotency: the result returns the idempotency_key used, 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: true fails with dry_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:

JSON
{
  "paymentMethodId": "pm_01m4tx5nj8emwbyf7y9vcf48b6",
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz",
  "idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}

Result output:

JSON
{
  "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

ArgumentTypeRequiredDescription
entity_idID (ent_…)noOptional: the brand's legal entity (ent_…); brand_id alone is enough.
brand_idID (brd_…)noThe 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_keystringnoA 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_id with an organization or entity key; a brand key acts on its own brand.
  • Idempotency: the result returns the idempotency_key used, 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: true fails with dry_run_not_supported.

Errors

Example

Add a card

Arguments:

JSON
{
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz",
  "idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}

Result output:

JSON
{
  "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

ArgumentTypeRequiredDescription
paymentMethodIdID (pm_…)yesYour default card (pm_…), as you saw it.
entity_idID (ent_…)noOptional: the brand's legal entity (ent_…); brand_id alone is enough.
brand_idID (brd_…)noThe 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_keystringnoA 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_id with an organization or entity key; a brand key acts on its own brand.
  • Idempotency: the result returns the idempotency_key used, 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: true fails with dry_run_not_supported.

Errors

Example

Use the card you just added for your subscriptions

Arguments:

JSON
{
  "paymentMethodId": "pm_01m4tx5nj8emwbyf7y9vcf48cb",
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz",
  "idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}

Result output:

JSON
{
  "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

ArgumentTypeRequiredDescription
customerIdID (cus_…)yesThe customer the link signs in (cus_…).
entity_idID (ent_…)noOptional: the brand's legal entity (ent_…); brand_id alone is enough.
brand_idID (brd_…)noThe 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_keystringnoA 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_id with an organization or entity key; a brand key acts on its own brand.
  • Idempotency: the result returns the idempotency_key used, 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: true fails with dry_run_not_supported.

Errors

Example

A sign-in link for a customer

Arguments:

JSON
{
  "customerId": "cus_01m4tx5nj8emwbyf7y9vcf48b1",
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz",
  "idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}

Result output:

JSON
{
  "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

ArgumentTypeRequiredDescription
portalSessionIdID (psess_…)yesThe portal session's ID (psess_…).
tokenstringyesThe token of the sign-in link.
entity_idID (ent_…)noOptional: the brand's legal entity (ent_…); brand_id alone is enough.
brand_idID (brd_…)noThe 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_keystringnoA 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_id with an organization or entity key; a brand key acts on its own brand.
  • Idempotency: the result returns the idempotency_key used, 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: true fails with dry_run_not_supported.

Errors

Example

Redeem the link from the email

Arguments:

JSON
{
  "portalSessionId": "psess_01m4tx5nj8emwbyf7y9vcf48b2",
  "token": "Q2hhbmdlTWVJbkV4YW1wbGVzT25seS4uLi4uLi4uLi4",
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz",
  "idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}

Result output:

JSON
{
  "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

ArgumentTypeRequiredDescription
entity_idID (ent_…)noOptional: the brand's legal entity (ent_…); brand_id alone is enough.
brand_idID (brd_…)noThe 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_keystringnoA 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_id with an organization or entity key; a brand key acts on its own brand.
  • Idempotency: the result returns the idempotency_key used, 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: true fails with dry_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:

JSON
{
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz",
  "idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}

Result output:

JSON
{
  "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

ArgumentTypeRequiredDescription
subscriptionIdID (sub_…)yesOne of your subscriptions (sub_…).
promotionCodestringyesA promotion code as the customer typed it (case doesn't matter). (1–50 characters)
entity_idID (ent_…)noOptional: the brand's legal entity (ent_…); brand_id alone is enough.
brand_idID (brd_…)noThe 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_keystringnoA 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_runbooleannotrue: check permissions and rules and compute the result, then roll everything back. Nothing is saved. (default false)
  • Brand: pass brand_id with an organization or entity key; a brand key acts on its own brand.
  • Idempotency: the result returns the idempotency_key used, 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: true checks 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:

JSON
{
  "subscriptionId": "sub_01m4tx5nj8emwbyf7y9vcf48c0",
  "promotionCode": "AUTUMN20",
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz",
  "idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}

Result output:

JSON
{
  "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

ArgumentTypeRequiredDescription
subscriptionIdID (sub_…)yesOne of your subscriptions (sub_…).
expectedVersionintegernoThe subscription's version you saw (the preview's); if it changed meanwhile, version_conflict. (1–9007199254740991)
feedbackone of too_expensive, missing_features, switched_service, unused, customer_service, too_complex, low_quality, othernoWhy you're leaving, when the brand asks (collectCancelReason).
commentstringnoAnything you'd like to tell the brand, when it asks. (1–500 characters)
entity_idID (ent_…)noOptional: the brand's legal entity (ent_…); brand_id alone is enough.
brand_idID (brd_…)noThe 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_keystringnoA 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_runbooleannotrue: check permissions and rules and compute the result, then roll everything back. Nothing is saved. (default false)
  • Brand: pass brand_id with an organization or entity key; a brand key acts on its own brand.
  • Idempotency: the result returns the idempotency_key used, 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: true checks 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:

JSON
{
  "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:

JSON
{
  "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

ArgumentTypeRequiredDescription
subscriptionIdID (sub_…)yesOne of your subscriptions (sub_…).
entity_idID (ent_…)noOptional: the brand's legal entity (ent_…); brand_id alone is enough.
brand_idID (brd_…)noThe 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_id with an organization or entity key; a brand key acts on its own brand.
  • Read-only: changes nothing; idempotency_key is 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:

JSON
{
  "subscriptionId": "sub_01m4tx5nj8emwbyf7y9vcf48c0",
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz"
}

Result output:

JSON
{
  "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

ArgumentTypeRequiredDescription
limitintegernoPage size, 1 to 100 (default 20). (1–100)
starting_afterstringnoThe next_cursor of the previous page. (1–64 characters)
entity_idID (ent_…)noOptional: the brand's legal entity (ent_…); brand_id alone is enough.
brand_idID (brd_…)noThe 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_id with an organization or entity key; a brand key acts on its own brand.
  • Read-only: changes nothing; idempotency_key is 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:

JSON
{
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz"
}

Result output:

JSON
{
  "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

ArgumentTypeRequiredDescription
subscriptionIdID (sub_…)yesOne of your subscriptions (sub_…).
itemIdID (si_…)yesThe subscription item to change (si_…).
priceIdID (price_…)noSwitch the item to this price: one the brand's plan-change matrix allows from its price.
quantityintegernoThe item's new quantity (seats), when the brand lets customers change it. (1–1000000)
entity_idID (ent_…)noOptional: the brand's legal entity (ent_…); brand_id alone is enough.
brand_idID (brd_…)noThe 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_id with an organization or entity key; a brand key acts on its own brand.
  • Read-only: changes nothing; idempotency_key is ignored.

Errors

Example

Upgrading from Plus to Pro mid-period

Arguments:

JSON
{
  "subscriptionId": "sub_01m4tx5nj8emwbyf7y9vcf48c0",
  "itemId": "si_01m4tx5nj8emwbyf7y9vcf48c1",
  "priceId": "price_01m4tx5nj8emwbyf7y9vcf48c3",
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz"
}

Result output:

JSON
{
  "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

ArgumentTypeRequiredDescription
subscriptionIdID (sub_…)yesOne of your subscriptions (sub_…).
expectedVersionintegernoThe subscription's version you saw (the preview's); if it changed meanwhile, version_conflict. (1–9007199254740991)
entity_idID (ent_…)noOptional: the brand's legal entity (ent_…); brand_id alone is enough.
brand_idID (brd_…)noThe 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_keystringnoA 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_id with an organization or entity key; a brand key acts on its own brand.
  • Idempotency: the result returns the idempotency_key used, 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: true fails with dry_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:

JSON
{
  "subscriptionId": "sub_01m4tx5nj8emwbyf7y9vcf48c0",
  "expectedVersion": 5,
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz",
  "idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}

Result output:

JSON
{
  "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

ArgumentTypeRequiredDescription
subscriptionIdID (sub_…)yesOne of your subscriptions (sub_…).
paymentMethodIdID (pm_…) or nullyesOne of your saved payment methods (pm_…), or null to charge your default one.
entity_idID (ent_…)noOptional: the brand's legal entity (ent_…); brand_id alone is enough.
brand_idID (brd_…)noThe 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_keystringnoA 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_id with an organization or entity key; a brand key acts on its own brand.
  • Idempotency: the result returns the idempotency_key used, 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: true fails with dry_run_not_supported.

Errors

Example

Charge the new card

Arguments:

JSON
{
  "subscriptionId": "sub_01m4tx5nj8emwbyf7y9vcf48c0",
  "paymentMethodId": "pm_01m4tx5nj8emwbyf7y9vcf48cb",
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz",
  "idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}

Result output:

JSON
{
  "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

ArgumentTypeRequiredDescription
subscriptionIdID (sub_…)yesOne of your subscriptions (sub_…).
itemIdID (si_…)yesThe subscription item to change (si_…).
priceIdID (price_…)noSwitch the item to this price: one the brand's plan-change matrix allows from its price.
quantityintegernoThe item's new quantity (seats), when the brand lets customers change it. (1–1000000)
expectedVersionintegernoThe subscription's version you saw (the preview's); if it changed meanwhile, version_conflict. (1–9007199254740991)
entity_idID (ent_…)noOptional: the brand's legal entity (ent_…); brand_id alone is enough.
brand_idID (brd_…)noThe 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_keystringnoA 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_runbooleannotrue: check permissions and rules and compute the result, then roll everything back. Nothing is saved. (default false)
  • Brand: pass brand_id with an organization or entity key; a brand key acts on its own brand.
  • Idempotency: the result returns the idempotency_key used, 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: true checks permissions and rules and computes the result, then rolls everything back.

Errors

Example

Move up to Pro now

Arguments:

JSON
{
  "subscriptionId": "sub_01m4tx5nj8emwbyf7y9vcf48c0",
  "itemId": "si_01m4tx5nj8emwbyf7y9vcf48c1",
  "priceId": "price_01m4tx5nj8emwbyf7y9vcf48c3",
  "expectedVersion": 4,
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz",
  "idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}

Result output:

JSON
{
  "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

ArgumentTypeRequiredDescription
namestring or nullnoYour name; null removes it.
businessNamestring or nullnoYour business's legal name, for invoices; null removes it.
billingAddressobject or nullnoReplaces the billing address; null removes it.
entity_idID (ent_…)noOptional: the brand's legal entity (ent_…); brand_id alone is enough.
brand_idID (brd_…)noThe 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_keystringnoA 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_id with an organization or entity key; a brand key acts on its own brand.
  • Idempotency: the result returns the idempotency_key used, 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: true fails with dry_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:

JSON
{
  "billingAddress": {
    "line1": "Ring 1",
    "city": "Wien",
    "postalCode": "1010",
    "country": "AT"
  },
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz",
  "idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}

Result output:

JSON
{
  "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

ArgumentTypeRequiredDescription
typestringyesThe 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).
valuestringyesThe number as the customer gave it, with or without the country prefix and separators. (1–50 characters)
countrystringyesThe country that issued the tax ID (ISO 3166-1 alpha-2; EL = Greece).
entity_idID (ent_…)noOptional: the brand's legal entity (ent_…); brand_id alone is enough.
brand_idID (brd_…)noThe 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_keystringnoA 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_id with an organization or entity key; a brand key acts on its own brand.
  • Idempotency: the result returns the idempotency_key used, 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: true fails with dry_run_not_supported.

Errors

Example

Add an Austrian VAT ID

Arguments:

JSON
{
  "type": "eu_vat",
  "value": "ATU 0000 0000",
  "country": "AT",
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz",
  "idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}

Result output:

JSON
{
  "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

ArgumentTypeRequiredDescription
taxIdIdstringyesThe tax ID's id on the customer: its canonical number, e.g. ATU12345678.
entity_idID (ent_…)noOptional: the brand's legal entity (ent_…); brand_id alone is enough.
brand_idID (brd_…)noThe 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_keystringnoA 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_id with an organization or entity key; a brand key acts on its own brand.
  • Idempotency: the result returns the idempotency_key used, 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: true fails with dry_run_not_supported.

Errors

Example

Remove a VAT ID

Arguments:

JSON
{
  "taxIdId": "ATU00000000",
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz",
  "idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}

Result output:

JSON
{
  "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

ArgumentTypeRequiredDescription
walletIdID (wal_…)yesOne of your wallets (wal_…).
enabledbooleanyesTurn auto-reload on or off.
belowobjectnoReload when the balance falls below this (at most the reload amount).
amountobjectnoHow much each reload buys: an amount the brand sells as credits.
paymentMethodIdID (pm_…)noThe card to charge (pm_…); default: your default payment method.
entity_idID (ent_…)noOptional: the brand's legal entity (ent_…); brand_id alone is enough.
brand_idID (brd_…)noThe 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_keystringnoA 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_id with an organization or entity key; a brand key acts on its own brand.
  • Idempotency: the result returns the idempotency_key used, 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: true fails with dry_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:

JSON
{
  "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:

JSON
{
  "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

ArgumentTypeRequiredDescription
limitintegernoPage size, 1 to 100 (default 20). (1–100)
starting_afterstringnoThe next_cursor of the previous page. (1–64 characters)
entity_idID (ent_…)noOptional: the brand's legal entity (ent_…); brand_id alone is enough.
brand_idID (brd_…)noThe 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_id with an organization or entity key; a brand key acts on its own brand.
  • Read-only: changes nothing; idempotency_key is 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:

JSON
{
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz"
}

Result output:

JSON
{
  "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

ArgumentTypeRequiredDescription
priceIdID (price_…)yesOne of the brand's credit packs (portal.credit_packs.list).
amountobjectnoA custom-amount pack: how much to top up, within the pack's minimum and maximum.
entity_idID (ent_…)noOptional: the brand's legal entity (ent_…); brand_id alone is enough.
brand_idID (brd_…)noThe 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_keystringnoA 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_id with an organization or entity key; a brand key acts on its own brand.
  • Idempotency: the result returns the idempotency_key used, 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: true fails with dry_run_not_supported.

Errors

Example

Buy the €25 pack

Arguments:

JSON
{
  "priceId": "price_01m4tx5nj8emwbyf7y9vcf48c9",
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz",
  "idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}

Result output:

JSON
{
  "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

ArgumentTypeRequiredDescription
limitintegernoPage size, 1 to 100 (default 20). (1–100)
starting_afterstringnoThe next_cursor of the previous page. (1–64 characters)
walletIdID (wal_…)yesOne of your wallets (wal_…).
entity_idID (ent_…)noOptional: the brand's legal entity (ent_…); brand_id alone is enough.
brand_idID (brd_…)noThe 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_id with an organization or entity key; a brand key acts on its own brand.
  • Read-only: changes nothing; idempotency_key is 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:

JSON
{
  "walletId": "wal_01m4tx5nj8emwbyf7y9vcf48c7",
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz"
}

Result output:

JSON
{
  "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
}