---
title: "Billing Portal"
method: POST
path: "/api/workspaces/{slug}/billing/portal"
tags: ["Workspace Billing"]
---

# Billing Portal

`POST /api/workspaces/{slug}/billing/portal`

## Request

- Base URL: `https://api.kolens.ai` (the document declares no server; this is the origin it was published from)
- URL: `https://api.kolens.ai/api/workspaces/{slug}/billing/portal`
- Auth: HTTP bearer

Confirm the caller can manage payment methods + return the
workspace's Stripe customer id for the Vercel portal-session call.

Role gate: ``admin`` or ``billing`` only. Plain members → 403.

Workspaces that have never run a checkout don't have a
``stripe_customer_id`` yet. We still return 200 with the field set
to null — the Vercel route surfaces a friendly "run a top-up first"
error instead of opening the portal.

## Path parameters

- `slug` string, required

## Headers

- `x-kolens-user-id` string, nullable

## Response `200`

Successful Response

- PortalCustomerOut: Role-gated handshake the Vercel BFF uses to create a Stripe Customer Portal session. The Railway layer does NOT call Stripe itself — the Stripe SDK lives only on Vercel (alongside the existing checkout + webhook flows). Railway's job here is purely: 1. Verify the caller's role (admin / billing — members are 403'd). 2. Return the workspace's persisted ``stripe_customer_id`` so the Vercel route can pass it into ``stripe.billingPortal.sessions .create``. If no customer has been minted yet (workspace never ran a checkout), ``stripe_customer_id`` is null. The Vercel route surfaces a clean "Run a top-up first" error instead of opening the portal.
  - `workspace_id` string, required
  - `slug` string, required
  - `stripe_customer_id` string, nullable

## Other responses

- `422`: Validation Error

---

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