Zinc Documentation Audit
One-line state: Zinc's v2 docs are feature-rich but riddled with cross-page contradictions — the docs domain advertised to agents is dead, auth examples ship an empty token, order pricing differs 100x between pages, the order lifecycle has at least three mutually incompatible status vocabularies, and three headline marketing claims (international shipping, cancel-in-flight, per-call pricing) are flatly contradicted by the reference. An AI agent ingesting these docs will copy broken auth, target a US-only endpoint while reading international promises, and branch on status strings the API never emits.
1. The docs domain advertised to agents in llms.txt is dead (critical)
Location: https://www.zinc.com/llms.txt and https://docs.zinc.com/
Problem: The site's own LLM index (www.zinc.com/llms.txt) points all three "Documentation" entries — "API Documentation", "Quickstart Guide", and "API Reference (v2)" — at docs.zinc.com. That host returns ECONNREFUSED (connection refused); https://docs.zinc.com/quickstart and https://docs.zinc.com/v2/api-reference do not resolve. The live docs are actually served from www.zinc.com/docs/.
Consequence: llms.txt exists specifically so coding agents and crawlers can find docs without scraping. Zinc points every agent at a dead domain on the first hop. An agent following the canonical index lands on nothing, and a developer clicking the same links gets a connection error before reading a single page.
The fix: Rewrite llms.txt so every "Documentation" link uses the live https://www.zinc.com/docs/... host (or stand up a redirect from docs.zinc.com to www.zinc.com/docs). Validate the index links in CI so a dead canonical host fails the build.
2. Every auth example ships an empty token (critical)
Location: /docs/v2/api-reference/introduction/authentication (recurs in /docs/quickstart, /docs/v2/api-reference/introduction/idempotency, .../sandbox, and /docs/v2/mpp)
Problem: The Authentication page's only example renders the credential as Authorization: Bearer with the placeholder stripped to nothing. The same empty-Bearer artifact reappears in the Idempotency, Sandbox, and MPP examples. In the Quickstart, ZINC_API_KEY= is assigned an empty value, and the "Replace … with your actual token" instruction points at a mangled, empty code block.
Consequence: This is the single most-copied snippet in any API doc. An agent or developer extracting it sends Authorization: Bearer with no token and receives 401 — and because the placeholder is literally absent (not a marked <YOUR_KEY>), there's no signal that anything needs substituting. The auth example, which should be the most reliable page, is the most broken.
The fix: Restore a visible, clearly-marked placeholder everywhere — Authorization: Bearer YOUR_API_KEY and ZINC_API_KEY=znk_xxx — and add a rendering test so an empty credential token fails docs publishing.
3. "United States only" in the order reference vs five countries in the changelog (critical)
Location: /docs/v2/api-reference/orders/create-order vs /docs/changelog and /docs/what-is-zinc
Problem: Create Order states flatly: "We currently only support shipping to addresses in the United States," requires Country (must be "US"), and the non_us_retailer error reads "Only US retailer sites are supported." The Sandbox page even lists "Shipping address country validation" among bypassed checks. But the changelog announces shipping to Great Britain, Canada, New Zealand, Australia, and Germany, plus "International Currency Support" with a 3% FX markup, and the "What is Zinc?" page advertises a "Cross Border" use case: "purchasing and shipping products internationally."
Consequence: A developer building international checkout reads the changelog, designs for GB/EUR/AUD, then hits a hard country must be "US" validation and a non_us_retailer rejection at runtime. An agent cannot reconcile "supports Germany" with "only US retailer sites are supported" — it will either refuse valid US orders or attempt impossible international ones.
The fix: State the actual supported-country list in one authoritative place and reference it everywhere. If international support is staged per-retailer, document the real matrix (which retailers ship where) and remove the absolute "United States only" language and the non_us_retailer blanket error, or scope it precisely.
4. Order pricing differs 100x between the homepage and the Wallet doc (critical)
Location: https://www.zinc.com/ vs /docs/v2/wallet
Problem: The homepage advertises "Transparent pricing: $0.01 per call with discounts as you grow." The Wallet doc says each order "draws its cost plus a flat $1 API fee from that balance" and "Each order draws order_cost + $1 from your balance." $0.01 per call vs a flat $1 per order is a 100x discrepancy, and the Connect page's worked example ("Zinc fee $1.00") confirms the $1 figure.
Consequence: This is the number that decides whether a developer integrates. Someone modeling unit economics off the homepage's "$0.01 per call" underprices their own product by 100x relative to the actual $1 order fee, then discovers the gap in production billing.
The fix: Reconcile the marketing and Wallet figures. If "$0.01 per call" refers to non-order API calls (search, tracking reads) and "$1" is the per-order fee, say exactly that on both pages with the distinction spelled out.
5. Three incompatible order-status vocabularies across the API (critical)
Location: /docs/v2/agent-skills/usage, /docs/v2/api-reference/orders/list-orders, /docs/v2/api-reference/introduction/webhooks (and get-order, tracking, create-order)
Problem: The order lifecycle is described with at least three non-matching enums:
- Agent-Skill "Order Statuses":
pending,in_progress,order_placed,order_failed,cancelled - List Orders "Order Statuses":
pending,in_progress,ordered,shipped,delivered,cancelled,failed - Webhook events:
order.started,order.placed,order.failed,order.tracking_received,order.delivered,order.cancelled
So "order succeeded" is order_placed (skill), ordered (list), and order.placed (webhook). Get Order's item example uses shipped; Tracking uses order_placed; Create Order says the initial status is pending. Create Return additionally requires the order be in order_placed status — a value the List/Get Orders pages never define.
Consequence: Status branching is the core of any order-management integration. A developer who codes if status == "order_placed" against the Agent-Skill docs will never match the ordered the List Orders API actually returns. An agent silently mishandles every order because no two pages agree on the success string, and returns become un-fileable because the required order_placed precondition doesn't exist in the orders API's own vocabulary.
The fix: Define one canonical order-status enum and one canonical webhook-event set, publish a single mapping table (status → webhook event), and make every page — skill, list, get, tracking, create, returns — reference that one table.
6. The shipping-address schema is different on nearly every page (critical)
Location: /docs/quickstart vs /docs/v2/api-reference/orders/create-order vs /docs/v2/mpp
Problem: The address object's field names and structure change page to page:
- Quickstart:
first_name,last_name,address_line1,address_line2,phone_number, and nocountry - Create Order reference: a single
name,address_line_1(underscored), requiredcountry: "US",phone - MPP guide: single
name+address_line1; Sandbox/Multiple-Products examples useaddress_line_1
So the same field is spelled address_line1 and address_line_1; the name is one field or two; phone is phone or phone_number; country is required or absent.
Consequence: An order request that validates against the Quickstart schema (first_name/last_name/address_line1, no country) is rejected by the actual endpoint, which wants a single name, address_line_1, and a required country. This is the request body developers and agents build first, and the underscore difference (address_line1 vs address_line_1) is exactly the kind of silent mismatch agents can't self-correct.
The fix: Publish one canonical address schema (ideally generated from the OpenAPI spec) and use it verbatim in Quickstart, Create Order, MPP, and Sandbox. Pick one spelling for each field and never vary it.
7. Two overlapping error-code taxonomies with different names for the same failure (critical)
Location: /docs/v2/api-reference/introduction/error-handling (and /docs/v2/agent-skills/usage)
Problem: The Error Handling page presents two parallel tables — "API Error Codes" (request-time) and "Order Processing Errors" (error_type) — that name the same conditions differently:
- out of stock:
out_of_stock(request-time) vsproduct_out_of_stock(processing) - bad address:
invalid_shipping_address(request-time) vsshipping_address_invalid(processing) - bad variant:
invalid_variant(request-time) vsproduct_variant_unavailable(processing)
The Agent-Skill "Error Handling" table then mixes both vocabularies (product_out_of_stock, invalid_shipping_address, retailer_unavailable) in a single list that appears nowhere intact in the reference.
Consequence: Robust integrations switch on error codes. A developer handling out_of_stock misses the processing-time product_out_of_stock and silently mishandles the failure. An agent building an error map can't tell whether invalid_shipping_address and shipping_address_invalid are one condition or two, so its retry/branch logic is wrong by construction.
The fix: Either unify request-time and processing-time errors into one namespaced enum, or keep them separate but document, per condition, that "this request-time code corresponds to this processing error_type." Make the Agent-Skill table a subset of the canonical reference, not a third spelling.
8. v2 feature availability: "coming soon" vs "full feature parity" (significant)
Location: /docs/what-is-zinc vs /docs/v2/migrating-from-v1
Problem: "What is Zinc?" says: "Some features from v1 (account automation, tracking, returns, cancellations) are coming soon." The Migrating-from-V1 page says the opposite: "Zinc v2 has reached full feature parity with v1. Returns, cancellations, and product data are all available in v2." Live v2 reference pages for Returns, Tracking, and Cancel Order exist, and the changelog describes them as shipped.
Consequence: A developer deciding whether v2 can handle their returns flow gets a yes and a no on two adjacent pages. Anyone who believes "coming soon" needlessly stays on v1 (which the migration page urges them to leave), while the feature is actually live.
The fix: Update "What is Zinc?" to reflect current availability. If a subset of v1 features (e.g. account automation) is genuinely still pending, name only those, and stop listing tracking/returns/cancellations as "coming soon" when shipped reference pages exist.
9. Return reason and status enums: human tables vs machine enums don't match (significant)
Location: /docs/v2/api-reference/returns/create-return and .../list-returns vs /docs/v2/api-reference/introduction/api-changelog
Problem: Create Return documents reason values as flat strings (damaged, not_delivered, empty_box, wrong_item, defective, not_as_described, wrong_size, no_longer_needed, forced_cancellation, other). The API Changelog's ReturnRequestReason enum is namespaced and shorter: return.request_label, nondelivery.not_delivered, nondelivery.damaged, nondelivery.empty_box, tracking.request_update, cancel.forced_cancellation, other. Separately, List Returns documents four statuses (open, approved, denied, credited) and calls them terminal, but the Changelog's ReturnRequestStatus enum has six (open, in_progress, approved, denied, completed, credited) — in_progress and completed are never documented in the human pages, and no webhook emits a completed event.
Consequence: A developer sending reason: "wrong_size" (a documented human value) may be rejected by an API that expects the namespaced enum — and there's no documented value for several human reasons at all. State machines built on the four documented return statuses break when the API returns in_progress or completed.
The fix: Generate the Return reason and status tables directly from the OpenAPI enums so the human docs and machine schema can't drift. Document in_progress and completed, and add the webhook event(s) that correspond to reaching completed.
10. Homepage promises "kill orders in flight"; the API can only cancel queued orders (significant)
Location: /docs/v2/api-reference/orders/cancel-order vs https://www.zinc.com/
Problem: Cancel Order is explicit that cancellation is queue-only: "Currently cancellation is only possible while the order is still waiting in the queue to be processed. Once we start an order, we are unable to cancel it. Orders that have already been started or completed cannot be cancelled." The homepage markets the opposite capability: "Cancel in-flight — Pre-shipped orders or kill orders in flight without contacting support team." "In flight" is precisely the started-but-not-shipped state the API says it cannot cancel.
Consequence: A developer who builds a "cancel in-flight order" feature on the homepage's promise finds the API rejects cancellation for any order that has begun processing — only orders still sitting in the queue can be cancelled. The marketed capability (kill orders mid-flight) doesn't exist, and the real cancellation window is narrow and pre-processing, which changes how an integration must handle a user's "cancel" click.
The fix: Align the homepage with the API: describe cancellation as available only while an order is queued (before processing starts), and drop or rephrase "kill orders in flight," since in-flight (started) orders cannot be cancelled.
11. Webhooks say "don't poll"; Get Order says "poll every 30–60 seconds" (significant)
Location: /docs/v2/api-reference/orders/get-order vs /docs/v2/api-reference/introduction/webhooks
Problem: The Webhooks page advises: "Instead of polling the API for updates, configure a webhook URL." The Get Order page says: "Poll this endpoint to track order progress. We recommend checking every 30-60 seconds while the order is processing."
Consequence: The docs give contradictory guidance on the most basic integration decision — push vs pull. A developer can't tell whether polling is discouraged or recommended, and an agent following Get Order will hammer the endpoint that the Webhooks page exists to replace.
The fix: State one recommended pattern (webhooks for production; polling as a fallback or for sandbox), and on Get Order note that polling is a fallback when webhooks aren't configured, with the recommended interval.
12. Credential is called "client token" in Quickstart and "API key" in Authentication (significant)
Location: /docs/quickstart vs /docs/v2/api-reference/introduction/authentication
Problem: Quickstart instructs: "visit your Zinc dashboard to find your client token." Authentication says: "authenticate every request with your API key … find your API key in your Zinc dashboard." The Agent-Skill and Wallet docs use yet another label, the ZINC_API_KEY env var.
Consequence: A first-time user searching the dashboard for a "client token" may not realize it's the same thing the Authentication page calls an "API key." For agents parsing setup instructions, two names for one credential is an unnecessary ambiguity in the most security-sensitive step.
The fix: Pick one term ("API key") and use it consistently across Quickstart, Authentication, Agent Skills, and Wallet.
13. Dashboard URL is three different domains (significant)
Location: /docs/v2/migrating-from-v1, /docs/v2/api-reference/introduction/authentication, /docs/v2/api-reference/configuration/managed-accounts
Problem: The dashboard is cited as app.zinc.com (Authentication, Agent-Skills, homepage), dash.zinc.com (Configuration/Managed Accounts: "find this address in the Zinc dashboard … https://dash.zinc.com"), and dash.zinc.io (Migrating-from-V1, as the v1 dashboard). The separate dead docs.zinc.com adds a fourth *.zinc.com host.
Consequence: A developer told to fetch their forwarding address from dash.zinc.com may hit a domain that doesn't serve the v2 dashboard (which is app.zinc.com everywhere else). Inconsistent host names also undermine trust in links during an account/credentials flow, where users are rightly cautious about where they enter passwords and TOTP secrets.
The fix: Standardize on app.zinc.com for the v2 dashboard across all pages; reserve dash.zinc.io references explicitly for v1 only, clearly labeled.
14. Managed accounts: duplicated docs that already disagree, plus an inverted marketing definition (significant)
Location: /docs/v2/api-reference/configuration/managed-accounts vs /docs/v2/api-reference/managed-accounts/create-managed-account (and homepage)
Problem: The same /managed-accounts CRUD endpoints are documented twice — once under "Configuration" and once under dedicated "Managed Account" reference pages — and the two already drift: the Configuration page's Create response JSON omits has_forwarding, which the dedicated Create page lists as a returned field. Worse, the homepage inverts the concept's meaning: it describes "Managed accounts" as "Skip account creation and maintenance. Leverage Zinc's robust ordering capacity" (i.e. use Zinc's accounts), while the docs define a managed account as your own retailer credentials (email/password/TOTP you provide). The homepage also calls this "Bring Your Own Account."
Consequence: A developer reading the homepage believes managed accounts mean "Zinc handles the retailer account for me," then the API demands they POST their own retailer email, password, and totp_secret. Two CRUD reference surfaces that disagree on the response shape mean codegen against one is wrong against the other.
The fix: Collapse the duplicated managed-account docs into one canonical reference (generated from the spec, so has_forwarding can't go missing). Rewrite the homepage so "Managed accounts" / "Bring Your Own Account" matches the docs' definition: you supply your own retailer credentials.
15. LaserShip is announced in the changelog but missing from the carrier table (significant)
Location: /docs/changelog vs /docs/v2/api-reference/orders/tracking (and homepage FAQ)
Problem: The changelog announces "LaserShip Tracking Support": "Orders shipped via LaserShip now include tracking numbers." The Tracking page's "Supported Carriers" table lists only UPS, FedEx, USPS, Amazon Logistics, and DHL — no LaserShip — and the homepage FAQ repeats the same five-carrier list.
Consequence: A developer building carrier-aware logic (icons, tracking-URL templates, the documented carrier enum) has no carrier value for LaserShip, so a shipment that the changelog says is supported arrives with an undocumented or unhandled carrier. Agents mapping carrier to a known set will drop LaserShip on the floor.
The fix: Add LaserShip to the Supported Carriers table with its carrier value and example tracking format, and update the homepage FAQ list — or, if LaserShip isn't actually emitted yet, correct the changelog.
16. Internal doc links are missing the /docs prefix and 404 (significant)
Location: /docs/v2/wallet, /docs/v2/connect, /docs/v2/agent-skills/overview, /docs/changelog
Problem: The Wallet and Connect pages link to /v2/connect and /v2/mpp (and /v2/api-reference/orders/create-order) without the /docs base path; https://www.zinc.com/v2/connect 404s. The Agent-Skills intro links OpenClaw via /docs/openclaw, which 404s. The changelog's MPP entry references anchor #2026-03-27, which has no matching dated heading on the page.
Consequence: Core cross-references between Wallet, Connect, and MPP — the three payment models — are broken, so a developer comparing payment options hits dead links navigating between them. A broken in-page anchor and a 404'd OpenClaw link further erode navigation for both humans and crawlers.
The fix: Prefix internal doc links with /docs (or make them root-relative correctly), fix the /docs/openclaw target, correct the #2026-03-27 anchor to a real heading id, and run a link checker in CI.
17. The agent skill has three different identifiers (significant)
Location: /docs/v2/agent-skills/setup
Problem: One skill is referenced under three names: the GitHub repo is github.com/zincio/universal-checkout-skill, the ClawHub install is clawhub install a5huynh/universal-checkout (a personal handle, different package name), and the OpenClaw config file keys it as zinc-orders. The setup page also says "Get your API key from the Zinc dashboard" with no link in the export.
Consequence: A developer (or an agent auto-installing the skill) can't tell whether a5huynh/universal-checkout, universal-checkout-skill, and zinc-orders are the same thing or three artifacts. Installing the ClawHub package under a personal handle, while the repo lives under the official zincio org, also raises a supply-chain question the docs don't address.
The fix: Use one canonical name for the skill, document the relationship between the GitHub repo and the ClawHub package (and confirm a5huynh is the official publisher), and make the config key match. Add the dashboard link for the API key.
18. Connect's payment object is undocumented on the Create Order reference (significant)
Location: /docs/v2/connect vs /docs/v2/api-reference/orders/create-order
Problem: The Stripe Connect page instructs developers to "Add a payment object to your standard create-order call with mode: 'connect'," and defines that object's fields (mode, required payment_method, required customer, required margin). But the Create Order reference documents only the shipping address, optional order data (po_number, metadata), and the response — it never mentions a payment object or a mode field. The field that switches billing from wallet to Connect exists only on the Connect guide, not on the endpoint's own reference.
Consequence: A developer reading the Create Order reference alone — the canonical endpoint doc and the page an agent indexes for the request schema — has no way to discover the payment object. They can't enable Connect billing, and an agent generating a create-order body from the reference silently omits the mode switch and always bills the prepaid wallet.
The fix: Document the payment object (and mode) on the Create Order reference itself, generated from the OpenAPI spec, cross-linking the Connect guide for detail, so the endpoint's own schema is complete.
19. The OpenAPI v2 spec is published under a v1 path, and Wallet/Connect bodies are missing from the LLM export (significant)
Location: /docs/llms.txt, /docs/versions/latest.json, llms-full.txt
Problem: The docs index lists the v2 OpenAPI spec at https://www.zinc.com/docs/v1/api-reference/openapi/v2.json — a v2 spec under a /v1/ path. Separately, the Wallet and Stripe Connect pages are listed in llms.txt but their bodies are absent from llms-full.txt (the consolidated LLM export), so an agent ingesting the full export silently lacks both payment models.
Consequence: Machine-readable specs and the consolidated LLM export are precisely what agents rely on. A spec at a contradictory /v1/.../v2.json path invites fetching the wrong version, and two payment pages missing from llms-full.txt mean an agent ingesting the full export lacks the Wallet and Connect payment models entirely.
The fix: Serve the v2 OpenAPI spec from a v2 path, and regenerate llms-full.txt so every page listed in llms.txt (including Wallet and Connect) is actually present in the export. Add a check that the two indexes stay in sync.
20. MPP claims a Rust SDK that has no package (minor)
Location: /docs/v2/mpp
Problem: The MPP guide states: "Official SDKs are available in TypeScript, Python, and Rust," but the install instructions only cover mppx (npm) and pympp (pip). No Rust crate or install command is shown.
Consequence: A Rust developer takes the claim at face value, goes looking for the crate, and finds nothing — the third "official SDK" doesn't exist in the docs. Agents enumerating supported languages will advertise Rust support Zinc doesn't document.
The fix: Either add the Rust crate name and install command, or remove Rust from the "available in" sentence until it ships.
21. Homepage ships placeholder metrics and a v1 code chip (minor)
Location: https://www.zinc.com/
Problem: The homepage's headline metrics are unfilled placeholders: "0M Orders Placed", "0M+ Products Shipped", "0M+ Products Indexed". The hero code chip shows POST /v1/orders, while every documented endpoint is v2 (POST /orders).
Consequence: "0M Orders Placed" reads as either zero traction or an unfinished page — a credibility hit on the first screen. A developer copying the hero snippet targets a /v1/orders path that the v2 docs never use.
The fix: Populate the metrics (or remove them until real numbers exist) and update the hero code chip to the v2 POST /orders endpoint.
22. List Orders has no pagination while the product is sold on scale (minor)
Location: /docs/v2/api-reference/orders/list-orders vs homepage
Problem: List Orders states: "Currently, all orders are returned in a single response. Pagination will be added in a future update." The homepage markets "Simple, scalable pricing" and "robust ordering capacity" aimed at high-volume, agent-driven ordering.
Consequence: A high-volume integration — exactly the audience the homepage targets — will eventually receive an unbounded single-response order list, risking timeouts or truncation with no documented cursor or limit to work around it.
The fix: Document the current behavior's practical limits (max results, any implicit cap) and the intended pagination contract (cursor/limit) so developers can design around it now rather than rewrite later.
23. "50+ retailers" is advertised but only two are named (minor)
Location: https://www.zinc.com/llms.txt vs homepage FAQ
Problem: The root llms.txt advertises "50+ Online Retailers," but the homepage FAQ names only "Amazon in several regions, Best Buy, and more." No page enumerates the 50+ retailers or links a supported-retailer list.
Consequence: A developer or agent deciding whether a specific target retailer is supported has a headline count (50+) but only two concrete names and nothing to check coverage against. They can't programmatically determine whether a given store works, and the gap between "50+" and "Amazon, Best Buy, and more" reads as unsubstantiated.
The fix: Publish a canonical supported-retailer list (ideally machine-readable) that the "50+" claim references, so coverage is checkable rather than asserted.
What they do well
- The Idempotency page is a genuinely good model: a clear 36-character key cap, explicit uniqueness guarantee, and the subtle-but-correct guidance that an
already_existsresponse on retry is "success, not failure." - The Sandbox page is concrete and agent-friendly — eight named test product URLs mapped to specific scenarios (
test-success,test-out-of-stock,test-price-exceeded,test-invalid-address) and an explicit list of which validations are bypassed. - The Wallet and Connect pages lay out funding methods, processing fees, and a fully worked Connect charge example ($50 order → $55.41 charged, itemized), which is exactly the transparency billing docs need.
Top 3 recommendations
- Generate the contract-bearing docs from one source of truth. Order statuses, error codes, return enums, and the address schema should all be generated from the OpenAPI spec so the Quickstart, reference, agent-skill, and changelog pages physically cannot disagree. This single change resolves findings 5, 6, 7, and 9.
- Fix the two things that break first contact: dead
docs.zinc.comand the empty auth token. The canonical docs domain inllms.txtand theAuthorization: Bearerexample are the first things a developer or agent touches, and both are currently broken (findings 1 and 2). - Reconcile marketing with the API on the highest-stakes claims: international shipping (US-only vs five countries), pricing ($0.01/call vs $1/order), cancellation ("kill orders in flight" vs queue-only), and what "managed accounts" means (your credentials vs Zinc's). These are the claims that drive integration decisions, and they currently contradict the reference docs (findings 3, 4, 10, 14).