Skip to content

Exchange a presented magic-link token for a first-party session

POST
/login/sessions
curl --request POST \
--url https://quikstik-core.fly.dev/login/sessions \
--header 'Content-Type: application/json' \
--data '{ "token": "example", "device": { "name": "example", "platform": "ios" } }'

Consumes the token (single-use, storage-arbitrated) and mints a first-party session for the resolved (issuer, subject) actor. Expired, consumed, unknown, and unresolvable subjects are one indistinguishable 404.

With an optional device, the session is also registered as a device session and the response adds refresh_token (returned once) and refresh_expires_at_ms (now + 90 days). A malformed device is a 400 returned before the link is consumed, so it never burns the single-use token.

Media typeapplication/json
object
token
required
string
>= 32 characters
device

Supplied by a device that wants a refresh credential (staff POST /login/sessions only).

object
name
required

Human label (“Wilton’s iPhone”); trimmed.

string
>= 1 characters <= 100 characters
platform
required
string
Allowed values: ios ipados macos android web

Session minted (plus a refresh credential when a device was supplied)

Media typeapplication/json
object
scopes
required

The effective permission tokens of the session’s membership, sorted (empty when the membership is not active).

Array<string>
role
required

The highest role the scopes imply. Open (see contracts/openapi/README.md); the app reads ownership here, not from GET /team.

string
session_token
required

First-party bearer session; present as Authorization: Bearer.

string
refresh_token

Present only when a device was supplied. 32 random bytes, hex encoded. Shown exactly once; only its SHA-256 digest persists.

string
refresh_expires_at_ms

Present with refresh_token; now + 90 days, epoch ms.

integer format: int64

Example generated

{
"scopes": [
"example"
],
"role": "example",
"session_token": "example",
"refresh_token": "example",
"refresh_expires_at_ms": 1
}

Malformed body

Media typeapplication/json
object
reason
required
string

Example generated

{
"reason": "example"
}

Expired, consumed, unknown, or unresolvable — indistinguishable by design

Media typeapplication/json
object
reason
required
string

Example generated

{
"reason": "example"
}

No durable backend is wired for this operation (fail-closed)

Media typeapplication/json
object
reason
required

Stable, machine-readable reason code (e.g. “sold_out”, “processor_not_configured”, “rail_refused”).

string

Example

{
"reason": "sold_out"
}