openapi: 3.2.0 info: title: MadeOnSol Exports API version: 1.20.0 description: Real-time Solana & Robinhood Chain trading intelligence — KOL wallet trades, deployer reputation, token buyer-quality scoring, wallet tracking, and copy-trade signals across both chains (Robinhood Chain paths under `/rhc/*`). contact: name: MadeOnSol url: https://madeonsol.com/contact license: name: Proprietary url: https://madeonsol.com/terms servers: - url: https://madeonsol.com/api/v1 description: Production security: - BearerAuth: [] tags: - name: Exports description: Bulk Parquet exports of every fully archived closed month (token_trades, rhc_trades, token_ohlc_1m, rhc_ohlc_1m) via presigned S3 links. BUSINESS+. paths: /exports: get: tags: - Exports summary: Bulk Parquet exports of archived months (BUSINESS+) description: 'BUSINESS+ (BASIC/PRO/ULTRA get 403 with an `upgrade` block; ENTERPRISE passes). Two modes on one route. **Listing** (no params): every fully archived closed month per dataset — `token_trades`, `rhc_trades`, `token_ohlc_1m`, `rhc_ohlc_1m` — with row count, byte size and file count. A month appears only once both archive providers have verified it (the same ledger the nightly archive exporter writes), so the list is the set you can actually download. **Download** (`dataset` + `month`): presigned S3 GET links for that month''s Parquet files (zstd-compressed, one or more files per month). Links are valid for about **1 hour** — fetch them when you are ready to download, not ahead of time; re-request to mint fresh ones. The ledger row count is returned so you can verify the files you pulled. Both params are required together. The current (open) month is never listed — it is live in Postgres and served by the paginated endpoints (`/tokens/{mint}/trades`, `/rhc/trades`, `/tokens/{mint}/candles`). Files are identical to the immutable dual-provider archive described at https://madeonsol.com/data-integrity — ideal for backtests, model training and offline analytics without paging through the API.' parameters: - name: dataset in: query required: false schema: type: string enum: - token_trades - rhc_trades - token_ohlc_1m - rhc_ohlc_1m description: Dataset to download. Required together with `month`; omit both to list. - name: month in: query required: false schema: type: string pattern: ^\d{4}-\d{2}$ example: 2026-07 description: Closed month `YYYY-MM` (UTC). Required together with `dataset`. responses: '200': description: Listing (no params) or one month's download links (dataset + month). content: application/json: schema: oneOf: - type: object title: ExportListing required: - datasets properties: datasets: type: array items: type: object required: - dataset - months properties: dataset: type: string enum: - token_trades - rhc_trades - token_ohlc_1m - rhc_ohlc_1m months: type: array description: Fully archived closed months, newest first. items: type: object required: - month - rows - bytes - files properties: month: type: string example: 2026-07 description: '`YYYY-MM`, UTC-derived closed month' rows: type: integer description: Row count recorded in the archive ledger at export time bytes: type: integer description: Total compressed Parquet bytes for the month files: type: integer description: Number of Parquet files the month is split into - type: object title: ExportMonth required: - dataset - month - rows - bytes - files - license_note properties: dataset: type: string enum: - token_trades - rhc_trades - token_ohlc_1m - rhc_ohlc_1m month: type: string example: 2026-07 rows: type: integer bytes: type: integer files: type: array items: type: object required: - name - bytes - url - expires_at properties: name: type: string description: Object name, e.g. `token_trades/2026-07/part-0001.parquet` bytes: type: integer url: type: string format: uri description: Presigned S3 GET URL — no auth header needed, plain HTTP GET expires_at: type: string format: date-time description: When the presigned URL stops working (~1 h after issue) license_note: type: string description: The terms the files are released under (BUSINESS embed license; redistribution is ENTERPRISE) headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-RateLimit-Limit: $ref: '#/components/headers/XRateLimitLimit' X-RateLimit-Remaining: $ref: '#/components/headers/XRateLimitRemaining' X-RateLimit-Used: $ref: '#/components/headers/XRateLimitUsed' X-RateLimit-Reset: $ref: '#/components/headers/XRateLimitReset' X-Data-Delay: $ref: '#/components/headers/XDataDelay' X-MadeOnSol-Quota-Warning: $ref: '#/components/headers/XMadeOnSolQuotaWarning' '400': description: '`dataset` or `month` invalid, or only one of the two given.' content: application/json: schema: type: object required: - error properties: error: type: string enum: - invalid_params headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-RateLimit-Limit: $ref: '#/components/headers/XRateLimitLimit' X-RateLimit-Remaining: $ref: '#/components/headers/XRateLimitRemaining' X-RateLimit-Used: $ref: '#/components/headers/XRateLimitUsed' X-RateLimit-Reset: $ref: '#/components/headers/XRateLimitReset' '401': $ref: '#/components/responses/Unauthorized' '403': description: Tier below BUSINESS. Standard tier-gate body with an `upgrade` block. content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-RateLimit-Limit: $ref: '#/components/headers/XRateLimitLimit' X-RateLimit-Remaining: $ref: '#/components/headers/XRateLimitRemaining' X-RateLimit-Used: $ref: '#/components/headers/XRateLimitUsed' X-RateLimit-Reset: $ref: '#/components/headers/XRateLimitReset' '404': description: That month is not (yet) fully archived for that dataset — it is either the open month or still being verified. content: application/json: schema: type: object required: - error properties: error: type: string enum: - month_not_archived headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-RateLimit-Limit: $ref: '#/components/headers/XRateLimitLimit' X-RateLimit-Remaining: $ref: '#/components/headers/XRateLimitRemaining' X-RateLimit-Used: $ref: '#/components/headers/XRateLimitUsed' X-RateLimit-Reset: $ref: '#/components/headers/XRateLimitReset' '429': $ref: '#/components/responses/RateLimited' '500': $ref: '#/components/responses/InternalError' '502': description: The archive store could not be reached to sign the links. Retry later; nothing was consumed from your quota. content: application/json: schema: type: object required: - error properties: error: type: string enum: - archive_unavailable headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-RateLimit-Limit: $ref: '#/components/headers/XRateLimitLimit' X-RateLimit-Remaining: $ref: '#/components/headers/XRateLimitRemaining' X-RateLimit-Used: $ref: '#/components/headers/XRateLimitUsed' X-RateLimit-Reset: $ref: '#/components/headers/XRateLimitReset' '503': $ref: '#/components/responses/AuthBackendUnavailable' x-madeonsol-tier: BUSINESS x-badges: - name: BUSINESS position: after color: '#7c3aed' operationId: getExports x-operation-id-source: derived components: schemas: ServiceUnavailableError: type: object description: '503: a temporary capacity or maintenance condition (e.g. a Postgres statement timeout during a partition move, or the API-key lookup itself being unavailable). Retry after `Retry-After` seconds.' required: - error properties: error: type: string code: type: string description: Machine-readable code where one exists, e.g. `auth_backend_unavailable`. error_kind: type: string description: e.g. `statement_timeout`, `auth_backend` retry_after_seconds: type: number retryable: type: boolean support: type: string _rid: type: string RateLimitError: type: object description: '429 when a quota is exhausted: the daily quota, the per-minute burst, or a free-tier network/key-rotation cap. `datasets` is added on `daily` only.' required: - error - error_kind - message - tier - limit - resets_at properties: error: type: string enum: - rate_limit_exceeded error_kind: type: string enum: - daily - burst - ip_cap - ip_rotation message: type: string tier: type: string enum: - BASIC - PRO - ULTRA - BUSINESS - ENTERPRISE limit: type: integer resets_at: type: string format: date-time upgrade: type: object description: The plan that lifts this limit, with price and a checkout link. properties: tier: type: string enum: - PRO - ULTRA - BUSINESS - ENTERPRISE price_eur_per_month: type: number price_usdc_per_month: type: number daily_limit: type: integer burst_limit: type: integer pitch: type: string url: type: string upgrade_url: type: string format: uri reactivate: type: boolean description: Present (true) when the key's paid subscription has just lapsed and `tier` is the plan it held. prior_tier: type: string enum: - PRO - ULTRA - BUSINESS datasets: type: object properties: pitch: type: string url: type: string format: uri ConcurrencyLimitError: type: object description: '429 from the heavy-query limiter: too many heavy requests in flight for this key (`concurrency`) or for the whole server (`capacity`). Clears as soon as in-flight requests finish.' required: - error - error_kind - retry_after_seconds properties: error: type: string error_kind: type: string enum: - concurrency - capacity retry_after_seconds: type: number max_concurrent: type: integer support: type: string _rid: type: string Error: type: object description: Generic error envelope. `error` is always present (SDKs match on it). Errors returned after authentication also carry `_rid` and, on paid tiers, `support`; authentication-stage errors (missing/invalid key, origin) carry neither. properties: error: type: string description: Error message or machine code (e.g. `tier_required`, `rate_limit_exceeded`). Never changed once shipped. message: type: string description: Human-readable explanation, when `error` is a machine code. code: type: string description: Machine-readable sub-code on some errors (e.g. `invalid_query`, `invalid_wallet`, `origin_not_allowed`). error_kind: type: string description: Machine-readable category on some errors (e.g. `statement_timeout`, `concurrency`, `daily`). detail: type: string description: Extra human-readable context. hint: type: string retry_after_seconds: type: number description: When present on a 429/503, the same value is sent as the `Retry-After` header. signup_url: type: string format: uri description: 'On the missing-key / bad-format 401: where to get a free key.' docs_url: type: string format: uri support: type: string description: 'Paid tiers, errors after authentication: priority support contact.' _rid: type: string description: Request ID — include when reporting issues required: - error headers: XRateLimitReset: description: When the window resets, as a Unix epoch in SECONDS (next 00:00 UTC for the daily quota; about 60 s ahead for a per-minute bucket). schema: type: integer example: 1791158400 XRateLimitRemaining: description: Calls left in that window. `0` on a quota 429. schema: type: integer minimum: 0 example: 9412 XMadeOnSolQuotaWarning: description: 'Free-tier only, past about 70 % of the daily quota: a human-readable usage line with an upgrade link.' schema: type: string example: '150/200 used - upgrade for 50x higher limits: https://madeonsol.com/checkout?tier=PRO' XRateLimitUsed: description: Calls used in that window (`limit − remaining`). schema: type: integer minimum: 0 example: 588 RetryAfter: description: Seconds to wait before retrying (RFC 9110 delay-seconds, at least 1). Sent on every 429 (quota, concurrency, authentication-failure) and every 503; when the body carries `retry_after_seconds` it is the same value. required: true schema: type: integer minimum: 1 example: 10 XDataDelay: description: 'Free-tier only: the feed in this body is delayed by this many seconds (same value as the body''s `delay_seconds`). Absent on real-time responses.' schema: type: integer minimum: 1 example: 300 XRateLimitLimit: description: 'Size of the rate-limit window this call was counted against: the daily quota for your tier on data endpoints, the per-minute bucket on `/stream/token` (10) and `/stream/sessions` (30), the hourly allowance for the public demo key. Sent once the key has been authenticated and rate-limited, so it is absent on authentication-stage errors.' schema: type: integer minimum: 0 example: 10000 XRequestId: description: Unique id of this response. On responses built after authentication it is also the `request_id` of the audit-log row for the call, so quote it in a support request. Present on every response of the keyed API. required: true schema: type: string format: uuid example: 3f6c1d2e-8a4b-4f7e-9c21-5b0d7e6a9f13 responses: AuthBackendUnavailable: description: 'The API-key or subscription lookup could not be answered (authentication stage). Retryable and not your fault: it is never counted as a failed authentication. Honour `Retry-After`; no `X-RateLimit-*`, no `_rid`.' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' Retry-After: $ref: '#/components/headers/RetryAfter' content: application/json: schema: $ref: '#/components/schemas/ServiceUnavailableError' examples: auth_backend: summary: API-key lookup unavailable value: error: Authentication temporarily unavailable — please retry. code: auth_backend_unavailable error_kind: auth_backend retryable: true retry_after_seconds: 5 InternalError: description: Unexpected server error from the route (`Internal server error`, with `_rid`). Safe to retry with backoff. A failed API-key lookup is a 503 `auth_backend_unavailable`, never a 500. headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-RateLimit-Limit: $ref: '#/components/headers/XRateLimitLimit' X-RateLimit-Remaining: $ref: '#/components/headers/XRateLimitRemaining' X-RateLimit-Used: $ref: '#/components/headers/XRateLimitUsed' X-RateLimit-Reset: $ref: '#/components/headers/XRateLimitReset' content: application/json: schema: $ref: '#/components/schemas/Error' examples: route: summary: Route error value: error: Internal server error _rid: eyJ1IjoiZXhhbXBsZSIsInQiOjE3OTEwMDAwMDAwMDAsImgiOiIwMDAwMDAwMCJ9 Unauthorized: description: 'Missing or invalid API key, or an expired free-tier key. Authentication-stage error: no `X-RateLimit-*` headers, no `_rid`.' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' content: application/json: schema: $ref: '#/components/schemas/Error' examples: missing_key: summary: No Bearer key value: error: 'Missing authentication — use API key (Authorization: Bearer msk_...)' hint: Get a free API key — 200 calls/day, no payment signup_url: https://madeonsol.com/developer docs_url: https://madeonsol.com/api-docs bad_format: summary: Malformed key value: error: Invalid API key format hint: Get a free API key — 200 calls/day, no payment signup_url: https://madeonsol.com/developer docs_url: https://madeonsol.com/api-docs RateLimited: description: Too many requests. Every 429 carries `Retry-After`. A quota 429 (`rate_limit_exceeded`, with `X-RateLimit-*`), a heavy-query concurrency 429 (`error_kind` `concurrency`/`capacity`), or the per-IP authentication-failure block (30 failed auths/min; plain `error` + `retry_after_seconds`, no `X-RateLimit-*`). headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-RateLimit-Limit: $ref: '#/components/headers/XRateLimitLimit' X-RateLimit-Remaining: $ref: '#/components/headers/XRateLimitRemaining' X-RateLimit-Used: $ref: '#/components/headers/XRateLimitUsed' X-RateLimit-Reset: $ref: '#/components/headers/XRateLimitReset' Retry-After: $ref: '#/components/headers/RetryAfter' content: application/json: schema: anyOf: - $ref: '#/components/schemas/RateLimitError' - $ref: '#/components/schemas/ConcurrencyLimitError' - $ref: '#/components/schemas/Error' examples: daily: summary: Daily quota exhausted value: error: rate_limit_exceeded error_kind: daily message: Daily rate limit exceeded — 200/day for BASIC tier. Resets at midnight UTC. tier: BASIC limit: 200 resets_at: '2026-10-05T00:00:00.000Z' upgrade: tier: PRO price_eur_per_month: 43 price_usdc_per_month: 49 daily_limit: 10000 burst_limit: 300 pitch: … url: https://madeonsol.com/pricing upgrade_url: https://madeonsol.com/checkout?tier=PRO datasets: pitch: … url: https://madeonsol.com/datasets concurrency: summary: Too many heavy queries in flight value: error: Too many concurrent heavy queries. Up to 8 of these endpoints may be in flight at once per key — retry in a moment, or lower your request concurrency. error_kind: concurrency retry_after_seconds: 2 max_concurrent: 8 _rid: eyJ1IjoiZXhhbXBsZSIsInQiOjE3OTEwMDAwMDAwMDAsImgiOiIwMDAwMDAwMCJ9 auth_failures: summary: Per-IP authentication-failure block value: error: Too many authentication failures from this IP. Retry in 60s. retry_after_seconds: 42 securitySchemes: BearerAuth: type: http scheme: bearer bearerFormat: msk_... description: Get a free API key at https://madeonsol.com/developer x-madeonsol-tiers: - tier: FREE plan: BASIC name: Free price_eur_monthly: 0 price_eur_annual: 0 price_usd_monthly: 0 daily_limit: 200 burst_limit: 60 url: https://madeonsol.com/developer - tier: PRO plan: PRO name: Pro price_eur_monthly: 43 price_eur_annual: 430 price_usd_monthly: 49 daily_limit: 10000 burst_limit: 300 url: https://madeonsol.com/checkout?tier=PRO - tier: ULTRA plan: ULTRA name: Ultra price_eur_monthly: 131 price_eur_annual: 1310 price_usd_monthly: 149 daily_limit: 100000 burst_limit: 600 url: https://madeonsol.com/checkout?tier=ULTRA - tier: BUSINESS plan: BUSINESS name: Business price_eur_monthly: 400 price_eur_annual: 4000 price_usd_monthly: 456 daily_limit: 500000 burst_limit: 3000 url: https://madeonsol.com/checkout?tier=BUSINESS - tier: ENTERPRISE plan: ENTERPRISE name: Enterprise price_eur_monthly: null price_eur_annual: null price_usd_monthly: null daily_limit: null burst_limit: null url: https://madeonsol.com/pricing#enterprise x-provenance: method: harvested publisher: MadeOnSol source: https://madeonsol.com/api/v1/openapi.json note: split by refine-openapis from the provider's own published document, verified by fetch (openapi/_original/*.fetch.json); stamped by stamp-openapi-provenance.py (roadmap#793)