OctoWiki
Memory Appendix

Runa Products Singular Vs Plural

Resolved — Runa docs are source of truth, all endpoints are SINGULAR. Both the Go client and the mock were wrong (plural) and have been corrected to match the O

Source memory file: project_runa_products_singular_vs_plural.md · Category: Project / investigation This is a verbatim dump of Claude's persistent memory for the Octopus project. Rendered inside a code block so nothing is altered.

---
name: runa-products-endpoint-singular-plural-divergence-resolved-2026-05-14
description: "Resolved — Runa docs are source of truth, all endpoints are SINGULAR. Both the Go client and the mock were wrong (plural) and have been corrected to match the OpenAPI spec."
metadata: 
  node_type: memory
  type: project
  originSessionId: 4d85b6ca-afd6-4218-b823-11d697609b2f
---

**Resolution (2026-05-14):** Verified against Runa's OpenAPI spec at https://developer.runa.io/reference/2024-02-05/openapi.json. **All product and order endpoints are singular**: `/v2/product`, `/v2/product/{code}`, `/v2/product/categories`, `/v2/product/countries`, `/v2/order`, `/v2/order/{id}`, `/v2/order/estimate`. Plural forms were a pre-existing error in both `runa_vendor.go` and the mock — they matched each other but neither matched real Runa, which means the production catalog-sync path had never been exercised against live Runa (option 3 of the original triage).

A larger refactor in the same session also corrected substantial **schema** mismatches that were inherited along with the path bug:
- Balance multi-response: was `{data: [...]}`, real Runa returns a top-level JSON array
- Products list: `data``catalog`; `pagination.next.after``pagination.cursors.after`; added `pagination.page.limit`
- Product get: dropped `data` wrapper; product is returned flat
- Denominations: `{min, max, values}` (numeric) → `{minimum_value, maximum_value, available_list}` (decimal strings); now nested under `gift_card`/`payment`/`subscription` per `payout_type`
- Order items: flat `payout_link` → nested `payout: {status, status_updated_at, url, expiry_date}`; item status moved into `payout.status` with values `ACTIVE | CLAIMED | CANCELLED | EXPIRED`
- Monetary fields are decimal strings end-to-end (parsed at the Go mapper boundary into `float64`)
- Async accept response: now `AsyncAcceptedOrder { id, status }` only (per spec); PROCESSING polls return `IncompleteOrder { id, status, message }`
- Order ID prefix: `ord_…``O-…` (matches real Runa)

**Why:** User asked the docs to be treated as source of truth and asked for full alignment of mock + integration.

**How to apply:** All Runa work should now treat `runa_vendor.go`, `runa_client.go`, `frontend/mocky-balboa/src/routes/runa.ts`, and `frontend/mocky-balboa/src/types/runa.ts` as authoritative — they mirror the OpenAPI spec. Do not reintroduce plural paths or numeric monetary fields. If real Runa returns something different in production, that's a docs/spec bug worth surfacing, not a sign to revert.