@0xinsider/sdk 0.35.0 → 0.37.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/schema.d.ts CHANGED
@@ -51,6 +51,23 @@ export type AgentRegistration = {
51
51
  auth_guide_url: string;
52
52
  };
53
53
  };
54
+ /** A computed count with its truth state; the same envelope as AnalyticNumber. */
55
+ export type AnalyticInteger = {
56
+ state: "computed" | "known_zero" | "not_computed" | "not_applicable" | "stale" | "unavailable" | "insufficient_data";
57
+ source: "provider" | "insert_time" | "cache" | "computed";
58
+ value?: number;
59
+ freshness_at?: string;
60
+ };
61
+ /** A computed number with its truth state. value is present when state is computed or known_zero and may be present when stale; any other state says why there is no number, never a stand-in zero. */
62
+ export type AnalyticNumber = {
63
+ /** computed: a measured value. known_zero: a measured zero. stale: a value past its freshness window. not_computed: an input is unknown. not_applicable: the measure does not apply. unavailable: the source could not be read. insufficient_data: too few observations. */
64
+ state: "computed" | "known_zero" | "not_computed" | "not_applicable" | "stale" | "unavailable" | "insufficient_data";
65
+ /** Where the value came from, for diagnostics: cache is a stored aggregate, computed a request-time aggregate. Decide on state, not source. */
66
+ source: "provider" | "insert_time" | "cache" | "computed";
67
+ value?: number;
68
+ /** When the stored inputs behind the value were written or last confirmed (cache), or the request time (computed). */
69
+ freshness_at?: string;
70
+ };
54
71
  export type ApiDiscovery = {
55
72
  /** Canonical API origin for public V1 requests. */
56
73
  api_base_url: string;
@@ -77,8 +94,10 @@ export type ApiError = {
77
94
  /** The recommended next request instant (RFC3339), always in the future. Present on every retryable error: `pick_not_released`, `rate_limited`, `rate_limit_unavailable`, and `read_model_warming`. Omitted otherwise. The absolute twin of `Retry-After`; prefer the header for the sleep duration. For `pick_not_released`, the earliest of the next scheduled release, the next automatic selector attempt, the operating-window start, or about 60 seconds. See that response. */
78
95
  retry_at?: string;
79
96
  freshness?: FreshnessFailure;
80
- /** ADDITIVE (#7209). The specific, actionable cause behind `code`, when there is one more specific than the code itself. `code` keeps its published values, so existing clients are unaffected; new clients branch on `reason`. Omitted when the code already says everything we know. pick_not_released: no Pick of the Day is published for the current product day; schedule one request against retry_at instead of polling. unknown_endpoint: the PATH is not a route on this API -- read GET /api/v1, do not retry. trader_not_tracked: the wallet is real and the URL is right, but the trader is not on the warm sync tier (the wallets we sync most often) -- stop asking for this wallet. market_out_of_scope (404 not_found, #27303): the market or event is real and the URL is right, but 0xinsider serves sports and esports markets only -- stop asking for this market; neither a retry nor another URL for it can succeed. cursor_expired: pagination went stale mid-walk -- re-request the first page and continue. read_model_warming: the requested endpoint cannot serve its read model yet; exact causes are endpoint-specific and can include a cold or contended refresh or a dependency that prevented refresh. database_unavailable: the API's database or its connection pool is temporarily unreachable (a connection-class failure, not a query fault); code stays rate_limit_unavailable, nothing is rate-limited, retry after Retry-After / retry_at. idempotency_in_progress: retain the exact Idempotency-Key and request body, then retry shortly. webhook_delivery_in_progress: retry the URL or signing-secret configuration change after the destination's active request completes. request_accounting_unavailable: accounting capacity is unavailable before the handler executes; retry after Retry-After / retry_at. sandbox_api_key: the credential is a sandbox key (oxi_sk_test_) from POST /api/v1/agents/register, which only the sandbox server accepts -- call the sandbox base URL with it, or get a live key or OAuth access token; do not retry it here. api_key_in_query: the key was sent as a ?token= query parameter, which no route reads because URLs land in logs and history; the key itself was not checked -- resend it as Authorization: Bearer. subscription_inactive: the key is valid but the account's Pro subscription has lapsed (402 subscription_required); permanent until a person reactivates at https://0xinsider.com/billing, which the message names -- stop retrying on a schedule and surface the link. The key owner is emailed once per lapse. monthly_quota_exceeded: the account has used the requests its plan includes for the UTC calendar month (429 rate_limited); retry_at and Retry-After name the first of next month, the only retry that can succeed, and the message names https://0xinsider.com/developers, where pay as you go for requests over the quota is turned on. The X-Monthly-Quota-Limit, X-Monthly-Quota-Remaining and X-Monthly-Quota-Reset headers on every authenticated response say how close the account is. invalid_query, invalid_path, invalid_body (400 bad_request, #16146): a query parameter, a path segment or the JSON body did not parse or does not fit the route's schema, so no handler ran; param names the field when the parser named one (a query key, a path segment, a JSON path such as traders[0], or body); fix the request, never retry it as sent. unsupported_media_type (415 bad_request, param content-type): send the body with Content-Type: application/json. payload_too_large (413 bad_request, param body): the body is over 1048576 bytes. method_not_allowed (405 bad_request): the path is a route but not with this method; the Allow header names the methods it serves. ip_rate_limited (429 rate_limited, #16380): the per-address budget every caller behind one IP shares, counted before authentication, is spent; not the key's own window, and the RateLimit-* headers describe that bucket. ip_throttled (429 rate_limited): the address is in a cooldown after sustained over-limit traffic; Retry-After is minutes to days, and a request before it does not shorten the cooldown. cold_read_limited (429 rate_limited, #28752): the account has used its share of cold reads on GET /api/v1/market/{condition_id}/holders, requests for a market whose roster is not cached, each of which walks Polymarket (6 a minute, in bursts of up to 400); not the per-minute window, so a cached market still answers, and the next cold read is admitted at Retry-After. max_plan_required (403 forbidden, #23085): the key is valid and its account is on Pro, but the route is part of Max (GET /api/v1/edge-hunter); every Pro route keeps working with the same key, so do not disable it. The message names https://0xinsider.com/pricing, where the account upgrades; a retry never clears it. endpoint_retired (410): this legacy sports endpoint no longer serves data. Migrate to GET /api/v1/edge-hunter; retrying the retired path cannot succeed. */
81
- reason?: "cursor_expired" | "unknown_endpoint" | "pick_not_released" | "trader_not_tracked" | "market_out_of_scope" | "read_model_warming" | "database_unavailable" | "request_accounting_unavailable" | "idempotency_in_progress" | "webhook_delivery_in_progress" | "webhook_secret_rotation_not_prepared" | "webhook_secret_rotation_overlap_active" | "sandbox_api_key" | "api_key_in_query" | "subscription_inactive" | "monthly_quota_exceeded" | "invalid_query" | "unknown_query_parameter" | "invalid_path" | "invalid_body" | "unsupported_media_type" | "payload_too_large" | "method_not_allowed" | "ip_rate_limited" | "ip_throttled" | "cold_read_limited" | "export_expired" | "freshness_ceiling_unsatisfied" | "max_plan_required" | "endpoint_retired";
97
+ /** Where a person clears this 402 (#32592): pays the overdue invoice (reason payment_overdue) or reactivates the plan (reason subscription_inactive). Present on every 402 subscription_required, omitted on every other error. The message names the same URL; link this field instead of parsing the message. */
98
+ billing_url?: string;
99
+ /** ADDITIVE (#7209). The specific, actionable cause behind `code`, when there is one more specific than the code itself. `code` keeps its published values, so existing clients are unaffected; new clients branch on `reason`. Omitted when the code already says everything we know. pick_not_released: no Pick of the Day is published for the current product day; schedule one request against retry_at instead of polling. unknown_endpoint: the PATH is not a route on this API -- read GET /api/v1, do not retry. trader_not_tracked: the wallet is real and the URL is right, but the trader is not on the warm sync tier (the wallets we sync most often) -- stop asking for this wallet. market_out_of_scope (404 not_found, #27303): the market or event is real and the URL is right, but 0xinsider serves sports and esports markets only -- stop asking for this market; neither a retry nor another URL for it can succeed. cursor_expired: pagination went stale mid-walk -- re-request the first page and continue. read_model_warming: the requested endpoint cannot serve its read model yet; exact causes are endpoint-specific and can include a cold or contended refresh or a dependency that prevented refresh. database_unavailable: the API's database or its connection pool is temporarily unreachable (a connection-class failure, not a query fault); code stays rate_limit_unavailable, nothing is rate-limited, retry after Retry-After / retry_at. idempotency_in_progress: retain the exact Idempotency-Key and request body, then retry shortly. webhook_delivery_in_progress: retry the URL or signing-secret configuration change after the destination's active request completes. request_accounting_unavailable: accounting capacity is unavailable before the handler executes; retry after Retry-After / retry_at. sandbox_api_key: the credential is a sandbox key (oxi_sk_test_) from POST /api/v1/agents/register, which only the sandbox server accepts -- call the sandbox base URL with it, or get a live key or OAuth access token; do not retry it here. api_key_in_query: the key was sent as a ?token= query parameter, which no route reads because URLs land in logs and history; the key itself was not checked -- resend it as Authorization: Bearer. subscription_inactive: the key is valid but the account's Pro subscription has lapsed (402 subscription_required); permanent until a person reactivates at https://0xinsider.com/billing, which the message names -- stop retrying on a schedule and surface the link. The key owner is emailed once per lapse. payment_overdue (402 subscription_required, #32592): the key is valid and the account still has its plan, but its latest subscription invoice is unpaid (Stripe past_due or unpaid), so paid access is paused; permanent until a person pays at billing_url (https://0xinsider.com/billing) -- stop retrying on a schedule and surface the link. The key works again the moment the invoice is paid. GET /api/v1/me and GET /api/v1/usage keep answering. monthly_quota_exceeded: the account has used the requests its plan includes for the UTC calendar month (429 rate_limited); retry_at and Retry-After name the first of next month, the only retry that can succeed, and the message names https://0xinsider.com/developers, where pay as you go for requests over the quota is turned on. The X-Monthly-Quota-Limit, X-Monthly-Quota-Remaining and X-Monthly-Quota-Reset headers on every authenticated response say how close the account is. invalid_query, invalid_path, invalid_body (400 bad_request, #16146): a query parameter, a path segment or the JSON body did not parse or does not fit the route's schema, so no handler ran; param names the field when the parser named one (a query key, a path segment, a JSON path such as traders[0], or body); fix the request, never retry it as sent. unsupported_media_type (415 bad_request, param content-type): send the body with Content-Type: application/json. payload_too_large (413 bad_request, param body): the body is over 1048576 bytes. method_not_allowed (405 bad_request): the path is a route but not with this method; the Allow header names the methods it serves. ip_rate_limited (429 rate_limited, #16380): the per-address budget every caller behind one IP shares, counted before authentication, is spent; not the key's own window, and the RateLimit-* headers describe that bucket. ip_throttled (429 rate_limited): the address is in a cooldown after sustained over-limit traffic; Retry-After is minutes to days, and a request before it does not shorten the cooldown. cold_read_limited (429 rate_limited, #28752): the account has used its share of cold reads on GET /api/v1/market/{condition_id}/holders, requests for a market whose roster is not cached, each of which walks Polymarket (6 a minute, in bursts of up to 400); not the per-minute window, so a cached market still answers, and the next cold read is admitted at Retry-After. max_plan_required (403 forbidden, #23085): the key is valid and its account is on Pro, but the route is part of Max (GET /api/v1/edge-hunter); every Pro route keeps working with the same key, so do not disable it. The message names https://0xinsider.com/pricing, where the account upgrades; a retry never clears it. endpoint_retired (410): this legacy sports endpoint no longer serves data. Migrate to GET /api/v1/edge-hunter; retrying the retired path cannot succeed. */
100
+ reason?: "cursor_expired" | "unknown_endpoint" | "pick_not_released" | "trader_not_tracked" | "market_out_of_scope" | "read_model_warming" | "database_unavailable" | "request_accounting_unavailable" | "idempotency_in_progress" | "webhook_delivery_in_progress" | "webhook_secret_rotation_not_prepared" | "webhook_secret_rotation_overlap_active" | "sandbox_api_key" | "api_key_in_query" | "subscription_inactive" | "payment_overdue" | "monthly_quota_exceeded" | "invalid_query" | "unknown_query_parameter" | "invalid_path" | "invalid_body" | "unsupported_media_type" | "payload_too_large" | "method_not_allowed" | "ip_rate_limited" | "ip_throttled" | "cold_read_limited" | "export_expired" | "freshness_ceiling_unsatisfied" | "max_plan_required" | "endpoint_retired";
82
101
  };
83
102
  meta: ResponseMeta;
84
103
  };
@@ -91,8 +110,10 @@ export type ApiErrorBody = {
91
110
  /** The recommended next retry instant (RFC3339). Present on every retryable error (reason=pick_not_released, code=rate_limited including reason=monthly_quota_exceeded, code=rate_limit_unavailable, reason=read_model_warming) and omitted otherwise. Always in the future. For pick_not_released, the recommendation is advisory and can change before a pick becomes available. Schedule the next request from the returned value instead of inferring a release schedule or polling. Prefer Retry-After for the duration because it is immune to client clock skew. */
92
111
  retry_at?: string;
93
112
  freshness?: FreshnessFailure;
94
- /** ADDITIVE (#7209). The specific, actionable cause behind `code`, when there is one more specific than the code itself. `code` keeps its published values, so existing clients are unaffected; new clients branch on `reason`. Omitted when the code already says everything we know. pick_not_released: no Pick of the Day is published for the current product day; schedule one request against retry_at instead of polling. unknown_endpoint: the PATH is not a route on this API -- read GET /api/v1, do not retry. trader_not_tracked: the wallet is real and the URL is right, but the trader is not on the warm sync tier (the wallets we sync most often) -- stop asking for this wallet. market_out_of_scope (404 not_found, #27303): the market or event is real and the URL is right, but 0xinsider serves sports and esports markets only -- stop asking for this market; neither a retry nor another URL for it can succeed. cursor_expired: pagination went stale mid-walk -- re-request the first page and continue. read_model_warming: the requested endpoint cannot serve its read model yet; exact causes are endpoint-specific and can include a cold or contended refresh or a dependency that prevented refresh. database_unavailable: the API's database or its connection pool is temporarily unreachable (a connection-class failure, not a query fault); code stays rate_limit_unavailable, nothing is rate-limited, retry after Retry-After / retry_at. idempotency_in_progress: retain the exact Idempotency-Key and request body, then retry shortly. webhook_delivery_in_progress: retry the URL or signing-secret configuration change after the destination's active request completes. request_accounting_unavailable: accounting capacity is unavailable before the handler executes; retry after Retry-After / retry_at. sandbox_api_key: the credential is a sandbox key (oxi_sk_test_) from POST /api/v1/agents/register, which only the sandbox server accepts -- call the sandbox base URL with it, or get a live key or OAuth access token; do not retry it here. api_key_in_query: the key was sent as a ?token= query parameter, which no route reads because URLs land in logs and history; the key itself was not checked -- resend it as Authorization: Bearer. subscription_inactive: the key is valid but the account's Pro subscription has lapsed (402 subscription_required); permanent until a person reactivates at https://0xinsider.com/billing, which the message names -- stop retrying on a schedule and surface the link. The key owner is emailed once per lapse. monthly_quota_exceeded: the account has used the requests its plan includes for the UTC calendar month (429 rate_limited); retry_at and Retry-After name the first of next month, the only retry that can succeed, and the message names https://0xinsider.com/developers, where pay as you go for requests over the quota is turned on. The X-Monthly-Quota-Limit, X-Monthly-Quota-Remaining and X-Monthly-Quota-Reset headers on every authenticated response say how close the account is. invalid_query, invalid_path, invalid_body (400 bad_request, #16146): a query parameter, a path segment or the JSON body did not parse or does not fit the route's schema, so no handler ran; param names the field when the parser named one (a query key, a path segment, a JSON path such as traders[0], or body); fix the request, never retry it as sent. unsupported_media_type (415 bad_request, param content-type): send the body with Content-Type: application/json. payload_too_large (413 bad_request, param body): the body is over 1048576 bytes. method_not_allowed (405 bad_request): the path is a route but not with this method; the Allow header names the methods it serves. ip_rate_limited (429 rate_limited, #16380): the per-address budget every caller behind one IP shares, counted before authentication, is spent; not the key's own window, and the RateLimit-* headers describe that bucket. ip_throttled (429 rate_limited): the address is in a cooldown after sustained over-limit traffic; Retry-After is minutes to days, and a request before it does not shorten the cooldown. cold_read_limited (429 rate_limited, #28752): the account has used its share of cold reads on GET /api/v1/market/{condition_id}/holders, requests for a market whose roster is not cached, each of which walks Polymarket (6 a minute, in bursts of up to 400); not the per-minute window, so a cached market still answers, and the next cold read is admitted at Retry-After. max_plan_required (403 forbidden, #23085): the key is valid and its account is on Pro, but the route is part of Max (GET /api/v1/edge-hunter); every Pro route keeps working with the same key, so do not disable it. The message names https://0xinsider.com/pricing, where the account upgrades; a retry never clears it. endpoint_retired (410): this legacy sports endpoint no longer serves data. Migrate to GET /api/v1/edge-hunter; retrying the retired path cannot succeed. */
95
- reason?: "cursor_expired" | "unknown_endpoint" | "pick_not_released" | "trader_not_tracked" | "market_out_of_scope" | "read_model_warming" | "database_unavailable" | "request_accounting_unavailable" | "idempotency_in_progress" | "webhook_delivery_in_progress" | "webhook_secret_rotation_not_prepared" | "webhook_secret_rotation_overlap_active" | "sandbox_api_key" | "api_key_in_query" | "subscription_inactive" | "monthly_quota_exceeded" | "invalid_query" | "unknown_query_parameter" | "invalid_path" | "invalid_body" | "unsupported_media_type" | "payload_too_large" | "method_not_allowed" | "ip_rate_limited" | "ip_throttled" | "cold_read_limited" | "export_expired" | "freshness_ceiling_unsatisfied" | "max_plan_required" | "endpoint_retired";
113
+ /** Where a person clears this 402 (#32592): pays the overdue invoice (reason payment_overdue) or reactivates the plan (reason subscription_inactive). Present on every 402 subscription_required, omitted on every other error. The message names the same URL; link this field instead of parsing the message. */
114
+ billing_url?: string;
115
+ /** ADDITIVE (#7209). The specific, actionable cause behind `code`, when there is one more specific than the code itself. `code` keeps its published values, so existing clients are unaffected; new clients branch on `reason`. Omitted when the code already says everything we know. pick_not_released: no Pick of the Day is published for the current product day; schedule one request against retry_at instead of polling. unknown_endpoint: the PATH is not a route on this API -- read GET /api/v1, do not retry. trader_not_tracked: the wallet is real and the URL is right, but the trader is not on the warm sync tier (the wallets we sync most often) -- stop asking for this wallet. market_out_of_scope (404 not_found, #27303): the market or event is real and the URL is right, but 0xinsider serves sports and esports markets only -- stop asking for this market; neither a retry nor another URL for it can succeed. cursor_expired: pagination went stale mid-walk -- re-request the first page and continue. read_model_warming: the requested endpoint cannot serve its read model yet; exact causes are endpoint-specific and can include a cold or contended refresh or a dependency that prevented refresh. database_unavailable: the API's database or its connection pool is temporarily unreachable (a connection-class failure, not a query fault); code stays rate_limit_unavailable, nothing is rate-limited, retry after Retry-After / retry_at. idempotency_in_progress: retain the exact Idempotency-Key and request body, then retry shortly. webhook_delivery_in_progress: retry the URL or signing-secret configuration change after the destination's active request completes. request_accounting_unavailable: accounting capacity is unavailable before the handler executes; retry after Retry-After / retry_at. sandbox_api_key: the credential is a sandbox key (oxi_sk_test_) from POST /api/v1/agents/register, which only the sandbox server accepts -- call the sandbox base URL with it, or get a live key or OAuth access token; do not retry it here. api_key_in_query: the key was sent as a ?token= query parameter, which no route reads because URLs land in logs and history; the key itself was not checked -- resend it as Authorization: Bearer. subscription_inactive: the key is valid but the account's Pro subscription has lapsed (402 subscription_required); permanent until a person reactivates at https://0xinsider.com/billing, which the message names -- stop retrying on a schedule and surface the link. The key owner is emailed once per lapse. payment_overdue (402 subscription_required, #32592): the key is valid and the account still has its plan, but its latest subscription invoice is unpaid (Stripe past_due or unpaid), so paid access is paused; permanent until a person pays at billing_url (https://0xinsider.com/billing) -- stop retrying on a schedule and surface the link. The key works again the moment the invoice is paid. GET /api/v1/me and GET /api/v1/usage keep answering. monthly_quota_exceeded: the account has used the requests its plan includes for the UTC calendar month (429 rate_limited); retry_at and Retry-After name the first of next month, the only retry that can succeed, and the message names https://0xinsider.com/developers, where pay as you go for requests over the quota is turned on. The X-Monthly-Quota-Limit, X-Monthly-Quota-Remaining and X-Monthly-Quota-Reset headers on every authenticated response say how close the account is. invalid_query, invalid_path, invalid_body (400 bad_request, #16146): a query parameter, a path segment or the JSON body did not parse or does not fit the route's schema, so no handler ran; param names the field when the parser named one (a query key, a path segment, a JSON path such as traders[0], or body); fix the request, never retry it as sent. unsupported_media_type (415 bad_request, param content-type): send the body with Content-Type: application/json. payload_too_large (413 bad_request, param body): the body is over 1048576 bytes. method_not_allowed (405 bad_request): the path is a route but not with this method; the Allow header names the methods it serves. ip_rate_limited (429 rate_limited, #16380): the per-address budget every caller behind one IP shares, counted before authentication, is spent; not the key's own window, and the RateLimit-* headers describe that bucket. ip_throttled (429 rate_limited): the address is in a cooldown after sustained over-limit traffic; Retry-After is minutes to days, and a request before it does not shorten the cooldown. cold_read_limited (429 rate_limited, #28752): the account has used its share of cold reads on GET /api/v1/market/{condition_id}/holders, requests for a market whose roster is not cached, each of which walks Polymarket (6 a minute, in bursts of up to 400); not the per-minute window, so a cached market still answers, and the next cold read is admitted at Retry-After. max_plan_required (403 forbidden, #23085): the key is valid and its account is on Pro, but the route is part of Max (GET /api/v1/edge-hunter); every Pro route keeps working with the same key, so do not disable it. The message names https://0xinsider.com/pricing, where the account upgrades; a retry never clears it. endpoint_retired (410): this legacy sports endpoint no longer serves data. Migrate to GET /api/v1/edge-hunter; retrying the retired path cannot succeed. */
116
+ reason?: "cursor_expired" | "unknown_endpoint" | "pick_not_released" | "trader_not_tracked" | "market_out_of_scope" | "read_model_warming" | "database_unavailable" | "request_accounting_unavailable" | "idempotency_in_progress" | "webhook_delivery_in_progress" | "webhook_secret_rotation_not_prepared" | "webhook_secret_rotation_overlap_active" | "sandbox_api_key" | "api_key_in_query" | "subscription_inactive" | "payment_overdue" | "monthly_quota_exceeded" | "invalid_query" | "unknown_query_parameter" | "invalid_path" | "invalid_body" | "unsupported_media_type" | "payload_too_large" | "method_not_allowed" | "ip_rate_limited" | "ip_throttled" | "cold_read_limited" | "export_expired" | "freshness_ceiling_unsatisfied" | "max_plan_required" | "endpoint_retired";
96
117
  };
97
118
  export type BatchGameHoldersItem = {
98
119
  /** Zero-based request index. Duplicate inputs keep separate result rows. */
@@ -473,10 +494,10 @@ export type CreateWebhookVerificationAttemptRequest = {
473
494
  /** The original one-time token returned by webhook creation or a URL change. Sent only in the request body and signed receiver challenge. */
474
495
  verification_token: string;
475
496
  };
476
- /** Compact data age and coverage for a response body, always present on the operations that publish it. Read status and as_of to decide whether to use the body at all, and field_groups to see which part is weak. Everything here comes from stored observation clocks, so a cached body reports the same ages a freshly computed one does: meta.cached and meta.cache_age_s stay the only transport-time facts and neither makes this block newer. The per-field audit object is still available through expand=trust; this is the default summary of the same question. */
497
+ /** Compact data age and coverage for a response body, always present on the operations that publish it. Read status and as_of to decide whether to use the body at all, and field_groups to see which part is weak. Everything here comes from stored observation clocks, and each clocked group is judged against its writer's budget when the body is served, so a cached body reports the same ages and verdicts a freshly computed one does: meta.cached and meta.cache_age_s stay the only transport-time facts and neither makes this block newer. The per-field audit object is still available through expand=trust; this is the default summary of the same question. */
477
498
  export type DataQuality = {
478
- /** fresh when every group is fresh, unavailable when every group is unavailable, and partial in every other case. */
479
- status: "fresh" | "partial" | "unknown" | "untracked" | "unavailable";
499
+ /** fresh when every group is fresh, unavailable when every group is unavailable, stale when every group is fresh or stale and at least one is stale, and partial in every other case. */
500
+ status: "fresh" | "stale" | "partial" | "unknown" | "untracked" | "unavailable";
480
501
  /** The oldest as_of among the groups that carry one: the age of the weakest clock this body rests on. Omitted when no group carries a clock. */
481
502
  as_of?: string;
482
503
  /** One entry per field group. Entries may be added in later releases, so match on group rather than on position or length. */
@@ -488,12 +509,14 @@ export type DataQualityGroup = {
488
509
  group: string;
489
510
  /** The table and column that write this group, named so the verdict can be audited (for example trader_rankings.computed_at). */
490
511
  owner: string;
491
- /** fresh: served, and as_of carries this group's real observation or computation clock. partial: some of the group's fields are served and some are missing. unknown: served, and this read has no clock for it, so no age may be inferred. untracked: 0xinsider does not track this group for this subject, by design. unavailable: the group could not be served. New values may be added; treat one you do not recognize as unknown. fresh means the group is tracked and clocked, not that it is inside any particular tolerance: compare as_of against your own. */
492
- status: "fresh" | "partial" | "unknown" | "untracked" | "unavailable";
512
+ /** fresh: served, as_of carries this group's real observation or computation clock, and that clock is no older than max_age_s when the group carries one. stale: served and clocked, but as_of is older than max_age_s, so the writer has missed a refresh its schedule promises. partial: some of the group's fields are served and some are missing. unknown: served, and this read has no clock for it, so no age may be inferred. untracked: 0xinsider does not track this group for this subject, by design. unavailable: the group could not be served. New values may be added; treat one you do not recognize as unknown. Neither fresh nor stale is a tolerance for your use: compare as_of against your own. */
513
+ status: "fresh" | "stale" | "partial" | "unknown" | "untracked" | "unavailable";
493
514
  /** When this group's values were observed or computed. Omitted whenever the read cannot measure it, and never filled with the serialization time, the cache time, or another group's clock. */
494
515
  as_of?: string;
495
516
  /** Why the status is not fresh. Omitted when it is. */
496
517
  reason?: string;
518
+ /** The longest gap in seconds this group's writer leaves between refreshes on its schedule: the age past which the group reads stale instead of fresh. Omitted when the writer keeps no schedule its clock could fall behind (a clock stamped once, only on a change, or when a reader asks), and such a group is never stale. It is the writer's promise, not a tolerance for your use. */
519
+ max_age_s?: number;
497
520
  };
498
521
  /** The full Max-only Edge Hunter board. Defaults cover all sports and esports with B grade floor, 48-hour horizon and at most 30 rows; common selections, legacy category, min_grade and limit narrow that projection. No pagination or ETag. Rows add frozen market_context discovery metadata. */
499
522
  export type EdgeHunterBoard = {
@@ -589,7 +612,7 @@ export type EdgeHunterObservation = {
589
612
  /** Source cohort or additive projection view. emerging_pile is projected from wider_holder after source computation, overlaps its funnel denominator, and uses the source row's directional evidence. */
590
613
  cohort: "wider_holder" | "in_play" | "emerging_pile";
591
614
  /** Which evaluation place in its sport the row took this refresh. most_held: a wider_holder place for up to 8 markets per sport kicking off within 24 hours, most graded holders first, kept until kickoff while the holder page still shows a card or could not be read; also every in_play row, whose places go most graded holders first. soonest_kickoff: a wider_holder place among the sport's 8 soonest kickoffs, which is not kept. emerging_pile rows carry their source wider_holder row's value. Filter on most_held to keep the most-held selection. most_held_48h: every row of an admission=most_held_48h response, which is that opt-in selection (up to 8 markets per sport within 48 hours, most graded holders first, recomputed every refresh), including a market a default place also evaluates; it never appears on a default response. */
592
- admitted_by: "most_held" | "soonest_kickoff" | "most_held_48h";
615
+ admitted_by: "most_held" | "soonest_kickoff" | "most_held_48h" | "most_held_48h_by_type";
593
616
  /** Always true. This row must not be routed to an order executor. */
594
617
  observation_only: true;
595
618
  /**
@@ -672,7 +695,7 @@ export type EdgeHunterObservationDeparture = {
672
695
  /** Canonical sport bucket the departed card carried. */
673
696
  category: "Basketball" | "Football" | "Baseball" | "Hockey" | "MMA" | "Boxing" | "Soccer" | "Cricket" | "Golf" | "Tennis" | "Esports" | "Racing" | "Table Tennis" | "Pickleball";
674
697
  /** The departed card's admitted_by: most_held_48h on an admission=most_held_48h response. */
675
- admitted_by: "most_held" | "soonest_kickoff" | "most_held_48h";
698
+ admitted_by: "most_held" | "soonest_kickoff" | "most_held_48h" | "most_held_48h_by_type";
676
699
  /** Kickoff the departed card carried, in UTC. The entry leaves the list once it passes. */
677
700
  game_start_time: string;
678
701
  /** The departed card's observed_at: when its last evaluation finished. */
@@ -702,7 +725,7 @@ export type EdgeHunterObservationsResponse = {
702
725
  /** Normalized axes requested with a selector or category; omitted on an unselected default read. */
703
726
  selection?: EdgeHunterSelection;
704
727
  /** Echoes the opt-in admission rule a request named; omitted on a default read. When present, data, funnel, coverage, degraded and degradation_reasons describe that selection, not the default one. */
705
- admission?: "most_held_48h";
728
+ admission?: "most_held_48h" | "most_held_48h_by_type";
706
729
  /** cohort=wider_holder only, for the default selection and for admission=most_held_48h: markets that had a wider_holder card in an earlier snapshot of the same selection, have none in this one, and have not kicked off. Newest departed_at first. An entry stays until the game_start_time it carries passes, and leaves as soon as the market has a card again; while it stays, its reason changes only while it reports an unfinished read or when view=signals starts or stops listing the market (see reason), and a later departure of the same market is a new entry. Filtered by the same category and selectors as data, and identical on every page of one snapshot. At most 336 entries; when more are open, the oldest departures leave first. An empty array means no card of this selection is departed. Omitted on in_play and emerging_pile, and on a snapshot that could not establish its departures, never reported as an empty array. Tracking that restarts after its record is lost starts empty. */
707
730
  departures?: EdgeHunterObservationDeparture[];
708
731
  /** The ranked_at of the view=signals snapshot whose listed markets this snapshot excluded from wider_holder as primary_slate_candidate. The refresh that computed this snapshot read it as it started, so it can be one refresh older than the view=signals snapshot a reader sees at the same moment. null when no view=signals snapshot could be read: that refresh excluded no market, and the funnel counts no primary_slate_candidate. Omitted on an unmappable legacy category, which reads no generation. */
@@ -1233,8 +1256,8 @@ export type FreshnessFailure = {
1233
1256
  actual_age_s?: number;
1234
1257
  /** The oldest stored data-quality clock used to calculate actual_age_s, when one is available. */
1235
1258
  as_of?: string;
1236
- /** The trader body's whole-response data-quality status. Only fresh can satisfy max_age_s. */
1237
- data_quality_status: "fresh" | "partial" | "unknown" | "untracked" | "unavailable";
1259
+ /** The trader body's whole-response data-quality status. Only fresh or stale can satisfy max_age_s: the ceiling replaces each writer's own budget, so a stale body inside it is answered. */
1260
+ data_quality_status: "fresh" | "stale" | "partial" | "unknown" | "untracked" | "unavailable";
1238
1261
  };
1239
1262
  /** One sports or esports game: both sides, its schedule, provider status, its winner markets (the moneyline, or the soccer 1X2 legs) with their available provider moneyline price states and liquidity, and the game's traded volume. The event's other markets are on GET /api/v1/games/{event_slug}/markets. Assembled from the same provider-first live and upcoming projections the site's boards use, with no request-time provider fan-out. */
1240
1263
  export type Game = {
@@ -1267,6 +1290,8 @@ export type Game = {
1267
1290
  sharp_money?: GameSharpMoney;
1268
1291
  /** The game's traded volume, the figure the sports board carries for it, with its source, basis, unit and observation clock. A client can rank or filter games by volume.value. Omitted when the board carries no volume for the game, and on a finished game read from its stored record (freshness.source stored); never 0 in place of a missing figure. */
1269
1292
  volume?: GameVolume;
1293
+ /** A tennis game's tournament edition: its tier, city and country, venue time zone and official dates, from 0xinsider's reviewed registry, the same facts the game's page states. Omitted outside tennis, on a finished game read from its stored record (freshness.source stored), and when the registry holds no single edition for the game's competition label, players' tour and kickoff; never a guessed value. */
1294
+ tournament?: GameTournament;
1270
1295
  };
1271
1296
  /** One side of the game. */
1272
1297
  export type GameCompetitor = {
@@ -1490,6 +1515,38 @@ export type GameStatus = {
1490
1515
  /** Whether the game is over. Always present. */
1491
1516
  ended: boolean;
1492
1517
  };
1518
+ /** The tennis tournament edition a game belongs to, curated from official ATP, WTA and ITF pages (source_urls), never from Polymarket, which supplies the competition label and tour only. Selected by the game's provider competition label, the one tour both players carry, and the kickoff's day at the venue. A fact no official page states is omitted. */
1519
+ export type GameTournament = {
1520
+ /** The provider competition label that selected this edition, verbatim, for example "Erste Bank Open, Qualification". */
1521
+ label: string;
1522
+ /** The edition's tour. */
1523
+ tour: "atp" | "wta" | "itf";
1524
+ /** The match's draw. qualification when the provider competition label carries Polymarket's qualifying mark (", Qualification", which 0xinsider also applies when Polymarket flags an ATP Challenger qualifying fixture); main for an unmarked ATP or WTA label. Omitted for an unmarked ITF label: Polymarket gives an ITF qualifying match the main draw's label. */
1525
+ draw?: "main" | "qualification";
1526
+ /** The edition's verified category, for example "ATP Masters 1000", "ATP Challenger 50", "WTA 1000" or "ITF M25". Omitted when no official page states it. */
1527
+ tier?: string;
1528
+ /** The verified venue city and country. Omitted when no official page states it. */
1529
+ location?: GameTournamentLocation;
1530
+ /** The venue's IANA time zone, for example "Europe/Rome". Format the venue's local time with it; it carries the daylight-saving rules. Omitted when the venue is unverified. */
1531
+ timezone?: string;
1532
+ /** The year of the edition. */
1533
+ edition_year: number;
1534
+ /** The edition's first official day, inclusive (YYYY-MM-DD). A qualifying match can be played up to two days before it. */
1535
+ starts_on: string;
1536
+ /** The edition's last official day, inclusive (YYYY-MM-DD). */
1537
+ ends_on: string;
1538
+ /** The official tournament, tour, federation or time-zone pages the facts were taken from. */
1539
+ source_urls: string[];
1540
+ /** When those pages were checked: a catalogue verification date, not a score or price clock. */
1541
+ checked_on: string;
1542
+ };
1543
+ /** A verified tournament venue. */
1544
+ export type GameTournamentLocation = {
1545
+ city: string;
1546
+ country: string;
1547
+ /** ISO 3166-1 alpha-2, for example "IT". */
1548
+ country_code: string;
1549
+ };
1493
1550
  /** One fill in a game, as the game page's activity list shows it. */
1494
1551
  export type GameTrade = {
1495
1552
  /** Stable fill identity: {transaction_hash}:{token_id}:{wallet}:{shares}. Deduplicate on it across polls. */
@@ -2013,8 +2070,8 @@ export type MarketHolder = {
2013
2070
  category_avg_price?: number;
2014
2071
  /** Where `category_win_rate` and `category_win_record` were measured: for an esports market the game (`LoL`, `CS2`, `Dota 2`, `Valorant`, `Call of Duty`, `Honor of Kings`, `Mobile Legends: Bang Bang`, `Overwatch`, `Rainbow Six Siege`, `Rocket League` or `StarCraft II`), and for a market in a registered league that league, by the name Pick of the Day uses for it (`NBA`, `NFL`, `NHL`, `MLB`, `UFC`, `ATP`, `WTA`, `LaLiga`, `Premier League`, `Serie A`, `UEFA Champions League`, `MLS` and the other registered leagues). Present only when the wallet's record there clears the 5-settled-market floor, in which case the rate and record are the game's or league's rather than the whole `category` bucket's. Absent when the rate is the bucket's (the wallet's history there is under the floor, or the competition is not a registered league), on every other market, and beside every non-measured status. Label the rate with this when present and with the market's `category` otherwise. */
2015
2072
  category_win_rate_game?: string;
2016
- /** Why `category_win_rate` is present or absent: `measured` (rate present), `not_enough_data` (under the floor of 5 decided markets in the category), or `unavailable` (the market has no canonical category, or the read failed; retry later). Always present on this route. */
2017
- category_win_rate_status: "measured" | "not_enough_data" | "unavailable";
2073
+ /** Why `category_win_rate` is present or absent: `measured` (rate present), `not_enough_data` (under the floor of 5 decided markets in the category), or `unavailable` (the market has no canonical category, or the read failed; retry later). Never `untracked`: this route lists S, A and B wallets only, all on file. Always present on this route. */
2074
+ category_win_rate_status: "measured" | "not_enough_data" | "unavailable" | "untracked";
2018
2075
  category_evidence?: HolderCategoryEvidence;
2019
2076
  /** Whole days since this wallet's first trade, rounded down (3.6 days reads 3), so it changes once a day, not on every request. The five badge fields are present together, only for a wallet that draws at least one badge. */
2020
2077
  wallet_age_days?: number | null;
@@ -2238,8 +2295,8 @@ export type MarketTopHolder = {
2238
2295
  category_avg_price?: number;
2239
2296
  /** Where `category_win_rate` and `category_win_record` were measured: for an esports market the game (`LoL`, `CS2`, `Dota 2`, `Valorant`, `Call of Duty`, `Honor of Kings`, `Mobile Legends: Bang Bang`, `Overwatch`, `Rainbow Six Siege`, `Rocket League` or `StarCraft II`), and for a market in a registered league that league, by the name Pick of the Day uses for it (`NBA`, `NFL`, `NHL`, `MLB`, `UFC`, `ATP`, `WTA`, `LaLiga`, `Premier League`, `Serie A`, `UEFA Champions League`, `MLS` and the other registered leagues). Present only when the wallet's record there clears the 5-settled-market floor, in which case the rate and record are the game's or league's rather than the whole `category` bucket's. Absent when the rate is the bucket's (the wallet's history there is under the floor, or the competition is not a registered league), on every other market, and beside every non-measured status. Label the rate with this when present and with the market's `category` otherwise. */
2240
2297
  category_win_rate_game?: string;
2241
- /** Why `category_win_rate` is present or absent: `measured` (rate present), `not_enough_data` (under the floor of 5 decided markets in the category), or `unavailable` (the read failed; retry later). Absent when the market has no canonical category. */
2242
- category_win_rate_status?: "measured" | "not_enough_data" | "unavailable";
2298
+ /** Why `category_win_rate` is present or absent: `measured` (rate present), `not_enough_data` (the wallet is on file and under the floor of 5 decided markets in the category), `untracked` (0xinsider has no record of the wallet, so nothing was measured; `grade`, `in_scope_realized_pnl` and `in_scope_volume` are null too), or `unavailable` (a read failed; retry later). Absent when the market has no canonical category. */
2299
+ category_win_rate_status?: "measured" | "not_enough_data" | "unavailable" | "untracked";
2243
2300
  /** Whole days since this wallet's first trade, rounded down (3.6 days reads 3), so it changes once a day, not on every request. The five badge fields are present together, only for a wallet that draws at least one badge. */
2244
2301
  wallet_age_days?: number | null;
2245
2302
  /** True when the wallet's first trade was under 30 days ago. */
@@ -2266,6 +2323,8 @@ export type MarketTopHolders = {
2266
2323
  computed_at?: string;
2267
2324
  /** When Polymarket's holder board behind this sample was fetched: up to 120 s before `computed_at` on an open market. Absent on a resolved market, whose sample reads no live board, and when the fetch stamp could not be read. */
2268
2325
  positions_as_of?: string;
2326
+ /** `true` when this is the market's last sample, served after its 30 s window while a refresh runs; `positions_as_of` is then at most 300 s old. Read again in a few seconds for the refreshed sample. Always `false` on a resolved market. */
2327
+ source_stale: boolean;
2269
2328
  };
2270
2329
  export type MarketTopHoldersTotals = {
2271
2330
  /** Holders in the YES sample, before `limit`. Equal to `sample_cap` means that many or more. */
@@ -2374,8 +2433,8 @@ export type PickHolder = {
2374
2433
  category_avg_price?: number;
2375
2434
  /** Where `category_win_rate` and `category_win_record` were measured, by the same name the pick's `display_category` uses for it: for an esports pick the game (`LoL`, `CS2`, `Dota 2`, `Valorant`, `Call of Duty`, `Honor of Kings`, `Mobile Legends: Bang Bang`, `Overwatch`, `Rainbow Six Siege`, `Rocket League` or `StarCraft II`), and for a pick in a registered league that league (`NBA`, `NFL`, `NHL`, `MLB`, `UFC`, `ATP`, `WTA`, `LaLiga`, `Premier League`, `Serie A`, `UEFA Champions League`, `MLS` and the other registered leagues). Present only when the wallet's record there clears the 5-settled-market floor, in which case the rate and record are the game's or league's rather than the whole `category` bucket's. Absent when the rate is the bucket's (the wallet's history there is under the floor, or the competition is not a registered league), on every other pick, beside every non-measured status, and on payloads predating the field. Label the rate with this when present and with `category` otherwise. */
2376
2435
  category_win_rate_game?: string;
2377
- /** Why `category_win_rate` is present or absent on a `display_holders` entry: `measured` (rate present), `not_enough_data` (the wallet is below the settled-market floor of 5 in the category), or `unavailable` (the annotation read failed; retry later). Absent entirely on `holders` entries, legacy rows, and payloads predating the field -- absence means the roster was never annotated, not a small sample. */
2378
- category_win_rate_status?: "measured" | "not_enough_data" | "unavailable";
2436
+ /** Why `category_win_rate` is present or absent on a `display_holders` entry: `measured` (rate present), `not_enough_data` (the wallet is below the settled-market floor of 5 in the category), or `unavailable` (the annotation read failed; retry later). Never `untracked`: every recorded holder is a graded wallet 0xinsider has on file. Absent entirely on `holders` entries, legacy rows, and payloads predating the field -- absence means the roster was never annotated, not a small sample. */
2437
+ category_win_rate_status?: "measured" | "not_enough_data" | "unavailable" | "untracked";
2379
2438
  /** Whole days since this wallet's first trade, rounded down (3.6 days reads 3), so it changes once a day, not on every request, and the pick's ETag holds between requests. Stamped at serve time from the wallet's current trader record, never frozen with the pick. The five badge fields are present together, and only for a wallet that carries at least one badge; all absent means no badge, or a body cached before the fields shipped. */
2380
2439
  wallet_age_days?: number | null;
2381
2440
  /** True when the wallet's first trade was under 30 days ago. Stamped at serve time from the wallet's current trader record, never frozen with the pick. The five badge fields are present together, and only for a wallet that carries at least one badge; all absent means no badge, or a body cached before the fields shipped. */
@@ -4113,7 +4172,7 @@ export type Trader = {
4113
4172
  * @deprecated
4114
4173
  */
4115
4174
  realized?: number;
4116
- /** Unrealized P&L in USD of the wallet's open positions on its synced sports and esports markets, from the open-position aggregate (dated by the positions data_quality group). Each open position's market is judged when the aggregate is read (#32119), so a stored position on a market of another category (one that moved out of sports and esports, or one stored before its category was known) is left out. Before 2026-10-27 (#29366) it covered every Polymarket market. */
4175
+ /** Unrealized (mark-to-market) P&L in USD of the wallet's open positions on its synced sports and esports markets: Polymarket's cashPnl, current value minus cost, summed over the positions still held, from the open-position aggregate (dated by the positions data_quality group). It is the same figure as position_summary.unrealized_mtm on GET /api/v1/trader/{address}/context. It leaves out the realized P&L of partial sells on those markets, which pnl.in_scope.realized_pnl already counts; until 2026-11-02 (#32459) it added it, so pnl.in_scope.realized_pnl plus pnl.unrealized counted those sells twice. Each open position's market is judged when the aggregate is read (#32119), so a stored position on a market of another category (one that moved out of sports and esports, or one stored before its category was known) is left out. Before 2026-10-27 (#29366) it covered every Polymarket market. */
4117
4176
  unrealized?: number;
4118
4177
  /**
4119
4178
  * Deprecated since 2026-10-27 and never sent: its one member was the lossless counterpart of the retired pnl.realized (#29830).
@@ -4184,6 +4243,18 @@ export type Trader = {
4184
4243
  /** Current evidence for observed and known categories (expand=categories or expand[]=categories). Omitted unless expanded. Includes insufficient, stale, unknown and degraded rows; absence of a category is not proof of skill. The global grade is unchanged. Sports and esports only (#28827): a category outside them is omitted, as on GET /api/v1/trader/{address}/categories. */
4185
4244
  category_records?: CategorySkillV2[];
4186
4245
  category_skill_model?: CategorySkillModelReadiness;
4246
+ /** Edge over the price paid: the profile's Edge badge and the leaderboard's Edge measure (#32622). Present on every read of a wallet with at least one settled sports or esports market of $20 or more at a known entry price; omitted for a wallet with none, where there is nothing to measure. */
4247
+ price_edge?: TraderPriceEdge;
4248
+ /** The profile's strategy measures over the wallet's sports and esports markets: P&L and win rate by UTC hour, hold-time and first-trade-hour bands, position sizes with category concentration, and entry-price buckets (#32622). Present only when expand=breakdowns or expand[]=breakdowns is requested, on GET /api/v1/trader/{address} only: POST /api/v1/traders/batch refuses the expand. Computed on the read from the wallet's whole sports and esports history, so a large wallet can take seconds; a read that cannot finish answers 503 with Retry-After. */
4249
+ breakdowns?: TraderBreakdowns;
4250
+ };
4251
+ /** The profile's strategy measures (expand=breakdowns, #32622), the same figures as the wallet profile's Insights and Performance breakdown cards. Every figure counts the wallet's sports and esports markets only, from the same read the profile makes. */
4252
+ export type TraderBreakdowns = {
4253
+ hourly_performance: TraderHourlyPerformance;
4254
+ time_breakdowns: TraderTimeBreakdowns;
4255
+ /** Position sizes and category concentration. null when the wallet has no sports or esports position with trades, or its stored markets carry no freshness clock yet. */
4256
+ risk_metrics: TraderRiskMetrics | null;
4257
+ entry_price_analysis: TraderEntryPriceAnalysis;
4187
4258
  };
4188
4259
  export type TraderCategoryRecord = {
4189
4260
  /** Canonical category bucket, for example Soccer, Esports, Tennis. Esports is one bucket, and the Esports record carries its per-game records in games. */
@@ -4237,7 +4308,7 @@ export type TraderContext = {
4237
4308
  synced_realized_pnl: number;
4238
4309
  /** Native net realized P&L (USD) over every Polymarket market of the wallet, including credited rebates, read directly from the accounting snapshot. For the sports and esports figure read trader.pnl.in_scope.realized_pnl. Position sync_coverage is separate from this accounting basis. */
4239
4310
  total_realized_pnl: number;
4240
- /** Mark-to-market unrealized P&L (USD) on open positions in synced sports and esports markets (#32119). */
4311
+ /** Mark-to-market unrealized P&L (USD) on open positions in synced sports and esports markets (#32119): Polymarket's cashPnl summed over the positions still held, without the realized P&L of partial sells. The same figure as trader.pnl.unrealized, in this body and on GET /api/v1/trader/{address}, since 2026-11-02 (#32459). */
4241
4312
  unrealized_mtm: number;
4242
4313
  /** Cost basis (USD) currently tied up in open positions in synced sports and esports markets (#32119). */
4243
4314
  cost_basis_locked: number;
@@ -4255,6 +4326,32 @@ export type TraderContext = {
4255
4326
  /** Human-readable statement of the point-in-time snapshot semantics (data_as_of is the open-position clock -- the latest /positions snapshot, else the last completed sync; the snapshot advances only open positions, so resolved counts and win rate can include settled markets recorded after the last full sync; native realized P&L follows its accounting snapshot -- not a live feed; re-fetch for fresher data). */
4256
4327
  freshness_note: string;
4257
4328
  };
4329
+ /** Results by the entry price the wallet paid for the side it held, over its sports and esports markets with a known held entry price. */
4330
+ export type TraderEntryPriceAnalysis = {
4331
+ /** One bucket per price band that has a market, in bucket_order. */
4332
+ buckets: TraderEntryPriceBucket[];
4333
+ /** The bucket with the highest win rate among buckets of at least 3 markets. null when no bucket has 3. */
4334
+ best_bucket: string | null;
4335
+ /** The bucket with the lowest win rate among buckets of at least 3 markets. null when no bucket has 3. */
4336
+ worst_bucket: string | null;
4337
+ };
4338
+ /** One entry-price band. Each measure is an analytic value: read state before value. */
4339
+ export type TraderEntryPriceBucket = {
4340
+ /** The band: $0.01-$0.19, $0.20-$0.39, $0.40-$0.59, $0.60-$0.79 or $0.80-$0.99. The last also holds any price below $0.01. */
4341
+ bucket: string;
4342
+ /** Display position, 0 first, from the lowest price up. */
4343
+ bucket_order: number;
4344
+ /** Markets in the band. */
4345
+ markets: AnalyticInteger;
4346
+ /** Of those, the markets that made money. */
4347
+ wins: AnalyticInteger;
4348
+ /** wins over decided markets (a nonzero P&L), as a percentage 0-100. */
4349
+ win_rate: AnalyticNumber;
4350
+ /** Realized P&L of the band's markets, USD. */
4351
+ total_pnl: AnalyticNumber;
4352
+ /** Mean realized P&L of a market in the band, USD. */
4353
+ avg_pnl: AnalyticNumber;
4354
+ };
4258
4355
  export type TraderEsportsGameRecord = {
4259
4356
  /** The game, by the same name every holder chip carries in category_win_rate_game: LoL, CS2, Dota 2, Valorant, Call of Duty, Honor of Kings, Mobile Legends: Bang Bang, Overwatch, Rainbow Six Siege, Rocket League or StarCraft II. Match a chip to this row by string equality. */
4260
4357
  game: string;
@@ -4428,6 +4525,36 @@ export type TraderGradeAt = {
4428
4525
  source_observed_by: string | null;
4429
4526
  } | null;
4430
4527
  };
4528
+ /** P&L and win rate by the UTC hour of the wallet's first trade in each settled sports or esports market. */
4529
+ export type TraderHourlyPerformance = {
4530
+ /** One entry per UTC hour that has a settled market; an hour with none is absent. */
4531
+ entries: ({
4532
+ /** UTC hour of the first trade, 0-23. */
4533
+ hour: number;
4534
+ /** Settled markets first traded in this hour. */
4535
+ markets: number;
4536
+ /** Of those, the markets that made money at settlement. */
4537
+ wins: number;
4538
+ /** wins over the decided markets (settled with a nonzero P&L), as a percentage 0-100. 0 when no market of the hour is decided: read it with wins and markets. */
4539
+ win_rate: number;
4540
+ /** Realized P&L of those markets, USD. */
4541
+ total_pnl: number;
4542
+ /** total_pnl over markets, USD. */
4543
+ avg_pnl: number;
4544
+ })[];
4545
+ /** The hour with the highest win_rate. null when no entry exists. */
4546
+ best_hour: number | null;
4547
+ /** best_hour's win_rate, percentage 0-100. null exactly when best_hour is. */
4548
+ best_win_rate: number | null;
4549
+ /** The hour with the lowest win_rate. null when no entry exists. */
4550
+ worst_hour: number | null;
4551
+ /** worst_hour's win_rate, percentage 0-100. null exactly when worst_hour is. */
4552
+ worst_win_rate: number | null;
4553
+ /** best_win_rate minus worst_win_rate, percentage points. null when either is null. */
4554
+ spread_pp: number | null;
4555
+ /** true when at least 12 hours have markets and every one of them has the same P&L sign, so the hour-to-hour spread says little about timing. */
4556
+ is_approximate: boolean;
4557
+ };
4431
4558
  /** A trader's sports and esports daily P&L object. `id`, `freshness_at`, `series_status` and `realized` are always present. The five sections -- `entries`, `stats`, `monthly`, `year_totals`, `drawdown` -- are present unless the request's `sections` parameter excluded them, so a request that sends no `sections` always carries all five. An excluded section is absent from the object, never null and never an empty array. While `series_status` is `pending` the sections are empty and never mean zero. */
4432
4559
  export type TraderPnl = {
4433
4560
  /** Prefixed trader ID (`trd_...`). */
@@ -4581,6 +4708,40 @@ export type TraderPnlInScope = {
4581
4708
  /** When the record behind these figures was computed. */
4582
4709
  as_of: string;
4583
4710
  };
4711
+ /** Edge over the price paid (#32270, #32622): how often the side a wallet bought won, minus the average price it paid, over its settled sports and esports markets of $20 or more at a known entry price. Probabilities throughout: edge 0.012 is 1.2 percentage points. The same figures as LeaderboardPriceEdge, plus the shrunk edge the profile badge and the leaderboard show. No hold: a hold is a fact of a leaderboard row, not of the wallet. */
4712
+ export type TraderPriceEdge = {
4713
+ /** Settled sports and esports markets of $20 or more at a known entry price that the edge is measured over. */
4714
+ markets: number;
4715
+ /** Share of those markets whose bought side won, 0-1. */
4716
+ hit_rate: number;
4717
+ /** Mean entry price paid, 0-1. */
4718
+ avg_price: number;
4719
+ /** hit_rate minus avg_price, full precision, before any shrink. */
4720
+ edge: number;
4721
+ /** Whether the edge ranks. ranked: at least 30 such markets, and the share of the decided ones that made money at settlement within 10 points of the win rate over the same markets. too_few_markets: fewer than 30. sample_mismatch: that share is more than 10 points away, so these markets do not describe the wallet's record. */
4722
+ status: "ranked" | "too_few_markets" | "sample_mismatch";
4723
+ /** The edge the profile badge shows and the leaderboard ranks: edge pulled toward field_average by how short the record is. A short record moves most of the way to the average; a long one keeps nearly all of its own edge. Present only when status is ranked. */
4724
+ shrunk_edge?: number;
4725
+ /** The mean edge of every ranked wallet that traded in the last 180 days, the value shrunk_edge is pulled toward. Present only with shrunk_edge. */
4726
+ field_average?: number;
4727
+ };
4728
+ /** Position sizes over the wallet's sports and esports positions with trades, and how concentrated its volume is across categories. Each measure is an analytic value: read state before value. The four size measures are not_computed while any position's cost is unknown; the counts are always measured. */
4729
+ export type TraderRiskMetrics = {
4730
+ /** Mean cost basis of a position, USD. */
4731
+ avg_position_size: AnalyticNumber;
4732
+ /** Median cost basis of a position, USD. */
4733
+ median_position_size: AnalyticNumber;
4734
+ /** Largest cost basis of a position, USD. */
4735
+ max_position_size: AnalyticNumber;
4736
+ /** Population standard deviation of the cost basis, USD. */
4737
+ position_size_stddev: AnalyticNumber;
4738
+ /** Sports and esports positions with trades. */
4739
+ total_positions: number;
4740
+ /** Positions whose cost is more than twice the mean. */
4741
+ large_position_count: number;
4742
+ /** Herfindahl index of the wallet's volume across sports and esports categories: each category's share of the volume, squared and summed. 1.0 is a single category; lower is more spread. not_computed while any category's cost is unknown, and no value without volume. */
4743
+ concentration: AnalyticNumber;
4744
+ };
4584
4745
  /** Lossless counterparts for trader statistics. The object is present exactly when stats.total_volume is. */
4585
4746
  export type TraderStatsExact = {
4586
4747
  /** stats.total_volume as an exact decimal: the sports and esports USD volume, the sum of every stored trade day's `trader_trade_daily_rollups.in_scope_volume_total` (#29830). Polymarket's full-history wallet-wide user volume before 2026-10-27. */
@@ -4593,6 +4754,30 @@ export type TraderSyncedData = {
4593
4754
  /** Proxy wallet address. */
4594
4755
  wallet: string;
4595
4756
  };
4757
+ /** One band of a time breakdown. */
4758
+ export type TraderTimeBreakdownBand = {
4759
+ /** The band's label. */
4760
+ category_value: string;
4761
+ /** Markets with a realized P&L in the band. */
4762
+ n_markets: number;
4763
+ /** Resolved markets that made money over resolved markets with a nonzero P&L, 0-1. null when none is decided. */
4764
+ win_rate: number | null;
4765
+ /** total_pnl over n_markets, USD. */
4766
+ expectancy: number | null;
4767
+ /** Gross profit over gross loss. null when the band has no loss. */
4768
+ profit_factor: number | null;
4769
+ /** Realized P&L of the band's markets, USD. */
4770
+ total_pnl: number | null;
4771
+ };
4772
+ /** The hold-time and first-trade-hour bands of the profile's Performance breakdown card, over the wallet's sports and esports markets with a realized P&L. */
4773
+ export type TraderTimeBreakdowns = {
4774
+ /** One row per band of time from the wallet's first to its last trade in a market: < 1h, 1h – 1d, 1 – 3d, 3 – 7d, 1 – 4w, > 4w. A band with no market is absent. */
4775
+ by_duration: TraderTimeBreakdownBand[];
4776
+ /** One row per four-hour UTC band of the wallet's first trade in a market: 00-03, 04-07, 08-11, 12-15, 16-19, 20-23. A band with no market is absent. */
4777
+ by_hour_bucket: TraderTimeBreakdownBand[];
4778
+ /** One-line summaries keyed by breakdown (by_duration, by_hour_bucket), from bands of at least 5 markets with a known P&L. A key is present only when there is a summary. */
4779
+ insights: Record<string, string>;
4780
+ };
4596
4781
  /** Field-level trust metadata returned only when GET /api/v1/trader/{address} includes expand=trust. */
4597
4782
  export type TraderTrust = {
4598
4783
  /** Since 2026-10-27 always not_computed: pnl.total is retired and never sent (#29830). The key stays so the object keeps its shape. */
@@ -5600,8 +5785,8 @@ export interface OperationQuery {
5600
5785
  category?: string;
5601
5786
  /** Required for view=observations; wider_holder, in_play or emerging_pile. Rejected for board and signals. wider_holder covers pregame markets that view=signals does not list at its widest selection (horizon_hours=48, min_grade=B, no selector), so until kickoff a market is read in one view or the other. A market view=signals lists leaves wider_holder as primary_slate_candidate, and is a wider_holder candidate again in the first refresh after view=signals stops listing it; signals_as_of names the view=signals snapshot each refresh read. Each refresh evaluates at most 8 markets per sport and cohort, wider_holder by soonest kickoff (most graded holders first among equal kickoffs) and in_play by most graded holders, so a market among a sport's 8 soonest stays evaluated until its game starts. wider_holder also evaluates up to 8 more markets per sport that kick off within 24 hours, most graded holders first, so the most-held markets appear up to a day before kickoff. Each of these keeps its place in later refreshes until its game starts while its holder page still shows a card or could not be read, so a more-held market waits for a free place instead of displacing it. Each row's admitted_by names its place, most_held or soonest_kickoff. The rest count in coverage.capacity_limited_markets. Emerging pile is a wider-holder projection with sharp_pct in [0.75,0.85), complete holder scans and future kickoff, not an independent population or arrival history. */
5602
5787
  cohort?: "wider_holder" | "in_play" | "emerging_pile";
5603
- /** Opt-in admission rule for view=observations with cohort=wider_holder; rejected for every other view and cohort. most_held_48h returns the most-held selection instead of the default one: per sport, up to 8 wider_holder markets kicking off within 48 hours, most graded holders first, then most volume, then soonest kickoff, at most 4 per event. It is recomputed every refresh and keeps no place, so a more-held market displaces a less-held one; markets view=signals lists stay excluded. Every row carries admitted_by most_held_48h, including a market the default selection also evaluates, and the response echoes admission. funnel, coverage, degraded and degradation_reasons describe this selection: a wider_holder candidate it did not take counts as capacity_limited. Its markets that no default place covers are read after every default market, at most once per 10 minutes, so a row can rest on a holder page up to 15 minutes old (holder_snapshot_at). Omit for the default selection. Cursors and ETags bind it. */
5604
- admission?: "most_held_48h";
5788
+ /** Opt-in admission rule for view=observations with cohort=wider_holder; rejected for every other view and cohort. most_held_48h returns the most-held selection instead of the default one: per sport, up to 8 wider_holder markets kicking off within 48 hours, most graded holders first, then most volume, then soonest kickoff, at most 4 per event. It is recomputed every refresh and keeps no place, so a more-held market displaces a less-held one; markets view=signals lists stay excluded. Every row carries admitted_by most_held_48h, including a market the default selection also evaluates, and the response echoes admission. funnel, coverage, degraded and degradation_reasons describe this selection: a wider_holder candidate it did not take counts as capacity_limited. Its markets that no default place covers are read after every default market, at most once per 10 minutes, so a row can rest on a holder page up to 15 minutes old (holder_snapshot_at). most_held_48h_by_type requires market_types and runs the same rule within each named type: per sport and market_context.market_type, up to 8 markets (4 per event), most graded holders first, then volume, then soonest kickoff, so a type whose markets hold fewer wallets than team-win markets still gets its own places. Its rows carry admitted_by most_held_48h_by_type and it has no departures. Its markets no other selection read are read after every default and most_held_48h market, at most once per 10 minutes. A market type not asked for in the last 30 minutes answers 503 read_model_warming with Retry-After 45 s while the next refresh computes it. Omit for the default selection. Cursors and ETags bind it. */
5789
+ admission?: "most_held_48h" | "most_held_48h_by_type";
5605
5790
  /** Board and signals: S, A or B, default B. Requires a piled-side holder at the selected grade floor or better, without removing other holders. Rejected for observations. */
5606
5791
  min_grade?: "S" | "A" | "B";
5607
5792
  /** Board row cap defaults to 30 and clamps integer values to 1..30; total counts selected admitted rows before the cap. Signals and observations page size defaults to 20 and clamps to 1..100. */
@@ -5762,11 +5947,11 @@ export interface OperationQuery {
5762
5947
  };
5763
5948
  getSuspiciousTrade: Record<string, never>;
5764
5949
  getTrader: {
5765
- /** Backward-compatible alias for expand. Repeatable: strategy, categories, trust. quant_metrics is accepted and returns nothing since 2026-10-27. */
5766
- "expand[]"?: ("strategy" | "categories" | "quant_metrics" | "trust")[];
5767
- /** Include heavy fields and trust metadata. Repeatable: strategy, categories, trust. quant_metrics is accepted and returns nothing since 2026-10-27. */
5768
- expand?: ("strategy" | "categories" | "quant_metrics" | "trust")[];
5769
- /** Opt into a whole-response freshness ceiling in seconds. The server returns 200 only when data_quality.status is fresh and data_quality.as_of is no older than this value; otherwise it returns 409 with error.reason=freshness_ceiling_unsatisfied. No refresh or alternate read is attempted. */
5950
+ /** Backward-compatible alias for expand. Repeatable: strategy, categories, trust, breakdowns. quant_metrics is accepted and returns nothing since 2026-10-27. */
5951
+ "expand[]"?: ("strategy" | "categories" | "quant_metrics" | "trust" | "breakdowns")[];
5952
+ /** Include heavy fields and trust metadata. Repeatable: strategy, categories, trust, breakdowns. breakdowns adds the profile's strategy measures (Trader.breakdowns), computed on the read, and is served on this route only. quant_metrics is accepted and returns nothing since 2026-10-27. */
5953
+ expand?: ("strategy" | "categories" | "quant_metrics" | "trust" | "breakdowns")[];
5954
+ /** Opt into a whole-response freshness ceiling in seconds. The server returns 200 only when data_quality.status is fresh or stale and data_quality.as_of is no older than this value; otherwise it returns 409 with error.reason=freshness_ceiling_unsatisfied. No refresh or alternate read is attempted. */
5770
5955
  max_age_s?: number;
5771
5956
  };
5772
5957
  getTraderCategoryRecords: {
@@ -6458,7 +6643,7 @@ export interface OperationBody {
6458
6643
  batchGetTraders: {
6459
6644
  /** Trader identities to resolve in input order. */
6460
6645
  traders: string[];
6461
- /** Shared expand flags applied to every trader item. */
6646
+ /** Shared expand flags applied to every trader item. breakdowns is refused with 400 (param expand): it computes on the read per wallet, so GET /api/v1/trader/{address} serves it one wallet at a time. */
6462
6647
  expand?: ("strategy" | "categories" | "quant_metrics" | "trust")[];
6463
6648
  };
6464
6649
  cancelTraderExport: never;