API Report Card · Workflow & CRM · Methodology v1.1
Aptly
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.
Functional Coverage
Design & Reliability
Access Control
Docs & AI-Ready
Access & Cost
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.
What this means for you.
One paragraph per category, in plain language.
1 · Functional Coverage
You can read everything on an Aptly board and create and change cards reliably, all of it verified live. What you cannot do is manage the automation layer through the API, and you cannot be notified when something changes. Every integration has to poll on a timer and ask what changed since last time. That works, and the updated-since filter is honest at hour granularity, but two things need care: send a malformed timestamp and Aptly quietly hands back every record instead of erroring, and the built-in text search did not reliably find cards that plainly existed. There is also no way to delete a card through the API, only archive, so a mistaken record has to be cleaned up by hand.
2 · Design & Reliability
The weakest part of the API, and weak in a specific way: the everyday experience is good, and the guarantees you would want before trusting it with unattended automation are missing. Errors come back clean and machine-readable, paging through cards is solid, and pulling a full dataset or just what changed is easy. What is missing matters. If a write times out and your code retries, you can get a duplicate card, because there is no way to say this is the same request. If two automations touch the same card at once, the second silently wins: the run proved it by sending a deliberately out-of-date update and watching Aptly accept it. There is no version number on the API and no published breaking-change policy, and no working status page. One practical quirk: after a write, reading the card back immediately can still show the old value for a minute or two.
3 · Access Control
Full marks, and the part that matters most for handing access to an AI agent. You can issue a key that can only read, only on the boards you name, and nothing else, and you can kill it yourself in seconds without emailing anyone. Requests outside a key's boards or permissions come back as a 403. The one gap is that there is no practice environment, only your live account, which is why the write testing in this run was confined to a single labelled fixture that was created and then archived.
4 · Docs & AI-Ready
Full marks, and not a close call. The documentation is complete, public, and specifically built so an AI assistant can read it and write correct code: a real OpenAPI file, a per-endpoint reference, every page retrievable as Markdown, and a single file containing the entire API that you can paste into a chat. Aptly also runs its own MCP server with real write tools. Two things to know: one link Aptly advertises as an OpenAPI file actually serves an unrelated sample document, and the customer help centre is months out of date and contradicts the developer docs in three places, so trust the developer portal.
5 · Access & Cost
Half marks on the heaviest category in the rubric, and the single biggest reason for the grade. Creating a key is genuinely self-serve, with no sales call and no approval step. But API access requires Aptly's Premium plan. The pricing page publishes three tiers with prices and lists API access as a Premium feature, so the gate is visible and priced before you commit. Worth knowing for a practical reason too: if a subscription is ever downgraded, the API and every automation built on it stop working, and nothing in the developer documentation warns of that dependency.
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.
Category 1 · Functional Coverage
7.5 / 15Object coverage
weighted coverage = 68% (0.50–0.84 band, no critical object absent). Records fully writable; automations/triggers, boards, and custom-field definitions are read-only. [openapi.yaml paths /api/board/{boardId}, /api/contacts, /api/tasks, /api/knowledge/create, /api/board/{boardId}/configuration/automations, .../workflows, .../fields; live: GET /api/schema/lease returned 81 field definitions with no write path, 2026-09-03]
Core operational actions
weighted coverage = 65% (0.50–0.84 band, no critical write workflow wholly absent). Record create and update are live-verified and work exactly as documented, including patch semantics. Triggers can be fired but not received. [live: POST /api/board/lease create at 19:03:37Z, update at 19:04:16Z, stage transition at 19:05Z, 2026-09-03]
Delete or lifecycle actions
weighted coverage = 80% (0.50–0.84 band, no critical lifecycle action absent). Stage change, archive, assignment, and task completion are all available; hard delete is absent from the entire API. [live: DELETE /api/board/lease/{cardId} → 404 NOT_FOUND "Route not found", 2026-09-03; openapi.yaml contains no delete: operation on any of 53 paths]
Change notification
no push mechanism, but efficient incremental polling is documented and works. updatedAtMin on the card-list endpoint is live-verified honored at hour granularity: updatedAtMin one hour back returned count: 0, one day back returned count: 190, and a 2030 timestamp returned count: 0; cards expose updatedAt, and the endpoint also filters on relatedId, contactEmail, assignee, and includeArchived. Two caveats. First, updatedAtMin=notadate returned 200 with a full unfiltered result set instead of a 400 — an invalid incremental cursor silently degrades to "fetch everything." Second, the documented keyTerm full-text filter returned count: 0 for APITEST while a card named APITEST-DELETE 2026-09-03 api-grader existed on the board, and count: 0 for Riverside on a 190-card Inland Empire lease board, so that filter is not dependable for change detection. [openapi.yaml /api/board/{boardId} parameters; pagination.md; live probes 10–13 and 24–28, 2026-09-03]
Category 2 · Design & Reliability
4.1 / 10Modern API conventions
resource-oriented JSON over HTTPS with a real OpenAPI 3.0.3 specification, standard status codes, and CORS headers, but conventions are mixed: POST doubles as create and update for cards (upsert keyed on an _id in the body) with no PUT or PATCH; no DELETE verb exists on any of the 53 paths; and several operations are RPC-shaped paths rather than resources — POST /api/knowledge/create, POST /api/routing-groups/create, POST /api/routing-groups/{id}/archive, POST /api/tasks/search. [openapi.yaml paths list; live: DELETE → 404 NOT_FOUND, 2026-09-03]
Consistent typing
value typing is genuinely strong. Across all 190 live lease cards, every core type held: money is always {"amount":2850,"currency":"USD"}, dates are always ISO strings, persons and relatedAptlets are always arrays, address and tel are always objects. Three inconsistencies, cited exactly: (1) the OpenAPI Error schema declares error as type: string alongside a sibling message, but every live error returns error as a nested object — {"error":{"code":"BOARD_NOT_FOUND","message":"Board not found"}} — so the published schema for the core error contract is the wrong shape; (2) the list envelope is {data, count, page, pageSize}, the single-card envelope is {data:{…}}, and GET /api/routing-groups returns a bare JSON array with no envelope at all; (3) board field 68 ("Renewal RENT $") is declared type: "string" by GET /api/schema/lease but returns numbers (2900, 3195, 2995) — a customer-configured field, and the only value-level mismatch in 190 records. [openapi.yaml components.schemas.Error lines 585–591; live probes 03, 16, 36, 45, 04/08, 2026-09-03]
Structured errors
the reported-error contract satisfies every element of a full pass: a consistent structured body, a populated and stable machine-readable code, a human-readable message, and correct HTTP status semantics. Ten deliberate probes returned 401 UNAUTHORIZED (bad key and missing key), 404 BOARD_NOT_FOUND, 404 CARD_NOT_FOUND, 404 NOT_FOUND (unknown route and unsupported verb), 400 INVALID_DATA ("pageSize must be a number between 1 and 1000", "page must be a number between 0 and 9999"), and 400 MISSING_DATA ("page parameter is required") — same shape every time. The limitation that holds this to partial: validation is not applied uniformly, so one class of failure returns no error at all. updatedAtMin=notadate returned 200 with 54,651 bytes of unfiltered cards, while an equally invalid page=abc on the same endpoint correctly returned 400. A sync built on that parameter is told everything succeeded when its incremental cursor was discarded. [live probes 14–23, 2026-09-03]
Duplicate prevention
no idempotency mechanism is documented anywhere in the 250,953-byte documentation corpus or the specification: no idempotency key, no request-identifier deduplication, and no natural key on card create. POST /api/board/{boardId} deduplicates only when the caller already supplies an _id, which is unknowable before the first call succeeds, so a retried create after a timeout produces a second card. The one exception is narrow and not general-purpose: POST /api/web-forms/{formId} has a configurable "Update if this field matches" mode. Graded from documented absence; there was no mechanism to test. [openapi.yaml — no Idempotency-Key parameter on any path; llms-full.txt — zero occurrences of "idempot"]
Graceful handling under load
rate-limits.md publishes explicit numeric limits (120 requests per minute per key, 20 per second burst), documents the 429 response body, states that "The response includes a Retry-After header indicating how many seconds to wait," and supplies a worked exponential-backoff implementation. Noted contradiction with observed behavior: 255 requests issued in about 1.1 seconds across two concurrent bursts (45 then 210) all returned 200, and no rate-limit or quota headers appear on normal responses — so the documented ceiling did not engage and the 429 path itself was not observable. [rate-limits.md; live probes 53–54, 2026-09-03]
Pagination for large collections
on cards, pagination is exemplary and live-verified: page plus pageSize (max 1000), a count total, stable ordering (page 0 returned an identical ID sequence on repeat, and pages 0 and 1 had zero overlap), and a clean 400 INVALID_DATA above the cap. The limitation: GET /api/contacts ignores the page-size parameter entirely — both ?limit=3 and ?pageSize=3 returned 200 records and echoed pageSize: 200 — so page size on a 2,112-record core collection is an undocumented fixed cap the caller cannot control. Compounding it, pagination.md documents the parameter as limit, default 50, maximum 100, which matches neither endpoint tested (cards use pageSize to 1000; contacts ignore both). Traversal still works — contact pages advanced with no overlap and a correct count. [pagination.md; live probes 05–09, 32, 48–52, 2026-09-03]
Bulk or incremental export
documented incremental sync via updated-since plus pagination, which the check accepts as qualifying, and live-verified end to end: updatedAtMin is honored, and pageSize=1000 returned the full 190-card dataset in a single 620,429-byte response with no per-record calls. GET /api/docs/openapi additionally serves the specification unauthenticated. [openapi.yaml /api/board/{boardId} parameters; pagination.md "Iterating all records"; live probes 08, 11, 24–28, 2026-09-03]
Webhook security and delivery reliability
cannot be established either way. Aptly's developer portal contains no webhook, event, or subscription content across its entire 250,953-byte corpus, and GET /api/board/{boardId}/configuration — documented as returning all configuration sections — returned nine sections (fields, automations, options, tabViews, workflows, groups, shares, theme, filters) with zero occurrences of "webhook", "callback", or "postback" in the full response. Against that, the Help Center page carries a "Webhooks" table whose addCard row reads "Triggered when a new card is added / Sends a notification to inform the receiving service that a card was added to the board", while the same page's other eight rows are plainly inbound API endpoints and the page states the API "only supports creating cards on a board." A capability is referenced but no signature scheme, retry policy, replay guidance, event catalog, or registration path is readable, so it can be neither credited nor ruled out. Resolving it does not change the published score (see Total). [llms-full.txt; live probe 37; Help Center page captured 2026-09-03, page last updated 2026-05-26]
Concurrency and conflict control
neither optimistic concurrency nor documented conflict semantics. Live-verified: ETag is returned on every read and conditional reads work (If-None-Match with a current ETag returned 304), but an update sent with a deliberately stale If-Match header was accepted with 200 and applied anyway, overwriting the newer value. No 409 response is defined on any of the 53 paths, no version field is documented as a write guard, and no concurrency limits or behavior are published. Two integrations writing the same card will silently clobber each other. [live probes 30–31, W05–W09, 2026-09-03; openapi.yaml — no 409 response, no If-Match parameter]
Versioning and backward compatibility
no version identifier in the request contract. The specification carries info.version: "1.0" as metadata, but every one of the 53 paths is unversioned (/api/...), and grepping the specification for /v1, api-version, X-Api-Version, and Accept-Version returns nothing. No backward-compatibility policy defining breaking versus non-breaking changes and no deprecation window or notice policy appears anywhere in the documentation corpus. Practice is better than policy — one endpoint is labeled "Deprecated alias" with the legacy path still live, and the changelog notes when a change preserves existing behavior — but there is no version contract to build against. (Currency of change communication is graded separately in C4.4 and is strong; that evidence is not reused here.) [openapi.yaml paths and info; llms-full.txt — no backward-compatibility or deprecation-window policy; api-reference/board/add-a-tab-view-legacy.md]
Request traceability
an identifier is present on every response but is not Aptly's and is not documented. Every live response carried a unique CF-Ray (for example a356f85efc622a56-SJC), which is Cloudflare's edge identifier. Aptly publishes no request or correlation identifier, and its documentation corpus never mentions CF-Ray, a request ID, or any trace mechanism usable with support. [live — 99 individually captured calls, all carrying CF-Ray, none carrying an Aptly-issued identifier, 2026-09-03; llms-full.txt — no request-identifier or support-trace guidance]
Service availability and status transparency
no public availability signal. status.getaptly.com resolves through Cloudflare but serves an unterminated 301 redirect loop; /, /history, and /api/v2/status.json are all unretrievable, and there is no status page at docs.getaptly.com/status or www.getaptly.com/status (both 404). No uptime figure, incident history, or SLA language appears anywhere in the documentation corpus. [live probes, 2026-09-03; llms-full.txt — zero occurrences of "status page", "uptime", "SLA", or "incident"]
Category 3 · Access Control
5 / 5Read-only credentials
keys can be restricted to a subset of read, insert, and update permissions: "Board API keys (x-token) can now be scoped to specific boards and to a subset of read/insert/update permissions, configurable from Setup → Developer → Board API Tokens. Requests outside a key's allowed boards or permissions now receive a 403 FORBIDDEN." A read-only key is a key granted read alone. [changelog.md, 2026-08-13; corroborated in openapi.yaml info.description]
Scoped credentials
fine-grained on both axes named by the check: by resource (specific boardIds) and by action (read / insert / update), enforced with 403 FORBIDDEN. Independently corroborated live: the operator's key returned only the four boards with API access enabled, not the company's full board set. POST /api/web-forms/{formId} documents an even narrower form-specific key — "a board-scoped insert key can only submit to the one form it was created for." [changelog.md 2026-08-13 and 2026-08-16; openapi.yaml info.description; live probe 03, 2026-09-03]
Multiple keys
keys are created individually with a name and an optional expiration ("Click Create New Key, enter a name, and optionally set an expiration date"), managed as a list under Setup → Developer → Board API Tokens, and the documentation's own best practice assumes several coexist: "Use a single API key per integration." [authentication.md, "API keys"; rate-limits.md, "Best practices"; changelog.md 2026-08-13]
Rotation and revocation
self-serve, no support ticket. Keys are created in-product with an optional expiration date, and revocation is archiving the key from the same Setup → Developer → Board API Tokens screen: "Keys without an expiration remain active until archived. Expired or archived keys return 401." The whole API can also be switched off per board from Card Sources → API. [authentication.md, "API keys" and "Key expiration"; changelog.md 2026-08-13]
Test and production isolation
no sandbox or separate test environment exists. openapi.yaml declares exactly one server (https://core-api.getaptly.com, "Production"), and the documentation corpus contains zero occurrences of "sandbox", "test environment", or "staging". With no test environment, the check does not apply; the absence is what forced this run's write testing onto the controlled live-data protocol. [openapi.yaml servers; llms-full.txt]
Category 4 · Docs & AI-Ready
5 / 5Complete self-serve reference
complete, public, and example-rich, with no login and no reverse-engineering required. An 89-entry indexed reference covers authentication (x-token header, with an explicit warning against query-string credentials), pagination, rate limits, field-type value formats, and delegate tokens, plus a dedicated page per endpoint. Core endpoints carry worked request and response examples: the card-create page publishes the request schema with an example body ({name: "John Smith", abc123: "john@example.com", ghi789: 1500}) and the full response schema. Live cross-check: the specification's documented parameters, error statuses, and response envelope for the card endpoints matched observed behavior. Noted but not penalized here: the separate customer Help Center (last updated 2026-05-26) contradicts the developer portal in three places — it says the API "only supports creating cards on a board" against 53 documented paths, tells readers to pass the token "via an x-token query parameter" where the portal warns against exactly that, and labels the API endpoint list "Webhooks." The check grades the reference a developer builds from, and that reference is complete and correct. [llms.txt; authentication.md; api-reference/cards/create-or-update-a-card.md; Help Center page captured 2026-09-03]
Reliable machine-consumable integration path
two complete, maintained mechanisms, either of which suffices. First, a genuine OpenAPI 3.0.3 specification (157,565 bytes, 53 paths, 24 named component schemas, three declared security schemes) suitable for code and tool generation, served both from the docs domain and unauthenticated from GET /api/docs/openapi. Second, a first-party operations-capable MCP server, documented tool by tool with scopes and throttles, exposing write operations including create-card, update-card-field, create-card-comment, create-card-task, send-email, and send-sms — verified reachable live in this session against the operator's account. One caveat that costs nothing here because the specification is complete: https://docs.getaptly.com/api-reference/openapi.json, linked from llms.txt as an OpenAPI spec, actually serves the Mintlify sample "OpenAPI Plant Store" document and is not an Aptly artifact. Note also that the "Aptly SDK" is a browser embed SDK (window.aptly) for iframe apps, not a server-side API client, so it is not the qualifying mechanism. [openapi.yaml; mcp-server.md; aptly-sdk-reference.md; live MCP tool invocation, 2026-09-03]
AI-readable documentation
comprehensive and purpose-built for retrieval. llms.txt indexes all 89 pages with one-line descriptions; llms-full.txt is a 250,953-byte complete corpus; every documentation page and every endpoint page is separately retrievable as Markdown at a predictable .md URL; and a dedicated llm-context.md exists specifically to be pasted into an AI assistant. Each page even carries a header pointing at the index. This run's entire documentation packet was assembled from these files. [llms.txt; llms-full.txt; llm-context.md; per-endpoint .md URLs]
Kept current
a dated changelog with clear, specific, per-endpoint entries, current to 2026-08-24, ten days before this run, with 23 dated entries running back to 2026-04-07. Entries name the exact endpoint and behavior changed, distinguish "New Endpoint" from "Enhancement" from "Fix", and state compatibility effects where they exist ("Existing keys are unaffected — an absent boardIds/permissions on a key means full access to all boards, as before"). The MCP tool reference is auto-generated from source, which keeps it in step by construction. This grades currency of change communication only; the versioning contract is graded in C2.10, where it fails, and that evidence is not reused here. [changelog.md; mcp-server.md header: "Auto-generated from src/mcp/**/*.tools.ts"]
Category 5 · Access & Cost
7.5 / 15Self-serve API key
once an account is entitled, credential creation is entirely self-serve with no sales call, support ticket, or approval step: "Open the board in Aptly → Go to Card Sources → API → Toggle the API on → Click Create New Key, enter a name, and optionally set an expiration date → Copy the key." The Help Center describes the same in-product path via the board's Integrations menu. Corroborated live: the operator's own self-created key authenticated and worked across 99 individually captured calls. Plan eligibility is scored separately in C5.3 and is not counted against this check. [authentication.md, "API keys"; Help Center, "How to Enable the API"; live probe 01, 2026-09-03]
Not commercially gated
API access requires a premium plan. Aptly's own Help Center states of the API: "It does require a Premium Subscription. If you're interested in upgrading, please reach out to sales@getaptly.com or simply upgrade in your account. Go to Settings > Subscription > Manage > Upgrade under Premium." That is explicit top-tier gating, and it is not identity or regulatory verification, so the check's KYC/KYB carve-out does not apply. Corroborated by the pricing page: https://www.getaptly.com/pricing publishes three named tiers — Essential, Premium and Enterprise — with prices, and lists "Access to Aptly API" as a Premium plan feature and "Enterprise API" under Enterprise. That independently confirms the Help Center's Premium requirement, so an operator can both see the gate and price it [rechecked live 2026-09-09]. Mitigating in a small way: the upgrade itself can be self-served in-account rather than requiring the sales email. Aptly's developer portal never mentions the premium requirement, which is why this check moved from a provisional yes to no during the controlled verification pass. [Help Center page captured 2026-09-03, last updated 2026-05-26; https://www.getaptly.com/pricing]
What works
- Fully verified: a fixture card was created, updated, stage-changed and archived on a live board
- Documentation built for AI retrieval, including one file containing the entire API
- A real OpenAPI 3.0.3 spec plus a first-party MCP server with write tools
- A dated changelog current to ten days before the run, with per-endpoint entries
- Keys scoped to named boards and to read, insert or update, enforced with a 403
- Self-serve keys with optional expiration, revocable by you in seconds
- Published numeric rate limits with a worked backoff example
- Exemplary card pagination: stable ordering verified across repeat and adjacent pages
What to watch
- API access requires a Premium plan, so the entry-level tier cannot use it
- No webhooks, so every integration polls on a timer
- No idempotency: a timeout followed by a retry can create a duplicate card
- No concurrency control: a deliberately stale update was accepted and applied
- No delete anywhere in the API; cards can only be archived
- A malformed updated-since value returns 200 and every record instead of an error
- No version identifier on any endpoint, and no breaking-change policy
- No working status page: status.getaptly.com serves a redirect loop
- Reads immediately after a write can show the old value for a minute or two
- No sandbox, so every test happens in the live account
The bottom line for a property manager
Aptly's API is unusually well documented and unusually lightly guaranteed: a genuinely useful tool with almost none of the safety rails you would want around unattended automation. You can read every board, card, contact and task, create and update cards, fire your board workflows by changing a card's stage, and pull either a full dataset or just what changed since last night. All of it was verified against a live account, including creating, updating and archiving a card on a production board. The documentation is outstanding and the access controls are excellent, letting you hand an AI agent a key that can only read, only on the boards you choose, revocable in seconds. What you cannot build is anything that needs to react the moment something happens, or anything that must not be allowed to go wrong quietly. There are no webhooks, so every integration polls. There is no way to mark a write as a retry, so a timeout can produce a duplicate. There is no protection against two automations overwriting each other, proven live with a deliberately stale update. No version number, no breaking-change policy, no working status page, and no practice environment. The F lands there for two concrete reasons: those write and reliability guarantees, and the fact that the API sits behind an unpublished Premium plan. None of that makes Aptly the wrong tool. It is a property-management-specialized workflow and communication layer and that is the job it does. But it is not a system of record and not a bank: it holds no funds, has no ledger, and documents no trust, escrow or security-deposit accounting. Use it to read and write workflow state on a schedule, with a read-only key wherever one will do, and pause before verifying a write.
Check it yourself.
Both files behind this page, in full.
Aptly’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 Aptly reportThe grading file
The exact rubric behind every score on this page. Same file, every platform. Run it yourself and compare.
Download the methodologyFound 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 errorMethodology inspired by SaaStr’s AI Agent API Report Card. Sponsored by Column.