Skip to content
Fixedmark
REST API

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

Interactive reference

Loading the interactive reference…