Depot consoleapi
Registers the console routes.
This page is generated from the Depot route table. Every operation carries the permission it needs and the anchor an agent can link to.
POST /api/v1/console/email/change
Section titled “POST /api/v1/console/email/change”POSTpublic facehosteditmspremote
Source: [object Object]
emailChangeRequest mails a confirmation code to an address the caller wants to move their account to.
Request body
Section titled “Request body”Required, sent as application/json.
| Field | Type | Description |
|---|---|---|
newEmailrequired | string |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
202 | application/json, object | Accepted. |
400 | application/problem+json, ProblemDetails | The request body is not acceptable. |
Request samples
Section titled “Request samples”# Sign in first. outpost_console is a session cookie, so send it with every request.curl -X POST 'https://depot.example.com/api/v1/console/email/change' \ -H 'X-CSRF-Token: <csrf-token>' \ -H 'Accept: application/json' \ -H 'Content-Type: application/json' \ -b 'outpost_console=<console-session>' \ --data '{ "newEmail": "string"}'# Sign in first. outpost_console is a session cookie, so send it with every request.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Content-Type' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$body = @'{ "newEmail": "string"}'@
$response = Invoke-RestMethod ` -Method Post ` -Uri 'https://depot.example.com/api/v1/console/email/change' ` -Headers $headers ` -ContentType 'application/json' ` -Body $body
$response | ConvertTo-Json -Depth 10# Sign in first. outpost_console is a session cookie, so send it with every request.
import requests
url = "https://depot.example.com/api/v1/console/email/change"headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json",}cookies = { "outpost_console": "<console-session>",}payload = { "newEmail": "string",}
response = requests.post(url, headers=headers, cookies=cookies, json=payload, timeout=30)response.raise_for_status()print(response.json())// Sign in first. outpost_console is a session cookie, so send it with every request.package main
import ( "bytes" "fmt" "io" "log" "net/http")
func main() { body := []byte(`{ "newEmail": "string"}`)
req, err := http.NewRequest(http.MethodPost, "https://depot.example.com/api/v1/console/email/change", bytes.NewReader(body)) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.Header.Set("Content-Type", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// Sign in first. outpost_console is a session cookie, so send it with every request.// The session cookie is set by signing in. A script cannot set it itself.
const url = "https://depot.example.com/api/v1/console/email/change";
const response = await fetch(url, { method: "POST", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json", }, credentials: "include", body: JSON.stringify({ "newEmail": "string" }),});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// Sign in first. outpost_console is a session cookie, so send it with every request.
using System;using System.Net.Http;using System.Text;
using var client = new HttpClient();
const string body = """{ "newEmail": "string"}""";
var request = new HttpRequestMessage(HttpMethod.Post, "https://depot.example.com/api/v1/console/email/change");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");request.Content = new StringContent(body, Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());POST /api/v1/console/email/confirm
Section titled “POST /api/v1/console/email/confirm”POSTpublic facehosteditmspremote
Source: [object Object]
emailChangeConfirm spends the code and moves the account.
THE SESSIONS ARE KEPT
Section titled “THE SESSIONS ARE KEPT”Unlike a password change, nothing here is revoked. An email address is not a credential: knowing it does not sign anybody in, and the person who just proved control of the new mailbox is the person holding this session. Ending their sessions would sign them out of the console in the middle of a settings page for no gain, and it would not inconvenience an attacker who by definition still holds the password.
Request body
Section titled “Request body”Required, sent as application/json.
| Field | Type | Description |
|---|---|---|
coderequired | string |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
204 | None | Success, with no body. |
400 | application/problem+json, ProblemDetails | The request body is not acceptable. |
Request samples
Section titled “Request samples”# Sign in first. outpost_console is a session cookie, so send it with every request.curl -X POST 'https://depot.example.com/api/v1/console/email/confirm' \ -H 'X-CSRF-Token: <csrf-token>' \ -H 'Accept: application/json' \ -H 'Content-Type: application/json' \ -b 'outpost_console=<console-session>' \ --data '{ "code": "string"}'# Sign in first. outpost_console is a session cookie, so send it with every request.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Content-Type' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$body = @'{ "code": "string"}'@
$response = Invoke-RestMethod ` -Method Post ` -Uri 'https://depot.example.com/api/v1/console/email/confirm' ` -Headers $headers ` -ContentType 'application/json' ` -Body $body
$response | ConvertTo-Json -Depth 10# Sign in first. outpost_console is a session cookie, so send it with every request.
import requests
url = "https://depot.example.com/api/v1/console/email/confirm"headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json",}cookies = { "outpost_console": "<console-session>",}payload = { "code": "string",}
response = requests.post(url, headers=headers, cookies=cookies, json=payload, timeout=30)response.raise_for_status()print(response.json())// Sign in first. outpost_console is a session cookie, so send it with every request.package main
import ( "bytes" "fmt" "io" "log" "net/http")
func main() { body := []byte(`{ "code": "string"}`)
req, err := http.NewRequest(http.MethodPost, "https://depot.example.com/api/v1/console/email/confirm", bytes.NewReader(body)) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.Header.Set("Content-Type", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// Sign in first. outpost_console is a session cookie, so send it with every request.// The session cookie is set by signing in. A script cannot set it itself.
const url = "https://depot.example.com/api/v1/console/email/confirm";
const response = await fetch(url, { method: "POST", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json", }, credentials: "include", body: JSON.stringify({ "code": "string" }),});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// Sign in first. outpost_console is a session cookie, so send it with every request.
using System;using System.Net.Http;using System.Text;
using var client = new HttpClient();
const string body = """{ "code": "string"}""";
var request = new HttpRequestMessage(HttpMethod.Post, "https://depot.example.com/api/v1/console/email/confirm");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");request.Content = new StringContent(body, Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());POST /api/v1/console/handoff
Section titled “POST /api/v1/console/handoff”POSTNo permission neededpublic facehosteditmspremote
Source: [object Object]
handoff exchanges a provisioning handoff token for a console session.
This is what lets a customer who just finished signup land in their own dashboard without signing in to something they have not set a password for yet. It is a DepotInvite row underneath, with a five-minute life instead of a week: single use, stored only as a hash, and scoped to the tenant whose database it was written to, so the tenant router has already decided which account a token can possibly refer to before this handler runs.
The account arrives “invited” and is activated here. That is the whole reason this is not just acceptInvite: the session middleware rejects a non-active user, so a handoff that left the status alone would mint a session that the very next request throws away. Activating without a credential is deliberate and safe in both directions - password login needs a stored hash and finds none, and the first-run wizard is where one gets set.
Request body
Section titled “Request body”Required, sent as application/json.
| Field | Type | Description |
|---|---|---|
tokenrequired | string |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/json, object | Success. |
400 | application/problem+json, ProblemDetails | The request body is not acceptable. |
Request samples
Section titled “Request samples”# This operation is public. It takes no credentials.curl -X POST 'https://depot.example.com/api/v1/console/handoff' \ -H 'Accept: application/json' \ -H 'Content-Type: application/json' \ --data '{ "token": "string"}'# This operation is public. It takes no credentials.
$headers = @{ 'Accept' = 'application/json' 'Content-Type' = 'application/json'}
$body = @'{ "token": "string"}'@
$response = Invoke-RestMethod ` -Method Post ` -Uri 'https://depot.example.com/api/v1/console/handoff' ` -Headers $headers ` -ContentType 'application/json' ` -Body $body
$response | ConvertTo-Json -Depth 10# This operation is public. It takes no credentials.
import requests
url = "https://depot.example.com/api/v1/console/handoff"headers = { "Accept": "application/json", "Content-Type": "application/json",}payload = { "token": "string",}
response = requests.post(url, headers=headers, json=payload, timeout=30)response.raise_for_status()print(response.json())// This operation is public. It takes no credentials.package main
import ( "bytes" "fmt" "io" "log" "net/http")
func main() { body := []byte(`{ "token": "string"}`)
req, err := http.NewRequest(http.MethodPost, "https://depot.example.com/api/v1/console/handoff", bytes.NewReader(body)) if err != nil { log.Fatal(err) }
req.Header.Set("Accept", "application/json") req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// This operation is public. It takes no credentials.
const url = "https://depot.example.com/api/v1/console/handoff";
const response = await fetch(url, { method: "POST", headers: { "Accept": "application/json", "Content-Type": "application/json", }, body: JSON.stringify({ "token": "string" }),});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// This operation is public. It takes no credentials.
using System;using System.Net.Http;using System.Text;
using var client = new HttpClient();
const string body = """{ "token": "string"}""";
var request = new HttpRequestMessage(HttpMethod.Post, "https://depot.example.com/api/v1/console/handoff");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Content = new StringContent(body, Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());POST /api/v1/console/login
Section titled “POST /api/v1/console/login”POSTNo permission neededpublic facehosteditmspremote
Source: [object Object]
login verifies a password and mints a session.
Every failure returns the same 401 with the same body. Not for tidiness: distinguishing “no such account” from “wrong password” hands an attacker a membership oracle for the customer’s staff list, and distinguishing “locked” tells them their guessing is working.
Request body
Section titled “Request body”Required, sent as application/json.
| Field | Type | Description |
|---|---|---|
emailrequired | string | |
passwordrequired | string |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/json, object | Success. |
400 | application/problem+json, ProblemDetails | The request body is not acceptable. |
Request samples
Section titled “Request samples”# This operation is public. It takes no credentials.curl -X POST 'https://depot.example.com/api/v1/console/login' \ -H 'Accept: application/json' \ -H 'Content-Type: application/json' \ --data '{ "email": "string", "password": "string"}'# This operation is public. It takes no credentials.
$headers = @{ 'Accept' = 'application/json' 'Content-Type' = 'application/json'}
$body = @'{ "email": "string", "password": "string"}'@
$response = Invoke-RestMethod ` -Method Post ` -Uri 'https://depot.example.com/api/v1/console/login' ` -Headers $headers ` -ContentType 'application/json' ` -Body $body
$response | ConvertTo-Json -Depth 10# This operation is public. It takes no credentials.
import requests
url = "https://depot.example.com/api/v1/console/login"headers = { "Accept": "application/json", "Content-Type": "application/json",}payload = { "email": "string", "password": "string",}
response = requests.post(url, headers=headers, json=payload, timeout=30)response.raise_for_status()print(response.json())// This operation is public. It takes no credentials.package main
import ( "bytes" "fmt" "io" "log" "net/http")
func main() { body := []byte(`{ "email": "string", "password": "string"}`)
req, err := http.NewRequest(http.MethodPost, "https://depot.example.com/api/v1/console/login", bytes.NewReader(body)) if err != nil { log.Fatal(err) }
req.Header.Set("Accept", "application/json") req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// This operation is public. It takes no credentials.
const url = "https://depot.example.com/api/v1/console/login";
const response = await fetch(url, { method: "POST", headers: { "Accept": "application/json", "Content-Type": "application/json", }, body: JSON.stringify({ "email": "string", "password": "string" }),});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// This operation is public. It takes no credentials.
using System;using System.Net.Http;using System.Text;
using var client = new HttpClient();
const string body = """{ "email": "string", "password": "string"}""";
var request = new HttpRequestMessage(HttpMethod.Post, "https://depot.example.com/api/v1/console/login");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Content = new StringContent(body, Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());POST /api/v1/console/login/break-glass
Section titled “POST /api/v1/console/login/break-glass”POSTNo permission neededpublic facehosteditmspremote
Source: [object Object]
Break glass
Request body
Section titled “Request body”Required, sent as application/json.
| Field | Type | Description |
|---|---|---|
coderequired | string | |
emailrequired | string | |
passwordrequired | string | |
recoveryCoderequired | string |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/json, MeResponse | Success. |
400 | application/problem+json, ProblemDetails | The request body is not acceptable. |
200 response body
| Field | Type | Description |
|---|---|---|
breakGlassrequired | boolean | |
breakGlassExpiresAt | string (date-time) or null | |
displayNamerequired | string | |
emailrequired | string | |
hasPasswordrequired | boolean | |
mfaEnrolmentDeadline | string (date-time) or null | |
passwordChangeReason | string or null | |
permissionsrequired | array of string | |
phonerequired | string | |
phoneVerifiedrequired | boolean | |
pioneerAutoApproverequired | boolean | |
publicIdrequired | string | |
requiredAction | string or null | |
rolesrequired | array of string |
Request samples
Section titled “Request samples”# This operation is public. It takes no credentials.curl -X POST 'https://depot.example.com/api/v1/console/login/break-glass' \ -H 'Accept: application/json' \ -H 'Content-Type: application/json' \ --data '{ "code": "string", "email": "string", "password": "string", "recoveryCode": "string"}'# This operation is public. It takes no credentials.
$headers = @{ 'Accept' = 'application/json' 'Content-Type' = 'application/json'}
$body = @'{ "code": "string", "email": "string", "password": "string", "recoveryCode": "string"}'@
$response = Invoke-RestMethod ` -Method Post ` -Uri 'https://depot.example.com/api/v1/console/login/break-glass' ` -Headers $headers ` -ContentType 'application/json' ` -Body $body
$response | ConvertTo-Json -Depth 10# This operation is public. It takes no credentials.
import requests
url = "https://depot.example.com/api/v1/console/login/break-glass"headers = { "Accept": "application/json", "Content-Type": "application/json",}payload = { "code": "string", "email": "string", "password": "string", "recoveryCode": "string",}
response = requests.post(url, headers=headers, json=payload, timeout=30)response.raise_for_status()print(response.json())// This operation is public. It takes no credentials.package main
import ( "bytes" "fmt" "io" "log" "net/http")
func main() { body := []byte(`{ "code": "string", "email": "string", "password": "string", "recoveryCode": "string"}`)
req, err := http.NewRequest(http.MethodPost, "https://depot.example.com/api/v1/console/login/break-glass", bytes.NewReader(body)) if err != nil { log.Fatal(err) }
req.Header.Set("Accept", "application/json") req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// This operation is public. It takes no credentials.
const url = "https://depot.example.com/api/v1/console/login/break-glass";
const response = await fetch(url, { method: "POST", headers: { "Accept": "application/json", "Content-Type": "application/json", }, body: JSON.stringify({ "code": "string", "email": "string", "password": "string", "recoveryCode": "string" }),});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// This operation is public. It takes no credentials.
using System;using System.Net.Http;using System.Text;
using var client = new HttpClient();
const string body = """{ "code": "string", "email": "string", "password": "string", "recoveryCode": "string"}""";
var request = new HttpRequestMessage(HttpMethod.Post, "https://depot.example.com/api/v1/console/login/break-glass");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Content = new StringContent(body, Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());POST /api/v1/console/login/code
Section titled “POST /api/v1/console/login/code”POSTNo permission neededpublic facehosteditmspremote
Source: [object Object]
loginCodeSend mints and delivers a one-time code, and ALWAYS answers 202 with the same body.
THE SAME ANSWER FOR EVERY OUTCOME
Section titled “THE SAME ANSWER FOR EVERY OUTCOME”Unknown address, disabled account, locked-out account, inside the resend cooldown, over the daily send cap, and a code genuinely on its way all produce byte-identical 202s. Any distinction is a membership oracle for the customer’s staff list, available to anybody who can reach the login page.
WHY DELIVERY IS DISPATCHED TO A GOROUTINE
Section titled “WHY DELIVERY IS DISPATCHED TO A GOROUTINE”This is not about latency. Sending inline would make a real address answer in however long SMTP takes - hundreds of milliseconds to seconds - and an unknown address answer instantly, which hands straight back the oracle the identical body was protecting. A timing difference is a difference. The goroutine takes a BACKGROUND context with its own timeout rather than the request’s, because the request context is cancelled the moment this handler returns, which is immediately.
The cost is that a delivery in flight when the process stops is lost. That is acceptable: the user’s remedy is to press the button again, the cooldown is sixty seconds, and the alternative is holding a request open long enough to be timed.
Request body
Section titled “Request body”Required, sent as application/json.
| Field | Type | Description |
|---|---|---|
emailrequired | string |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
202 | application/json, object | Accepted. |
400 | application/problem+json, ProblemDetails | The request body is not acceptable. |
Request samples
Section titled “Request samples”# This operation is public. It takes no credentials.curl -X POST 'https://depot.example.com/api/v1/console/login/code' \ -H 'Accept: application/json' \ -H 'Content-Type: application/json' \ --data '{ "email": "string"}'# This operation is public. It takes no credentials.
$headers = @{ 'Accept' = 'application/json' 'Content-Type' = 'application/json'}
$body = @'{ "email": "string"}'@
$response = Invoke-RestMethod ` -Method Post ` -Uri 'https://depot.example.com/api/v1/console/login/code' ` -Headers $headers ` -ContentType 'application/json' ` -Body $body
$response | ConvertTo-Json -Depth 10# This operation is public. It takes no credentials.
import requests
url = "https://depot.example.com/api/v1/console/login/code"headers = { "Accept": "application/json", "Content-Type": "application/json",}payload = { "email": "string",}
response = requests.post(url, headers=headers, json=payload, timeout=30)response.raise_for_status()print(response.json())// This operation is public. It takes no credentials.package main
import ( "bytes" "fmt" "io" "log" "net/http")
func main() { body := []byte(`{ "email": "string"}`)
req, err := http.NewRequest(http.MethodPost, "https://depot.example.com/api/v1/console/login/code", bytes.NewReader(body)) if err != nil { log.Fatal(err) }
req.Header.Set("Accept", "application/json") req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// This operation is public. It takes no credentials.
const url = "https://depot.example.com/api/v1/console/login/code";
const response = await fetch(url, { method: "POST", headers: { "Accept": "application/json", "Content-Type": "application/json", }, body: JSON.stringify({ "email": "string" }),});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// This operation is public. It takes no credentials.
using System;using System.Net.Http;using System.Text;
using var client = new HttpClient();
const string body = """{ "email": "string"}""";
var request = new HttpRequestMessage(HttpMethod.Post, "https://depot.example.com/api/v1/console/login/code");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Content = new StringContent(body, Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());POST /api/v1/console/login/code/verify
Section titled “POST /api/v1/console/login/code/verify”POSTNo permission neededpublic facehosteditmspremote
Source: [object Object]
loginCodeVerify spends a code and signs the account in.
Every failure is ONE identical 401: unknown address, disabled account, locked out, no live code, expired code, exhausted attempts, and simply the wrong digits. Distinguishing any of them tells an attacker which addresses exist or how close they are.
A CODE SIGN-IN DOES NOT SATISFY TOTP
Section titled “A CODE SIGN-IN DOES NOT SATISFY TOTP”It goes through startSession like every other door, and startSession challenges a confirmed enrolment. That is not a courtesy: the code proves control of a mailbox, and an attacker who has the mailbox is exactly the attacker the second factor is for. TestCodeLoginStillRequiresTotpWhenEnrolled pins it, and TestEverySignInPathGoesThroughStartSession pins the mechanism.
Request body
Section titled “Request body”Required, sent as application/json.
| Field | Type | Description |
|---|---|---|
coderequired | string | |
emailrequired | string |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/json, object | Success. |
400 | application/problem+json, ProblemDetails | The request body is not acceptable. |
Request samples
Section titled “Request samples”# This operation is public. It takes no credentials.curl -X POST 'https://depot.example.com/api/v1/console/login/code/verify' \ -H 'Accept: application/json' \ -H 'Content-Type: application/json' \ --data '{ "code": "string", "email": "string"}'# This operation is public. It takes no credentials.
$headers = @{ 'Accept' = 'application/json' 'Content-Type' = 'application/json'}
$body = @'{ "code": "string", "email": "string"}'@
$response = Invoke-RestMethod ` -Method Post ` -Uri 'https://depot.example.com/api/v1/console/login/code/verify' ` -Headers $headers ` -ContentType 'application/json' ` -Body $body
$response | ConvertTo-Json -Depth 10# This operation is public. It takes no credentials.
import requests
url = "https://depot.example.com/api/v1/console/login/code/verify"headers = { "Accept": "application/json", "Content-Type": "application/json",}payload = { "code": "string", "email": "string",}
response = requests.post(url, headers=headers, json=payload, timeout=30)response.raise_for_status()print(response.json())// This operation is public. It takes no credentials.package main
import ( "bytes" "fmt" "io" "log" "net/http")
func main() { body := []byte(`{ "code": "string", "email": "string"}`)
req, err := http.NewRequest(http.MethodPost, "https://depot.example.com/api/v1/console/login/code/verify", bytes.NewReader(body)) if err != nil { log.Fatal(err) }
req.Header.Set("Accept", "application/json") req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// This operation is public. It takes no credentials.
const url = "https://depot.example.com/api/v1/console/login/code/verify";
const response = await fetch(url, { method: "POST", headers: { "Accept": "application/json", "Content-Type": "application/json", }, body: JSON.stringify({ "code": "string", "email": "string" }),});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// This operation is public. It takes no credentials.
using System;using System.Net.Http;using System.Text;
using var client = new HttpClient();
const string body = """{ "code": "string", "email": "string"}""";
var request = new HttpRequestMessage(HttpMethod.Post, "https://depot.example.com/api/v1/console/login/code/verify");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Content = new StringContent(body, Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());GET /api/v1/console/login/passkey
Section titled “GET /api/v1/console/login/passkey”GETNo permission neededpublic facehosteditmspremoteIdempotent
Source: [object Object]
passwordlessAvailable answers the sign-in page’s one question: may it offer “Sign in with a passkey” here? A fact about the organisation and the host, identical for every caller, so it says nothing about any account.
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/json, object | Success. |
Request samples
Section titled “Request samples”# This operation is public. It takes no credentials.# This operation is idempotent. Repeating it does not repeat its effect.curl -X GET 'https://depot.example.com/api/v1/console/login/passkey' \ -H 'Accept: application/json'# This operation is public. It takes no credentials.# This operation is idempotent. Repeating it does not repeat its effect.
$headers = @{ 'Accept' = 'application/json'}
$response = Invoke-RestMethod ` -Method Get ` -Uri 'https://depot.example.com/api/v1/console/login/passkey' ` -Headers $headers
$response | ConvertTo-Json -Depth 10# This operation is public. It takes no credentials.# This operation is idempotent. Repeating it does not repeat its effect.
import requests
url = "https://depot.example.com/api/v1/console/login/passkey"headers = { "Accept": "application/json",}
response = requests.get(url, headers=headers, timeout=30)response.raise_for_status()print(response.json())// This operation is public. It takes no credentials.// This operation is idempotent. Repeating it does not repeat its effect.package main
import ( "fmt" "io" "log" "net/http")
func main() { req, err := http.NewRequest(http.MethodGet, "https://depot.example.com/api/v1/console/login/passkey", nil) if err != nil { log.Fatal(err) }
req.Header.Set("Accept", "application/json")
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// This operation is public. It takes no credentials.// This operation is idempotent. Repeating it does not repeat its effect.
const url = "https://depot.example.com/api/v1/console/login/passkey";
const response = await fetch(url, { method: "GET", headers: { "Accept": "application/json", },});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// This operation is public. It takes no credentials.// This operation is idempotent. Repeating it does not repeat its effect.
using System;using System.Net.Http;
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://depot.example.com/api/v1/console/login/passkey");request.Headers.TryAddWithoutValidation("Accept", "application/json");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());POST /api/v1/console/login/passkey/begin
Section titled “POST /api/v1/console/login/passkey/begin”POSTNo permission neededpublic facehosteditmspremote
Source: [object Object]
Begin passwordless
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/json, CeremonyResponse | Success. |
200 response body
| Field | Type | Description |
|---|---|---|
ceremonyIdrequired | string | |
publicKeyrequired | any |
Request samples
Section titled “Request samples”# This operation is public. It takes no credentials.curl -X POST 'https://depot.example.com/api/v1/console/login/passkey/begin' \ -H 'Accept: application/json'# This operation is public. It takes no credentials.
$headers = @{ 'Accept' = 'application/json'}
$response = Invoke-RestMethod ` -Method Post ` -Uri 'https://depot.example.com/api/v1/console/login/passkey/begin' ` -Headers $headers
$response | ConvertTo-Json -Depth 10# This operation is public. It takes no credentials.
import requests
url = "https://depot.example.com/api/v1/console/login/passkey/begin"headers = { "Accept": "application/json",}
response = requests.post(url, headers=headers, timeout=30)response.raise_for_status()print(response.json())// This operation is public. It takes no credentials.package main
import ( "fmt" "io" "log" "net/http")
func main() { req, err := http.NewRequest(http.MethodPost, "https://depot.example.com/api/v1/console/login/passkey/begin", nil) if err != nil { log.Fatal(err) }
req.Header.Set("Accept", "application/json")
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// This operation is public. It takes no credentials.
const url = "https://depot.example.com/api/v1/console/login/passkey/begin";
const response = await fetch(url, { method: "POST", headers: { "Accept": "application/json", },});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// This operation is public. It takes no credentials.
using System;using System.Net.Http;
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://depot.example.com/api/v1/console/login/passkey/begin");request.Headers.TryAddWithoutValidation("Accept", "application/json");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());POST /api/v1/console/login/passkey/finish
Section titled “POST /api/v1/console/login/passkey/finish”POSTNo permission neededpublic facehosteditmspremote
Source: [object Object]
finishPasswordless is the passwordless door.
It is a sign-in like login and it keeps every rule login keeps, in the same order and for the same reasons: one answer for every failure before the account is proven, the account’s status and lockout, the network and hour rules after the credential has verified, the failure counter on a bad assertion against a known account, and startSession as the only way a session is minted.
WHAT IT DOES NOT ASK FOR
Section titled “WHAT IT DOES NOT ASK FOR”No second factor. The passkey was verified with user verification required (a PIN or a biometric on the authenticator), so it is possession and knowledge or inherence at once; asking for TOTP on top would make the door pointless and is not what the organisation turned on.
No password composition check. That needs the plaintext, which this door never sees, and a password that no longer meets the policy is not in use here; it is caught at the password door the next time it is typed, which is where it can do harm. AN EXPIRED PASSWORD IS STILL ENFORCED, exactly as the code and handoff doors enforce it: the password is still a working credential for this account, and its expiry is about the credential, not the door.
Request body
Section titled “Request body”Required, sent as application/json.
| Field | Type | Description |
|---|---|---|
ceremonyIdrequired | string | |
credentialrequired | any | Arbitrary JSON. |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/json, MeResponse | Success. |
400 | application/problem+json, ProblemDetails | The request body is not acceptable. |
200 response body
| Field | Type | Description |
|---|---|---|
breakGlassrequired | boolean | |
breakGlassExpiresAt | string (date-time) or null | |
displayNamerequired | string | |
emailrequired | string | |
hasPasswordrequired | boolean | |
mfaEnrolmentDeadline | string (date-time) or null | |
passwordChangeReason | string or null | |
permissionsrequired | array of string | |
phonerequired | string | |
phoneVerifiedrequired | boolean | |
pioneerAutoApproverequired | boolean | |
publicIdrequired | string | |
requiredAction | string or null | |
rolesrequired | array of string |
Request samples
Section titled “Request samples”# This operation is public. It takes no credentials.curl -X POST 'https://depot.example.com/api/v1/console/login/passkey/finish' \ -H 'Accept: application/json' \ -H 'Content-Type: application/json' \ --data '{ "ceremonyId": "string", "credential": "string"}'# This operation is public. It takes no credentials.
$headers = @{ 'Accept' = 'application/json' 'Content-Type' = 'application/json'}
$body = @'{ "ceremonyId": "string", "credential": "string"}'@
$response = Invoke-RestMethod ` -Method Post ` -Uri 'https://depot.example.com/api/v1/console/login/passkey/finish' ` -Headers $headers ` -ContentType 'application/json' ` -Body $body
$response | ConvertTo-Json -Depth 10# This operation is public. It takes no credentials.
import requests
url = "https://depot.example.com/api/v1/console/login/passkey/finish"headers = { "Accept": "application/json", "Content-Type": "application/json",}payload = { "ceremonyId": "string", "credential": "string",}
response = requests.post(url, headers=headers, json=payload, timeout=30)response.raise_for_status()print(response.json())// This operation is public. It takes no credentials.package main
import ( "bytes" "fmt" "io" "log" "net/http")
func main() { body := []byte(`{ "ceremonyId": "string", "credential": "string"}`)
req, err := http.NewRequest(http.MethodPost, "https://depot.example.com/api/v1/console/login/passkey/finish", bytes.NewReader(body)) if err != nil { log.Fatal(err) }
req.Header.Set("Accept", "application/json") req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// This operation is public. It takes no credentials.
const url = "https://depot.example.com/api/v1/console/login/passkey/finish";
const response = await fetch(url, { method: "POST", headers: { "Accept": "application/json", "Content-Type": "application/json", }, body: JSON.stringify({ "ceremonyId": "string", "credential": "string" }),});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// This operation is public. It takes no credentials.
using System;using System.Net.Http;using System.Text;
using var client = new HttpClient();
const string body = """{ "ceremonyId": "string", "credential": "string"}""";
var request = new HttpRequestMessage(HttpMethod.Post, "https://depot.example.com/api/v1/console/login/passkey/finish");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Content = new StringContent(body, Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());POST /api/v1/console/login/start
Section titled “POST /api/v1/console/login/start”POSTNo permission neededpublic facehosteditmspremote
Source: [object Object]
loginStart reports which sign-in methods this Depot offers.
IT MUST NEVER TOUCH THE STORE, and TestLoginStartNeverTouchesTheStore fails the day it does.
WHY THE ANSWER IGNORES THE ADDRESS
Section titled “WHY THE ANSWER IGNORES THE ADDRESS”The obvious design looks the address up and answers [“password”] when it has one and [“code”] when it does not. That is what several large providers do, and it leaks exactly one bit: whether an address has a password. The bit sounds harmless and is not, because the case that matters here is the account with NO credential - the abandoned first-run owner - and that account would be the one answer that is distinguishable from “never heard of this address”.
Worse, a per-address answer is differentiable by TIMING however carefully the branches are written: one path runs a blind-index lookup and the other does not. A response computed from process configuration cannot leak what it never read, and that is the entire argument. The cost is that an account with no password is shown a password field once, which is a moment of confusion against a permanent oracle.
WHY THE EMAIL IS STILL PARSED
Section titled “WHY THE EMAIL IS STILL PARSED”SSO will route on the DOMAIN, and a domain is a per-domain fact, not a per-account one: “everybody at acme.example signs in with Okta” tells an attacker nothing about whether any particular person has an account. So the field is accepted, sanity-checked, and today ignored. Rejecting a value that is not an address keeps the SPA honest about what it is sending.
Request body
Section titled “Request body”Required, sent as application/json.
| Field | Type | Description |
|---|---|---|
emailrequired | string |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/json, LoginStartResponse | Success. |
400 | application/problem+json, ProblemDetails | The request body is not acceptable. |
200 response body
| Field | Type | Description |
|---|---|---|
methodsrequired | array of string |
Request samples
Section titled “Request samples”# This operation is public. It takes no credentials.curl -X POST 'https://depot.example.com/api/v1/console/login/start' \ -H 'Accept: application/json' \ -H 'Content-Type: application/json' \ --data '{ "email": "string"}'# This operation is public. It takes no credentials.
$headers = @{ 'Accept' = 'application/json' 'Content-Type' = 'application/json'}
$body = @'{ "email": "string"}'@
$response = Invoke-RestMethod ` -Method Post ` -Uri 'https://depot.example.com/api/v1/console/login/start' ` -Headers $headers ` -ContentType 'application/json' ` -Body $body
$response | ConvertTo-Json -Depth 10# This operation is public. It takes no credentials.
import requests
url = "https://depot.example.com/api/v1/console/login/start"headers = { "Accept": "application/json", "Content-Type": "application/json",}payload = { "email": "string",}
response = requests.post(url, headers=headers, json=payload, timeout=30)response.raise_for_status()print(response.json())// This operation is public. It takes no credentials.package main
import ( "bytes" "fmt" "io" "log" "net/http")
func main() { body := []byte(`{ "email": "string"}`)
req, err := http.NewRequest(http.MethodPost, "https://depot.example.com/api/v1/console/login/start", bytes.NewReader(body)) if err != nil { log.Fatal(err) }
req.Header.Set("Accept", "application/json") req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// This operation is public. It takes no credentials.
const url = "https://depot.example.com/api/v1/console/login/start";
const response = await fetch(url, { method: "POST", headers: { "Accept": "application/json", "Content-Type": "application/json", }, body: JSON.stringify({ "email": "string" }),});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// This operation is public. It takes no credentials.
using System;using System.Net.Http;using System.Text;
using var client = new HttpClient();
const string body = """{ "email": "string"}""";
var request = new HttpRequestMessage(HttpMethod.Post, "https://depot.example.com/api/v1/console/login/start");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Content = new StringContent(body, Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());POST /api/v1/console/logout
Section titled “POST /api/v1/console/logout”POSTNo permission neededpublic facehosteditmspremote
Source: [object Object]
logout revokes the current session.
It is idempotent and always answers 204: a client signing out of a session that is already gone has got what it wanted.
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
204 | None | Success, with no body. |
Request samples
Section titled “Request samples”# This operation is public. It takes no credentials.curl -X POST 'https://depot.example.com/api/v1/console/logout'# This operation is public. It takes no credentials.
$response = Invoke-RestMethod ` -Method Post ` -Uri 'https://depot.example.com/api/v1/console/logout'
$response | ConvertTo-Json -Depth 10# This operation is public. It takes no credentials.
import requests
url = "https://depot.example.com/api/v1/console/logout"
response = requests.post(url, timeout=30)response.raise_for_status()print(response.json())// This operation is public. It takes no credentials.package main
import ( "fmt" "io" "log" "net/http")
func main() { req, err := http.NewRequest(http.MethodPost, "https://depot.example.com/api/v1/console/logout", nil) if err != nil { log.Fatal(err) }
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// This operation is public. It takes no credentials.
const url = "https://depot.example.com/api/v1/console/logout";
const response = await fetch(url, { method: "POST",});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// This operation is public. It takes no credentials.
using System;using System.Net.Http;
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://depot.example.com/api/v1/console/logout");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());GET /api/v1/console/me
Section titled “GET /api/v1/console/me”GETpublic facehosteditmspremoteIdempotent
Source: [object Object]
me reports the signed-in user, their roles, and their effective permissions, which is what the SPA needs to decide what to render.
NO CLIENT DATABASE ON THE CONTEXT IS A 404, NOT A 401, and me is the only route in this package that draws that distinction. The SPA’s consoleAuthGuard reads the two apart: a 404 means this face mounts no sign-in surface and the route is admitted, a 401 means signed out and the browser is redirected to /login. A face that has no client database has no account to sign in to either, so answering 401 would park it on a login form whose every POST is refused for the same reason - a dead loop with nothing behind it. That is the pre-bootstrap MSP face, which has no self client yet, and a dev run against a control database where nobody has marked one.
requireUser and signedInUser deliberately keep their 401 for this case. Only me is read as a statement about the FACE rather than about the caller.
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/json, MeResponse | Success. |
200 response body
| Field | Type | Description |
|---|---|---|
breakGlassrequired | boolean | |
breakGlassExpiresAt | string (date-time) or null | |
displayNamerequired | string | |
emailrequired | string | |
hasPasswordrequired | boolean | |
mfaEnrolmentDeadline | string (date-time) or null | |
passwordChangeReason | string or null | |
permissionsrequired | array of string | |
phonerequired | string | |
phoneVerifiedrequired | boolean | |
pioneerAutoApproverequired | boolean | |
publicIdrequired | string | |
requiredAction | string or null | |
rolesrequired | array of string |
Request samples
Section titled “Request samples”# Sign in first. outpost_console is a session cookie, so send it with every request.# This operation is idempotent. Repeating it does not repeat its effect.curl -X GET 'https://depot.example.com/api/v1/console/me' \ -H 'X-CSRF-Token: <csrf-token>' \ -H 'Accept: application/json' \ -b 'outpost_console=<console-session>'# Sign in first. outpost_console is a session cookie, so send it with every request.# This operation is idempotent. Repeating it does not repeat its effect.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$response = Invoke-RestMethod ` -Method Get ` -Uri 'https://depot.example.com/api/v1/console/me' ` -Headers $headers
$response | ConvertTo-Json -Depth 10# Sign in first. outpost_console is a session cookie, so send it with every request.# This operation is idempotent. Repeating it does not repeat its effect.
import requests
url = "https://depot.example.com/api/v1/console/me"headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json",}cookies = { "outpost_console": "<console-session>",}
response = requests.get(url, headers=headers, cookies=cookies, timeout=30)response.raise_for_status()print(response.json())// Sign in first. outpost_console is a session cookie, so send it with every request.// This operation is idempotent. Repeating it does not repeat its effect.package main
import ( "fmt" "io" "log" "net/http")
func main() { req, err := http.NewRequest(http.MethodGet, "https://depot.example.com/api/v1/console/me", nil) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// Sign in first. outpost_console is a session cookie, so send it with every request.// This operation is idempotent. Repeating it does not repeat its effect.// The session cookie is set by signing in. A script cannot set it itself.
const url = "https://depot.example.com/api/v1/console/me";
const response = await fetch(url, { method: "GET", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", }, credentials: "include",});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// Sign in first. outpost_console is a session cookie, so send it with every request.// This operation is idempotent. Repeating it does not repeat its effect.
using System;using System.Net.Http;
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://depot.example.com/api/v1/console/me");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());PATCH /api/v1/console/me
Section titled “PATCH /api/v1/console/me”PATCHpublic facehosteditmspremote
Source: [object Object]
Update me
Request body
Section titled “Request body”Required, sent as application/json.
| Field | Type | Description |
|---|---|---|
displayNamerequired | string |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
204 | None | Success, with no body. |
400 | application/problem+json, ProblemDetails | The request body is not acceptable. |
Request samples
Section titled “Request samples”# Sign in first. outpost_console is a session cookie, so send it with every request.curl -X PATCH 'https://depot.example.com/api/v1/console/me' \ -H 'X-CSRF-Token: <csrf-token>' \ -H 'Accept: application/json' \ -H 'Content-Type: application/json' \ -b 'outpost_console=<console-session>' \ --data '{ "displayName": "string"}'# Sign in first. outpost_console is a session cookie, so send it with every request.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Content-Type' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$body = @'{ "displayName": "string"}'@
$response = Invoke-RestMethod ` -Method Patch ` -Uri 'https://depot.example.com/api/v1/console/me' ` -Headers $headers ` -ContentType 'application/json' ` -Body $body
$response | ConvertTo-Json -Depth 10# Sign in first. outpost_console is a session cookie, so send it with every request.
import requests
url = "https://depot.example.com/api/v1/console/me"headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json",}cookies = { "outpost_console": "<console-session>",}payload = { "displayName": "string",}
response = requests.patch(url, headers=headers, cookies=cookies, json=payload, timeout=30)response.raise_for_status()print(response.json())// Sign in first. outpost_console is a session cookie, so send it with every request.package main
import ( "bytes" "fmt" "io" "log" "net/http")
func main() { body := []byte(`{ "displayName": "string"}`)
req, err := http.NewRequest(http.MethodPatch, "https://depot.example.com/api/v1/console/me", bytes.NewReader(body)) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.Header.Set("Content-Type", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// Sign in first. outpost_console is a session cookie, so send it with every request.// The session cookie is set by signing in. A script cannot set it itself.
const url = "https://depot.example.com/api/v1/console/me";
const response = await fetch(url, { method: "PATCH", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json", }, credentials: "include", body: JSON.stringify({ "displayName": "string" }),});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// Sign in first. outpost_console is a session cookie, so send it with every request.
using System;using System.Net.Http;using System.Text;
using var client = new HttpClient();
const string body = """{ "displayName": "string"}""";
var request = new HttpRequestMessage(HttpMethod.Patch, "https://depot.example.com/api/v1/console/me");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");request.Content = new StringContent(body, Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());GET /api/v1/console/me/activity
Section titled “GET /api/v1/console/me/activity”GETpublic facehosteditmspremoteIdempotent
Source: [object Object]
myActivity lists what has happened to the signed-in account, newest first.
OWN ROWS ONLY BY CONSTRUCTION, not by filtering afterwards: the target is the caller’s own public id and the caller cannot influence it, so there is no parameter to get wrong and no way to ask for somebody else’s history. That is what lets this route sit outside the audit:read permission the full chain needs.
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/json, object | Success. |
Request samples
Section titled “Request samples”# Sign in first. outpost_console is a session cookie, so send it with every request.# This operation is idempotent. Repeating it does not repeat its effect.curl -X GET 'https://depot.example.com/api/v1/console/me/activity' \ -H 'X-CSRF-Token: <csrf-token>' \ -H 'Accept: application/json' \ -b 'outpost_console=<console-session>'# Sign in first. outpost_console is a session cookie, so send it with every request.# This operation is idempotent. Repeating it does not repeat its effect.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$response = Invoke-RestMethod ` -Method Get ` -Uri 'https://depot.example.com/api/v1/console/me/activity' ` -Headers $headers
$response | ConvertTo-Json -Depth 10# Sign in first. outpost_console is a session cookie, so send it with every request.# This operation is idempotent. Repeating it does not repeat its effect.
import requests
url = "https://depot.example.com/api/v1/console/me/activity"headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json",}cookies = { "outpost_console": "<console-session>",}
response = requests.get(url, headers=headers, cookies=cookies, timeout=30)response.raise_for_status()print(response.json())// Sign in first. outpost_console is a session cookie, so send it with every request.// This operation is idempotent. Repeating it does not repeat its effect.package main
import ( "fmt" "io" "log" "net/http")
func main() { req, err := http.NewRequest(http.MethodGet, "https://depot.example.com/api/v1/console/me/activity", nil) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// Sign in first. outpost_console is a session cookie, so send it with every request.// This operation is idempotent. Repeating it does not repeat its effect.// The session cookie is set by signing in. A script cannot set it itself.
const url = "https://depot.example.com/api/v1/console/me/activity";
const response = await fetch(url, { method: "GET", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", }, credentials: "include",});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// Sign in first. outpost_console is a session cookie, so send it with every request.// This operation is idempotent. Repeating it does not repeat its effect.
using System;using System.Net.Http;
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://depot.example.com/api/v1/console/me/activity");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());POST /api/v1/console/mfa
Section titled “POST /api/v1/console/mfa”POSTNo permission neededpublic facehosteditmspremote
Source: [object Object]
mfa answers the second-factor challenge and promotes the session.
Like the password path, every failure is the same 401: which of “wrong code”, “already used”, and “expired challenge” it was tells an attacker how close they are, and tells a legitimate user nothing they can act on beyond “try again”.
Request body
Section titled “Request body”Required, sent as application/json.
| Field | Type | Description |
|---|---|---|
ceremonyIdrequired | string | |
coderequired | string | |
credentialrequired | any | Arbitrary JSON. |
recoveryCoderequired | string | |
rememberDevicerequired | boolean |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/json, MeResponse | Success. |
400 | application/problem+json, ProblemDetails | The request body is not acceptable. |
200 response body
| Field | Type | Description |
|---|---|---|
breakGlassrequired | boolean | |
breakGlassExpiresAt | string (date-time) or null | |
displayNamerequired | string | |
emailrequired | string | |
hasPasswordrequired | boolean | |
mfaEnrolmentDeadline | string (date-time) or null | |
passwordChangeReason | string or null | |
permissionsrequired | array of string | |
phonerequired | string | |
phoneVerifiedrequired | boolean | |
pioneerAutoApproverequired | boolean | |
publicIdrequired | string | |
requiredAction | string or null | |
rolesrequired | array of string |
Request samples
Section titled “Request samples”# This operation is public. It takes no credentials.curl -X POST 'https://depot.example.com/api/v1/console/mfa' \ -H 'Accept: application/json' \ -H 'Content-Type: application/json' \ --data '{ "ceremonyId": "string", "code": "string", "credential": "string", "recoveryCode": "string", "rememberDevice": false}'# This operation is public. It takes no credentials.
$headers = @{ 'Accept' = 'application/json' 'Content-Type' = 'application/json'}
$body = @'{ "ceremonyId": "string", "code": "string", "credential": "string", "recoveryCode": "string", "rememberDevice": false}'@
$response = Invoke-RestMethod ` -Method Post ` -Uri 'https://depot.example.com/api/v1/console/mfa' ` -Headers $headers ` -ContentType 'application/json' ` -Body $body
$response | ConvertTo-Json -Depth 10# This operation is public. It takes no credentials.
import requests
url = "https://depot.example.com/api/v1/console/mfa"headers = { "Accept": "application/json", "Content-Type": "application/json",}payload = { "ceremonyId": "string", "code": "string", "credential": "string", "recoveryCode": "string", "rememberDevice": False,}
response = requests.post(url, headers=headers, json=payload, timeout=30)response.raise_for_status()print(response.json())// This operation is public. It takes no credentials.package main
import ( "bytes" "fmt" "io" "log" "net/http")
func main() { body := []byte(`{ "ceremonyId": "string", "code": "string", "credential": "string", "recoveryCode": "string", "rememberDevice": false}`)
req, err := http.NewRequest(http.MethodPost, "https://depot.example.com/api/v1/console/mfa", bytes.NewReader(body)) if err != nil { log.Fatal(err) }
req.Header.Set("Accept", "application/json") req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// This operation is public. It takes no credentials.
const url = "https://depot.example.com/api/v1/console/mfa";
const response = await fetch(url, { method: "POST", headers: { "Accept": "application/json", "Content-Type": "application/json", }, body: JSON.stringify({ "ceremonyId": "string", "code": "string", "credential": "string", "recoveryCode": "string", "rememberDevice": false }),});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// This operation is public. It takes no credentials.
using System;using System.Net.Http;using System.Text;
using var client = new HttpClient();
const string body = """{ "ceremonyId": "string", "code": "string", "credential": "string", "recoveryCode": "string", "rememberDevice": false}""";
var request = new HttpRequestMessage(HttpMethod.Post, "https://depot.example.com/api/v1/console/mfa");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Content = new StringContent(body, Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());POST /api/v1/console/mfa/passkey
Section titled “POST /api/v1/console/mfa/passkey”POSTNo permission neededpublic facehosteditmspremote
Source: [object Object]
beginMFAPasskey starts the passkey challenge for a session that has proven its first factor.
The allow list is the account’s passkeys on this host.
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/json, CeremonyResponse | Success. |
200 response body
| Field | Type | Description |
|---|---|---|
ceremonyIdrequired | string | |
publicKeyrequired | any |
Request samples
Section titled “Request samples”# This operation is public. It takes no credentials.curl -X POST 'https://depot.example.com/api/v1/console/mfa/passkey' \ -H 'Accept: application/json'# This operation is public. It takes no credentials.
$headers = @{ 'Accept' = 'application/json'}
$response = Invoke-RestMethod ` -Method Post ` -Uri 'https://depot.example.com/api/v1/console/mfa/passkey' ` -Headers $headers
$response | ConvertTo-Json -Depth 10# This operation is public. It takes no credentials.
import requests
url = "https://depot.example.com/api/v1/console/mfa/passkey"headers = { "Accept": "application/json",}
response = requests.post(url, headers=headers, timeout=30)response.raise_for_status()print(response.json())// This operation is public. It takes no credentials.package main
import ( "fmt" "io" "log" "net/http")
func main() { req, err := http.NewRequest(http.MethodPost, "https://depot.example.com/api/v1/console/mfa/passkey", nil) if err != nil { log.Fatal(err) }
req.Header.Set("Accept", "application/json")
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// This operation is public. It takes no credentials.
const url = "https://depot.example.com/api/v1/console/mfa/passkey";
const response = await fetch(url, { method: "POST", headers: { "Accept": "application/json", },});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// This operation is public. It takes no credentials.
using System;using System.Net.Http;
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://depot.example.com/api/v1/console/mfa/passkey");request.Headers.TryAddWithoutValidation("Accept", "application/json");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());GET /api/v1/console/passkeys
Section titled “GET /api/v1/console/passkeys”GETpublic facehosteditmspremoteIdempotent
Source: [object Object]
List my passkeys
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/json, PasskeyListResponse | Success. |
200 response body
| Field | Type | Description |
|---|---|---|
acceptedrequired | boolean | |
availablerequired | boolean | |
maxrequired | integer | |
passkeysrequired | array of PasskeyView | |
passkeys[].backupStaterequired | boolean | |
passkeys[].createdAtrequired | string (date-time) | |
passkeys[].lastUsedAt | string (date-time) or null | |
passkeys[].nickname | string or null | |
passkeys[].publicIdrequired | string | |
passkeys[].rpIdrequired | string | |
passkeys[].transportsrequired | array of string | |
passkeys[].usableHererequired | boolean | |
passwordlessrequired | boolean | |
rpIdrequired | string |
Request samples
Section titled “Request samples”# Sign in first. outpost_console is a session cookie, so send it with every request.# This operation is idempotent. Repeating it does not repeat its effect.curl -X GET 'https://depot.example.com/api/v1/console/passkeys' \ -H 'X-CSRF-Token: <csrf-token>' \ -H 'Accept: application/json' \ -b 'outpost_console=<console-session>'# Sign in first. outpost_console is a session cookie, so send it with every request.# This operation is idempotent. Repeating it does not repeat its effect.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$response = Invoke-RestMethod ` -Method Get ` -Uri 'https://depot.example.com/api/v1/console/passkeys' ` -Headers $headers
$response | ConvertTo-Json -Depth 10# Sign in first. outpost_console is a session cookie, so send it with every request.# This operation is idempotent. Repeating it does not repeat its effect.
import requests
url = "https://depot.example.com/api/v1/console/passkeys"headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json",}cookies = { "outpost_console": "<console-session>",}
response = requests.get(url, headers=headers, cookies=cookies, timeout=30)response.raise_for_status()print(response.json())// Sign in first. outpost_console is a session cookie, so send it with every request.// This operation is idempotent. Repeating it does not repeat its effect.package main
import ( "fmt" "io" "log" "net/http")
func main() { req, err := http.NewRequest(http.MethodGet, "https://depot.example.com/api/v1/console/passkeys", nil) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// Sign in first. outpost_console is a session cookie, so send it with every request.// This operation is idempotent. Repeating it does not repeat its effect.// The session cookie is set by signing in. A script cannot set it itself.
const url = "https://depot.example.com/api/v1/console/passkeys";
const response = await fetch(url, { method: "GET", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", }, credentials: "include",});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// Sign in first. outpost_console is a session cookie, so send it with every request.// This operation is idempotent. Repeating it does not repeat its effect.
using System;using System.Net.Http;
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://depot.example.com/api/v1/console/passkeys");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());PATCH /api/v1/console/passkeys/{passkeyId}
Section titled “PATCH /api/v1/console/passkeys/{passkeyId}”PATCHpublic facehosteditmspremote
Source: [object Object]
Rename my passkey
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
passkeyId | path | string | Yes |
Request body
Section titled “Request body”Required, sent as application/json.
| Field | Type | Description |
|---|---|---|
nicknamerequired | string |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/json, PasskeyView | Success. |
400 | application/problem+json, ProblemDetails | The request body is not acceptable. |
404 | application/problem+json, ProblemDetails | No resource matches that id. |
200 response body
| Field | Type | Description |
|---|---|---|
backupStaterequired | boolean | |
createdAtrequired | string (date-time) | |
lastUsedAt | string (date-time) or null | |
nickname | string or null | |
publicIdrequired | string | |
rpIdrequired | string | |
transportsrequired | array of string | |
usableHererequired | boolean |
Request samples
Section titled “Request samples”# Sign in first. outpost_console is a session cookie, so send it with every request.curl -X PATCH 'https://depot.example.com/api/v1/console/passkeys/<passkeyId>' \ -H 'X-CSRF-Token: <csrf-token>' \ -H 'Accept: application/json' \ -H 'Content-Type: application/json' \ -b 'outpost_console=<console-session>' \ --data '{ "nickname": "string"}'# Sign in first. outpost_console is a session cookie, so send it with every request.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Content-Type' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$body = @'{ "nickname": "string"}'@
$response = Invoke-RestMethod ` -Method Patch ` -Uri 'https://depot.example.com/api/v1/console/passkeys/<passkeyId>' ` -Headers $headers ` -ContentType 'application/json' ` -Body $body
$response | ConvertTo-Json -Depth 10# Sign in first. outpost_console is a session cookie, so send it with every request.
import requests
url = "https://depot.example.com/api/v1/console/passkeys/<passkeyId>"headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json",}cookies = { "outpost_console": "<console-session>",}payload = { "nickname": "string",}
response = requests.patch(url, headers=headers, cookies=cookies, json=payload, timeout=30)response.raise_for_status()print(response.json())// Sign in first. outpost_console is a session cookie, so send it with every request.package main
import ( "bytes" "fmt" "io" "log" "net/http")
func main() { body := []byte(`{ "nickname": "string"}`)
req, err := http.NewRequest(http.MethodPatch, "https://depot.example.com/api/v1/console/passkeys/<passkeyId>", bytes.NewReader(body)) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.Header.Set("Content-Type", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// Sign in first. outpost_console is a session cookie, so send it with every request.// The session cookie is set by signing in. A script cannot set it itself.
const url = "https://depot.example.com/api/v1/console/passkeys/<passkeyId>";
const response = await fetch(url, { method: "PATCH", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json", }, credentials: "include", body: JSON.stringify({ "nickname": "string" }),});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// Sign in first. outpost_console is a session cookie, so send it with every request.
using System;using System.Net.Http;using System.Text;
using var client = new HttpClient();
const string body = """{ "nickname": "string"}""";
var request = new HttpRequestMessage(HttpMethod.Patch, "https://depot.example.com/api/v1/console/passkeys/<passkeyId>");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");request.Content = new StringContent(body, Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());DELETE /api/v1/console/passkeys/{passkeyId}
Section titled “DELETE /api/v1/console/passkeys/{passkeyId}”DELETEpublic facehosteditmspremoteIdempotent
Source: [object Object]
Remove my passkey
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
passkeyId | path | string | Yes |
Request body
Section titled “Request body”Required, sent as application/json.
The body is any.
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
204 | None | Success, with no body. |
400 | application/problem+json, ProblemDetails | The request body is not acceptable. |
404 | application/problem+json, ProblemDetails | No resource matches that id. |
Request samples
Section titled “Request samples”# Sign in first. outpost_console is a session cookie, so send it with every request.# This operation is idempotent. Repeating it does not repeat its effect.curl -X DELETE 'https://depot.example.com/api/v1/console/passkeys/<passkeyId>' \ -H 'X-CSRF-Token: <csrf-token>' \ -H 'Accept: application/json' \ -H 'Content-Type: application/json' \ -b 'outpost_console=<console-session>' \ --data '"string"'# Sign in first. outpost_console is a session cookie, so send it with every request.# This operation is idempotent. Repeating it does not repeat its effect.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Content-Type' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$body = @'"string"'@
$response = Invoke-RestMethod ` -Method Delete ` -Uri 'https://depot.example.com/api/v1/console/passkeys/<passkeyId>' ` -Headers $headers ` -ContentType 'application/json' ` -Body $body
$response | ConvertTo-Json -Depth 10# Sign in first. outpost_console is a session cookie, so send it with every request.# This operation is idempotent. Repeating it does not repeat its effect.
import requests
url = "https://depot.example.com/api/v1/console/passkeys/<passkeyId>"headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json",}cookies = { "outpost_console": "<console-session>",}payload = "string"
response = requests.delete(url, headers=headers, cookies=cookies, json=payload, timeout=30)response.raise_for_status()print(response.json())// Sign in first. outpost_console is a session cookie, so send it with every request.// This operation is idempotent. Repeating it does not repeat its effect.package main
import ( "bytes" "fmt" "io" "log" "net/http")
func main() { body := []byte(`"string"`)
req, err := http.NewRequest(http.MethodDelete, "https://depot.example.com/api/v1/console/passkeys/<passkeyId>", bytes.NewReader(body)) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.Header.Set("Content-Type", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// Sign in first. outpost_console is a session cookie, so send it with every request.// This operation is idempotent. Repeating it does not repeat its effect.// The session cookie is set by signing in. A script cannot set it itself.
const url = "https://depot.example.com/api/v1/console/passkeys/<passkeyId>";
const response = await fetch(url, { method: "DELETE", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json", }, credentials: "include", body: JSON.stringify("string"),});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// Sign in first. outpost_console is a session cookie, so send it with every request.// This operation is idempotent. Repeating it does not repeat its effect.
using System;using System.Net.Http;using System.Text;
using var client = new HttpClient();
const string body = """"string"""";
var request = new HttpRequestMessage(HttpMethod.Delete, "https://depot.example.com/api/v1/console/passkeys/<passkeyId>");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");request.Content = new StringContent(body, Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());POST /api/v1/console/passkeys/registration/begin
Section titled “POST /api/v1/console/passkeys/registration/begin”POSTpublic facehosteditmspremote
Source: [object Object]
Begin passkey registration
Request body
Section titled “Request body”Required, sent as application/json.
The body is any.
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/json, CeremonyResponse | Success. |
400 | application/problem+json, ProblemDetails | The request body is not acceptable. |
200 response body
| Field | Type | Description |
|---|---|---|
ceremonyIdrequired | string | |
publicKeyrequired | any |
Request samples
Section titled “Request samples”# Sign in first. outpost_console is a session cookie, so send it with every request.curl -X POST 'https://depot.example.com/api/v1/console/passkeys/registration/begin' \ -H 'X-CSRF-Token: <csrf-token>' \ -H 'Accept: application/json' \ -H 'Content-Type: application/json' \ -b 'outpost_console=<console-session>' \ --data '"string"'# Sign in first. outpost_console is a session cookie, so send it with every request.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Content-Type' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$body = @'"string"'@
$response = Invoke-RestMethod ` -Method Post ` -Uri 'https://depot.example.com/api/v1/console/passkeys/registration/begin' ` -Headers $headers ` -ContentType 'application/json' ` -Body $body
$response | ConvertTo-Json -Depth 10# Sign in first. outpost_console is a session cookie, so send it with every request.
import requests
url = "https://depot.example.com/api/v1/console/passkeys/registration/begin"headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json",}cookies = { "outpost_console": "<console-session>",}payload = "string"
response = requests.post(url, headers=headers, cookies=cookies, json=payload, timeout=30)response.raise_for_status()print(response.json())// Sign in first. outpost_console is a session cookie, so send it with every request.package main
import ( "bytes" "fmt" "io" "log" "net/http")
func main() { body := []byte(`"string"`)
req, err := http.NewRequest(http.MethodPost, "https://depot.example.com/api/v1/console/passkeys/registration/begin", bytes.NewReader(body)) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.Header.Set("Content-Type", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// Sign in first. outpost_console is a session cookie, so send it with every request.// The session cookie is set by signing in. A script cannot set it itself.
const url = "https://depot.example.com/api/v1/console/passkeys/registration/begin";
const response = await fetch(url, { method: "POST", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json", }, credentials: "include", body: JSON.stringify("string"),});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// Sign in first. outpost_console is a session cookie, so send it with every request.
using System;using System.Net.Http;using System.Text;
using var client = new HttpClient();
const string body = """"string"""";
var request = new HttpRequestMessage(HttpMethod.Post, "https://depot.example.com/api/v1/console/passkeys/registration/begin");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");request.Content = new StringContent(body, Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());POST /api/v1/console/passkeys/registration/finish
Section titled “POST /api/v1/console/passkeys/registration/finish”POSTpublic facehosteditmspremote
Source: [object Object]
Finish passkey registration
Request body
Section titled “Request body”Required, sent as application/json.
| Field | Type | Description |
|---|---|---|
ceremonyIdrequired | string | |
credentialrequired | any | Arbitrary JSON. |
nicknamerequired | string |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
201 | application/json, PasskeyView | Created. |
400 | application/problem+json, ProblemDetails | The request body is not acceptable. |
201 response body
| Field | Type | Description |
|---|---|---|
backupStaterequired | boolean | |
createdAtrequired | string (date-time) | |
lastUsedAt | string (date-time) or null | |
nickname | string or null | |
publicIdrequired | string | |
rpIdrequired | string | |
transportsrequired | array of string | |
usableHererequired | boolean |
Request samples
Section titled “Request samples”# Sign in first. outpost_console is a session cookie, so send it with every request.curl -X POST 'https://depot.example.com/api/v1/console/passkeys/registration/finish' \ -H 'X-CSRF-Token: <csrf-token>' \ -H 'Accept: application/json' \ -H 'Content-Type: application/json' \ -b 'outpost_console=<console-session>' \ --data '{ "ceremonyId": "string", "credential": "string", "nickname": "string"}'# Sign in first. outpost_console is a session cookie, so send it with every request.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Content-Type' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$body = @'{ "ceremonyId": "string", "credential": "string", "nickname": "string"}'@
$response = Invoke-RestMethod ` -Method Post ` -Uri 'https://depot.example.com/api/v1/console/passkeys/registration/finish' ` -Headers $headers ` -ContentType 'application/json' ` -Body $body
$response | ConvertTo-Json -Depth 10# Sign in first. outpost_console is a session cookie, so send it with every request.
import requests
url = "https://depot.example.com/api/v1/console/passkeys/registration/finish"headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json",}cookies = { "outpost_console": "<console-session>",}payload = { "ceremonyId": "string", "credential": "string", "nickname": "string",}
response = requests.post(url, headers=headers, cookies=cookies, json=payload, timeout=30)response.raise_for_status()print(response.json())// Sign in first. outpost_console is a session cookie, so send it with every request.package main
import ( "bytes" "fmt" "io" "log" "net/http")
func main() { body := []byte(`{ "ceremonyId": "string", "credential": "string", "nickname": "string"}`)
req, err := http.NewRequest(http.MethodPost, "https://depot.example.com/api/v1/console/passkeys/registration/finish", bytes.NewReader(body)) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.Header.Set("Content-Type", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// Sign in first. outpost_console is a session cookie, so send it with every request.// The session cookie is set by signing in. A script cannot set it itself.
const url = "https://depot.example.com/api/v1/console/passkeys/registration/finish";
const response = await fetch(url, { method: "POST", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json", }, credentials: "include", body: JSON.stringify({ "ceremonyId": "string", "credential": "string", "nickname": "string" }),});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// Sign in first. outpost_console is a session cookie, so send it with every request.
using System;using System.Net.Http;using System.Text;
using var client = new HttpClient();
const string body = """{ "ceremonyId": "string", "credential": "string", "nickname": "string"}""";
var request = new HttpRequestMessage(HttpMethod.Post, "https://depot.example.com/api/v1/console/passkeys/registration/finish");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");request.Content = new StringContent(body, Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());POST /api/v1/console/password
Section titled “POST /api/v1/console/password”POSTpublic facehosteditmspremote
Source: [object Object]
setPassword sets or replaces the signed-in user’s own password.
Step one of the first-run wizard, and the ordinary change-my-password path afterwards. The account that arrived through a handoff has no credential at all, which is why the FIRST set does not ask for the current one: there would be nothing to ask for.
WHEN THE CURRENT PASSWORD IS ASKED FOR, AND WHEN IT IS NOT
Section titled “WHEN THE CURRENT PASSWORD IS ASKED FOR, AND WHEN IT IS NOT”An ordinary change asks. The session is not enough on its own: a session is what an attacker has after a stolen laptop, a borrowed unlocked screen, or an XSS that never needed the cookie’s value, and re-proving the password is what stops all three from silently becoming permanent, because changing the password is precisely what locks the real owner out. Same reasoning as totpDisable asking for a current code.
TWO CASES ARE EXEMPT, and both are exempt because there is nothing to ask:
- The account has no stored credential. The abandoned first-run owner and the handoff arrival both land here, and demanding a password they have never had would make them unrecoverable.
- The session was minted by a one-time CODE. That is the forgot-password flow, and it is the whole reason the code flow exists: the person cannot remember the password, which is why they proved control of the mailbox instead. Proof of the mailbox is a STRONGER claim on the account than the password is, since the mailbox is where a new code goes. Refusing here would leave a locked-out user with the reset flow ending one step short of resetting anything, which is what TestForgotPasswordIsCodeLoginThenSetPassword pins.
The exempt branches still burn the same PBKDF2 work, so the two do not answer at measurably different speeds and the response does not say whether the account has a password.
Every other session for the account is revoked and the caller gets a fresh one. Changing a password that leaves an attacker’s session alive has not changed anything, and issuing new cookies here is what keeps the person who made the change signed in.
A REFUSAL IS RECORDED, AND ONE KIND OF REFUSAL IS MAILED
Section titled “A REFUSAL IS RECORDED, AND ONE KIND OF REFUSAL IS MAILED”Every branch that says no writes verbPasswordChangeFailed with a reason. Before that, a person holding a stolen session could sit on this form probing the password and leave the chain looking exactly as it does when nothing happened at all.
Only bad_current_password raises a notice. A weak or reused choice is the account’s own owner picking badly - they are already reading the refusal on screen, and mailing it as a security alert is how a person learns to skip the one message in the list that means somebody else is typing.
Request body
Section titled “Request body”Required, sent as application/json.
| Field | Type | Description |
|---|---|---|
currentPasswordrequired | string | |
passwordrequired | string |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/json, object | Success. |
204 | None | Success, with no body. |
400 | None | The request was refused. |
Request samples
Section titled “Request samples”# Sign in first. outpost_console is a session cookie, so send it with every request.curl -X POST 'https://depot.example.com/api/v1/console/password' \ -H 'X-CSRF-Token: <csrf-token>' \ -H 'Accept: application/json' \ -H 'Content-Type: application/json' \ -b 'outpost_console=<console-session>' \ --data '{ "currentPassword": "string", "password": "string"}'# Sign in first. outpost_console is a session cookie, so send it with every request.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Content-Type' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$body = @'{ "currentPassword": "string", "password": "string"}'@
$response = Invoke-RestMethod ` -Method Post ` -Uri 'https://depot.example.com/api/v1/console/password' ` -Headers $headers ` -ContentType 'application/json' ` -Body $body
$response | ConvertTo-Json -Depth 10# Sign in first. outpost_console is a session cookie, so send it with every request.
import requests
url = "https://depot.example.com/api/v1/console/password"headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json",}cookies = { "outpost_console": "<console-session>",}payload = { "currentPassword": "string", "password": "string",}
response = requests.post(url, headers=headers, cookies=cookies, json=payload, timeout=30)response.raise_for_status()print(response.json())// Sign in first. outpost_console is a session cookie, so send it with every request.package main
import ( "bytes" "fmt" "io" "log" "net/http")
func main() { body := []byte(`{ "currentPassword": "string", "password": "string"}`)
req, err := http.NewRequest(http.MethodPost, "https://depot.example.com/api/v1/console/password", bytes.NewReader(body)) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.Header.Set("Content-Type", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// Sign in first. outpost_console is a session cookie, so send it with every request.// The session cookie is set by signing in. A script cannot set it itself.
const url = "https://depot.example.com/api/v1/console/password";
const response = await fetch(url, { method: "POST", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json", }, credentials: "include", body: JSON.stringify({ "currentPassword": "string", "password": "string" }),});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// Sign in first. outpost_console is a session cookie, so send it with every request.
using System;using System.Net.Http;using System.Text;
using var client = new HttpClient();
const string body = """{ "currentPassword": "string", "password": "string"}""";
var request = new HttpRequestMessage(HttpMethod.Post, "https://depot.example.com/api/v1/console/password");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");request.Content = new StringContent(body, Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());GET /api/v1/console/password/rules
Section titled “GET /api/v1/console/password/rules”GETpublic facehosteditmspremoteIdempotent
Source: [object Object]
passwordRulesRead answers GET /api/v1/console/password/rules for the caller’s own account.
Session required and nothing else, exactly like the rest of the account security surface: everything it reports is about the caller, and a technician who may not manage anybody still has to be able to change their own password.
It is a separate round trip from the page’s other reads rather than a field on /me because the rules are per-tenant policy that can move under a running session, and folding them into the identity document would cache them for as long as somebody stays signed in.
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/json, PasswordRulesResponse | Success. |
200 response body
| Field | Type | Description |
|---|---|---|
changedAt | string (date-time) or null | |
hasPasswordrequired | boolean | |
historyCountrequired | integer | |
maxAgeDaysrequired | integer | |
minLengthrequired | integer | |
requireDigitrequired | boolean | |
requireSymbolrequired | boolean | |
requireUpperrequired | boolean |
Request samples
Section titled “Request samples”# Sign in first. outpost_console is a session cookie, so send it with every request.# This operation is idempotent. Repeating it does not repeat its effect.curl -X GET 'https://depot.example.com/api/v1/console/password/rules' \ -H 'X-CSRF-Token: <csrf-token>' \ -H 'Accept: application/json' \ -b 'outpost_console=<console-session>'# Sign in first. outpost_console is a session cookie, so send it with every request.# This operation is idempotent. Repeating it does not repeat its effect.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$response = Invoke-RestMethod ` -Method Get ` -Uri 'https://depot.example.com/api/v1/console/password/rules' ` -Headers $headers
$response | ConvertTo-Json -Depth 10# Sign in first. outpost_console is a session cookie, so send it with every request.# This operation is idempotent. Repeating it does not repeat its effect.
import requests
url = "https://depot.example.com/api/v1/console/password/rules"headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json",}cookies = { "outpost_console": "<console-session>",}
response = requests.get(url, headers=headers, cookies=cookies, timeout=30)response.raise_for_status()print(response.json())// Sign in first. outpost_console is a session cookie, so send it with every request.// This operation is idempotent. Repeating it does not repeat its effect.package main
import ( "fmt" "io" "log" "net/http")
func main() { req, err := http.NewRequest(http.MethodGet, "https://depot.example.com/api/v1/console/password/rules", nil) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// Sign in first. outpost_console is a session cookie, so send it with every request.// This operation is idempotent. Repeating it does not repeat its effect.// The session cookie is set by signing in. A script cannot set it itself.
const url = "https://depot.example.com/api/v1/console/password/rules";
const response = await fetch(url, { method: "GET", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", }, credentials: "include",});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// Sign in first. outpost_console is a session cookie, so send it with every request.// This operation is idempotent. Repeating it does not repeat its effect.
using System;using System.Net.Http;
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://depot.example.com/api/v1/console/password/rules");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());DELETE /api/v1/console/phone
Section titled “DELETE /api/v1/console/phone”DELETEpublic facehosteditmspremoteIdempotent
Source: [object Object]
Phone remove
Request body
Section titled “Request body”Required, sent as application/json.
| Field | Type | Description |
|---|---|---|
coderequired | string | |
phonerequired | string |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/json, object | Success. |
202 | application/json, object | Accepted. |
400 | application/problem+json, ProblemDetails | The request body is not acceptable. |
Request samples
Section titled “Request samples”# Sign in first. outpost_console is a session cookie, so send it with every request.# This operation is idempotent. Repeating it does not repeat its effect.curl -X DELETE 'https://depot.example.com/api/v1/console/phone' \ -H 'X-CSRF-Token: <csrf-token>' \ -H 'Accept: application/json' \ -H 'Content-Type: application/json' \ -b 'outpost_console=<console-session>' \ --data '{ "code": "string", "phone": "string"}'# Sign in first. outpost_console is a session cookie, so send it with every request.# This operation is idempotent. Repeating it does not repeat its effect.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Content-Type' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$body = @'{ "code": "string", "phone": "string"}'@
$response = Invoke-RestMethod ` -Method Delete ` -Uri 'https://depot.example.com/api/v1/console/phone' ` -Headers $headers ` -ContentType 'application/json' ` -Body $body
$response | ConvertTo-Json -Depth 10# Sign in first. outpost_console is a session cookie, so send it with every request.# This operation is idempotent. Repeating it does not repeat its effect.
import requests
url = "https://depot.example.com/api/v1/console/phone"headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json",}cookies = { "outpost_console": "<console-session>",}payload = { "code": "string", "phone": "string",}
response = requests.delete(url, headers=headers, cookies=cookies, json=payload, timeout=30)response.raise_for_status()print(response.json())// Sign in first. outpost_console is a session cookie, so send it with every request.// This operation is idempotent. Repeating it does not repeat its effect.package main
import ( "bytes" "fmt" "io" "log" "net/http")
func main() { body := []byte(`{ "code": "string", "phone": "string"}`)
req, err := http.NewRequest(http.MethodDelete, "https://depot.example.com/api/v1/console/phone", bytes.NewReader(body)) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.Header.Set("Content-Type", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// Sign in first. outpost_console is a session cookie, so send it with every request.// This operation is idempotent. Repeating it does not repeat its effect.// The session cookie is set by signing in. A script cannot set it itself.
const url = "https://depot.example.com/api/v1/console/phone";
const response = await fetch(url, { method: "DELETE", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json", }, credentials: "include", body: JSON.stringify({ "code": "string", "phone": "string" }),});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// Sign in first. outpost_console is a session cookie, so send it with every request.// This operation is idempotent. Repeating it does not repeat its effect.
using System;using System.Net.Http;using System.Text;
using var client = new HttpClient();
const string body = """{ "code": "string", "phone": "string"}""";
var request = new HttpRequestMessage(HttpMethod.Delete, "https://depot.example.com/api/v1/console/phone");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");request.Content = new StringContent(body, Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());POST /api/v1/console/phone/confirm
Section titled “POST /api/v1/console/phone/confirm”POSTpublic facehosteditmspremote
Source: [object Object]
Phone confirm
Request body
Section titled “Request body”Required, sent as application/json.
| Field | Type | Description |
|---|---|---|
coderequired | string | |
phonerequired | string |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/json, object | Success. |
202 | application/json, object | Accepted. |
400 | application/problem+json, ProblemDetails | The request body is not acceptable. |
Request samples
Section titled “Request samples”# Sign in first. outpost_console is a session cookie, so send it with every request.curl -X POST 'https://depot.example.com/api/v1/console/phone/confirm' \ -H 'X-CSRF-Token: <csrf-token>' \ -H 'Accept: application/json' \ -H 'Content-Type: application/json' \ -b 'outpost_console=<console-session>' \ --data '{ "code": "string", "phone": "string"}'# Sign in first. outpost_console is a session cookie, so send it with every request.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Content-Type' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$body = @'{ "code": "string", "phone": "string"}'@
$response = Invoke-RestMethod ` -Method Post ` -Uri 'https://depot.example.com/api/v1/console/phone/confirm' ` -Headers $headers ` -ContentType 'application/json' ` -Body $body
$response | ConvertTo-Json -Depth 10# Sign in first. outpost_console is a session cookie, so send it with every request.
import requests
url = "https://depot.example.com/api/v1/console/phone/confirm"headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json",}cookies = { "outpost_console": "<console-session>",}payload = { "code": "string", "phone": "string",}
response = requests.post(url, headers=headers, cookies=cookies, json=payload, timeout=30)response.raise_for_status()print(response.json())// Sign in first. outpost_console is a session cookie, so send it with every request.package main
import ( "bytes" "fmt" "io" "log" "net/http")
func main() { body := []byte(`{ "code": "string", "phone": "string"}`)
req, err := http.NewRequest(http.MethodPost, "https://depot.example.com/api/v1/console/phone/confirm", bytes.NewReader(body)) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.Header.Set("Content-Type", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// Sign in first. outpost_console is a session cookie, so send it with every request.// The session cookie is set by signing in. A script cannot set it itself.
const url = "https://depot.example.com/api/v1/console/phone/confirm";
const response = await fetch(url, { method: "POST", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json", }, credentials: "include", body: JSON.stringify({ "code": "string", "phone": "string" }),});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// Sign in first. outpost_console is a session cookie, so send it with every request.
using System;using System.Net.Http;using System.Text;
using var client = new HttpClient();
const string body = """{ "code": "string", "phone": "string"}""";
var request = new HttpRequestMessage(HttpMethod.Post, "https://depot.example.com/api/v1/console/phone/confirm");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");request.Content = new StringContent(body, Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());POST /api/v1/console/phone/start
Section titled “POST /api/v1/console/phone/start”POSTpublic facehosteditmspremote
Source: [object Object]
Phone start
Request body
Section titled “Request body”Required, sent as application/json.
| Field | Type | Description |
|---|---|---|
coderequired | string | |
phonerequired | string |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/json, object | Success. |
202 | application/json, object | Accepted. |
400 | application/problem+json, ProblemDetails | The request body is not acceptable. |
Request samples
Section titled “Request samples”# Sign in first. outpost_console is a session cookie, so send it with every request.curl -X POST 'https://depot.example.com/api/v1/console/phone/start' \ -H 'X-CSRF-Token: <csrf-token>' \ -H 'Accept: application/json' \ -H 'Content-Type: application/json' \ -b 'outpost_console=<console-session>' \ --data '{ "code": "string", "phone": "string"}'# Sign in first. outpost_console is a session cookie, so send it with every request.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Content-Type' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$body = @'{ "code": "string", "phone": "string"}'@
$response = Invoke-RestMethod ` -Method Post ` -Uri 'https://depot.example.com/api/v1/console/phone/start' ` -Headers $headers ` -ContentType 'application/json' ` -Body $body
$response | ConvertTo-Json -Depth 10# Sign in first. outpost_console is a session cookie, so send it with every request.
import requests
url = "https://depot.example.com/api/v1/console/phone/start"headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json",}cookies = { "outpost_console": "<console-session>",}payload = { "code": "string", "phone": "string",}
response = requests.post(url, headers=headers, cookies=cookies, json=payload, timeout=30)response.raise_for_status()print(response.json())// Sign in first. outpost_console is a session cookie, so send it with every request.package main
import ( "bytes" "fmt" "io" "log" "net/http")
func main() { body := []byte(`{ "code": "string", "phone": "string"}`)
req, err := http.NewRequest(http.MethodPost, "https://depot.example.com/api/v1/console/phone/start", bytes.NewReader(body)) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.Header.Set("Content-Type", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// Sign in first. outpost_console is a session cookie, so send it with every request.// The session cookie is set by signing in. A script cannot set it itself.
const url = "https://depot.example.com/api/v1/console/phone/start";
const response = await fetch(url, { method: "POST", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json", }, credentials: "include", body: JSON.stringify({ "code": "string", "phone": "string" }),});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// Sign in first. outpost_console is a session cookie, so send it with every request.
using System;using System.Net.Http;using System.Text;
using var client = new HttpClient();
const string body = """{ "code": "string", "phone": "string"}""";
var request = new HttpRequestMessage(HttpMethod.Post, "https://depot.example.com/api/v1/console/phone/start");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");request.Content = new StringContent(body, Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());GET /api/v1/console/totp
Section titled “GET /api/v1/console/totp”GETpublic facehosteditmspremoteIdempotent
Source: [object Object]
totpStatus reports whether the second factor is on, how many recovery codes remain, and how many the current set had, so the console can say “7 of 10” without assuming the size of a set.
A user burning through them is worth showing.
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/json, object | Success. |
Request samples
Section titled “Request samples”# Sign in first. outpost_console is a session cookie, so send it with every request.# This operation is idempotent. Repeating it does not repeat its effect.curl -X GET 'https://depot.example.com/api/v1/console/totp' \ -H 'X-CSRF-Token: <csrf-token>' \ -H 'Accept: application/json' \ -b 'outpost_console=<console-session>'# Sign in first. outpost_console is a session cookie, so send it with every request.# This operation is idempotent. Repeating it does not repeat its effect.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$response = Invoke-RestMethod ` -Method Get ` -Uri 'https://depot.example.com/api/v1/console/totp' ` -Headers $headers
$response | ConvertTo-Json -Depth 10# Sign in first. outpost_console is a session cookie, so send it with every request.# This operation is idempotent. Repeating it does not repeat its effect.
import requests
url = "https://depot.example.com/api/v1/console/totp"headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json",}cookies = { "outpost_console": "<console-session>",}
response = requests.get(url, headers=headers, cookies=cookies, timeout=30)response.raise_for_status()print(response.json())// Sign in first. outpost_console is a session cookie, so send it with every request.// This operation is idempotent. Repeating it does not repeat its effect.package main
import ( "fmt" "io" "log" "net/http")
func main() { req, err := http.NewRequest(http.MethodGet, "https://depot.example.com/api/v1/console/totp", nil) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// Sign in first. outpost_console is a session cookie, so send it with every request.// This operation is idempotent. Repeating it does not repeat its effect.// The session cookie is set by signing in. A script cannot set it itself.
const url = "https://depot.example.com/api/v1/console/totp";
const response = await fetch(url, { method: "GET", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", }, credentials: "include",});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// Sign in first. outpost_console is a session cookie, so send it with every request.// This operation is idempotent. Repeating it does not repeat its effect.
using System;using System.Net.Http;
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://depot.example.com/api/v1/console/totp");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());POST /api/v1/console/totp/confirm
Section titled “POST /api/v1/console/totp/confirm”POSTpublic facehosteditmspremote
Source: [object Object]
totpConfirm proves the secret was scanned, turns TOTP on, and returns the recovery codes.
This is the only time the recovery codes are ever shown.
Request body
Section titled “Request body”Required, sent as application/json.
| Field | Type | Description |
|---|---|---|
ceremonyIdrequired | string | |
coderequired | string | |
credentialrequired | any | Arbitrary JSON. |
recoveryCoderequired | string | |
rememberDevicerequired | boolean |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/json, object | Success. |
400 | application/problem+json, ProblemDetails | The request body is not acceptable. |
Request samples
Section titled “Request samples”# Sign in first. outpost_console is a session cookie, so send it with every request.curl -X POST 'https://depot.example.com/api/v1/console/totp/confirm' \ -H 'X-CSRF-Token: <csrf-token>' \ -H 'Accept: application/json' \ -H 'Content-Type: application/json' \ -b 'outpost_console=<console-session>' \ --data '{ "ceremonyId": "string", "code": "string", "credential": "string", "recoveryCode": "string", "rememberDevice": false}'# Sign in first. outpost_console is a session cookie, so send it with every request.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Content-Type' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$body = @'{ "ceremonyId": "string", "code": "string", "credential": "string", "recoveryCode": "string", "rememberDevice": false}'@
$response = Invoke-RestMethod ` -Method Post ` -Uri 'https://depot.example.com/api/v1/console/totp/confirm' ` -Headers $headers ` -ContentType 'application/json' ` -Body $body
$response | ConvertTo-Json -Depth 10# Sign in first. outpost_console is a session cookie, so send it with every request.
import requests
url = "https://depot.example.com/api/v1/console/totp/confirm"headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json",}cookies = { "outpost_console": "<console-session>",}payload = { "ceremonyId": "string", "code": "string", "credential": "string", "recoveryCode": "string", "rememberDevice": False,}
response = requests.post(url, headers=headers, cookies=cookies, json=payload, timeout=30)response.raise_for_status()print(response.json())// Sign in first. outpost_console is a session cookie, so send it with every request.package main
import ( "bytes" "fmt" "io" "log" "net/http")
func main() { body := []byte(`{ "ceremonyId": "string", "code": "string", "credential": "string", "recoveryCode": "string", "rememberDevice": false}`)
req, err := http.NewRequest(http.MethodPost, "https://depot.example.com/api/v1/console/totp/confirm", bytes.NewReader(body)) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.Header.Set("Content-Type", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// Sign in first. outpost_console is a session cookie, so send it with every request.// The session cookie is set by signing in. A script cannot set it itself.
const url = "https://depot.example.com/api/v1/console/totp/confirm";
const response = await fetch(url, { method: "POST", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json", }, credentials: "include", body: JSON.stringify({ "ceremonyId": "string", "code": "string", "credential": "string", "recoveryCode": "string", "rememberDevice": false }),});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// Sign in first. outpost_console is a session cookie, so send it with every request.
using System;using System.Net.Http;using System.Text;
using var client = new HttpClient();
const string body = """{ "ceremonyId": "string", "code": "string", "credential": "string", "recoveryCode": "string", "rememberDevice": false}""";
var request = new HttpRequestMessage(HttpMethod.Post, "https://depot.example.com/api/v1/console/totp/confirm");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");request.Content = new StringContent(body, Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());POST /api/v1/console/totp/disable
Section titled “POST /api/v1/console/totp/disable”POSTpublic facehosteditmspremote
Source: [object Object]
totpDisable turns the second factor off.
It requires a current code, because “turn off MFA” from a hijacked session would otherwise be the easiest way to make a stolen session permanent.
Request body
Section titled “Request body”Required, sent as application/json.
| Field | Type | Description |
|---|---|---|
ceremonyIdrequired | string | |
coderequired | string | |
credentialrequired | any | Arbitrary JSON. |
recoveryCoderequired | string | |
rememberDevicerequired | boolean |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
204 | None | Success, with no body. |
400 | application/problem+json, ProblemDetails | The request body is not acceptable. |
Request samples
Section titled “Request samples”# Sign in first. outpost_console is a session cookie, so send it with every request.curl -X POST 'https://depot.example.com/api/v1/console/totp/disable' \ -H 'X-CSRF-Token: <csrf-token>' \ -H 'Accept: application/json' \ -H 'Content-Type: application/json' \ -b 'outpost_console=<console-session>' \ --data '{ "ceremonyId": "string", "code": "string", "credential": "string", "recoveryCode": "string", "rememberDevice": false}'# Sign in first. outpost_console is a session cookie, so send it with every request.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Content-Type' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$body = @'{ "ceremonyId": "string", "code": "string", "credential": "string", "recoveryCode": "string", "rememberDevice": false}'@
$response = Invoke-RestMethod ` -Method Post ` -Uri 'https://depot.example.com/api/v1/console/totp/disable' ` -Headers $headers ` -ContentType 'application/json' ` -Body $body
$response | ConvertTo-Json -Depth 10# Sign in first. outpost_console is a session cookie, so send it with every request.
import requests
url = "https://depot.example.com/api/v1/console/totp/disable"headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json",}cookies = { "outpost_console": "<console-session>",}payload = { "ceremonyId": "string", "code": "string", "credential": "string", "recoveryCode": "string", "rememberDevice": False,}
response = requests.post(url, headers=headers, cookies=cookies, json=payload, timeout=30)response.raise_for_status()print(response.json())// Sign in first. outpost_console is a session cookie, so send it with every request.package main
import ( "bytes" "fmt" "io" "log" "net/http")
func main() { body := []byte(`{ "ceremonyId": "string", "code": "string", "credential": "string", "recoveryCode": "string", "rememberDevice": false}`)
req, err := http.NewRequest(http.MethodPost, "https://depot.example.com/api/v1/console/totp/disable", bytes.NewReader(body)) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.Header.Set("Content-Type", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// Sign in first. outpost_console is a session cookie, so send it with every request.// The session cookie is set by signing in. A script cannot set it itself.
const url = "https://depot.example.com/api/v1/console/totp/disable";
const response = await fetch(url, { method: "POST", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json", }, credentials: "include", body: JSON.stringify({ "ceremonyId": "string", "code": "string", "credential": "string", "recoveryCode": "string", "rememberDevice": false }),});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// Sign in first. outpost_console is a session cookie, so send it with every request.
using System;using System.Net.Http;using System.Text;
using var client = new HttpClient();
const string body = """{ "ceremonyId": "string", "code": "string", "credential": "string", "recoveryCode": "string", "rememberDevice": false}""";
var request = new HttpRequestMessage(HttpMethod.Post, "https://depot.example.com/api/v1/console/totp/disable");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");request.Content = new StringContent(body, Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());POST /api/v1/console/totp/enrol
Section titled “POST /api/v1/console/totp/enrol”POSTpublic facehosteditmspremote
Source: [object Object]
totpEnrol issues a secret and the URI an authenticator app scans.
It does NOT turn TOTP on: the secret is unconfirmed until a code proves the user actually scanned it, so a half-finished enrolment cannot lock anyone out.
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/json, object | Success. |
Request samples
Section titled “Request samples”# Sign in first. outpost_console is a session cookie, so send it with every request.curl -X POST 'https://depot.example.com/api/v1/console/totp/enrol' \ -H 'X-CSRF-Token: <csrf-token>' \ -H 'Accept: application/json' \ -b 'outpost_console=<console-session>'# Sign in first. outpost_console is a session cookie, so send it with every request.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$response = Invoke-RestMethod ` -Method Post ` -Uri 'https://depot.example.com/api/v1/console/totp/enrol' ` -Headers $headers
$response | ConvertTo-Json -Depth 10# Sign in first. outpost_console is a session cookie, so send it with every request.
import requests
url = "https://depot.example.com/api/v1/console/totp/enrol"headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json",}cookies = { "outpost_console": "<console-session>",}
response = requests.post(url, headers=headers, cookies=cookies, timeout=30)response.raise_for_status()print(response.json())// Sign in first. outpost_console is a session cookie, so send it with every request.package main
import ( "fmt" "io" "log" "net/http")
func main() { req, err := http.NewRequest(http.MethodPost, "https://depot.example.com/api/v1/console/totp/enrol", nil) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// Sign in first. outpost_console is a session cookie, so send it with every request.// The session cookie is set by signing in. A script cannot set it itself.
const url = "https://depot.example.com/api/v1/console/totp/enrol";
const response = await fetch(url, { method: "POST", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", }, credentials: "include",});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// Sign in first. outpost_console is a session cookie, so send it with every request.
using System;using System.Net.Http;
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://depot.example.com/api/v1/console/totp/enrol");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());POST /api/v1/console/totp/recovery-codes
Section titled “POST /api/v1/console/totp/recovery-codes”POSTpublic facehosteditmspremote
Source: [object Object]
regenerateRecoveryCodes issues a fresh set of ten recovery codes and voids the old ones, for the signed-in account.
It asks for a CURRENT TOTP CODE, which is the “recent second factor” the operation needs and the same step-up totpDisable takes: a hijacked session that could mint itself recovery codes would have a permanent second factor of its own. The step is recorded so the code cannot be replayed.
The new codes are returned exactly once, here.
Request body
Section titled “Request body”Required, sent as application/json.
| Field | Type | Description |
|---|---|---|
ceremonyIdrequired | string | |
coderequired | string | |
credentialrequired | any | Arbitrary JSON. |
recoveryCoderequired | string | |
rememberDevicerequired | boolean |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/json, object | Success. |
400 | application/problem+json, ProblemDetails | The request body is not acceptable. |
Request samples
Section titled “Request samples”# Sign in first. outpost_console is a session cookie, so send it with every request.curl -X POST 'https://depot.example.com/api/v1/console/totp/recovery-codes' \ -H 'X-CSRF-Token: <csrf-token>' \ -H 'Accept: application/json' \ -H 'Content-Type: application/json' \ -b 'outpost_console=<console-session>' \ --data '{ "ceremonyId": "string", "code": "string", "credential": "string", "recoveryCode": "string", "rememberDevice": false}'# Sign in first. outpost_console is a session cookie, so send it with every request.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Content-Type' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$body = @'{ "ceremonyId": "string", "code": "string", "credential": "string", "recoveryCode": "string", "rememberDevice": false}'@
$response = Invoke-RestMethod ` -Method Post ` -Uri 'https://depot.example.com/api/v1/console/totp/recovery-codes' ` -Headers $headers ` -ContentType 'application/json' ` -Body $body
$response | ConvertTo-Json -Depth 10# Sign in first. outpost_console is a session cookie, so send it with every request.
import requests
url = "https://depot.example.com/api/v1/console/totp/recovery-codes"headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json",}cookies = { "outpost_console": "<console-session>",}payload = { "ceremonyId": "string", "code": "string", "credential": "string", "recoveryCode": "string", "rememberDevice": False,}
response = requests.post(url, headers=headers, cookies=cookies, json=payload, timeout=30)response.raise_for_status()print(response.json())// Sign in first. outpost_console is a session cookie, so send it with every request.package main
import ( "bytes" "fmt" "io" "log" "net/http")
func main() { body := []byte(`{ "ceremonyId": "string", "code": "string", "credential": "string", "recoveryCode": "string", "rememberDevice": false}`)
req, err := http.NewRequest(http.MethodPost, "https://depot.example.com/api/v1/console/totp/recovery-codes", bytes.NewReader(body)) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.Header.Set("Content-Type", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// Sign in first. outpost_console is a session cookie, so send it with every request.// The session cookie is set by signing in. A script cannot set it itself.
const url = "https://depot.example.com/api/v1/console/totp/recovery-codes";
const response = await fetch(url, { method: "POST", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json", }, credentials: "include", body: JSON.stringify({ "ceremonyId": "string", "code": "string", "credential": "string", "recoveryCode": "string", "rememberDevice": false }),});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// Sign in first. outpost_console is a session cookie, so send it with every request.
using System;using System.Net.Http;using System.Text;
using var client = new HttpClient();
const string body = """{ "ceremonyId": "string", "code": "string", "credential": "string", "recoveryCode": "string", "rememberDevice": false}""";
var request = new HttpRequestMessage(HttpMethod.Post, "https://depot.example.com/api/v1/console/totp/recovery-codes");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");request.Content = new StringContent(body, Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());Content is licensed CC BY 4.0. Code samples are MIT. Outpost and the Outpost mark are trademarks of Outpost Business Solutions, PBC and are not covered by either license.