Auth API · stations
Unlock a station as a person
POST /api/auth/station/unlock
Unlock a station as a person: the device's credential with their userId and station pin (method: pin).
No sign-in left open on the device unlocks it. The answer is a short-lived token whose subject is the person, in the station's view, carrying the station and its location — every service narrows the person's roles by the view's ceiling, an owner's or admin's too, and what they do is theirs on the trail. 401 StationCredentialInvalid for an unknown credential or a revoked station, 401 StationUnlockRefused for a wrong PIN (attemptsLeft), no PIN, an owner's or admin's PIN under six digits, or someone who is not an active member, 429 StationPinLocked after five wrong PINs in a row for that person (auth.station-pin-locked is appended as it locks), 429 StationLocked after ten failed unlocks in a row at the station whoever they named (auth.station-locked is appended as it locks) — each lockout fifteen minutes, doubling with every lockout in a row up to a day until an unlock succeeds — 403 ViewNotOpened when the person's roles do not open the station's view, 400 InvalidStation for a malformed body. Appends auth.station-unlocked. No idempotency key: each unlock is a fresh sign-in at the device, like any sign-in, and mints a fresh token.
Access
Public: The enrolled device's own credential, verified by the route before anything is read: a station signs in as itself, and nobody is signed in on it yet, with the person's own station PIN
No token: the link, the claim or the first message is the credential, as the description says.
How scopes come from roles: Scopes and roles. Getting a token: Authentication and app tokens.
Retries
No idempotency key: each unlock is a fresh sign-in at the device, like any sign-in, and mints a fresh token.
Why every write carries a key: Idempotency and retries.
Facts appended
auth.station-pin-lockedauth.station-lockedauth.station-unlocked
What a fact is: Facts and the journal.
AI agent tool
No agent tool. Unlocking a station takes the device's credential and a person's PIN or passkey at the device itself; a credential is never handed to an agent.
Connecting an agent: AI agents.
Example
From Set up a station and unlock it with a PIN, step 7: Unlock it with the PIN.
curl -X POST 'https://auth.getmanifold.io/api/auth/station/unlock' \
-H 'Content-Type: application/json' \
-d '{
"method": "pin",
"credential": "stc_Qm9vdGgtMi1pbi10aGUtd2FyZWhvdXNl",
"userId": "usr_01a15869-1d2e-7f3a-8b4c-5d6e7f8a9b0c",
"pin": "482913"
}'const response = await fetch("https://auth.getmanifold.io/api/auth/station/unlock", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
"method": "pin",
"credential": "stc_Qm9vdGgtMi1pbi10aGUtd2FyZWhvdXNl",
"userId": "usr_01a15869-1d2e-7f3a-8b4c-5d6e7f8a9b0c",
"pin": "482913"
}),
});
if (!response.ok) throw new Error(`${response.status}: ${await response.text()}`);
const answer = await response.json();{
"method": "pin",
"credential": "stc_Qm9vdGgtMi1pbi10aGUtd2FyZWhvdXNl",
"userId": "usr_01a15869-1d2e-7f3a-8b4c-5d6e7f8a9b0c",
"pin": "482913"
}{
"token": "eyJhbGciOiJFZERTQSJ9.eyJ2aWV3IjoicGljay1wYWNrIn0.c3RhdGlvbi1zZXNzaW9u",
"expiresAt": "2026-08-17T09:15:00.000Z",
"tenantId": "org_01a1586e-f9f6-7a8b-9c7d-6e5f4a3b2c1d",
"userId": "usr_01a15869-1d2e-7f3a-8b4c-5d6e7f8a9b0c",
"view": "pick-pack",
"station": {
"stationId": "stn_01a1586e-f9f6-7a8b-9c7d-6e5f4a3b2c1d",
"locationId": "loc_01a15852-46d1-7a3b-9c0d-1e2f3a4b5c6d"
}
}The panel writes this call in other languages. It does not send it: Manifold's APIs do not take calls from a web
page on another site, so run the call from your own code or terminal. The whole API as OpenAPI: auth-service.openapi.json. Base URL https://auth.getmanifold.io.
Errors
Every refusal is a status and a JSON body whose _tag names it. A refused call changes nothing.
400
| Refusal | What it means | What to do |
|---|---|---|
InvalidStation reason | The request was refused. reason says why. | Fix the request. Sending it again unchanged gets the same answer. |
401
| Refusal | What it means | What to do |
|---|---|---|
StationCredentialInvalid reason | The request was refused. reason says why. | Nothing changed. |
StationUnlockRefused reason, attemptsLeft | The request was refused. reason says why. | Nothing changed. |
403
| Refusal | What it means | What to do |
|---|---|---|
ViewNotOpened view | The request was refused, and nothing changed. | The caller may not do this. Use a token whose roles allow it. |
429
| Refusal | What it means | What to do |
|---|---|---|
StationPinLocked until | After five wrong PINs in a row for that person (auth.station-pin-locked is appended as it locks). | Nothing changed. |
StationLocked until | The request was refused, and nothing changed. | Nothing changed. |
How refusals work everywhere: Errors.
Used in
- Set up a station and unlock it with a PIN, step 7: Unlock it with the PIN