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-locked
  • auth.station-locked
  • auth.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
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"
}'
JavaScript (fetch)
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();
Request body · application/json
{
  "method": "pin",
  "credential": "stc_Qm9vdGgtMi1pbi10aGUtd2FyZWhvdXNl",
  "userId": "usr_01a15869-1d2e-7f3a-8b4c-5d6e7f8a9b0c",
  "pin": "482913"
}
Response · 200 · application/json
{
  "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

RefusalWhat it meansWhat to do
InvalidStation reason The request was refused. reason says why.Fix the request. Sending it again unchanged gets the same answer.

401

RefusalWhat it meansWhat 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

RefusalWhat it meansWhat 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

RefusalWhat it meansWhat 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