{
  "openapi": "3.1.0",
  "info": {
    "title": "ZendIQ Agent API",
    "version": "1.4.0",
    "summary": "Execution-layer swap protection for Solana, priced per call over x402.",
    "description": "Scores token risk and sandwich exposure for a proposed Solana swap and returns a Safe / Protect / Refuse verdict; /optimize returns the same verdict together with an unsigned optimised transaction.\n\nIMPORTANT — NETWORK: everything runs on Solana **mainnet**. Payments settle in **mainnet USDC** over x402; token scores, sandwich exposure and quotes are read from mainnet; and /optimize returns a real mainnet transaction. The `network` field of GET /v1/agent states the payment network, so read it there rather than hardcoding it. The paying wallet needs USDC and no SOL: the facilitator sponsors the payment's network fee. POST /v1/agent/optimize builds its route for `taker`, a mainnet wallet holding the input amount and SOL for fees and rent (a gasless Jupiter Ultra fill is exempt from the SOL); it can be the paying wallet or a different one.\n\nCUSTODY: none. ZendIQ never holds keys or funds. /analyse builds no transaction; /optimize returns an unsigned transaction you sign and submit yourself.\n\nLATENCY: most of a call is token screening, shared by all callers and cached per mint for 60 s. A first scan of a token typically takes 1–5 s (measured 0.5–4.6 s on the live service, 2 Oct 2026) and is bounded at ~12 s by per-source timeouts; a mint scanned in the last 60 s returns in under 1 s; /optimize with a valid `analysisId` skips the scan and typically takes 1–2 s. Set your client timeout to at least 120 s on /analyse and /optimize (see SETTLEMENT) and at least 30 s on /analyse-token. A scan always runs to completion and is never returned partially, because an agent cannot act on a partly scanned token. The one bounded read is creator history: when a creator's record is longer than its 4 s budget allows, `creator_launches_30d` reports the count read so far as a lower bound (`reason: partial_scan_lower_bound`), never as a pass. A source that does not answer within its own timeout is reported as an `unknown` signal inside a completed 200; treat unknown as unknown, never as safe.\n\nSETTLEMENT: a paid call replies only once the server knows whether your payment landed, usually within a second or two. If the facilitator cannot confirm the transfer itself, the server reads the chain until the transfer confirms, fails, or can no longer land because its blockhash expired (about 60 s after you signed), and never waits more than 90 s. Keep the connection open. A 402 after a paid attempt carries a PAYMENT-RESPONSE header whose `errorReason` says what happened: `settlement_failed_on_chain` or `settlement_not_landed` means you were not charged; `settlement_pending` means it could not be confirmed either way, so look up that header's `transaction` on chain before paying again.\n\nHANDSHAKE: /analyse-token and /analyse return an `analysisId`. Pass it to /optimize within 60 s to reuse that token scan; market state (quote, price impact, sandwich exposure, congestion) is always fetched fresh. A missing, expired or unknown ID means a full scan, never an error. An ID for a different mint is rejected with 400 `analysis_mint_mismatch`, uncharged.\n\nAUTHORITATIVE SOURCE: GET /v1/agent returns a live, free, unauthenticated manifest of every endpoint, its price, the rate limits, and the field-stability contract. If this document and that manifest disagree, the manifest is correct.\n\nNot financial advice.",
    "termsOfService": "https://zendiq.ai/agents/terms/",
    "contact": { "name": "ZendIQ (Leviathan Technologies AB)", "email": "support@zendiq.ai", "url": "https://zendiq.ai" },
    "license": { "name": "See repository", "url": "https://github.com/ZendIQ/ZendIQ-Agent-API" },
    "x-guidance": "Start with GET /v1/agent (free): it states the live prices, the payment network and which response fields are stable. POST /v1/agent/analyse-token (free) screens one mint. POST /v1/agent/analyse ($0.01) returns a Safe / Protect / Refuse verdict; POST /v1/agent/optimize ($0.02) also returns an unsigned transaction for `taker`, to verify against `plan` before signing. Both are paid over x402: the first request returns 402 with the challenge in the PAYMENT-REQUIRED header; sign it and resend the same request with PAYMENT-SIGNATURE. A call that fails (any 4xx/5xx) is not charged. Treat an `unknown` signal as unknown, never as safe. Allow at least 120 s on paid calls."
  },
  "externalDocs": {
    "description": "Full reference",
    "url": "https://zendiq.ai/llms-full.txt"
  },
  "servers": [
    {
      "url": "https://api.zendiq.ai",
      "description": "Production. Payments settle in USDC on Solana mainnet."
    },
    {
      "url": "https://zendiq-backend.onrender.com",
      "description": "Alternate address for the same production service."
    }
  ],
  "tags": [
    { "name": "discovery", "description": "Free, unauthenticated service description." },
    { "name": "screening", "description": "Free token risk screening." },
    { "name": "triage", "description": "Paid swap analysis and transaction building." }
  ],
  "paths": {
    "/v1/agent": {
      "get": {
        "operationId": "getServiceManifest",
        "tags": ["discovery"],
        "security": [],
        "summary": "Service manifest",
        "description": "Free and unauthenticated. Declares every endpoint, its price, the rate limits, the custody position, and which response fields are stable versus experimental. Fetch this at runtime rather than hardcoding prices.",
        "responses": {
          "200": {
            "description": "The live service manifest.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ServiceManifest" }
              }
            }
          }
        }
      }
    },
    "/v1/agent/analyse-token": {
      "post": {
        "operationId": "screenToken",
        "tags": ["screening"],
        "security": [],
        "summary": "Screen a single token mint (free)",
        "description": "Free and rate-limited; registered ahead of the payment gate so it is never charged. Returns a risk score for one SPL token mint together with the signals behind it, and an `analysisId` that /optimize can reuse for 60 s. A first scan takes 1–5 s typically and up to ~12 s; set a client timeout of at least 30 s. A source that does not answer in time is an `unknown` signal inside the 200, not a failure. If the token cannot be screened at all, the call returns 502 `analysis_unavailable` rather than a score it could not compute — there is no degraded 200 on this endpoint. Rate-limited per IP on its own, higher limit than the paid endpoints.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/ScreenRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Screening result.",
            "headers": { "X-API-Version": { "$ref": "#/components/headers/XApiVersion" } },
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ScreenResponse" } }
            }
          },
          "400": { "$ref": "#/components/responses/InvalidRequest" },
          "422": { "$ref": "#/components/responses/NotAMint" },
          "429": { "$ref": "#/components/responses/ScreenRateLimited" },
          "500": { "$ref": "#/components/responses/InternalError" },
          "502": { "$ref": "#/components/responses/AnalysisUnavailable" }
        }
      }
    },
    "/v1/agent/analyse": {
      "post": {
        "operationId": "analyseSwap",
        "tags": ["triage"],
        "x-payment-info": { "protocols": [{ "x402": {} }], "price": { "mode": "fixed", "currency": "USD", "amount": "0.01" } },
        "summary": "Verdict for a proposed swap ($0.01)",
        "description": "Costs $0.01, paid over x402. Returns a Safe / Protect / Refuse verdict with token risk, sandwich exposure, and a recommended execution path. Builds no transaction. Always settles on success, because the verdict itself is the deliverable — a Refuse verdict is a useful answer, not a failure. Returns an `analysisId` for the token scan of `outputMint`, reusable by /optimize for 60 s. A first scan of a token can take up to ~12 s and settlement up to 90 s; set a client timeout of at least 120 s.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": { "schema": { "$ref": "#/components/schemas/AnalyseRequest" } }
          }
        },
        "responses": {
          "200": {
            "description": "Verdict and supporting evidence.",
            "headers": {
              "X-API-Version": { "$ref": "#/components/headers/XApiVersion" },
              "PAYMENT-RESPONSE": { "$ref": "#/components/headers/PaymentResponse" }
            },
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/AnalyseResponse" } }
            }
          },
          "400": { "$ref": "#/components/responses/InvalidRequest" },
          "402": { "$ref": "#/components/responses/PaymentRequired" },
          "409": { "$ref": "#/components/responses/AuthorizationAlreadyUsed" },
          "422": { "$ref": "#/components/responses/NotAMint" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/InternalError" },
          "502": { "$ref": "#/components/responses/AnalysisUnavailable" }
        }
      }
    },
    "/v1/agent/optimize": {
      "post": {
        "operationId": "optimizeSwap",
        "tags": ["triage"],
        "x-payment-info": { "protocols": [{ "x402": {} }], "price": { "mode": "fixed", "currency": "USD", "amount": "0.02" } },
        "summary": "Unsigned optimised transaction ($0.02)",
        "description": "Costs $0.02, paid over x402. Returns an unsigned swap transaction, the structured `plan` it is supposed to contain, how to `submit` it, a simulation result, and an itemised `netBenefit` breakdown. The venue is chosen by risk, then by net benefit: a Jupiter route, or a direct venue when it beats that route after costs (`plan.venueDecision` shows the comparison) — read `plan.venue` and follow `submit` rather than assuming one. Verify the decoded transaction against `plan` before signing — do not blind-sign. A failed build returns >= 400 and charges nothing.\n\nThis endpoint returns the same `verdict` (Safe / Protect / Refuse), `confidence` and `reasons` as /analyse, but does not refuse to build: a Refuse still comes back with a transaction, because calling /optimize means you have decided to trade. Read `verdict` before signing, and do not sign a Refuse unless you mean to trade against it.\n\nPass `analysisId` from /analyse-token or /analyse (for this `outputMint`, within 60 s) to skip the token scan; the call then typically takes 1–2 s. `analysis` states whether the scan was reused, served from the shared 60 s cache, or run fresh, and why. A missing, expired or unknown ID means a full scan, never an error; an ID for another mint is 400 `analysis_mint_mismatch`, uncharged. Without a usable ID a first scan can take up to ~12 s, and settlement can add up to 90 s; set a client timeout of at least 120 s.\n\nThe route is built against mainnet liquidity, so `taker` is a mainnet wallet. It can be the wallet that pays for the call (which needs USDC and no SOL) or a different one. The `taker` must hold the input amount and mainnet SOL for the network fee, any priority fee, and rent for token accounts the swap opens; on a SOL input the SOL being swapped comes on top. A gasless Jupiter Ultra fill, where `plan.priorityFee.feePayer` is not the `taker`, is exempt from the SOL. A `taker` that cannot fund the trade gets 422 `taker_insufficient_balance`, uncharged, naming the token and the shortfall.\n\nBuild-only trial: `taker` is only a public key and nothing is signed here, so any funded mainnet address can be passed to see the full `plan`, `simulation` and `plan.venueDecision`. A transaction built for a wallet you do not control cannot be signed or submitted; to land a trade, pass your own wallet.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": { "schema": { "$ref": "#/components/schemas/OptimizeRequest" } }
          }
        },
        "responses": {
          "200": {
            "description": "Unsigned transaction with the arithmetic behind it.",
            "headers": {
              "X-API-Version": { "$ref": "#/components/headers/XApiVersion" },
              "PAYMENT-RESPONSE": { "$ref": "#/components/headers/PaymentResponse" }
            },
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/OptimizeResponse" } }
            }
          },
          "400": { "$ref": "#/components/responses/OptimizeBadRequest" },
          "402": { "$ref": "#/components/responses/PaymentRequired" },
          "409": { "$ref": "#/components/responses/AuthorizationAlreadyUsed" },
          "422": { "$ref": "#/components/responses/TakerInsufficientBalance" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/InternalError" },
          "502": { "$ref": "#/components/responses/OptimizeUnavailable" }
        }
      }
    },
    "/v1/agent/bundle": {
      "post": {
        "operationId": "submitBundle",
        "tags": ["triage"],
        "security": [],
        "summary": "Submit a signed Jito bundle transaction (free)",
        "description": "Free and rate-limited; you paid at /optimize. Forwards a signed transaction from an /optimize response with submit.method jito_bundle to Jito's block engine under ZendIQ's whitelisted key, then reports landing, read from the chain. Only a transaction that pays a non-zero tip to a Jito tip account, whose message is exactly one /optimize issued in the last 5 minutes, and whose fee-payer signature verifies is forwarded. Idempotent: posting the same signed transaction again returns the first submission with replayed: true and a fresh status check; a second bundle is never sent. not_landed is reported only when the blockhash has expired and the signature is not on chain; a status check that could not run is unknown.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["signedTransaction"],
                "properties": { "signedTransaction": { "type": "string", "contentEncoding": "base64" } }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Submitted (or replayed). Read `state`.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BundleSubmission" } } }
          },
          "400": {
            "description": "invalid_request, invalid_transaction (undecodable or unsigned) or invalid_signature. Nothing was sent.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          },
          "422": {
            "description": "no_jito_tip (the transaction pays no tip to a Jito tip account) or transaction_not_issued (not a transaction /optimize returned, issued over 5 minutes ago, or before a server restart). Nothing was sent.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          },
          "429": {
            "description": "rate_limited: Jito submissions are rate-limited across all callers and nothing was sent. Retry the same signed transaction after Retry-After seconds.",
            "headers": { "Retry-After": { "$ref": "#/components/headers/RetryAfter" } },
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          },
          "500": {
            "description": "Unexpected error. The bundle may or may not have been sent; check the signature on chain before signing a new transaction.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          }
        }
      }
    },
    "/v1/agent/bundle/{signature}": {
      "get": {
        "operationId": "bundleStatus",
        "tags": ["triage"],
        "security": [],
        "summary": "Status of a bundle submitted through this server (free)",
        "parameters": [{ "name": "signature", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": {
          "200": {
            "description": "Current state, re-checked on chain unless already terminal.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BundleSubmission" } } }
          },
          "404": {
            "description": "unknown_submission: no submission with this signature was made through this server in the last 30 minutes.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          }
        }
      }
    }
  },
  "components": {
    "headers": {
      "XApiVersion": {
        "description": "The API version that served this response.",
        "schema": { "type": "string", "examples": ["v1"] }
      },
      "PaymentResponse": {
        "description": "Base64-encoded x402 settlement receipt, including the settlement transaction.",
        "schema": { "type": "string" }
      },
      "PaymentRequired": {
        "description": "Base64-encoded x402 challenge naming the price, asset, network and payee.",
        "schema": { "type": "string" }
      },
      "RetryAfter": {
        "description": "Seconds to wait before retrying. A 429 does not consume a payment authorization — present the same one again.",
        "schema": { "type": "integer" }
      },
      "TermsLink": {
        "description": "The API terms that a payment accepts.",
        "schema": { "type": "string", "examples": ["<https://zendiq.ai/agents/terms/>; rel=\"terms-of-service\""] }
      }
    },
    "responses": {
      "InvalidRequest": {
        "description": "A field failed validation. The message names the field. You were not charged.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "OptimizeBadRequest": {
        "description": "You were not charged. Either `invalid_request` (a field failed validation; the message names it) or `analysis_mint_mismatch`: `analysisId` was issued for a different mint than this request's `outputMint`. A mismatch is rejected rather than scanned past, because pairing one token's analysis with another token's trade is a caller bug that a silent rescan would hide. The body names both mints; screen the right mint, or omit `analysisId`. A missing, expired or unknown `analysisId` is never a 400 — it means a full scan.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "PaymentRequired": {
        "description": "Payment required. Paying for a call accepts the API terms at https://zendiq.ai/agents/terms/, also linked from the `Link` header (rel=terms-of-service) on every response. Sign an authorization for exactly the amount in the PAYMENT-REQUIRED header and retry the identical request with a PAYMENT-SIGNATURE header (X-PAYMENT is accepted as an alias; a bare PAYMENT header is not read). This is the normal first response to a valid request on a paid endpoint, not an error. Requests are validated before the challenge: a malformed body is 400 `invalid_request`, an address that is not a token mint is 422 `not_a_mint`, and on /optimize a `taker` that does not hold the input amount is 422 `taker_insufficient_balance`, all without a 402, so you never sign a payment for a request that cannot be served. A 402 in reply to a paid attempt instead carries a PAYMENT-RESPONSE header: its `errorReason` `settlement_failed_on_chain` or `settlement_not_landed` means the transfer did not move and you were not charged; `settlement_pending` means it could not be confirmed within 90 s and may have landed: keep the PAYMENT-SIGNATURE header and retry once with the same header after about 5 s. If the transfer landed, that retry is redeemed for one fresh response on the same route within 24 h, and its PAYMENT-RESPONSE carries `redeemed: true`. Do not sign a new authorization.",
        "headers": {
          "PAYMENT-REQUIRED": { "$ref": "#/components/headers/PaymentRequired" },
          "PAYMENT-RESPONSE": { "$ref": "#/components/headers/PaymentResponse" },
          "Link": { "$ref": "#/components/headers/TermsLink" }
        }
      },
      "RateLimited": {
        "description": "Rate limited: 60 requests per IP per minute, 30 per payer per minute. Does not consume a payment authorization — present the same one again after Retry-After.",
        "headers": { "Retry-After": { "$ref": "#/components/headers/RetryAfter" } },
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RateLimitError" } } }
      },
      "ScreenRateLimited": {
        "description": "Rate limited on the free screen endpoint, which has its own per-IP limit (240 per minute by default). Back off for Retry-After seconds and retry.",
        "headers": { "Retry-After": { "$ref": "#/components/headers/RetryAfter" } },
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RateLimitError" } } }
      },
      "AuthorizationAlreadyUsed": {
        "description": "This payment authorization was already presented and has not settled into a result that can be replayed. Read `priorOutcome`: `in_flight` means retry shortly with the same authorization; any other value means request a fresh 402 challenge — except `settle_threw` and `settle_unknown`, where the transfer may have landed and paying again could double-charge; check it on chain first. A replay of an authorization that did settle is not a 409: it returns the original 200 body with `replayed: true`. A retry of a `settlement_pending` payment is answered by redemption instead: `already_served` (redeemed once already), `redemption_in_progress`, `redemption_route_mismatch` (only the route it paid for), `redemption_amount_mismatch`, `redemption_expired` (after 24 h; email support@zendiq.ai with the `settlement` signature for a refund) or `settlement_unresolved` (not yet known; do not pay again). Those carry `charged` and `settlement`.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AuthorizationError" } } }
      },
      "InternalError": {
        "description": "Unexpected server error. You were not charged.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "TakerInsufficientBalance": {
        "description": "Error code `taker_insufficient_balance` or `not_a_mint`; you were not charged for either. `not_a_mint`: `inputMint` or `outputMint` is not a token mint (see the NotAMint response). `taker_insufficient_balance`: No transaction is returned, and you were not charged. The `taker` cannot fund the trade: `need` is `input_token` when it does not hold the input amount (checked before anything is quoted or built), or `sol_for_fees` when it cannot pay for the transaction it would sign (base fee, priority fee and Jito tip in the bytes, plus rent for token accounts the swap opens). A transaction whose fee payer is not the taker (a gasless Ultra fill) is exempt, and another venue that can fill gaslessly is tried before refusing. The body names the token, the amount required and held, and the shortfall. Balances are read from the taker's associated token accounts. SOL requirements are lower bounds, and when the balances cannot be read the check is skipped and the build proceeds.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "NotAMint": {
        "description": "Error code `not_a_mint`. The address in `field` (`mint`, `inputMint` or `outputMint`) is not an SPL Token or Token-2022 mint, so no score is produced and no `analysisId` is issued. `reason` is `account_not_found` (no account exists at the address; usually a typo, or a mint created seconds ago, so retry shortly) or `not_a_mint_account` (an account exists but is not a mint, such as a wallet or token account; `owner` names its program). Checked before the 402 challenge, so you never sign a payment for it, and never charged.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "OptimizeUnavailable": {
        "description": "No transaction is returned, and you were not charged. Error code `optimize_unavailable`; `causes` names what each venue returned. Raised when: no venue could build a transaction, including after falling back; the Jupiter route could not be priced and Ultra could not be built (an unprotected direct venue is never substituted for it); the built transaction's programs contradict its venue, or a bundle carries no Jito tip; or the built transaction's venue could not be verified from its bytes or, on jupiter_ultra, from its provenance.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "AnalysisUnavailable": {
        "description": "An upstream data source failed and no honest verdict could be produced. ZendIQ returns this rather than guessing, because a safety check that fails open is worse than no check. A slow source does not cause this: a source that runs out its timeout is an `unknown` signal inside a completed 200. You were not charged.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      }
    },
    "schemas": {
      "Base58Pubkey": {
        "type": "string",
        "pattern": "^[1-9A-HJ-NP-Za-km-z]{32,44}$",
        "description": "A Solana public key in base58.",
        "examples": ["So11111111111111111111111111111111111111112"]
      },
      "AtomicAmount": {
        "type": "string",
        "pattern": "^\\d{1,20}$",
        "description": "Amount in atomic units as a decimal string — not a number and not a float. 1 SOL is \"1000000000\". Must be greater than zero.",
        "examples": ["1000000000"]
      },
      "RiskLevel": {
        "type": "string",
        "enum": ["LOW", "MEDIUM", "HIGH", "CRITICAL"]
      },
      "Verdict": {
        "type": "string",
        "enum": ["Safe", "Protect", "Refuse"],
        "description": "Closed enum within v1 — no new values will be added inside this version, so it is safe to switch on exhaustively. Safe: proceed. Protect: proceed only with the protection in recommendedExecution. Refuse: do not execute."
      },
      "Provenance": {
        "type": "object",
        "description": "Where the answer came from, so it can be audited rather than trusted.",
        "properties": {
          "schema_version": { "type": "string", "description": "Per endpoint. /analyse and /analyse-token are at 1.3 and /optimize at 1.2. 1.2 (/optimize 1.1) added `rejected_value` on signals and `inputs.marketPair`; 1.3 (/optimize 1.2) added `inputs.priceHistory` and the `rate_limited` reason." },
          "snapshot": {
            "type": ["object", "null"],
            "description": "The chain position the evidence was observed at. Null on /analyse-token when the slot lookup failed entirely.",
            "properties": {
              "slot": { "type": ["integer", "null"], "description": "Confirmed slot, or null when the RPC could not supply one." },
              "observed_at": { "type": "string", "format": "date-time" },
              "reason": { "type": "string", "description": "Present only when slot is null. Either \"slot_unavailable\" when the node answered but had no confirmed slot, or \"rpc_unavailable (<error>)\" when no node answered at all; the parenthesised part is the underlying error text and is not a fixed set." }
            }
          },
          "evidence_fingerprint": { "type": "string", "description": "16-character SHA-256 prefix over the evidence used." },
          "signals_resolved": { "type": "string", "description": "Coverage: resolved signals over the total, e.g. \"11/16\" means five came back unknown. An unknown never lowers the score; most add a `…: unavailable` factor with +5 caution points. The exception is a short list of established assets (regulated stablecoins, established protocol tokens, liquid staking tokens), named by an asset-class factor such as `Regulated stablecoin`: checks that do not apply to that class are not scored, so an unknown on one of them adds nothing. Treat unknown as unknown, never as safe: a LOW score with low coverage means few checks were answered, not that the token is low-risk.", "examples": ["14/16"] },
          "signals": {
            "type": "array",
            "description": "Always all 16 signals, in a fixed order, whether or not each one resolved. A signal that could not be read is present with status \"unknown\" and a reason, never omitted and never silently downgraded to a pass. The example below is illustrative and shows one of every status; it is not a live reading of any particular token.",
            "items": { "$ref": "#/components/schemas/Signal" },
            "examples": [[
              { "id": "mint_authority", "type": "categorical", "value": null, "unit": null, "status": "pass" },
              { "id": "freeze_authority", "type": "categorical", "value": null, "unit": null, "status": "pass" },
              { "id": "top_holder_concentration", "type": "continuous", "value": 18.42, "unit": "percent", "status": "warn" },
              { "id": "rugcheck_report", "type": "categorical", "value": { "rugged": false, "risks": [{ "name": "Large amount of LP Unlocked", "level": "warn" }] }, "unit": null, "status": "warn" },
              { "id": "launch_platform", "type": "categorical", "value": "non_launchpad", "unit": null, "status": "pass" },
              { "id": "lp_locked", "type": "continuous", "value": 96.2, "unit": "percent", "status": "pass" },
              { "id": "price_change_3m", "type": "continuous", "value": -21.7, "unit": "percent", "status": "warn" },
              { "id": "price_change_long_term", "type": "continuous", "value": null, "unit": "percent", "status": "unknown", "reason": "insufficient_history" },
              { "id": "volume_trend", "type": "continuous", "value": 0.42, "unit": "ratio_7d_to_baseline", "status": "pass" },
              { "id": "token_age", "type": "continuous", "value": 412.6, "unit": "days", "status": "pass" },
              { "id": "price_change_24h", "type": "continuous", "value": 3.81, "unit": "percent", "status": "pass" },
              { "id": "liquidity_depth", "type": "continuous", "value": 184000, "unit": "usd", "status": "pass" },
              { "id": "market_cap", "type": "continuous", "value": 2450000, "unit": "usd", "status": "pass" },
              { "id": "creator_launches_30d", "type": "continuous", "value": 1, "unit": "tokens", "status": "pass" },
              { "id": "creator_rug_rate", "type": "continuous", "value": null, "unit": "percent", "status": "unknown", "reason": "creator_history_unavailable" },
              { "id": "creation_slot_transactions", "type": "continuous", "value": 2, "unit": "transactions", "status": "pass" }
            ]]
          },
          "inputs": {
            "type": "object",
            "properties": {
              "marketPair": {
                "type": ["object", "null"],
                "description": "The DexScreener pool the market signals (price_change_24h, liquidity_depth, market_cap) were read from. With three or more priced pools, pools priced more than 5× from the median are dropped before the deepest is chosen (method median_consensus); with fewer, the deepest is used (method deepest). Null when the data came from another source or none.",
                "properties": {
                  "pairAddress": { "type": ["string", "null"] },
                  "method": { "type": "string", "enum": ["median_consensus", "deepest"] },
                  "poolsConsidered": { "type": "integer" },
                  "poolsDropped": { "type": "integer" },
                  "medianPriceUsd": { "type": ["number", "null"] }
                }
              },
              "priceHistory": {
                "type": ["object", "null"],
                "description": "Where price_change_3m, price_change_long_term and volume_trend came from. source is fresh (fetched on this scan), cache (a series fetched up to 6 h earlier; daily candles change once a day), skipped (token under a day old) or none (not available; reason says why). cache_age_s is the cache age when the scan ran; it is left out of evidence_fingerprint, so responses served from one cached series share a fingerprint.",
                "properties": {
                  "source": { "type": "string", "enum": ["fresh", "cache", "skipped", "none"] },
                  "fetched_at": { "type": ["string", "null"], "format": "date-time" },
                  "cache_age_s": { "type": ["integer", "null"] },
                  "pool": { "type": ["string", "null"], "description": "The pool the daily candles belong to." },
                  "reason": { "type": ["string", "null"], "enum": [null, "rate_limited", "timeout", "http_error", "network", "fetch_failed", "under_a_day_old"] },
                  "http_status": { "type": ["integer", "null"], "description": "The upstream status when one answered, e.g. 429. Null when no request was sent, for example because the rate limiter held it back." }
                }
              }
            }
          }
        }
      },
      "Signal": {
        "type": "object",
        "description": "One piece of evidence. `status` is the scorer's reading of `value`, not a summary of the token. Treat \"unknown\" as absence of evidence: it is not a pass, and it is why `signals_resolved` is below 16.",
        "properties": {
          "id": {
            "type": "string",
            "description": "Stable identifier. The full set is fixed at 16 and every response contains all of them.",
            "enum": [
              "mint_authority", "freeze_authority", "top_holder_concentration", "rugcheck_report",
              "launch_platform", "lp_locked", "price_change_3m", "price_change_long_term",
              "volume_trend", "token_age", "price_change_24h", "liquidity_depth",
              "market_cap", "creator_launches_30d", "creator_rug_rate", "creation_slot_transactions"
            ]
          },
          "type": { "type": "string", "enum": ["categorical", "continuous"] },
          "value": {
            "type": ["string", "number", "object", "null"],
            "description": "The observed reading, in `unit`. Null whenever status is \"unknown\". For mint_authority and freeze_authority, null is the good case and means the authority is revoked; a base58 address means it is still held. An authority set to the all-zero address (11111111111111111111111111111111) is reported as held, because that is what the chain stores."
          },
          "unit": {
            "type": ["string", "null"],
            "description": "Null for categorical signals.",
            "enum": [null, "percent", "usd", "days", "tokens", "transactions", "ratio_7d_to_baseline"]
          },
          "rejected_value": {
            "type": "number",
            "description": "Present only with reason implausible_value: the figure the source reported, in `unit`, which no real token can have. It is kept for audit and is not a reading of the token; do not repeat it as fact."
          },
          "status": {
            "type": "string",
            "description": "pass = read and unremarkable. warn / fail = read and concerning, at increasing severity. unknown = could not be read at all.",
            "enum": ["pass", "warn", "fail", "unknown"]
          },
          "reason": {
            "type": "string",
            "description": "Present when status is \"unknown\", naming which source did not answer, either because it failed or because it ran its full timeout. insufficient_history is a fact about the token; every other unknown value is a fact about our data sources on this call, so the same mint can resolve differently on a later call. implausible_value means the source answered with a figure no real token can have (market cap above $5T, or a 24h gain above +10,000% on a token over 30 days old); the figure is in `rejected_value`. rate_limited means the price-history source was rate-limiting requests, so price_change_3m, price_change_long_term and volume_trend were not checked on this call; a later call can resolve them. One value appears on a read signal: partial_scan_lower_bound on creator_launches_30d, meaning the creator's history ran past its time budget and `value` is a lower bound; its status is then warn or fail, never pass.",
            "enum": [
              "onchain_unavailable", "holder_distribution_unavailable", "rugcheck_unavailable",
              "launch_platform_unavailable", "lp_lock_unavailable", "price_history_fetch_failed",
              "insufficient_history", "volume_history_unavailable", "creation_time_unavailable",
              "market_data_unavailable", "creator_history_unavailable", "creation_slot_unavailable",
              "implausible_value", "rate_limited", "partial_scan_lower_bound"
            ]
          }
        },
        "required": ["id", "type", "value", "unit", "status"]
      },
      "TokenRisk": {
        "description": "Degrades to an explicit available:false object when screening did not complete because of an unexpected upstream failure (/analyse and /optimize only — /analyse-token returns 502 instead). A slow source never causes this; it is reported as an unknown signal inside a completed scan. It never returns a safe-looking score it could not compute. On the unavailable variant, fees and overall risk were sized as if the token scored assumedLevel, and on /analyse the verdict fails closed to Protect with confidence \"low\" and a token_screening entry in `degraded` — a Protect reached this way means the token was not checked, not that it was found risky.",
        "oneOf": [
          {
            "type": "object",
            "properties": {
              "mint": { "$ref": "#/components/schemas/Base58Pubkey" },
              "symbol": { "type": ["string", "null"] },
              "score": { "type": "number" },
              "level": { "$ref": "#/components/schemas/RiskLevel" },
              "factors": {
                "type": "array",
                "description": "Omitted on /optimize.",
                "items": {
                  "type": "object",
                  "properties": {
                    "name": { "type": "string" },
                    "severity": { "$ref": "#/components/schemas/RiskLevel" },
                    "detail": { "type": "string" },
                    "points": { "type": "number", "description": "This factor's contribution to score. Points across all factors sum to score before the 0–100 clamp." }
                  }
                }
              },
              "dataSource": { "type": ["string", "null"], "description": "Omitted on /optimize." }
            },
            "required": ["mint", "score", "level"]
          },
          {
            "type": "object",
            "properties": {
              "mint": { "$ref": "#/components/schemas/Base58Pubkey" },
              "available": { "const": false },
              "error": { "type": "string", "examples": ["scan_failed"] },
              "assumedScore": { "type": "number", "examples": [50] },
              "assumedLevel": { "$ref": "#/components/schemas/RiskLevel" },
              "note": { "type": "string" }
            },
            "required": ["available", "assumedScore", "assumedLevel"]
          }
        ]
      },
      "SandwichExposure": {
        "oneOf": [
          {
            "type": "object",
            "properties": {
              "score": { "type": "number" },
              "level": { "$ref": "#/components/schemas/RiskLevel" },
              "estimatedLossPercentage": { "type": ["number", "null"], "description": "A model estimate, not a measurement: the sandwich risk score maps to a fixed share of the trade (score 80+: 2%, 60+: 0.8%, 40+: 0.3%, 20+: 0.05%, below 20: 0.01%). If a trade is sandwiched, the loss is bounded by its slippage tolerance, not by this figure." },
              "estimatedLossUsd": { "type": ["number", "null"], "description": "estimatedLossPercentage applied to the trade value. A model estimate, not a measurement." },
              "confidence": { "type": ["string", "null"], "description": "high when pool liquidity was known when scoring, or when the fill is off-chain (RFQ: there is no pool to sandwich); medium otherwise." },
              "factors": { "type": "array", "items": { "type": "object" } }
            },
            "required": ["score", "level"]
          },
          {
            "type": "object",
            "properties": {
              "available": { "const": false },
              "reason": { "type": "string" }
            },
            "required": ["available"]
          }
        ]
      },
      "RiskBlock": {
        "type": "object",
        "properties": {
          "score": { "type": "number" },
          "level": { "$ref": "#/components/schemas/RiskLevel" },
          "floored": { "type": "boolean" },
          "factors": { "type": "array", "items": { "type": "object" } }
        }
      },
      "ScreenRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": ["mint"],
        "properties": {
          "mint": { "$ref": "#/components/schemas/Base58Pubkey" }
        }
      },
      "ScreenResponse": {
        "allOf": [
          { "$ref": "#/components/schemas/Provenance" },
          {
            "type": "object",
            "properties": {
              "stage": { "const": "screen" },
              "mint": { "$ref": "#/components/schemas/Base58Pubkey" },
              "analysisId": { "$ref": "#/components/schemas/AnalysisId" },
              "analysisExpiresAt": { "$ref": "#/components/schemas/AnalysisExpiresAt" },
              "tokenRisk": { "$ref": "#/components/schemas/TokenRisk" },
              "cache": {
                "type": "object",
                "properties": {
                  "hit": { "type": "boolean" },
                  "ageSeconds": { "type": "number" },
                  "observedAt": { "type": ["string", "null"], "format": "date-time" }
                }
              },
              "disclaimer": { "type": "string" },
              "latencyMs": { "type": "integer" }
            }
          }
        ]
      },
      "AnalyseRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": ["inputMint", "outputMint", "amount"],
        "properties": {
          "inputMint": { "$ref": "#/components/schemas/Base58Pubkey" },
          "outputMint": {
            "allOf": [{ "$ref": "#/components/schemas/Base58Pubkey" }],
            "description": "Must differ from inputMint."
          },
          "amount": { "$ref": "#/components/schemas/AtomicAmount" },
          "slippageBps": {
            "type": "integer",
            "minimum": 0,
            "maximum": 10000,
            "description": "Optional. Omit to let the router decide."
          }
        }
      },
      "OptimizeRequest": {
        "allOf": [
          { "$ref": "#/components/schemas/AnalyseRequest" },
          {
            "type": "object",
            "required": ["taker"],
            "properties": {
              "taker": {
                "allOf": [{ "$ref": "#/components/schemas/Base58Pubkey" }],
                "description": "The mainnet wallet that will sign and submit. Not the wallet that pays for the call. Must hold the input amount and SOL for the network fee, priority fee and token account rent, except on a gasless Jupiter Ultra fill; otherwise the call returns 422 `taker_insufficient_balance`, uncharged. Only the public key is needed: any funded mainnet address can be passed to see a build-only plan and simulation."
              },
              "analysisId": {
                "type": "string",
                "maxLength": 128,
                "description": "Optional. The `analysisId` from /analyse-token or /analyse for this `outputMint`, within 60 s of issue. Reuses that token scan (authorities, holders, RugCheck, launch platform, age, creator history, bundled-launch check); the quote, price impact, sandwich exposure and congestion are always fetched fresh. A missing, expired or unknown ID means a full scan, never an error. An ID for a different mint is rejected with 400 `analysis_mint_mismatch`, uncharged."
              },
              "method": {
                "type": "string",
                "enum": ["jito"],
                "description": "Optional. `jito` forces a Jito bundle venue (raydium_jito or jupiter_swap_jito), even where risk scoring would not bundle. Only bundle venues are considered, ranked by net value after tip, and no unbundled route is substituted if none builds. `plan.choice` is then `forced`. Any other value is 400."
              }
            }
          }
        ]
      },
      "AnalyseResponse": {
        "allOf": [
          { "$ref": "#/components/schemas/Provenance" },
          {
            "type": "object",
            "properties": {
              "verdict": { "$ref": "#/components/schemas/Verdict" },
              "analysisId": { "$ref": "#/components/schemas/AnalysisId" },
              "analysisExpiresAt": { "$ref": "#/components/schemas/AnalysisExpiresAt" },
              "confidence": { "type": ["string", "null"], "description": "Experimental field." },
              "reasons": { "type": "array", "items": { "type": "string" } },
              "recommendedExecution": {
                "type": "object",
                "description": "Stable fields. How to execute if you proceed.",
                "properties": {
                  "path": {
                    "type": "string",
                    "description": "jupiter_direct on Safe, jito_bundle on Protect, none on Refuse.",
                    "enum": ["jupiter_direct", "jito_bundle", "none"]
                  },
                  "priorityFeeLamports": { "type": ["integer", "null"], "description": "Null means omit the parameter and let the venue size it." },
                  "jitoTipLamports": { "type": ["integer", "null"], "description": "Non-null only when path is jito_bundle. A tip outside a bundle buys nothing." }
                }
              },
              "overallRisk": { "$ref": "#/components/schemas/RiskBlock" },
              "executionRisk": { "$ref": "#/components/schemas/RiskBlock" },
              "tokenRisk": { "$ref": "#/components/schemas/TokenRisk" },
              "sandwichExposure": { "$ref": "#/components/schemas/SandwichExposure" },
              "route": {
                "type": "object",
                "description": "Degrades to available:false when routing data is unavailable.",
                "properties": {
                  "type": { "type": ["string", "null"] },
                  "swapType": { "type": ["string", "null"] },
                  "hops": { "type": ["integer", "null"], "description": "Sequential swaps in the route. A hop split across several pools counts once; see legs." },
                  "legs": { "type": ["integer", "null"], "description": "Pool legs in the route plan. Above hops when a hop is split across pools." },
                  "split": { "type": ["boolean", "null"], "description": "True when at least one hop is split across pools. Null when the route plan carries no mints to tell." },
                  "priceImpactPct": { "type": ["number", "null"] },
                  "slippageBps": { "type": ["integer", "null"] },
                  "inAmount": { "type": ["string", "null"] },
                  "outAmount": { "type": ["string", "null"] },
                  "inUsdValue": { "type": ["number", "null"] },
                  "outUsdValue": { "type": ["number", "null"] },
                  "available": { "type": "boolean" },
                  "reason": { "type": "string" }
                }
              },
              "marketContext": { "type": ["object", "null"] },
              "degraded": {
                "type": ["array", "null"],
                "items": { "type": "string" },
                "description": "Null when all three sub-analyses (token screening, route, sandwich exposure) produced a result, even if some of the sixteen token checks came back unknown: per-check coverage is in signals_resolved, not here. Otherwise a non-empty array of \"source:reason\" strings naming what did not. Test for null rather than comparing against true. Check it before trusting the verdict at full confidence.",
                "examples": [["token_screening:unavailable", "sandwich_exposure:unavailable"]]
              },
              "disclaimer": { "type": "string" },
              "latencyMs": { "type": "integer" },
              "replayed": { "$ref": "#/components/schemas/Replayed" }
            },
            "required": ["verdict", "recommendedExecution", "reasons", "disclaimer"]
          }
        ]
      },
      "OptimizeResponse": {
        "allOf": [
          { "$ref": "#/components/schemas/Provenance" },
          {
            "type": "object",
            "properties": {
              "analysis": {
                "type": "object",
                "description": "Experimental. Where this response's token analysis came from, so reuse is stated rather than assumed.",
                "properties": {
                  "source": {
                    "type": "string",
                    "enum": ["reused", "cached", "full_scan"],
                    "description": "reused = your analysisId was valid and its scan was reused. cached = no usable ID, but a completed scan of this mint from the last 60 s (by any caller) was used. full_scan = a new scan was run for this call."
                  },
                  "reason": {
                    "type": ["string", "null"],
                    "enum": [null, "no_analysis_id", "analysis_id_expired", "analysis_id_unknown"],
                    "description": "Why the analysisId was not used; null when it was. analysis_id_unknown includes IDs issued before a server restart."
                  },
                  "analysisId": { "type": ["string", "null"], "description": "The ID of the scan actually used, reusable until expiresAt. Null if the scan did not complete." },
                  "expiresAt": { "type": ["string", "null"], "format": "date-time" },
                  "observedAt": { "type": ["string", "null"], "format": "date-time", "description": "When the token scan completed." },
                  "ageSeconds": { "type": ["integer", "null"], "description": "Age of the token scan at response time; at most 60 when reused or cached." }
                }
              },
              "overallRisk": { "$ref": "#/components/schemas/RiskBlock" },
              "executionRisk": { "$ref": "#/components/schemas/RiskBlock" },
              "verdict": {
                "oneOf": [{ "$ref": "#/components/schemas/Verdict" }, { "type": "null" }],
                "description": "The same verdict /analyse gives for this trade. The transaction is built even on Refuse; do not sign it unless you mean to trade against the verdict."
              },
              "confidence": { "type": ["string", "null"], "description": "Experimental field." },
              "reasons": { "type": "array", "items": { "type": "string" } },
              "tokenRisk": {
                "$ref": "#/components/schemas/TokenRisk",
                "description": "Reduced here: /optimize returns mint, symbol, score and level only. The factors array and dataSource are carried by /analyse and /analyse-token, not by this endpoint. An available:false object means the token was never screened."
              },
              "sandwichExposure": {
                "$ref": "#/components/schemas/SandwichExposure",
                "description": "Reduced here: /optimize returns score, level, the two estimatedLoss fields and confidence. The factors array is carried by /analyse, not by this endpoint."
              },
              "plan": {
                "type": "object",
                "description": "The structured plan to verify the transaction bytes against before signing.",
                "properties": {
                  "venue": {
                    "type": "string",
                    "description": "jupiter_ultra or jupiter_swap under risk-based selection; raydium when the verdict is Safe and its direct quote beats that Jupiter route after costs; raydium_jito or jupiter_swap_jito (a Jito bundle) on any verdict when the bundle beats the Jupiter route after its tip and landing risk, or when the request sent method: jito (see venueDecision and choice). Other venue names appear only when a venue was forced through the authenticated debug override, in which case `override` is non-null. Treat unrecognised values as unknown, not as an error.",
                    "examples": ["jupiter_ultra", "jupiter_swap", "raydium", "raydium_jito", "jupiter_swap_jito"]
                  },
                  "choice": {
                    "type": "string",
                    "enum": ["selected", "forced"],
                    "description": "selected = ZendIQ chose the venue. forced = the request forced it (method: jito, or the debug override); `override` says how, and the plan is not a risk-derived routing decision."
                  },
                  "venueLabel": { "type": "string" },
                  "venueFallback": {
                    "type": ["string", "null"],
                    "description": "Null normally. When the venue that was chosen could not build, this carries its error and `venue` is the one that was built instead: the Jupiter route, then jupiter_ultra. A fallback only ever moves toward the Jupiter route; a direct venue is built only when it won the comparison. If the Jupiter route could not be priced and Ultra cannot be built, the call fails with optimize_unavailable (uncharged) rather than substituting an unprotected route. Whatever the chosen venue would have provided was not applied; read `mevProtection` and `reason` for the venue actually built."
                  },
                  "venueDecision": {
                    "type": "object",
                    "description": "Experimental. The cross-venue comparison behind `venue`. netUsd = outUsd + unspentInputUsd − priorityFeeUsd − tipUsd − expectedMevLossUsd − landingRiskUsd, per candidate. A direct venue wins only when it beats the Jupiter baseline by more than marginUsd; Jupiter wins ties and anything unpriced. Unprotected raydium competes only on a Safe verdict. Jito bundle venues compete on every verdict, except that on Safe they step aside while ZendIQ's shared Jito submission budget is more than half used.",
                    "properties": {
                      "evaluated": { "type": "boolean" },
                      "basis": { "type": "string", "enum": ["net_benefit", "unavailable_no_usd_rates", "unavailable_baseline_unpriced", "not_evaluated_venue_forced", "forced_method_ranked", "forced_method_unpriced"], "description": "forced_method_* = method: jito; bundle venues were ranked against each other only, with no baseline." },
                      "baseline": { "type": ["string", "null"], "description": "The Jupiter venue risk scoring selected. Null under method: jito." },
                      "chosen": { "type": "string", "description": "The ranking's winner. If it could not be built, `plan.venue` differs and `plan.venueFallback` says why." },
                      "marginUsd": { "type": "number", "description": "0.1% of the trade, capped at $1. The share absorbs quote-to-fill noise, which grows with trade size; past $1 a gap between venues is a real price difference. No fixed dollar floor." },
                      "advantageUsd": { "type": ["number", "null"], "description": "Winner's netUsd minus the baseline's; null when the baseline was kept." },
                      "candidates": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "venue": { "type": "string" },
                            "status": { "type": "string", "enum": ["quoted", "ineligible", "failed", "unknown", "build_failed"], "description": "unknown = the quote was slower than 2.5 s or a cost could not be priced; it does not compete. build_failed = it won but could not be built (see buildError), so the next venue was built." },
                            "buildError": { "type": "string", "description": "Present on build_failed, for example a route too large to carry a Jito tip under the 1,232-byte limit." },
                            "reason": { "type": ["string", "null"], "description": "Why the venue is ineligible." },
                            "error": { "type": ["string", "null"] },
                            "inAmount": { "type": "string" },
                            "outAmount": { "type": "string" },
                            "outUsd": { "type": "number" },
                            "unspentInputUsd": { "type": "number", "description": "Input the venue did not spend, which stays in the wallet." },
                            "priorityFeeUsd": { "type": "number" },
                            "tipUsd": { "type": "number" },
                            "expectedMevLossUsd": { "type": "number", "description": "The modelled sandwich cost (sandwichExposure.estimatedLossUsd), charged only to routes exposed to the public mempool. An estimate, not a measurement." },
                            "landingRiskUsd": { "type": "number", "description": "Charged only to Jito bundle venues: an assumed 5% not-landed rate times the /optimize fee, the cost of rebuilding after a bundle that does not land. Nothing moves when a bundle does not land." },
                            "netUsd": { "type": ["number", "null"] }
                          }
                        }
                      },
                      "note": { "type": "string" }
                    }
                  },
                  "override": {
                    "type": ["object", "null"],
                    "description": "Null unless the caller forced a Jito bundle with method: jito. When present, this plan does not reflect risk-based routing.",
                    "properties": {
                      "forcedVenue": { "type": "null" },
                      "method": { "type": ["string", "null"], "examples": ["jito"] },
                      "riskVenue": { "type": "string" },
                      "enabledBy": { "type": "string", "enum": ["request"] },
                      "warning": { "type": "string" }
                    }
                  },
                  "bundle": { "type": "boolean" },
                  "mevProtection": {
                    "type": "string",
                    "description": "upstream = the venue provides MEV protection (Ultra). jito_bundle = the transaction carries a Jito tip and is submitted through /v1/agent/bundle to Jito's block engine rather than the public mempool; it lands whole or not at all. none = the route is exposed like any pool swap; a priority fee buys inclusion, not protection.",
                    "enum": ["upstream", "jito_bundle", "none"]
                  },
                  "jito": {
                    "type": ["object", "null"],
                    "description": "Present on bundle venues. The tip is the last instruction of the swap transaction: a SystemProgram transfer from the fee payer to tipAccount.",
                    "properties": {
                      "tipLamports": { "type": "integer", "description": "At least 10,000. Above that, sized by the risk model up to 500,000; tipSource says which." },
                      "tipUsd": { "type": ["number", "null"] },
                      "tipAccount": { "type": "string" },
                      "tipSource": { "type": ["string", "null"], "enum": ["zendiq_risk_model", "bundle_tip_floor", null] },
                      "tipInstruction": { "type": "string" },
                      "bytes": { "type": "integer", "description": "Size of the transaction with the tip." },
                      "headroomBytes": { "type": "integer", "description": "1,232 minus bytes." },
                      "lookupTable": {
                        "type": ["object", "null"],
                        "description": "jupiter_swap_jito only. The frozen ZendIQ address lookup table (HHhmPLodSmawV1EtMCNBiQTHdYpECQB2X7w1MbM6pdEw), verified at server start to be frozen, active and hold exactly its 17 protocol-wide addresses. A table that fails that check makes jupiter_swap_jito refuse to build.",
                        "properties": {
                          "address": { "type": "string" },
                          "frozen": { "const": true },
                          "active": { "const": true },
                          "used": { "type": "boolean", "description": "Whether the transaction resolves accounts through it. When compiling without it is smaller, the smaller transaction is kept." },
                          "verifiedAtSlot": { "type": ["integer", "null"], "description": "Slot of the start-up verification." },
                          "verifiedAt": { "type": "string", "format": "date-time" },
                          "writtenInFull": {
                            "type": "object",
                            "description": "Non-signer, non-program accounts no table resolved. An account outside the table costs 32 bytes and never causes a refusal.",
                            "properties": { "accounts": { "type": "integer" }, "bytes": { "type": "integer" } }
                          }
                        }
                      }
                    }
                  },
                  "transaction": {
                    "type": "object",
                    "description": "Experimental. Read from the returned transaction's own bytes, so the venue and protection claims can be checked rather than trusted. A transaction whose programs contradict plan.venue, a bundle with no Jito tip, or a transaction whose venue cannot be verified (from its programs, or on jupiter_ultra from its provenance) is never returned: the call fails with optimize_unavailable and is not charged.",
                    "properties": {
                      "programs": { "type": "array", "items": { "type": "string" }, "description": "Top-level program ids invoked by the transaction, including a third-party router's." },
                      "bytes": { "type": "integer", "description": "Size of the returned transaction." },
                      "venueVerified": { "type": "boolean", "description": "Always true in a returned plan. A transaction whose venue cannot be verified is refused, never returned with false." },
                      "verifiedBy": { "type": "string", "enum": ["programs", "provenance"], "description": "How the venue was verified. programs: a program belonging to plan.venue is present. provenance: jupiter_ultra only, when Ultra filled through a third-party router (plan.route.router) whose programs are not published; the bytes are exactly the transaction Ultra returned for this requestId, the taker signs it, and Ultra /execute only lands that transaction. Provenance never excuses undecodable bytes or a bundle without a tip." },
                      "jitoTip": {
                        "type": ["object", "null"],
                        "description": "A SystemProgram transfer to a Jito tip account inside the transaction, or null.",
                        "properties": { "lamports": { "type": "integer" }, "account": { "type": "string" } }
                      },
                      "note": { "type": "string" }
                    }
                  },
                  "priorityFee": { "$ref": "#/components/schemas/PriorityFee" },
                  "jitoTipLamports": { "type": "integer" },
                  "slippageBps": { "type": ["integer", "null"] },
                  "route": {
                    "type": "object",
                    "properties": {
                      "type": { "type": ["string", "null"] },
                      "swapType": { "type": ["string", "null"] },
                      "router": { "type": ["string", "null"], "description": "jupiter_ultra only. The router Ultra filled the order through, as Ultra names it (for example metis, jupiterz, okx)." },
                      "hops": { "type": ["integer", "null"], "description": "Sequential swaps in the route. A hop split across several pools counts once; see legs." },
                      "legs": { "type": ["integer", "null"], "description": "Pool legs in the route plan. Above hops when a hop is split across pools." },
                      "split": { "type": ["boolean", "null"], "description": "True when at least one hop is split across pools. Null when the route plan carries no mints to tell." },
                      "inAmount": { "type": ["string", "null"], "description": "The amount actually quoted. On raydium with a SOL input this is below the requested amount by solReserveLamports." },
                      "outAmount": { "type": ["string", "null"] },
                      "priceImpactPct": { "type": ["number", "null"] },
                      "requestedInAmount": { "type": "string", "description": "raydium only. The amount you asked to swap." },
                      "solReserveLamports": { "type": "integer", "description": "raydium only. SOL held back from the input to cover fees and rent; it stays in the wallet." },
                      "reserveNote": { "type": ["string", "null"], "description": "raydium only. Why solReserveLamports was held back, present whenever it is above 0. A wallet that holds less SOL than the requested amount is refused rather than built." },
                      "minimumOutAmount": { "type": "string", "description": "raydium only. The slippage floor encoded in the transaction." }
                    }
                  },
                  "reason": { "type": "string", "description": "Experimental. Why this venue was built. On a route with no MEV protection (raydium, jupiter_swap) it also states the modelled sandwich cost and the worst case: the loss if the trade is sandwiched, bounded by the slippage floor in the transaction." }
                }
              },
              "submit": {
                "type": "object",
                "description": "How to submit the signed transaction. Differs by venue — branch on `method`, never on a venue guess.",
                "properties": {
                  "method": {
                    "type": "string",
                    "description": "jupiter_ultra_execute: POST `body` to `url`; broadcasting yourself forfeits Ultra's MEV protection and invalidates netBenefit. rpc_send_transaction: send to your own Solana RPC; the priority fee is already inside. jito_bundle: POST { signedTransaction } to `path` on this API (free); ZendIQ forwards it to Jito under its whitelisted key. Never send a jito_bundle transaction to an RPC or a block engine yourself.",
                    "enum": ["jupiter_ultra_execute", "rpc_send_transaction", "jito_bundle"]
                  },
                  "path": { "type": "string", "description": "Present on jito_bundle.", "examples": ["/v1/agent/bundle"] },
                  "url": { "type": "string", "description": "Present on jupiter_ultra_execute.", "examples": ["https://lite-api.jup.ag/ultra/v1/execute"] },
                  "requestId": { "type": ["string", "null"] },
                  "body": { "type": "string", "description": "Present on jupiter_ultra_execute.", "examples": ["{ signedTransaction, requestId }"] },
                  "lastValidBlockHeight": { "type": ["integer", "null"], "description": "Present on rpc_send_transaction and jito_bundle. Null when the venue embedded a blockhash whose remaining validity is unknown — send promptly." },
                  "note": { "type": "string" }
                },
                "required": ["method"]
              },
              "netBenefit": {
                "type": "object",
                "description": "Every cost itemised so the stated net can be checked rather than trusted.",
                "properties": {
                  "currency": { "const": "USD" },
                  "expectedMevLossUsd": { "type": ["number", "null"], "description": "The modelled sandwich cost for this trade. An estimate, not a measurement." },
                  "priorityFee": {
                    "type": ["string", "number", "null"],
                    "description": "A string on jupiter_ultra, where the fee is venue-managed; its decoded amount is in plan.priorityFee. On other venues, the applied fee in USD, or null when no SOL price was available."
                  },
                  "jitoTipUsd": { "type": ["number", "null"], "description": "0 when no tip is paid. Null when a tip is paid but no SOL price was available." },
                  "priorityFeeUsd": { "type": ["number", "null"], "description": "The priority fee your taker pays, decoded from the transaction and priced in USD. 0 on a bundle (none is set) and on a gasless fill (Jupiter pays it). Null when it could not be read or priced." },
                  "jupiterPlatformFeeUsd": { "type": ["number", "null"], "description": "Jupiter's own fee on this trade, in USD. On jupiter_ultra it is the fee Ultra reports (0–50 bps by pair; for example 2 bps on SOL–USDC). It is already inside the quoted amounts, so it is not deducted from the route comparison again. 0 on every other venue, where no Jupiter fee applies. Null when Jupiter did not report it or it could not be priced: unknown, never zero." },
                  "jupiterPlatformFee": {
                    "type": ["object", "null"],
                    "description": "How jupiterPlatformFeeUsd was derived.",
                    "properties": {
                      "feeBps": { "type": ["number", "null"] },
                      "feeMint": { "type": ["string", "null"] },
                      "side": { "type": ["string", "null"], "enum": ["input", "output", null], "description": "Which leg of the trade the fee is taken from." },
                      "amount": { "type": ["string", "null"], "description": "Raw fee amount in feeMint's atomic units." },
                      "usd": { "type": ["number", "null"] },
                      "source": { "type": "string", "enum": ["quote", "not_applicable"] },
                      "note": { "type": "string" }
                    }
                  },
                  "zendiqFeeUsd": { "type": ["number", "null"] },
                  "netUsd": { "type": ["number", "null"], "description": "netUsd = expectedMevLossUsd − zendiqFeeUsd − jitoTipUsd − jupiterPlatformFeeUsd − priorityFeeUsd: the sandwich loss this route avoids, less every fee you pay to execute it. Stated only on routes that claim MEV protection (jupiter_ultra and the Jito bundle venues). The 5,000-lamport base signature fee is paid on every route alike and is not included. Read netUsdBasis before treating null as a failure." },
                  "netUsdBasis": {
                    "type": "string",
                    "description": "Why netUsd has the value it does. not_claimed_on_this_route = null by design: the route provides no MEV protection, so no avoided-loss credit is claimed. unavailable_no_mev_estimate = no sandwich estimate was available. unavailable_no_sol_price = a bundle's Jito tip could not be priced. unavailable_platform_fee = Jupiter's fee could not be priced. unavailable_priority_fee = the priority fee could not be read from the transaction or priced. In every unavailable case no net is stated.",
                    "enum": ["computed", "unavailable_no_mev_estimate", "not_claimed_on_this_route", "unavailable_no_sol_price", "unavailable_platform_fee", "unavailable_priority_fee"]
                  },
                  "note": { "type": "string" }
                }
              },
              "simulation": {
                "type": "object",
                "description": "An 'unknown' simulation is not a passing simulation.",
                "properties": {
                  "status": { "type": "string", "enum": ["ok", "failed", "unknown"] },
                  "err": { "type": ["object", "string", "null"] },
                  "unitsConsumed": { "type": ["integer", "null"] },
                  "logs": { "type": ["array", "null"], "items": { "type": "string" }, "description": "Last 10 log lines." },
                  "reason": { "type": "string", "description": "Present when status is unknown." }
                }
              },
              "transaction": {
                "type": "string",
                "contentEncoding": "base64",
                "description": "Unsigned VersionedTransaction. ZendIQ never sees a key. On a jito_bundle venue ZendIQ broadcasts it after you sign it, and only those exact bytes."
              },
              "requestId": { "type": ["string", "null"], "description": "Always present. Non-null only on jupiter_ultra, where it is required for /execute." },
              "custody": { "type": "string", "examples": ["none — transaction is unsigned; sign and submit with your own wallet"] },
              "latencyMs": { "type": "integer" },
              "replayed": { "$ref": "#/components/schemas/Replayed" }
            },
            "required": ["plan", "submit", "netBenefit", "transaction", "requestId", "custody"]
          }
        ]
      },
      "BundleSubmission": {
        "type": "object",
        "properties": {
          "signature": { "type": "string" },
          "bundleId": { "type": ["string", "null"] },
          "acceptedBy": { "type": ["string", "null"], "description": "The Jito region that accepted the bundle.", "examples": ["slc"] },
          "state": {
            "type": "string",
            "enum": ["landed", "failed_on_chain", "pending", "not_landed", "unknown", "rejected"],
            "description": "landed = on chain. failed_on_chain = included, but the swap failed (err); it did not execute. pending = accepted, not yet on chain, blockhash still valid: poll the GET, do not sign a new transaction yet. not_landed = the blockhash expired and the signature is not on chain; nothing was charged. unknown = landing could not be confirmed either way; it may have landed, so poll before retrying. rejected = every block engine refused it (errors); it cannot land."
          },
          "slot": { "type": ["integer", "null"] },
          "err": { "type": ["object", "string", "null"] },
          "jitoTip": { "type": "object", "properties": { "lamports": { "type": "integer" }, "account": { "type": "string" } } },
          "submittedAt": { "type": "string", "format": "date-time" },
          "replayed": { "type": "boolean", "description": "True when this is the first submission's record, returned to a repeat post or a status read." },
          "errors": { "type": ["array", "null"], "items": { "type": "string" } },
          "diagnosis": {
            "type": ["object", "null"],
            "description": "Set only when state is not_landed: Jito's view of why. Informational; landing is judged on chain. A source that could not be read is listed in `errors` and its fields are null, never guessed.",
            "properties": {
              "jitoStatus": { "type": ["string", "null"], "enum": ["Pending", "Landed", "Failed", "Invalid", null], "description": "From getInflightBundleStatuses on the accepting region. Invalid = not in Jito's 5-minute tracker." },
              "jitoNote": { "type": ["string", "null"] },
              "region": { "type": ["string", "null"] },
              "landedSlot": { "type": ["integer", "null"] },
              "tipLamports": { "type": ["integer", "null"] },
              "tipFloorLamports": { "type": ["object", "null"], "description": "Recent landed-tip percentiles.", "properties": { "p25": { "type": "integer" }, "p50": { "type": "integer" }, "p75": { "type": "integer" } } },
              "tipBelowMedian": { "type": ["boolean", "null"] },
              "errors": { "type": "array", "items": { "type": "string" } }
            }
          },
          "note": { "type": "string" }
        },
        "required": ["signature", "state", "replayed"]
      },
      "ServiceManifest": {
        "type": "object",
        "properties": {
          "service": { "const": "zendiq-agent-api" },
          "version": { "type": "string" },
          "network": { "type": "string", "examples": ["mainnet"] },
          "terms": { "type": "string", "format": "uri", "description": "The API terms that paying for a call accepts." },
          "endpoints": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "method": { "type": "string" },
                "path": { "type": "string" },
                "priceUsd": { "type": "number" },
                "settles": { "type": "string" },
                "description": { "type": "string" }
              }
            }
          },
          "compatibility": {
            "type": "object",
            "properties": {
              "version": { "type": "string" },
              "additiveOnly": { "type": "boolean" },
              "notes": { "type": "array", "items": { "type": "string" } },
              "fields": {
                "type": "object",
                "properties": {
                  "stable": { "type": "array", "items": { "type": "string" } },
                  "experimental": { "type": "array", "items": { "type": "string" } }
                }
              }
            }
          },
          "rateLimits": {
            "type": "object",
            "properties": {
              "perIpPerMinute": { "type": "integer", "description": "Paid endpoints, per IP." },
              "perPayerPerMinute": { "type": "integer", "description": "Paid endpoints, per paying wallet." },
              "screenPerIpPerMinute": { "type": "integer", "description": "The free /analyse-token, per IP. Counted separately from the paid limits." },
              "note": { "type": "string" }
            }
          },
          "bundleLookupTable": {
            "type": "object",
            "description": "Result of the start-up check on the frozen ZendIQ address lookup table. When verified is false, jupiter_swap_jito refuses to build until a restart verifies it; nothing else is affected.",
            "properties": {
              "address": { "type": ["string", "null"], "examples": ["HHhmPLodSmawV1EtMCNBiQTHdYpECQB2X7w1MbM6pdEw"] },
              "verified": { "type": "boolean", "description": "Frozen, active and holding exactly its 17 protocol-wide addresses." },
              "verifiedAtSlot": { "type": ["integer", "null"] },
              "error": { "type": ["string", "null"] }
            }
          },
          "bundleRegions": {
            "type": "object",
            "description": "Result of the start-up check on the Jito block-engine regions bundles are forwarded to. When valid is false, every bundle venue refuses to build and method \"jito\" returns optimize_unavailable, uncharged, until a restart with a valid configuration.",
            "properties": {
              "regions": { "type": "array", "items": { "type": "string" }, "examples": [["ny", "slc"]], "description": "Block engines each bundle is sent to. Empty when valid is false." },
              "valid": { "type": "boolean" },
              "error": { "type": ["string", "null"] }
            }
          },
          "custody": { "type": "string" }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": { "type": "string", "examples": ["invalid_request", "analysis_mint_mismatch", "taker_insufficient_balance", "analysis_unavailable", "optimize_unavailable", "internal_error"] },
          "message": { "type": "string" },
          "causes": { "type": "array", "items": { "type": "string" }, "description": "Present on 502 and on taker_insufficient_balance. The underlying failure of each source or venue." },
          "analysisMint": { "type": "string", "description": "Present on analysis_mint_mismatch: the mint the analysisId was issued for." },
          "outputMint": { "type": "string", "description": "Present on analysis_mint_mismatch: this request's outputMint." },
          "need": { "type": "string", "enum": ["input_token", "sol_for_fees"], "description": "Present on taker_insufficient_balance: what the taker is short of." },
          "taker": { "type": "string", "description": "Present on taker_insufficient_balance." },
          "token": {
            "type": "object",
            "description": "Present on taker_insufficient_balance: the token the taker is short of. SOL on `sol_for_fees`.",
            "properties": {
              "mint": { "type": "string" },
              "symbol": { "type": ["string", "null"] },
              "decimals": { "type": ["integer", "null"] }
            }
          },
          "required": { "type": ["string", "null"], "description": "Present on taker_insufficient_balance: amount needed, in whole tokens as a decimal string. Null when the mint's decimals could not be read; use requiredAtomic." },
          "held": { "type": ["string", "null"], "description": "Present on taker_insufficient_balance: amount the taker holds, in whole tokens." },
          "shortfall": { "type": ["string", "null"], "description": "Present on taker_insufficient_balance: required minus held, in whole tokens." },
          "requiredAtomic": { "type": "string", "description": "Present on taker_insufficient_balance: required, in atomic units." },
          "heldAtomic": { "type": "string", "description": "Present on taker_insufficient_balance: held, in atomic units." },
          "shortfallAtomic": { "type": "string", "description": "Present on taker_insufficient_balance: shortfall, in atomic units." }
        }
      },
      "AnalysisId": {
        "type": ["string", "null"],
        "description": "Experimental. Identifies this token scan for reuse by /optimize until analysisExpiresAt (60 s from the scan, shared with the scan cache). Null when the scan did not complete. Held in server memory: a restart makes it unknown, which costs a full scan, not an error.",
        "examples": ["an_Q2hsZ3v0mK7pX1aRt9Yc"]
      },
      "AnalysisExpiresAt": {
        "type": ["string", "null"],
        "format": "date-time",
        "description": "Experimental. When analysisId stops being reusable. A scan served from the cache keeps its original expiry."
      },
      "RateLimitError": {
        "type": "object",
        "properties": {
          "error": { "const": "rate_limited" },
          "limit": { "type": "string", "enum": ["ip", "payer"] },
          "retryAfterSeconds": { "type": "integer" },
          "message": { "type": "string" }
        }
      },
      "AuthorizationError": {
        "type": "object",
        "properties": {
          "error": { "enum": ["authorization_already_used", "already_served", "redemption_in_progress", "redemption_route_mismatch", "redemption_amount_mismatch", "redemption_expired", "settlement_unresolved"] },
          "message": { "type": "string" },
          "priorOutcome": { "type": "string", "examples": ["in_flight", "settle_threw", "charged_unserved"] },
          "priorRoute": { "type": "string" },
          "charged": { "type": ["boolean", "null"], "description": "On a settlement_pending retry: whether that payment was charged. Null means not yet known." },
          "settlement": { "type": "string", "description": "On a settlement_pending retry: the payment's settlement transaction signature." }
        }
      },
      "Replayed": {
        "type": "boolean",
        "const": true,
        "description": "Present only when this 200 is the cached result of a payment authorization that already settled. Same body and PAYMENT-RESPONSE as the original; you were not charged again."
      },
      "PriorityFee": {
        "type": "object",
        "description": "An object on every venue. Read `control` before any number: it states what requestedLamports means. appliedLamports is read back from the built transaction and is the only verified charge.",
        "properties": {
          "source": { "type": ["string", "null"], "description": "Null when no fee was requested.", "enum": ["zendiq_risk_model", "trade_size_cap", "debug_override_default", null] },
          "control": {
            "type": "string",
            "description": "venue_managed = the venue sized it (always on jupiter_ultra). ceiling = requestedLamports is an upper bound the venue may undercut. exact_budget = requestedLamports is spent in full, rounded up to the compute-unit price.",
            "enum": ["venue_managed", "ceiling", "exact_budget"]
          },
          "requestedLamports": { "type": ["integer", "null"] },
          "appliedLamports": { "type": ["integer", "null"], "description": "The priority fee decoded from the built transaction's ComputeBudget instructions. Null only when those could not be read, in which case nothing in this object is a verified charge." },
          "appliedUsd": { "type": ["number", "null"] },
          "microLamports": { "type": ["integer", "null"] },
          "computeUnitLimit": { "type": ["integer", "null"] },
          "feePayer": { "type": ["string", "null"], "description": "The transaction's fee payer, decoded from the bytes. When it is not your taker (a gasless jupiter_ultra fill), Jupiter pays the decoded fee, not your wallet." },
          "solPriceUsd": { "type": ["number", "null"] },
          "solPriceSource": { "type": ["string", "null"] },
          "note": { "type": "string" }
        },
        "required": ["control"]
      }
    }
  }
}
