---
title: "Fixedmark REST API Reference"
description: "Every route of the Fixedmark REST API: apps, static IPs, proxy credentials, jobs, usage, API keys, orgs, members, and teams, with auth, errors, and rate limits."
canonical: https://fixedmark.com/docs/api
updated: 2026-11-03
source: Fixedmark (https://fixedmark.com), BitMask LLP
---

Reference

# REST API

Manage apps, static IPs, proxy credentials, usage, keys, and your organization over HTTPS at https\://api.fixedmark.com. The full OpenAPI 3.1 description is at /openapi.yaml.

[Download openapi.yaml](https://fixedmark.com/openapi.yaml)

## Authenticate

Send an API key as Authorization: Bearer fm\_… Create keys in Settings, API keys. A key acts only in its org and only with its scopes. Tokens never go in a path or query string.

## Errors

Every error is {"error": {"code", "message", "request_id"}}. The request_id is also in the X-Request-Id header. An app you cannot reach answers 404, like one that does not exist.

## Rate limits

600 requests a minute per key, and 60 a minute on create routes. Each answer has RateLimit-Limit and RateLimit-Remaining; over the limit you get 429 rate_limited with Retry-After.

## Routes

118 operations. Each names the action your role or key needs.

### Health

- GET `/healthz` Answers 200 while the process serves at all.
- GET `/readyz` Answers 200 only when Postgres answers and the job client runs.

### Me

- GET `/v1/me` Who the token is: the user or key, its org, role, and scopes.
- GET `/v1/me/orgs` Every org the signed-in user belongs to, and the active one.

### Orgs

- POST `/v1/orgs` Create an org owned by the caller.
- GET `/v1/orgs/{id}` The org. `{id}` org:read
- PATCH `/v1/orgs/{id}` Rename the org or set its country and billing email. `{id}` org:manage
- DELETE `/v1/orgs/{id}` Delete the org. The owner only. `{id}` org:delete
- POST `/v1/orgs/{id}/export` Export the org's data. The owner only. `{id}` org:delete
- POST `/v1/orgs/{id}/transfer` Make another member the owner. `{id}` org:delete

### Members

- GET `/v1/orgs/{id}/members` The members with their roles, teams, and app grants. `{id}` org:read
- PATCH `/v1/orgs/{id}/members/{user}` Change a member's role. `{id}` `{user}` member:manage
- DELETE `/v1/orgs/{id}/members/{user}` Remove a member and revoke the keys they made. `{id}` `{user}` member:manage

### Invitations

- GET `/v1/orgs/{id}/invitations` The open invitations. `{id}` org:read
- POST `/v1/orgs/{id}/invitations` Invite an email to the org, or to one app. The token travels only in the mailed link. `{id}` member:invite
- DELETE `/v1/orgs/{id}/invitations/{invite}` Revoke an open invitation. `{id}` `{invite}` member:invite
- POST `/v1/orgs/{id}/invitations/{invite}/resend` Send the invitation again with a fresh link. `{id}` `{invite}` member:invite
- POST `/v1/invitations/lookup` What an invitation token invites the caller to.
- POST `/v1/invitations/accept` Join the org or app an invitation token names.
- POST `/v1/invitations/decline` Decline an invitation.

### Teams

- GET `/v1/orgs/{id}/teams` The org's teams. `{id}` org:read
- POST `/v1/orgs/{id}/teams` Create a team. `{id}` team:manage
- GET `/v1/teams/{id}` A team with its people and app grants. `{id}` org:read
- PATCH `/v1/teams/{id}` Rename a team or link an SSO group. Linking a group to a team with hand-added members answers 409 team.idp_confirm naming them, until the request sends confirm true. `{id}` team:manage
- DELETE `/v1/teams/{id}` Delete a team and its grants. `{id}` team:manage
- PUT `/v1/teams/{id}/members/{user}` Add an org member to the team. `{id}` `{user}` team:manage
- DELETE `/v1/teams/{id}/members/{user}` Remove a member from the team. `{id}` `{user}` team:manage
- PUT `/v1/teams/{id}/apps/{app}` Grant the team a role on an app. `{id}` `{app}` team:manage
- DELETE `/v1/teams/{id}/apps/{app}` Remove the team's grant on an app. `{id}` `{app}` team:manage

### App members

- GET `/v1/apps/{id}/members` Who reaches the app: direct grants and team grants. `{id}` app:read
- PUT `/v1/apps/{id}/members/{user}` Grant a user, or with kind=team a team, a role on the app. `{id}` `{user}` `?kind` app:members
- DELETE `/v1/apps/{id}/members/{user}` Remove a user's, or with kind=team a team's, grant on the app. `{id}` `{user}` `?kind` app:members

### Billing

- GET `/v1/orgs/{id}/seats` Seats used and paid for. `{id}` billing:manage
- GET `/v1/billing` The org's plan, subscription, quotas, and usage this period. org:read
- GET `/v1/billing/channel` The payment channels the org may use and the one it uses. org:read
- PUT `/v1/billing/channel` Choose the payment channel before the first subscription. billing:manage
- POST `/v1/billing/checkout` Start a checkout for a plan. billing:manage
- GET `/v1/billing/manage` A link to the payment provider's customer portal. billing:manage
- GET `/v1/billing/gst` The org's GST details. org:read
- PUT `/v1/billing/gst` Set the org's GSTIN and legal name. billing:manage
- GET `/v1/billing/invoices` The org's invoices. org:read
- GET `/v1/billing/invoices/{invoice}/pdf` An invoice as PDF. `{invoice}` org:read
- GET `/v1/billing/packs` Bandwidth packs on the org's plan, the ones applied this period, and auto purchase. org:read
- POST `/v1/billing/packs` Buy a bandwidth pack. billing:manage
- PATCH `/v1/billing/auto-pack` Turn automatic pack purchase on or off. billing:manage
- GET `/v1/billing/addons` The add-ons on the org's plan and channel. billing:manage
- GET `/v1/billing/addons/preview` What the next invoice charges for add-ons. billing:manage

### SSO

- GET `/v1/orgs/{id}/sso` The org's SSO setup and domains. `{id}` org:read
- POST `/v1/orgs/{id}/sso` Turn SSO on for verified domains. `{id}` sso:configure
- PATCH `/v1/orgs/{id}/sso` Enforce SSO or stop enforcing it. `{id}` sso:configure
- DELETE `/v1/orgs/{id}/sso` Turn SSO off. `{id}` sso:configure
- POST `/v1/orgs/{id}/sso/portal` A short-lived link to the identity provider setup portal. `{id}` sso:configure

### Audit

- GET `/v1/orgs/{id}/audit` The org's audit log, newest first. `{id}` `?limit` `?before` audit:read

### API keys

- GET `/v1/orgs/{id}/api-keys` The org's API keys. Never the secret. `{id}` org:read
- POST `/v1/orgs/{id}/api-keys` Create an API key. The answer holds the key once; it is never shown again. `{id}` token:rotate
- DELETE `/v1/orgs/{id}/api-keys/{key}` Revoke an API key. `{id}` `{key}` token:rotate

### Apps

- GET `/v1/apps` The apps the caller can see: every app for an owner, admin, or developer, and the apps its grants name for anyone else. app:read
- POST `/v1/apps` Create an app in the caller's org. app:create
- GET `/v1/apps/{id}` The app and the IPs it holds. `{id}` app:read
- DELETE `/v1/apps/{id}` Delete an app that holds no IPs. `{id}` app:delete
- GET `/v1/apps/{id}/routes` The app's live inbound routes, each with its hostname, its placements, and the state its nodes report. `{id}` app:read
- POST `/v1/apps/{id}/routes` Add an inbound route to the app. A shared route is placed on two shared ingress IPs of its region and reached at \<name>.\<org-slug>.\<zone>, TCP on a port from 20000 to 29999 or TLS on 443 by its hostname. A dedicated route goes on one IP of the app. Answers the route, pending, and the job that pushes it to its nodes. `{id}` route:edit
- GET `/v1/apps/{id}/routes/{route_id}` One live route of the app. `{id}` `{route_id}` app:read
- DELETE `/v1/apps/{id}/routes/{route_id}` End an inbound route. Its partner policies go with it, its placements are released, and its nodes drop it. `{id}` `{route_id}` route:edit
- PUT `/v1/apps/{id}/routes/{route_id}/placement` Move the route between the shared pool and a dedicated IP of the app. The route keeps its id, name, hostname, and partners; a TCP route keeps its port when it is free on the new IPs. Audited as route:placement. `{id}` `{route_id}` route:edit
- PUT `/v1/apps/{id}/routes/{route_id}/partners` Replace the route's whole partner list. The node's firewall and proxy take the new list from one push. `{id}` `{route_id}` route:edit

### IPs

- POST `/v1/apps/{id}/ips` Provision a dedicated IP for the app. Answers the job to watch. `{id}` ip:allocate
- GET `/v1/ips` The IPs the caller can see, in pages. With app, one app's; an app the caller cannot read answers 404. `?app` `?after` `?limit` app:read
- GET `/v1/ips/{id}` An IP with its listeners and routes. `{id}` app:read
- POST `/v1/ips/{id}/reactivate` Put a suspended or quarantined IP back on a node under the same address. `{id}` ip:allocate
- GET `/v1/routes/{route_id}/access` The users and teams an access route admits. `{route_id}` app:read
- PUT `/v1/routes/{route_id}/access` Replace an access route's whole policy. Nodes take the change in their next access push. `{route_id}` access:grant
- GET `/v1/regions` The regions IPs can be created in. org:read
- GET `/v1/jobs/{id}/events` The provisioning job's steps as server-sent events. `{id}` app:read

### Connectors

- GET `/v1/apps/{id}/connectors` The app's connectors, each with the sessions nodes report for it. No reply after the create holds a token. `{id}` app:read
- POST `/v1/apps/{id}/connectors` Add a connector to the app. Answers the connector, its token once, and the command that installs fixedmarkd with it. `{id}` route:edit
- GET `/v1/apps/{id}/connectors/{cid}` One connector of the app, with its sessions. `{id}` `{cid}` app:read
- DELETE `/v1/apps/{id}/connectors/{cid}` Delete a connector. A connector a live route names answers 409 connector.in_use with the route ids. `{id}` `{cid}` route:edit
- POST `/v1/apps/{id}/connectors/{cid}/rotate` Give a connector a new token. The old token stops working at once, and a session it opened closes with token_rotated. `{id}` `{cid}` route:edit
- GET `/v1/connector/plan` The plan fixedmarkd serves, for the connector whose token is the bearer. Metered at 60 a minute per token prefix.
- POST `/v1/connector/acme/dns01` Publish a DNS-01 challenge TXT at \_acme-challenge.\<hostname> for the connector whose token is the bearer.
- GET `/v1/connector/acme/dns01/{id}` Whether the connector's challenge is visible on every authoritative nameserver of the tunnel zone. `{id}`
- DELETE `/v1/connector/acme/dns01/{id}` Remove the connector's challenge and its value from the zone. A challenge already gone answers the same. `{id}`
- PUT `/v1/connector/certs/{hostname}` Record the certificate state fixedmarkd holds for the hostname of a live http route it serves. `{hostname}`

### Access

- GET `/v1/access/config` What fm-access needs before it signs in. 404 access.disabled while access is off.
- POST `/v1/access/authorizations` Approve one fm-access sign-in for the signed-in user. Returns a one-time code valid for 120 seconds.
- POST `/v1/access/token` Exchange a sign-in code for a session, or refresh a session's credential. Metered at 30 a minute per address and 60 a minute per session (access.rate_limited).
- GET `/v1/access/routes` The access routes the credential's user reaches in its org, directly or through a team.
- POST `/v1/access/logout` End the session the refresh token names and revoke it on nodes. An unknown token answers 204 too.
- GET `/v1/orgs/{org_id}/access/sessions` The org's live access sessions. No token is shown, only each refresh token's prefix. `{org_id}` access:manage
- POST `/v1/orgs/{org_id}/access/sessions/{session_id}/revoke` End one access session. Nodes close its live connections within seconds. Audited as access:session_revoke. `{org_id}` `{session_id}` access:manage
- POST `/v1/orgs/{org_id}/access/users/{user}/revoke` End every access session of a member and revoke every credential they hold. user is a user id or an email. Audited as access:user_revoke. `{org_id}` `{user}` access:manage

### Credentials

- GET `/v1/apps/{id}/credentials` The app's proxy credentials. Prefixes only. `{id}` app:read
- POST `/v1/apps/{id}/credentials` Create a proxy credential. The answer holds the token once. `{id}` token:rotate
- POST `/v1/apps/{id}/credentials/{cred}/rotate` Replace a credential's token. The old one works until its grace ends. `{id}` `{cred}` token:rotate
- DELETE `/v1/apps/{id}/credentials/{cred}` Revoke a credential. `{id}` `{cred}` token:rotate

### Policy

- GET `/v1/apps/{id}/credentials/{cred}/policy` The credential's destination allowlist. No rules means every destination the node allows. `{id}` `{cred}` app:read
- POST `/v1/apps/{id}/credentials/{cred}/policy` Add one allowlist rule. Nodes enforce it within seconds. Needs a plan with egress_firewall, else 402 plan.feature_required; existing rules stay in force and can still be deleted and tested without it. `{id}` `{cred}` policy:edit
- POST `/v1/apps/{id}/credentials/{cred}/policy/test` Whether the credential's rules allow a destination, and by which rule. `{id}` `{cred}` app:read
- DELETE `/v1/apps/{id}/credentials/{cred}/policy/{rule}` Delete one allowlist rule. Deleting the last allows every destination again. `{id}` `{cred}` `{rule}` policy:edit

### Jobs

- GET `/v1/jobs/{id}` A provisioning job's state. `{id}` app:read

### Logs

- GET `/v1/apps/{id}/logs` One page of the app's connection logs, newest first. `{id}` `?since` `?until` `?direction` `?dest` `?result` `?egress_ip` `?peer` `?listener_port` `?route_id` `?user_id` `?limit` `?cursor` logs:read
- GET `/v1/apps/{id}/logs/stream` The app's new connection log rows as server-sent events. `{id}` `?direction` `?dest` `?result` `?egress_ip` `?peer` `?listener_port` `?route_id` `?user_id` logs:read

### Usage

- GET `/v1/apps/{id}/usage` The app's usage by bucket and direction. `{id}` `?from` `?to` `?granularity` `?direction` logs:read
- GET `/v1/apps/{id}/usage/series` The app's usage by bucket, one series per direction. `{id}` `?from` `?to` `?granularity` `?direction` logs:read
- GET `/v1/orgs/{id}/usage/period` The org's current billing period, one object per product. `{id}` logs:read

### Plans

- GET `/v1/plans` The active plans with every quota and feature, priced on the caller's channel and currency. org:read

### Webhooks

- GET `/v1/orgs/{id}/webhooks` The org's webhook endpoints. `{id}` logs:read
- POST `/v1/orgs/{id}/webhooks` Add a webhook endpoint. The answer holds the signing secret once. Needs a plan with webhooks, else 402 plan.feature_required. `{id}` org:manage
- PATCH `/v1/orgs/{id}/webhooks/{endpoint}` Change an endpoint's URL, events, or enabled flag. Without the webhooks feature only {"enabled":false} is taken; any other change is 402 plan.feature_required. `{id}` `{endpoint}` org:manage
- DELETE `/v1/orgs/{id}/webhooks/{endpoint}` Delete an endpoint. `{id}` `{endpoint}` org:manage
- GET `/v1/orgs/{id}/webhooks/{endpoint}/deliveries` An endpoint's recent deliveries and their attempts. `{id}` `{endpoint}` logs:read
- POST `/v1/orgs/{id}/webhooks/{endpoint}/deliveries/{delivery}/replay` Send a delivery again. Needs a plan with webhooks, else 402 plan.feature_required. `{id}` `{endpoint}` `{delivery}` org:manage
- GET `/v1/webhooks/events` The event names an endpoint can subscribe to.

### Vercel

- GET `/v1/vercel/installation` The org's Vercel installation and linked projects. org:read
- GET `/v1/vercel/installation/projects` The Vercel projects the installation can see. org:read
- PUT `/v1/vercel/installation/projects/{project}` Link a Vercel project to an app and write its proxy env vars. `{project}` token:rotate
- DELETE `/v1/vercel/installation/projects/{project}` Unlink a Vercel project and remove its env vars. `{project}` token:rotate

## Interactive reference

Loading the interactive reference…
