Integrations
FRT Connect API
Connect apps, automations, and AI agents to Flat Rate Tracker.
Connect apps, automations, and AI agents to Flat Rate Tracker.
FRT Connect provides authenticated programmatic access to supported Flat Rate Tracker functionality for the signed-in user’s own account. It is a Pro feature.
Base URL
https://api.flatratetracker.com/connect/v1
All FRT Connect V1 endpoints use this base URL and authenticate with an FRT Connect API key.
Supported in V1
- List and search saved Jobs
- List and search Work Orders (paginated)
-
Create Work Orders (optional initial
flag_hours) - Set or clear Work Order flagged hours
- Upload Work Order photos
- List Work Order photos and obtain temporary download URLs
- Retrieve Work Orders
- Delete Work Orders
V1 does not include Scan RO, billing, Team management, PayTrack, ToolTrack, webhooks, reporting/stats endpoints, or a public developer portal.
Getting started
- Sign in to Flat Rate Tracker.
- Open Account → API Access.
- Create an API key (requires Pro).
- Copy the full key when it is shown and store it securely.
-
Send the key as an
Authorization: Bearertoken. - Call endpoints under the Connect V1 base URL above.
The complete key is shown only once at creation. Listing keys later never reveals the secret.
Authentication
Authorization: Bearer frt_live_example.EXAMPLE_SECRET
Use your real key in place of the example. The example credential above is fictional and will not work.
Key format
frt_live_<publicId>.<secret>
Important
- Treat API keys like passwords.
- Store them server-side or in a secrets manager.
- Never commit keys to source control.
- Never embed keys in publicly distributed client-side code.
- The full credential cannot be retrieved again after creation.
- Revoke a compromised key immediately.
- Prefer one key per integration when practical.
Rotation
There is no in-place secret rotation. To rotate:
- Create a new key.
- Update the integration to use the new key.
- Confirm it works.
- Revoke the old key.
Revoked keys stop working immediately and cannot be restored.
API keys authenticate FRT Connect only. They cannot create or manage other API keys, change billing, manage Team membership, or perform account administration. Use the API key Bearer token on every Connect request — browser session cookies are not accepted.
Saved Jobs
GET /jobs
List the authenticated user’s saved Jobs.
Optional query parameters:
| Param | Description |
|---|---|
q |
Name search (case-insensitive). When present, returns matching saved Jobs by name. |
usage |
Default includes usage stats. Pass
usage=false to omit them.
|
sort |
When not searching: name (default),
used, or recent.
|
Example:
GET /connect/v1/jobs?q=brake
Authorization: Bearer frt_live_example.EXAMPLE_SECRET
Example response:
{
"jobs": [
{
"id": "66f0aaaaaaaaaaaaaaaaaaaa",
"name": "Brake pads",
"hours": 1.5,
"pay_type": "customer",
"times_used": 12,
"last_used_date": "20260920"
}
]
}
Use a returned Job id as
saved_job_id when creating a Work Order. Only
Jobs accessible to the authenticated account are listed or
searchable.
GET /jobs?q=<search>
Same endpoint with q set. Search uses
case-insensitive name matching over the user’s accessible
saved Jobs.
List Work Orders
GET /work-orders
List Work Orders owned by the authenticated account. Results are always scoped to that account — clients never send a user or tenant identifier.
Headers:
| Header | Required | Description |
|---|---|---|
Authorization |
yes | Bearer API key |
Query parameters:
| Param | Type | Required | Notes |
|---|---|---|---|
limit |
integer | no |
Page size. Default 50. Minimum
1. Maximum 100.
|
cursor |
string | no |
Opaque continuation token from a previous response’s
next_cursor.
|
from |
string | no |
Inclusive start date YYYY-MM-DD (Work
Order date).
|
to |
string | no |
Inclusive end date YYYY-MM-DD (Work Order
date).
|
search |
string | no | Free-text search (see below). |
Unknown query parameters are rejected with 400
UNSUPPORTED_FIELD. Invalid limit,
cursor, or dates are rejected with
400 (INVALID_LIMIT,
INVALID_CURSOR, INVALID_DATE, or
INVALID_DATE_RANGE).
Ordering
-
Without
from/to: newest Work Order date first, then newestcreated_at, then id. -
With
fromand/orto: same newest-relevant ordering Flat Rate Tracker uses for date-bounded Work Order lists (Work Orders whose Work Order date falls in the requested window).
from or to may be sent alone. When
only one bound is present, the other side is treated as open.
Pagination
Responses never dump an entire history in one call.
{
"work_orders": [ /* Work Order objects */ ],
"next_cursor": "…"
}
-
next_cursoris an opaque string when more results remain. -
next_cursorisnullwhen there is no next page. -
Pass
next_cursorback ascursorunchanged to continue. Do not construct cursors yourself.
Search
search matches (case-insensitive) across:
- RO / Work Order number (
ro_number) descriptionvehiclevinodometeradvisortag- job names on the Work Order
Numeric-only search also matches RO number prefixes (for
example search=123 can match RO
12345).
Filters not supported in V1
pay_type and flagged query filters
are not available. Unknown filter names are
rejected rather than ignored. Agents can filter or total hours
from returned Work Order objects after listing.
Example
GET /connect/v1/work-orders?from=2026-09-01&to=2026-09-30&search=Corolla&limit=5
Authorization: Bearer frt_live_example.EXAMPLE_SECRET
{
"work_orders": [
{
"id": "66f0cccccccccccccccccccc",
"ro_number": "54821",
"date": "2026-09-29",
"description": "Customer waiting",
"vehicle": "2022 Toyota Corolla",
"vin": "1FTFW1E57MFA18462",
"odometer": "64218",
"advisor": "Mike",
"tag": "A17",
"booked_hours": 2.3,
"flagged_hours": 2.3,
"jobs": [
{
"name": "Brake pads",
"hours": 1.5,
"pay_type": "customer",
"saved_job_id": "66f0aaaaaaaaaaaaaaaaaaaa"
},
{
"name": "Diagnosis",
"hours": 0.8,
"pay_type": "internal",
"saved_job_id": null
}
],
"created_at": "2026-09-29T23:10:00.000Z"
}
],
"next_cursor": null
}
Agent-oriented examples:
-
Last five Work Orders:
GET /work-orders?limit=5 -
Yesterday’s Work Orders:
GET /work-orders?from=YYYY-MM-DD&to=YYYY-MM-DD(same calendar day) -
Find RO 12345:
GET /work-orders?search=12345 -
Corolla ROs last month:
GET /work-orders?from=YYYY-MM-DD&to=YYYY-MM-DD&search=Corolla -
Warranty hours this month: list with
from/to, then sumjobs[].hourswherepay_typeiswarranty(and/or useflagged_hourson each Work Order as needed)
Each list item uses the same public Work Order object shape as
GET /work-orders/:id and create responses.
Create a Work Order
POST /work-orders
Create a Work Order for the authenticated account. Connect uses Flat Rate Tracker’s normal Work Order creation behavior (validation, job snapshots, pay calculation, and flagging).
Headers:
| Header | Required | Description |
|---|---|---|
Authorization |
yes | Bearer API key |
Idempotency-Key |
recommended | Unique string per intended create (max 100 characters). Safe retries return the same Work Order. |
Content-Type |
yes | application/json |
Body fields:
| Field | Type | Required | Notes |
|---|---|---|---|
ro_number |
string | yes | Repair Order / Work Order number |
date |
string | yes | YYYY-MM-DD |
jobs |
array | yes | At least one job line (see below). Connect does not accept top-level hours. |
description |
string | no | Short description shown in Flat Rate Tracker |
vehicle |
string | no | |
vin |
string | no | Normalized to uppercase |
odometer |
string | no | Maps to Work Order mileage (stored as trimmed text, max 12 characters) |
advisor |
string | no | |
tag |
string | no | RO / vehicle tag identifier (preserve leading zeros; not coerced to a number) |
flag_hours |
number | no | Initial flagged hours. See Initial flagging below. |
Unknown top-level fields and unknown job properties are
rejected with UNSUPPORTED_FIELD. Do not send
internal Flat Rate Tracker fields (for example
notes, ownership IDs, or pay snapshots).
Ownership is determined by the authenticated API key’s account. Clients cannot assign a Work Order to another Flat Rate Tracker user. API consumers do not send pay snapshots or expected earnings — Flat Rate Tracker calculates pay internally.
Initial flagging
Connect maps flag_hours into Flat Rate Tracker’s
normal create-time flagging behavior. Omitting the field
preserves the historical Connect default (fully flagged).
flag_hours |
Result |
|---|---|
| omitted | Work Order is fully flagged at creation (same as Quick Add / prior Connect behavior). |
0 |
Work Order is created completely unflagged. No zero-hour allocation is written. |
N where N > 0 |
Work Order is created with
exactly N hours
initially flagged.
|
Rules:
-
Values must be in tenths (one decimal place), from
0through99.9(the same individual hours limit Flat Rate Tracker uses for a single flag allocation). -
Partial flagging and overflagging (when
Nexceeds booked hours) follow Flat Rate Tracker’s normal create rules. -
Invalid, negative, non-numeric, or out-of-range values are
rejected with
422.
Examples:
Fully flagged (omit flag_hours — backward
compatible):
{
"ro_number": "54821",
"date": "2026-09-29",
"jobs": [{ "name": "Diagnosis", "hours": 2.5, "pay_type": "customer" }]
}
→ flagged_hours equals
booked_hours (for example 2.5).
Unflagged:
{
"ro_number": "54822",
"date": "2026-09-29",
"flag_hours": 0,
"jobs": [{ "name": "Diagnosis", "hours": 2.5, "pay_type": "customer" }]
}
→ flagged_hours is 0.
Partially flagged:
{
"ro_number": "54823",
"date": "2026-09-29",
"flag_hours": 2.5,
"jobs": [{ "name": "Diagnosis", "hours": 5.0, "pay_type": "customer" }]
}
→ flagged_hours is 2.5.
Successful create and successful idempotent replay both return
201 with a
work_order object.
Job formats
Each job line must include either a
saved_job_id or both name and
hours.
jobs[].name is the Job description/name (not the
Work Order short description).
pay_type values:
customerwarrantyinternal
These map to Flat Rate Tracker’s labor types. Invalid values
return 422 with INVALID_PAY_TYPE.
#### Saved Job reference
When saved_job_id is provided, omitted job values
are inherited from the saved Job. Supplying name,
hours, or pay_type overrides that
value for this Work Order only and does not modify the saved
Job.
-
Minimum request:
{ "saved_job_id": "…" }— inheritsname,hours, andpay_type. -
pay_typemay be sent alone to override labor type while still inheriting name and hours. -
To override name and/or hours, send both
nameandhourstogether withsaved_job_id. Sending only one of those fields is ignored and the saved Job name/hours are used. - The Work Order stores a snapshot of the resolved name, hours, and pay type.
-
saved_job_idmust identify a saved Job accessible to the authenticated account. Unknown or inaccessible IDs return404withUNKNOWN_SAVED_JOB.
Minimum saved-job reference:
{
"saved_job_id": "66f0aaaaaaaaaaaaaaaaaaaa"
}
Override pay type only:
{
"saved_job_id": "66f0aaaaaaaaaaaaaaaaaaaa",
"pay_type": "warranty"
}
Override name and hours (and optionally pay type) for this Work Order only:
{
"saved_job_id": "66f0aaaaaaaaaaaaaaaaaaaa",
"name": "Brake pads",
"hours": 1.6,
"pay_type": "warranty"
}
#### One-time Job
One-time Jobs require name and
hours. If pay_type is omitted, it
defaults to customer.
{
"name": "Diagnosis",
"hours": 1.0
}
Job hours must be in tenths (for example 1.2) and
at least 0.1.
Example create request
POST /connect/v1/work-orders
Authorization: Bearer frt_live_example.EXAMPLE_SECRET
Idempotency-Key: agent-run-7f3c-ro-54821
Content-Type: application/json
{
"ro_number": "54821",
"date": "2026-09-29",
"description": "Customer waiting",
"vehicle": "2022 Toyota Camry",
"vin": "1FTFW1E57MFA18462",
"odometer": "64218",
"advisor": "Mike",
"tag": "A17",
"jobs": [
{
"saved_job_id": "66f0aaaaaaaaaaaaaaaaaaaa"
},
{
"name": "Diagnosis",
"hours": 0.8,
"pay_type": "internal"
}
]
}
Example 201 response:
{
"work_order": {
"id": "66f0cccccccccccccccccccc",
"ro_number": "54821",
"date": "2026-09-29",
"description": "Customer waiting",
"vehicle": "2022 Toyota Camry",
"vin": "1FTFW1E57MFA18462",
"odometer": "64218",
"advisor": "Mike",
"tag": "A17",
"booked_hours": 2.3,
"flagged_hours": 2.3,
"jobs": [
{
"name": "Brake pads",
"hours": 1.5,
"pay_type": "customer",
"saved_job_id": "66f0aaaaaaaaaaaaaaaaaaaa"
},
{
"name": "Diagnosis",
"hours": 0.8,
"pay_type": "internal",
"saved_job_id": null
}
],
"created_at": "2026-09-29T23:10:00.000Z"
}
}
Retrieve a Work Order
GET /work-orders/:id
Fetch a Work Order owned by the authenticated account. Other
accounts’ Work Order IDs return 404. Manager/Team
peek is not available through Connect.
GET /connect/v1/work-orders/66f0cccccccccccccccccccc
Authorization: Bearer frt_live_example.EXAMPLE_SECRET
The response uses the same work_order object
shape as create.
Set Work Order flagged hours
PUT /work-orders/:id/flagging
Set the desired total currently flagged hours on a Work Order owned by the authenticated account. This is not a delta and does not expose Flat Rate Tracker’s internal allocation rows.
Headers:
| Header | Required | Description |
|---|---|---|
Authorization |
yes | Bearer API key |
Content-Type |
yes | application/json |
Body:
| Field | Type | Required | Notes |
|---|---|---|---|
flag_hours |
number | yes |
Desired total flagged hours after the call. Tenths
precision. Range 0–99.9.
|
Semantics:
flag_hours |
Result |
|---|---|
0 |
Completely unflag the Work Order (flagged_hours
becomes 0).
|
N where N > 0 |
Reconcile so flagged_hours becomes
exactly N.
|
Connect performs this by calling Flat Rate Tracker’s existing flag allocation create / update / delete operations (same domain path the app uses). Allocation dates, pay-rate snapshots, paid state, and history are preserved according to those operations. Overflagging relative to booked hours is allowed when you intentionally set a total above booked hours (same as confirming an over-flag in the app).
Unknown body fields are rejected with
UNSUPPORTED_FIELD. Other accounts’ Work Order IDs
return 404 WORK_ORDER_NOT_FOUND (no
existence leak). Invalid values return 422.
Example — unflag:
PUT /connect/v1/work-orders/66f0cccccccccccccccccccc/flagging
Authorization: Bearer frt_live_example.EXAMPLE_SECRET
Content-Type: application/json
{
"flag_hours": 0
}
Example — set total flagged hours to 2.5:
{
"flag_hours": 2.5
}
Successful response is 200 with
the updated work_order object (same shape as
create/get).
Delete a Work Order
DELETE /work-orders/:id
Permanently delete a Work Order owned by the authenticated account. Connect uses the same delete operation as Flat Rate Tracker itself: the Work Order and its flag allocations (and attachments) are removed. Deletion is not reversible.
Headers:
| Header | Required | Description |
|---|---|---|
Authorization |
yes | Bearer API key |
Successful response:
204 No Content (empty body).
Other accounts’ Work Order IDs, unknown IDs, and
repeated deletes of an already-deleted Work
Order all return 404
WORK_ORDER_NOT_FOUND. Clients cannot delete
another user’s Work Order by guessing an ID.
DELETE /connect/v1/work-orders/66f0cccccccccccccccccccc
Authorization: Bearer frt_live_example.EXAMPLE_SECRET
Upload a Work Order photo
POST /work-orders/:id/photos
Attach an image to a Work Order owned by the authenticated account. The photo is stored through Flat Rate Tracker’s normal Work Order Photos / attachment pipeline and appears in the Work Order Photos area in the app exactly like a photo attached in Flat Rate Tracker.
This endpoint only uploads an attachment. It does not run Scan RO, OCR, vision, or any AI processing.
Headers:
| Header | Required | Description |
|---|---|---|
Authorization |
yes | Bearer API key |
Content-Type |
yes |
multipart/form-data (set automatically by
most HTTP clients when using -F / form
upload)
|
Multipart fields:
| Field | Required | Notes |
|---|---|---|
file |
yes |
Image bytes. Field name must be file.
|
Supported formats (declared MIME / content type):
-
image/jpeg(.jpg/.jpeg) image/pngimage/webpimage/heicimage/heif
PDFs and other non-image types are rejected. Maximum file size: 20 MB (same as Flat Rate Tracker Work Order attachments).
Other accounts’ Work Order IDs return 404
WORK_ORDER_NOT_FOUND. Missing/empty files,
unsupported types, and oversized uploads return
400 with stable codes such as
MISSING_FILE, INVALID_ATTACHMENT,
invalid_file_type, or
file_too_large.
Idempotency-Key is not supported for photo uploads in V1 (Connect idempotency currently applies to Work Order create JSON only). Retries may create additional attachments — avoid blindly retrying a successful upload.
Example:
curl -X POST \
https://api.flatratetracker.com/connect/v1/work-orders/WORK_ORDER_ID/photos \
-H "Authorization: Bearer frt_live_example.EXAMPLE_SECRET" \
-F "[email protected]"
Example 201 response:
{
"photo": {
"id": "66f0eeeeeeeeeeeeeeeeeeee",
"file_name": "repair-order.jpg",
"mime_type": "image/jpeg",
"size_bytes": 248391,
"kind": "photo",
"created_at": "2026-09-30T18:40:00.000Z"
}
}
List Work Order photos
GET /work-orders/:id/photos
List photos attached to a Work Order owned by the authenticated account. Returns metadata only (no image bytes and no permanent download URL).
Photos are ordered newest-first (same ordering Flat Rate Tracker uses for Work Order attachments).
An owned Work Order with no photos returns
{ "photos": [] } rather than an error. Other
accounts’ Work Order IDs return 404
WORK_ORDER_NOT_FOUND.
GET /connect/v1/work-orders/66f0cccccccccccccccccccc/photos
Authorization: Bearer frt_live_example.EXAMPLE_SECRET
Example response:
{
"photos": [
{
"id": "66f0eeeeeeeeeeeeeeeeeeee",
"file_name": "repair-order.jpg",
"mime_type": "image/jpeg",
"size_bytes": 248391,
"kind": "photo",
"created_at": "2026-09-30T18:40:00.000Z"
}
]
}
Download / view a Work Order photo
GET /work-orders/:id/photos/:photoId
Obtain a temporary download URL for one photo on a Work Order you own.
Flat Rate Tracker authenticates the request, verifies the photo belongs to that Work Order, and returns a short-lived URL. The image bytes are then downloaded directly from storage — they are not streamed or buffered through the Connect API.
| Field | Description |
|---|---|
photo |
Same public photo object as list/upload |
url |
Temporary download/view URL |
expires_in |
Lifetime of url in seconds (currently
300 / 5 minutes)
|
After expires_in elapses, request a new URL.
There are no permanent or public attachment URLs.
Unauthorized Work Orders, photos that belong to a different
Work Order, non-photo attachments, and unknown IDs return
404 (WORK_ORDER_NOT_FOUND or
PHOTO_NOT_FOUND) without revealing whether
another user’s attachment exists.
curl -X GET \
https://api.flatratetracker.com/connect/v1/work-orders/WORK_ORDER_ID/photos/PHOTO_ID \
-H "Authorization: Bearer frt_live_example.EXAMPLE_SECRET"
Example response:
{
"photo": {
"id": "66f0eeeeeeeeeeeeeeeeeeee",
"file_name": "repair-order.jpg",
"mime_type": "image/jpeg",
"size_bytes": 248391,
"kind": "photo",
"created_at": "2026-09-30T18:40:00.000Z"
},
"url": "https://example-temporary-download.example/…",
"expires_in": 300
}
Clients should then GET the returned
url to download the image. Do not treat
url as a durable link.
Idempotency
Send an Idempotency-Key header when creating Work
Orders.
Idempotency-Key: agent-run-7f3c-ro-54821
Generate a unique Idempotency-Key for each intended Work Order creation.
If a request may have succeeded but the client is unsure (timeout, network failure), retry the same Work Order request with the same Idempotency-Key.
Behavior
| Situation | Result |
|---|---|
| First successful create |
201 with the created
work_order
|
| Same Idempotency-Key + same/equivalent Work Order request |
201 with the originally created Work
Order. Does not create a duplicate.
|
| Same Idempotency-Key + different Work Order request |
409 Conflict with
IDEMPOTENCY_KEY_REUSE
|
| Concurrent identical requests with the same key | At most one Work Order is created |
Equivalent means the normalized create payload fingerprints
the same (for example VIN casing and numeric hours like
2 vs 2.0 are treated as equivalent
after normalization).
Example conflict response:
{
"error": {
"code": "IDEMPOTENCY_KEY_REUSE",
"message": "This Idempotency-Key was already used with a different request."
},
"code": "IDEMPOTENCY_KEY_REUSE",
"message": "This Idempotency-Key was already used with a different request."
}
Do not use the RO number itself as the Idempotency-Key unless you can guarantee the semantics you need. Prefer a generated unique request identifier. RO numbers are not globally unique.
Errors
Connect returns JSON with a stable code nested
under error and mirrored at the top level:
{
"error": {
"code": "INVALID_API_KEY",
"message": "Invalid API key."
},
"code": "INVALID_API_KEY",
"message": "Invalid API key."
}
| Status | Codes |
|---|---|
400 |
MALFORMED_REQUEST,
UNSUPPORTED_FIELD,
INVALID_IDEMPOTENCY_KEY,
INVALID_LIMIT,
INVALID_CURSOR,
INVALID_DATE,
INVALID_DATE_RANGE,
MISSING_FILE,
INVALID_ATTACHMENT,
invalid_file_type,
file_too_large
|
401 |
API_KEY_REQUIRED,
INVALID_API_KEY (includes missing,
malformed, invalid, and revoked keys)
|
402 |
payment_required (inactive
trial/subscription — applies to all Connect endpoints)
|
403 |
capability_required (missing Pro /
Connect entitlement),
not_tracked_technician (Work Order
create, flagging, photo upload, and delete),
CONNECT_ACCESS_DISABLED
|
404 |
WORK_ORDER_NOT_FOUND,
UNKNOWN_SAVED_JOB,
PHOTO_NOT_FOUND
|
409 |
IDEMPOTENCY_KEY_REUSE,
FLAG_OVER_EXPECTED
|
422 |
INVALID_WORK_ORDER,
INVALID_JOB,
INVALID_PAY_TYPE,
INVALID_FLAG_HOURS, invalid hours/date
|
429 |
RATE_LIMITED |
503 |
STORAGE_UNAVAILABLE |
CONNECT_ACCESS_DISABLED means Connect access for
the account has been disabled independently of the user’s
normal Flat Rate Tracker login, subscription, and app access.
Existing API keys are not deleted when Connect is disabled.
Rate limits
Applied only to /connect/v1:
- 120 requests per minute per API key
- 180 requests per minute per IP
Exceeding a limit returns 429 with
RATE_LIMITED. Responses include a
Retry-After header (seconds).
Limits protect service reliability. There is no daily quota in V1.
Flat Rate Tracker may rate-limit or disable Connect access
when necessary to protect the service. Disabling Connect
blocks /connect/v1 for that account only and does
not suspend the user’s Flat Rate Tracker
account, subscription, or normal app access.
Pro entitlement
FRT Connect requires Pro-level entitlement for Connect.
- Creating keys in Account → API Access requires Pro.
-
Every
/connect/v1request requires an active entitlement and Connect capability. -
Inactive trial/subscription returns
402payment_requiredon Connect reads and writes. -
Missing Connect capability (for example Solo while still
entitled) returns
403capability_required. - List and revoke remain available after a downgrade so keys can be cleaned up.
- Existing keys are not deleted or revoked when Pro ends; Connect requests stop until eligible entitlement returns.
-
Work Order creates, flagging updates, photo uploads, and
deletes also enforce Flat Rate Tracker’s tracked-technician
write rule (
403not_tracked_technicianwhen Team coverage is active and the member is not a tracked technician).
Security and key management
- Use one API key per integration.
- Give keys descriptive names.
- Store secrets server-side.
- Never commit credentials.
- Revoke exposed credentials immediately.
- Replace credentials when rotating or retiring an integration.
- Remove keys for integrations no longer in use.
Revoked credentials cannot be restored. Create a new key and revoke the previous one to rotate.
Machine-readable copy
The same documentation is available as Markdown at:
https://flatratetracker.com/connect.md