{
  "openapi": "3.1.0",
  "info": {
    "title": "QSentia Connect API",
    "version": "1.0.0",
    "description": "Read-only model output, telemetry, entitlement, and webhook registration API for approved QSentia Connect partners."
  },
  "servers": [
    {
      "url": "https://www.qsentia.com"
    }
  ],
  "security": [
    {
      "QSentiaBearer": []
    }
  ],
  "paths": {
    "/api/connect/v1/models": {
      "get": {
        "summary": "List entitled QSentia models",
        "description": "Returns the models entitled for the API key, including broker state, latest signal timestamp, and live paper return context.",
        "responses": {
          "200": {
            "description": "Entitled model list"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "429": {
            "description": "QSentia Connect request limit exceeded"
          }
        }
      }
    },
    "/api/connect/v1/models/{model_id}/latest-signal": {
      "get": {
        "summary": "Fetch latest model signal",
        "description": "Returns the latest signal package for one entitled model with run, producer, account, and broker evidence when available.",
        "parameters": [
          {
            "name": "model_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "examples": {
              "brppoCrypto": {
                "value": "brppo_crypto"
              }
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Latest normalized signal payload",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LatestSignalResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "403": {
            "description": "Model entitlement required"
          },
          "429": {
            "description": "QSentia Connect request limit exceeded"
          }
        }
      }
    },
    "/api/connect/v1/custom-models": {
      "get": {
        "summary": "List approved custom candidates",
        "description": "Returns approved Risk Studio custom candidates explicitly entitled to the API key. Private model artifacts and internal metadata are never exposed.",
        "responses": {
          "200": {
            "description": "Approved custom model candidate list",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomModelListResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "429": {
            "description": "QSentia Connect request limit exceeded"
          }
        }
      }
    },
    "/api/connect/v1/custom-models/{candidate_id}": {
      "get": {
        "summary": "Fetch approved custom candidate",
        "description": "Returns one approved Risk Studio candidate summary for the customer that owns and is entitled to the candidate.",
        "parameters": [
          {
            "name": "candidate_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "examples": {
              "candidate": {
                "value": "cmv_..."
              }
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Approved custom model candidate",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomModelDetailResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "404": {
            "description": "Custom candidate not found or not entitled"
          },
          "429": {
            "description": "QSentia Connect request limit exceeded"
          }
        }
      }
    },
    "/api/connect/v1/custom-models/{candidate_id}/backtest": {
      "get": {
        "summary": "Fetch approved custom candidate backtest",
        "description": "Returns custom risk-layer metrics, curve, gates, and evidence summary. Artifact storage locations remain private to QSentia.",
        "parameters": [
          {
            "name": "candidate_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Approved custom model backtest result",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomModelBacktestResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "404": {
            "description": "Custom candidate not found or not entitled"
          },
          "429": {
            "description": "QSentia Connect request limit exceeded"
          }
        }
      }
    },
    "/api/connect/v1/custom-models/{candidate_id}/latest-signal": {
      "get": {
        "summary": "Fetch latest custom candidate signal",
        "description": "Returns a normalized signal-style package for an approved custom candidate. Before live deployment this represents the approved candidate and its risk-layer evidence, not a broker-connected live trade signal.",
        "parameters": [
          {
            "name": "candidate_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Latest normalized custom candidate signal payload",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomLatestSignalResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "404": {
            "description": "Custom candidate not found or not entitled"
          },
          "429": {
            "description": "QSentia Connect request limit exceeded"
          }
        }
      }
    },
    "/api/connect/v1/webhooks": {
      "get": {
        "summary": "List webhook endpoints",
        "responses": {
          "200": {
            "description": "Registered webhook endpoints and recent deliveries"
          },
          "401": {
            "description": "Missing or invalid API key"
          }
        }
      },
      "post": {
        "summary": "Register webhook endpoint",
        "description": "Registers an HTTPS webhook endpoint for approved event delivery. The signing secret is returned once.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookCreateRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Webhook endpoint created"
          },
          "400": {
            "description": "Invalid webhook endpoint request"
          },
          "401": {
            "description": "Missing or invalid API key"
          }
        }
      },
      "patch": {
        "summary": "Update webhook endpoint status",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookUpdateRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Webhook endpoint updated"
          },
          "400": {
            "description": "Invalid webhook update request"
          },
          "401": {
            "description": "Missing or invalid API key"
          }
        }
      }
    },
    "/api/connect/v1/webhooks/test": {
      "post": {
        "summary": "Queue a test webhook delivery",
        "description": "Queues test delivery records for active webhook endpoints. Outbound dispatch runs immediately only when QSENTIA_CONNECT_WEBHOOKS_ENABLED=true.",
        "responses": {
          "200": {
            "description": "Webhook test delivery records queued"
          },
          "400": {
            "description": "No active webhook endpoints"
          },
          "401": {
            "description": "Missing or invalid API key"
          }
        }
      }
    },
    "/api/connect/applications": {
      "post": {
        "summary": "Submit partner application",
        "description": "Captures a broker, family office, exchange, fund, or platform onboarding application for QSentia approval.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PartnerApplicationRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Partner application captured"
          },
          "400": {
            "description": "Invalid application request"
          }
        }
      }
    },
    "/api/connect/request": {
      "post": {
        "summary": "Request Connect onboarding",
        "security": [],
        "responses": {
          "200": {
            "description": "Onboarding request captured"
          }
        }
      }
    },
    "/api/connect/internal/webhook-worker": {
      "post": {
        "summary": "Dispatch queued webhooks",
        "description": "Internal worker endpoint protected by x-qsentia-connect-worker-secret.",
        "security": [],
        "responses": {
          "200": {
            "description": "Queued deliveries processed"
          },
          "401": {
            "description": "Worker secret required"
          }
        }
      }
    },
    "/api/connect/internal/billing-sync": {
      "post": {
        "summary": "Aggregate pending billing usage",
        "description": "Internal usage-meter aggregation endpoint protected by x-qsentia-connect-worker-secret. Supports record-only, mark-reported, and external billing webhook modes.",
        "security": [],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InternalBillingSyncRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Pending billing usage summarized or marked reported"
          },
          "401": {
            "description": "Worker secret required"
          }
        }
      }
    },
    "/api/connect/internal/scheduled": {
      "get": {
        "summary": "Run scheduled Connect workers",
        "description": "Internal scheduled worker endpoint protected by x-qsentia-connect-worker-secret. Runs webhook delivery and usage billing sync in one call.",
        "security": [],
        "responses": {
          "200": {
            "description": "Scheduled worker run summary"
          },
          "401": {
            "description": "Worker secret required"
          }
        }
      },
      "post": {
        "summary": "Run scheduled Connect workers",
        "description": "Internal scheduled worker endpoint protected by x-qsentia-connect-worker-secret. Supports disabling webhooks or billing per run.",
        "security": [],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InternalScheduledWorkerRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Scheduled worker run summary"
          },
          "401": {
            "description": "Worker secret required"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "QSentiaBearer": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "qs_sandbox_..."
      }
    },
    "schemas": {
      "LatestSignalResponse": {
        "type": "object",
        "required": ["model_id", "signal_time", "action", "asset", "target_weight", "confidence", "risk_state", "broker_status", "evidence", "raw_payload"],
        "properties": {
          "schema_version": {
            "type": "string",
            "example": "qsentia.connect.signal.v1"
          },
          "delivered_at": {
            "type": "string",
            "format": "date-time"
          },
          "model_id": {
            "type": "string",
            "example": "brppo_crypto"
          },
          "signal_time": {
            "type": ["string", "null"],
            "format": "date-time"
          },
          "action": {
            "type": ["string", "null"],
            "example": "hold"
          },
          "asset": {
            "type": ["string", "null"],
            "example": "BTC/USD"
          },
          "target_weight": {
            "type": ["number", "null"],
            "example": 0.25
          },
          "confidence": {
            "type": ["number", "null"],
            "example": 0.72
          },
          "risk_state": {
            "type": ["string", "null"],
            "example": "normal"
          },
          "broker_status": {
            "type": ["string", "null"],
            "example": "online"
          },
          "evidence": {
            "type": "object",
            "additionalProperties": true
          },
          "raw_payload": {
            "type": ["object", "null"],
            "additionalProperties": true
          }
        }
      },
      "CustomModelSummary": {
        "type": "object",
        "required": ["id", "name", "base_model_id", "status", "performance", "evidence", "endpoints"],
        "properties": {
          "id": {
            "type": "string",
            "example": "cmv_..."
          },
          "name": {
            "type": "string",
            "example": "BTC-SENT-DY custom drawdown mandate"
          },
          "base_model_id": {
            "type": "string",
            "example": "btc-futures-sentiment-daily"
          },
          "mandate_id": {
            "type": ["string", "null"]
          },
          "status": {
            "type": "string",
            "example": "approved"
          },
          "deployment_status": {
            "type": ["string", "null"],
            "example": "approved"
          },
          "deployment_scope": {
            "type": ["string", "null"],
            "example": "connect"
          },
          "approved_at": {
            "type": ["string", "null"],
            "format": "date-time"
          },
          "updated_at": {
            "type": ["string", "null"],
            "format": "date-time"
          },
          "risk_profile": {
            "type": ["string", "null"],
            "example": "Max 8% drawdown, 20% turnover"
          },
          "performance": {
            "type": "object",
            "additionalProperties": true
          },
          "evidence": {
            "type": "object",
            "additionalProperties": true
          },
          "endpoints": {
            "type": "object",
            "required": ["detail", "backtest", "latest_signal"],
            "properties": {
              "detail": {
                "type": "string"
              },
              "backtest": {
                "type": "string"
              },
              "latest_signal": {
                "type": "string"
              }
            }
          }
        }
      },
      "CustomModelListResponse": {
        "type": "object",
        "required": ["schema_version", "delivered_at", "custom_models"],
        "properties": {
          "schema_version": {
            "type": "string",
            "example": "qsentia.connect.custom_models.v1"
          },
          "delivered_at": {
            "type": "string",
            "format": "date-time"
          },
          "customer": {
            "type": "object",
            "additionalProperties": true
          },
          "custom_models": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CustomModelSummary"
            }
          }
        }
      },
      "CustomModelDetailResponse": {
        "type": "object",
        "required": ["schema_version", "delivered_at", "custom_model"],
        "properties": {
          "schema_version": {
            "type": "string",
            "example": "qsentia.connect.custom_model.v1"
          },
          "delivered_at": {
            "type": "string",
            "format": "date-time"
          },
          "custom_model": {
            "$ref": "#/components/schemas/CustomModelSummary"
          }
        }
      },
      "CustomModelBacktestResponse": {
        "type": "object",
        "required": ["schema_version", "delivered_at", "custom_model", "backtest"],
        "properties": {
          "schema_version": {
            "type": "string",
            "example": "qsentia.connect.custom_backtest.v1"
          },
          "delivered_at": {
            "type": "string",
            "format": "date-time"
          },
          "custom_model": {
            "type": "object",
            "additionalProperties": true
          },
          "backtest": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "CustomLatestSignalResponse": {
        "type": "object",
        "required": ["schema_version", "delivered_at", "model_id", "signal_time", "action", "asset", "target_weight", "confidence", "risk_state", "broker_status", "evidence", "raw_payload"],
        "properties": {
          "schema_version": {
            "type": "string",
            "example": "qsentia.connect.custom_signal.v1"
          },
          "delivered_at": {
            "type": "string",
            "format": "date-time"
          },
          "model_id": {
            "type": "string",
            "example": "cmv_..."
          },
          "signal_time": {
            "type": ["string", "null"],
            "format": "date-time"
          },
          "action": {
            "type": ["string", "null"],
            "example": "risk_overlay_candidate_ready"
          },
          "asset": {
            "type": ["string", "null"],
            "example": "BTC futures universe"
          },
          "target_weight": {
            "type": ["number", "null"],
            "example": 0.18
          },
          "confidence": {
            "type": ["number", "null"],
            "example": 0.81
          },
          "risk_state": {
            "type": ["string", "null"],
            "example": "approved_custom_candidate"
          },
          "broker_status": {
            "type": ["string", "null"],
            "example": "not_connected_custom_candidate"
          },
          "evidence": {
            "type": "object",
            "additionalProperties": true
          },
          "raw_payload": {
            "type": ["object", "null"],
            "additionalProperties": true
          }
        }
      },
      "WebhookCreateRequest": {
        "type": "object",
        "required": ["url"],
        "properties": {
          "label": {
            "type": "string",
            "example": "Primary OMS receiver"
          },
          "url": {
            "type": "string",
            "format": "uri",
            "example": "https://partner.example.com/qsentia/webhooks"
          },
          "eventTypes": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": ["model.signal.created", "model.order.created", "model.telemetry.updated", "connect.test"]
            }
          },
          "environment": {
            "type": "string",
            "enum": ["sandbox", "paper", "live"],
            "default": "sandbox"
          }
        }
      },
      "WebhookUpdateRequest": {
        "type": "object",
        "required": ["id", "status"],
        "properties": {
          "id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": ["active", "paused", "disabled"]
          }
        }
      },
      "PartnerApplicationRequest": {
        "type": "object",
        "required": ["organization", "email", "useCase"],
        "properties": {
          "organization": {
            "type": "string",
            "example": "Northbridge Family Office"
          },
          "primaryContact": {
            "type": "string",
            "example": "Jane Doe"
          },
          "email": {
            "type": "string",
            "format": "email",
            "example": "jane@northbridge.example"
          },
          "integrationType": {
            "type": "string",
            "enum": ["family_office", "broker", "exchange", "fund", "oms", "other"],
            "default": "family_office"
          },
          "desiredModels": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "example": ["Crypto futures", "Options"]
          },
          "expectedMonthlyRequests": {
            "type": "integer",
            "minimum": 1,
            "default": 10000
          },
          "useCase": {
            "type": "string",
            "example": "Ingest QSentia model outputs into a portfolio analytics dashboard and OMS review workflow."
          }
        }
      },
      "InternalBillingSyncRequest": {
        "type": "object",
        "properties": {
          "limit": {
            "type": "integer",
            "minimum": 1,
            "default": 250
          },
          "markReported": {
            "type": "boolean",
            "default": false
          },
          "stripeMeterEventId": {
            "type": "string"
          }
        }
      },
      "InternalScheduledWorkerRequest": {
        "type": "object",
        "properties": {
          "webhooks": {
            "type": "boolean",
            "default": true
          },
          "billing": {
            "type": "boolean",
            "default": true
          },
          "webhookLimit": {
            "type": "integer",
            "minimum": 1,
            "default": 50
          },
          "billingLimit": {
            "type": "integer",
            "minimum": 1,
            "default": 500
          },
          "markReported": {
            "type": "boolean",
            "default": false
          },
          "stripeMeterEventId": {
            "type": "string"
          }
        }
      }
    }
  }
}
