Skip to content

Auth

View as Markdown

Sign-in and token issuance. Humans use passkeys (WebAuthn). The CLI uses the OAuth 2.0 device flow (RFC 8628) and receives a one-hour access token plus a refresh token. A development-only login exists when the server runs with GUV_DEV_LOGIN=true.

Operation Method and path
Sign in without a passkey (development only) POST /v1/auth/dev-login
Send a sign-up verification code by email POST /v1/auth/email-verification
Start registering a passkey (sign-up, or add a passkey when signed in) POST /v1/auth/passkeys/register/begin
Finish registering a passkey POST /v1/auth/passkeys/register/finish
Start signing in with a passkey POST /v1/auth/passkeys/login/begin
Finish signing in with a passkey POST /v1/auth/passkeys/login/finish
Start a passkey assertion for a sensitive action POST /v1/auth/step-up/begin
Start the CLI device flow (RFC 8628) POST /v1/auth/device/code
Show which device is asking to sign in GET /v1/auth/device
Approve or deny a CLI sign-in (signed-in user) POST /v1/auth/device/decision
Exchange a device code, authorisation code or refresh token for an access token POST /v1/auth/token
Sign out the current session POST /v1/auth/logout

Sign in without a passkey (development only)

Section titled “Sign in without a passkey (development only)”
POST /v1/auth/dev-login

Exists only when the server runs with GUV_DEV_LOGIN=true; otherwise returns 404 dev_login_disabled. Creates the user (and a personal workspace) if needed and sets the session cookie.

  • Operation: devLogin
  • Scopes: none (public)
  • CLI: guv login --dev
  • Idempotency-Key: optional

Request body (application/json, required): DevLoginRequest

Field Type Description
email * string (email)
name string
platform_admin boolean Make the user a platform admin (development only). Default false.
create_workspace boolean Create a personal workspace if the user has none. Default true.

Responses

Status Meaning Body
200 OK OK. LoginResponse

Errors: 400, 401, 409, 422, 429, 500, as problem+json with a stable code.

Example

Terminal window
curl -X POST https://api.guv.dev/v1/auth/dev-login \
-H "Authorization: Bearer $GUV_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"email": "dev@guv.local",
"name": "Dev User",
"platform_admin": false,
"create_workspace": true
}'

Example response

{
"session": {
"id": "ses_01jbx2m9k7q4r8t5v6w3y0z1pr",
"kind": "browser",
"auth_method": "passkey",
"user_agent": "string",
"ip": "string",
"current": false,
"created_at": "2026-10-22T13:05:00Z",
"last_seen_at": "2026-10-22T13:05:00Z",
"expires_at": "2026-10-22T13:05:00Z"
},
"access_token": "string",
"csrf_token": "string",
"user": {
"id": "usr_01jbx2m9k7q4r8t5v6w3y0z1us",
"email": "stewart@primecarers.co.uk",
"name": "Stewart Webb",
"avatar_url": null,
"github_login": "stewartwebb",
"has_passkey": true,
"is_platform_admin": true,
"default_workspace_id": "ws_01jbx2m9k7q4r8t5v6w3y0z1ws",
"created_at": "2026-10-22T09:00:00Z"
},
"new_user": false
}
POST /v1/auth/email-verification
  • Operation: startEmailVerification
  • Scopes: none (public)
  • Idempotency-Key: optional

Request body (application/json, required): EmailVerificationRequest

Field Type Description
email * string (email)

Responses

Status Meaning Body
202 Accepted Accepted. EmailVerificationStarted

Errors: 400, 401, 409, 422, 429, 500, as problem+json with a stable code.

Example

Terminal window
curl -X POST https://api.guv.dev/v1/auth/email-verification \
-H "Authorization: Bearer $GUV_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"email": "ada@example.co.uk"
}'

Example response

{
"verification_id": "string",
"expires_at": "2026-10-22T13:05:00Z"
}

Start registering a passkey (sign-up, or add a passkey when signed in)

Section titled “Start registering a passkey (sign-up, or add a passkey when signed in)”
POST /v1/auth/passkeys/register/begin
  • Operation: beginPasskeyRegistration
  • Scopes: any signed-in principal
  • Idempotency-Key: optional

Request body (application/json, required): PasskeyRegisterBeginRequest

Field Type Description
name string Label for the passkey.
email string (email)
display_name string
verification_id string
code string

Responses

Status Meaning Body
200 OK OK. PasskeyChallenge

Errors: 400, 401, 403, 409, 422, 429, 500, as problem+json with a stable code.

Example

Terminal window
curl -X POST https://api.guv.dev/v1/auth/passkeys/register/begin \
-H "Authorization: Bearer $GUV_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "MacBook Touch ID",
"email": "ada@example.co.uk",
"display_name": "string",
"verification_id": "string",
"code": "string"
}'

Example response

{
"challenge_id": "string",
"expires_at": "2026-10-22T13:05:00Z",
"public_key_creation": {
"challenge": "string",
"rp": {
"id": "guv.dev",
"name": "Guv"
},
"user": {
"id": "string",
"name": "string",
"displayName": "string"
},
"pubKeyCredParams": [
{}
],
"timeout": 1,
"authenticatorSelection": {},
"attestation": "string",
"excludeCredentials": [
{}
]
},
"public_key_request": {
"challenge": "string",
"rpId": "string",
"timeout": 1,
"userVerification": "required",
"allowCredentials": [
{}
]
}
}
POST /v1/auth/passkeys/register/finish

Signs the new user in when this completes a sign-up.

  • Operation: finishPasskeyRegistration
  • Scopes: any signed-in principal
  • Idempotency-Key: optional

Request body (application/json, required): PasskeyFinishRequest

Field Type Description
challenge_id * string
credential * WebAuthnCredential
name string

Responses

Status Meaning Body
200 OK OK. LoginResponse

Errors: 400, 401, 403, 409, 422, 429, 500, as problem+json with a stable code.

Example

Terminal window
curl -X POST https://api.guv.dev/v1/auth/passkeys/register/finish \
-H "Authorization: Bearer $GUV_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"challenge_id": "string",
"credential": {
"id": "string",
"rawId": "string",
"type": "public-key",
"response": {},
"authenticatorAttachment": "string",
"clientExtensionResults": {}
},
"name": "string"
}'

Example response

{
"session": {
"id": "ses_01jbx2m9k7q4r8t5v6w3y0z1pr",
"kind": "browser",
"auth_method": "passkey",
"user_agent": "string",
"ip": "string",
"current": false,
"created_at": "2026-10-22T13:05:00Z",
"last_seen_at": "2026-10-22T13:05:00Z",
"expires_at": "2026-10-22T13:05:00Z"
},
"access_token": "string",
"csrf_token": "string",
"user": {
"id": "usr_01jbx2m9k7q4r8t5v6w3y0z1us",
"email": "stewart@primecarers.co.uk",
"name": "Stewart Webb",
"avatar_url": null,
"github_login": "stewartwebb",
"has_passkey": true,
"is_platform_admin": true,
"default_workspace_id": "ws_01jbx2m9k7q4r8t5v6w3y0z1ws",
"created_at": "2026-10-22T09:00:00Z"
},
"new_user": false
}
POST /v1/auth/passkeys/login/begin
  • Operation: beginPasskeyLogin
  • Scopes: none (public)
  • Idempotency-Key: optional

Request body (application/json, required): PasskeyLoginBeginRequest

Field Type Description
email string (email) Optional; omit for discoverable credentials (passkey autofill).

Responses

Status Meaning Body
200 OK OK. PasskeyChallenge

Errors: 400, 401, 409, 422, 429, 500, as problem+json with a stable code.

Example

Terminal window
curl -X POST https://api.guv.dev/v1/auth/passkeys/login/begin \
-H "Authorization: Bearer $GUV_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"email": "ada@example.co.uk"
}'

Example response

{
"challenge_id": "string",
"expires_at": "2026-10-22T13:05:00Z",
"public_key_creation": {
"challenge": "string",
"rp": {
"id": "guv.dev",
"name": "Guv"
},
"user": {
"id": "string",
"name": "string",
"displayName": "string"
},
"pubKeyCredParams": [
{}
],
"timeout": 1,
"authenticatorSelection": {},
"attestation": "string",
"excludeCredentials": [
{}
]
},
"public_key_request": {
"challenge": "string",
"rpId": "string",
"timeout": 1,
"userVerification": "required",
"allowCredentials": [
{}
]
}
}
POST /v1/auth/passkeys/login/finish
  • Operation: finishPasskeyLogin
  • Scopes: none (public)
  • Idempotency-Key: optional

Request body (application/json, required): PasskeyFinishRequest

Field Type Description
challenge_id * string
credential * WebAuthnCredential
name string

Responses

Status Meaning Body
200 OK OK. LoginResponse

Errors: 400, 401, 409, 422, 429, 500, as problem+json with a stable code.

Example

Terminal window
curl -X POST https://api.guv.dev/v1/auth/passkeys/login/finish \
-H "Authorization: Bearer $GUV_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"challenge_id": "string",
"credential": {
"id": "string",
"rawId": "string",
"type": "public-key",
"response": {},
"authenticatorAttachment": "string",
"clientExtensionResults": {}
},
"name": "string"
}'

Example response

{
"session": {
"id": "ses_01jbx2m9k7q4r8t5v6w3y0z1pr",
"kind": "browser",
"auth_method": "passkey",
"user_agent": "string",
"ip": "string",
"current": false,
"created_at": "2026-10-22T13:05:00Z",
"last_seen_at": "2026-10-22T13:05:00Z",
"expires_at": "2026-10-22T13:05:00Z"
},
"access_token": "string",
"csrf_token": "string",
"user": {
"id": "usr_01jbx2m9k7q4r8t5v6w3y0z1us",
"email": "stewart@primecarers.co.uk",
"name": "Stewart Webb",
"avatar_url": null,
"github_login": "stewartwebb",
"has_passkey": true,
"is_platform_admin": true,
"default_workspace_id": "ws_01jbx2m9k7q4r8t5v6w3y0z1ws",
"created_at": "2026-10-22T09:00:00Z"
},
"new_user": false
}

Start a passkey assertion for a sensitive action

Section titled “Start a passkey assertion for a sensitive action”
POST /v1/auth/step-up/begin

Used before approving an agent request, revealing credentials, deleting resources, raising a spend cap or changing the kill switch. The resulting PasskeyAssertion is sent with that action.

  • Operation: beginStepUp
  • Scopes: any signed-in principal
  • Idempotency-Key: optional

Request body (application/json, required): StepUpBeginRequest

Field Type Description
purpose * approval, kill_switch, reveal_credentials, delete_resource, raise_spend_cap
subject_id string The approval, database or other resource the assertion is bound to.

Responses

Status Meaning Body
200 OK OK. PasskeyChallenge

Errors: 400, 401, 403, 409, 422, 429, 500, as problem+json with a stable code.

Example

Terminal window
curl -X POST https://api.guv.dev/v1/auth/step-up/begin \
-H "Authorization: Bearer $GUV_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"purpose": "approval",
"subject_id": "apr_01jbx2m9k7q4r8t5v6w3y0z1ap"
}'

Example response

{
"challenge_id": "string",
"expires_at": "2026-10-22T13:05:00Z",
"public_key_creation": {
"challenge": "string",
"rp": {
"id": "guv.dev",
"name": "Guv"
},
"user": {
"id": "string",
"name": "string",
"displayName": "string"
},
"pubKeyCredParams": [
{}
],
"timeout": 1,
"authenticatorSelection": {},
"attestation": "string",
"excludeCredentials": [
{}
]
},
"public_key_request": {
"challenge": "string",
"rpId": "string",
"timeout": 1,
"userVerification": "required",
"allowCredentials": [
{}
]
}
}
POST /v1/auth/device/code
  • Operation: requestDeviceCode
  • Scopes: none (public)
  • CLI: guv login
  • Idempotency-Key: optional

Request body (application/json, required): DeviceCodeRequest

Field Type Description
client_id * string
scope string Space-separated scopes. Defaults to the user’s full role.
device_name string

Responses

Status Meaning Body
200 OK OK. DeviceCodeResponse

Errors: 400, 401, 409, 422, 429, 500, as problem+json with a stable code.

Example

Terminal window
curl -X POST https://api.guv.dev/v1/auth/device/code \
-H "Authorization: Bearer $GUV_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"client_id": "guv-cli",
"scope": "string",
"device_name": "stewart-mbp"
}'

Example response

{
"device_code": "string",
"user_code": "BDWP-HQPK",
"verification_uri": "https://guv.dev/device",
"verification_uri_complete": "https://guv.dev/device?code=BDWP-HQPK",
"expires_in": 600,
"interval": 5
}
GET /v1/auth/device
  • Operation: lookupDeviceCode
  • Scopes: any signed-in principal

Parameters

Name In Type Description
user_code * query string Code shown in the terminal.

Responses

Status Meaning Body
200 OK OK. DeviceLookup

Errors: 400, 401, 403, 429, 500, as problem+json with a stable code.

Example

Terminal window
curl https://api.guv.dev/v1/auth/device \
-H "Authorization: Bearer $GUV_TOKEN"

Example response

{
"user_code": "string",
"client_id": "string",
"device_name": "string",
"ip": "string",
"requested_at": "2026-10-22T13:05:00Z",
"scopes": [
"deploy:staging"
]
}

Approve or deny a CLI sign-in (signed-in user)

Section titled “Approve or deny a CLI sign-in (signed-in user)”
POST /v1/auth/device/decision
  • Operation: decideDeviceCode
  • Scopes: any signed-in principal
  • Idempotency-Key: required

Request body (application/json, required): DeviceDecisionRequest

Field Type Description
user_code * string
decision * approve, deny

Responses

Status Meaning Body
204 No Content Done. No body. none

Errors: 400, 401, 403, 409, 422, 429, 500, as problem+json with a stable code.

Example

Terminal window
curl -X POST https://api.guv.dev/v1/auth/device/decision \
-H "Authorization: Bearer $GUV_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"user_code": "string",
"decision": "approve"
}'

Exchange a device code, authorisation code or refresh token for an access token

Section titled “Exchange a device code, authorisation code or refresh token for an access token”
POST /v1/auth/token

Errors use problem+json with the OAuth error field set (authorization_pending, slow_down, expired_token, access_denied, invalid_grant).

  • Operation: issueToken
  • Scopes: none (public)

Request body (application/json, required): TokenRequest

Field Type Description
grant_type * urn:ietf:params:oauth:grant-type:device_code, refresh_token, authorization_code
client_id * string
device_code string
refresh_token string
code string
code_verifier string
redirect_uri string

Responses

Status Meaning Body
200 OK OK. TokenResponse

Errors: 400, 401, 409, 422, 429, 500, as problem+json with a stable code.

Example

Terminal window
curl -X POST https://api.guv.dev/v1/auth/token \
-H "Authorization: Bearer $GUV_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"grant_type": "urn:ietf:params:oauth:grant-type:device_code",
"client_id": "string",
"device_code": "string",
"refresh_token": "string",
"code": "string",
"code_verifier": "string",
"redirect_uri": "string"
}'

Example response

{
"access_token": "string",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "string",
"scope": "string"
}
POST /v1/auth/logout
  • Operation: logout
  • Scopes: any signed-in principal
  • CLI: guv logout
  • Idempotency-Key: optional

Responses

Status Meaning Body
204 No Content Done. No body. none

Errors: 400, 401, 403, 409, 422, 429, 500, as problem+json with a stable code.

Example

Terminal window
curl -X POST https://api.guv.dev/v1/auth/logout \
-H "Authorization: Bearer $GUV_TOKEN"