The reference material exists and even ships an /llms.txt index for agents — but two contracts will silently break client code: the tutorial tells you to check for a 200 that the example response never sends (it returns 201), and the same 422 status code carries two structurally different JSON bodies depending on which endpoint failed. Around those sit a self-contradicting version number, a sandbox-only set of hosts with no documented production URL, and an error-handling section that links to two 404s.
1. The API reports three different version numbers (significant)
Problem: The site nav badge says v0.4.0. The getting-started page asserts "This documentation applies to BillPro API v1. All minor versions retain full backward compatibility with previous minor versions." The embedded OpenAPI definition declares "version": "0.2.0". Three authoritative surfaces, three different version strings, with no explanation of how they relate.
Consequence: A developer (or an agent reading the OpenAPI spec to pin a version) cannot tell what version they are actually targeting. The "full backward compatibility" promise is keyed to "v1," but the machine-readable spec it documents is 0.2.0, so tooling that trusts the spec version and tooling that trusts the prose disagree about what compatibility guarantees apply. This confuses rather than crashes — there is no demonstrated runtime failure path — but it undermines trust in the spec.
The fix: Make the OpenAPI info.version, the nav badge, and the prose agree on one scheme. If the public API is "v1" (the server URL is /api/v1), set info.version accordingly and reserve 0.x numbers for the docs-site build, clearly labeled as such.
2. The error-handling section links to two dead pages (significant)
Location:/reference/billpro-api.md — "Get API response codes" section
Problem: The response-codes guidance links to "the reference documentation for ... Products ... Orders." Both URLs return HTTP 404 page_not_found. The real pages are /reference/products-api and /reference/orders-api, and neither nor appears in .
Code verification
Runtime snippet checks
Run the public examples from this audit in an isolated sandbox and attach the pass, fixed, skipped, and failed counts to the report.
Consequence: A developer trying to interpret an error and follow the link to the relevant resource reference lands on a 404. This breaks navigation, not production code, but it does so exactly when something has already gone wrong (handling a failed request).
The fix: Repoint the links to /reference/products-api and /reference/orders-api. Add a link-check step against /llms.txt to CI so paths outside the published index can't ship.
3. Success code is "200" in prose but "201" in every example body (critical)
Problem: On create-single-order, the response is declared "200": { "description": "OK" }, but the example response body ends with "status": 201. The tutorial repeats the contradiction: "Check the response code, which should be 200 for a successful request. Here is an example of response data from a successful request" — and the create-single-order example body that this success path describes carries "status": 201.
Consequence: A developer who writes if (response.status === 200) — exactly what the tutorial tells them to do — will reject a successful order if the API actually returns 201. An agent generating client code from the prose and an agent reading the example body produce incompatible success checks.
The fix: Decide whether order creation returns 200 or 201, then make the declared response code, the example body's status field, and the tutorial instruction all match it.
4. Success codes are inconsistent across the create endpoints (significant)
Problem: create-single-order declares the success response as 200 OK. create-subscription-order declares success as 201. create-customer's success is 201. So three sibling "create" operations on the same API use two different success codes with no stated rationale. The sloppiness shows up in the schema too: create-subscription-order's status property is typed { "type": "integer", "example": 201, "default": 0 } — a 0 default is a nonsensical value for an HTTP status echo.
Consequence: Developers cannot write one shared response handler for "create succeeded." Every endpoint needs a bespoke status check, and the inconsistency is invisible until a specific call fails the wrong assertion. A client that trusts the schema default could also read a status of 0, which is not a valid HTTP code.
The fix: Standardize creation semantics across all create endpoints (REST convention is 201 Created), document why any endpoint differs, and remove the "default": 0 from the status field.
5. Two incompatible 422 error shapes for the same status code (critical)
Problem: The order endpoints document a 422 body as { "error": "Bad Request" } — a single string field. create-customer documents a 422 as field-keyed arrays: { "email": ["is invalid", "can't be blank"], "first_name": ["can't be blank"], "last_name": ["can't be blank"] }. Same status code, two structurally different JSON shapes, with no note that the contract varies by endpoint. Worse, the create-customer 422 schema doesn't even describe its own example: it uses placeholder property names with spaces — "error array", "error key", "error description" — that don't correspond to the actual keys (email, first_name, last_name). It reads like an auto-generated, non-functional schema.
Consequence: Agents fail silently here. Code that parses response.error works for order failures and breaks on customer failures (where there is no error key); code that iterates field arrays breaks on order failures. And no client can be generated from the customer 422 schema, because its property names don't match the payload it documents. Humans muddle through with judgment; programmatic clients cannot.
The fix: Adopt one validation-error envelope across the API (e.g. always field-keyed arrays, or always an errors[] list with field + message), document it once, reference it from each endpoint, and regenerate the customer 422 schema so its property names match the real response keys.
6. The 422 example carries a "Bad Request" message that contradicts its own status (significant)
Problem: The order 422 example is labeled "Missing or not found product ID" / "Missing product ID" but the body is { "error": "Bad Request" }. "Bad Request" is the standard reason phrase for 400, not 422, and the message says nothing about which field was missing.
Consequence: A developer matching on the message string to distinguish validation failures gets a generic "Bad Request" that maps to the wrong HTTP semantic and gives no actionable field information. It also conflicts with create-customer's 422, which does name the offending fields.
The fix: Return a 422 body that names the failing field (consistent with the customer endpoint), and drop the "Bad Request" phrasing under a 422 code.
7. recurring_collection_interval enum may conflict with the Scheduled Payments API (significant)
Location:/reference/create-subscription-order.md; cross-references /reference/scheduled-payments-api (per /llms.txt: "Manage recurring payments in orders")
Problem: create-subscription-order defines recurring_collection_interval with enum: [ "MONTH", "QUARTER", "YEAR" ] — no DAY, no WEEK. Per /llms.txt, a separate Scheduled Payments API also "manages recurring payments in orders," so two documented surfaces govern the same recurring-interval concept. (Note: the Scheduled Payments page itself was not in the audited scrape, so its exact enum could not be confirmed here — this finding flags the duplication and a likely value mismatch for confirmation, not a verified contradiction.)
Consequence: When one concept (the recurring interval) is configured through two endpoints, any divergence in the accepted enum values means a developer who sends a value valid on one surface gets rejected on the other, with no way to tell which list is authoritative from the docs. The subscription enum's omission of DAY/WEEK is the immediate, verified constraint developers must work within.
The fix: Confirm the Scheduled Payments interval enum against create-subscription-order's MONTH/QUARTER/YEAR. If they differ, reconcile to a single source of truth; if both surfaces genuinely accept different intervals, state that explicitly on both pages and explain why.
8. payment_link example and schema use different domains (significant)
Location:/reference/create-subscription-order.md
Problem: The example response value sets "payment_link": "https://test.bill.pro/checkout/610/9YhsgYtyJj3ERxq3GfHeMQ39", but the response schema documents the same field with "example": "https://test.billing.gg/checkout/610/9YhsgYtyJj3ERxq3GfHeMQ39" — a completely different domain (test.billing.gg).
Consequence: A developer building allow-lists, redirect validation, or link-domain assertions against the documented host has two contradictory answers. An agent extracting the checkout host from the spec may hard-code billing.gg, which never appears anywhere else in the docs.
The fix: Make the schema example use the same host as the example value (test.bill.pro), and confirm which domain checkout links actually resolve to in each environment.
9. No production server URL or checkout host is documented (significant)
Problem: Every documented host is a sandbox. The OpenAPI servers block lists only { "url": "https://test.bill.pro/api/v1" }. The payment_link examples point to https://test.bill.pro/... and https://test.billing.gg/.... There is no documented production base URL and no documented production checkout host anywhere in the scraped reference.
Consequence: A developer who finishes integration against the docs has no documented endpoint to ship against. They must guess the production host (drop "test."? a different domain entirely, given billing.gg also appears?), which is exactly the kind of guess that causes a launch to hit the sandbox or a wrong host in production.
The fix: Document the production base URL and the production checkout host explicitly, alongside the sandbox values, in the OpenAPI servers block and the getting-started page — ideally as a labeled environment table (sandbox vs production).
10. List endpoints return no pagination metadata (significant)
Problem: "The default page size is 25 items in a list. There is no pagination metadata. Get the pages sequentially by incrementing the page number. If the page list contains 25 items, try to get the next page, until you get a page with fewer than 25 items."
Consequence: Without a total count or next/has_more field, a client cannot know how many pages exist, cannot parallelize fetches, and must always issue one extra "empty/short" request to detect the end. A collection whose size is an exact multiple of 25 forces a guaranteed extra round-trip every time. This is workable for humans but fragile for agents that need to size or page deterministically.
The fix: Add minimal pagination metadata (total_count and/or has_more/next_page) to list responses. If that's not feasible short-term, document the exact stopping condition and the multiple-of-25 edge case prominently.
11. /llms-full.txt returns a 404 (minor)
Location:/llms-full.txt
Problem: The site ships /llms.txt (which exists and lists pages), but the conventional companion full-text file /llms-full.txt returns HTTP 404 page_not_found. The docs only ever point agents to /llms.txt, so this is a missing optional companion rather than a broken promise.
Consequence: An AI coding agent that probes the common llms.txt / llms-full.txt pairing by convention will request /llms-full.txt, get a 404, and fall back to per-page crawling. No documented affordance is broken, but the agent does extra work it could have avoided.
The fix: Optionally publish a real /llms-full.txt containing the concatenated Markdown, or add a one-line note in /llms.txt that only the index file is published so agents don't probe the missing path.
12. Typos in the machine-readable index, tutorial title, and code-block labels (minor)
Problem: The tutorial title/description reads "Learn how to create a single pyament order" — and the same typo is carried verbatim in the /llms.txt index entry. The getting-started page additionally shows code-block titles "requeest" and "resonse."
Consequence: Typos in the agent-facing llms.txt index degrade keyword matching and look unmaintained at exactly the surface meant to be authoritative for machine consumers. Misspelled code-block labels reduce confidence in copy-paste correctness.
The fix: Correct "pyament" → "payment," "requeest" → "request," "resonse" → "response," and regenerate /llms.txt from corrected source so the index stays in sync.
What they do well
Ships an /llms.txt index and embeds real OpenAPI definitions, so agents have a machine-readable starting point.
Documents the auth flow concretely (Bearer + token in the Authorization header over HTTPS).
Provides task-oriented tutorials ("create an order," "create a customer") alongside raw reference, not just endpoint dumps.
Top 3 recommendations
Fix the two contracts that silently break client code first: the 200-vs-201 success mismatch (declared code, example body, and tutorial instruction must agree) and the two incompatible 422 shapes across orders vs customers (one envelope, and a customer schema whose property names match its payload).
Document the production environment. Publish the production base URL and checkout host alongside the sandbox values, and reconcile the version number across nav, prose, and OpenAPI info.version.
Reconcile the remaining cross-page conflicts: confirm the recurring_collection_interval enum against the Scheduled Payments API, align the payment_link host between example and schema, and repoint the dead /docs/products and /docs/orders links in the error-handling section.