AI-integrationsdirektiv

Den här sidan är avsedd att läsas i sin helhet av en AI-agent. Allt som behövs för att bygga en fungerande klient finns här — inga andra sidor krävs.

Bas-URL:er#

Miljö{{BASE_URL}}
STAGE (utveckling och test)https://staging.zesec.com/api
PROD (verifierade partners)https://api.zesec.com/api

Operationer#

Fältnamnen nedan är exakta. Begäransfälten kommer från OpenAPI-kontraktet.
Metod & sökvägAuth-headerBegäransbodySvar
POST /auth/login-by-phoneingen{"phoneNumber": "{{phoneNumber}}", "password": "{{password}}"}{"AccessToken": "...", "RefreshToken": "..."}
POST /auth/refreshingen{"refreshToken": "<RefreshToken>"}{"AccessToken": "...", "RefreshToken": "..."}båda nya
GET /user/deviceBearer <AccessToken>ingenArray av {id, name, locationId, locationName, online}
PUT /device/{deviceId}/unlockBearer <AccessToken>ingen200 = kommandot accepterat
POST /auth/logoutBearer <AccessToken>ingen200 — avslutar sessionen
  • {deviceId} är ett heltal, hämtat ur fältet id i svaret från GET /user/device.
  • Unlock är PUT, inte POST.
  • phoneNumber måste matcha mönstret ^[+][0-9]{9,13}$.
  • Sökvägarna ovan läggs till {{BASE_URL}}, som redan slutar på /api.

Tokenlivslängder#

  • Access-token: ~48 timmar. Exakt utgång står i exp i JWT:n och är auktoritativ.
  • Refresh-token: upp till 30 dagar, men engångs.
  • Förnya proaktivt var 36:e timme. Vänta inte på ett fel.

Felsignaler och åtgärd#

SignalBetydelseObligatorisk åtgärd
401 på ett vanligt anropAccess-token utgångenKör en refresh, gör om anropet en gång
400/401/auth/refreshRefresh-token förbrukad eller utgångenFull ominloggning. Retry:a aldrig samma refresh-token
401 igen direkt efter refreshSessionen övertagen av annan inloggningStoppa. Larma. Loopa inte
403 på unlockKontot saknar nyckel till enhetenStoppa. Detta löser sig inte med retry
404 på unlockOkänt {deviceId}Hämta enhetslistan på nytt och uppdatera mappningen
429För många anropRespektera Retry-After. Exponentiell backoff med jitter
5xxTillfälligt felBackoff. Aldrig på unlock — se nedan

Hårda begränsningar#

  1. En aktiv session per konto. Implementera tokenlagret som en singleton med delad lagring. Två inloggningar med samma konto avlivar varandras sessioner.
  2. Refresh-token är engångs. Ett samtidigt anropspar utan lås förbrukar två tokens och dödar sessionen. Single-flight-lås krävs.
  3. Skriv båda tokens atomiskt. En halv skrivning förlorar sessionen permanent.
  4. Logga in server-side, aldrig per öppning. Öppningsögonblicket ska innehålla exakt ett API-anrop.
  5. Retry:a aldrig unlock automatiskt. Ett upprepat anrop kan öppna dörren igen. Visa felet för en människa.
  6. Hårdkoda aldrig `{deviceId}`. Hämta och cachelagra mappningen.
  7. Tokens lämnar aldrig servern. Inte till webbklient, inte till mobilapp, inte till loggar.

Referensimplementation#

Reference implementation (language-agnostic)
STATE  session = { accessToken, refreshToken, rotatedAt }   # ONE per API account, server-side
CONST  PROACTIVE_REFRESH = 36 hours
LOCK   refreshLock                                        # single-flight

FUNCTION getValidAccessToken():
    IF session EXISTS AND now() - session.rotatedAt < PROACTIVE_REFRESH:
        RETURN session.accessToken
    ACQUIRE refreshLock                                   # concurrent callers wait, never race
        IF session EXISTS:
            TRY:
                RETURN rotate(session.refreshToken)
            CATCH RefreshFailed:                          # 400/401 => token spent or expired
                RETURN login()
        ELSE:
            RETURN login()
    RELEASE refreshLock

FUNCTION rotate(refreshToken):
    res = POST {{BASE_URL}}/auth/refresh  body {"refreshToken": refreshToken}
    IF res.status IN (400, 401): THROW RefreshFailed      # do NOT retry this token, ever
    IF res.status >= 500: BACKOFF once, THEN THROW RefreshFailed
    session = { accessToken: res.AccessToken,
                refreshToken: res.RefreshToken,           # BOTH, in ONE atomic write
                rotatedAt: now() }
    RETURN session.accessToken

FUNCTION login():
    res = POST {{BASE_URL}}/auth/login-by-phone  body {"phoneNumber": ..., "password": ...}
    IF NOT res.ok: ALERT operator; STOP                   # never loop on a failed login
    session = { accessToken: res.AccessToken, refreshToken: res.RefreshToken, rotatedAt: now() }
    RETURN session.accessToken

FUNCTION apiCall(method, path, body = null):
    res = REQUEST method, {{BASE_URL}} + path, Authorization: "Bearer " + getValidAccessToken()
    IF res.status == 401:
        invalidate session.accessToken
        res = REQUEST method, {{BASE_URL}} + path, Authorization: "Bearer " + getValidAccessToken()
    RETURN res                                            # still 401 => STOP, alert; do not loop

FUNCTION unlock(internalDoorId):
    ASSERT callerIsAuthorized(internalDoorId)             # YOUR rules, not KiiOn's
    deviceId = cachedMapping[internalDoorId]              # from syncDevices(), never hard-coded
    RETURN apiCall("PUT", "/device/" + deviceId + "/unlock")   # exactly one call, no retry loop

Maskinläsbara källor#

Kopiera sidan som Markdown: Visa som Markdown