Skip to content
Convexly

Convexly Public API

v1

Read wallet diagnostics and resolution records in your own tools. Each endpoint below lists its authentication and plan requirements.

Current

Edge Score API

Jump to reference. Compute the Edge Score composite from features or end-to-end from a Polymarket wallet address. Auth: Supabase JWT.

https://www.convexly.app/api/engine/edge-score

Current

Track Record Summary

Jump to reference. Public aggregate status for wallet ratings, Coherence Signals, and caveat language.

https://www.convexly.app/api/track-record/summary

Current

Wallet Verdict API

Jump to reference. Four-state skill verdict (skilled / luck / insufficient / flagged) for one wallet or a tier-capped batch. Auth: X-API-Key.

https://www.convexly.app/api/engine/public/wallets

Current

Resolved-Markets Feed

Full feed documentation. Queryable settled-condition feed read from Polygon: payout vectors, winning outcome index, reporting oracle, settlement block. Chain-only by default, with a reproducible coverage receipt. Auth: X-API-Key, plan-gated with a daily row quota.

https://www.convexly.app/api/engine/public/resolved-markets

The public engine hostname is the same-origin proxy at https://www.convexly.app/api/engine. The old dedicated API subdomain is not published; do not use it in new integrations.

Authentication

Current · Edge Score API

Uses your Supabase access token (JWT) via the Authorization: Bearer <token> header. The Edge Score routes do NOT accept X-API-Key. Sign in at convexly.app/login to obtain a session token (or use the Supabase client library with your project credentials). Plan-gated to Researcher and above.

curl -H "Authorization: Bearer <supabase_jwt>" \
  https://www.convexly.app/api/engine/edge-score/from-wallet \
  -H "Content-Type: application/json" \
  -d '{"wallet_address": "0x..."}'

Current · Wallet Verdict API

Uses the X-API-Key header: single and batch four-state skill verdicts for a Polymarket wallet. See the Wallet Verdict reference for full paths, batch caps, and daily quotas. Same plan requirement as Edge Score above (Researcher tier or higher).

curl -H "X-API-Key: cvx_your_api_key_here" \
  "https://www.convexly.app/api/engine/public/wallets/0x56687bf447db6ffa42ffe2204a05edaa20f55839/verdict"

Keep your credentials secret. Both Supabase JWTs and X-API-Keys grant access to data scoped to your account. If a credential is compromised, rotate it immediately (sign out + back in for JWT; revoke + regenerate for X-API-Key).

Check your quota usage

GET /public/usage reports the calling key's wallet-read consumption for the current UTC day: plan, wallets used, cap, and remaining. It costs no quota, and the number it reports is read from the same counter that enforces the cap, so the two can never disagree.

curl -H "X-API-Key: cvx_your_api_key_here" \
  "https://www.convexly.app/api/engine/public/usage"

# {"plan":"researcher","usage_date":"2026-08-14",
#  "wallets_used":3,"wallets_cap":200,"wallets_remaining":197}

Edge Score

The Edge Score API exposes the Edge Score composite (posture + conviction + discipline) to external callers. The full methodology is published at Edge Score Methodology V1. Two endpoints: compute from precomputed features, or compute end-to-end from a Polymarket wallet address.

Base URL: https://www.convexly.app/api/engine/edge-score

Authentication: Supabase access token (JWT) via Authorization: Bearer <token>. The Edge Score API does not accept X-API-Key. Sign in via the Convexly frontend to obtain a session token, or use the Supabase client library with your project credentials.

Versioning: every response carries version: "v4", the engine's own version constant. TWO DATES, and this page used to print only the second as if it were the first: the composite was refit to V4 on 2026-07-13 (the receipt is the /methods/changelog entry, which cites the Edge Score engine module), while the emitted version string still read "v3b" until 2026-08-10. A response captured between those dates carries the old string over the new composite. A future composite will be served under a new version string with deprecation notice to all active callers.

POST/edge-score/from-features

Compute Edge Score from precomputed features. Rate limit: 60 requests / minute / IP.

Request Body

{
  "skill_brier": 0.05,      // null triggers median imputation
  "concentration": 0.6,     // 0-5, null triggers median imputation
  "n_positions": 40         // required, >= 0
}

Response (200)

// Shape verified against the engine source; values illustrative.
{
  "edge_score": 72.4,                     // 0-100 percentile
  "label": "Strong",
  "tagline": "Above average on the composite...",
  "risk_flag": false,
  "breakdown": {
    "edge_score": 72.4,
    "posture_percentile": 65.1,
    "conviction_percentile": 78.3,
    "discipline_percentile": 71.0,
    "posture_contribution": 0.47,
    "conviction_contribution": 2.12,
    "discipline_contribution": -0.28,
    "raw_score": 2.31,
    "n_positions_used": 40,
    "used_median_skill": false,
    "used_median_concentration": false,
    "calibration_percentile": 65.1,       // deprecated alias
    "calibration_contribution": 0.47      // deprecated alias
  },
  "version": "v4"
}
POST/edge-score/from-wallet

End-to-end: pages the public Polymarket Data API for up to 3,500 recent trades (500-row pages, hard offset cap at 3,100), with a positions fallback for resolved positions beyond that cap, resolves outcomes, computes features, runs the composite. Cached for 24 hours per wallet, and the response reports its own cache age. Rate limit: 10 requests / minute / IP.

Request Body

{
  "wallet_address": "0x56687bf447db6ffa42ffe2204a05edaa20f55839"
}

Response (200)

// Shape verified against the engine source; values illustrative.
{
  "edge_score": 91.2,
  "label": "Elite",
  "tagline": "Top decile by composite score...",
  "risk_flag": true,
  "breakdown": { ... },                   // same shape as /from-features
  "version": "v4",
  "wallet_address": "0x56687bf447db6ffa42ffe2204a05edaa20f55839",  // echoes the request
  "n_trades": 1843,                       // venue-window trade count
  "score_cache": {
    "cached": true,
    "cached_at": "2026-08-10T04:11:00+00:00",
    "age_seconds": 39240,
    "next_refresh_at": "2026-08-11T04:11:00+00:00",
    "ttl_hours": 24
  },
  "record_proof": {                       // chain record; null-filled if none
    "emission_completeness": "provably_full",
    "is_provably_full": true,
    "completeness_proof": {
      "basis": "chain",
      "reconciled_at_block": 91130322,
      "reconciled_as_of": "2026-07-01T00:00:00+00:00",
      "chain_stale": false,
      "is_provably_full": true,
      "verification": "Ask Polygon whether this address moved an outcome token after reconciled_at_block..."
    },
    "certified_at_block": 91130322,
    "realized_edge": 0.0231,
    "realized_edge_ci_bca": [0.0159, 0.0345],
    "realized_edge_n": 127,               // priced sample
    "n_resolved_complete": 214,           // full resolved set
    "method_version": "1c6e3ddc...",
    "unavailable_reason": null
  }
}

record_proof is the block-anchored completeness proof for the wallet, plus the realized edge priced off the reconstructed on-chain record. It is a different quantity from edge_score, which is a composite over a window of recent venue trades; neither bounds the other, so do not compare or combine them. When no current complete read exists for the wallet, every key in the block is null and unavailable_reason says which: no current complete read, or the lookup itself failed. The keys are never omitted. That is a statement about our files, not about the address.

Errors

401 Missing or invalid JWT

422 Invalid wallet address format

429 Rate limit exceeded; retry after the Retry-After header

502 Polymarket upstream temporarily unreachable

Usage metering: every successful and rate-limited call writes one row to our internal usage log (user ID, route, plan tier, status code, duration, wallet address if applicable). Append-only, user-read-own, service-role write. No metered billing is applied today. Pricing tiers may layer on top in a future release with advance notice.

Backwards compatibility: the calibration_percentile and calibration_contribution fields in the breakdown are deprecated aliases for posture_percentile and posture_contribution (the pillar rename landed 2026-04-18 and is explained in this post). Aliases will be removed in a later major version with at least 60 days of deprecation notice.

Track Record

The public track-record summary is a compact status endpoint for Convexly's evidence layer. It reports artifact coverage and caveats; it does not claim mature realized performance before resolved outcomes and matched controls support that wording.

GET/api/track-record/summary

Aggregate public status for wallet ratings, Coherence Signals, and evidence caveats.

publicLeaderboardSize counts the rows currently published on the Convexly wallet board. That board is a closed set of wallet addresses selected once on 2026-04-19 and never added to, screened at selection on realized PnL, and rescored daily. It is not a ranking of Polymarket wallets, and the field name is retained only for backward compatibility.

curl "https://www.convexly.app/api/track-record/summary"
// Captured from the live endpoint on 2026-08-28 (trimmed); counts move daily.
{
  "schema_version": "platform_track_record_summary_v1",
  "methodology_versions": {
    "wallet_ratings": "edge_score_v3b_public_snapshot",
    "coherence_signals": "cme_v0_1",
    "market_trust": "market_trust_card_v0_2"
  },
  "summary": {
    "walletRatings": { "publicLeaderboardSize": 41 },
    "coherenceSignals": { "signalsEmittedToday": 0 },
    "marketTrust": { "publicCards": 1 }
  }
}

Wallet Verdict

The Wallet Verdict API returns the four-state skill-badge machine (skilled / luck / insufficient / flagged) for a Polymarket wallet, with the realized edge always paired to its BCa bootstrap 95% interval in the same object. This is a different surface from the Edge Score composite above: same kind of input (a wallet address), different math, different response shape. Authentication is X-API-Key, not JWT.

Base URL: https://www.convexly.app/api/engine/public/wallets

Authentication: X-API-Key: cvx_your_api_key_here header on every request below except the badge redirect, which is keyless. Generate keys from Settings → API Keys (Researcher tier or higher: Researcher, Trader, or Enterprise; legacy Pro is also permitted).

GET/public/wallets/{address}/verdict

Single-wallet verdict. Served from the on-chain-reconciled record only: nothing is computed on request, and no Polymarket window is read on this path. A wallet carries an estimate only while a full-history read has been reconciled for it and nothing has moved on chain since the block that read was reconciled at. Otherwise every estimate field comes back null (second response below). Rate limit: 30 requests / minute / IP.

curl -H "X-API-Key: cvx_your_api_key_here" \
  "https://www.convexly.app/api/engine/public/wallets/0x56687bf447db6ffa42ffe2204a05edaa20f55839/verdict"

Response (200)

{
  "address": "0x56687bf447db6ffa42ffe2204a05edaa20f55839",
  "state": "skilled",
  "styles": ["directional"],
  "state_definition": "n_resolved >= 30, at least 20 independent resolved events, and the BCa bootstrap 95% lower bound on realized edge is above zero, all on the resolved record. Retrospective and in-sample, uncorrected for multiple comparisons. See styles for how the edge is earned and its payoff shape.",
  "realized_edge": 0.11,
  "realized_edge_ci_bca": [0.047, 0.267],
  "n_resolved": 45,
  "edge_disclosure": "+11.0pp (95% CI [+4.7pp, +26.7pp], n=45 of 58 resolved...",
  "n_resolved_complete": 58,
  "n_priced": 45,
  "n_unpriced": 13,
  "concentration": 0.2,
  "owner_verified": {
    "claimed": false,
    "verified": false,
    "verified_at": null,
    "verification_expires_at": null
  },
  "as_of": "2026-07-01T00:00:00.000Z",
  "reconciled_at_block": 91130322,
  "moved_since_block": false,
  "bound": "through block 91,130,322 (walked 2026-07-01 00:00 UTC).",
  "verdict_sentence": "Outside the luck range on the resolved record through block 91,130,322 (walked 2026-07-01 00:00 UTC).",
  "completeness_proof": {
    "basis": "chain",
    "reconciled_at_block": 91130322,
    "reconciled_as_of": "2026-07-01T00:00:00.000Z",
    "chain_stale": false,
    "moved_since_block": false,
    "is_provably_full": true,
    "verification": "Ask Polygon whether this address moved an outcome token after reconciled_at_block. A negative answer is the completeness claim; no venue API response can carry an equivalent check."
  },
  "method_hash": "b41f8c...a97d3e",
  "methodology_version": "badge-v1",
  "disclaimer": "Diagnostics on a public on-chain record, not investment advice, not a forecast."
}

Response (200), no current reconciled read

{
  "address": "0x1111111111111111111111111111111111111111",
  "state": "insufficient",
  "styles": ["directional"],
  "state_definition": "No skill state is returned because this endpoint has no on-chain-reconciled full-history read it can serve for this wallet. A read that has landed is returned bounded to the block it was reconciled at, whether or not the address has traded since, unless it has expired, its method is superseded, or the chain could not be asked; without one, the only record available omits redeemed positions and is not the complete resolved record. No realized edge is returned in its place, and n_resolved is null rather than zero: the resolved record was never counted here, and a count that was never taken is not a count that came to zero.",
  "realized_edge": null,
  "realized_edge_ci_bca": null,
  "n_resolved": null,
  "edge_disclosure": null,
  "n_resolved_complete": null,
  "n_priced": null,
  "n_unpriced": null,
  "concentration": null,
  "owner_verified": {
    "claimed": false,
    "verified": false,
    "verified_at": null,
    "verification_expires_at": null
  },
  "as_of": null,
  "reconciled_at_block": null,
  "moved_since_block": null,
  "bound": null,
  "verdict_sentence": null,
  "completeness_proof": null,
  "method_hash": null,
  "methodology_version": "badge-v1",
  "disclaimer": "Diagnostics on a public on-chain record, not investment advice, not a forecast."
}

Branch on n_resolved, not on state. n_resolved is a positive integer exactly when a read served, and null when none did. It is never 0. state does not answer the same question: insufficient is also returned WITH a realized_edge and an interval, for a wallet whose resolved record was read in full but whose sample size, event count, or share of positions carrying a recoverable entry price left that record unreadable either way. On that response n_resolved counts the priced subset the interval was computed on, which can be a small share of the resolved record, and state_definition says which cause applied. Read the edge on an insufficient response as a diagnostic, not as a skill reading.

Both denominators travel in the payload. n_resolved_complete is the size of the full resolved record, n_priced (equal to n_resolved) is the subset the edge was computed on, n_unpriced is the remainder no entry price was recovered for, and edge_disclosure carries the edge, its interval, and both counts in one string. These are the same fields, from the same stored read, that the reconciled-read endpoint below returns.

Check currency yourself with reconciled_at_block. reconciled_at_block is the Polygon block this read's completeness was checked against: at that block the reconstructed record matched the wallet's on-chain settlement history in full. Ask the chain whether the address moved an outcome token after this block; if it did, the read no longer covers the whole record. The field does not say the read is current now, only where an independent currency check must start. It is null on the no-read response, never 0.

A moved record is answered, bounded to its block. moved_since_block is the chain's serve-time answer to the currency question above. When it is true the state and its numbers still come back, and they cover the record up to reconciled_at_block and no further: positions taken after it are not in them. Reads are bounded rather than withheld because a walk of a full record takes longer than an active address's gap between trades, so the busiest addresses would otherwise never be readable at all. bound says that as one sentence you can print beside any number here, and verdict_sentence is the state itself in the same words convexly.app/w renders. Prefer it over writing your own sentence from state: the bare token carries no interval, no denominator and no bound. All three are null on the no-read response.

The proof rides in the payload. completeness_proof gathers the chain anchors this response already carries into one block, plus the verification instruction you can run against Polygon yourself. Its presence is packaging, not a verdict: read the is_provably_full and chain_stale values inside it, not the block's existence. method_hash is the hash of the frozen methodology the read was computed and gated under; pin it and compare across reads to detect a method change. Both are null on the no-read response.

Errors

400 Address is not a 0x-prefixed 42-character hex string

401 Missing, invalid, or revoked API key

403 Key's plan does not permit public API access

429 Daily wallet quota reached (see table below) or the per-minute rate limit was exceeded

502 Analysis failed; Polymarket may be temporarily unreachable

POST/public/wallets/verdict/batch

Verdicts for up to your plan's per-call cap of DISTINCT addresses (see table below; 100 is the platform ceiling regardless of plan). Invalid addresses return a per-item error instead of failing the whole batch. Rate limit: 10 requests / minute / IP.

Request Body

{
  "addresses": [
    "0x56687bf447db6ffa42ffe2204a05edaa20f55839",
    "0x1111111111111111111111111111111111111111"
  ]
}
curl -X POST "https://www.convexly.app/api/engine/public/wallets/verdict/batch" \
  -H "X-API-Key: cvx_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"addresses": ["0x56687bf447db6ffa42ffe2204a05edaa20f55839"]}'

Response (200)

{
  "requested": 2,
  "returned": 2,
  "plan": "researcher",
  "max_for_plan": 10,
  "results": [
    {
      "address": "0x56687bf447db6ffa42ffe2204a05edaa20f55839",
      "ok": true,
      "verdict": { "...": "same shape as the single verdict response above" }
    },
    {
      "address": "0x1111111111111111111111111111111111111111",
      "ok": true,
      "verdict": { "...": "same shape as the single verdict response above" }
    }
  ],
  "disclaimer": "Diagnostics on a public on-chain record, not investment advice, not a forecast."
}

403 when the number of distinct valid addresses exceeds your plan's per-call cap (no compute runs that call); otherwise the same error set as the single-wallet endpoint above applies.

GET/public/wallets/{address}/badgeNo key required

302 redirect to the embeddable badge SVG. Kept as a stable URL family for embedders. Rate limit: 60 requests / minute / IP.

curl -i "https://www.convexly.app/api/engine/public/wallets/0x56687bf447db6ffa42ffe2204a05edaa20f55839/badge"
# HTTP/1.1 302 Found
# Location: https://www.convexly.app/api/badge/wallet/0x56687bf447db6ffa42ffe2204a05edaa20f55839

For an embed, point an img tag at the destination URL directly rather than following the redirect on every page load: https://www.convexly.app/api/badge/wallet/{address}. Pass ?style=light for the light theme.

Per-plan batch cap and daily wallet quota

The batch cap bounds DISTINCT wallets in a single call; the daily quota bounds total wallets served per key, per UTC day, across both the single and batch endpoints above. The quota resets at 00:00 UTC.

Free

Not included

Researcher

10 / batch call
200 / UTC day

Trader

50 / batch call
1,000 / UTC day

Enterprise

250 / batch call
25,000 / UTC day

250 distinct wallets per batch call is the platform ceiling regardless of plan. Some Enterprise accounts run on a custom-quoted cap or quota instead of the default shown here; contact us if yours should differ. Every figure on this page comes from the same table the API enforces from; see Limits.

Wallet Set

Your plan does not buy a number of reads; it buys a bounded set of distinct wallets that Convexly keeps current on a per-tier cadence. These three endpoints make that set fully self-service over the API (previously available only from the dashboard): add a wallet, list your set with each member's lifecycle and staleness, and remove one. Once a wallet is in your set, read its on-chain-reconciled full-history read with the sibling GET /wallets/{address}/reconciled-read endpoint documented below. Authentication is X-API-Key, same as Wallet Verdict above.

Base URL: https://www.convexly.app/api/engine/public/wallets/reconciled-set

Authentication: X-API-Key: cvx_your_api_key_here on every request below. Generate keys from Settings → API Keys (Researcher tier or higher).

GET/public/wallets/reconciled-set

List every wallet in your set. Always returns 200, even at a cap of zero (limit: 0, can_add: false) -- viewing your own set is never refused. Rate limit: 30 requests / minute / IP.

curl -H "X-API-Key: cvx_your_api_key_here" \
  "https://www.convexly.app/api/engine/public/wallets/reconciled-set"

Response (200)

{
  "plan": "researcher",
  "limit": 10,
  "count": 1,
  "can_add": true,
  "refresh_days": 30,
  "members": [
    {
      "wallet_address": "0x56687bf447db6ffa42ffe2204a05edaa20f55839",
      "read_state": "ready",
      "added_at": "2026-07-20T00:00:00+00:00",
      "last_read_at": "2026-07-27T00:00:00+00:00",
      "next_refresh_due_at": "2026-08-26T00:00:00+00:00",
      "refresh_days": 30,
      "read_staleness": "fresh",
      "read_age_days": 2.0
    }
  ]
}

read_state is one of queued, running, ready, or failed, set by the refresh worker on its own schedule. read_staleness is fresh, due, or overdue relative to your plan's cadence; a wallet that has never been read is always overdue, never fresh.

POST/public/wallets/reconciled-set

Add one wallet to your set. Serialized against your plan cap on the database side, so concurrent adds (multiple keys, multiple processes, or a mix of the API and the dashboard) cannot land more wallets than your plan allows. Rate limit: 10 requests / minute / IP.

Request Body

{
  "wallet_address": "0x56687bf447db6ffa42ffe2204a05edaa20f55839"
}
curl -X POST "https://www.convexly.app/api/engine/public/wallets/reconciled-set" \
  -H "X-API-Key: cvx_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"wallet_address": "0x56687bf447db6ffa42ffe2204a05edaa20f55839"}'

Response (201)

{
  "member": {
    "wallet_address": "0x56687bf447db6ffa42ffe2204a05edaa20f55839",
    "read_state": "queued",
    "added_at": "2026-07-29T00:00:00+00:00",
    "last_read_at": null,
    "next_refresh_due_at": null,
    "refresh_days": 30,
    "read_staleness": "overdue",
    "read_age_days": null
  },
  "limit": 10,
  "count": 1
}

Errors

400 Address is not a 0x-prefixed 42-character hex string

403 Your set is already at its cap. The body names the limit and your current count: {"code": "wallet_set_cap_reached", "limit": 10, "count": 10, "plan": "researcher"}

409 This wallet is already in your set

502 The add could not be completed; retry

DELETE/public/wallets/reconciled-set/{address}

Remove one wallet from your set, freeing a slot under your plan's cap. Rate limit: 30 requests / minute / IP.

curl -X DELETE "https://www.convexly.app/api/engine/public/wallets/reconciled-set/0x56687bf447db6ffa42ffe2204a05edaa20f55839" \
  -H "X-API-Key: cvx_your_api_key_here"

Response (200)

{
  "success": true,
  "wallet_address": "0x56687bf447db6ffa42ffe2204a05edaa20f55839"
}

404 when the address is not in your set (or belongs to another account); 400 for a malformed address, before any delete is attempted.

GET/public/wallets/{address}/reconciled-read

Read the on-chain-reconciled full-history read for one wallet in your set. 404s for a wallet you have not added (costs no daily quota). Rate limit: 30 requests / minute / IP.

curl -H "X-API-Key: cvx_your_api_key_here" \
  "https://www.convexly.app/api/engine/public/wallets/0x56687bf447db6ffa42ffe2204a05edaa20f55839/reconciled-read"

Response (200, read landed)

{
  "address": "0x56687bf447db6ffa42ffe2204a05edaa20f55839",
  "available": true,
  "member": {
    "read_state": "ready",
    "added_at": "2026-07-20T00:00:00+00:00",
    "last_read_at": "2026-07-27T00:00:00+00:00",
    "next_refresh_due_at": "2026-08-26T00:00:00+00:00",
    "refresh_days": 30
  },
  "state": "skilled",
  "state_definition": "n_resolved >= 30, ...",
  "styles": ["directional"],
  "realized_edge": 0.0174,
  "realized_edge_ci_bca": [0.0102, 0.0244],
  "edge_disclosure": "realized edge 0.0174, 95% CI [0.0102, 0.0244], n=4099 of 4948 resolved...",
  "n_resolved_complete": 4948,
  "n_priced": 4099,
  "n_unpriced": 849,
  "reconciled_as_of": "2026-07-28T13:00:00+00:00",
  "reconciled_at_block": 91134712,
  "moved_since_block": true,
  "bound": "through block 91,134,712 (walked 2026-07-28 13:00 UTC). The address has moved outcome tokens since that block, so newer positions are not in this read.",
  "completeness_proof": {
    "basis": "chain",
    "reconciled_at_block": 91134712,
    "reconciled_as_of": "2026-07-28T13:00:00+00:00",
    "chain_stale": true,
    "moved_since_block": true,
    "is_provably_full": true,
    "verification": "Ask Polygon whether this address moved an outcome token after reconciled_at_block. A negative answer is the completeness claim; no venue API response can carry an equivalent check."
  },
  "method_hash": "b41f8c...a97d3e",
  "disclaimer": "Diagnostics on a public on-chain record, not investment advice, not a forecast."
}

Before the read lands, or when a stored read no longer proves complete, this returns {"available": false, "unavailable_reason": "not_reconciled", ...} with every estimate field null -- a partial or windowed number is never returned in its place.

moved_since_block is the chain's serve-time answer to whether the address moved an outcome token after reconciled_at_block. When it is true the read is still returned, bounded to that block: the counts and the estimate cover the record up to it, and positions taken after it are not in them. Reads are bounded rather than withheld because a walk of a full record takes longer than an active address's gap between trades, so the busiest addresses would otherwise never be readable at all. bound says the same thing as one sentence you can print beside any number from this response, and it is the wording convexly.app/w renders for the same read. Both are null when available is false.

reconciled_at_block is the Polygon block this read's completeness was checked against, so you can check currency independently: ask the chain whether the address moved an outcome token after this block. It does not say the read is current now. Null when available is false, never 0.

completeness_proof and method_hash carry the same anchors as the verdict endpoint: the proof block gathers the reconciliation stamps plus the verification instruction, and the hash pins the frozen methodology the read was gated under. Read the values inside the block, not its presence. Both are null when available is false.

Per-plan set cap and refresh cadence

The cap bounds distinct wallets in your set; the cadence is how often the refresh worker re-reads each member. These figures are generated from the same plan table the API enforces, so this page cannot state a limit the product does not apply.

Free

Not included

Researcher / Pro

10 wallets
30-day cadence

Trader

50 wallets
7-day cadence

Enterprise

100 wallets
7-day cadence

100 wallets is the platform ceiling regardless of plan. Some Enterprise accounts run on a custom-quoted cap instead of the default shown here; a bespoke cap always resolves no higher than 100 and, if left unset on an otherwise-customized account, floors to the Researcher cap rather than the Enterprise default.

Key Management

Key management endpoints use JWT authentication (your Supabase session), not API keys. These are used by the Settings page to manage keys.

POST/keys

Generate a new API key. The raw key is returned once and cannot be retrieved again.

Request Body

{
  "label": "My Slack integration"   // optional, default "default"
}

Response (201)

{
  "raw_key": "cvx_abc123...",
  "key_id": "uuid",
  "label": "My Slack integration",
  "created_at": "2026-03-07T12:00:00Z",
  "message": "Store this key securely -- it will not be shown again."
}
GET/keys

List your API keys (shows last 4 characters only).

Response (200)

{
  "keys": [
    {
      "id": "uuid",
      "label": "My Slack integration",
      "key_preview": "3xyz",
      "revoked": false,
      "last_used_at": "2026-03-07T10:00:00Z",
      "created_at": "2026-03-01T12:00:00Z"
    }
  ]
}
DELETE/keys/{key_id}

Revoke an API key. The key will immediately stop working.

Response (200)

{
  "revoked": true,
  "key_id": "uuid"
}

Rate Limits & Plan Access

Free

Edge Score API not included. Use the analyzer for one-off scoring.

Researcher · Trader INCLUDED

60 req/min on /from-features, 10 req/min on /from-wallet. Authenticate via Supabase JWT (X-API-Key for the wallet-verdict and key-authenticated public routes). Generate keys from Settings → API Keys.

Custom Enterprise

Higher rate limits, dedicated capacity statement, support windows, and named-cohort benchmarking only when written into an approved pilot or contract. Contact for terms.

Every limited response is a 429 in one documented shape, with Retry-After and the three RateLimit- headers below. Free-tier callers receive HTTP 403 with detail.error = "plan_required" and an upgrade_url pointing at /pricing.

Limits

Every limit a key holder is subject to, with the window it applies over and who it is counted against. These figures are read from the same table the API enforces from, so this page cannot state a number the server does not use.

Requests per operation

Counted per calling IP address, per operation. Each API instance keeps its own counter, so the published number is the least you get rather than an exact ceiling: while more than one instance is serving, the effective limit is higher.

OperationLimitAuth
GET /forecasts/{commitment_id}60 / 60snone
POST /forecasts/{commitment_id}/reveal30 / 60sX-API-Key
POST /forecasts/commit30 / 60sX-API-Key
GET /usage30 / 60sX-API-Key
GET /wallets/{address}/badge60 / 60snone
GET /wallets/{address}/proof-of-record30 / 60sX-API-Key
GET /wallets/{address}/reconciled-read30 / 60sX-API-Key
GET /wallets/{address}/verdict30 / 60sX-API-Key
GET /wallets/reconciled-set30 / 60sX-API-Key
POST /wallets/reconciled-set10 / 60sX-API-Key
DELETE /wallets/reconciled-set/{address}30 / 60sX-API-Key
GET /wallets/screen30 / 60sX-API-Key
POST /wallets/verdict/batch10 / 60sX-API-Key

Wallet reads per one UTC day, resetting at 00:00 UTC

Counted per API key, in one shared database counter, so it reads the same on every instance. Rotating a key continues the same day's count rather than starting a new one. Counted on GET /wallets/{address}/verdict, POST /wallets/verdict/batch, GET /wallets/{address}/reconciled-read, GET /wallets/{address}/proof-of-record. Read your own consumption and your tier's published figure from GET /usage.

Researcher

200 wallet reads per day
10 distinct wallets per batch call
10 new set addresses per 7 days

Trader

1,000 wallet reads per day
50 distinct wallets per batch call
10 new set addresses per 7 days

Enterprise

25,000 wallet reads per day
250 distinct wallets per batch call
25 new set addresses per 7 days

Counted per call, on distinct valid addresses. Duplicates and malformed addresses do not buy headroom and are not charged. Counted per account, on addresses that have never been in your set. Re-adding an address you have held before does not draw on it, adds made while first filling your set do not draw on it, and removals are free and immediate. A tier name this API does not publish resolves to the Researcher figures, never to a higher one.

What a limited response looks like

Every published operation can return 429 and every 429 carries the same object under detail. Branch on code, never on the sentence: rate_limit_exceeded clears in seconds, daily_wallet_quota_exceeded clears when the UTC day rolls, and wallet_set_window_limit_reached clears when your rolling allowance frees a slot.

HTTP/1.1 429 Too Many Requests
RateLimit-Limit: 10
RateLimit-Remaining: 0
RateLimit-Reset: 60
Retry-After: 6

{
  "detail": {
    "error": "Rate limit exceeded. Please try again later.",
    "code": "rate_limit_exceeded",
    "limit": 10,
    "limit_unit": "requests",
    "window_seconds": 60,
    "window_description": "a rolling 60 second window",
    "scope": "...",
    "retry_after_seconds": 6
  }
}

The three RateLimit- headers (RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset) are on every response from these operations, refused or not, so you can pace against your budget instead of discovering it by being refused. RateLimit-Reset is seconds until your bucket is full again; Retry-After on a 429 is the shorter wait until one request is available. They are exposed to browser clients through CORS.

Code Examples

Edge Score from a Polymarket wallet

curl -X POST https://www.convexly.app/api/engine/edge-score/from-wallet \
  -H "Authorization: Bearer $SUPABASE_JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "wallet_address": "0x56687bf447db6ffa42ffe2204a05edaa20f55839"
  }'

Wallet skill verdict (X-API-Key)

curl https://www.convexly.app/api/engine/public/wallets/0x56687bf447db6ffa42ffe2204a05edaa20f55839/verdict \
  -H "X-API-Key: cvx_your_key"

Error Responses

401

Missing or invalid API key

404

Resource not found (or doesn't belong to you)

422

Validation error (missing or invalid fields)

429

Rate limit exceeded

502

Upstream database error

Need help? Manage API keys · Go to Dashboard

Machine-readable spec: the X-API-Key surface (Wallet Verdict and the other key-authenticated public routes) publishes an OpenAPI 3 document at convexly.app/openapi.json, generated from the FastAPI schema and checked in CI against drift from the code.