docs(indexer): OpenAPI/path pack + curls for swaps, pools, fees, burns, windows #1204

Closed
opened 2026-09-04 07:57:58 +00:00 by PlasticDigits · 6 comments

Summary

Publish a single indexer HTTP docs pack so integrators and research agents can hit the live read API without reverse-engineering docs/indexer-invariants.md or the utoipa derive.

Bundle (do not split):

  1. Machine catalog — live OpenAPI (GET /api-docs/openapi.json, Swagger UI /swagger-ui/) or a checked-in path list generated from the same ApiDoc source.
  2. Copy-paste curls for the five evidence surfaces: swaps, pools, fees, burns, volume windows.
  3. Window semantics — trailing 24h/7d/30d vs UTC calendar-day series vs DeFiLlama UTC day — so agents do not mix them.
  4. OpenAPI coverage gaps for routes that already exist but are missing from ApiDoc.

This is docs + OpenAPI completeness. No new business endpoints, no indexer ingest change, no frontend.

Related (do not merge into this ticket): #707 (execute-path integrator warning: pool-only pair.swap vs solver/hybrid). #216 hybrid volume. #577 trailing-window decay. #586 / #614 protocol fees. #652 / #668 volume series. #631 DeFiLlama UTC-day.


Current codebase

The indexer already has an HTTP surface and a generated spec. What is missing is a pack a human or agent can copy in one sitting.

OpenAPI is live, not a pack

indexer/src/api/mod.rs ApiDoc (utoipa::OpenApi) is mounted as:

Path Role
GET /api-docs/openapi.json Generated OpenAPI 3 JSON
GET /swagger-ui/ Interactive UI

Regression: indexer/tests/security.rs openapi_spec_available, swagger_ui_available. QA checklist mentions both (QA_TEMPLATE.md 13.7.x).

There is no committed openapi.json / path markdown under docs/. Integrators are told “see Swagger” in passing (docs/integrators.md, docs/integrators-hybrid-volume.md) but not given curls for the five surfaces below.

ApiDoc.paths omits existing Axum routes (handlers may still have #[utoipa::path]):

  • GET /health
  • GET /api/v1/health/fee-discount
  • GET /api/v1/compliance/blacklist-check (LCD-heavy; compliance.rs)

Those stay out of the five-surface pack except as a one-line “also on the router / LCD-heavy” note. Closing the ApiDoc gap is in scope so the spec matches build_router.

Docs today are the wrong shape for copy-paste HTTP

Doc What it is Why it is not the pack
docs/indexer-invariants.md Invariant matrix (caps, caches, L10, LCD governors) Too large; not example curls
docs/integrators.md Hybrid execute, limit-book LCD proxy, route solve Book/solver, not tape/fees/burns windows
docs/route-solver.md Solver theory + one local curl Quotes, not historical swaps
docs/DEFILLAMA.md UTC-day listing adapter Calendar day ≠ trailing 24h
Runbooks (suspicious-activity-queries.md, launch-monitoring.md, …) Operator curls Incident-shaped; not a public pack
skills/AGENTS_INDEXER_*.md Agent playbooks Scattered; not indexed from docs/README.md as HTTP catalog

The five surfaces (already implemented)

Swaps (tape / quote — not execute)

Method Path Notes
GET /api/v1/pairs/{addr}/trades Indexed swap_events; optional pool_leg_volume / book_leg_volume; raw amount strings + offer_decimals / ask_decimals (#557)
GET /api/v1/traders/{addr}/trades Optional pair=, format=json|csv, limit/before (CSV formula prefix #432)
GET /api/v1/route/solve Quote, not history. LCD-heavy. amount_in → global best execution. pool_only=true is pool-only.
GET /gt/events?fromBlock=&toBlock= Combined swap+liquidity; max 2000 blocks / 5000 rows (#646 / #694)

Headline volume = one parent swap_events row (L10). Do not sum limit_order_fills into pair volume.

Pools

Method Path Notes
GET /api/v1/pairs Paginated factory pairs. sort=volume_24h (raw quote) vs volume_usd_24h / liquidity_usd (NULLS LAST). Additive volume_usd_24h, liquidity_usd, created_at.
GET /api/v1/pairs/{addr} Detail; code_id_frozen may be true while still listed.
GET /api/v1/pairs/{addr}/liquidity-events Mint/burn LP events; limit/before like trades.
GET /api/v1/pairs/{addr}/stats Trailing 24h OHLC + volume_usd. volume_base/volume_quote stay raw integers.
GET /api/v1/tokens/{addr}/pairs Pairs that include the asset.

Fees (protocol treasury — not Terra Classic burn tax)

Method Path Notes
GET /api/v1/protocol/fees?window= Allowlist 24h | 7d | 30d else 400. O(1) rollup, 60s cache. by_source / by_token.
GET /api/v1/protocol/fees/daily grain=hourly|daily|monthly + capped limit. Extra from/to/window/days → 400.
GET /api/v1/overview Additive total_fees_{24h,7d,30d}_usd (idle "0", activity+unpriced null).
GET /api/v1/defillama/daily?timestamp= UTC calendar day (unix % 86400 == 0). Not trailing 24h.

Parser explicitly does not count Terra Classic burn tax / tax_amount as treasury fees (protocol_fees.rs).

Burns (post-swap hooks)

There is no /burns route. Burns are GET /api/v1/hooks:

  • Parser keeps wasm action values that start with after_swap_ (parser.rs parse_hook_events). Tests use after_swap_burn.
  • Amount/token from burn_amount / burn_token (fallback tax_amount / tax_token).
  • Query: optional hook_address, limit clamped 1–200 (default 50).
  • On-chain: smartcontracts/contracts/hooks/burn-hook, lp-burn-hook. LP mint/burn pool events stay on liquidity-events, not hooks.

Volume windows

Kind Path Clock
Trailing DEX census GET /api/v1/overview Utc::now() − N for 24h/7d/30d volume and fees. Not midnight UTC reset (#576).
Token offer-side windows GET /api/v1/tokens/{addr} → volume_stats[].window 24h / 7d / 30d rollup; idle windows zero (#577). Lifetime totals are not zeroed.
Pair 24h stamp GET /api/v1/pairs?sort=volume_usd_24h, GET .../pairs/{addr}/stats ~5 min aggregator; GET does not SUM(swap_events).
UTC series GET /api/v1/protocol/volume/daily?days=7|30 or grain= + limit= Calendar buckets (#652 / #668). from/to → 400.
Listing UTC day GET /api/v1/defillama/daily?timestamp= Gem-excluded; not overview.

Trader volume_24h / 7d / 30d exist on profile JSON but are raw; retail USD is total_volume_usd. Do not document trader rolling windows as human USD.

Rate limits and read-only

  • Almost all routes are GET. Only POST /api/v1/route/solve writes nothing to indexer DB (JSON merge + optional LCD sim). Other POST → 405.
  • Global governor default 60 RPS. LCD-heavy (route solve, limit-book, CG/CMC orderbook, progress, blacklist-check) default 10 RPS. Abuse → 429 + Retry-After.
  • Example curls in the pack must not loop LCD-heavy routes.

Why the work is needed

  1. The spec exists behind a running process. Agents that only have the git tree cannot see /api-docs/openapi.json. A path list (generated from ApiDoc) plus curls in docs/ is usable offline.
  2. The five evidence questions share one HTTP family but live in five different docs. Swaps vs fees vs burns vs windows are easy to conflate (burn tax vs treasury fee; trailing 24h vs Llama UTC day; volume_quote_24h raw vs volume_usd_24h).
  3. Wrong curl wastes LCD and looks like abuse. Without a pack, agents copy limit-book / route/solve examples for volume research.
  4. OpenAPI drift. Routes added to build_router without ApiDoc.paths silently disappear from Swagger. The pack should fail CI if the five-surface paths (and the known health/compliance gaps once closed) leave the spec.

This does not change quotes, ingest, or the dApp.


Constraints / guardrails

  • Docs + OpenAPI only. No new query params, no new tables, no deployment or env changes, no contract migrate.
  • Do not invent routes. Especially no /burns, /swaps, or /api/v1/listings. Burns = /hooks. Pools = /pairs.
  • Placeholders only in curls: $INDEXER, $PAIR, $TOKEN, $TRADER, $HOOK. Never paste real wallets, tx hashes, or private captures. Default $INDEXER in docs: https://indexer.dex.cl8y.com (already public) and http://127.0.0.1:3001 for local.
  • URLSearchParams / -G --data-urlencode — never concatenate user strings into the path or query.
  • Window allowlists stay. Document 400 for window=1h, window=ust1-window, grain=week, extra from/to on fee/volume daily. Do not suggest those params.
  • Trailing ≠ calendar ≠ Llama. Three clocks; label every curl.
  • L10. One parent swap per taker tx. Pack must say “do not SUM fills into volume.”
  • Fees ≠ burn tax ≠ hook burn. Treasury GET /protocol/fees is not after_swap_burn and not LUNC burn tax.
  • Raw vs USD. volume_quote_24h / pair stats volume_base are raw integers. USD fields may be JSON null (unpriced), "0" (idle), or a decimal string. Document that.
  • LCD-heavy curls are optional appendix, not the five-surface default. If shown: one-shot, note 10 RPS / 429.
  • CSV. If trader-trades CSV is shown: format=csv only on trader history; mention leading =/+/-/@ prefix. No CSV on /protocol/fees.
  • Gems / listing exclude. Do not tell agents to scrape /gt for the pool table; official pool list is /api/v1/pairs. Gem pairs omitted from /cg /cmc /gt.
  • No secrets. Pack must not document DATABASE_URL, LCD URLs beyond “configured upstream,” or operator runbook host names.
  • Do not expand #707 here. One sentence + link: quotes/history HTTP ≠ on-chain execute.

Relevant files

File Why
indexer/src/api/mod.rs ApiDoc, build_router, Swagger mount, governors
indexer/src/api/pairs.rs Pairs, trades, liquidity-events, stats
indexer/src/api/hooks.rs Burns via hook events
indexer/src/api/protocol_fees.rs Trailing fee windows
indexer/src/api/protocol_fee_series.rs Fee UTC series
indexer/src/api/protocol_volume.rs Volume UTC series
indexer/src/api/overview.rs Trailing census
indexer/src/api/tokens.rs volume_stats windows
indexer/src/api/defillama.rs UTC-day listing
indexer/src/api/gt.rs Block-range events
indexer/src/api/route_solver.rs Swap quotes
indexer/src/api/compliance.rs Missing from ApiDoc.paths
indexer/src/indexer/parser.rs after_swap_* / burn_amount
indexer/src/indexer/volume_aggregator.rs Window refresh
indexer/tests/security.rs OpenAPI / Swagger / injection / 429
indexer/tests/api_hooks.rs Hook list + limit clamp
docs/indexer-invariants.md Canonical semantics (link, do not duplicate the matrix)
docs/README.md Index the new pack
docs/integrators.md Cross-link pack vs execute guidance

  1. Add docs/indexer-http.md (name may be docs/indexer-http-pack.md if that matches local docs style). Structure:
    • Base URL + Accept: application/json.
    • How to get the full spec: curl -sS "$INDEXER/api-docs/openapi.json" and /swagger-ui/.
    • Path table grouped: Swaps, Pools, Fees, Burns, Volume windows. Columns: method, path, clock (trailing / UTC day / n/a), LCD-heavy?, default/max limit, 400/404 notes, invariant link.
    • Example curls (one happy path each). Use -sS and jq filters on documented fields only (items[0].pair_address, window, action, volume_usd).
    • Anti-mixups box: L10, fees vs burns vs burn tax, raw vs USD, trailing vs protocol/volume/daily vs /defillama/daily.
    • Unhappy curls: invalid window, unknown pair 404, POST to a GET route 405.
  2. Link from docs/README.md (Architecture) and a short paragraph at the top of docs/integrators.md (“read API pack” vs “execute hybrid”).
  3. Close ApiDoc gaps for /health, /api/v1/health/fee-discount, /api/v1/compliance/blacklist-check so Swagger matches build_router. Tag Compliance/Health. Do not add new JSON fields.
  4. CI drift check (small): extend openapi_spec_available (or a new test) to assert the five-surface paths exist in ApiDoc::openapi() JSON paths. Optional: make verify-issue-<id> greps that docs/indexer-http.md contains each path string and the three clock labels.
  5. Curls to include (implementer fills real placeholders; keep these shapes):
INDEXER="${INDEXER:-https://indexer.dex.cl8y.com}"

# Pools
curl -sS "$INDEXER/api/v1/pairs?sort=volume_usd_24h&order=desc&limit=5"
curl -sS "$INDEXER/api/v1/pairs/$PAIR"
curl -sS "$INDEXER/api/v1/pairs/$PAIR/liquidity-events?limit=20"
curl -sS "$INDEXER/api/v1/pairs/$PAIR/stats"

# Swaps (tape)
curl -sS "$INDEXER/api/v1/pairs/$PAIR/trades?limit=20"
curl -sS "$INDEXER/api/v1/traders/$TRADER/trades?limit=20"

# Volume windows (trailing vs UTC series vs Llama day)
curl -sS "$INDEXER/api/v1/overview"
curl -sS "$INDEXER/api/v1/tokens/$TOKEN"
curl -sS "$INDEXER/api/v1/protocol/volume/daily?days=7"
curl -sS "$INDEXER/api/v1/protocol/volume/daily?grain=hourly&limit=24"

# Fees (trailing allowlist)
curl -sS "$INDEXER/api/v1/protocol/fees?window=24h"
curl -sS "$INDEXER/api/v1/protocol/fees/daily?grain=daily&limit=14"

# Burns (hooks; not treasury fees)
curl -sS "$INDEXER/api/v1/hooks?limit=50"
curl -sS -G "$INDEXER/api/v1/hooks" --data-urlencode "hook_address=$HOOK" --data-urlencode "limit=50"

# Spec
curl -sS "$INDEXER/api-docs/openapi.json" | jq '.paths | keys'
  1. Optional quote curl in an appendix only: GET /api/v1/route/solve?token_in=&token_out=&amount_in= with LCD-heavy warning. Do not put it in the default five-surface list as “swaps history.”

Land as one MR. Do not rewrite indexer-invariants.md into the pack.


Acceptance criteria

  • AC1. docs/indexer-http.md (or agreed name) exists and is linked from docs/README.md.
  • AC2. Pack contains a path table and example curls for all five families: swaps tape, pools, fees, burns/hooks, volume windows.
  • AC3. Pack states how to fetch OpenAPI (/api-docs/openapi.json) and Swagger UI (/swagger-ui/).
  • AC4. Three clocks are labeled: trailing 24h|7d|30d, UTC series protocol/volume/daily / protocol/fees/daily, DeFiLlama UTC day. At least one curl each.
  • AC5. Pack says burns are GET /api/v1/hooks (after_swap_burn); treasury fees are GET /api/v1/protocol/fees; Terra Classic burn tax is neither.
  • AC6. Pack says pair/token raw volume fields are not human USD; null vs "0" vs string.
  • AC7. Pack says L10: do not SUM limit_order_fills into pair volume.
  • AC8. ApiDoc.paths includes /health, /api/v1/health/fee-discount, /api/v1/compliance/blacklist-check (existing handlers only).
  • AC9. Test or make verify-issue-<id> fails if a five-surface path disappears from OpenAPI JSON or from the markdown pack.
  • AC10. No new Axum routes. No secrets. Placeholders only. LCD-heavy not in the default loop.
  • AC11. #707 remains the execute-path doc; this pack does not restate Pattern C / greedy except a pointer.

Test plan (functional paths)

# Path Expect
T1 GET $INDEXER/api-docs/openapi.json 200 JSON; paths includes /api/v1/pairs, /api/v1/pairs/{addr}/trades, /api/v1/hooks, /api/v1/protocol/fees, /api/v1/overview, /api/v1/protocol/volume/daily, /api/v1/tokens/{addr}
T2 GET $INDEXER/swagger-ui/ 200 HTML containing swagger
T3 GET /api/v1/pairs?sort=volume_usd_24h&order=desc&limit=5 200; items length ≤ 5
T4 GET /api/v1/pairs/{known}/stats 200; volume_base/volume_quote digit strings; volume_usd string or null
T5 GET /api/v1/pairs/{known}/trades?limit=20 200 array; optional hybrid legs
T6 GET /api/v1/pairs/{known}/liquidity-events?limit=20 200
T7 GET /api/v1/overview 200; 24h/7d/30d volume and fee fields present
T8 GET /api/v1/tokens/{known} 200; volume_stats[].window in 24h,7d,30d
T9 GET /api/v1/protocol/fees?window=24h 200; window echo; by_source
T10 GET /api/v1/protocol/fees?window=7d and 30d 200
T11 GET /api/v1/protocol/fees?window=1h 400
T12 GET /api/v1/protocol/fees/daily?grain=daily&limit=14 200
T13 GET /api/v1/protocol/volume/daily?days=7 200; timezone UTC
T14 GET /api/v1/protocol/volume/daily?grain=hourly&limit=24 200
T15 GET /api/v1/hooks?limit=50 200 array; action may be after_swap_burn
T16 GET /api/v1/hooks?hook_address={known} 200; all rows that address
T17 GET /api/v1/pairs/not-a-pair/trades 404
T18 POST /api/v1/pairs 405
T19 GET /health {"status":"ok"}; listed in OpenAPI after AC8
T20 Pack markdown Every T3–T16 path string appears in docs/indexer-http.md
T21 Local fixture Existing api_hooks, indexer_protocol_fees, indexer_protocol_volume, api_pairs, security OpenAPI tests still green

Vitest/Playwright not required. Indexer: cargo test slice + make verify-issue-<id> greps. Optional live curl against local indexer in the verify script when the process is up; skip-live if not.


Test plan (attack, hack, and abuse)

# Vector Expect
A1 window=24h;DROP%20TABLE / window=javascript:alert(1) 400; body not echoed as HTML
A2 grain=hourly;DROP / grain=week on volume/fees daily 400
A3 Extra from/to/window on /protocol/fees/daily 400 (cache-bust / injection)
A4 hook_address SQL metacharacters Parameterized; 200 empty or 200 filtered; never 500 with sqlx text
A5 limit=-1 / limit=0 on hooks, trades, pairs Clamp to 1, never negative LIMIT 500
A6 limit=999999 Upper clamp (hooks 200, pairs list max, trades max)
A7 offset=10001 on /pairs 400
A8 Path /api/v1/pairs/<script> / overlong bech32 404 or 400; no reflected XSS in JSON
A9 Pack curl using string concat "$INDEXER/api/v1/pairs/" + user without encode Forbidden in docs; show -G --data-urlencode or {addr} as whole path segment after checksum
A10 Documented loop of GET /api/v1/route/solve or /limit-book Must not appear as default pack; if appendix, one-shot + 429 note
A11 Example that SUMs /limit-fills as volume Forbidden by AC7
A12 Example treating /protocol/fees by_source as burn-hook burns Forbidden by AC5
A13 Trader CSV format=csv cell =cmd Prefixed '; pack mentions it if CSV curl is included
A14 OpenAPI JSON served with attacker-controlled description from query Spec is static ApiDoc; no query reflection
A15 POST /api/v1/route/solve body > 128 KiB 413; pack does not encourage POST for the five surfaces
A16 Gem pair on /gt/events / /cg/pairs Omit/404; pack must not instruct scraping gems via GT for pool census
A17 Pack includes Authorization / DB URL / LCD host Fail review
A18 GET /api/v1/compliance/blacklist-check?tokens= 17 addrs 400 before LCD (document as LCD-heavy appendix only)

Existing coverage to keep green: security.rs, api_hooks.rs, indexer_protocol_fees.rs, indexer_protocol_fees_series.rs, api_limit_lower_bound.rs.


Verification criteria

  • make verify-issue-<id> (new): markdown greps for the five families, three clocks, /api-docs/openapi.json, /api/v1/hooks, allowlist 24h|7d|30d; OpenAPI unit test includes the five-surface paths + health/compliance after AC8.
  • cargo test -p cl8y-dex-indexer --test security --test api_hooks --test api_pairs --test indexer_protocol_fees --test indexer_protocol_volume -- --test-threads=1 (adjust binary names to match crate) stays green.
  • Manual: curl the spec keys; run the happy curls against local indexer or the public read API with placeholders filled from GET /api/v1/pairs?limit=1 (no wallet). Invalid window returns 400. /hooks returns JSON array.
  • Docs review: docs/README.md lists the pack; no secret strings; #707 still owns execute-path wording.

Out of scope

  • New indexer routes (/burns, /swaps, /volume-windows).
  • Changing rollup math, window cutoffs, or ingest (#577 / #586 behavior stays).
  • Pair-direct vs hybrid execute docs (#707 / #704).
  • Exact-out /route/solve.
  • Dumping every CG/CMC/GT listing field into the pack (link OpenAPI + docs/CG_CMC_COMPLIANCE.md / docs/DEFILLAMA.md).
  • Frontend Protocol/Pool copy.
  • Operator incident runbooks.
  • Checked-in production dumps of OpenAPI responses (PII / tape). Spec paths only.
## Summary Publish a **single indexer HTTP docs pack** so integrators and research agents can hit the live read API without reverse-engineering [`docs/indexer-invariants.md`](docs/indexer-invariants.md) or the utoipa derive. Bundle (do not split): 1. **Machine catalog** — live OpenAPI (`GET /api-docs/openapi.json`, Swagger UI `/swagger-ui/`) **or** a checked-in path list generated from the same `ApiDoc` source. 2. **Copy-paste curls** for the five evidence surfaces: **swaps**, **pools**, **fees**, **burns**, **volume windows**. 3. **Window semantics** — trailing `24h`/`7d`/`30d` vs UTC calendar-day series vs DeFiLlama UTC day — so agents do not mix them. 4. **OpenAPI coverage gaps** for routes that already exist but are missing from `ApiDoc`. This is **docs + OpenAPI completeness**. No new business endpoints, no indexer ingest change, no frontend. Related (do not merge into this ticket): [#707](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/707) (execute-path integrator warning: pool-only `pair.swap` vs solver/hybrid). [#216](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/216) hybrid volume. [#577](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/577) trailing-window decay. [#586](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/586) / [#614](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/614) protocol fees. [#652](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/652) / [#668](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/668) volume series. [#631](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/631) DeFiLlama UTC-day. --- ## Current codebase The indexer already **has** an HTTP surface and a generated spec. What is missing is a **pack** a human or agent can copy in one sitting. ### OpenAPI is live, not a pack [`indexer/src/api/mod.rs`](indexer/src/api/mod.rs) `ApiDoc` (`utoipa::OpenApi`) is mounted as: | Path | Role | |------|------| | `GET /api-docs/openapi.json` | Generated OpenAPI 3 JSON | | `GET /swagger-ui/` | Interactive UI | Regression: [`indexer/tests/security.rs`](indexer/tests/security.rs) `openapi_spec_available`, `swagger_ui_available`. QA checklist mentions both ([`QA_TEMPLATE.md`](QA_TEMPLATE.md) 13.7.x). There is **no** committed `openapi.json` / path markdown under `docs/`. Integrators are told “see Swagger” in passing ([`docs/integrators.md`](docs/integrators.md), [`docs/integrators-hybrid-volume.md`](docs/integrators-hybrid-volume.md)) but not given curls for the five surfaces below. **`ApiDoc.paths` omits existing Axum routes** (handlers may still have `#[utoipa::path]`): - `GET /health` - `GET /api/v1/health/fee-discount` - `GET /api/v1/compliance/blacklist-check` (LCD-heavy; [`compliance.rs`](indexer/src/api/compliance.rs)) Those stay out of the five-surface pack except as a one-line “also on the router / LCD-heavy” note. Closing the `ApiDoc` gap is in scope so the spec matches `build_router`. ### Docs today are the wrong shape for copy-paste HTTP | Doc | What it is | Why it is not the pack | |-----|------------|-------------------------| | [`docs/indexer-invariants.md`](docs/indexer-invariants.md) | Invariant matrix (caps, caches, L10, LCD governors) | Too large; not example curls | | [`docs/integrators.md`](docs/integrators.md) | Hybrid execute, limit-book LCD proxy, route solve | Book/solver, not tape/fees/burns windows | | [`docs/route-solver.md`](docs/route-solver.md) | Solver theory + one local curl | Quotes, not historical swaps | | [`docs/DEFILLAMA.md`](docs/DEFILLAMA.md) | UTC-day listing adapter | Calendar day ≠ trailing 24h | | Runbooks (`suspicious-activity-queries.md`, `launch-monitoring.md`, …) | Operator curls | Incident-shaped; not a public pack | | `skills/AGENTS_INDEXER_*.md` | Agent playbooks | Scattered; not indexed from `docs/README.md` as HTTP catalog | ### The five surfaces (already implemented) **Swaps (tape / quote — not execute)** | Method | Path | Notes | |--------|------|--------| | GET | `/api/v1/pairs/{addr}/trades` | Indexed `swap_events`; optional `pool_leg_volume` / `book_leg_volume`; raw amount strings + `offer_decimals` / `ask_decimals` ([#557](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/557)) | | GET | `/api/v1/traders/{addr}/trades` | Optional `pair=`, `format=json\|csv`, `limit`/`before` (CSV formula prefix [#432](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/432)) | | GET | `/api/v1/route/solve` | **Quote**, not history. LCD-heavy. `amount_in` → global best execution. `pool_only=true` is pool-only. | | GET | `/gt/events?fromBlock=&toBlock=` | Combined swap+liquidity; max 2000 blocks / 5000 rows ([#646](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/646) / [#694](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/694)) | Headline volume = one parent `swap_events` row (**L10**). Do not sum `limit_order_fills` into pair volume. **Pools** | Method | Path | Notes | |--------|------|--------| | GET | `/api/v1/pairs` | Paginated factory pairs. `sort=volume_24h` (raw quote) vs `volume_usd_24h` / `liquidity_usd` (NULLS LAST). Additive `volume_usd_24h`, `liquidity_usd`, `created_at`. | | GET | `/api/v1/pairs/{addr}` | Detail; `code_id_frozen` may be true while still listed. | | GET | `/api/v1/pairs/{addr}/liquidity-events` | Mint/burn LP events; `limit`/`before` like trades. | | GET | `/api/v1/pairs/{addr}/stats` | Trailing **24h** OHLC + `volume_usd`. `volume_base`/`volume_quote` stay **raw integers**. | | GET | `/api/v1/tokens/{addr}/pairs` | Pairs that include the asset. | **Fees (protocol treasury — not Terra Classic burn tax)** | Method | Path | Notes | |--------|------|--------| | GET | `/api/v1/protocol/fees?window=` | Allowlist **`24h` \| `7d` \| `30d`** else **400**. O(1) rollup, 60s cache. `by_source` / `by_token`. | | GET | `/api/v1/protocol/fees/daily` | `grain=hourly\|daily\|monthly` + capped `limit`. Extra `from`/`to`/`window`/`days` → **400**. | | GET | `/api/v1/overview` | Additive `total_fees_{24h,7d,30d}_usd` (idle `"0"`, activity+unpriced `null`). | | GET | `/api/v1/defillama/daily?timestamp=` | **UTC calendar day** (`unix % 86400 == 0`). Not trailing 24h. | Parser explicitly does **not** count Terra Classic burn tax / `tax_amount` as treasury fees ([`protocol_fees.rs`](indexer/src/indexer/protocol_fees.rs)). **Burns (post-swap hooks)** There is **no** `/burns` route. Burns are **`GET /api/v1/hooks`**: - Parser keeps wasm `action` values that start with `after_swap_` ([`parser.rs`](indexer/src/indexer/parser.rs) `parse_hook_events`). Tests use `after_swap_burn`. - Amount/token from `burn_amount` / `burn_token` (fallback `tax_amount` / `tax_token`). - Query: optional `hook_address`, `limit` clamped **1–200** (default 50). - On-chain: [`smartcontracts/contracts/hooks/burn-hook`](smartcontracts/contracts/hooks/burn-hook), [`lp-burn-hook`](smartcontracts/contracts/hooks/lp-burn-hook). LP mint/burn **pool** events stay on `liquidity-events`, not hooks. **Volume windows** | Kind | Path | Clock | |------|------|--------| | Trailing DEX census | `GET /api/v1/overview` | `Utc::now() − N` for 24h/7d/30d volume **and** fees. Not midnight UTC reset ([#576](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/576)). | | Token offer-side windows | `GET /api/v1/tokens/{addr}` → `volume_stats[].window` | `24h` / `7d` / `30d` rollup; idle windows **zero** ([#577](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/577)). Lifetime totals are not zeroed. | | Pair 24h stamp | `GET /api/v1/pairs?sort=volume_usd_24h`, `GET .../pairs/{addr}/stats` | ~5 min aggregator; GET does not `SUM(swap_events)`. | | UTC series | `GET /api/v1/protocol/volume/daily?days=7\|30` or `grain=` + `limit=` | Calendar buckets ([#652](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/652) / [#668](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/668)). `from`/`to` → **400**. | | Listing UTC day | `GET /api/v1/defillama/daily?timestamp=` | Gem-excluded; not overview. | Trader `volume_24h` / `7d` / `30d` exist on profile JSON but are **raw**; retail USD is `total_volume_usd`. Do not document trader rolling windows as human USD. ### Rate limits and read-only - Almost all routes are **GET**. Only `POST /api/v1/route/solve` writes nothing to indexer DB (JSON merge + optional LCD sim). Other POST → **405**. - Global governor default **60 RPS**. LCD-heavy (route solve, limit-book, CG/CMC orderbook, progress, blacklist-check) default **10 RPS**. Abuse → **429** + `Retry-After`. - Example curls in the pack must **not** loop LCD-heavy routes. --- ## Why the work is needed 1. **The spec exists behind a running process.** Agents that only have the git tree cannot see `/api-docs/openapi.json`. A path list (generated from `ApiDoc`) plus curls in `docs/` is usable offline. 2. **The five evidence questions share one HTTP family** but live in five different docs. Swaps vs fees vs burns vs windows are easy to conflate (burn tax vs treasury fee; trailing 24h vs Llama UTC day; `volume_quote_24h` raw vs `volume_usd_24h`). 3. **Wrong curl wastes LCD and looks like abuse.** Without a pack, agents copy limit-book / `route/solve` examples for volume research. 4. **OpenAPI drift.** Routes added to `build_router` without `ApiDoc.paths` silently disappear from Swagger. The pack should fail CI if the five-surface paths (and the known health/compliance gaps once closed) leave the spec. This does **not** change quotes, ingest, or the dApp. --- ## Constraints / guardrails - **Docs + OpenAPI only.** No new query params, no new tables, no deployment or env changes, no contract migrate. - **Do not invent routes.** Especially no `/burns`, `/swaps`, or `/api/v1/listings`. Burns = `/hooks`. Pools = `/pairs`. - **Placeholders only** in curls: `$INDEXER`, `$PAIR`, `$TOKEN`, `$TRADER`, `$HOOK`. Never paste real wallets, tx hashes, or private captures. Default `$INDEXER` in docs: `https://indexer.dex.cl8y.com` (already public) **and** `http://127.0.0.1:3001` for local. - **`URLSearchParams` / `-G --data-urlencode`** — never concatenate user strings into the path or query. - **Window allowlists stay.** Document **400** for `window=1h`, `window=ust1-window`, `grain=week`, extra `from`/`to` on fee/volume daily. Do not suggest those params. - **Trailing ≠ calendar ≠ Llama.** Three clocks; label every curl. - **L10.** One parent swap per taker tx. Pack must say “do not SUM fills into volume.” - **Fees ≠ burn tax ≠ hook burn.** Treasury `GET /protocol/fees` is not `after_swap_burn` and not LUNC burn tax. - **Raw vs USD.** `volume_quote_24h` / pair stats `volume_base` are raw integers. USD fields may be JSON `null` (unpriced), `"0"` (idle), or a decimal string. Document that. - **LCD-heavy curls are optional appendix**, not the five-surface default. If shown: one-shot, note 10 RPS / 429. - **CSV.** If trader-trades CSV is shown: `format=csv` only on trader history; mention leading `=`/`+`/`-`/`@` prefix. No CSV on `/protocol/fees`. - **Gems / listing exclude.** Do not tell agents to scrape `/gt` for the pool table; official pool list is `/api/v1/pairs`. Gem pairs omitted from `/cg` `/cmc` `/gt`. - **No secrets.** Pack must not document `DATABASE_URL`, LCD URLs beyond “configured upstream,” or operator runbook host names. - **Do not expand [#707](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/707)** here. One sentence + link: quotes/history HTTP ≠ on-chain execute. --- ## Relevant files | File | Why | |------|-----| | [`indexer/src/api/mod.rs`](indexer/src/api/mod.rs) | `ApiDoc`, `build_router`, Swagger mount, governors | | [`indexer/src/api/pairs.rs`](indexer/src/api/pairs.rs) | Pairs, trades, liquidity-events, stats | | [`indexer/src/api/hooks.rs`](indexer/src/api/hooks.rs) | Burns via hook events | | [`indexer/src/api/protocol_fees.rs`](indexer/src/api/protocol_fees.rs) | Trailing fee windows | | [`indexer/src/api/protocol_fee_series.rs`](indexer/src/api/protocol_fee_series.rs) | Fee UTC series | | [`indexer/src/api/protocol_volume.rs`](indexer/src/api/protocol_volume.rs) | Volume UTC series | | [`indexer/src/api/overview.rs`](indexer/src/api/overview.rs) | Trailing census | | [`indexer/src/api/tokens.rs`](indexer/src/api/tokens.rs) | `volume_stats` windows | | [`indexer/src/api/defillama.rs`](indexer/src/api/defillama.rs) | UTC-day listing | | [`indexer/src/api/gt.rs`](indexer/src/api/gt.rs) | Block-range events | | [`indexer/src/api/route_solver.rs`](indexer/src/api/route_solver.rs) | Swap **quotes** | | [`indexer/src/api/compliance.rs`](indexer/src/api/compliance.rs) | Missing from `ApiDoc.paths` | | [`indexer/src/indexer/parser.rs`](indexer/src/indexer/parser.rs) | `after_swap_*` / `burn_amount` | | [`indexer/src/indexer/volume_aggregator.rs`](indexer/src/indexer/volume_aggregator.rs) | Window refresh | | [`indexer/tests/security.rs`](indexer/tests/security.rs) | OpenAPI / Swagger / injection / 429 | | [`indexer/tests/api_hooks.rs`](indexer/tests/api_hooks.rs) | Hook list + limit clamp | | [`docs/indexer-invariants.md`](docs/indexer-invariants.md) | Canonical semantics (link, do not duplicate the matrix) | | [`docs/README.md`](docs/README.md) | Index the new pack | | [`docs/integrators.md`](docs/integrators.md) | Cross-link pack vs execute guidance | --- ## Recommended direction 1. **Add [`docs/indexer-http.md`](docs/indexer-http.md)** (name may be `docs/indexer-http-pack.md` if that matches local docs style). Structure: - Base URL + `Accept: application/json`. - **How to get the full spec:** `curl -sS "$INDEXER/api-docs/openapi.json"` and `/swagger-ui/`. - **Path table** grouped: Swaps, Pools, Fees, Burns, Volume windows. Columns: method, path, clock (trailing / UTC day / n/a), LCD-heavy?, default/max `limit`, 400/404 notes, invariant link. - **Example curls** (one happy path each). Use `-sS` and `jq` filters on documented fields only (`items[0].pair_address`, `window`, `action`, `volume_usd`). - **Anti-mixups** box: L10, fees vs burns vs burn tax, raw vs USD, trailing vs `protocol/volume/daily` vs `/defillama/daily`. - **Unhappy curls:** invalid `window`, unknown pair 404, `POST` to a GET route 405. 2. **Link** from [`docs/README.md`](docs/README.md) (Architecture) and a short paragraph at the top of [`docs/integrators.md`](docs/integrators.md) (“read API pack” vs “execute hybrid”). 3. **Close `ApiDoc` gaps** for `/health`, `/api/v1/health/fee-discount`, `/api/v1/compliance/blacklist-check` so Swagger matches `build_router`. Tag Compliance/Health. Do not add new JSON fields. 4. **CI drift check (small):** extend `openapi_spec_available` (or a new test) to assert the five-surface paths exist in `ApiDoc::openapi()` JSON `paths`. Optional: `make verify-issue-<id>` greps that `docs/indexer-http.md` contains each path string and the three clock labels. 5. **Curls to include** (implementer fills real placeholders; keep these shapes): ```bash INDEXER="${INDEXER:-https://indexer.dex.cl8y.com}" # Pools curl -sS "$INDEXER/api/v1/pairs?sort=volume_usd_24h&order=desc&limit=5" curl -sS "$INDEXER/api/v1/pairs/$PAIR" curl -sS "$INDEXER/api/v1/pairs/$PAIR/liquidity-events?limit=20" curl -sS "$INDEXER/api/v1/pairs/$PAIR/stats" # Swaps (tape) curl -sS "$INDEXER/api/v1/pairs/$PAIR/trades?limit=20" curl -sS "$INDEXER/api/v1/traders/$TRADER/trades?limit=20" # Volume windows (trailing vs UTC series vs Llama day) curl -sS "$INDEXER/api/v1/overview" curl -sS "$INDEXER/api/v1/tokens/$TOKEN" curl -sS "$INDEXER/api/v1/protocol/volume/daily?days=7" curl -sS "$INDEXER/api/v1/protocol/volume/daily?grain=hourly&limit=24" # Fees (trailing allowlist) curl -sS "$INDEXER/api/v1/protocol/fees?window=24h" curl -sS "$INDEXER/api/v1/protocol/fees/daily?grain=daily&limit=14" # Burns (hooks; not treasury fees) curl -sS "$INDEXER/api/v1/hooks?limit=50" curl -sS -G "$INDEXER/api/v1/hooks" --data-urlencode "hook_address=$HOOK" --data-urlencode "limit=50" # Spec curl -sS "$INDEXER/api-docs/openapi.json" | jq '.paths | keys' ``` 6. **Optional quote curl** in an appendix only: `GET /api/v1/route/solve?token_in=&token_out=&amount_in=` with LCD-heavy warning. Do not put it in the default five-surface list as “swaps history.” Land as **one MR**. Do not rewrite `indexer-invariants.md` into the pack. --- ## Acceptance criteria - [ ] **AC1.** `docs/indexer-http.md` (or agreed name) exists and is linked from `docs/README.md`. - [ ] **AC2.** Pack contains a path table **and** example curls for all five families: swaps tape, pools, fees, burns/hooks, volume windows. - [ ] **AC3.** Pack states how to fetch **OpenAPI** (`/api-docs/openapi.json`) and Swagger UI (`/swagger-ui/`). - [ ] **AC4.** Three clocks are labeled: trailing `24h|7d|30d`, UTC series `protocol/volume/daily` / `protocol/fees/daily`, DeFiLlama UTC day. At least one curl each. - [ ] **AC5.** Pack says burns are `GET /api/v1/hooks` (`after_swap_burn`); treasury fees are `GET /api/v1/protocol/fees`; Terra Classic burn tax is neither. - [ ] **AC6.** Pack says pair/token **raw** volume fields are not human USD; `null` vs `"0"` vs string. - [ ] **AC7.** Pack says **L10**: do not SUM `limit_order_fills` into pair volume. - [ ] **AC8.** `ApiDoc.paths` includes `/health`, `/api/v1/health/fee-discount`, `/api/v1/compliance/blacklist-check` (existing handlers only). - [ ] **AC9.** Test or `make verify-issue-<id>` fails if a five-surface path disappears from OpenAPI JSON or from the markdown pack. - [ ] **AC10.** No new Axum routes. No secrets. Placeholders only. LCD-heavy not in the default loop. - [ ] **AC11.** [#707](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/707) remains the execute-path doc; this pack does not restate Pattern C / greedy except a pointer. --- ## Test plan (functional paths) | # | Path | Expect | |---|------|--------| | T1 | `GET $INDEXER/api-docs/openapi.json` | 200 JSON; `paths` includes `/api/v1/pairs`, `/api/v1/pairs/{addr}/trades`, `/api/v1/hooks`, `/api/v1/protocol/fees`, `/api/v1/overview`, `/api/v1/protocol/volume/daily`, `/api/v1/tokens/{addr}` | | T2 | `GET $INDEXER/swagger-ui/` | 200 HTML containing swagger | | T3 | `GET /api/v1/pairs?sort=volume_usd_24h&order=desc&limit=5` | 200; `items` length ≤ 5 | | T4 | `GET /api/v1/pairs/{known}/stats` | 200; `volume_base`/`volume_quote` digit strings; `volume_usd` string or null | | T5 | `GET /api/v1/pairs/{known}/trades?limit=20` | 200 array; optional hybrid legs | | T6 | `GET /api/v1/pairs/{known}/liquidity-events?limit=20` | 200 | | T7 | `GET /api/v1/overview` | 200; 24h/7d/30d volume **and** fee fields present | | T8 | `GET /api/v1/tokens/{known}` | 200; `volume_stats[].window` in `24h`,`7d`,`30d` | | T9 | `GET /api/v1/protocol/fees?window=24h` | 200; `window` echo; `by_source` | | T10 | `GET /api/v1/protocol/fees?window=7d` and `30d` | 200 | | T11 | `GET /api/v1/protocol/fees?window=1h` | **400** | | T12 | `GET /api/v1/protocol/fees/daily?grain=daily&limit=14` | 200 | | T13 | `GET /api/v1/protocol/volume/daily?days=7` | 200; `timezone` UTC | | T14 | `GET /api/v1/protocol/volume/daily?grain=hourly&limit=24` | 200 | | T15 | `GET /api/v1/hooks?limit=50` | 200 array; `action` may be `after_swap_burn` | | T16 | `GET /api/v1/hooks?hook_address={known}` | 200; all rows that address | | T17 | `GET /api/v1/pairs/not-a-pair/trades` | **404** | | T18 | `POST /api/v1/pairs` | **405** | | T19 | `GET /health` | `{"status":"ok"}`; listed in OpenAPI after AC8 | | T20 | Pack markdown | Every T3–T16 path string appears in `docs/indexer-http.md` | | T21 | Local fixture | Existing `api_hooks`, `indexer_protocol_fees`, `indexer_protocol_volume`, `api_pairs`, `security` OpenAPI tests still green | Vitest/Playwright not required. Indexer: `cargo test` slice + `make verify-issue-<id>` greps. Optional live `curl` against local indexer in the verify script when the process is up; skip-live if not. --- ## Test plan (attack, hack, and abuse) | # | Vector | Expect | |---|--------|--------| | A1 | `window=24h;DROP%20TABLE` / `window=javascript:alert(1)` | **400**; body not echoed as HTML | | A2 | `grain=hourly;DROP` / `grain=week` on volume/fees daily | **400** | | A3 | Extra `from`/`to`/`window` on `/protocol/fees/daily` | **400** (cache-bust / injection) | | A4 | `hook_address` SQL metacharacters | Parameterized; 200 empty or 200 filtered; never 500 with sqlx text | | A5 | `limit=-1` / `limit=0` on hooks, trades, pairs | Clamp to **1**, never negative `LIMIT` **500** | | A6 | `limit=999999` | Upper clamp (hooks 200, pairs list max, trades max) | | A7 | `offset=10001` on `/pairs` | **400** | | A8 | Path `/api/v1/pairs/<script>` / overlong bech32 | **404** or 400; no reflected XSS in JSON | | A9 | Pack curl using string concat `"$INDEXER/api/v1/pairs/" + user` without encode | Forbidden in docs; show `-G --data-urlencode` or `{addr}` as whole path segment after checksum | | A10 | Documented loop of `GET /api/v1/route/solve` or `/limit-book` | Must **not** appear as default pack; if appendix, one-shot + 429 note | | A11 | Example that `SUM`s `/limit-fills` as volume | Forbidden by AC7 | | A12 | Example treating `/protocol/fees` `by_source` as burn-hook burns | Forbidden by AC5 | | A13 | Trader CSV `format=csv` cell `=cmd` | Prefixed `'`; pack mentions it if CSV curl is included | | A14 | OpenAPI JSON served with attacker-controlled `description` from query | Spec is static `ApiDoc`; no query reflection | | A15 | `POST /api/v1/route/solve` body > 128 KiB | **413**; pack does not encourage POST for the five surfaces | | A16 | Gem pair on `/gt/events` / `/cg/pairs` | Omit/404; pack must not instruct scraping gems via GT for pool census | | A17 | Pack includes `Authorization` / DB URL / LCD host | Fail review | | A18 | `GET /api/v1/compliance/blacklist-check?tokens=` 17 addrs | **400** before LCD (document as LCD-heavy appendix only) | Existing coverage to keep green: [`security.rs`](indexer/tests/security.rs), [`api_hooks.rs`](indexer/tests/api_hooks.rs), [`indexer_protocol_fees.rs`](indexer/tests/indexer_protocol_fees.rs), [`indexer_protocol_fees_series.rs`](indexer/tests/indexer_protocol_fees_series.rs), [`api_limit_lower_bound.rs`](indexer/tests/api_limit_lower_bound.rs). --- ## Verification criteria - `make verify-issue-<id>` (new): markdown greps for the five families, three clocks, `/api-docs/openapi.json`, `/api/v1/hooks`, allowlist `24h|7d|30d`; OpenAPI unit test includes the five-surface paths + health/compliance after AC8. - `cargo test -p cl8y-dex-indexer --test security --test api_hooks --test api_pairs --test indexer_protocol_fees --test indexer_protocol_volume -- --test-threads=1` (adjust binary names to match crate) stays green. - Manual: `curl` the spec keys; run the happy curls against local indexer **or** the public read API with placeholders filled from `GET /api/v1/pairs?limit=1` (no wallet). Invalid `window` returns 400. `/hooks` returns JSON array. - Docs review: `docs/README.md` lists the pack; no secret strings; [#707](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/707) still owns execute-path wording. --- ## Out of scope - New indexer routes (`/burns`, `/swaps`, `/volume-windows`). - Changing rollup math, window cutoffs, or ingest ([#577](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/577) / [#586](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/586) behavior stays). - Pair-direct vs hybrid **execute** docs ([#707](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/707) / [#704](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/704)). - Exact-out `/route/solve`. - Dumping every CG/CMC/GT listing field into the pack (link OpenAPI + [`docs/CG_CMC_COMPLIANCE.md`](docs/CG_CMC_COMPLIANCE.md) / [`docs/DEFILLAMA.md`](docs/DEFILLAMA.md)). - Frontend Protocol/Pool copy. - Operator incident runbooks. - Checked-in production dumps of OpenAPI **responses** (PII / tape). Spec paths only.
Member

Market overnight 2026-09-06: fee-ledger work is home on this repo (new intakes for pair_creation / SKU+settings invoices / actor-cohort). This docs/OpenAPI pack remains the integrator path for Market to pull fee facts once those sources land — already ready+agent:implement. New fee intakes: #1209, #1210, #1211, #1212.

Market overnight 2026-09-06: fee-ledger work is home on this repo (new intakes for pair_creation / SKU+settings invoices / actor-cohort). This docs/OpenAPI pack remains the integrator path for Market to pull fee facts once those sources land — already `ready`+`agent:implement`. New fee intakes: #1209, #1210, #1211, #1212.
Author
Owner

/agent implement

/agent implement
Author
Owner

cl8y-agent-control: queued design_author job 85d4b800-34f5-440d-b1ee-b594e6a5bd84 (not executed; no Hetzner VM).

cl8y-agent-control: queued `design_author` job `85d4b800-34f5-440d-b1ee-b594e6a5bd84` (not executed; no Hetzner VM).
Author
Owner

cl8y-agent-control: queued implement job 6341f1c9-5d61-4bde-8fb2-dfd0d5365cbe (not executed; no Hetzner VM).

cl8y-agent-control: queued `implement` job `6341f1c9-5d61-4bde-8fb2-dfd0d5365cbe` (not executed; no Hetzner VM).
Author
Owner

cl8y-agent-control: fix-cycle cap reached; human required.

cl8y-agent-control: fix-cycle cap reached; human required.
Author
Owner

cl8y-agent-control: needs_human inbox card POST failed. Job stays parked.

cl8y-agent-control: needs_human inbox card POST failed. Job stays parked.
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
code/cl8y-dex-terraclassic#1204
No description provided.