Verify an SSH signature and receive a JWT.
const url = 'https://example.com/v1/auth/verify';const options = { method: 'POST', headers: {'Content-Type': 'application/json'}, body: '{"challengeId":"example","payload":"example","publicKeyFingerprint":"example","signature":"example"}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url https://example.com/v1/auth/verify \ --header 'Content-Type: application/json' \ --data '{ "challengeId": "example", "payload": "example", "publicKeyFingerprint": "example", "signature": "example" }'Authorizations
Section titled “Authorizations”- None
Request Bodyrequired
Section titled “Request Bodyrequired”JSON body for POST /auth/verify.
The server decides from payload and signature alone. The origin, the
purpose, the nonce and, for a claim, the administrator’s name and
publicKey all travel inside the signed payload; there is no unsigned
copy of any of them.
JSON body for POST /auth/verify.
The server decides from payload and signature alone. The origin, the
purpose, the nonce and, for a claim, the administrator’s name and
publicKey all travel inside the signed payload; there is no unsigned
copy of any of them.
object
The challenge ID from the challenge response. Only selects the nonce; the signature binds the choice.
The exact UTF-8 text hf signed, sent verbatim (a JSON object; see
auth_proto::payload). At most 4096 bytes.
Fingerprint of the public key (as stored in the credential). Only selects the stored credential on a login and is ignored on a claim; it is never trusted, because verification against the selected key binds it.
PEM-encoded SSHSIG over payload, under namespace
henceforth-login-v2 or henceforth-claim-v2, which also selects the
login or claim branch.
Examplegenerated
{ "challengeId": "example", "payload": "example", "publicKeyFingerprint": "example", "signature": "example"}Responses
Section titled “ Responses ”Successful POST /auth/verify response: the same token fields as a login,
plus whether this request claimed the instance (#994 D3, #1022).
Successful POST /auth/verify response: the same token fields as a login,
plus whether this request claimed the instance (#994 D3, #1022).
object
true exactly when this very request won the claim latch and created
the first administrator; false for an ordinary login. A claim
signature never yields a login: one that loses the race to an earlier
claim is refused 409 INSTANCE_ALREADY_CLAIMED, and a login signature
never claims. A client that read GET /status before verifying
reports a claim from this field, not from what it saw earlier.
Token lifetime in seconds.
Signed JWT (ES256).
Examplegenerated
{ "claimed": true, "expiresIn": 1, "token": "example"}Failed to parse the request body as JSON
Expected request with Content-Type: application/json
Failed to deserialize the JSON body into the target type
default
Section titled “default”Structured error envelope (SPEC-DATA-0006): {"errors": [{"domain", "scope", "target", "code", "message"}, ...]}. domain (added #950 D9) names the subsystem that produced the entry (data, metadata, apply, git, auth, iam, hfx, or api for a transport-level failure with no domain error behind it) and is always present.
The top-level response body. Wraps a non-empty list of [ErrorEntry].
object
The error entries making up the envelope. Always non-empty.
One entry in the error envelope.
Carries a [Domain] naming the subsystem that produced it, an anchoring
[ErrorScope], an optional target that disambiguates within the
scope, a stable SCREAMING_SNAKE_CASE code, and a human-readable
English message. See SPEC-DATA-0006 for the full envelope contract.
object
Machine-readable, stable, SCREAMING_SNAKE_CASE identifier.
Which subsystem produced this entry. Always serialized.
data::error::DataError – record CRUD, validation, queries.
server::routes::metadata::error::MetadataError – the metadata
write API (module/object/field/screen PUT and POST).
server::apply::ApplyError – the metadata apply/plan pipeline.
server::git::GitError – the git-backed metadata repository.
auth::error::AuthError – JWT and credential authentication.
server::routes::iam::error::IamError – principal and credential
management (IAM handlers still emit Domain::Data for genuine
request-body field validation, which stays DataError).
server::routes::hfx::error::HfxError – HFX parse/emit.
Module packages (.hfmod, #1165): package format, signature and
compatibility refusals, and the install lifecycle that consumes them.
A transport-level ApiError built directly (forbidden,
not_found, internal, service_unavailable), with no
<Domain>Error behind it.
HFX source location for an HFX_PARSE entry.
Emitted as the extra location field on the entry JSON when present.
The line/column are 1-based. The frame is the offending source line
extracted verbatim from the input.
object
1-based column within the line, measured in UTF-16 code units of the prefix so browser-side editors can consume it directly.
The offending source line text, when available.
1-based line number within the source text.
Server-authored English message. Not part of the API contract; may change across releases without notice.
The key of the validation rule that failed, when the code is
RULE_FAILED. None for all other codes. Skipped during
serialization when absent so it does not appear on unrelated
entries (same pattern as location).
Field key for field scope, record id for record scope, null
otherwise. Always serialized, even when null.
Example
{ "errors": [ { "domain": "data", "scope": "field" } ]}