Skip to content

Keys and identity

Base URL: https://app.trymaglev.com/api/v1. All endpoint families.

Service health, what an interview turn would actually do right now, and the endpoint families available.

Public health probe that reaches the key store. Returns supported endpoint families and interviewer configuration or recent degraded state; neither establishes a successful model completion or supplier execution.

Authentication: see endpoint notes; no bearer key required.

200 response schema
{
"type": "object",
"properties": {
"status": {
"type": "string",
"const": "ok"
},
"version": {
"type": "string"
},
"interviewer": {
"type": "string",
"enum": [
"model",
"guided",
"degraded"
]
},
"baseUrl": {
"type": "string"
},
"families": {
"type": "array",
"items": {
"type": "string"
}
},
"requestId": {
"type": "string"
}
},
"required": [
"status",
"version",
"interviewer",
"baseUrl",
"families",
"requestId"
]
}

Errors: 403 Insufficient access; 500 Server failure. See recovery guidance.

This document. Answered as the OpenAPI document itself, not wrapped in a result envelope, and cacheable for five minutes.

Authentication: see endpoint notes; no bearer key required.

200 response schema
{
"type": "object",
"properties": {
"openapi": {
"type": "string",
"description": "OpenAPI version of this document, currently 3.1.0"
},
"info": {
"type": "object",
"properties": {
"title": {
"type": "string"
},
"version": {
"type": "string",
"description": "Shape of the contract, not the deployed release. Read commit from GET /health for that."
},
"description": {
"type": "string"
},
"contact": {
"type": "object",
"properties": {}
}
},
"required": [
"title",
"version"
]
},
"servers": {
"type": "array",
"items": {
"type": "object",
"properties": {
"url": {
"type": "string"
}
}
}
},
"security": {
"type": "array",
"items": {
"type": "object",
"properties": {}
}
},
"tags": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"description": {
"type": "string"
}
},
"required": [
"name"
]
}
},
"components": {
"type": "object",
"properties": {
"securitySchemes": {
"type": "object",
"properties": {}
},
"schemas": {
"type": "object",
"properties": {}
}
}
},
"paths": {
"type": "object",
"properties": {}
},
"x-scopes": {
"type": "object",
"properties": {}
},
"x-lifecycle-stages": {
"type": "array",
"items": {
"type": "object",
"properties": {}
}
},
"x-action-kinds": {
"type": "object",
"properties": {}
}
},
"required": [
"openapi",
"info",
"paths",
"components"
]
}

Errors: 400 Validation error; 403 Scope or mode does not allow this. On scope_required, details.obtainable says whether a key that carries the scope can be issued at all: true means reissuing this key with the scope would work, false means no key of this kind can ever carry it, because contacts:read and approvals:write are granted on live mode alone and maglev:* belongs to Maglev’s own keys. Stored scopes narrow the grant a mode and organisation already allow, so widening a key never adds one.; 404 Not found; 429 rate_limit: a limit for this key or route is spent. details.retryAfter is how many whole seconds of the refused window remain, at least 1, and the Retry-After header carries the same number. Wait that long rather than assuming a minute; the windows here range from 60 to 86400 seconds. Nothing was done.; 500 internal_error: the request did not complete and the reason was not one this API anticipates. Whether anything was written is unknown. Read error.message, which carries what is safe to do next, and quote the X-Request-Id header to Maglev; the request id is in the body too. Where an operation takes an Idempotency-Key, repeating the request with the same key is the way to learn the earlier outcome without running the work twice, unless the message says to use a new one.. See recovery guidance.

List the signed in organisation’s keys. Takes no credential and refuses nobody: without a Maglev browser session it answers signedIn false and an empty list, so a bearer key alone will not fill it in.

Uses the browser session. An unauthenticated call returns signedIn false and an empty key list. No secret is returned.

Authentication: see endpoint notes; no bearer key required.

200 response schema
{
"oneOf": [
{
"type": "object",
"properties": {
"signedIn": {
"const": false
},
"keys": {
"type": "array",
"items": {
"$ref": "#/components/schemas/KeyInfo"
}
},
"requestId": {
"type": "string"
}
},
"required": [
"signedIn",
"keys",
"requestId"
]
},
{
"type": "object",
"properties": {
"signedIn": {
"const": true
},
"organisation": {
"type": "string"
},
"keys": {
"type": "array",
"items": {
"$ref": "#/components/schemas/KeyInfo"
}
},
"requestId": {
"type": "string"
}
},
"required": [
"signedIn",
"organisation",
"keys",
"requestId"
]
}
]
}

Errors: 403 Insufficient access; 500 Server failure. See recovery guidance.

Issue a key. Sandbox keys are anonymous and live one day; live keys need a signed in organisation owner and live 90 days.

Sandbox issuance is unauthenticated and rate-limited. Live issuance requires a signed-in organization owner session in the console. Scopes are assigned from key mode and organization role and enforced per operation. Custom caller-selected scopes are not supported by this endpoint.

Authentication: see endpoint notes; no bearer key required.

Field Type Required Details
mode string Yes One of: sandbox, live
name string No
201 response schema
{
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"key": {
"type": "string"
},
"mode": {
"type": "string",
"enum": [
"sandbox",
"live"
]
},
"expiresAt": {
"type": "string",
"format": "date-time"
},
"scopes": {
"type": "array",
"items": {
"type": "string"
}
},
"note": {
"type": "string"
},
"requestId": {
"type": "string"
}
},
"required": [
"id",
"key",
"mode",
"expiresAt",
"scopes",
"note",
"requestId"
]
}

Errors: 400 Invalid input or missing idempotency header; 401 Invalid key or missing sign-in; 403 Insufficient access; 429 Rate limit reached; 500 Server failure. See recovery guidance.

Revoke a key: the current key with a bearer token, or any of the organisation’s keys with an owner session. Either credential is enough and one of the two is required.

A bearer key can revoke only itself. A signed-in organization owner can revoke a key belonging to the organization.

Authentication: bearer key or browser session.

Parameter Location Required Details
id path Yes
200 response schema
{
"type": "object",
"properties": {
"revoked": {
"type": "boolean",
"const": true
},
"requestId": {
"type": "string"
}
},
"required": [
"revoked",
"requestId"
]
}

Errors: 400 Invalid input or missing idempotency header; 401 Invalid key or missing sign-in; 403 Insufficient access; 404 Endpoint or record unavailable; 429 Rate limit reached; 500 Server failure. See recovery guidance.

The calling key: mode, organisation kind and effective scopes.

Authentication: bearer key.

200 response schema
{
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"name": {
"type": "string"
},
"mode": {
"type": "string",
"enum": [
"sandbox",
"live"
]
},
"expiresAt": {
"type": "string",
"format": "date-time"
},
"organisationKind": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"scopes": {
"type": "array",
"items": {
"type": "string"
}
},
"requestId": {
"type": "string"
}
},
"required": [
"id",
"name",
"mode",
"expiresAt",
"organisationKind",
"scopes",
"requestId"
]
}

Errors: 401 Invalid key or missing sign-in; 403 Insufficient access; 429 Rate limit reached; 500 Server failure. See recovery guidance.

Start a short-lived browser connection for Maglev CLI. No API key is created until an organization owner explicitly reviews and approves the matching terminal code.

JSON Maglev pairing protocol inspired by device authorization; not an OAuth/OIDC interoperability endpoint. Codes expire after ten minutes. Keep deviceCode private; only userCode enters the browser URL. Only the fixed Maglev application URL is returned in production. Ten starts per IP per hour.

Authentication: see endpoint notes; no bearer key required.

Field Type Required Details
client “maglev-cli” Yes
name string Yes Min length 1. Max length 80
mode string Yes One of: sandbox, live
201 response schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"deviceCode": {
"type": "string",
"pattern": "^[A-Za-z0-9_-]{43}$"
},
"userCode": {
"type": "string",
"pattern": "^[A-HJ-NP-Z2-9]{4}-[A-HJ-NP-Z2-9]{4}$"
},
"verificationUri": {
"type": "string",
"format": "uri"
},
"verificationUriComplete": {
"type": "string",
"format": "uri"
},
"expiresAt": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
},
"expiresIn": {
"type": "number",
"const": 600
},
"interval": {
"type": "number",
"const": 5
},
"note": {
"type": "string"
},
"requestId": {
"type": "string",
"description": "Echoed in the X-Request-Id response header. Quote it to Maglev when asking what became of a request."
}
},
"required": [
"deviceCode",
"userCode",
"verificationUri",
"verificationUriComplete",
"expiresAt",
"expiresIn",
"interval",
"note",
"requestId"
],
"additionalProperties": false
}

Errors: 400 Validation error. A body is read here, so one of five codes rides on this status and none is retryable unchanged. validation_error: the body parsed and failed the schema, and details names each path. invalid_json: there was no body, or it was not valid UTF-8 JSON. invalid_text: a string or property name holds a NUL or an unpaired Unicode surrogate, which storage cannot keep, so correct the source text. duplicate_key: a JSON object repeats a property name, no path is given, and the conflicting fields have to be resolved at the source. invalid_number: a number is outside the range that survives the round trip, so quote large identifiers as strings. Nothing was written in any of them.; 403 Scope or mode does not allow this. On scope_required, details.obtainable says whether a key that carries the scope can be issued at all: true means reissuing this key with the scope would work, false means no key of this kind can ever carry it, because contacts:read and approvals:write are granted on live mode alone and maglev:* belongs to Maglev’s own keys. Stored scopes narrow the grant a mode and organisation already allow, so widening a key never adds one.; 404 Not found; 413 body_too_large: the request body exceeds this operation’s limit, and it was not read to the end. Send less in one request rather than repeating it; 415 content_type: the request did not declare application/json, so no body was read and nothing was written. Set the header and send it again; 429 rate_limit: a limit for this key or route is spent. details.retryAfter is how many whole seconds of the refused window remain, at least 1, and the Retry-After header carries the same number. Wait that long rather than assuming a minute; the windows here range from 60 to 86400 seconds. Nothing was done.; 500 internal_error: the request did not complete and the reason was not one this API anticipates. Whether anything was written is unknown. Read error.message, which carries what is safe to do next, and quote the X-Request-Id header to Maglev; the request id is in the body too. Where an operation takes an Idempotency-Key, repeating the request with the same key is the way to learn the earlier outcome without running the work twice, unless the message says to use a new one.. See recovery guidance.

Check the same browser connection after its polling interval. Receives the same temporary API key after approval, including after a lost response.

Wait at least interval seconds. HTTP 400 authorization_pending means wait; slow_down returns an increased details.interval (up to thirty seconds), persisted by the server. access_denied, expired_token, issuance_limit and device_consumed are terminal outcomes. The temporary key expires with the ten-minute request. Save it privately and acknowledge using that key before expiry; acknowledgement extends its normal lifetime. Owner/session/organization/scope changes or revoked keys cannot silently mint a replacement.

Authentication: see endpoint notes; no bearer key required.

Field Type Required Details
deviceCode string Yes Pattern: ^[A-Za-z0-9_-]{43}$
200 response schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
},
"key": {
"type": "string",
"pattern": "^maglev_(test|live)_[A-Za-z0-9_-]{43}$"
},
"mode": {
"type": "string",
"enum": [
"sandbox",
"live"
]
},
"expiresAt": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
},
"scopes": {
"type": "array",
"items": {
"type": "string"
}
},
"acknowledgementRequired": {
"type": "boolean",
"const": true
},
"keyLifetimeDays": {
"anyOf": [
{
"type": "number",
"const": 1
},
{
"type": "number",
"const": 90
}
]
},
"requestId": {
"type": "string",
"description": "Echoed in the X-Request-Id response header. Quote it to Maglev when asking what became of a request."
}
},
"required": [
"id",
"key",
"mode",
"expiresAt",
"scopes",
"acknowledgementRequired",
"keyLifetimeDays",
"requestId"
],
"additionalProperties": false
}

Errors: 400 Validation error. A body is read here, so one of five codes rides on this status and none is retryable unchanged. validation_error: the body parsed and failed the schema, and details names each path. invalid_json: there was no body, or it was not valid UTF-8 JSON. invalid_text: a string or property name holds a NUL or an unpaired Unicode surrogate, which storage cannot keep, so correct the source text. duplicate_key: a JSON object repeats a property name, no path is given, and the conflicting fields have to be resolved at the source. invalid_number: a number is outside the range that survives the round trip, so quote large identifiers as strings. Nothing was written in any of them.; 403 Scope or mode does not allow this. On scope_required, details.obtainable says whether a key that carries the scope can be issued at all: true means reissuing this key with the scope would work, false means no key of this kind can ever carry it, because contacts:read and approvals:write are granted on live mode alone and maglev:* belongs to Maglev’s own keys. Stored scopes narrow the grant a mode and organisation already allow, so widening a key never adds one.; 404 Not found; 413 body_too_large: the request body exceeds this operation’s limit, and it was not read to the end. Send less in one request rather than repeating it; 415 content_type: the request did not declare application/json, so no body was read and nothing was written. Set the header and send it again; 429 rate_limit: a limit for this key or route is spent. details.retryAfter is how many whole seconds of the refused window remain, at least 1, and the Retry-After header carries the same number. Wait that long rather than assuming a minute; the windows here range from 60 to 86400 seconds. Nothing was done.; 500 internal_error: the request did not complete and the reason was not one this API anticipates. Whether anything was written is unknown. Read error.message, which carries what is safe to do next, and quote the X-Request-Id header to Maglev; the request id is in the body too. Where an operation takes an Idempotency-Key, repeating the request with the same key is the way to learn the earlier outcome without running the work twice, unless the message says to use a new one.. See recovery guidance.

Confirm that this terminal privately saved its own new key; requires that exact key as Bearer authorization.

Call only after durable local credential persistence. Extends live keys to ninety days or sandbox keys to one day from key creation. Same-request acknowledgement is replayable; it never issues another key. After acknowledgement device polling cannot retrieve the token again. Revocation remains available through DELETE /keys/{id}.

Authentication: bearer key.

Field Type Required Details
deviceCode string Yes Pattern: ^[A-Za-z0-9_-]{43}$
200 response schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"acknowledged": {
"type": "boolean",
"const": true
},
"id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
},
"mode": {
"type": "string",
"enum": [
"sandbox",
"live"
]
},
"expiresAt": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
},
"requestId": {
"type": "string",
"description": "Echoed in the X-Request-Id response header. Quote it to Maglev when asking what became of a request."
}
},
"required": [
"acknowledged",
"id",
"mode",
"expiresAt",
"requestId"
],
"additionalProperties": false
}

Errors: 400 Validation error. A body is read here, so one of five codes rides on this status and none is retryable unchanged. validation_error: the body parsed and failed the schema, and details names each path. invalid_json: there was no body, or it was not valid UTF-8 JSON. invalid_text: a string or property name holds a NUL or an unpaired Unicode surrogate, which storage cannot keep, so correct the source text. duplicate_key: a JSON object repeats a property name, no path is given, and the conflicting fields have to be resolved at the source. invalid_number: a number is outside the range that survives the round trip, so quote large identifiers as strings. Nothing was written in any of them.; 401 unauthorized: no bearer key was presented, or the one presented is unknown, expired or revoked. Present a valid key; nothing was done.; 403 Scope or mode does not allow this. On scope_required, details.obtainable says whether a key that carries the scope can be issued at all: true means reissuing this key with the scope would work, false means no key of this kind can ever carry it, because contacts:read and approvals:write are granted on live mode alone and maglev:* belongs to Maglev’s own keys. Stored scopes narrow the grant a mode and organisation already allow, so widening a key never adds one.; 404 Not found; 413 body_too_large: the request body exceeds this operation’s limit, and it was not read to the end. Send less in one request rather than repeating it; 415 content_type: the request did not declare application/json, so no body was read and nothing was written. Set the header and send it again; 429 rate_limit: a limit for this key or route is spent. details.retryAfter is how many whole seconds of the refused window remain, at least 1, and the Retry-After header carries the same number. Wait that long rather than assuming a minute; the windows here range from 60 to 86400 seconds. Nothing was done.; 500 internal_error: the request did not complete and the reason was not one this API anticipates. Whether anything was written is unknown. Read error.message, which carries what is safe to do next, and quote the X-Request-Id header to Maglev; the request id is in the body too. Where an operation takes an Idempotency-Key, repeating the request with the same key is the way to learn the earlier outcome without running the work twice, unless the message says to use a new one.. See recovery guidance.

Cancel an unacknowledged terminal request using its private deviceCode. Any issued temporary key is revoked atomically.

Does not revoke an acknowledged installation; use the ordinary key revocation endpoint for that. Repeating cancellation is safe.

Authentication: see endpoint notes; no bearer key required.

Field Type Required Details
deviceCode string Yes Pattern: ^[A-Za-z0-9_-]{43}$
200 response schema
{
"type": "object",
"properties": {
"cancelled": {
"const": true
},
"requestId": {
"type": "string",
"description": "Echoed in the X-Request-Id response header. Quote it to Maglev when asking what became of a request."
}
},
"required": [
"cancelled",
"requestId"
]
}

Errors: 400 Validation error. A body is read here, so one of five codes rides on this status and none is retryable unchanged. validation_error: the body parsed and failed the schema, and details names each path. invalid_json: there was no body, or it was not valid UTF-8 JSON. invalid_text: a string or property name holds a NUL or an unpaired Unicode surrogate, which storage cannot keep, so correct the source text. duplicate_key: a JSON object repeats a property name, no path is given, and the conflicting fields have to be resolved at the source. invalid_number: a number is outside the range that survives the round trip, so quote large identifiers as strings. Nothing was written in any of them.; 403 Scope or mode does not allow this. On scope_required, details.obtainable says whether a key that carries the scope can be issued at all: true means reissuing this key with the scope would work, false means no key of this kind can ever carry it, because contacts:read and approvals:write are granted on live mode alone and maglev:* belongs to Maglev’s own keys. Stored scopes narrow the grant a mode and organisation already allow, so widening a key never adds one.; 404 Not found; 413 body_too_large: the request body exceeds this operation’s limit, and it was not read to the end. Send less in one request rather than repeating it; 415 content_type: the request did not declare application/json, so no body was read and nothing was written. Set the header and send it again; 429 rate_limit: a limit for this key or route is spent. details.retryAfter is how many whole seconds of the refused window remain, at least 1, and the Retry-After header carries the same number. Wait that long rather than assuming a minute; the windows here range from 60 to 86400 seconds. Nothing was done.; 500 internal_error: the request did not complete and the reason was not one this API anticipates. Whether anything was written is unknown. Read error.message, which carries what is safe to do next, and quote the X-Request-Id header to Maglev; the request id is in the body too. Where an operation takes an Idempotency-Key, repeating the request with the same key is the way to learn the earlier outcome without running the work twice, unless the message says to use a new one.. See recovery guidance.

Error · KeyInfo