Preview VersionThese are pre-release scores, not final grades. Every vendor's full markdown report is published here so the scoring can be checked line by line. A complete rerun follows in roughly 60 days. Found a factual error?

← All platforms

API Report Card · Maintenance · Methodology v1.1

FixGrid

Preliminary grade
C- 70/100
34.79 / 50 raw
Evidence tier
Fully verified, controlled live
Date run
Sep 28, 2026
Evaluating model
Claude Fable 5.1 and Claude Opus 5.5
Verification coverage
100%
Live-test battery
Complete
Checks
27 of 27 scored

NOTE: This grade is produced by an AI analysis run against the same published grading file, which you can download and check here. The scope is deliberately narrow: API and data access. It is not Peter Lohmann's opinion of FixGrid as a software platform, and it says nothing about FixGrid's features, reliability, interface, support, or standing in the industry.

Where the points came from.

Five categories, each worth a fixed share of the 100 points. A category earns the fraction of its checks it passes, times its maximum.

Letter grades are absolute, never curved.

The same numeric bands apply to every platform. Nothing here is scored relative to the rest of the board.

A+97-100
A93-96
A−90-92
B+87-89
B83-86
B−80-82
C+77-79
C73-76
C−70-72
D+67-69
D63-66
D−60-62
Fbelow 60

What this means for you.

One paragraph per category, in plain language.

1 · Functional Coverage

3.8 / 15 points

You can pull the whole maintenance record into your own tools, create work orders, and move them through their status lifecycle, including a clean close that requires a note and a cancel that requires a reason code. You can also feed move-ins and notices to vacate in from your PMS. What you cannot do is assign or dispatch work to a technician or vendor, edit a work order after it is filed, or manage vendors, appointments, estimates, invoices, owner approvals or tags; those stay inside the FixGrid app. Assignment is what costs the most, because create, assign and complete are the three critical maintenance workflows and one of them is missing. Webhooks tell you about new and changed tickets and unit turns, but not about assignment changes.

See technical details

2 · Design & Reliability

7.9 / 10 points

The engineering is solid for automation and AI agents. Every one of 1,158 captured records matched its published type, errors carry one shape with stable codes, an idempotency key really did stop a duplicate ticket live, throttling tells you exactly how long to wait, keyset paging walked all 398 tickets with no duplicates, the version contract promises 90 days' notice of breaking changes, and webhooks are signed and retried on a published schedule. The gaps: nothing stops two tools from silently overwriting each other's status change, there is no documented request ID to quote to support, there is no public status page (a written SLA comes only with Enterprise), and there is no bulk export or changed-since filter for units, properties, vendors or inspections.

See technical details

3 · Access Control

4.4 / 5 points

You can give each integration or AI agent its own labelled key, make it read-only, and revoke it yourself in the app, and each key gets its own rate-limit budget, so one tool cannot starve another. You cannot narrow a key to one property or one kind of record: a write key can create tickets, change statuses and record move-ins across the whole company. There is no sandbox, so testing happens in a real or demo company.

See technical details

4 · Docs & AI-Ready

3.8 / 5 points

A developer or an AI coding tool can generate a working client straight from the public OpenAPI 3.1 file, which matched the live API with zero deviations, and the changelog is dated and current. The reference lacks realistic request and response examples for the everyday calls, and its write examples are placeholders the API would refuse. There is no full-text llms file for AI tools, only an index of links, and a few documented error sentences do not match what the API actually returns, so build against the error codes, not the words.

See technical details

5 · Access & Cost

15 / 15 points

Every plan, including the $49 a month Starter plan, includes the API and webhooks with no add-on or partner fee, and your own Company Admin creates keys in the app without a sales call.

See technical details

Every check, and why it scored that way.

The same 27 checks are applied to every platform. What changes is which are N-A and what the core objects mean for that kind of software. Each mark below is quoted from the run's own report. Marks are AI-generated and cover API access only.

Category 1 · Functional Coverage

3.8 / 15
C1.1

Object coverage

weighted coverage = 59.5% (12.5 / 21); no critical object absent.** [judgment-sensitive]

Partial
C1.2

Core operational actions

weighted coverage = 41.7% (12.5 / 30); a critical write workflow ("assign it") is absent.** Under a workflows-only reading the coverage is 66.7% (6 / 9), and the mark is still no under the rule's critical-absent clause.

No
C1.3

Delete or lifecycle actions

weighted coverage = 38.1% (8 / 21); a critical lifecycle action (reassign/unassign) is absent.** [judgment-sensitive]

No
C1.4

Change notification

push covers 58.8% (10 / 17) of the critical-plus-important state changes.**

Partial

Category 2 · Design & Reliability

7.9 / 10
C2.1

Modern API conventions

.** Resource-oriented REST over JSON: 26 operations on 23 paths using GET, POST, PATCH and DELETE (reference.html "Operations"; openapi.json servers https://app.fixgrid.app/api/v1); a wrong verb gets 405 with an Allow header (errors.html method_not_allowed; live 044 and 045, allow: GET, PATCH). Live GET 007–034, POST 060 and 076, PATCH 066–083, DELETE 084.

Yes
C2.2

Consistent typing

.** [judgment-sensitive] Every schema in openapi.json components.schemas types every field (nullable as type arrays; enums for ticket status/priority, asset status, turn status, inspection status/result, unit occupancy, vendor compliance state, NTV and destination codes; format date, date-time, uri). Rendered examples are type-consistent (index /whoami; webhooks.html envelope, turn payload and 201 body; errors.html). Live: this run type-checked every preserved row against the published schemas — 1,158 list rows across nine schemas (all 398 tickets, 8 properties, 15 vendors, 85 inspections, 15 meters; 200 of 774 units; 200 of 5,388 assets; 232 of 708 turns; 5 readings), 15 single-object responses, the one webhook-subscription list row and the webhook delivery's data — with 0 type, enum, format, missing-key or undocumented-key deviations. Noted, not counted as type inconsistencies: the three card-only webhook events carry data.property / data.unit as label strings in a separately documented inline dict discriminated by event (webhooks.html "Three keys carry a small inline dict"); OccupancyRecord.status is documented as lower-case "active" while Turn.status is Title-Case (a value-casing, not a type, difference).

Yes
C2.3

Structured errors

.** One shape, {"error":{"code","message"}}; "code is stable and safe to branch on", nineteen codes each mapped to an HTTP status (errors.html "The contract", "The nineteen codes"; openapi.json components.schemas.Error). Live: all 30 error captures use that shape with a populated code and correct status — e.g. 035 400 invalid_cursor, 037 422 invalid_request, 004 401 unauthorized + WWW-Authenticate, 046 403 insufficient_scope, 042 404 not_found, 044 405 + Allow, 063 409 idempotency_mismatch, 050_rate_limit_429 429 rate_limited. Limitations noted: invalid_request covers many 400/422 situations (the docs ask you to branch on status and sentence); two live message sentences differ from the documented ones (064, 063) with the code unchanged.

Yes
C2.4

Duplicate prevention

.** Every consequential write requires an Idempotency-Key — ticket create, ticket status, notice, move-in, webhook create (errors.html idempotency_key_required; reference.html parameter tables). Live: 048 refuses a create without the key (400); 060 + 061 with the same key produced one ticket (3568; replay flagged x-idempotent-replay: 1; unit ticket count 11 → 12 in 059/062); 063 changed body with the same key → 409; 069 PATCH replay; 077–078 webhook create replayed (same subscription 11, secret omitted, reordered events treated as the same request as documented). Delete is naturally idempotent (089 → 404). Notice / move-in idempotency is documentation-graded.

Yes
C2.5

Graceful handling under load

.** A documented 429 with Retry-After as "An integer number of seconds" and a stated allowance of 120 per minute per key (limits.html "The limit", "On 429"; errors.html rate_limited; openapi.json x-rate-limit and a 429 response on every keyed operation). Live: 050_rate_limit_429 (429, retry-after: 30); 055 a second key unaffected; 056 recovered after 32 s. (The spec does not declare the Retry-After header object; the guides do.)

Yes
C2.6

Pagination for large collections

.** Keyset cursors, rows "ascending by id", next_cursor null at the end, inserts during a walk "do not shift or duplicate rows", default 50 / maximum 200 with the clamp documented (objects.html "The list envelope", "Paging"). Live: 007 + 008 traversed all 398 tickets with no duplicates; 009 limit=999 applied as 200; 010 default 50; 034 readings paging. There is no total count; the next-page token satisfies the check.

Yes
C2.7

Bulk or incremental export

.** [judgment-sensitive] No bulk, export or async-job operation exists among the 26 operations (openapi.json paths). Full datasets can be walked in 200-row pages, and updated_since supports incremental sync on tickets, assets, turns and meters (honored live: 026, 027, 032), but not on units, properties, vendors or inspections ("/properties and /vendors take limit and cursor only"; inspections "There is no updated_since"; units take property_id only — objects.html "Filters"). An account export exists only by contacting support (terms.html §7).

Partial
C2.8

Webhook security and delivery reliability

.** Signed: FixGrid-Signature with an HMAC-SHA256 over <timestamp>.<body>, Python and Node verification code and a 5-minute tolerance (webhooks.html "Verifying the signature"). Retry policy: first attempt on the hourly sweep, retries at 1 · 2 · 4 · 8 · 24 h, then parked, records kept 30 days, parked deliveries retryable by the admin (webhooks.html "Delivery, retries, and giving up"). Consumer guidance: dedupe on event_id, at-least-once delivery, timestamp tolerance against replay, per-object ordering by occurred_at (webhooks.html "The envelope", "Your endpoint"). Live: subscription 076; one delivery (1790607263438.json) with the signature header and the seven-key envelope in the documented order; HMAC verified valid as recorded in packet_manifest_v2_FINAL.md. Retries were not observable (the one delivery succeeded).

Yes
C2.9

Concurrency and conflict control

.** [judgment-sensitive] No ETag / If-Match or record version (schema_version is the payload-shape version — limits.html "schema_version is not the API version"); PATCH has no precondition and "any status is reachable from any other" (objects.html "Writing a ticket"); a search of all developer-host sources finds no ETag, If-Match, concurrency or conflict guidance; no live response carries an ETag (79 of 79). The only 409s are idempotency-key collisions (idempotency_in_progress, idempotency_mismatch), which prevent duplicate application of the same request — credited in C2.4 — but do nothing against two integrations overwriting each other's change.

No
C2.10

Versioning and backward compatibility

.** Version in the path, /api/v1, "If we ever publish a v2, v1 keeps answering"; explicit breaking and non-breaking lists; breaking changes "get 90 days' notice" (limits.html "Versioning", "These are breaking…", "These are not breaking…"); corroborated by changelog.html "Deprecation" (announced at least ninety days ahead, old behaviour keeps working). (This evidence is not reused in C4.4.)

Yes
C2.11

Request traceability

.** No request or correlation identifier is documented anywhere in the packet; the documented support path is "contact support with the time and the path" (errors.html internal_error). Live responses all carry rndr-id and CF-RAY (79 of 79) — hosting/CDN identifiers that FixGrid does not document or tie to support.

Partial
C2.12

Service availability and status transparency

.** No public status page (/status.html is 404 on both hosts — dev_status., www_status.; status.fixgrid.app does not resolve per the manifests). An SLA exists only inside Enterprise contracts ("Dedicated CSM + written SLAs"; "SLA-backed uptime — Written availability commitments" — pricing.html Enterprise card and "Why Enterprise costs what it does"); standard terms disclaim uninterrupted service (terms.html §10).

Partial

Category 3 · Access Control

4.4 / 5
C3.1

Read-only credentials

.** The read scope "opens every list and fetch" and nothing else (authentication.html "Scopes"). Live: 001 shows scope read; the read key is refused on a ticket create (046 → 403) and on a subscription delete (080 → 403).

Yes
C3.2

Scoped credentials

.** [judgment-sensitive] Three fixed scopes — read, write (every write plus read and webhooks) and webhook_only (subscriptions only) — "fixed at mint" (authentication.html "Scopes"; developers.fixgrid.app "What it carries"). Every key is company-wide (developers.fixgrid.app "What the key is"); a key cannot be limited to one property, one resource type or one action (e.g. create tickets but not record occupancy). Live: 047 a webhook_only key is refused on /tickets (403). Broad scoping only.

Partial
C3.3

Multiple keys

.** Keys are created and labelled per integration (developers.fixgrid.app "How you get one"), and the rate limit is counted per key "so two integrations at the same customer never spend each other's budget" (limits.html "The limit"). Live: three distinct keys in use (001–003); 055 one key's throttle left another unaffected.

Yes
C3.4

Rotation and revocation

.** The Company Admin can "Revoke it on the same tab at any time", and a lost key is replaced by minting a new one and revoking the old (developers.fixgrid.app "How you get one", "If you have lost a value"); a revoked key answers 401 key_revoked (errors.html). Self-serve for the account owner; documentation-graded (revocation was not captured).

Yes
C3.5

Test and production isolation

.** No sandbox or separate test environment is documented (keys begin fg_live_; no sandbox, test-key or staging material anywhere in the packet). The battery ran on the operator's demonstration company in production.

N-A

Category 4 · Docs & AI-Ready

3.8 / 5
C4.1

Complete self-serve reference

.** Coverage is complete — all 26 operations with parameters, request bodies, every response code with its error sentences, and full field tables, plus authentication, error and webhook guides (reference.html; authentication.html; errors.html; webhooks.html). Worked examples are thin for the core endpoints: every operation has a curl request, but the write examples are generated placeholders (reference.html POST /tickets: "unit_id":0 … "priority":"string","category":"string", values that would be refused), and there are no response examples in the reference or the OpenAPI document (zero example keys). Rendered response JSON exists only for /whoami, the list-envelope skeleton, error bodies and webhook payloads — not for listing, fetching, creating or updating a ticket, unit, turn or vendor. Documentation-versus-live contradictions observed: the status-in-create refusal reads "The server sets status; omit it." live (064) versus the documented "This endpoint always creates a ticket with status 'Open'. Remove status from the request body."; the create mismatch reads "different ticket request" live (063) versus the documented "different ticket payload"; webhook events are documented as "Stored sorted and deduplicated" but were echoed and listed in request order (076, 079); authentication.html says no-store applies to "Every response on the namespace", but the 404-path and 405 responses carried no Cache-Control (043, 044, 045). The site footer's claim that every error sentence is copied from the running product is therefore not fully borne out.

Partial
C4.2

Reliable machine-consumable integration path

.** A public OpenAPI 3.1 document at developers.fixgrid.app/openapi.json and app.fixgrid.app/api/v1/openapi.json (byte-identical, no key needed) covers all 26 operations with typed schemas, enums, required fields and the bearer security scheme; this run found 0 deviations between it and the live data. No official SDK and no MCP server (developers.fixgrid.app warns never to put a key in a Claude/MCP connector); one strong mechanism suffices. Limitations: no operationIds (generators must synthesize names), no examples, Retry-After not declared as a response header.

Yes
C4.3

AI-readable documentation

.** developers.fixgrid.app/llms.txt (1,515 bytes) is an index of links with one-line summaries (dev_llms.txt); llms-full.txt is 404 (dev_llms-full.txt "Not Found"); there is no per-endpoint Markdown or plain-text corpus. The marketing llms.txt carries a "Developer API" section of links only (www_llms.txt). Index-only.

Partial
C4.4

Kept current

.** [judgment-sensitive] The changelog records every change with dates from v1 on 2026-09-08 through 2026-09-24, naming the objects and routes affected ("Every change to the API, dated. Additive changes land here on the day they ship"); the reference is "Generated from openapi.json — the same document the API serves" and "The reference and this page both change with the same publish" (reference.html; changelog.html "Watching this page"); sitemap lastmod dates run 2026-09-12 to 2026-09-24 (dev_sitemap.xml); the two OpenAPI copies are identical and matched live data on 2026-09-28. Blemish: the 2026-09-12 Turn entry is marked "Documented here 2026-09-24" — a 12-day lag against the same-day promise, disclosed by the changelog itself; there is no email list.

Yes

Category 5 · Access & Cost

15 / 15
C5.1

Self-serve API key

.** "A Company Admin mints the key inside FixGrid" on Integrations → Developers; "You do not apply to FixGrid for a key" (developers.fixgrid.app "How you get one", "Talk to us"); security.html "Access control". Live: the three keys used were minted by the operator (packet_manifest_v2_FINAL.md; 001–003).

Yes
C5.3

Not commercially gated

.** [judgment-sensitive] "Included on every plan. Starter, Professional, and Enterprise. No partner fee." (developers.fixgrid.app "What it carries"); "Public API + signed webhooks — Included on every plan … No partner fee, no API add-on." and the "On every plan" list (pricing.html); the cheapest plan is Starter at $49/mo. The API documentation shows no plan gating of any endpoint or scope and no plan-related error code. Note: some product modules whose data sits behind API objects — Grid Auriga vendor management, the full asset registry, utilities — are Professional-tier product features (pricing.html "Compare plans"), so a Starter company would have less data to read; this is product packaging, not an API entitlement.

Yes

What works

  • Every captured record matched its published schema: 1,158 rows across nine object types, zero deviations
  • Idempotency keys required on every consequential write, and a replayed create made one ticket, not two
  • One error shape with nineteen stable codes, confirmed across 30 live error responses
  • A documented 429 with Retry-After and per-key budgets, tripped and recovered live
  • Keyset pagination walked all 398 tickets with no duplicates
  • Signed webhooks with a published retry ladder, and a signed delivery received live
  • Versioned path with a written promise of 90 days' notice before breaking changes
  • Read-only keys, multiple labelled keys, and self-serve revocation
  • A public OpenAPI 3.1 file that matched live data, plus a dated changelog
  • API and webhooks on every plan with no partner fee, and keys minted by your own admin

What to watch

  • No way to assign or dispatch a work order to a technician or vendor
  • Work orders cannot be edited after creation: only status, closing note and cancellation fields are writable
  • Vendors, appointments and inspections are read-only; estimates, invoices, owner approvals and tags are absent
  • No webhook event for assignment changes
  • No ETag or version check, so two tools can overwrite each other's status change
  • No changed-since filter on units, properties, vendors or inspections, and no bulk export
  • No documented request ID to quote to support
  • No public status page; a written SLA only on Enterprise
  • Keys are company-wide: none can be limited to one property or one record type
  • No sandbox or test environment
  • Write examples in the reference are placeholders, and there are no response examples
  • A few live error sentences differ from the documented ones

Check it yourself.

Both files behind this page, in full.

NOTE: Grades are AI-generated from the published grading file, and cover API access only.

FixGrid’s full report

The complete markdown report this page is built from, including the evidence packet, the run metadata and every check in full.

Download the FixGrid report

The grading file

The exact rubric behind every score on this page. Same file, every platform. Run it yourself and compare.

Download the methodology

Found a factual error in your grade?

Tell us and we will fix it. Every mark on this page traces to a specific piece of first-party evidence or a live API call, and the full report is published so you can see exactly what was checked and what it was checked against.

Confirmed factual errors are corrected immediately.

Everything else waits. We do not rescore piecemeal on request, because a board where some vendors have been re-run and others have not is not a fair comparison. Shipped improvements, changed documentation and disagreements about judgement all go into the next full rerun.

Contact us with a factual error