API Overview
One integration surface: GraphQL — endpoint, authentication, daily quotas, and what an API key actually reaches
FreeMaint has one API surface for machine-to-machine integration: GraphQL, at /api/v1/graphql. The REST routes you see in the browser are the application's own API and are authenticated by a user session — an API key does not reach them. Plan for GraphQL when you integrate.
REST API
The REST routes under /api/v1/{resource} are what the FreeMaint web and mobile apps call, authenticated with a user session (JWT). An API key (fmk_...) does NOT authenticate them — it is accepted on /api/v1/graphql and on the energy ingest endpoint only. If you are building an integration, read the GraphQL section below; the REST paths are documented here because you will see them in your browser's network tab, not because a key can call them.
Example (user session, NOT an API key — a fmk_ key will not authenticate this route): curl -H 'Authorization: Bearer <session-token>' 'https://freemaint.com/api/v1/work-orders?status=OPEN&assigneeId=42&page=1&pageSize=20'
GraphQL API
The GraphQL endpoint is POST /api/v1/graphql. It covers a deliberate subset of the product — work orders, assets, asset downtime, parts and their stock movements, meters, purchase orders, locations, vendors, customers and floor plans — not the whole application. Use GraphQL when you would otherwise have to call several endpoints to assemble one screen — for example, a tile showing a work order with its asset details, parts used and total labor cost. The ERP integration guide lists what the schema does not reach.
Example query: query { workOrders(status: "OPEN", limit: 10) { id reference title status priority assetId dueDate } }
Authentication
Both APIs accept a JWT bearer token in the Authorization header, issued by POST /api/v1/auth/login and valid for 2 hours (refresh with the refresh token). For server-to-server integrations use an API key instead: an admin creates it in Company Settings > API (/company-settings/api), and a person whose role allows it creates their own under Profile > Security > API keys (/profile/api-keys); send Authorization: Bearer fmk_... . A key acts as its owner: it has their permissions and sees only what they see. IMPORTANT: an API key authenticates the GraphQL endpoint (POST /api/v1/graphql), the energy ingest endpoint and /public-api/{whoami,ping} — it does NOT authenticate the REST resource routes, which require a user JWT.
Header
Authorization: Bearer <token>
Token TTL
Access token: 2 hours. Refresh token: 30 days.
Tier availability
Business
API keys, GraphQL reads and writes, 5,000 calls per day
Enterprise
Everything in Business, 100,000 calls per day, plus the energy ingest endpoint
Rate limits
The quota is a DAILY counter per company, shared between GraphQL and the energy ingest endpoint, and it resets at 00:00 UTC. Business allows 5,000 calls per day, Enterprise 100,000. Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset. There is no per-minute window and no Retry-After header. On the REST surfaces the refusal is HTTP 403; on GraphQL the HTTP status stays 200 — GraphQL always answers 200 — and the refusal is in errors[0].extensions.code = FORBIDDEN. Do not poll for HTTP 403 on GraphQL: read the errors array.
Error format
Every error carries an HTTP status, a machine-readable code and a human-readable message. 4xx are client errors (validation, permission, not found); 5xx are server errors and should be retried with exponential backoff. On GraphQL the code is in extensions.code, and the values you can pattern-match are: BAD_REQUEST, UNAUTHENTICATED, FORBIDDEN, NOT_FOUND, CONFLICT, BAD_USER_INPUT and TOO_MANY_REQUESTS. When input validation fails the message names the exact fields at fault — for example "quantity must be a number conforming to the specified constraints" — so you never have to guess which one your system got wrong. A lookup that simply matches nothing is not an error: part(barcode:) returns null.
Official SDKs and tooling
There is no official client SDK today. The GraphQL endpoint has introspection enabled, so any GraphQL client (Apollo, graphql-codegen, Insomnia, Postman) can discover the schema by pointing at POST /api/v1/graphql with your API key. We do not currently publish a static OpenAPI document or an SDL file at a fixed URL.
Tip
API keys are not available on Core or Starter.
Related articles
Was this page helpful?