Generate authorization code

Issues a short-lived single-use authorization code for a user. The code should
be embedded in a magic link email sent to the user. Called by trusted S2S services only.

Events fired

EventTriggerDescription
authorization.CodeIssuedsyncFired when the authorization code is successfully issued.
Recent Requests
Log in to see full request history
TimeStatusUser Agent
Retrieving recent requests…
LoadingLoading…
📘

Eventsauthorization.CodeIssued

Why this endpoint exists

This is the retailer's door into identity delegation. Your customer is signed in on your platform; you want them shopping on the Funtrips storefront without a second login, and without sharing your session cookie or their credentials.

So your backend — authenticated as you, over S2S — declares "this customer is legitimate, mint them a session", and receives a single-use code. That code is the only thing that travels through the customer's browser. Your credentials never leave your server.

Read Session exchange for the full flow. This page is the endpoint reference for step 1.

Request

curl -X POST https://api.acc.funtrips.io/v2/session \
  -H "Authorization: Bearer $S2S_TOKEN" \
  -H "Accept-Language: nl-NL" \
  -H "Content-Type: application/json" \
  -d '{
    "external_user_id": "customer-4815162342",
    "ttl_seconds": 300,
    "integration_context": {
      "loyalty_points_balance": 1250,
      "tier": "gold"
    }
  }'
FieldTypeRequiredNotes
external_user_idstringYesYour identifier for the customer. 1–128 characters
ttl_secondsintegerNoCode lifetime. 60–900, default 300
integration_contextobjectNoFree-form values for your campaign's integration adapter

There is no campaign parameter. The campaign is resolved from your client_id via the token's campaign claim. One client id, one campaign.

external_user_id becomes the customer's identity

Whatever you send is stored verbatim and becomes the canonical reference for this customer everywhere in the platform — their orders, their tickets, their loyalty balance. There is no separate platform user id and no mapping table for you to keep.

Send something stable. An account number or internal UUID is better than an e-mail address, because an e-mail change would otherwise fork the customer's history into two identities.

It is echoed back on GET /me so your front-end can confirm which customer the session belongs to.

ttl_seconds — keep it short

The code only has to survive one redirect. The default 300 seconds is already generous; the 900-second maximum exists for flows with a human step in between. Shorter is strictly safer, since the code is a bearer credential in transit.

integration_context — your side-channel

A free-form JSON bag stored on the session and handed to the integration adapter built for your campaign. This is how information that only your system holds reaches the platform at session-creation time: a current loyalty point balance, a customer tier, a partner reference.

Which keys are meaningful is agreed per integration. Unknown keys are stored and otherwise ignored, and values may be any JSON type. It comes back on the code exchange response so your front-end can see what the platform is working with.

Keep the JSON-encoded size within 2 KiB. It rides on the session, and an oversized context is a session that cannot be minted.

Accept-Language is captured here, not later

The locale negotiated on this call is stamped onto the session and returned as locale when the code is exchanged. Send the customer's language now, and your storefront can switch UI language from the token instead of guessing from the browser.

Response

200 OK:

{
  "data": {
    "session_id": "9f8e7d6c-5b4a-4938-8271-6a5b4c3d2e1f",
    "campaign_id": "3c1e5a90-7b2d-4f61-9a83-1d4e7f2b8c05",
    "code": "s7Qk2mXpR4vY8nB1tL6wZ3hJ9dF0gC5aE2iU7oS4rN8",
    "code_expires_at": "2026-09-03T14:35:00Z",
    "webframe": {
      "url": "https://tickets.your-brand.example",
      "query_param": "code"
    }
  },
  "meta": { "correlation_id": "…" }
}
FieldUse
code43-character single-use token. Put it in the redirect URL
code_expires_atAbsolute expiry — do not queue the redirect past it
session_idThe session this code will mint. Useful in support conversations
campaign_idThe campaign resolved from your credentials
webframe.urlCampaign-configured storefront host
webframe.query_paramName of the query parameter that must carry the code

Composing the redirect

{webframe.url}[/your/path]?{webframe.query_param}={code}
https://tickets.your-brand.example?code=s7Qk2mXpR4vY8nB1tL6wZ3hJ9dF0gC5aE2iU7oS4rN8

Read query_param rather than hardcoding code, and you survive a configuration change. Deep-linking is fine — append your path before the query string.

The webframe block is absent when your campaign has no webframe configured, which means you are hosting the storefront yourself and already know the destination.

🚧

The code is a credential in a URL.

Do not log the composed URL, and keep it out of analytics and referrer headers. The short TTL and single use limit the exposure, but the cleanest posture is that the code exists only in the redirect itself.

Retrying is cheap here

Send an Idempotency-Key — a client-generated UUID per hand-off — so retries are attributable.

A duplicate request on this route costs nothing either way: each code is single-use and expires
within minutes, so an extra code that never gets exchanged simply lapses. Retry freely on a
network failure rather than failing the customer's click.

Errors

StatustypeWhat to do
400external_user_id missing or over 128 characters, or ttl_seconds outside 60–900
401S2S token missing, expired or invalid. Re-fetch and retry once
403Token valid but lacks funtrips/session.create — a provisioning issue, contact us
500session-mint-failedSession could not be created. Retryable
500authorization-code-save-failedCode could not be stored. Retryable

A 401 here is almost always an expired cached token; refresh and retry once before treating it as an error. A 403 cannot be fixed from your side.

Notes

  • authorization.CodeIssued is published on success.
  • Mint per hand-off, not per session. Call this when the customer clicks through to the storefront, not at login. A code minted early and used late is an expired code.
  • The code is spent by your front-end, at POST /session/token. Do not exchange it server-side — the whole point is that the resulting session lands in the customer's browser.
Body Params

Issues a short-lived one-time authorization code for a user. Called by
trusted S2S services. The campaign is derived from the caller's
credentials.

string
required
length between 1 and 128

Identifier for the user in the caller's system (typically email). The
platform stores this value verbatim and treats it as the canonical
user reference for all subsequent admin endpoints — pass the same
value back when calling e.g.
GET /admin/fulfillment/users/{external_user_id}/orders or
POST /admin/loyalty/ledgers/{external_user_id}/entries.

integer
60 to 900

Code lifetime in seconds. Defaults to 300.

integration_context
object

Free-form key-value bag supplied by the integrator as input for the
platform's per-retailer integration package. The integration adapter
(under internal/<integration>/infra/) reads agreed-upon keys from
this object to seed retailer-specific state for the session — for
example a current loyalty point balance, a customer tier, a partner
reference, or anything else the integration needs from the integrator's
system at session-creation time.

The set of recognised keys is integration-specific and documented per
integration; unknown keys are stored on the session and otherwise
ignored. Values may be any JSON type (string, number, boolean, object,
array) — the integration adapter is responsible for interpreting them.

Headers
uuid
required

Client-generated UUID. Identical keys within the TTL window return the cached response.

string

RFC 7231 language preference. The best match against supported locales (nl-NL, en-GB, de-DE) is selected. Defaults to nl-NL when omitted.

string
enum
Defaults to application/json

Generated from available response content types

Allowed:
Responses

Language
Credentials
OAuth2
Missing 1 required scope
URL
LoadingLoading…
Response
Click Try It! to start a request and see the response here! Or choose an example:
application/json
application/problem+json