Integrations

FRT Connect API

Connect apps, automations, and AI agents to Flat Rate Tracker.

Machine-readable docs: https://flatratetracker.com/connect.md

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

  1. Sign in to Flat Rate Tracker.
  2. Open Account → API Access.
  3. Create an API key (requires Pro).
  4. Copy the full key when it is shown and store it securely.
  5. Send the key as an Authorization: Bearer token.
  6. 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:

  1. Create a new key.
  2. Update the integration to use the new key.
  3. Confirm it works.
  4. 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 newest created_at, then id.
  • With from and/or to: 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_cursor is an opaque string when more results remain.
  • next_cursor is null when there is no next page.
  • Pass next_cursor back as cursor unchanged to continue. Do not construct cursors yourself.

search matches (case-insensitive) across:

  • RO / Work Order number (ro_number)
  • description
  • vehicle
  • vin
  • odometer
  • advisor
  • tag
  • 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 sum jobs[].hours where pay_type is warranty (and/or use flagged_hours on 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 0 through 99.9 (the same individual hours limit Flat Rate Tracker uses for a single flag allocation).
  • Partial flagging and overflagging (when N exceeds 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:

  • customer
  • warranty
  • internal

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": "…" } — inherits name, hours, and pay_type.
  • pay_type may be sent alone to override labor type while still inheriting name and hours.
  • To override name and/or hours, send both name and hours together with saved_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_id must identify a saved Job accessible to the authenticated account. Unknown or inaccessible IDs return 404 with UNKNOWN_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/png
  • image/webp
  • image/heic
  • image/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/v1 request requires an active entitlement and Connect capability.
  • Inactive trial/subscription returns 402 payment_required on Connect reads and writes.
  • Missing Connect capability (for example Solo while still entitled) returns 403 capability_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 (403 not_tracked_technician when 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