Developers

Everything in Flex,
reachable from code.

A REST API with a published OpenAPI spec, signed webhooks, and an MCP server so an AI agent can work inside your account with exactly the permissions you give it. All of it is in the base plan — there is no enterprise tier to unlock.

OpenAPI 3 spec Interactive reference llms.txt for agents Per-key permissions
Quickstart

Two calls to a working integration.

  1. Create a key. In Flex, go to Settings → Integrations → API Keys. Tick the permissions the key needs and set its rate limit. The secret (flx_live_…) is shown once and stored only as a hash.
  2. Ask what you're allowed to do. GET /api/v1/modules returns the organization's active modules and the key's permissions, so your code can discover its own capabilities.
  3. Read, then write. Lists are cursor-paged; writes take an Idempotency-Key so a retried request never creates a duplicate.

Base URL: https://app.flexonthejob.com/api/v1. Every response is scoped to the organization that owns the key — there is no cross-tenant access, by design.

bash
# 1. What can this key do?
curl https://app.flexonthejob.com/api/v1/modules \
  -H "Authorization: Bearer flx_live_…"

# 2. First page of items, 50 at a time
curl "https://app.flexonthejob.com/api/v1/items?limit=50" \
  -H "Authorization: Bearer flx_live_…"
# → { "items": [ … ], "nextCursor": "eyJpZCI6NTB9" }

# 3. Create a customer — safe to retry with the same key
curl -X POST https://app.flexonthejob.com/api/v1/customers \
  -H "Authorization: Bearer flx_live_…" \
  -H "Idempotency-Key: 7c1f2a4e-order-1042" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Sunny Point Café", "email": "ap@sunnypoint.example",
        "phone": "828-555-0142", "customerType": "Commercial" }'
# → 201 Created, Location: /api/v1/customers/57
Keys and permissions

Scoped by default.

A key is not a login. It is a named credential with its own permission set, its own rate limit and its own request log, and it can be revoked without touching anyone's password.

Per-key permissions

When you create a key you pick from the same permissions Flex uses for people — ViewJobs, ManageJobs, ViewInventory, ManageCustomers, ViewInvoices, ManageInvoices and so on. A key can only be granted permissions its creator holds. Each endpoint names the permission it needs; without it the call gets a 403.

Per-key rate limits

Fixed one-minute windows: 120 requests/min per key by default (adjustable per key when you create it) and 600/min per organization. Over the limit you get 429 with a Retry-After header in seconds. Reads and writes count the same.

Stored hashed, logged, revocable

The secret is shown once and stored as a salted PBKDF2 hash; a leaked database does not leak keys. Every request is logged against the key (method, path, status, timing), and Settings shows when each key was last used. Revoke a key and it stops working on the next request.

Conventions

The same rules on every endpoint.

ConcernHow it works
AuthenticationAuthorization: Bearer flx_live_… on every request. Missing or invalid → 401 problem detail.
PaginationList endpoints return { "items": [...], "nextCursor": "…" }. Pass after=<nextCursor> for the next page and limit (1–200, default 50) to size it. nextCursor is null on the last page; cursors are opaque and stable.
IdempotencyEvery POST, PUT, PATCH and DELETE requires an Idempotency-Key header (any unique string, up to 128 characters). Replaying the same key with the same body returns the original response; the same key with a different body is rejected with 422. Keys are remembered for 24 hours per organization.
CachingSingle-resource GETs return a weak ETag. Send it back as If-None-Match to get 304 Not Modified with no body.
ErrorsRFC 7807 application/problem+json. 400 validation errors list per-field messages under errors; 401/403/404/409/422/429/500 carry title, detail and a traceId to quote to support.
ModulesEndpoints that belong to an optional module (scheduling, for example) answer 403 with a problem detail when the organization hasn't turned that module on. GET /api/v1/modules tells you in advance.
FormatsJSON, camelCase, enums as strings, timestamps ISO-8601 UTC, money as decimal numbers, ids as integers.
Endpoints — v1

Customers, jobs, schedule, items, invoices.

The full reference with request and response schemas is at app.flexonthejob.com/docs. This is the map.

ResourceOperationsPermission
ModulesGET /modules — active modules and the key's permissions.any key
CustomersGET /customers · POST /customers · GET /customers/{id} · PUT /customers/{id} · GET /customers/{id}/locationsViewCustomers / ManageCustomers
JobsGET /jobs · POST /jobs (estimate or draft, with material, labor and custom-charge lines) · GET /jobs/{id} · POST /jobs/{id}/status · POST /jobs/{id}/assignees · POST /jobs/{id}/reschedule · POST /jobs/{id}/cancel (returns materials to stock)ViewJobs / ManageJobs
ScheduleGET /schedule — calendar events in a date range · GET /schedule-groups · GET /usersScheduling moduleViewJobs
ItemsGET /items — templates with pricing and total on-hand · GET /items/{id} — properties, price tiers, barcodes and per-location stockViewInventory
InvoicesGET /invoices · GET /invoices/{id} (lines and payments) · POST /invoices/from-job (draft, same defaults as the Generate Invoice page) · POST /invoices/{id}/mark-sentViewInvoices / ManageInvoices

Paths are relative to https://app.flexonthejob.com/api/v1. Next up on the API roadmap: CSV export on list endpoints, updatedSince filters for incremental sync, and inventory transfer and low-stock events — see the roadmap.

Webhooks

Signed, retried, logged.

Subscribe a URL to the events you care about under Settings → Integrations → Webhooks. Flex POSTs a JSON envelope, signs it, retries on failure and shows you every delivery attempt.

EventFires when
job.createdA job or estimate is created — from the API, the web app or a synced device.
job.status_changedA job moves to another status; data.previousStatus is included.
job.scheduledA job gets a start time or is rescheduled.
job.assignedThe crew on a job changes.
job.completedA job reaches a completed status.
invoice.createdAn invoice is created.
invoice.paidAn invoice is paid in full.
customer.createdA customer is created.
  • Signature: X-Flex-Signature: sha256=<hex> — HMAC-SHA256 of the raw body, keyed with the subscription secret.
  • Retries: up to 5 attempts with exponential backoff (2, 4, 8, 16, 32 s…), 10 s timeout per attempt. Any 2xx counts as delivered.
  • Auto-disable: after 20 consecutive failed deliveries the subscription is paused and shown as inactive; re-enable it when your endpoint is back.
  • Delivery log: every attempt — status code, timing, response — in Settings.
  • Endpoint rules: HTTPS only, redirects are never followed, private and link-local addresses are refused.
POST https://yourapp.example/hooks/flex
Content-Type: application/json
X-Flex-Signature: sha256=3f7a…c21e

{
  "event": "job.status_changed",
  "occurredAt": "2026-09-20T14:03:11Z",
  "organizationId": 13,
  "data": {
    "id": 4,
    "jobNumber": "JOB-00004",
    "customerId": 9,
    "status": "In Progress",
    "previousStatus": "Scheduled",
    "scheduledDate": "2026-09-20T13:30:00Z",
    "assigneeUserIds": ["u_luis", "u_kelsey"]
  }
}
verify.js — Node
import { createHmac, timingSafeEqual } from "node:crypto";

export function verify(rawBody, header, secret) {
  const expected = "sha256=" +
    createHmac("sha256", secret).update(rawBody).digest("hex");
  return header.length === expected.length &&
    timingSafeEqual(Buffer.from(header), Buffer.from(expected));
}
// Compute over the raw bytes, before any JSON parsing.
MCP server

Let an agent do the paperwork.

Flex speaks the Model Context Protocol natively. Connect Claude, Cursor, or any MCP client to https://app.flexonthejob.com/mcp with an API key and it gets typed tools for the same operations as the REST API — under the same permissions, the same rate limits and the same request log.

  • Streamable HTTP transport, stateless — no session to manage, works behind any proxy.
  • Auth is the same Authorization: Bearer flx_live_… header. No key, no tools.
  • Call get_org_capabilities first; it reports the active modules and the key's permissions so the agent knows what will succeed.
  • Nothing is inferred or hidden: every tool maps to a documented service operation, and writes are visible in the job history like any other change.
ToolsNeeds
get_org_capabilitiesany key
list_customers · get_customer · create_customerViewCustomers / ManageCustomers
list_jobs · get_job · create_job · update_job_statusViewJobs / ManageJobs
reschedule_job · assign_job · get_scheduleManageJobs / ViewJobs + Scheduling
list_items · get_itemViewInventory
list_invoices · get_invoice · create_invoice_from_jobViewInvoices / ManageInvoices
Resources: flex://org/modules · flex://job/{id}any key / ViewJobs
Claude Code
claude mcp add flex --transport http \
  https://app.flexonthejob.com/mcp \
  --header "Authorization: Bearer flx_live_…"
mcp.json — Cursor, Claude Desktop, others
{
  "mcpServers": {
    "flex": {
      "type": "http",
      "url": "https://app.flexonthejob.com/mcp",
      "headers": { "Authorization": "Bearer flx_live_…" }
    }
  }
}

A good first prompt

“What's scheduled for the Service crew next week, and which of those jobs still has a pending stock transfer?” — the agent calls get_schedule, then get_job per result, and never sees anything the key wasn't granted.

Give an agent a read-only key first. Add ManageJobs when you want it to reschedule or assign.

Stability

Versioning and change policy.

/api/v1 is additive-only

We add endpoints, fields, enum values and webhook events. We do not rename or remove fields, change types, or change the meaning of an existing value within v1. Treat unknown fields and unknown event names as ignorable and you will never break on a release.

Breaking changes get a new prefix

If we ever need to break something it ships as /api/v2 alongside v1. The old version keeps working for at least 12 months after the new one is announced, with the deprecation date in the docs, in llms.txt and by email to every organization that holds an active key.

Webhook and MCP contracts follow the same rule

Event names and envelope fields are append-only. MCP tool names and parameters are stable; new optional parameters may appear. The OpenAPI document is generated from the running code, so it is never out of date with what the server actually does.

Changes are listed in the API changelog. Questions about a planned integration: developers@flexonthejob.com.

Questions

For the person writing the code.

Is the API extra?

No. API keys, webhooks and the MCP server are in the base plan at every term. There is no developer tier, no per-call fee and no seat consumed by a key.

Is there a sandbox?

Your 30-day trial is a full account, and that is the easiest place to build against — create a key, point your code at it, and nothing you do touches anyone's real data. We also run a separate sandbox environment where webhook delivery and email are switched off; if you're evaluating Flex for an integration or a review and want a key on it, ask us.

Can I generate a client library?

Yes — the OpenAPI document at /openapi/v1.json is a standard OpenAPI 3 file. Feed it to openapi-generator, Kiota, NSwag, openapi-typescript or whatever your stack prefers. We don't publish our own SDKs yet; the spec is the contract.

How do I get everything out?

Every list endpoint pages through the whole collection with after=<nextCursor>, and single-resource GETs return the full record. Invoices also export as CSV from the web app for QuickBooks. A full account export on request is part of the data policy.

Will an AI agent using MCP see data it shouldn't?

No. The MCP server reuses the API key's permission set and organization scope exactly. A key with ViewJobs only cannot list customers, invoices or items, and no key can reach another organization.

What about webhooks for inventory transfers?

Not yet — today's events cover jobs, invoices and customers. Transfer and low-stock events are the next additions; they'll appear as new event names without changing existing ones.

Where does the data live, and is there a SOC 2 report?

See the security page. Short version: TLS everywhere, hashed credentials, tenant isolation on every query, signed webhooks, and no SOC 2 report yet — we say so rather than imply otherwise.

Open the reference and try a call.

Start a free trial, create a key, paste it into the docs. Ten minutes to your first request.

API reference Start free trial