Reference
Updated
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.
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
/healthzAnswers 200 while the process serves at all. - GET
/readyzAnswers 200 only when Postgres answers and the job client runs.
Me
- GET
/v1/meWho the token is: the user or key, its org, role, and scopes. - GET
/v1/me/orgsEvery org the signed-in user belongs to, and the active one.
Orgs
- POST
/v1/orgsCreate 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}/exportExport the org's data. The owner only.{id}org:delete - POST
/v1/orgs/{id}/transferMake another member the owner.{id}org:delete
Members
- GET
/v1/orgs/{id}/membersThe 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}/invitationsThe open invitations.{id}org:read - POST
/v1/orgs/{id}/invitationsInvite 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}/resendSend the invitation again with a fresh link.{id}{invite}member:invite - POST
/v1/invitations/lookupWhat an invitation token invites the caller to. - POST
/v1/invitations/acceptJoin the org or app an invitation token names. - POST
/v1/invitations/declineDecline an invitation.
Teams
- GET
/v1/orgs/{id}/teamsThe org's teams.{id}org:read - POST
/v1/orgs/{id}/teamsCreate 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}/membersWho 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}?kindapp:members - DELETE
/v1/apps/{id}/members/{user}Remove a user's, or with kind=team a team's, grant on the app.{id}{user}?kindapp:members
Billing
- GET
/v1/orgs/{id}/seatsSeats used and paid for.{id}billing:manage - GET
/v1/billingThe org's plan, subscription, quotas, and usage this period.org:read - GET
/v1/billing/channelThe payment channels the org may use and the one it uses.org:read - PUT
/v1/billing/channelChoose the payment channel before the first subscription.billing:manage - POST
/v1/billing/checkoutStart a checkout for a plan.billing:manage - GET
/v1/billing/manageA link to the payment provider's customer portal.billing:manage - GET
/v1/billing/gstThe org's GST details.org:read - PUT
/v1/billing/gstSet the org's GSTIN and legal name.billing:manage - GET
/v1/billing/invoicesThe org's invoices.org:read - GET
/v1/billing/invoices/{invoice}/pdfAn invoice as PDF.{invoice}org:read - GET
/v1/billing/packsBandwidth packs on the org's plan, the ones applied this period, and auto purchase.org:read - POST
/v1/billing/packsBuy a bandwidth pack.billing:manage - PATCH
/v1/billing/auto-packTurn automatic pack purchase on or off.billing:manage - GET
/v1/billing/addonsThe add-ons on the org's plan and channel.billing:manage - GET
/v1/billing/addons/previewWhat the next invoice charges for add-ons.billing:manage
SSO
- GET
/v1/orgs/{id}/ssoThe org's SSO setup and domains.{id}org:read - POST
/v1/orgs/{id}/ssoTurn SSO on for verified domains.{id}sso:configure - PATCH
/v1/orgs/{id}/ssoEnforce SSO or stop enforcing it.{id}sso:configure - DELETE
/v1/orgs/{id}/ssoTurn SSO off.{id}sso:configure - POST
/v1/orgs/{id}/sso/portalA short-lived link to the identity provider setup portal.{id}sso:configure
Audit
- GET
/v1/orgs/{id}/auditThe org's audit log, newest first.{id}?limit?beforeaudit:read
API keys
- GET
/v1/orgs/{id}/api-keysThe org's API keys. Never the secret.{id}org:read - POST
/v1/orgs/{id}/api-keysCreate 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/appsThe 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/appsCreate 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}/routesThe app's live inbound routes, each with its hostname, its placements, and the state its nodes report.{id}app:read - POST
/v1/apps/{id}/routesAdd 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}/placementMove 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}/partnersReplace 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}/ipsProvision a dedicated IP for the app. Answers the job to watch.{id}ip:allocate - GET
/v1/ipsThe IPs the caller can see, in pages. With app, one app's; an app the caller cannot read answers 404.?app?after?limitapp:read - GET
/v1/ips/{id}An IP with its listeners and routes.{id}app:read - POST
/v1/ips/{id}/reactivatePut a suspended or quarantined IP back on a node under the same address.{id}ip:allocate - GET
/v1/routes/{route_id}/accessThe users and teams an access route admits.{route_id}app:read - PUT
/v1/routes/{route_id}/accessReplace an access route's whole policy. Nodes take the change in their next access push.{route_id}access:grant - GET
/v1/regionsThe regions IPs can be created in.org:read - GET
/v1/jobs/{id}/eventsThe provisioning job's steps as server-sent events.{id}app:read
Connectors
- GET
/v1/apps/{id}/connectorsThe 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}/connectorsAdd 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}/rotateGive 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/planThe plan fixedmarkd serves, for the connector whose token is the bearer. Metered at 60 a minute per token prefix. - POST
/v1/connector/acme/dns01Publish 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/configWhat fm-access needs before it signs in. 404 access.disabled while access is off. - POST
/v1/access/authorizationsApprove one fm-access sign-in for the signed-in user. Returns a one-time code valid for 120 seconds. - POST
/v1/access/tokenExchange 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/routesThe access routes the credential's user reaches in its org, directly or through a team. - POST
/v1/access/logoutEnd the session the refresh token names and revoke it on nodes. An unknown token answers 204 too. - GET
/v1/orgs/{org_id}/access/sessionsThe 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}/revokeEnd 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}/revokeEnd 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}/credentialsThe app's proxy credentials. Prefixes only.{id}app:read - POST
/v1/apps/{id}/credentialsCreate a proxy credential. The answer holds the token once.{id}token:rotate - POST
/v1/apps/{id}/credentials/{cred}/rotateReplace 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}/policyThe credential's destination allowlist. No rules means every destination the node allows.{id}{cred}app:read - POST
/v1/apps/{id}/credentials/{cred}/policyAdd 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/testWhether 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}/logsOne 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?cursorlogs:read - GET
/v1/apps/{id}/logs/streamThe app's new connection log rows as server-sent events.{id}?direction?dest?result?egress_ip?peer?listener_port?route_id?user_idlogs:read
Usage
- GET
/v1/apps/{id}/usageThe app's usage by bucket and direction.{id}?from?to?granularity?directionlogs:read - GET
/v1/apps/{id}/usage/seriesThe app's usage by bucket, one series per direction.{id}?from?to?granularity?directionlogs:read - GET
/v1/orgs/{id}/usage/periodThe org's current billing period, one object per product.{id}logs:read
Plans
- GET
/v1/plansThe active plans with every quota and feature, priced on the caller's channel and currency.org:read
Webhooks
- GET
/v1/orgs/{id}/webhooksThe org's webhook endpoints.{id}logs:read - POST
/v1/orgs/{id}/webhooksAdd 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}/deliveriesAn endpoint's recent deliveries and their attempts.{id}{endpoint}logs:read - POST
/v1/orgs/{id}/webhooks/{endpoint}/deliveries/{delivery}/replaySend a delivery again. Needs a plan with webhooks, else 402 plan.feature_required.{id}{endpoint}{delivery}org:manage - GET
/v1/webhooks/eventsThe event names an endpoint can subscribe to.
Vercel
- GET
/v1/vercel/installationThe org's Vercel installation and linked projects.org:read - GET
/v1/vercel/installation/projectsThe 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