ostradocs Menu

Routes

Every route the Ostra API and the MCP server serve, in one fixed form: the credential it takes, the scopes or role it needs, its Idempotency-Key, what it answers and its notable refusals. This list is read from the server’s own route table when these pages are built, so a route cannot be missing from it or listed here without existing.

Paths are under the base URL. Refusals link to the error codes; any route can also answer 400 invalid_request, 401 unauthenticated, 403 forbidden or insufficient_scope, 429 rate_limited and 500 internal_error, and a line or item you may not see is always 404 not_found. An answer written {a, b} is an object with those fields; a name, such as Line, is that object itself, as GET /v1/lines/{line_id} gives it.

Lines

Your lines and the sandbox line: status, addresses, proxy endpoints and credentials, a new carrier IP, the connection record and usage.

GET /v1/lines

The lines your organization holds now, then the sandbox line.

Credential
API key, OAuth token or session
Scopes
lines:read
Answers
200 {lines: [Line]}

GET /v1/lines/{line_id}

One line: status, carrier, number, addresses, health and proxy ports.

Credential
API key, OAuth token or session
Scopes
lines:read
Answers
200 Line
Refusals
not_found

GET /v1/lines/{line_id}/ip

The exit addresses the server's own checks last saw, with their networks.

Credential
API key, OAuth token or session
Scopes
lines:read
Answers
200 {line_id, sandbox, ipv4, ipv6, asn4, asn6, checked_at, geolocation}. geolocation is null on a real line; the sandbox gives a labelled sample.

POST /v1/lines/{line_id}/new-carrier-ip

Ask the carrier for a new IP address on this line. Returns at once with a job to read.

Credential
API key, OAuth token or session
Scopes
lines:new_carrier_ip
Idempotency-Key
Required
Request
No body, or {}.
Answers
202 {job}; the same answer again, with Ostra-Idempotent-Replay: true, for a repeated Idempotency-Key.

GET /v1/lines/{line_id}/new-carrier-ip/{job_id}

A new carrier IP request's state: pending, running, succeeded, failed, expired or refused.

Credential
API key, OAuth token or session
Scopes
lines:new_carrier_ip
Answers
200 {job}
Refusals
not_found

GET /v1/lines/{line_id}/proxy

The line's proxy endpoints, whether the gateway is taking credentials, and the line's credentials without their secrets.

Credential
API key, OAuth token or session
Scopes
lines:read
Answers
200 {line_id, sandbox, endpoints, gateway, credentials}

POST /v1/lines/{line_id}/proxy-credentials

Issue a username and secret for the line's HTTPS proxy port. The secret is in this answer only.

Credential
API key, OAuth token or session
Scopes
proxy_credentials:write
Idempotency-Key
Accepted
Request
{label?, expires_in_seconds?} (60 to 31536000; none means no expiry).
Answers
201 {credential, secret}

DELETE /v1/lines/{line_id}/proxy-credentials/{credential_id}

Revoke one credential. New sign-ins with it stop; a tunnel it already opened runs until it closes.

Credential
API key, OAuth token or session
Scopes
proxy_credentials:write
Answers
204

GET /v1/lines/{line_id}/sources

The source addresses the line's SOCKS5 and HTTP ports answer, with whether the gateway has each one. No list, no answer.

Credential
API key, OAuth token or session
Scopes
lines:read
Answers
200 {line_id, sandbox, ports, gateway, sources, removing, limits}
Refusals
not_found

PUT /v1/lines/{line_id}/sources

Set the source addresses the line's SOCKS5 and HTTP ports answer: the whole list, replacing the one there. Public addresses and networks no wider than a /24 or /48, at most 20.

Credential
API key, OAuth token or session
Scopes
proxy_credentials:write
Request
{sources: [{address, label?}]}; [] empties it.
Answers
200 as GET

GET /v1/lines/{line_id}/attestations

The line's stated purpose (its one basis, with who stated it and when), every earlier statement, and its blocked-site exceptions.

Credential
API key, OAuth token or session
Scopes
lines:read
Answers
200 {line_id, sandbox, current, attestations, exceptions}
Refusals
not_found

POST /v1/lines/{line_id}/attestations

State the line's purpose again: own_service, permission_to_test or line_number. It supersedes the current statement; a service under a blocked site asks Ostra to open it for this line (never ticketing).

Credential
API key, OAuth token or session
Scopes
lines:attest
Idempotency-Key
Accepted
Request
{basis, services?, operator_name?, permission_reference?}
Answers
201 {attestation, exceptions}

GET /v1/lines/{line_id}/connections

The connection record for your own time on the line: when, from where, to which host and port, how many bytes, and whether Ostra refused it. Never content.

Credential
API key, OAuth token or session
Scopes
connections:read
Request
?from=&;to= (ISO 8601; default the last 24 hours; at most 90 days) &limit= (1 to 5000, default 1000).
Answers
200 {line_id, sandbox, from, to, connections, totals}

GET /v1/lines/{line_id}/connections/export

Every connection record in a range as one file to save: CSV or JSON, streamed, with no limit on rows.

Credential
API key, OAuth token or session
Scopes
connections:read
Request
?from=&;to= (ISO 8601; default the last 24 hours; at most 90 days) &format=csv|json (default csv).
Answers
200 the file, Content-Disposition: attachment

GET /v1/lines/{line_id}/report

The line report: on a real line it answers available false, since line reports are not kept on the server; the sandbox gives a fixed, labelled sample.

Credential
API key, OAuth token or session
Scopes
reports:read
Answers
200 {line_id, available, ...}

GET /v1/usage

Connections and bytes in and out per line per day, for the lines while your organization held them.

Credential
API key, OAuth token or session
Scopes
usage:read
Request
?from=&;to= (YYYY-MM-DD, UTC; at most 92 days; default the last 30).
Answers
200 {from, to, days}

Orders

Order lines for your organization. Available lines are assigned at once; the rest wait in a first-come queue, and nothing is charged until a line is assigned. A signed-in session also needs the admin role to order.

GET /v1/orders

Your organization's line orders, open ones first, then the newest, 50 at most.

Credential
API key, OAuth token or session
Scopes
orders:read
Answers
200 {orders: [Order]}

GET /v1/orders/renewal-terms

The price and renewal terms an order agrees to, word for word as the dashboard shows them beside its consent: each line renews every month until you cancel it, its price, the calls charge (5 cents a minute, which varies with your calls), no minimum, any announced price change, and how to cancel online. Show them to the person agreeing.

Credential
API key, OAuth token or session
Scopes
orders:read
Answers
200 {required, renewal_terms: RenewalTerms | null}: required is true while billing is on; renewal_terms is null while the line has no price

POST /v1/orders/renewal-terms

Agree to the renewal terms in force with no order: for an organization holding lines it never agreed these terms for, or after a price changed. Kept for good, as every agreement is. A person agrees, signed in to the dashboard or an Ostra app: an API key is refused (forbidden).

Credential
OAuth token or session: a person, never an API key
Scopes
orders:write
Request
{accept_renewal_terms}: the id of the renewal terms in force.
Answers
201 {terms_id, agreed_at}

POST /v1/orders

Order lines. Available lines are assigned at once, each charged its first month when it is assigned; the rest wait in a first-come queue with nothing charged, and you get an email as each line is ready. An order that takes your organization above 5 lines is checked by a person at Ostra first.

Credential
API key, OAuth token or session
Scopes
orders:write
Idempotency-Key
Accepted
Request
{quantity, carrier?, attestation, accept_renewal_terms?}: quantity 1 to 1000; carrier att, verizon or tmobile for lines on that carrier only, or left out for any carrier; attestation is every line's purpose, {basis, services?, operator_name?, permission_reference?}; accept_renewal_terms the id of the renewal terms in force (GET /v1/orders/renewal-terms), your agreement to them, needed while billing is on. An API key names none (it cannot agree: forbidden) and orders under the terms a person of the organization last agreed to.
Answers
201 Order

GET /v1/orders/{order_id}

One order: how many lines, which are assigned, what it still waits for, and a sentence saying where it stands.

Credential
API key, OAuth token or session
Scopes
orders:read
Answers
200 Order
Refusals
not_found

POST /v1/orders/{order_id}/cancel

Cancel an order not yet filled. Lines already assigned from it stay yours.

Credential
API key, OAuth token or session
Scopes
orders:write
Request
{}
Answers
200 Order

POST /v1/orders/{order_id}/resume

Resume a paused order (a declined card, a payment owed, renewal terms to agree to, or an order from before orders stated their lines' purpose, which states it now). It keeps its place in the queue and is tried at once.

Credential
API key, OAuth token or session
Scopes
orders:write
Request
{accept_renewal_terms?, attestation?}: the id of the renewal terms in force, when your organization has not agreed to them (an order paused consent_needed). attestation is the lines' purpose, {basis, services?, operator_name?, permission_reference?}, needed for an order paused purpose_needed.
Answers
200 Order

Texts and calls

What reaches a line's number. A list never carries a body unless you ask for content, and every body or code returned is written to Ostra's audit log.

GET /v1/lines/{line_id}/messages

Texts received while your organization held the line, oldest first, by cursor.

Credential
API key, OAuth token or session
Scopes
messages:read, plus messages:read_content with include_content=true
Request
?cursor=&;limit= (1 to 200, default 50) &include_content= (true or false).
Answers
200 {line_id, messages, next_cursor}

GET /v1/lines/{line_id}/messages/wait

Wait for the first text after a cursor (no cursor: the next text from now).

Credential
API key, OAuth token or session
Scopes
messages:read, plus messages:read_content with include_content=true
Request
?cursor=&;timeout= (1 to 90 seconds, default 60) &limit= (1 to 50) &include_content=.
Answers
200 as the list when a text arrives; 204 with Ostra-Cursor on timeout.
Refusals
rate_limited

GET /v1/lines/{line_id}/messages/{message_id}

One text with its body and code. The view is written to the audit log before anything is decrypted.

Credential
API key, OAuth token or session
Scopes
messages:read and messages:read_content
Answers
200 {message}
Refusals
not_found

POST /v1/lines/{line_id}/simulate/message

Sandbox line only: store a simulated text, and fire message.received when the credential also holds webhooks:write.

Credential
API key, OAuth token or session
Scopes
messages:read
Request
{sender?, body?} (body at most 1000 characters; defaults: +13105550100 and a text with a six-digit code).
Answers
201 {message, webhook_event}: webhook_event says whether message.received was queued

GET /v1/lines/{line_id}/calls

Calls to the line, oldest first, by cursor: who called, when, how long, how each ended (in_progress, answered, missed, voicemail or rejected), and Twilio's check of the caller ID (stir_verstat, or null).

Credential
API key, OAuth token or session
Scopes
calls:read, plus calls:read_content with include_content=true
Request
?cursor=&;limit=&include_content=.
Answers
200 {line_id, calls, next_cursor}

GET /v1/lines/{line_id}/calls/{call_id}

One call with its transcript, digits and code, audit-logged like a text. Always 404 on the sandbox line.

Credential
API key, OAuth token or session
Scopes
calls:read and calls:read_content
Answers
200 {call}
Refusals
not_found

GET /v1/labels

The names your organization gave the numbers that text and call its lines, such as Chase for +18885550101. Each text and call from a named number carries its name as sender_label. A key limited to some lines cannot list them.

Credential
API key, OAuth token or session
Scopes
messages:read
Answers
200 {labels: [{sender, label, set_at}]}
Refusals
forbidden

PUT /v1/labels/{number}

Name a number, or rename it. The number may be written any way (+1 888 555 0101, 8885550101): Ostra matches it in E.164, a short code by its digits, an iMessage address in lower case. Every text and call from it then carries the name. Written to the audit log.

Credential
API key, OAuth token or session
Scopes
labels:write
Request
{label}: 1 to 60 characters, no line breaks or control characters.
Answers
200 {label: {sender, label, set_at}}

DELETE /v1/labels/{number}

Remove a number's name: its texts and calls show the number alone again. Written to the audit log.

Credential
API key, OAuth token or session
Scopes
labels:write
Answers
204

Webhooks

Endpoints that receive signed events for every line your organization holds.

GET /v1/webhooks

Your webhook endpoints.

Credential
API key, OAuth token or session
Scopes
webhooks:read
Answers
200 {endpoints}

POST /v1/webhooks

Create an endpoint. The signing secret is in this answer only. include_code is on unless you send false, for a credential holding messages:read_content.

Credential
API key, OAuth token or session
Scopes
webhooks:write, plus the scopes of the events it carries
Idempotency-Key
Accepted
Request
{url, events, description?, include_code?, include_body?}
Answers
201 {endpoint, secret}

GET /v1/webhooks/{endpoint_id}

One endpoint.

Credential
API key, OAuth token or session
Scopes
webhooks:read
Answers
200 Endpoint

PATCH /v1/webhooks/{endpoint_id}

Change events, description, include_code, include_body or enabled. Disabling fails every delivery still pending.

Credential
API key, OAuth token or session
Scopes
webhooks:write, plus the scopes of the result
Answers
200 Endpoint

DELETE /v1/webhooks/{endpoint_id}

Delete an endpoint. Nothing more is sent to it; its log stays readable for 30 days.

Credential
API key, OAuth token or session
Scopes
webhooks:write
Answers
204

POST /v1/webhooks/{endpoint_id}/secret

Replace the signing secret at once. The new one is in this answer only.

Credential
API key, OAuth token or session
Scopes
webhooks:write
Answers
200 {secret}

POST /v1/webhooks/{endpoint_id}/test

Send a webhook.test event now.

Credential
API key, OAuth token or session
Scopes
webhooks:write
Answers
202 {delivery_id}

GET /v1/webhooks/{endpoint_id}/deliveries

The delivery log, newest first.

Credential
API key, OAuth token or session
Scopes
webhooks:read
Request
?status= (pending, succeeded or failed) &limit= (1 to 200).
Answers
200 {deliveries}

POST /v1/webhooks/{endpoint_id}/deliveries/{delivery_id}/retry

Send a failed delivery again, once, now.

Credential
API key, OAuth token or session
Scopes
webhooks:write
Answers
202 {delivery_id}
Refusals
conflict

App devices

The Ostra apps register each device here and read its tunnel settings. Your own code rarely needs these.

POST /v1/devices

Register an Ostra app device for one line your organization holds: platform ios for the iPhone app, macos for the Mac app. The device makes its own key pair; only the public key is sent.

Credential
API key, OAuth token or session
Scopes
devices:write
Idempotency-Key
Accepted
Request
{line_id, platform, public_key, name?}; any other field refuses the request.
Answers
201 Device

GET /v1/devices

Your organization's devices, newest first, ended ones included.

Credential
API key, OAuth token or session
Scopes
devices:read
Request
?line_id=&;limit= (1 to 1000).
Answers
200 {devices}

GET /v1/devices/{device_id}

One device.

Credential
API key, OAuth token or session
Scopes
devices:read
Answers
200 Device
Refusals
not_found

DELETE /v1/devices/{device_id}

Revoke a device. A revoked device is never admitted again.

Credential
API key, OAuth token or session
Scopes
devices:write
Answers
204

GET /v1/devices/{device_id}/tunnel-config

Everything the app needs for its tunnel except its own private key, which never leaves the device.

Credential
API key, OAuth token or session
Scopes
devices:read
Answers
200 {interface, peer}

PUT /v1/devices/{device_id}/push

Store the device's push token, so the Ostra iPhone or Mac app is told of a text on its line.

Credential
API key, OAuth token or session
Scopes
devices:write, plus messages:read_content with show_code: true
Request
{apns_token, environment, show_code}
Answers
200 {push, delivery}

GET /v1/devices/{device_id}/push

The device's push registration, and whether Ostra sends pushes now.

Credential
API key, OAuth token or session
Scopes
devices:read
Answers
200 {push, delivery}

DELETE /v1/devices/{device_id}/push

Remove the device's push token.

Credential
API key, OAuth token or session
Scopes
devices:write
Answers
204

POST /v1/voice/token

A Twilio Voice access token, incoming only: for one iPhone app install, so calls to the lines of the devices it registered ring it (only the credential that registered the devices may ask), or with kind browser for a signed-in dashboard tab, or kind mac for the Ostra Mac app's own sign-in, each ringing for the organization's lines while its five-minute lease runs.

Credential
API key, OAuth token or session
Scopes
calls:read and calls:read_content and devices:write
Request
{device_ids, environment}, or {kind: "browser", browser_id?} from a session, or {kind: "mac", browser_id?} from the Mac app
Answers
200 {token, identity, expires_at, environment, ring_seconds, lines: [{device_id, line_id, rings}]}; for a browser 200 {token, identity, expires_at, browser_id, ring_seconds, lines: [{line_id, rings}]}

DELETE /v1/voice/registration

Stop calls ringing the app install that registered these devices (ringing turned off in the app), or a dashboard browser or a Mac by its browser_id. Only that install's own credential, or the browser's or Mac's own person, may ask.

Credential
API key, OAuth token or session
Scopes
calls:read
Request
{device_ids} or {browser_id}
Answers
204

Organization and members

Managed from the dashboard with a signed-in session. API keys and OAuth tokens cannot call these, except GET /v1/org.

GET /v1/orgs

The organizations you belong to, with your role in each.

Credential
Signed-in session only
Role
Any signed-in person; no organization needed
Answers
200 {organizations}

POST /v1/orgs

Create an individual's account or a business organization with you as its owner. At most 5 per person, one of them an individual's.

Credential
Signed-in session only
Role
Any signed-in person; no organization needed
Request
{name, kind?, turnstile_token?}
Answers
201 Organization

GET /v1/org

Your organization: status, verification, the policy accepted, and whether Ostra may assign it a line. Any key, token or session of the organization may read it.

Credential
Any key, OAuth token or session of the organization; no scope
Answers
200 Organization

PATCH /v1/org

Rename the organization, or require two-step sign-in of every member (only an admin who has two-step on can turn that on).

Credential
Signed-in session only
Role
The admin role
Request
{name?, require_two_step?}
Answers
200 Organization
Refusals
conflict

GET /v1/org/members

The people in the organization, their roles, and whether each has two-step sign-in on; and, apart, the people invited who have not joined, with no two-step state of theirs.

Credential
Signed-in session only
Role
Any member
Answers
200 {members, invitations}

POST /v1/org/members

Invite a person by email with a role (an owner to invite an owner). They get an email, and become a member only by accepting from their own session; until then nothing of the organization applies to them. 20 a day, cancelled ones included.

Credential
Signed-in session only
Role
The admin role
Request
{email, role}
Answers
201 {user_id, email, role, invited_at, status, notice_sent}

PATCH /v1/org/members/{user_id}

Change a person's role (an owner when the member is or becomes an owner). The last owner cannot be changed.

Credential
Signed-in session only
Role
The admin role
Request
{role}
Answers
200 {user_id, role}

DELETE /v1/org/members/{user_id}

Remove yourself, or, as an admin, another person (an owner to remove an owner). The last owner cannot be removed. Revokes the OAuth access they granted for the organization. For a person invited and not yet joined, an admin cancels the invitation (an owner for an owner's).

Credential
Signed-in session only
Role
Any member
Answers
204

GET /v1/invitations

Your own open invitations: each organization, the role, when, and the address of the admin who sent it. A closed organization's are left out.

Credential
Signed-in session only
Role
Any signed-in person; no organization needed
Answers
200 {invitations}

POST /v1/invitations/{org_id}/accept

Accept an invitation sent to you: you become a member with the role it names, and from then on the organization counts at your sign-in, its two-step requirement included. Only the invited person's own session can.

Credential
Signed-in session only
Role
Any signed-in person; no organization needed
Answers
200 {id, name, role, status, kind, internal}

POST /v1/invitations/{org_id}/decline

Decline an invitation sent to you. It ends; the organization can send another.

Credential
Signed-in session only
Role
Any signed-in person; no organization needed
Answers
204
Refusals
not_found

Verification and policy

Signing up: the details, the mobile number proved by a texted code, and the automatic check (the sanctions screen, and where the request comes from); acceptance of the acceptable use policy.

GET /v1/signup/turnstile

Whether the sign-up steps (creating an account, texting the mobile code, submitting) take a Cloudflare Turnstile token on this server, and the widget's public site key.

Credential
None
Answers
200 {required, site_key}

GET /v1/aup

The acceptable use policy in force: its version and where to read it.

Credential
None
Answers
200 {version, url}

GET /v1/org/verification

The sign-up record as saved: the details, the verified mobile number (its last four only to a member who is not an admin), and its status.

Credential
Signed-in session only
Role
Any member
Answers
200 Verification

PUT /v1/org/verification

Save the draft: account type, your legal name, a business's legal name and a US address. Fields sent replace what is saved, fields left out keep their value.

Credential
Signed-in session only
Role
The admin role
Answers
200 Verification

POST /v1/org/verification/submit

Submit with the 18-or-older statement and the policy in force. The automatic check runs at once: approved, or held for a person at Ostra.

Credential
Signed-in session only
Role
The admin role
Request
{attestations, turnstile_token?}
Answers
200 Verification

GET /v1/org/verification/phone

How the mobile number step works here: whether codes are really texted (twilio) or held for Ostra's staff (outbox), how long a code lasts, how many tries it takes.

Credential
Signed-in session only
Role
Any member
Answers
200 {mode, code_ttl_seconds, max_attempts}

POST /v1/org/verification/phone

Text a 6-digit code to a US mobile number. Internet-phone (VoIP) numbers, landlines and numbers Ostra uses are refused before anything is sent.

Credential
Signed-in session only
Role
The admin role
Request
{phone, turnstile_token?}
Answers
202 {sent, number_last4, expires_at, mode}

POST /v1/org/verification/phone/check

Prove the number with the code from the text: the record then holds it as verified.

Credential
Signed-in session only
Role
The admin role
Request
{code}
Answers
200 Verification

POST /v1/org/aup

Record your acceptance of the policy in force, with the time and your address.

Credential
Signed-in session only
Role
The admin role
Request
{version}
Answers
201 {version, accepted_at, accepted_by}

API keys

Made and revoked by an admin from a signed-in session. A key can never create or see another key.

GET /v1/keys

The organization's API keys, each with its prefix only.

Credential
Signed-in session only
Role
The admin role
Answers
200 {keys}

POST /v1/keys

Make a key with a name, its scopes, and optionally some lines and an expiry. The secret is in this answer only.

Credential
Signed-in session only
Role
The admin role
Request
{name, scopes, line_ids?, expires_at?}
Answers
201 {key, secret}
Refusals
conflict

DELETE /v1/keys/{key_id}

Revoke a key. It stops working at once.

Credential
Signed-in session only
Role
The admin role
Answers
204

Billing

A signed-in session only. Checkout and the card portal are Stripe's own pages.

GET /v1/billing

Where billing stands: the price sheet in force, the card on file, each line with its own subscription and renewal date (soonest first), any grace period or suspension, any invoice a canceled line left unpaid, the usage subscription, and the calls billed so far this period.

Credential
Signed-in session only
Role
Any member
Answers
200 Billing

POST /v1/lines/{line_id}/cancel

Cancel a line: it ends now, its subscription ends with no refund of the rest of the month, and its number is deleted, never given to another Ostra customer. Send it only after showing that the number is deleted. It needs a sign-in, or a second step, from the last 10 minutes, so an old session cannot delete a number.

Credential
Signed-in session only
Role
The admin role
Request
{confirm: "delete_number"}
Answers
200 {line_id, ended_at, number, billing}

POST /v1/billing/checkout

Open Stripe Checkout in setup mode to save a card; nothing is charged until Ostra assigns a line. While one is still open, the same one comes back.

Credential
Signed-in session only
Role
The admin role
Request
{}
Answers
201 {url, expires_at}

POST /v1/billing/portal

Open Stripe's Customer Portal for the card and the invoices, where an unpaid invoice is paid. Refused while the portal would let a customer end or change a line's subscription there.

Credential
Signed-in session only
Role
The admin role
Request
{}
Answers
200 {url}

Sign-in

Passwordless sign-in for the dashboard: a 6-digit code by email (with a link that asks before it signs in), then a session cookie.

GET /v1/auth/access

Whether Ostra is open by invitation on this server (the access gate), and whether this browser holds a pass from an access code, until when. A pass whose code was revoked or ended is cleared.

Credential
None
Answers
200 {required, unlocked, expires_at}

POST /v1/auth/access

An access code for this browser's pass, an HttpOnly cookie for 30 days or until the code ends. While the access gate is on, every route that makes an account or a session takes the pass. 10 wrong codes from a client network in an hour stop its code checks for the hour; across Ostra, 300 wrong in an hour stop them while Turnstile is off, and 3,000 whatever a code carries.

Credential
None
Request
{code, turnstile_token?} as application/json. Case, spaces and dashes in the code are ignored. While Cloudflare Turnstile is on, turnstile_token is a token from the widget drawn with the action access_code.
Answers
200 {required, unlocked: true, expires_at, room}, and sets the pass cookie. room is false for a code that has let in as many people as it allows: its pass opens sign-in for those people only.

POST /v1/auth/sign-in

Email a 6-digit sign-in code and a link, for 15 minutes; older ones for the address stop working. The answer is the same whether or not the address has an account.

Credential
None
Request
{email, turnstile_token?} as application/json. While Cloudflare Turnstile is on, a network's fourth sign-in email in an hour needs turnstile_token from the widget drawn with the action sign_in.
Answers
202 {sent: true}

POST /v1/auth/sign-in/code

Trade the address and its emailed code for a session cookie. 5 wrong codes end a code; 10 per address an hour, 30 per address a day, or 30 per client network an hour stop code checks there until the oldest leaves its window.

Credential
None
Request
{email, code} as application/json; spaces and dashes in the code are ignored.
Answers
200 Session, and sets the session cookie.

POST /v1/auth/sign-in/check

Whose sign-in link it is, without using it: the page the link opens asks before it signs in.

Credential
None
Request
{token} (the osl_ token from the link's fragment).
Answers
200 {email, expires_at}

POST /v1/auth/sign-in/verify

Trade the link's token for a session cookie, when the person taps Sign in.

Credential
None
Request
{token} (the osl_ token from the link's fragment).
Answers
200 Session, and sets the session cookie.

GET /v1/auth/session

Who is signed in, their organizations, and the CSRF token to send on every change.

Credential
Signed-in session only
Role
Any signed-in person; no organization needed
Answers
200 Session

POST /v1/auth/sign-out

End the session and clear the cookie.

Credential
Signed-in session only
Role
Any signed-in person; no organization needed
Answers
204

POST /v1/auth/two-step

The second step: when the email code answers a two-step challenge, trade its token and a code from the authenticator app (or a backup code) for the session cookie. Each code works once; 5 wrong end the challenge, and the person's and the network's limits are those of sign-in codes.

Credential
None
Request
{token, code} or {token, backup_code} as application/json.
Answers
200 Session, and sets the session cookie; with a backup code also backup_codes_left.

POST /v1/auth/two-step/enroll

When an organization requires two-step sign-in and the person has not set it up: a new authenticator secret and its otpauth URI, for a QR code.

Credential
None
Request
{token} (the enrollment challenge) as application/json.
Answers
200 {secret, otpauth_uri}

POST /v1/auth/two-step/enroll/confirm

Confirm the new secret with one code: two-step is on, the session cookie is set, and ten backup codes are shown this once.

Credential
None
Request
{token, code} as application/json.
Answers
200 Session, sets the session cookie, and backup_codes.

GET /v1/auth/two-step

Whether two-step sign-in is on for you, how many backup codes are left, and which of your organizations require it.

Credential
Signed-in session only
Role
Any signed-in person; no organization needed
Answers
200 {enabled, enabled_at, backup_codes_left, required_by}

POST /v1/auth/two-step/setup

Start turning two-step sign-in on: a new authenticator secret and its otpauth URI, pending until a code confirms it.

Credential
Signed-in session only
Role
Any signed-in person; no organization needed
Answers
200 {secret, otpauth_uri}
Refusals
conflict

POST /v1/auth/two-step/setup/confirm

Confirm with one code: two-step is on, ten backup codes are shown this once, and your other sessions end.

Credential
Signed-in session only
Role
Any signed-in person; no organization needed
Request
{code}
Answers
200 {enabled, backup_codes}

POST /v1/auth/two-step/backup-codes

Ten new backup codes, for a current code from the app; every old one stops working.

Credential
Signed-in session only
Role
Any signed-in person; no organization needed
Request
{code}
Answers
200 {backup_codes}

POST /v1/auth/two-step/disable

Turn two-step sign-in off with a code or a backup code: the secret and the backup codes are deleted and your other sessions end. Refused while an organization of yours requires it.

Credential
Signed-in session only
Role
Any signed-in person; no organization needed
Request
{code} or {backup_code}
Answers
200 {enabled}

OAuth

Authorization code with PKCE for the apps, the extension and MCP clients. Clients are registered by Ostra.

GET /.well-known/oauth-authorization-server

OAuth authorization server metadata (RFC 8414).

Credential
None
Answers
200 {issuer, authorization_endpoint, token_endpoint, ...}

GET /.well-known/oauth-protected-resource

Protected resource metadata for the REST API (RFC 9728).

Credential
None
Answers
200 {resource, authorization_servers, scopes_supported, bearer_methods_supported}

GET /oauth/authorize

Start of the authorization code flow. Checks client_id and redirect_uri, then sends the browser to the dashboard's consent screen. A wrong client or redirect is 400 and never redirects.

Credential
None
Request
?response_type=code&;client_id=&redirect_uri=&scope=&state=&code_challenge=&code_challenge_method=S256&resource=
Answers
302

GET /v1/oauth/authorize

For the consent screen: the client, the scopes asked for, and the organizations you may grant them for.

Credential
Signed-in session only
Role
Any signed-in person; no organization needed
Answers
200 {client, redirect_uri, state, scopes, resource, organizations}

POST /v1/oauth/authorize

Approve the request for one organization you are a member of (any other is 404), or deny it.

Credential
Signed-in session only
Role
Any signed-in person; no organization needed
Request
{client_id, redirect_uri, state?, scope, code_challenge, code_challenge_method, resource?, organization_id, decision}
Answers
200 {redirect_to}

POST /oauth/token

Trade a code (with its code_verifier) or a refresh token for tokens. Form-encoded.

Credential
OAuth client (form-encoded)
Request
grant_type=authorization_code with code, redirect_uri, client_id, code_verifier; or grant_type=refresh_token with refresh_token, client_id.
Answers
200 {access_token, token_type, expires_in, refresh_token, scope}; errors per RFC 6749 and RFC 8707.

POST /oauth/revoke

Revoke a token (RFC 7009). Form-encoded.

Credential
OAuth client (form-encoded)
Request
token, token_type_hint?
Answers
200

POST /oauth/introspect

Token introspection (RFC 7662), for a confidential client Ostra allowed to introspect.

Credential
OAuth client (form-encoded)
Answers
200 {active, ...}

GET /v1/oauth/grants

The apps you approved for the organization.

Credential
Signed-in session only
Role
Any member
Answers
200 {grants}

DELETE /v1/oauth/grants/{grant_id}

Revoke an app's access and every token issued from it.

Credential
Signed-in session only
Role
Any member
Answers
204

MCP server

Its own host and listener. One JSON-RPC message per POST.

POST mcp/mcp

The MCP endpoint: one JSON-RPC message per POST, answered with one JSON object or a short event stream. A batch is refused.

Credential
API key, or an OAuth token issued for the MCP server
Scopes
Each tool’s own (see the MCP page)
Answers
200; 401 or 403 with WWW-Authenticate when the credential is missing, refused or lacks a scope.

GET mcp/mcp

No sessions and no standing stream.

Credential
API key, or an OAuth token issued for the MCP server
Scopes
Each tool’s own (see the MCP page)
Answers
405

DELETE mcp/mcp

No sessions to end.

Credential
API key, or an OAuth token issued for the MCP server
Scopes
Each tool’s own (see the MCP page)
Answers
405

GET mcp/.well-known/oauth-protected-resource

Protected resource metadata for the MCP server (RFC 9728).

Credential
None
Answers
200 {resource, authorization_servers, scopes_supported, bearer_methods_supported, resource_name}

GET mcp/.well-known/oauth-protected-resource/mcp

The same metadata at the path the MCP specification names for the /mcp resource.

Credential
None
Answers
200, as above