Skip to main content

Portal Sessions

What is a portal session?

A portal session is a one-time link you send to your customer so they can see their own bill, invoices, subscription, and usage history on a MarginFront-hosted page. Think of it like a Google Doc share link, but for billing. You generate the link on your backend with your secret API key. Your customer clicks it once. They see only their own data. The link works for one hour, then expires.

Why use it?

  • Let your customers see their invoices and pay (or follow up) without bothering your support team.
  • Show them their usage and what they’re being billed for, so there are no surprises.
  • Keep your API key safe. Your customer never sees it.

How it works

  1. You call POST /v1/portal-sessions from your backend.
  2. MarginFront returns a URL like https://portal.marginfront.com/r/mgl_abc123....
  3. You send that URL to your customer (email, SMS, “View my billing” button in your app, whatever fits).
  4. They click it. The link is used up.
  5. They see a MarginFront page branded with your logo and colors showing their invoices, subscription, usage, and profile.
  6. After one hour, they’re logged out.
  7. If anyone tries the same link a second time, they get a “link already used” error.

The endpoints

All four endpoints require a secret API key (mf_sk_...). Publishable keys (mf_pk_...) are rejected with 401 Unauthorized. Always call these from your backend, never from the browser.

Create a portal session

POST /v1/portal-sessions
Request body:
{
  "customerExternalId": "acme-001",
  "returnUrl": "https://your-app.com/billing"
}
FieldTypeRequiredNotes
customerExternalIdstringOne of these twoYour system’s customer ID (the same one you use everywhere else with MarginFront).
customerIdstringOne of these twoMarginFront’s internal customer UUID. Use this if you already have it.
returnUrlstringNoStored on the session for your records. The portal does NOT auto-redirect; this is informational.
featuresstring[]NoWhich sections to enable. Valid values: invoices, subscriptions, usage, profile. The v1 portal shows all four sections regardless of what you send, so this field is informational only.
Response (201 Created):
{
  "id": "ps_a1b2c3d4...",
  "object": "portal_session",
  "url": "https://portal.marginfront.com/r/mgl_a1b2c3d4...",
  "token": "mgl_a1b2c3d4...",
  "customerId": "550e8400-e29b-41d4-a716-446655440000",
  "customerName": "Acme Corp",
  "customerEmail": "[email protected]",
  "expiresAt": "2026-05-13T17:30:00.000Z",
  "createdAt": "2026-05-13T16:30:00.000Z"
}
Give the url to your customer. Save the id if you want to look up or revoke the session later. That’s the only field you’ll need. curl example:
curl -X POST https://api.marginfront.com/v1/portal-sessions \
  -H "x-api-key: mf_sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "customerExternalId": "acme-001",
    "returnUrl": "https://your-app.com/billing"
  }'
Common errors:
  • 400 Bad Request: neither customerId nor customerExternalId was provided.
  • 401 Unauthorized: missing API key, or you sent a publishable (mf_pk_*) key.
  • 404 Not Found: no customer in your org matches the ID you sent.

List portal sessions

GET /v1/portal-sessions
Lists portal sessions your organization has created. Useful for audit or support. For example: “did Acme already open the link I sent yesterday?” Query parameters (all optional):
ParamTypeDefaultNotes
customerIdstringnoneShow only sessions for one customer (internal UUID).
limitnumber10Max 100.
includeExpiredbooleanfalseSet to true to also see expired or already-used sessions.
The token and url fields are NOT returned by this endpoint. Those are only returned at creation. If you lost the URL, mint a new session. Response (200 OK):
[
  {
    "id": "ps_a1b2c3d4...",
    "object": "portal_session",
    "customerId": "550e8400-e29b-41d4-a716-446655440000",
    "customerName": "Acme Corp",
    "customerEmail": "[email protected]",
    "expiresAt": "2026-05-13T17:30:00.000Z",
    "isExpired": false,
    "isUsed": false,
    "createdAt": "2026-05-13T16:30:00.000Z",
    "features": ["invoices", "subscriptions", "usage", "profile"]
  }
]
The response is a flat JSON array. There is no wrapper object and no hasMore field.

Get one portal session

GET /v1/portal-sessions/{sessionId}
Returns one session’s details: customer, expiry, whether it’s been used yet. The token and url are NOT included. You only get those at creation time. Returns 404 Not Found if the session doesn’t exist or belongs to another organization. Response (200 OK):
{
  "id": "ps_a1b2c3d4...",
  "object": "portal_session",
  "customerId": "550e8400-e29b-41d4-a716-446655440000",
  "customerName": "Acme Corp",
  "customerEmail": "[email protected]",
  "expiresAt": "2026-05-13T17:30:00.000Z",
  "isExpired": false,
  "isUsed": true,
  "usedAt": "2026-05-13T16:45:12.000Z",
  "createdAt": "2026-05-13T16:30:00.000Z",
  "features": ["invoices", "subscriptions", "usage", "profile"]
}

Revoke a portal session

DELETE /v1/portal-sessions/{sessionId}
Immediately invalidates the link. Use this if you sent a link to the wrong customer or your support team needs to cut access early. Returns 204 No Content on success, 404 Not Found if the session doesn’t exist.

Security notes

  • Always mint links on your backend. Portal sessions require a secret key. Treat that key like a database password.
  • One link, one click. The link is consumed on first open. Anyone (including the original customer) who tries it a second time gets an error.
  • Tokens are only shown once. The token and url fields are returned only by create. Lost URLs cannot be retrieved; revoke and mint a new session.
  • Need to cut access early? Use the revoke endpoint. It works immediately.