{"openapi":"3.1.0","info":{"title":"CipherExplain","description":"Privacy-preserving explainable AI — encrypted predictions with encrypted SHAP explanations.\n\n**Authentication:** pass your API key in the `X-API-Key` header on every request except `/health`.\n\n**Get a key:** https://vaultbytes.com/cipherexplain","version":"0.8.0"},"paths":{"/health":{"get":{"tags":["Status"],"summary":"Health check","description":"Returns the service status.\n\nNo API key required. Safe to use as a liveness probe.","operationId":"health_health_get","responses":{"200":{"description":"Service status","content":{"application/json":{"schema":{}}}}}}},"/handshake":{"post":{"tags":["Status"],"summary":"Open a signed session binding (Merkle + Rekor)","description":"Open a signed, audit-mode session binding.\n\nCommits the client's session inputs to a SHA-256 Merkle root,\ncountersigns the root, and publishes it to a transparency log when\ntransparency logging is enabled for the deployment.\n\nResponse is a JSON-serialised :class:`SessionArtifacts` bundle — the SDK\ncaches it and feeds ``merkle_root`` into every subsequent per-query\ntranscript as ``session_root``.","operationId":"handshake_handshake_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HandshakeRequest"}}},"required":true},"responses":{"200":{"description":"Session artifacts: Merkle root, countersignature, Rekor uuid","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/models":{"get":{"tags":["Models"],"summary":"List loaded models","description":"Returns metadata for all models available to the caller: their own\nregistered models plus the shared global demo models.\n\nRequires `X-API-Key` header.\n\n**Response fields per model**\n- `model_id` — identifier used in `/explain` requests\n- `feature_count` — number of input features expected\n- `coalition_count` — number of SHAP coalitions used\n- `feature_names` — ordered list of feature names matching the input array\n- `namespace` — `\"yours\"` for your own models, `\"global\"` for demo models","operationId":"list_models_models_get","responses":{"200":{"description":"List of models available to the caller","content":{"application/json":{"schema":{}}}}}}},"/startup":{"post":{"tags":["Models"],"summary":"Load demo credit model","description":"Downloads the UCI Adult dataset, trains a logistic regression classifier,\nand registers it as `credit_model` — ready for `/explain` calls.\n\nRequires `X-API-Key` header. Safe to call multiple times (re-trains in place).\n\n**Features (5 inputs, in order)**\n1. `age` — age in years\n2. `education-num` — years of education (numeric)\n3. `capital-gain` — capital gain (USD)\n4. `capital-loss` — capital loss (USD)\n5. `hours-per-week` — hours worked per week\n\nFeatures are automatically StandardScaler-normalised when registered.\nUse `/explain` with pre-scaled values or `/explain_raw` with raw values.","operationId":"startup_startup_post","responses":{"200":{"description":"Confirmation that the demo model was trained and registered","content":{"application/json":{"schema":{}}}}}}},"/models/register":{"post":{"tags":["Models"],"summary":"Register your own model","description":"Register a model using your own pre-trained weights (no training data sent).\n\nRequires `X-API-Key` header.\n\n**Slot limits by tier**\n- `free` — 1 model\n- `developer` — 10 models\n- `enterprise` — unlimited\n\n**Request body** — a `ModelSpec` JSON object:\n- `model_id` — unique identifier for the model (`^[A-Za-z0-9_\\-.]+$`, max 128 chars)\n- `model_type` — one of `\"logistic_regression\"`, `\"linear_svc\"`, `\"mlp\"`,\n  `\"decision_tree\"`, `\"random_forest\"`, `\"gradient_boosting\"`,\n  `\"xgboost\"`, `\"lightgbm\"`, `\"catboost\"`\n- `feature_names` — list of feature name strings (1–512 features)\n- `scaler` *(optional)* — `{\"type\":\"standard\",\"mean\":[...],\"scale\":[...]}` for auto-scaling on `/explain_raw`\n- `baseline` *(optional)* — per-feature baseline values for SHAP reference point\n\n**`spec` field by model_type**\n\n*Linear models* (`logistic_regression`, `linear_svc`):\n- `spec.coef` — 2-D array of coefficients, shape `(n_classes_or_1, n_features)`\n- `spec.intercept` — list of intercept values, shape `(n_classes_or_1,)`\n- `spec.classes` — list of class labels (integers)\n\n*Tree ensembles* (`random_forest`, `gradient_boosting`):\n- `spec.estimators` — list of serialised decision trees; use `cipherexplain_eval.serialize_model()`\n  to build this payload from a fitted sklearn estimator\n- `spec.is_classifier` — `true` for classifiers, `false` for regressors\n\n*Single tree* (`decision_tree`):\n- Same fields as a single entry in `spec.estimators` above\n\n**SHAP for tree models**: `fhe_mode` is ignored for `random_forest`/`gradient_boosting`/`decision_tree`;\nthe engine always runs the plaintext KernelSHAP algorithm.\n\n**Errors**\n- `401` — missing key\n- `403` — invalid key\n- `409` — a model with this `model_id` already exists; delete it first\n- `429` — model slot limit reached for your tier","operationId":"register_model_models_register_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterModelRequest"}}},"required":true},"responses":{"200":{"description":"Confirmation and metadata for the registered model","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterModelResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/models/{model_id}":{"delete":{"tags":["Models"],"summary":"Delete a registered model","description":"Remove a model you previously registered. This frees up a model slot.\n\nRequires `X-API-Key` header. You can only delete models registered\nunder your own key — global demo models cannot be deleted.\n\n**Errors**\n- `401` — missing key\n- `403` — invalid key\n- `404` — model not found under your key","operationId":"delete_model_models__model_id__delete","parameters":[{"name":"model_id","in":"path","required":true,"schema":{"type":"string","maxLength":128,"pattern":"^[A-Za-z0-9_\\-.]+$","title":"Model Id"}}],"responses":{"200":{"description":"Confirmation of deletion","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/models/{model_id}/commitment":{"get":{"tags":["Models"],"summary":"Fetch the FreiKZG-SHAP commitment for a model","description":"Return the FreiKZG-SHAP commitment for a registered model.\n\nClients fetch this once per model and cache it locally; every\nsubsequent `/explain` response's `proof` field is verified against this\ncached commitment.\n\nResponse shape:\n\n    {\n      \"model_id\":          \"<id>\",\n      \"frei_kzg_enabled\":  <bool>,\n      \"commitment\":        <serialized ModelCommitment or null>,\n    }\n\nWhen `frei_kzg_enabled` is False (the model predates the commitment,\n`CE_FREI_KZG_ENABLED` was unset at registration, or commitment\ncomputation failed) the `commitment` field is null. Existing API\ncallers who ignore unknown fields are unaffected.\n\n**Errors**\n- `401` — missing X-API-Key\n- `403` — invalid key\n- `404` — model not found under your key or the global namespace","operationId":"get_model_commitment_models__model_id__commitment_get","parameters":[{"name":"model_id","in":"path","required":true,"schema":{"type":"string","maxLength":128,"pattern":"^[A-Za-z0-9_\\-.]+$","title":"Model Id"}}],"responses":{"200":{"description":"Serialized ModelCommitment (SRS + row commitments + M)","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/models/{model_id}/attestation":{"get":{"tags":["Models"],"summary":"Re-fetch the safety attestation a buyer can verify offline","description":"Return the registration-time safety attestation for a model.\n\nA buyer registers a model once and gets back ``oms_root`` + version\nmetadata in the registration response. This endpoint lets them\nre-fetch the same attestation later — for periodic audits, for\nproving to a regulator that the deployed model is the one they\nregistered, or for confirming that nothing about the model\nchanged silently.\n\nResponse shape:\n\n    {\n      \"model_id\":              \"<id>\",\n      \"model_type\":            \"logistic_regression\" | ...,\n      \"feature_count\":         <int>,\n      \"n_classes\":             <int>,\n      \"key_namespace_prefix\":  \"<8-char sha256 prefix of caller's key>\",\n      \"version\":               \"<version str>\",\n      \"version_id\":            \"<uuid>\",\n      \"registered_at\":         \"<iso-8601>\",\n      \"spec_hash\":             \"<sha256 of canonical spec>\",\n      \"oms_root\":              \"<hex>\" | null,\n      \"oms_enabled\":           <bool>,\n      \"frei_kzg_enabled\":      <bool>,\n      \"frei_kzg_commitment_present\": <bool>,\n      \"vfhe_binding_active\":   <bool>,\n      \"verification_recipe\":   \"<short string of 4 verifier steps>\",\n    }\n\nThe recipe text is included so a buyer pasting this response into\na procurement audit gets a self-describing artifact — they don't\nneed to read the SDK docs to know what the fields are for.\n\n**Errors**\n- `401` — missing X-API-Key\n- `403` — invalid key\n- `404` — model not found under your key or the global namespace","operationId":"get_model_attestation_models__model_id__attestation_get","parameters":[{"name":"model_id","in":"path","required":true,"schema":{"type":"string","maxLength":128,"pattern":"^[A-Za-z0-9_\\-.]+$","title":"Model Id"}}],"responses":{"200":{"description":"OMS Merkle root, FreiKZG presence, vFHE binding flag, version metadata, spec hash. Combines all the integrity primitives a compliance officer needs into one auditable fetch.","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/models/{model_id}/annex_iv":{"get":{"tags":["Models"],"summary":"Generate the EU AI Act Annex IV technical-documentation dossier","description":"Return the auto-populated Annex IV dossier for a registered model.\n\nQuery parameters:\n    fmt: ``md`` (default) or ``pdf``.\n    operator_legal_name, operator_identifier, operator_contact_email:\n        optional deployer overrides for the cover and Declaration of\n        Conformity sections. When omitted the document carries\n        ``[customer-fill: ...]`` placeholders the compliance team\n        fills before submission.\n\nErrors:\n    401/403 — auth\n    404 — model not found under this key / global namespace\n    422 — unsupported ``fmt``","operationId":"get_annex_iv_models__model_id__annex_iv_get","parameters":[{"name":"model_id","in":"path","required":true,"schema":{"type":"string","maxLength":128,"pattern":"^[A-Za-z0-9_\\-.]+$","title":"Model Id"}},{"name":"fmt","in":"query","required":false,"schema":{"type":"string","default":"md","title":"Fmt"}},{"name":"operator_legal_name","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Operator Legal Name"}},{"name":"operator_identifier","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Operator Identifier"}},{"name":"operator_contact_email","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Operator Contact Email"}}],"responses":{"200":{"description":"Markdown (default) or PDF rendering of the Annex IV dossier for the registered model. Sections that depend on the deployer's own process carry `[customer-fill: ...]` placeholders.","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/explain":{"post":{"tags":["Explain"],"summary":"Encrypted SHAP explanation (pre-scaled features)","description":"Runs an encrypted SHAP explanation on a single input vector.\n\nRequires `X-API-Key` header. Counts against your monthly **explain** quota.\n\n**Request body**\n- `model_id` *(string)* — ID of a model. Your own registered models take\n  priority; falls back to global demo models (`credit_model`).\n- `features` *(list of float)* — input values in the same order as\n  `feature_names`, **pre-scaled**. Use `/explain_raw` for raw values.\n\n**Response fields**\n- `prediction` — model output (0.0–1.0 probability)\n- `base_rate` — average prediction across training set (SHAP baseline)\n- `shap_values` — per-feature attribution values (sum ≈ prediction − base_rate)\n- `feature_names` — ordered feature labels matching `shap_values`\n- `metadata` — internal diagnostics (coalition count, epsilon, etc.)\n\n**Errors**\n- `401` — missing `X-API-Key`\n- `403` — invalid or inactive key\n- `404` — model not found\n- `429` — monthly explain quota exceeded","operationId":"explain_explain_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExplainRequest"}}},"required":true},"responses":{"200":{"description":"Prediction, base rate, SHAP values, and feature names","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExplainResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/explain_raw":{"post":{"tags":["Explain"],"summary":"Encrypted SHAP explanation (auto-scaled features)","description":"Same as `/explain` but automatically applies the model's built-in\nStandardScaler before running the SHAP explanation. Use this when\npassing raw feature values.\n\nRequires `X-API-Key` header. Counts against your monthly **explain** quota.\n\n**Request body**\n- `model_id` *(string)* — ID of a loaded model (e.g. `\"credit_model\"`).\n- `features` *(list of float)* — raw input values (e.g. `[38, 13, 0, 0, 40]`).\n  The API scales them automatically if the model has a registered scaler.\n\n**Response fields** — same as `/explain`.\n\n**Errors**\n- `401` — missing `X-API-Key`\n- `403` — invalid or inactive key\n- `404` — model not found\n- `429` — monthly explain quota exceeded","operationId":"explain_raw_explain_raw_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExplainRequest"}}},"required":true},"responses":{"200":{"description":"Prediction, base rate, SHAP values, and feature names","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExplainResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/explain_tree":{"post":{"tags":["Explain"],"summary":"Attested tree-FHE-SHAP via encrypted leaf identity","description":"Tree-attested SHAP for tree ensembles (Random Forest /\nGradientBoosting / sklearn-compatible).\n\nThe server runs an FHE circuit and returns the per-tree reached-leaf\nnid with an integrity-attestation bundle. The SDK helper\n`cipherexplain_sdk.tree_attested.verify_and_explain` runs\n`shap.TreeExplainer` locally against the plaintext feature vector\nand checks the attestation.\n\nLimits: up to 100 trees and depth ≤ 6 are supported. Larger\nensembles return HTTP 422. The client-side verification step\nre-derives the reached leaves against the local plaintext feature\nvector and reconciles any that need it, so the returned SHAP values\nmatch a local ``shap.TreeExplainer`` run.","operationId":"explain_tree_explain_tree_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExplainRequest"}}},"required":true},"responses":{"200":{"description":"Cryptographically-attested per-tree leaf routing. The CLIENT computes the SHAP values locally via shap.TreeExplainer on the (public) model schema; this response certifies the path the model took for the encrypted input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExplainTreeResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/counterfactual":{"post":{"tags":["Explain"],"summary":"Closed-form LR counterfactual with ECOA reason codes","description":"Counterfactual explanation for logistic-regression models.\n\nGiven a prior ``/explain`` session, computes the smallest feature\nchange that would flip the decision, and returns ECOA Form C-1 reason\ncodes derived from the encrypted SHAP attributions.\n\nFlow:\n\n1. The encrypted input from the prior ``/explain`` call is retrieved\n   via ``req.session_token``.\n2. A homomorphic binding check ties the counterfactual to that exact\n   input: the server derives Enc(x') = Enc(x) + Enc(delta) itself\n   rather than accepting a client-supplied Enc(x'), which structurally\n   prevents a client from substituting an unrelated counterfactual.\n   The ``cf_attestation_mode`` field reports the binding assurance\n   level; in ``UNATTESTED`` mode the additional zero-knowledge proof\n   step is structurally validated but not cryptographically verified.\n3. The server runs the same encrypted SHAP / inference path on the\n   derived Enc(x') and returns the decoded delta, x', and Form C-1\n   reason codes from the server-computed attributions.\n\nThe response's ``cf_attestation_mode`` reports the binding assurance\nlevel configured for the deployment; the ``X-CF-Attestation`` response\nheader echoes it for SDK callers.\n\n**Errors**\n- 401/403 — auth\n- 404 — session unknown or expired\n- 422 — binding verification failed\n- 503 — the counterfactual route is not enabled on this deployment","operationId":"counterfactual_counterfactual_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CounterfactualRequest"}}},"required":true},"responses":{"200":{"description":"Counterfactual feature vector x' = x + delta plus ECOA Form C-1 reason codes derived from server-computed SHAP. The response's ``cf_attestation_mode`` field reports the binding assurance level for the deployment.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CounterfactualResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/keys/rotate":{"post":{"tags":["Account"],"summary":"Rotate your API key","description":"Issues a new API key and deactivates the current one. All registered\nmodels are automatically migrated to the new key — no re-registration\nrequired.\n\nRequires `X-API-Key` header.\n\n**Response fields**\n- `new_key` — your new API key. **Save this immediately — it is only shown once.**\n- `models_migrated` — number of registered models moved to the new key\n- `note` — reminder to update all clients with the new key\n\n**Your old key stops working immediately after this call.**","operationId":"rotate_key_keys_rotate_post","responses":{"200":{"description":"New API key and count of models migrated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RotateKeyResponse"}}}}}}},"/account/portal":{"post":{"tags":["Account"],"summary":"Get Stripe Customer Portal URL","description":"Returns a one-time Stripe Customer Portal URL for the authenticated key owner.\n\nUse this to cancel your subscription, update payment details, or download invoices.\nThe link expires after a short time — open it immediately.\n\nRequires `X-API-Key` header.\n\n**Errors**\n- `401` — missing key\n- `403` — invalid key\n- `404` — no Stripe subscription found for this key (free tier)\n- `503` — Stripe not configured","operationId":"customer_portal_account_portal_post","responses":{"200":{"description":"URL to the Stripe Customer Portal (cancel, update billing, invoices)","content":{"application/json":{"schema":{}}}}}}},"/account/payg/enable":{"post":{"tags":["Account"],"summary":"Enable Pay-As-You-Go billing","description":"Creates a Stripe Checkout session (setup mode) so you can save a payment\nmethod for Pay-As-You-Go billing.\n\nOnce PAYG is enabled, calls beyond your monthly quota are automatically\ncharged per call (£0.08/SHAP, £1.50/oracle run) and invoiced via Stripe\nat the end of each month. No action needed — billing is fully automated.\n\nRequires `X-API-Key` header.\n\n**Returns**\n- `url` — redirect the user here to complete card setup\n\n**Errors**\n- `401` — missing key\n- `503` — Stripe not configured","operationId":"payg_enable_account_payg_enable_post","responses":{"200":{"description":"Stripe Checkout URL to save a payment method for PAYG billing","content":{"application/json":{"schema":{}}}}}}},"/account/payg/usage":{"get":{"tags":["Account"],"summary":"PAYG usage and estimated charge","description":"Returns your Pay-As-You-Go usage for the current month — calls made beyond\nyour monthly quota — and the estimated GBP amount that will be invoiced.\n\nStripe pending invoice items are created automatically per call. Stripe\nfinalises and collects the invoice at the end of each billing period.\n\nRequires `X-API-Key` header.\n\n**Response fields**\n- `payg_enabled` — whether PAYG is active on this key\n- `month` — current billing month (`YYYY-MM`)\n- `total_gbp` — estimated charge for this month\n- `breakdown` — per endpoint: call count and GBP amount\n- `rates` — current per-call prices","operationId":"payg_usage_account_payg_usage_get","responses":{"200":{"description":"PAYG calls made beyond quota this month and estimated GBP charge","content":{"application/json":{"schema":{}}}}}}},"/account/payg/limit":{"post":{"tags":["Account"],"summary":"Set a monthly PAYG spend cap","description":"Set or remove a monthly spending cap for Pay-As-You-Go billing.\n\nOnce the cap is reached, further calls beyond your quota are blocked with\nHTTP 429 until the cap is raised or the month resets. This lets you\ncontrol maximum PAYG exposure.\n\nPass `limit_gbp: null` to remove the cap entirely.\n\nRequires `X-API-Key` header.\n\n**Examples**\n- `{\"limit_gbp\": 50}` — stop PAYG at £50 of overage this month\n- `{\"limit_gbp\": null}` — no cap, unlimited PAYG\n\n**Errors**\n- `401` — missing or invalid key\n- `400` — PAYG not enabled (call POST /account/payg/enable first)","operationId":"payg_set_limit_account_payg_limit_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaygLimitRequest"}}},"required":true},"responses":{"200":{"description":"Confirmation of the new spend limit","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/usage":{"get":{"tags":["Account"],"summary":"Monthly usage summary","description":"Returns how many calls you have used and how many remain this calendar\nmonth, split by endpoint type.\n\nRequires `X-API-Key` header.\n\n**Response fields**\n- `month` — current billing month (`YYYY-MM`)\n- `tier` — your plan (`free`, `developer`, `enterprise`)\n- `explain.used` / `explain.limit` / `explain.remaining` — SHAP API quota\n- `oracle.used` / `oracle.limit` / `oracle.remaining` — FHE Testing Oracle quota\n\n**Quota resets on the 1st of each calendar month (UTC).**","operationId":"usage_usage_get","responses":{"200":{"description":"Used and remaining quota for the current month, split by endpoint type","content":{"application/json":{"schema":{}}}}}}},"/usage/dp":{"get":{"tags":["Account"],"summary":"Daily differential-privacy budget usage","description":"Return the caller's daily DP budget and up to 30 days of history.\n\nThe per-key budget caps cumulative epsilon spent across `apply_dp=true`\ncalls in a single UTC day. The counter resets at UTC midnight.\n\nRequires `X-API-Key` header.\n\n**Response fields**\n- `epsilon_limit_daily` — per-day cap in effect for this key\n- `epsilon_spent_today` — sum of epsilon charged to this key since UTC 00:00\n- `epsilon_remaining_today` — `limit - spent` (never negative)\n- `reset_at` — ISO-8601 UTC timestamp of the next midnight\n- `history` — daily rows for up to 30 days, newest first","operationId":"usage_dp_usage_dp_get","responses":{"200":{"description":"Remaining DP budget for today and up to 30 days of history","content":{"application/json":{"schema":{}}}}}}},"/models/{model_id}/versions":{"get":{"tags":["Models"],"summary":"List all versions for a model","description":"Return every registered version for the given model_id under this key.","operationId":"list_model_versions_models__model_id__versions_get","parameters":[{"name":"model_id","in":"path","required":true,"schema":{"type":"string","maxLength":128,"pattern":"^[A-Za-z0-9_\\-.]+$","title":"Model Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModelVersionsListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/models/{model_id}/versions/{version}/activate":{"post":{"tags":["Models"],"summary":"Activate a model version","operationId":"activate_model_version_models__model_id__versions__version__activate_post","parameters":[{"name":"model_id","in":"path","required":true,"schema":{"type":"string","maxLength":128,"pattern":"^[A-Za-z0-9_\\-.]+$","title":"Model Id"}},{"name":"version","in":"path","required":true,"schema":{"type":"string","maxLength":64,"pattern":"^[A-Za-z0-9_\\-.]+$","title":"Version"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/models/{model_id}/versions/{version}/deactivate":{"post":{"tags":["Models"],"summary":"Deactivate a model version","operationId":"deactivate_model_version_models__model_id__versions__version__deactivate_post","parameters":[{"name":"model_id","in":"path","required":true,"schema":{"type":"string","maxLength":128,"pattern":"^[A-Za-z0-9_\\-.]+$","title":"Model Id"}},{"name":"version","in":"path","required":true,"schema":{"type":"string","maxLength":64,"pattern":"^[A-Za-z0-9_\\-.]+$","title":"Version"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/explain/batch":{"post":{"tags":["Explain"],"summary":"Submit an async batch explanation job","description":"Queue a batch of explanations for async processing. Results are POSTed\nto ``webhook_url`` when the job completes. Returns immediately with a\n``job_id`` for polling.","operationId":"explain_batch_explain_batch_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchExplainRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchExplainResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/explain/batch/{job_id}":{"get":{"tags":["Explain"],"summary":"Poll a batch explain job","operationId":"explain_batch_status_explain_batch__job_id__get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","maxLength":64,"pattern":"^[A-Za-z0-9\\-]+$","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchJobStatusResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["Explain"],"summary":"Cancel a queued batch job","operationId":"explain_batch_cancel_explain_batch__job_id__delete","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","maxLength":64,"pattern":"^[A-Za-z0-9\\-]+$","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/signup/free":{"post":{"summary":"Signup Free","description":"Step 1: validate email, send verification code.","operationId":"signup_free_signup_free_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FreeSignupRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/signup/verify":{"post":{"summary":"Verify Email","description":"Step 2: submit verification code, receive API key.","operationId":"verify_email_signup_verify_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/signup/checkout":{"post":{"summary":"Create Checkout","description":"Create Stripe checkout session and return URL.","operationId":"create_checkout_signup_checkout_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/signup/enterprise":{"post":{"summary":"Signup Enterprise","description":"Log enterprise enquiry and notify.","operationId":"signup_enterprise_signup_enterprise_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnterpriseRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/signup/webhook":{"post":{"summary":"Stripe Webhook","description":"Stripe webhook — fires after successful payment, creates + emails key.","operationId":"stripe_webhook_signup_webhook_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}}},"components":{"schemas":{"BGVZKBindingFields":{"properties":{"proof_hex":{"type":"string","maxLength":131072,"minLength":1,"title":"Proof Hex"},"pk_hex":{"type":"string","maxLength":131072,"minLength":1,"title":"Pk Hex"},"ct_hex":{"type":"string","maxLength":131072,"minLength":1,"title":"Ct Hex"},"d_b_hex":{"type":"string","maxLength":128,"minLength":1,"title":"D B Hex"}},"type":"object","required":["proof_hex","pk_hex","ct_hex","d_b_hex"],"title":"BGVZKBindingFields","description":"Optional lattice binding-proof sub-payload for ``/counterfactual``.\n\nAttached by the SDK when the lattice-arm binding is requested. Enabled\nper deployment; when not enabled these fields are accepted but ignored\nand the default binding path runs unchanged. All fields are hex-encoded.\nThe recommended binding path for consumer adverse-action use cases is\nthe default attested mode."},"BatchExplainRequest":{"properties":{"model_id":{"type":"string","maxLength":128,"pattern":"^[A-Za-z0-9_\\-.]+$","title":"Model Id"},"features":{"items":{"items":{"type":"number"},"type":"array"},"type":"array","title":"Features"},"webhook_url":{"type":"string","title":"Webhook Url"},"idempotency_key":{"anyOf":[{"type":"string","maxLength":128},{"type":"null"}],"title":"Idempotency Key"},"apply_dp":{"type":"boolean","title":"Apply Dp","default":false},"dp_epsilon":{"type":"number","maximum":10.0,"exclusiveMinimum":0.0,"title":"Dp Epsilon","default":10.0},"dp_delta":{"type":"number","exclusiveMaximum":0.001,"exclusiveMinimum":0.0,"title":"Dp Delta","default":1e-05},"dp_clip_C":{"type":"number","maximum":1.0,"exclusiveMinimum":0.0,"title":"Dp Clip C","default":0.2},"apply_confs":{"type":"boolean","title":"Apply Confs","default":false},"fhe_mode":{"type":"string","title":"Fhe Mode","default":"disable"},"fhe_mode_strict":{"type":"boolean","title":"Fhe Mode Strict","default":false}},"type":"object","required":["model_id","features","webhook_url"],"title":"BatchExplainRequest"},"BatchExplainResponse":{"properties":{"job_id":{"type":"string","title":"Job Id"},"status":{"type":"string","title":"Status"},"created_at":{"type":"string","title":"Created At"},"estimated_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Estimated Seconds"}},"type":"object","required":["job_id","status","created_at"],"title":"BatchExplainResponse"},"BatchJobStatusResponse":{"properties":{"job_id":{"type":"string","title":"Job Id"},"status":{"type":"string","title":"Status"},"model_id":{"type":"string","title":"Model Id"},"created_at":{"type":"string","title":"Created At"},"started_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Started At"},"completed_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Completed At"},"attempt_count":{"type":"integer","title":"Attempt Count"},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message"},"result_summary":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Result Summary"}},"type":"object","required":["job_id","status","model_id","created_at","attempt_count"],"title":"BatchJobStatusResponse"},"CatBoostFeatureMeta":{"properties":{"feature_index":{"type":"integer","title":"Feature Index"},"nan_value_treatment":{"type":"string","enum":["AsIs","AsFalse"],"title":"Nan Value Treatment"}},"type":"object","required":["feature_index","nan_value_treatment"],"title":"CatBoostFeatureMeta","description":"Per-feature NaN treatment picked at training time.\n\nv1 accepts only ``AsIs`` (feature never saw NaN) and ``AsFalse``\n(NaN routes left of every split on that feature). Python's\n``x > border`` returns ``False`` for NaN, matching both. ``AsTrue``\n/ ``Min`` / ``Max`` are rejected at extraction."},"CatBoostSpec":{"properties":{"trees":{"items":{"$ref":"#/components/schemas/CatBoostTreeSpec"},"type":"array","title":"Trees"},"n_classes":{"type":"integer","const":2,"title":"N Classes","default":2},"n_features":{"type":"integer","title":"N Features"},"scale":{"type":"number","title":"Scale","default":1.0},"bias":{"type":"number","title":"Bias","default":0.0},"float_features":{"items":{"$ref":"#/components/schemas/CatBoostFeatureMeta"},"type":"array","title":"Float Features","default":[]},"objective":{"type":"string","title":"Objective","default":"Logloss"}},"type":"object","required":["trees","n_features"],"title":"CatBoostSpec","description":"Spec for a CatBoost binary classifier (v1 — Gap 3 Workstream G).\n\nOnly ``SymmetricTree`` grow policy and ``Logloss`` / ``CrossEntropy``\nobjectives are accepted. The raw score formula is\n``raw = scale * sum(tree_outputs(x)) + bias`` followed by\n``sigmoid``."},"CatBoostSplit":{"properties":{"feature":{"type":"integer","title":"Feature"},"border":{"type":"number","title":"Border"}},"type":"object","required":["feature","border"],"title":"CatBoostSplit","description":"A single oblivious-tree split condition.\n\nCatBoost's default ``SymmetricTree`` policy re-uses the same split\nat every node of a given depth, so each tree is summarised as a\nflat ``splits`` list (length k) and a ``leaf_values`` lookup\n(length 2^k), using LSB-first bit ordering for the leaf index."},"CatBoostTreeSpec":{"properties":{"splits":{"items":{"$ref":"#/components/schemas/CatBoostSplit"},"type":"array","title":"Splits"},"leaf_values":{"items":{"type":"number"},"type":"array","title":"Leaf Values"}},"type":"object","required":["splits","leaf_values"],"title":"CatBoostTreeSpec","description":"A single oblivious tree: k splits + 2^k leaf values."},"CheckoutRequest":{"properties":{"email":{"type":"string","format":"email","title":"Email"},"plan":{"type":"string","enum":["pro","team","developer"],"title":"Plan","default":"pro"}},"type":"object","required":["email"],"title":"CheckoutRequest"},"CounterfactualReasonCode":{"properties":{"feature_name":{"type":"string","title":"Feature Name"},"form_c1_code":{"type":"string","title":"Form C1 Code"},"form_c1_text":{"type":"string","title":"Form C1 Text"},"phi_magnitude":{"type":"number","title":"Phi Magnitude"},"actionable":{"type":"boolean","title":"Actionable"},"regulatory_justification":{"type":"string","title":"Regulatory Justification"}},"type":"object","required":["feature_name","form_c1_code","form_c1_text","phi_magnitude","actionable","regulatory_justification"],"title":"CounterfactualReasonCode"},"CounterfactualRequest":{"properties":{"model_id":{"type":"string","maxLength":128,"pattern":"^[A-Za-z0-9_\\-.]+$","title":"Model Id"},"session_token":{"type":"string","maxLength":1024,"minLength":32,"title":"Session Token"},"enc_delta_hex":{"type":"string","maxLength":4194304,"minLength":1,"title":"Enc Delta Hex"},"c_x_hex":{"type":"string","maxLength":8192,"minLength":1,"title":"C X Hex"},"c_delta_hex":{"type":"string","maxLength":8192,"minLength":1,"title":"C Delta Hex"},"c_x_prime_hex":{"type":"string","maxLength":8192,"minLength":1,"title":"C X Prime Hex"},"pi_cf_hex":{"type":"string","maxLength":65536,"minLength":1,"title":"Pi Cf Hex"},"encoding_tag":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Encoding Tag"},"bgv_zk":{"anyOf":[{"$ref":"#/components/schemas/BGVZKBindingFields"},{"type":"null"}]}},"type":"object","required":["model_id","session_token","enc_delta_hex","c_x_hex","c_delta_hex","c_x_prime_hex","pi_cf_hex"],"title":"CounterfactualRequest","description":"Request body for `POST /counterfactual`.\n\nAll cryptographic bytes are hex-encoded. The session_token is the opaque\nidentifier the server returned in the first `/explain` call's\ncomposition β₁ bundle; client re-presents it here to retrieve the\nserver-side stored Enc(x) without re-encrypting.\n\nThe optional ``encoding_tag`` and ``bgv_zk`` fields carry an alternate\nlattice binding proof when the SDK requests it — see\n:class:`BGVZKBindingFields`. The default fields above remain required."},"CounterfactualResponse":{"properties":{"model_id":{"type":"string","title":"Model Id"},"x_prime":{"items":{"type":"number"},"type":"array","title":"X Prime"},"delta":{"items":{"type":"number"},"type":"array","title":"Delta"},"decision_flipped":{"type":"boolean","title":"Decision Flipped"},"reason_codes":{"items":{"$ref":"#/components/schemas/CounterfactualReasonCode"},"type":"array","title":"Reason Codes"},"attestation_beta2":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Attestation Beta2"},"cf_attestation_mode":{"type":"string","enum":["UNATTESTED","ATTESTED","ATTESTED_BGV_ZK"],"title":"Cf Attestation Mode","default":"UNATTESTED"},"warning":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Warning"},"timestamp_ms":{"type":"integer","title":"Timestamp Ms"},"metadata":{"additionalProperties":true,"type":"object","title":"Metadata"}},"type":"object","required":["model_id","x_prime","delta","decision_flipped","reason_codes","timestamp_ms"],"title":"CounterfactualResponse","description":"Response for ``POST /counterfactual``.\n\n``cf_attestation_mode`` reports the binding assurance level configured\nfor the deployment."},"DistilledProvenance":{"properties":{"teacher_id":{"type":"string","maxLength":128,"pattern":"^[A-Za-z0-9_\\-.]+$","title":"Teacher Id"},"teacher_fingerprint":{"type":"string","maxLength":64,"pattern":"^[0-9a-f]{64}$","title":"Teacher Fingerprint"},"student_depth":{"type":"integer","minimum":4.0,"title":"Student Depth"},"certificate_sha256":{"type":"string","maxLength":64,"pattern":"^[0-9a-f]{64}$","title":"Certificate Sha256"},"distill_loss":{"type":"string","const":"logit_kd_hard_label","title":"Distill Loss","default":"logit_kd_hard_label"}},"type":"object","required":["teacher_id","teacher_fingerprint","student_depth","certificate_sha256"],"title":"DistilledProvenance","description":"Optional provenance metadata for an advanced model-preparation path.\n\nRecords a reference fingerprint, a depth parameter, and a fidelity\ncertificate hash. This path is off by default and used only when\nexplicitly enabled for a deployment."},"EnterpriseRequest":{"properties":{"email":{"type":"string","format":"email","title":"Email"},"message":{"type":"string","maxLength":2000,"title":"Message","default":""}},"type":"object","required":["email"],"title":"EnterpriseRequest"},"ExplainRequest":{"properties":{"model_id":{"type":"string","maxLength":128,"pattern":"^[A-Za-z0-9_\\-.]+$","title":"Model Id"},"features":{"items":{"type":"number"},"type":"array","maxItems":512,"minItems":1,"title":"Features"},"fhe_mode":{"type":"string","title":"Fhe Mode","default":"simulate"},"fhe_mode_strict":{"type":"boolean","title":"Fhe Mode Strict","default":false},"apply_confs":{"type":"boolean","title":"Apply Confs","default":false},"apply_dp":{"type":"boolean","title":"Apply Dp","default":false},"dp_epsilon":{"type":"number","maximum":10.0,"exclusiveMinimum":0.0,"title":"Dp Epsilon","default":10.0},"dp_delta":{"type":"number","exclusiveMaximum":0.001,"exclusiveMinimum":0.0,"title":"Dp Delta","default":1e-05},"dp_clip_C":{"type":"number","maximum":1.0,"exclusiveMinimum":0.0,"title":"Dp Clip C","default":0.2},"impute_with_baseline":{"type":"boolean","title":"Impute With Baseline","default":false},"n_classes":{"type":"integer","maximum":10.0,"minimum":1.0,"title":"N Classes","default":2},"attribution_space_request":{"anyOf":[{"type":"string","enum":["logit","probability","both"]},{"type":"null"}],"title":"Attribution Space Request"},"strict_honesty":{"type":"boolean","title":"Strict Honesty","default":false},"range_proof":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Range Proof"},"sparse_recovery":{"type":"boolean","title":"Sparse Recovery","default":false},"sparse_recovery_s_prior":{"anyOf":[{"type":"integer","maximum":512.0,"minimum":1.0},{"type":"null"}],"title":"Sparse Recovery S Prior"},"linear_surrogate":{"type":"boolean","title":"Linear Surrogate","default":false}},"type":"object","required":["model_id","features"],"title":"ExplainRequest"},"ExplainResponse":{"properties":{"prediction":{"type":"number","title":"Prediction"},"base_rate":{"type":"number","title":"Base Rate"},"shap_values":{"items":{"type":"number"},"type":"array","title":"Shap Values"},"feature_names":{"items":{"type":"string"},"type":"array","title":"Feature Names"},"metadata":{"additionalProperties":true,"type":"object","title":"Metadata"},"confs_applied":{"type":"boolean","title":"Confs Applied","default":false},"confs_sigma":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Confs Sigma"},"dp_applied":{"type":"boolean","title":"Dp Applied","default":false},"dp_sigma":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Dp Sigma"},"dp_epsilon_used":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Dp Epsilon Used"},"proof":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Proof"},"regression_inputs_centered":{"anyOf":[{"items":{"type":"number"},"type":"array"},{"type":"null"}],"title":"Regression Inputs Centered"},"frei_kzg_enabled":{"type":"boolean","title":"Frei Kzg Enabled","default":false},"model_version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model Version"},"model_version_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model Version Id"},"cache_hit":{"type":"boolean","title":"Cache Hit","default":false},"cache_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cache Key"},"fhe_mode_requested":{"type":"string","title":"Fhe Mode Requested","default":"simulate"},"fhe_mode_used":{"type":"string","title":"Fhe Mode Used","default":"plaintext"},"fhe_mode_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Fhe Mode Reason"},"sparse_recovery_used":{"type":"boolean","title":"Sparse Recovery Used","default":false},"sparse_recovery_K_ratio":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Sparse Recovery K Ratio"},"sparse_recovery_K_prime":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Sparse Recovery K Prime"},"inclusion_proofs":{"anyOf":[{"items":{"additionalProperties":true,"type":"object"},"type":"array"},{"type":"null"}],"title":"Inclusion Proofs"},"binding_proof":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Binding Proof"},"epoch_attestation":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Epoch Attestation"},"shap_output_space_per_class":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Shap Output Space Per Class"},"multi_class_tier":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Multi Class Tier"},"attribution_space":{"anyOf":[{"type":"string","enum":["probability","logit"]},{"type":"null"}],"title":"Attribution Space"},"axiom_domain":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Axiom Domain"},"partial_open_proof":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Partial Open Proof"},"cf_session":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Cf Session"}},"type":"object","required":["prediction","base_rate","shap_values","feature_names","metadata"],"title":"ExplainResponse"},"ExplainTreeResponse":{"properties":{"model_id":{"type":"string","title":"Model Id"},"per_tree_nids":{"items":{"type":"integer"},"type":"array","title":"Per Tree Nids"},"per_tree_confidence":{"items":{"type":"number"},"type":"array","title":"Per Tree Confidence"},"feature_names":{"items":{"type":"string"},"type":"array","title":"Feature Names"},"composition_beta":{"type":"string","title":"Composition Beta"},"timestamp_ms":{"type":"integer","title":"Timestamp Ms"},"model_version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model Version"},"model_version_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model Version Id"},"metadata":{"additionalProperties":true,"type":"object","title":"Metadata"}},"type":"object","required":["model_id","per_tree_nids","per_tree_confidence","feature_names","composition_beta","timestamp_ms"],"title":"ExplainTreeResponse","description":"Response for tree-attested SHAP (`POST /explain_tree`).\n\nThe server returns a cryptographically-attested leaf-routing array\n(one nid per tree, with per-tree confidence values) plus the\nintegrity-attestation bundle. The CLIENT runs\n`shap.TreeExplainer(model).shap_values(x)` locally in plaintext\nusing the model schema and its own plaintext feature vector. The\nSHAP values delivered by the SDK come from the local TreeExplainer\ncall; this response carries only the routing attestation."},"FeatureMeta":{"properties":{"name":{"type":"string","maxLength":64,"pattern":"^[A-Za-z0-9_\\-.]+$","title":"Name"},"type":{"type":"string","enum":["numeric","binary","onehot","ordinal"],"title":"Type","default":"numeric"},"group_id":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Group Id"},"group_label":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Group Label"},"category_value":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Category Value"},"allows_nan":{"type":"boolean","title":"Allows Nan","default":false}},"type":"object","required":["name"],"title":"FeatureMeta","description":"Per-feature metadata (Gap 4 Workstream A).\n\nOptional descriptor attached to each column of a registered model. When\n``feature_metadata`` is omitted on a ``ModelSpec`` the server treats\nevery feature as numeric, preserving current behaviour.\n\nFields\n------\nname: column name (redundant with ``feature_names[i]`` but kept so\n    metadata rows are self-describing).\ntype: one of \"numeric\", \"binary\", \"onehot\", \"ordinal\".\ngroup_id: for ``onehot`` columns, links dummies of one parent feature\n    so a narrator can collapse them back into a single row.\ngroup_label: human-readable parent label (e.g. \"country\").\ncategory_value: the category this dummy represents (e.g. \"US\" for\n    ``country_US``).\nallows_nan: per-column NaN policy. When True the route handler lets\n    NaN flow through to the underlying model (XGBoost's native missing\n    routing). When False (default) NaN is rejected at the route\n    handler with a per-index 422."},"ForestSpec":{"properties":{"estimators":{"items":{"$ref":"#/components/schemas/TreeSpec"},"type":"array","title":"Estimators"},"is_classifier":{"type":"boolean","title":"Is Classifier"},"learning_rate":{"type":"number","title":"Learning Rate","default":1.0},"init_prediction":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Init Prediction"}},"type":"object","required":["estimators","is_classifier"],"title":"ForestSpec"},"FreeSignupRequest":{"properties":{"email":{"type":"string","format":"email","title":"Email"}},"type":"object","required":["email"],"title":"FreeSignupRequest"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"HandshakeRequest":{"properties":{"client_nonce":{"type":"string","maxLength":256,"minLength":32,"title":"Client Nonce","description":"hex-encoded >= 16 random bytes"},"ct_digest":{"type":"string","maxLength":256,"minLength":2,"title":"Ct Digest","description":"hex-encoded SHA-256 of canonical CKKS ct"},"pedersen_commit":{"type":"string","maxLength":1024,"minLength":2,"title":"Pedersen Commit","description":"hex-encoded Pedersen C = g^m h^r"},"tee_quote_digest":{"type":"string","maxLength":256,"minLength":2,"title":"Tee Quote Digest","description":"hex-encoded TEE quote digest — committed into the session Merkle root as an opaque client-supplied value; not independently verified against TEE hardware in this mode"},"session_id":{"anyOf":[{"type":"string","maxLength":64,"minLength":4},{"type":"null"}],"title":"Session Id","description":"optional caller-supplied session id"}},"type":"object","required":["client_nonce","ct_digest","pedersen_commit","tee_quote_digest"],"title":"HandshakeRequest","description":"Four client-supplied session inputs (hex strings)."},"LGBMNode":{"properties":{"nodeid":{"type":"integer","title":"Nodeid"},"split_feature":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Split Feature"},"split_condition":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Split Condition"},"yes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Yes"},"no":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"No"},"missing":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Missing"},"leaf":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Leaf"}},"type":"object","required":["nodeid"],"title":"LGBMNode","description":"A single LightGBM tree node (internal or leaf).\n\nStructurally identical to ``XGBNode``: internal nodes carry the\nsplit (``split_feature``, ``split_condition``) plus the three child\nids (``yes`` = left = ``feature <= threshold``, ``no`` = right =\n``feature > threshold``, ``missing`` = NaN routing fixed at\ntraining time by ``default_left``). Leaf nodes carry the raw-score\ncontribution in ``leaf``. A node is exactly one of the two; the\npost-validator rejects both/neither."},"LGBMSpec":{"properties":{"trees":{"items":{"$ref":"#/components/schemas/LGBMTreeSpec"},"type":"array","title":"Trees"},"n_classes":{"type":"integer","const":2,"title":"N Classes","default":2},"n_rounds":{"type":"integer","title":"N Rounds"},"base_score":{"type":"number","title":"Base Score"},"objective":{"type":"string","title":"Objective","default":"binary"}},"type":"object","required":["trees","n_rounds","base_score"],"title":"LGBMSpec","description":"Spec for a LightGBM binary classifier booster (v1).\n\nMulti-class is deferred. Objective must contain ``binary``; the\nextractor normalises ``LGBMClassifier.objective_`` to ``\"binary\"``\nbefore serialising. ``base_score`` is the raw-score prior (0.0 when\nthe classifier was trained without an explicit ``init_score``)."},"LGBMTreeSpec":{"properties":{"nodes":{"items":{"$ref":"#/components/schemas/LGBMNode"},"type":"array","title":"Nodes"}},"type":"object","required":["nodes"],"title":"LGBMTreeSpec"},"LinearSpec":{"properties":{"coef":{"items":{"items":{"type":"number"},"type":"array"},"type":"array","title":"Coef"},"intercept":{"items":{"type":"number"},"type":"array","title":"Intercept"},"classes":{"items":{"type":"integer"},"type":"array","title":"Classes"}},"type":"object","required":["coef","intercept","classes"],"title":"LinearSpec"},"MLPSpec":{"properties":{"coefs":{"items":{"items":{"items":{"type":"number"},"type":"array"},"type":"array"},"type":"array","title":"Coefs"},"intercepts":{"items":{"items":{"type":"number"},"type":"array"},"type":"array","title":"Intercepts"}},"type":"object","required":["coefs","intercepts"],"title":"MLPSpec"},"ModelVersionInfo":{"properties":{"version_id":{"type":"string","title":"Version Id"},"version":{"type":"string","title":"Version"},"registered_at":{"type":"string","title":"Registered At"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"is_active":{"type":"boolean","title":"Is Active"}},"type":"object","required":["version_id","version","registered_at","is_active"],"title":"ModelVersionInfo"},"ModelVersionsListResponse":{"properties":{"model_id":{"type":"string","title":"Model Id"},"versions":{"items":{"$ref":"#/components/schemas/ModelVersionInfo"},"type":"array","title":"Versions"}},"type":"object","required":["model_id","versions"],"title":"ModelVersionsListResponse"},"PaygLimitRequest":{"properties":{"limit_gbp":{"anyOf":[{"type":"number","maximum":10000.0,"minimum":0.0},{"type":"null"}],"title":"Limit Gbp","description":"Monthly PAYG spend cap in GBP. Set to null to remove the cap."}},"type":"object","title":"PaygLimitRequest"},"RegisterModelRequest":{"properties":{"model_id":{"type":"string","maxLength":128,"pattern":"^[A-Za-z0-9_\\-.]+$","title":"Model Id"},"model_type":{"type":"string","enum":["logistic_regression","linear_svc","decision_tree","random_forest","gradient_boosting","mlp","xgboost","lightgbm","catboost"],"title":"Model Type"},"feature_names":{"items":{"type":"string"},"type":"array","maxItems":512,"minItems":1,"title":"Feature Names"},"spec":{"anyOf":[{"$ref":"#/components/schemas/LinearSpec"},{"$ref":"#/components/schemas/TreeSpec"},{"$ref":"#/components/schemas/ForestSpec"},{"$ref":"#/components/schemas/MLPSpec"},{"$ref":"#/components/schemas/XGBoostSpec"},{"$ref":"#/components/schemas/LGBMSpec"},{"$ref":"#/components/schemas/CatBoostSpec"}],"title":"Spec"},"scaler":{"anyOf":[{"$ref":"#/components/schemas/ScalerSpec"},{"type":"null"}]},"baseline":{"anyOf":[{"items":{"type":"number"},"type":"array"},{"type":"null"}],"title":"Baseline"},"feature_metadata":{"anyOf":[{"items":{"$ref":"#/components/schemas/FeatureMeta"},"type":"array"},{"type":"null"}],"title":"Feature Metadata"},"enable_fhe_octe":{"type":"boolean","title":"Enable Fhe Octe","default":false},"distilled":{"anyOf":[{"$ref":"#/components/schemas/DistilledProvenance"},{"type":"null"}]},"x_ref":{"anyOf":[{"items":{"items":{"type":"number"},"type":"array"},"type":"array"},{"type":"null"}],"title":"X Ref"},"calibration_samples":{"anyOf":[{"items":{"items":{"type":"number"},"type":"array"},"type":"array"},{"type":"null"}],"title":"Calibration Samples"},"version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Version"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"fhe_mode":{"type":"string","enum":["simulate","execute"],"title":"Fhe Mode","default":"simulate"}},"type":"object","required":["model_id","model_type","feature_names","spec"],"title":"RegisterModelRequest"},"RegisterModelResponse":{"properties":{"model_id":{"type":"string","title":"Model Id"},"feature_count":{"type":"integer","title":"Feature Count"},"coalition_count":{"type":"integer","title":"Coalition Count"},"n_classes":{"type":"integer","title":"N Classes"},"is_binary":{"type":"boolean","title":"Is Binary"},"model_type":{"type":"string","title":"Model Type"},"fhe_registered":{"type":"boolean","title":"Fhe Registered","default":true},"fhe_registration_error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Fhe Registration Error"},"version":{"type":"string","title":"Version","default":"1"},"version_id":{"type":"string","title":"Version Id","default":""},"oms_root":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Oms Root"}},"type":"object","required":["model_id","feature_count","coalition_count","n_classes","is_binary","model_type"],"title":"RegisterModelResponse"},"RotateKeyResponse":{"properties":{"new_key":{"type":"string","title":"New Key"},"models_migrated":{"type":"integer","title":"Models Migrated"},"note":{"type":"string","title":"Note"}},"type":"object","required":["new_key","models_migrated","note"],"title":"RotateKeyResponse"},"ScalerSpec":{"properties":{"type":{"type":"string","enum":["standard","minmax","robust"],"title":"Type","default":"standard"},"mean":{"anyOf":[{"items":{"type":"number"},"type":"array"},{"type":"null"}],"title":"Mean"},"scale":{"items":{"type":"number"},"type":"array","title":"Scale"},"min":{"anyOf":[{"items":{"type":"number"},"type":"array"},{"type":"null"}],"title":"Min"},"feature_range":{"anyOf":[{"items":{"type":"number"},"type":"array"},{"type":"null"}],"title":"Feature Range"},"center":{"anyOf":[{"items":{"type":"number"},"type":"array"},{"type":"null"}],"title":"Center"}},"type":"object","required":["scale"],"title":"ScalerSpec","description":"Feature scaler stored alongside a model spec.\n\nSupported scaler types (Gap 3 Workstream C — sklearn Pipeline unwrap):\n  - ``\"standard\"``: StandardScaler — requires ``mean`` and ``scale``.\n  - ``\"minmax\"``: MinMaxScaler — requires ``min``, ``scale``,\n    ``feature_range`` (e.g. ``[0, 1]``).\n  - ``\"robust\"``: RobustScaler — requires ``center`` and ``scale``.\n\nThe server rehydrates the matching sklearn transformer at model\nregistration and applies it at explain time (``/explain_raw``)."},"TreeSpec":{"properties":{"nodes_b64":{"type":"string","title":"Nodes B64"},"values_b64":{"type":"string","title":"Values B64"},"nodes_dtype":{"type":"string","title":"Nodes Dtype"},"values_shape":{"items":{"type":"integer"},"type":"array","title":"Values Shape"},"n_features_in":{"type":"integer","title":"N Features In"},"n_classes":{"items":{"type":"integer"},"type":"array","title":"N Classes"},"n_outputs":{"type":"integer","title":"N Outputs"},"is_classifier":{"type":"boolean","title":"Is Classifier"},"classes":{"anyOf":[{"items":{"items":{"type":"number"},"type":"array"},"type":"array"},{"type":"null"}],"title":"Classes"},"max_depth":{"type":"integer","title":"Max Depth","default":0}},"type":"object","required":["nodes_b64","values_b64","nodes_dtype","values_shape","n_features_in","n_classes","n_outputs","is_classifier"],"title":"TreeSpec"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"VerifyRequest":{"properties":{"email":{"type":"string","format":"email","title":"Email"},"code":{"type":"string","title":"Code"}},"type":"object","required":["email","code"],"title":"VerifyRequest"},"XGBNode":{"properties":{"nodeid":{"type":"integer","title":"Nodeid"},"split_feature":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Split Feature"},"split_condition":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Split Condition"},"yes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Yes"},"no":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"No"},"missing":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Missing"},"leaf":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Leaf"}},"type":"object","required":["nodeid"],"title":"XGBNode","description":"A single XGBoost tree node (internal or leaf).\n\nInternal-node fields carry the split (``split_feature``,\n``split_condition``) plus the three child ids: ``yes`` (feature <\nthreshold), ``no`` (feature >= threshold), and ``missing`` (NaN\nrouting, fixed at training time). Leaf nodes carry the log-odds\ncontribution in ``leaf``. A node is exactly one of the two; the\npost-validator rejects both/neither."},"XGBTreeSpec":{"properties":{"nodes":{"items":{"$ref":"#/components/schemas/XGBNode"},"type":"array","title":"Nodes"}},"type":"object","required":["nodes"],"title":"XGBTreeSpec"},"XGBoostSpec":{"properties":{"trees":{"items":{"$ref":"#/components/schemas/XGBTreeSpec"},"type":"array","title":"Trees"},"n_classes":{"type":"integer","const":2,"title":"N Classes","default":2},"n_rounds":{"type":"integer","title":"N Rounds"},"base_score":{"type":"number","title":"Base Score"},"objective":{"type":"string","title":"Objective","default":"binary:logistic"}},"type":"object","required":["trees","n_rounds","base_score"],"title":"XGBoostSpec","description":"Spec for an XGBoost binary classifier booster (v1).\n\nMulti-class is deferred to Gap 3 Workstream D. Only\n``objective=\"binary:logistic\"`` is accepted; ``base_score`` is the\ninitial log-odds prior (caller extractor converts XGBoost's\nprobability base_score via logit)."}}}}