Developer API

Integrate KeyLockr QR login and end-to-end encrypted storage with one stable app_tag.

Overview

One external identity field

All external SSO requests send the effective app_tag. Without a custom tag, the effective value is the numeric Service ID, but it is still sent in app_tag; there is no external svc_id field.

Device bindings use safe_id + app_tag + sign_pk; AppData uses safe_id + app_tag.

The name shown by the Safe always comes from the title registered in MyDeveloper. QR callers cannot override it.

authQR login

The user authorizes with the KeyLockr Safe. Your backend verifies the device through an IP-allowlisted endpoint.

dataEncrypted storage

One Safe and tag share one ciphertext while every computer keeps an independent keypair and device row.

1. Register a Service

my.keylockr.app/developer

FieldRule
titleAuthoritative app name shown by the Safe.
server_ipsCaller IP allowlist for app_verify and sso_user_pubkey.
app_tagOptional custom identity. Blank uses the numeric Service ID. A custom value must be globally unique, non-numeric, and not system-reserved. A service without a custom tag can set one later in MyDeveloper; once a custom tag is set it is locked and cannot be changed.
data_approvedAllows AppData for new bindings; otherwise auth-only.

App Attest is not part of this flow, and there is no app_attest_challenge API. An app_tag is a public identifier, not a client secret.

2. QR Login

Handshake
POST https://api.keylockr.app/v3/handshake
Content-Type: application/octet-stream

// MessagePack
{
  sign_pk: Uint8Array, // Ed25519, 32 bytes
  enc_pk: Uint8Array,  // X25519, 32 bytes
  app_tag: "wuse"
}
// -> { _res: "ok", tmp_id: string }

SSO-only requests may omit name. AppData or Access Point requests must send the physical client/device name. The Safe always resolves the authoritative app title from MyDeveloper through app_tag.

Connect WS before showing the QR
sig = base64url(nacl.sign("tmp.{tmp_id}.{ts}", sign_sk))
wss://api.keylockr.app/v3/ws?sig={sig}

// Choose one current QR form:
sso://login?tmp_id={tmp_id}
keylockr://sso?tmp_id={tmp_id}

Show only one QR code per authorization. Both forms use the same tmp_id; SSO, AppData, and Access Point capabilities come from server-side handshake state and never require a second QR.

Authorization completion is WebSocket-only, not polled. A brief reconnect replays the completed result while tmp_id remains valid.

{
  action: "app_auth_result",
  status: "done",          // must check; anything else is not a completed authorization
  app_id: string,
  safe_id: string,
  capabilities: string[],  // e.g. ["sso"]; verify it matches what you requested
  user_nickname: string,
  user_enc_pk: Uint8Array,
  ap_access?: { full_access: boolean, scopes: string[] }, // Access Point only
  account_key_for_ap?: Uint8Array,                        // Access Point only
  data_plain?: Uint8Array,   // AppData only
  data_encrypted?: Uint8Array, // AppData only
  ver?: string                 // AppData only
}
Backend verification
POST https://api.keylockr.app/v3/app_verify
{
  app_tag: "wuse",
  app_id: string,
  safe_id: string,
  sign_pk: string // base64, same public key as handshake
}
// -> { _res: "ok", valid: boolean, nickname?: string }

The call must originate from a registered server_ips address. Check _res first; a hard error is not the same as valid:false.

3. AppData

AppData uses KPS identity app.{app_id}. Keep the recovered filekey in memory; KeyLockr stores only an encrypted filekey and ciphertext.

Request the filekey
action: "app_req_filekey", body: {}
// -> { status: "done" | "safe_auth_required" | "denied",
//      data_filekey?: Uint8Array, ver?: string }

// After Safe approval:
{ action: "app_filekey_result", status: "done",
  data_filekey: Uint8Array, data_enc: Uint8Array, ver: string }
Read
action: "app_get_data", body: {}
// -> {
//   file_id: string,
//   ver: string,
//   data_plain: Uint8Array,
//   data_encrypted: Uint8Array
// }
Versioned write
action: "app_set_data", body: {
  ver: string,          // exact version from the last read
  data_enc: Uint8Array
}
// -> { file_id: string, ver: string } // new version

// stale version:
// -> { _res: "err", code: "file_ver_conflict" }
ver is mandatory. On conflict, read the latest data, merge in your own business logic, and write with the new version. KeyLockr does not retry, merge, create revisions, or open a transaction for this operation.

4. Multi-device Rules

Device identity(safe_id, app_tag, sign_pk)
Shared data identity(safe_id, app_tag)
  • Every computer generates and retains its own sign_sk/enc_sk; never copy private keys between devices.
  • Computers using the same Safe/tag have separate device rows but reference one AppData file.
  • Adding a second device never rewrites the existing data_filekey, ciphertext, or version.
  • Concurrent edits are detected by version CAS; the app decides how to merge.

5. API Reference

Endpoint/actionIdentityPurpose
POST /v3/handshakenoneStart authorization with app_tag.
GET /v3/ws?sig=signed queryKPS WebSocket.
POST /v3/app_verifyservice IP allowlistVerify device and user.
app_req_filekeyapp.*Request the shared filekey from the Safe.
app_get_dataapp.*Read ciphertext and version.
app_set_dataapp.*Version-CAS write.
POST /v3/sso_user_pubkeyservice IP allowlistApproved plugin services fetch a bound Safe public key with app_tag.
GET /svc_pk/{app_tag}publicService-to-service encryption public key.

The Safe internally uses app_scan_info and app_add to complete a binding; third-party clients do not call them directly.

For integration questions, contact infosafex.cloud