From 9c3ea3af4854660cceb25a1dee3ace8b746ea3a0 Mon Sep 17 00:00:00 2001 From: Brian Hartford Date: Sun, 25 Jan 2026 14:38:58 -0500 Subject: [PATCH 1/3] API Spec --- API_REFERENCE.md | 126 +- README.md | 4 +- openapi.yaml | 6385 ++++++++++++++++++++++++++++++++ scripts/live_api_smoke.py | 11 +- src/kyro/exceptions.py | 10 +- src/kyro/models.py | 174 + src/kyro/rest/api/events.py | 19 +- src/kyro/rest/api/exchange.py | 5 +- src/kyro/rest/api/markets.py | 30 +- src/kyro/rest/api/orders.py | 64 +- src/kyro/rest/api/portfolio.py | 63 +- src/kyro/rest/client.py | 24 +- tests/conftest.py | 17 +- tests/test_api_modules.py | 69 +- 14 files changed, 6896 insertions(+), 105 deletions(-) create mode 100644 openapi.yaml create mode 100644 src/kyro/models.py diff --git a/API_REFERENCE.md b/API_REFERENCE.md index f65f926..4092435 100644 --- a/API_REFERENCE.md +++ b/API_REFERENCE.md @@ -1,11 +1,12 @@ # Kyro API Reference -Request/response documentation for every modular method: **exchange**, **markets**, **events**, **orders**, **portfolio**, **search**. +Request/response documentation for every modular method: **exchange**, **markets**, **events**, **orders**, **portfolio**, **search**. Aligned with the Kalshi OpenAPI spec. - **Import:** `from kyro.rest import exchange, markets, events, orders, portfolio, search` - **Client:** Pass `RestClient` as the first argument: `await exchange.get_exchange_status(client)` - **Base path:** `{KyroConfig.base_url}` (e.g. `https://api.elections.kalshi.com/trade-api/v2`) - **Auth:** Endpoints marked *Auth required* need `KyroConfig(auth_headers={...})` with KALSHI-ACCESS-KEY, TIMESTAMP, SIGNATURE. +- **Type safety:** Request bodies for `create_order`, `amend_order`, `decrease_order`, `batch_create_orders`, and `transfer_between_subaccounts` are validated before sending. Invalid values (e.g. `side` not in `yes`/`no`, `yes_price` outside 1–99, or `decrease_order` with both `reduce_by` and `reduce_to`) raise `KyroValidationError`. Enums and models: `from kyro.models import Side, Action, OrderType, TimeInForce, CreateOrderRequest, ...` --- @@ -83,7 +84,7 @@ data = await exchange.get_series_fee_changes(client) ### `get_user_data_timestamp` -**HTTP:** `GET /exchange/user-data-timestamp` +**HTTP:** `GET /exchange/user_data_timestamp` **Auth:** Yes **Usage:** @@ -91,7 +92,7 @@ data = await exchange.get_series_fee_changes(client) data = await exchange.get_user_data_timestamp(client) ``` -**Response (200):** `{ "timestamp": 1704067200 }` or similar +**Response (200):** `{ "as_of_time": "2025-01-15T12:00:00Z" }` (date-time when user data was last validated; sync with GetBalance, GetOrders, GetFills, GetPositions) --- @@ -355,9 +356,15 @@ data = await markets.get_market_candlesticks( **Usage:** ```python -data = await markets.get_series(client, "KXBTC") +data = await markets.get_series(client, "KXBTC", include_volume=False) ``` +**Query parameters** + +| Parameter | Type | Description | +|------------------|------|----------------------------------------------| +| `include_volume` | bool | If true, includes total volume for the series| + **Response (200):** ```json { @@ -378,15 +385,27 @@ data = await markets.get_series(client, "KXBTC") **Usage:** ```python -data = await markets.get_series_list(client, limit=20, cursor=None) +data = await markets.get_series_list( + client, + limit=20, + cursor=None, + category=None, + tags=None, + include_product_metadata=False, + include_volume=False, +) ``` **Query parameters** -| Parameter | Type | Description | -|-----------|------|-------------------| -| `limit` | int | Per page | -| `cursor` | str | Pagination cursor | +| Parameter | Type | Description | +|---------------------------|------|------------------------------------------------| +| `limit` | int | Per page | +| `cursor` | str | Pagination cursor | +| `category` | str | Filter by category | +| `tags` | str | Filter by tags | +| `include_product_metadata`| bool | Include product metadata in each series | +| `include_volume` | bool | Include total volume for each series | **Response (200):** ```json @@ -405,6 +424,8 @@ data = await markets.get_series_list(client, limit=20, cursor=None) **HTTP:** `GET /live-data?ticker={ticker}` **Auth:** No +Convenience helper using ticker. The OpenAPI spec defines `GET /live_data/{type}/milestone/{milestone_id}` and `GET /live_data/batch?milestone_ids=...`; these helpers use the ticker-based `/live-data` pattern. + **Usage:** ```python data = await markets.get_live_data(client, "KXBTC-24JAN15") @@ -419,6 +440,8 @@ data = await markets.get_live_data(client, "KXBTC-24JAN15") **HTTP:** `GET /live-data?tickers={ticker1},{ticker2},...` **Auth:** No +Convenience helper using comma-separated tickers. The OpenAPI spec defines `GET /live_data/batch` with `milestone_ids`; this helper uses the ticker-based `/live-data` pattern. + **Usage:** ```python data = await markets.get_multiple_live_data(client, "KXBTC-24JAN15,KXETH-24JAN15") @@ -565,15 +588,25 @@ If `start_ts`/`end_ts` omitted, uses last 24h. **Usage:** ```python -data = await events.get_multivariate_events(client, limit=100, cursor=None) +data = await events.get_multivariate_events( + client, + limit=100, + cursor=None, + series_ticker=None, + collection_ticker=None, + with_nested_markets=False, +) ``` **Query parameters** -| Parameter | Type | Description | -|-----------|------|-------------------| -| `limit` | int | Per page | -| `cursor` | str | Pagination cursor | +| Parameter | Type | Description | +|-----------------------|------|------------------------------------------| +| `limit` | int | Per page (1–200, default 100) | +| `cursor` | str | Pagination cursor | +| `series_ticker` | str | Filter by series (mutually exclusive with collection_ticker) | +| `collection_ticker` | str | Filter by collection ticker | +| `with_nested_markets` | bool | Include markets in each event | **Response (200):** `{ "events": [...], "cursor": "..." }` (multivariate shape per Kalshi) @@ -879,6 +912,8 @@ data = await orders.decrease_order(client, "ord-abc123", reduce_to=1) **HTTP:** `POST /portfolio/orders/batched` **Auth:** Yes +Each order is validated with `CreateOrderRequest`; invalid values raise `KyroValidationError` (message includes the failing index). Same body shape as `create_order` per item. + **Usage:** ```python data = await orders.batch_create_orders(client, [ @@ -889,7 +924,7 @@ data = await orders.batch_create_orders(client, [ **Body:** `{ "orders": [ { ... }, ... ] }` — each object same shape as `create_order`. -**Response (200):** `{ "orders": [ { ... }, ... ] }` +**Response (201):** `{ "orders": [ { "client_order_id"?, "order"?, "error"? }, ... ] }` per Kalshi --- @@ -966,19 +1001,21 @@ data = await portfolio.get_positions( ticker=None, event_ticker=None, subaccount=None, + settlement_status=None, ) ``` **Query parameters** -| Parameter | Type | Description | -|----------------|------|--------------------------------------------| -| `cursor` | str | Pagination cursor | -| `limit` | int | Per page (1–1000) | -| `count_filter` | str | `position`, `total_traded` (comma-separated)| -| `ticker` | str | Filter by market ticker | -| `event_ticker` | str | Filter by event ticker | -| `subaccount` | int | Filter by subaccount | +| Parameter | Type | Description | +|--------------------|------|--------------------------------------------| +| `cursor` | str | Pagination cursor | +| `limit` | int | Per page (1–1000) | +| `count_filter` | str | `position`, `total_traded` (comma-separated)| +| `ticker` | str | Filter by market ticker | +| `event_ticker` | str | Filter by event ticker | +| `subaccount` | int | Filter by subaccount | +| `settlement_status`| str | `all`, `unsettled`, `settled` (default unsettled) | **Response (200):** ```json @@ -1013,6 +1050,7 @@ data = await portfolio.get_positions( data = await portfolio.get_fills( client, ticker=None, + order_id=None, event_ticker=None, min_ts=None, max_ts=None, @@ -1027,6 +1065,7 @@ data = await portfolio.get_fills( | Parameter | Type | Description | |----------------|------|-------------------------| | `ticker` | str | Filter by market | +| `order_id` | str | Filter by order ID | | `event_ticker` | str | Filter by event | | `min_ts` | int | Min time (Unix) | | `max_ts` | int | Max time (Unix) | @@ -1053,11 +1092,19 @@ data = await portfolio.get_settlements( max_ts=None, limit=100, cursor=None, - subaccount=None, ) ``` -**Query parameters:** same as `get_fills`. +**Query parameters** + +| Parameter | Type | Description | +|----------------|------|-------------------------| +| `ticker` | str | Filter by market | +| `event_ticker` | str | Filter by event | +| `min_ts` | int | Min time (Unix) | +| `max_ts` | int | Max time (Unix) | +| `limit` | int | Per page | +| `cursor` | str | Pagination cursor | **Response (200):** `{ "settlements": [...], "cursor": "..." }` @@ -1084,43 +1131,40 @@ data = await portfolio.get_total_resting_order_value(client) **Usage:** ```python -data = await portfolio.create_subaccount(client, nickname="trading-1") +data = await portfolio.create_subaccount(client) ``` -**Body parameters** - -| Parameter | Type | Description | -|------------|------|-----------------| -| `nickname` | str | Optional label | - -**Response (200):** `{ "subaccount": { ... } }` per Kalshi +No request body (OpenAPI). **Response (201):** `{ "subaccount_number": 1 }` (1–32) --- ### `transfer_between_subaccounts` -**HTTP:** `POST /portfolio/transfers` +**HTTP:** `POST /portfolio/subaccounts/transfer` **Auth:** Yes **Usage:** ```python +import uuid data = await portfolio.transfer_between_subaccounts( client, + client_transfer_id=str(uuid.uuid4()), from_subaccount=0, to_subaccount=1, - amount=10000, + amount_cents=10000, ) ``` **Body parameters** -| Parameter | Type | Description | -|-------------------|------|--------------------| -| `from_subaccount` | int | Source (0 = main) | -| `to_subaccount` | int | Destination | -| `amount` | int | Amount in cents | +| Parameter | Type | Description | +|---------------------|------|--------------------------------------| +| `client_transfer_id`| str | Required; idempotency (e.g. UUID) | +| `from_subaccount` | int | Source 0–32 (0 = primary) | +| `to_subaccount` | int | Destination 0–32 | +| `amount_cents` | int | Amount in cents | -**Response (200):** `{ "transfer": { ... } }` per Kalshi +Invalid values raise `KyroValidationError`. **Response (200):** empty or transfer info per Kalshi --- diff --git a/README.md b/README.md index 93de870..ed3fe7d 100644 --- a/README.md +++ b/README.md @@ -338,7 +338,7 @@ async with RestClient(KyroConfig()) as client: try: await markets.get_market(client, "NONEXISTENT-TICKER") except KyroHTTPError as e: - # e.status, e.response_body, e.error_code — all set from the Kalshi response + # e.status, e.response_body, e.error_code, e.error_message, e.error_details if e.status == 404: print("Not found:", e.error_code) elif e.status in (401, 403): @@ -355,7 +355,7 @@ async with RestClient(KyroConfig()) as client: ### Example error output -Real tracebacks from a run. Each exception carries the relevant attributes (`e.status`, `e.response_body`, `e.error_code`, `e.timeout`, `e.details`)—branch or log right away, no parsing. +Real tracebacks from a run. Each exception carries the relevant attributes (`e.status`, `e.response_body`, `e.error_code`, `e.error_message`, `e.error_details`, `e.timeout`, `e.details`)—branch or log right away, no parsing. **`KyroHTTPError`** (4xx/5xx from Kalshi): diff --git a/openapi.yaml b/openapi.yaml new file mode 100644 index 0000000..4942128 --- /dev/null +++ b/openapi.yaml @@ -0,0 +1,6385 @@ +openapi: 3.0.0 +info: + title: Kalshi Trade API Manual Endpoints + version: 3.6.0 + description: Manually defined OpenAPI spec for endpoints being migrated to spec-first approach + +servers: + - url: https://api.elections.kalshi.com/trade-api/v2 + description: Production server + +paths: + /exchange/status: + get: + operationId: GetExchangeStatus + summary: Get Exchange Status + description: ' Endpoint for getting the exchange status.' + tags: + - exchange + responses: + '200': + description: Exchange status retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/ExchangeStatus' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/ExchangeStatus' + '503': + description: Service unavailable + content: + application/json: + schema: + $ref: '#/components/schemas/ExchangeStatus' + '504': + description: Gateway timeout + content: + application/json: + schema: + $ref: '#/components/schemas/ExchangeStatus' + + /exchange/announcements: + get: + operationId: GetExchangeAnnouncements + summary: Get Exchange Announcements + description: ' Endpoint for getting all exchange-wide announcements.' + tags: + - exchange + responses: + '200': + description: Exchange announcements retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/GetExchangeAnnouncementsResponse' + '500': + description: Internal server error + + /series/fee_changes: + get: + operationId: GetSeriesFeeChanges + summary: Get Series Fee Changes + tags: + - exchange + parameters: + - name: series_ticker + in: query + required: false + schema: + type: string + x-go-type-skip-optional-pointer: true + - name: show_historical + in: query + required: false + schema: + type: boolean + default: false + x-go-type-skip-optional-pointer: true + responses: + '200': + description: Series fee changes retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/GetSeriesFeeChangesResponse' + '400': + $ref: '#/components/responses/BadRequestError' + '500': + $ref: '#/components/responses/InternalServerError' + + /exchange/schedule: + get: + operationId: GetExchangeSchedule + summary: Get Exchange Schedule + description: ' Endpoint for getting the exchange schedule.' + tags: + - exchange + responses: + '200': + description: Exchange schedule retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/GetExchangeScheduleResponse' + '500': + description: Internal server error + + /exchange/user_data_timestamp: + get: + operationId: GetUserDataTimestamp + summary: Get User Data Timestamp + description: ' There is typically a short delay before exchange events are reflected in the API endpoints. Whenever possible, combine API responses to PUT/POST/DELETE requests with websocket data to obtain the most accurate view of the exchange state. This endpoint provides an approximate indication of when the data from the following endpoints was last validated: GetBalance, GetOrder(s), GetFills, GetPositions' + tags: + - exchange + responses: + '200': + description: User data timestamp retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/GetUserDataTimestampResponse' + '500': + description: Internal server error + + /portfolio/orders: + get: + operationId: GetOrders + summary: Get Orders + description: 'Restricts the response to orders that have a certain status: resting, canceled, or executed.' + tags: + - orders + security: + - kalshiAccessKey: [] + kalshiAccessSignature: [] + kalshiAccessTimestamp: [] + parameters: + - $ref: '#/components/parameters/TickerQuery' + - $ref: '#/components/parameters/EventTickerQuery' + - $ref: '#/components/parameters/MinTsQuery' + - $ref: '#/components/parameters/MaxTsQuery' + - $ref: '#/components/parameters/StatusQuery' + - $ref: '#/components/parameters/LimitQuery' + - $ref: '#/components/parameters/CursorQuery' + - $ref: '#/components/parameters/SubaccountQuery' + responses: + '200': + description: Orders retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/GetOrdersResponse' + '400': + $ref: '#/components/responses/BadRequestError' + '401': + $ref: '#/components/responses/UnauthorizedError' + '500': + $ref: '#/components/responses/InternalServerError' + + post: + operationId: CreateOrder + summary: Create Order + description: ' Endpoint for submitting orders in a market. Each user is limited to 200 000 open orders at a time.' + tags: + - orders + security: + - kalshiAccessKey: [] + kalshiAccessSignature: [] + kalshiAccessTimestamp: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateOrderRequest' + responses: + '201': + description: Order created successfully + content: + application/json: + schema: + $ref: '#/components/schemas/CreateOrderResponse' + '400': + $ref: '#/components/responses/BadRequestError' + '401': + $ref: '#/components/responses/UnauthorizedError' + '409': + $ref: '#/components/responses/ConflictError' + '429': + $ref: '#/components/responses/RateLimitError' + '500': + $ref: '#/components/responses/InternalServerError' + + /portfolio/orders/{order_id}: + get: + operationId: GetOrder + summary: Get Order + description: ' Endpoint for getting a single order.' + tags: + - orders + security: + - kalshiAccessKey: [] + kalshiAccessSignature: [] + kalshiAccessTimestamp: [] + parameters: + - $ref: '#/components/parameters/OrderIdPath' + responses: + '200': + description: Order retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/GetOrderResponse' + '401': + $ref: '#/components/responses/UnauthorizedError' + '404': + $ref: '#/components/responses/NotFoundError' + '500': + $ref: '#/components/responses/InternalServerError' + + delete: + operationId: CancelOrder + summary: Cancel Order + description: ' Endpoint for canceling orders. The value for the orderId should match the id field of the order you want to decrease. Commonly, DELETE-type endpoints return 204 status with no body content on success. But we can''t completely delete the order, as it may be partially filled already. Instead, the DeleteOrder endpoint reduce the order completely, essentially zeroing the remaining resting contracts on it. The zeroed order is returned on the response payload as a form of validation for the client.' + tags: + - orders + security: + - kalshiAccessKey: [] + kalshiAccessSignature: [] + kalshiAccessTimestamp: [] + parameters: + - $ref: '#/components/parameters/OrderIdPath' + responses: + '200': + description: Order cancelled successfully + content: + application/json: + schema: + $ref: '#/components/schemas/CancelOrderResponse' + '401': + $ref: '#/components/responses/UnauthorizedError' + '404': + $ref: '#/components/responses/NotFoundError' + '500': + $ref: '#/components/responses/InternalServerError' + + /portfolio/orders/batched: + post: + operationId: BatchCreateOrders + summary: Batch Create Orders + description: ' Endpoint for submitting a batch of orders. Each order in the batch is counted against the total rate limit for order operations. Consequently, the size of the batch is capped by the current per-second rate-limit configuration applicable to the user. At the moment of writing, the limit is 20 orders per batch.' + tags: + - orders + security: + - kalshiAccessKey: [] + kalshiAccessSignature: [] + kalshiAccessTimestamp: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/BatchCreateOrdersRequest' + responses: + '201': + description: Batch order creation completed + content: + application/json: + schema: + $ref: '#/components/schemas/BatchCreateOrdersResponse' + '400': + $ref: '#/components/responses/BadRequestError' + '401': + $ref: '#/components/responses/UnauthorizedError' + '403': + $ref: '#/components/responses/ForbiddenError' + '500': + $ref: '#/components/responses/InternalServerError' + + delete: + operationId: BatchCancelOrders + summary: Batch Cancel Orders + description: ' Endpoint for cancelling up to 20 orders at once.' + tags: + - orders + security: + - kalshiAccessKey: [] + kalshiAccessSignature: [] + kalshiAccessTimestamp: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/BatchCancelOrdersRequest' + responses: + '200': + description: Batch order cancellation completed + content: + application/json: + schema: + $ref: '#/components/schemas/BatchCancelOrdersResponse' + '400': + $ref: '#/components/responses/BadRequestError' + '401': + $ref: '#/components/responses/UnauthorizedError' + '403': + $ref: '#/components/responses/ForbiddenError' + '500': + $ref: '#/components/responses/InternalServerError' + + /portfolio/orders/{order_id}/amend: + post: + operationId: AmendOrder + summary: Amend Order + description: ' Endpoint for amending the max number of fillable contracts and/or price in an existing order. Max fillable contracts is `remaining_count` + `fill_count`.' + tags: + - orders + security: + - kalshiAccessKey: [] + kalshiAccessSignature: [] + kalshiAccessTimestamp: [] + parameters: + - $ref: '#/components/parameters/OrderIdPath' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/AmendOrderRequest' + responses: + '200': + description: Order amended successfully + content: + application/json: + schema: + $ref: '#/components/schemas/AmendOrderResponse' + '400': + $ref: '#/components/responses/BadRequestError' + '401': + $ref: '#/components/responses/UnauthorizedError' + '404': + $ref: '#/components/responses/NotFoundError' + '500': + $ref: '#/components/responses/InternalServerError' + + /portfolio/orders/{order_id}/decrease: + post: + operationId: DecreaseOrder + summary: Decrease Order + description: ' Endpoint for decreasing the number of contracts in an existing order. This is the only kind of edit available on order quantity. Cancelling an order is equivalent to decreasing an order amount to zero.' + tags: + - orders + security: + - kalshiAccessKey: [] + kalshiAccessSignature: [] + kalshiAccessTimestamp: [] + parameters: + - $ref: '#/components/parameters/OrderIdPath' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/DecreaseOrderRequest' + responses: + '200': + description: Order decreased successfully + content: + application/json: + schema: + $ref: '#/components/schemas/DecreaseOrderResponse' + '400': + $ref: '#/components/responses/BadRequestError' + '401': + $ref: '#/components/responses/UnauthorizedError' + '404': + $ref: '#/components/responses/NotFoundError' + '500': + $ref: '#/components/responses/InternalServerError' + + /portfolio/orders/queue_positions: + get: + operationId: GetOrderQueuePositions + summary: Get Queue Positions for Orders + description: ' Endpoint for getting queue positions for all resting orders. Queue position represents the number of contracts that need to be matched before an order receives a partial or full match, determined using price-time priority.' + tags: + - orders + security: + - kalshiAccessKey: [] + kalshiAccessSignature: [] + kalshiAccessTimestamp: [] + parameters: + - name: market_tickers + in: query + description: Comma-separated list of market tickers to filter by + schema: + type: string + - name: event_ticker + in: query + description: Event ticker to filter by + schema: + type: string + responses: + '200': + description: Queue positions retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/GetOrderQueuePositionsResponse' + '400': + $ref: '#/components/responses/BadRequestError' + '401': + $ref: '#/components/responses/UnauthorizedError' + '500': + $ref: '#/components/responses/InternalServerError' + + /portfolio/orders/{order_id}/queue_position: + get: + operationId: GetOrderQueuePosition + summary: Get Order Queue Position + description: ' Endpoint for getting an order''s queue position in the order book. This represents the amount of orders that need to be matched before this order receives a partial or full match. Queue position is determined using a price-time priority.' + tags: + - orders + security: + - kalshiAccessKey: [] + kalshiAccessSignature: [] + kalshiAccessTimestamp: [] + parameters: + - $ref: '#/components/parameters/OrderIdPath' + responses: + '200': + description: Queue position retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/GetOrderQueuePositionResponse' + '401': + $ref: '#/components/responses/UnauthorizedError' + '404': + $ref: '#/components/responses/NotFoundError' + '500': + $ref: '#/components/responses/InternalServerError' + + /portfolio/order_groups: + get: + operationId: GetOrderGroups + summary: Get Order Groups + description: ' Retrieves all order groups for the authenticated user.' + tags: + - order-groups + security: + - kalshiAccessKey: [] + kalshiAccessSignature: [] + kalshiAccessTimestamp: [] + responses: + '200': + description: Order groups retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/GetOrderGroupsResponse' + '400': + $ref: '#/components/responses/BadRequestError' + '401': + $ref: '#/components/responses/UnauthorizedError' + '500': + $ref: '#/components/responses/InternalServerError' + + /portfolio/order_groups/create: + post: + operationId: CreateOrderGroup + summary: Create Order Group + description: ' Creates a new order group with a contracts limit measured over a rolling 15-second window. When the limit is hit, all orders in the group are cancelled and no new orders can be placed until reset.' + tags: + - order-groups + security: + - kalshiAccessKey: [] + kalshiAccessSignature: [] + kalshiAccessTimestamp: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateOrderGroupRequest' + responses: + '201': + description: Order group created successfully + content: + application/json: + schema: + $ref: '#/components/schemas/CreateOrderGroupResponse' + '400': + $ref: '#/components/responses/BadRequestError' + '401': + $ref: '#/components/responses/UnauthorizedError' + '500': + $ref: '#/components/responses/InternalServerError' + + /portfolio/order_groups/{order_group_id}: + get: + operationId: GetOrderGroup + summary: Get Order Group + description: ' Retrieves details for a single order group including all order IDs and auto-cancel status.' + tags: + - order-groups + security: + - kalshiAccessKey: [] + kalshiAccessSignature: [] + kalshiAccessTimestamp: [] + parameters: + - $ref: '#/components/parameters/OrderGroupIdPath' + responses: + '200': + description: Order group retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/GetOrderGroupResponse' + '401': + $ref: '#/components/responses/UnauthorizedError' + '404': + $ref: '#/components/responses/NotFoundError' + '500': + $ref: '#/components/responses/InternalServerError' + delete: + operationId: DeleteOrderGroup + summary: Delete Order Group + description: ' Deletes an order group and cancels all orders within it. This permanently removes the group.' + tags: + - order-groups + security: + - kalshiAccessKey: [] + kalshiAccessSignature: [] + kalshiAccessTimestamp: [] + parameters: + - $ref: '#/components/parameters/OrderGroupIdPath' + responses: + '200': + description: Order group deleted successfully + content: + application/json: + schema: + $ref: '#/components/schemas/EmptyResponse' + '401': + $ref: '#/components/responses/UnauthorizedError' + '404': + $ref: '#/components/responses/NotFoundError' + '500': + $ref: '#/components/responses/InternalServerError' + + /portfolio/order_groups/{order_group_id}/reset: + put: + operationId: ResetOrderGroup + summary: Reset Order Group + description: ' Resets the order group''s matched contracts counter to zero, allowing new orders to be placed again after the limit was hit.' + tags: + - order-groups + security: + - kalshiAccessKey: [] + kalshiAccessSignature: [] + kalshiAccessTimestamp: [] + parameters: + - $ref: '#/components/parameters/OrderGroupIdPath' + requestBody: + required: false + content: + application/json: + schema: + $ref: '#/components/schemas/EmptyResponse' + responses: + '200': + description: Order group reset successfully + content: + application/json: + schema: + $ref: '#/components/schemas/EmptyResponse' + '401': + $ref: '#/components/responses/UnauthorizedError' + '404': + $ref: '#/components/responses/NotFoundError' + '500': + $ref: '#/components/responses/InternalServerError' + + /portfolio/order_groups/{order_group_id}/trigger: + put: + operationId: TriggerOrderGroup + summary: Trigger Order Group + description: ' Triggers the order group, canceling all orders in the group and preventing new orders until the group is reset.' + tags: + - order-groups + security: + - kalshiAccessKey: [] + kalshiAccessSignature: [] + kalshiAccessTimestamp: [] + parameters: + - $ref: '#/components/parameters/OrderGroupIdPath' + requestBody: + required: false + content: + application/json: + schema: + $ref: '#/components/schemas/EmptyResponse' + responses: + '200': + description: Order group triggered successfully + content: + application/json: + schema: + $ref: '#/components/schemas/EmptyResponse' + '401': + $ref: '#/components/responses/UnauthorizedError' + '404': + $ref: '#/components/responses/NotFoundError' + '500': + $ref: '#/components/responses/InternalServerError' + + /portfolio/order_groups/{order_group_id}/limit: + put: + operationId: UpdateOrderGroupLimit + summary: Update Order Group Limit + description: ' Updates the order group contracts limit (rolling 15-second window). If the updated limit would immediately trigger the group, all orders in the group are canceled and the group is triggered.' + tags: + - order-groups + security: + - kalshiAccessKey: [] + kalshiAccessSignature: [] + kalshiAccessTimestamp: [] + parameters: + - $ref: '#/components/parameters/OrderGroupIdPath' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateOrderGroupLimitRequest' + responses: + '200': + description: Order group limit updated successfully + content: + application/json: + schema: + $ref: '#/components/schemas/EmptyResponse' + '400': + $ref: '#/components/responses/BadRequestError' + '401': + $ref: '#/components/responses/UnauthorizedError' + '404': + $ref: '#/components/responses/NotFoundError' + '500': + $ref: '#/components/responses/InternalServerError' + + # Portfolio endpoints + /portfolio/balance: + get: + operationId: GetBalance + summary: Get Balance + description: ' Endpoint for getting the balance and portfolio value of a member. Both values are returned in cents.' + tags: + - portfolio + security: + - kalshiAccessKey: [] + kalshiAccessSignature: [] + kalshiAccessTimestamp: [] + responses: + '200': + description: Balance retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/GetBalanceResponse' + '401': + $ref: '#/components/responses/UnauthorizedError' + '500': + $ref: '#/components/responses/InternalServerError' + + /portfolio/subaccounts: + post: + operationId: CreateSubaccount + summary: Create Subaccount + description: 'Creates a new subaccount for the authenticated user. Subaccounts are numbered sequentially starting from 1. Maximum 32 subaccounts per user.' + tags: + - portfolio + security: + - kalshiAccessKey: [] + kalshiAccessSignature: [] + kalshiAccessTimestamp: [] + responses: + '201': + description: Subaccount created successfully + content: + application/json: + schema: + $ref: '#/components/schemas/CreateSubaccountResponse' + '400': + $ref: '#/components/responses/BadRequestError' + '401': + $ref: '#/components/responses/UnauthorizedError' + '500': + $ref: '#/components/responses/InternalServerError' + + /portfolio/subaccounts/transfer: + post: + operationId: ApplySubaccountTransfer + summary: Transfer Between Subaccounts + description: 'Transfers funds between the authenticated user''s subaccounts. Use 0 for the primary account, or 1-32 for numbered subaccounts.' + tags: + - portfolio + security: + - kalshiAccessKey: [] + kalshiAccessSignature: [] + kalshiAccessTimestamp: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ApplySubaccountTransferRequest' + responses: + '200': + description: Transfer completed successfully + content: + application/json: + schema: + $ref: '#/components/schemas/ApplySubaccountTransferResponse' + '400': + $ref: '#/components/responses/BadRequestError' + '401': + $ref: '#/components/responses/UnauthorizedError' + '500': + $ref: '#/components/responses/InternalServerError' + + /portfolio/subaccounts/balances: + get: + operationId: GetSubaccountBalances + summary: Get All Subaccount Balances + description: 'Gets balances for all subaccounts including the primary account.' + tags: + - portfolio + security: + - kalshiAccessKey: [] + kalshiAccessSignature: [] + kalshiAccessTimestamp: [] + responses: + '200': + description: Balances retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/GetSubaccountBalancesResponse' + '401': + $ref: '#/components/responses/UnauthorizedError' + '500': + $ref: '#/components/responses/InternalServerError' + + /portfolio/subaccounts/transfers: + get: + operationId: GetSubaccountTransfers + summary: Get Subaccount Transfers + description: 'Gets a paginated list of all transfers between subaccounts for the authenticated user.' + tags: + - portfolio + security: + - kalshiAccessKey: [] + kalshiAccessSignature: [] + kalshiAccessTimestamp: [] + parameters: + - $ref: '#/components/parameters/LimitQuery' + - $ref: '#/components/parameters/CursorQuery' + responses: + '200': + description: Transfers retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/GetSubaccountTransfersResponse' + '401': + $ref: '#/components/responses/UnauthorizedError' + '500': + $ref: '#/components/responses/InternalServerError' + + /portfolio/positions: + get: + operationId: GetPositions + summary: Get Positions + description: 'Restricts the positions to those with any of following fields with non-zero values, as a comma separated list. The following values are accepted: position, total_traded' + tags: + - portfolio + security: + - kalshiAccessKey: [] + kalshiAccessSignature: [] + kalshiAccessTimestamp: [] + parameters: + - $ref: '#/components/parameters/PositionsCursorQuery' + - $ref: '#/components/parameters/PositionsLimitQuery' + - $ref: '#/components/parameters/CountFilterQuery' + - $ref: '#/components/parameters/TickerQuery' + - $ref: '#/components/parameters/EventTickerQuery' + - $ref: '#/components/parameters/SubaccountQuery' + responses: + '200': + description: Positions retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/GetPositionsResponse' + '400': + $ref: '#/components/responses/BadRequestError' + '401': + $ref: '#/components/responses/UnauthorizedError' + '500': + $ref: '#/components/responses/InternalServerError' + + /portfolio/settlements: + get: + operationId: GetSettlements + summary: Get Settlements + description: ' Endpoint for getting the member''s settlements historical track.' + tags: + - portfolio + security: + - kalshiAccessKey: [] + kalshiAccessSignature: [] + kalshiAccessTimestamp: [] + parameters: + - $ref: '#/components/parameters/LimitQuery' + - $ref: '#/components/parameters/CursorQuery' + - $ref: '#/components/parameters/TickerQuery' + - $ref: '#/components/parameters/EventTickerQuery' + - $ref: '#/components/parameters/MinTsQuery' + - $ref: '#/components/parameters/MaxTsQuery' + responses: + '200': + description: Settlements retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/GetSettlementsResponse' + '400': + $ref: '#/components/responses/BadRequestError' + '401': + $ref: '#/components/responses/UnauthorizedError' + '500': + $ref: '#/components/responses/InternalServerError' + + /portfolio/summary/total_resting_order_value: + get: + operationId: GetPortfolioRestingOrderTotalValue + summary: Get Total Resting Order Value + description: ' Endpoint for getting the total value, in cents, of resting orders. This endpoint is only intended for use by FCM members (rare). Note: If you''re uncertain about this endpoint, it likely does not apply to you.' + tags: + - portfolio + security: + - kalshiAccessKey: [] + kalshiAccessSignature: [] + kalshiAccessTimestamp: [] + responses: + '200': + description: Total resting order value retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/GetPortfolioRestingOrderTotalValueResponse' + '401': + $ref: '#/components/responses/UnauthorizedError' + '500': + $ref: '#/components/responses/InternalServerError' + + /portfolio/fills: + get: + operationId: GetFills + summary: Get Fills + description: ' Endpoint for getting all fills for the member. A fill is when a trade you have is matched.' + tags: + - portfolio + security: + - kalshiAccessKey: [] + kalshiAccessSignature: [] + kalshiAccessTimestamp: [] + parameters: + - $ref: '#/components/parameters/TickerQuery' + - $ref: '#/components/parameters/OrderIdQuery' + - $ref: '#/components/parameters/MinTsQuery' + - $ref: '#/components/parameters/MaxTsQuery' + - $ref: '#/components/parameters/LimitQuery' + - $ref: '#/components/parameters/CursorQuery' + - $ref: '#/components/parameters/SubaccountQuery' + responses: + '200': + description: Fills retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/GetFillsResponse' + '400': + description: Bad request + '401': + description: Unauthorized + '500': + description: Internal server error + + /api_keys: + get: + operationId: GetApiKeys + summary: Get API Keys + description: ' Endpoint for retrieving all API keys associated with the authenticated user. API keys allow programmatic access to the platform without requiring username/password authentication. Each key has a unique identifier and name.' + tags: + - api-keys + security: + - kalshiAccessKey: [] + kalshiAccessSignature: [] + kalshiAccessTimestamp: [] + responses: + '200': + description: List of API keys retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/GetApiKeysResponse' + '401': + description: Unauthorized + '500': + description: Internal server error + + post: + operationId: CreateApiKey + summary: Create API Key + description: ' Endpoint for creating a new API key with a user-provided public key. This endpoint allows users with Premier or Market Maker API usage levels to create API keys by providing their own RSA public key. The platform will use this public key to verify signatures on API requests.' + tags: + - api-keys + security: + - kalshiAccessKey: [] + kalshiAccessSignature: [] + kalshiAccessTimestamp: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateApiKeyRequest' + responses: + '201': + description: API key created successfully + content: + application/json: + schema: + $ref: '#/components/schemas/CreateApiKeyResponse' + '400': + description: Bad request - invalid input + '401': + description: Unauthorized + '403': + description: Forbidden - insufficient API usage level + '500': + description: Internal server error + + /api_keys/generate: + post: + operationId: GenerateApiKey + summary: Generate API Key + description: ' Endpoint for generating a new API key with an automatically created key pair. This endpoint generates both a public and private RSA key pair. The public key is stored on the platform, while the private key is returned to the user and must be stored securely. The private key cannot be retrieved again.' + tags: + - api-keys + security: + - kalshiAccessKey: [] + kalshiAccessSignature: [] + kalshiAccessTimestamp: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/GenerateApiKeyRequest' + responses: + '201': + description: API key generated successfully + content: + application/json: + schema: + $ref: '#/components/schemas/GenerateApiKeyResponse' + '400': + description: Bad request - invalid input + '401': + description: Unauthorized + '500': + description: Internal server error + + /api_keys/{api_key}: + delete: + operationId: DeleteApiKey + summary: Delete API Key + description: ' Endpoint for deleting an existing API key. This endpoint permanently deletes an API key. Once deleted, the key can no longer be used for authentication. This action cannot be undone.' + tags: + - api-keys + security: + - kalshiAccessKey: [] + kalshiAccessSignature: [] + kalshiAccessTimestamp: [] + parameters: + - name: api_key + in: path + required: true + description: API key ID to delete + schema: + type: string + responses: + '204': + description: API key successfully deleted + '400': + description: Bad request - invalid API key ID + '401': + description: Unauthorized + '404': + description: API key not found + '500': + description: Internal server error + + /search/tags_by_categories: + get: + operationId: GetTagsForSeriesCategories + summary: Get Tags for Series Categories + description: | + Retrieve tags organized by series categories. + + This endpoint returns a mapping of series categories to their associated tags, which can be used for filtering and search functionality. + tags: + - search + responses: + '200': + description: Tags retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/GetTagsForSeriesCategoriesResponse' + '401': + description: Unauthorized + '500': + description: Internal server error + + /search/filters_by_sport: + get: + operationId: GetFiltersForSports + summary: Get Filters for Sports + description: | + Retrieve available filters organized by sport. + + This endpoint returns filtering options available for each sport, including scopes and competitions. It also provides an ordered list of sports for display purposes. + tags: + - search + responses: + '200': + description: Filters retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/GetFiltersBySportsResponse' + '401': + description: Unauthorized + '500': + description: Internal server error + + /account/limits: + get: + operationId: GetAccountApiLimits + summary: Get Account API Limits + description: ' Endpoint to retrieve the API tier limits associated with the authenticated user.' + tags: + - account + security: + - kalshiAccessKey: [] + kalshiAccessSignature: [] + kalshiAccessTimestamp: [] + responses: + '200': + description: Account API tier limits retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/GetAccountApiLimitsResponse' + '401': + description: Unauthorized + '500': + description: Internal server error + + /series/{series_ticker}/markets/{ticker}/candlesticks: + get: + operationId: GetMarketCandlesticks + summary: Get Market Candlesticks + description: 'Time period length of each candlestick in minutes. Valid values: 1 (1 minute), 60 (1 hour), 1440 (1 day).' + tags: + - market + parameters: + - name: series_ticker + in: path + required: true + description: Series ticker - the series that contains the target market + schema: + type: string + - name: ticker + in: path + required: true + description: Market ticker - unique identifier for the specific market + schema: + type: string + - name: start_ts + in: query + required: true + description: Start timestamp (Unix timestamp). Candlesticks will include those ending on or after this time. + schema: + type: integer + format: int64 + - name: end_ts + in: query + required: true + description: End timestamp (Unix timestamp). Candlesticks will include those ending on or before this time. + schema: + type: integer + format: int64 + - name: period_interval + in: query + required: true + description: Time period length of each candlestick in minutes. Valid values are 1 (1 minute), 60 (1 hour), or 1440 (1 day). + schema: + type: integer + enum: [1, 60, 1440] + x-oapi-codegen-extra-tags: + validate: "required,oneof=1 60 1440" + - name: include_latest_before_start + in: query + required: false + description: | + If true, prepends the latest candlestick available before the start_ts. This synthetic candlestick is created by: + 1. Finding the most recent real candlestick before start_ts + 2. Projecting it forward to the first period boundary (calculated as the next period interval after start_ts) + 3. Setting all OHLC prices to null, and `previous_price` to the close price from the real candlestick + schema: + type: boolean + default: false + responses: + '200': + description: Candlesticks retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/GetMarketCandlesticksResponse' + '400': + description: Bad request + '404': + description: Not found + '500': + description: Internal server error + + /markets/trades: + get: + operationId: GetTrades + summary: Get Trades + description: ' Endpoint for getting all trades for all markets. A trade represents a completed transaction between two users on a specific market. Each trade includes the market ticker, price, quantity, and timestamp information. This endpoint returns a paginated response. Use the ''limit'' parameter to control page size (1-1000, defaults to 100). The response includes a ''cursor'' field - pass this value in the ''cursor'' parameter of your next request to get the next page. An empty cursor indicates no more pages are available.' + tags: + - market + parameters: + - $ref: '#/components/parameters/MarketLimitQuery' + - $ref: '#/components/parameters/CursorQuery' + - $ref: '#/components/parameters/TickerQuery' + - $ref: '#/components/parameters/MinTsQuery' + - $ref: '#/components/parameters/MaxTsQuery' + responses: + '200': + description: Trades retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/GetTradesResponse' + '400': + description: Bad request + '500': + description: Internal server error + + /series/{series_ticker}/events/{ticker}/candlesticks: + get: + operationId: GetMarketCandlesticksByEvent + summary: Get Event Candlesticks + description: ' End-point for returning aggregated data across all markets corresponding to an event.' + tags: + - events + parameters: + - name: ticker + in: path + required: true + description: The event ticker + schema: + type: string + - name: series_ticker + in: path + required: true + description: The series ticker + schema: + type: string + - name: start_ts + in: query + required: true + description: Start timestamp for the range + schema: + type: integer + format: int64 + x-oapi-codegen-extra-tags: + validate: "required" + - name: end_ts + in: query + required: true + description: End timestamp for the range + schema: + type: integer + format: int64 + x-oapi-codegen-extra-tags: + validate: "required" + - name: period_interval + in: query + required: true + description: Specifies the length of each candlestick period, in minutes. Must be one minute, one hour, or one day. + schema: + type: integer + format: int32 + enum: [1, 60, 1440] + x-oapi-codegen-extra-tags: + validate: "required,oneof=1 60 1440" + responses: + '200': + description: Event candlesticks retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/GetEventCandlesticksResponse' + '400': + description: Bad request + '401': + description: Unauthorized + '500': + description: Internal server error + + /events: + get: + operationId: GetEvents + summary: Get Events + description: | + Get all events. This endpoint excludes multivariate events. + To retrieve multivariate events, use the GET /events/multivariate endpoint. + tags: + - events + parameters: + - name: limit + in: query + required: false + description: Parameter to specify the number of results per page. Defaults to 200. Maximum value is 200. + schema: + type: integer + minimum: 1 + maximum: 200 + default: 200 + - name: cursor + in: query + required: false + description: Parameter to specify the pagination cursor. Use the cursor value returned from the previous response to get the next page of results. Leave empty for the first page. + schema: + type: string + - name: with_nested_markets + in: query + required: false + description: Parameter to specify if nested markets should be included in the response. When true, each event will include a 'markets' field containing a list of Market objects associated with that event. + schema: + type: boolean + default: false + x-go-type-skip-optional-pointer: true + - name: with_milestones + in: query + required: false + description: If true, includes related milestones as a field alongside events. + schema: + type: boolean + default: false + x-go-type-skip-optional-pointer: true + - name: status + in: query + required: false + description: Filter by event status. Possible values are 'open', 'closed', 'settled'. Leave empty to return events with any status. + schema: + type: string + enum: ['open', 'closed', 'settled'] + - $ref: '#/components/parameters/SeriesTickerQuery' + - name: min_close_ts + in: query + required: false + description: Filter events with at least one market with close timestamp greater than this Unix timestamp (in seconds). + schema: + type: integer + format: int64 + responses: + '200': + description: Events retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/GetEventsResponse' + '400': + description: Bad request + '401': + description: Unauthorized + '500': + description: Internal server error + + /events/multivariate: + get: + operationId: GetMultivariateEvents + summary: Get Multivariate Events + description: 'Retrieve multivariate (combo) events. These are dynamically created events from multivariate event collections. Supports filtering by series and collection ticker.' + tags: + - events + parameters: + - name: limit + in: query + required: false + description: Number of results per page. Defaults to 100. Maximum value is 200. + schema: + type: integer + minimum: 1 + maximum: 200 + default: 100 + - name: cursor + in: query + required: false + description: Pagination cursor. Use the cursor value returned from the previous response to get the next page of results. + schema: + type: string + - $ref: '#/components/parameters/SeriesTickerQuery' + - name: collection_ticker + in: query + required: false + description: Filter events by collection ticker. Returns only multivariate events belonging to the specified collection. Cannot be used together with series_ticker. + schema: + type: string + - name: with_nested_markets + in: query + required: false + description: Parameter to specify if nested markets should be included in the response. When true, each event will include a 'markets' field containing a list of Market objects associated with that event. + schema: + type: boolean + default: false + responses: + '200': + description: Multivariate events retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/GetMultivariateEventsResponse' + '400': + description: Bad request - invalid parameters + '401': + description: Unauthorized + '500': + description: Internal server error + + /events/{event_ticker}: + get: + operationId: GetEvent + summary: Get Event + description: ' Endpoint for getting data about an event by its ticker. An event represents a real-world occurrence that can be traded on, such as an election, sports game, or economic indicator release. Events contain one or more markets where users can place trades on different outcomes.' + tags: + - events + parameters: + - name: event_ticker + in: path + required: true + description: Event ticker + schema: + type: string + - name: with_nested_markets + in: query + required: false + description: If true, markets are included within the event object. If false (default), markets are returned as a separate top-level field in the response. + schema: + type: boolean + default: false + x-go-type-skip-optional-pointer: true + responses: + '200': + description: Event retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/GetEventResponse' + '400': + description: Bad request + '404': + description: Event not found + '401': + description: Unauthorized + '500': + description: Internal server error + + /events/{event_ticker}/metadata: + get: + operationId: GetEventMetadata + summary: Get Event Metadata + description: ' Endpoint for getting metadata about an event by its ticker. Returns only the metadata information for an event.' + tags: + - events + parameters: + - name: event_ticker + in: path + required: true + description: Event ticker + schema: + type: string + responses: + '200': + description: Event metadata retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/GetEventMetadataResponse' + '400': + description: Bad request + '404': + description: Event not found + '401': + description: Unauthorized + '500': + description: Internal server error + + /series/{series_ticker}/events/{ticker}/forecast_percentile_history: + get: + operationId: GetEventForecastPercentilesHistory + summary: Get Event Forecast Percentile History + description: Endpoint for getting the historical raw and formatted forecast numbers for an event at specific percentiles. + tags: + - events + security: + - kalshiAccessKey: [] + kalshiAccessSignature: [] + kalshiAccessTimestamp: [] + parameters: + - name: ticker + in: path + required: true + description: The event ticker + schema: + type: string + - name: series_ticker + in: path + required: true + description: The series ticker + schema: + type: string + - name: percentiles + in: query + required: true + description: Array of percentile values to retrieve (0-10000, max 10 values) + schema: + type: array + items: + type: integer + format: int32 + minimum: 0 + maximum: 10000 + maxItems: 10 + style: form + explode: true + - name: start_ts + in: query + required: true + description: Start timestamp for the range + schema: + type: integer + format: int64 + - name: end_ts + in: query + required: true + description: End timestamp for the range + schema: + type: integer + format: int64 + - name: period_interval + in: query + required: true + description: Specifies the length of each forecast period, in minutes. 0 for 5-second intervals, or 1, 60, or 1440 for minute-based intervals. + schema: + type: integer + format: int32 + enum: [0, 1, 60, 1440] + responses: + '200': + description: Event forecast percentile history retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/GetEventForecastPercentilesHistoryResponse' + '400': + description: Bad request + '401': + description: Unauthorized + '500': + description: Internal server error + + /live_data/{type}/milestone/{milestone_id}: + get: + operationId: GetLiveData + summary: Get Live Data + description: Get live data for a specific milestone + tags: + - live-data + parameters: + - name: type + in: path + required: true + description: Type of live data + schema: + type: string + - name: milestone_id + in: path + required: true + description: Milestone ID + schema: + type: string + responses: + '200': + description: Live data retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/GetLiveDataResponse' + '404': + description: Live data not found + '500': + description: Internal server error + + /live_data/batch: + get: + operationId: GetLiveDatas + summary: Get Multiple Live Data + description: Get live data for multiple milestones + tags: + - live-data + parameters: + - name: milestone_ids + in: query + required: true + description: Array of milestone IDs + schema: + type: array + items: + type: string + maxItems: 100 + style: form + explode: true + responses: + '200': + description: Live data retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/GetLiveDatasResponse' + '500': + description: Internal server error + + /incentive_programs: + get: + operationId: GetIncentivePrograms + summary: Get Incentives + description: ' List incentives with optional filters. Incentives are rewards programs for trading activity on specific markets.' + tags: + - incentive-programs + parameters: + - name: status + in: query + required: false + description: 'Status filter. Can be "all", "active", "upcoming", "closed", or "paid_out". Default is "all".' + schema: + type: string + enum: [all, active, upcoming, closed, paid_out] + - name: type + in: query + required: false + description: 'Type filter. Can be "all", "liquidity", or "volume". Default is "all".' + schema: + type: string + enum: [all, liquidity, volume] + - name: limit + in: query + required: false + description: Number of results per page. Defaults to 100. Maximum value is 10000. + schema: + type: integer + minimum: 1 + maximum: 10000 + - name: cursor + in: query + required: false + description: Cursor for pagination + schema: + type: string + responses: + '200': + description: Incentive programs retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/GetIncentiveProgramsResponse' + '400': + description: Invalid request parameters + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + + /fcm/orders: + get: + operationId: GetFCMOrders + summary: Get FCM Orders + description: | + Endpoint for FCM members to get orders filtered by subtrader ID. + This endpoint requires FCM member access level and allows filtering orders by subtrader ID. + tags: + - fcm + security: + - kalshiAccessKey: [] + kalshiAccessSignature: [] + kalshiAccessTimestamp: [] + parameters: + - name: subtrader_id + in: query + required: true + description: Restricts the response to orders for a specific subtrader (FCM members only) + schema: + type: string + - $ref: '#/components/parameters/CursorQuery' + - $ref: '#/components/parameters/EventTickerQuery' + - $ref: '#/components/parameters/TickerQuery' + - name: min_ts + in: query + description: Restricts the response to orders after a timestamp, formatted as a Unix Timestamp + schema: + type: integer + format: int64 + - name: max_ts + in: query + description: Restricts the response to orders before a timestamp, formatted as a Unix Timestamp + schema: + type: integer + format: int64 + - name: status + in: query + description: Restricts the response to orders that have a certain status + schema: + type: string + enum: [resting, canceled, executed] + - name: limit + in: query + description: Parameter to specify the number of results per page. Defaults to 100 + schema: + type: integer + minimum: 1 + maximum: 1000 + responses: + '200': + description: Orders retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/GetOrdersResponse' + '400': + description: Bad request + '401': + description: Unauthorized + '404': + description: Not found + '500': + description: Internal server error + + /fcm/positions: + get: + operationId: GetFCMPositions + summary: Get FCM Positions + description: | + Endpoint for FCM members to get market positions filtered by subtrader ID. + This endpoint requires FCM member access level and allows filtering positions by subtrader ID. + tags: + - fcm + security: + - kalshiAccessKey: [] + kalshiAccessSignature: [] + kalshiAccessTimestamp: [] + parameters: + - name: subtrader_id + in: query + required: true + description: Restricts the response to positions for a specific subtrader (FCM members only) + schema: + type: string + - name: ticker + in: query + description: Ticker of desired positions + schema: + type: string + x-go-type-skip-optional-pointer: true + - name: event_ticker + in: query + description: Event ticker of desired positions + schema: + type: string + x-go-type-skip-optional-pointer: true + - name: count_filter + in: query + description: Restricts the positions to those with any of following fields with non-zero values, as a comma separated list + schema: + type: string + - name: settlement_status + in: query + description: Settlement status of the markets to return. Defaults to unsettled + schema: + type: string + enum: [all, unsettled, settled] + - name: limit + in: query + description: Parameter to specify the number of results per page. Defaults to 100 + schema: + type: integer + minimum: 1 + maximum: 1000 + - name: cursor + in: query + description: The Cursor represents a pointer to the next page of records in the pagination + schema: + type: string + responses: + '200': + description: Positions retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/GetPositionsResponse' + '400': + description: Bad request + '401': + description: Unauthorized + '404': + description: Not found + '500': + description: Internal server error + + /structured_targets: + get: + operationId: GetStructuredTargets + summary: Get Structured Targets + description: 'Page size (min: 1, max: 2000)' + tags: + - structured-targets + parameters: + - name: type + in: query + description: Filter by structured target type + required: false + schema: + type: string + - name: competition + in: query + description: Filter by competition + required: false + schema: + type: string + - name: page_size + in: query + description: Number of items per page (min 1, max 2000, default 100) + required: false + schema: + type: integer + format: int32 + minimum: 1 + maximum: 2000 + default: 100 + - name: cursor + in: query + description: Pagination cursor + required: false + schema: + type: string + responses: + '200': + description: Structured targets retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/GetStructuredTargetsResponse' + '401': + description: Unauthorized + '500': + description: Internal server error + + /structured_targets/{structured_target_id}: + get: + operationId: GetStructuredTarget + summary: Get Structured Target + description: ' Endpoint for getting data about a specific structured target by its ID.' + tags: + - structured-targets + parameters: + - name: structured_target_id + in: path + required: true + description: Structured target ID + schema: + type: string + responses: + '200': + description: Structured target retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/GetStructuredTargetResponse' + '401': + description: Unauthorized + '404': + description: Not found + '500': + description: Internal server error + + /markets/{ticker}/orderbook: + get: + operationId: GetMarketOrderbook + summary: Get Market Orderbook + description: ' Endpoint for getting the current order book for a specific market. The order book shows all active bid orders for both yes and no sides of a binary market. It returns yes bids and no bids only (no asks are returned). This is because in binary markets, a bid for yes at price X is equivalent to an ask for no at price (100-X). For example, a yes bid at 7¢ is the same as a no ask at 93¢, with identical contract sizes. Each side shows price levels with their corresponding quantities and order counts, organized from best to worst prices.' + tags: + - market + security: + - kalshiAccessKey: [] + kalshiAccessSignature: [] + kalshiAccessTimestamp: [] + parameters: + - $ref: '#/components/parameters/TickerPath' + - name: depth + in: query + description: Depth of the orderbook to retrieve (0 or negative means all levels, 1-100 for specific depth) + required: false + schema: + type: integer + minimum: 0 + maximum: 100 + default: 0 + x-oapi-codegen-extra-tags: + validate: omitempty,min=0,max=100 + responses: + '200': + description: Orderbook retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/GetMarketOrderbookResponse' + '401': + $ref: '#/components/responses/UnauthorizedError' + '404': + $ref: '#/components/responses/NotFoundError' + '500': + $ref: '#/components/responses/InternalServerError' + + /milestones/{milestone_id}: + get: + operationId: GetMilestone + summary: Get Milestone + description: ' Endpoint for getting data about a specific milestone by its ID.' + tags: + - milestone + parameters: + - name: milestone_id + in: path + required: true + description: Milestone ID + schema: + type: string + responses: + '200': + description: Milestone retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/GetMilestoneResponse' + '400': + description: Bad Request + '401': + description: Unauthorized + '404': + description: Not Found + '500': + description: Internal Server Error + + /milestones: + get: + operationId: GetMilestones + summary: Get Milestones + description: 'Minimum start date to filter milestones. Format: RFC3339 timestamp' + tags: + - milestone + parameters: + - name: limit + in: query + description: Number of milestones to return per page + required: true + schema: + type: integer + minimum: 1 + maximum: 500 + - name: minimum_start_date + in: query + description: Minimum start date to filter milestones. Format RFC3339 timestamp + required: false + schema: + type: string + format: date-time + - name: category + in: query + description: Filter by milestone category + required: false + schema: + type: string + - name: competition + in: query + description: Filter by competition + required: false + schema: + type: string + - name: source_id + in: query + description: Filter by source id + required: false + schema: + type: string + - name: type + in: query + description: Filter by milestone type + required: false + schema: + type: string + - name: related_event_ticker + in: query + description: Filter by related event ticker + required: false + schema: + type: string + - name: cursor + in: query + description: Pagination cursor. Use the cursor value returned from the previous response to get the next page of results + required: false + schema: + type: string + responses: + '200': + description: Milestones retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/GetMilestonesResponse' + '400': + description: Bad Request + '401': + description: Unauthorized + '500': + description: Internal Server Error + + # Communications endpoints + /communications/id: + get: + operationId: GetCommunicationsID + summary: Get Communications ID + description: ' Endpoint for getting the communications ID of the logged-in user.' + tags: + - communications + security: + - kalshiAccessKey: [] + kalshiAccessSignature: [] + kalshiAccessTimestamp: [] + responses: + '200': + description: Communications ID retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/GetCommunicationsIDResponse' + '401': + $ref: '#/components/responses/UnauthorizedError' + '500': + $ref: '#/components/responses/InternalServerError' + + /communications/rfqs: + get: + operationId: GetRFQs + summary: Get RFQs + description: ' Endpoint for getting RFQs' + tags: + - communications + security: + - kalshiAccessKey: [] + kalshiAccessSignature: [] + kalshiAccessTimestamp: [] + parameters: + - $ref: '#/components/parameters/CursorQuery' + - $ref: '#/components/parameters/EventTickerQuery' + - $ref: '#/components/parameters/MarketTickerQuery' + - name: limit + in: query + description: Parameter to specify the number of results per page. Defaults to 100. + schema: + type: integer + format: int32 + minimum: 1 + maximum: 100 + default: 100 + - name: status + in: query + description: Filter RFQs by status + schema: + type: string + - name: creator_user_id + in: query + description: Filter RFQs by creator user ID + schema: + type: string + responses: + '200': + description: RFQs retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/GetRFQsResponse' + '401': + $ref: '#/components/responses/UnauthorizedError' + '500': + $ref: '#/components/responses/InternalServerError' + + post: + operationId: CreateRFQ + summary: Create RFQ + description: ' Endpoint for creating a new RFQ. You can have a maximum of 100 open RFQs at a time.' + tags: + - communications + security: + - kalshiAccessKey: [] + kalshiAccessSignature: [] + kalshiAccessTimestamp: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateRFQRequest' + responses: + '201': + description: RFQ created successfully + content: + application/json: + schema: + $ref: '#/components/schemas/CreateRFQResponse' + '400': + $ref: '#/components/responses/BadRequestError' + '401': + $ref: '#/components/responses/UnauthorizedError' + '409': + $ref: '#/components/responses/ConflictError' + '500': + $ref: '#/components/responses/InternalServerError' + + /communications/rfqs/{rfq_id}: + get: + operationId: GetRFQ + summary: Get RFQ + description: ' Endpoint for getting a single RFQ by id' + tags: + - communications + security: + - kalshiAccessKey: [] + kalshiAccessSignature: [] + kalshiAccessTimestamp: [] + parameters: + - $ref: '#/components/parameters/RfqIdPath' + responses: + '200': + description: RFQ retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/GetRFQResponse' + '401': + $ref: '#/components/responses/UnauthorizedError' + '404': + $ref: '#/components/responses/NotFoundError' + '500': + $ref: '#/components/responses/InternalServerError' + + delete: + operationId: DeleteRFQ + summary: Delete RFQ + description: ' Endpoint for deleting an RFQ by ID' + tags: + - communications + security: + - kalshiAccessKey: [] + kalshiAccessSignature: [] + kalshiAccessTimestamp: [] + parameters: + - $ref: '#/components/parameters/RfqIdPath' + responses: + '204': + description: RFQ deleted successfully + '401': + $ref: '#/components/responses/UnauthorizedError' + '404': + $ref: '#/components/responses/NotFoundError' + '500': + $ref: '#/components/responses/InternalServerError' + + /communications/quotes: + get: + operationId: GetQuotes + summary: Get Quotes + description: ' Endpoint for getting quotes' + tags: + - communications + security: + - kalshiAccessKey: [] + kalshiAccessSignature: [] + kalshiAccessTimestamp: [] + parameters: + - $ref: '#/components/parameters/CursorQuery' + - $ref: '#/components/parameters/EventTickerQuery' + - $ref: '#/components/parameters/MarketTickerQuery' + - name: limit + in: query + description: Parameter to specify the number of results per page. Defaults to 500. + schema: + type: integer + format: int32 + minimum: 1 + maximum: 500 + default: 500 + - name: status + in: query + description: Filter quotes by status + schema: + type: string + x-go-type-skip-optional-pointer: true + - name: quote_creator_user_id + in: query + description: Filter quotes by quote creator user ID + schema: + type: string + x-go-type-skip-optional-pointer: true + - name: rfq_creator_user_id + in: query + description: Filter quotes by RFQ creator user ID + schema: + type: string + x-go-type-skip-optional-pointer: true + - name: rfq_creator_subtrader_id + in: query + description: Filter quotes by RFQ creator subtrader ID (FCM members only) + schema: + type: string + x-go-type-skip-optional-pointer: true + - name: rfq_id + in: query + description: Filter quotes by RFQ ID + schema: + type: string + x-go-type-skip-optional-pointer: true + responses: + '200': + description: Quotes retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/GetQuotesResponse' + '401': + $ref: '#/components/responses/UnauthorizedError' + '500': + $ref: '#/components/responses/InternalServerError' + + post: + operationId: CreateQuote + summary: Create Quote + description: ' Endpoint for creating a quote in response to an RFQ' + tags: + - communications + security: + - kalshiAccessKey: [] + kalshiAccessSignature: [] + kalshiAccessTimestamp: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateQuoteRequest' + responses: + '201': + description: Quote created successfully + content: + application/json: + schema: + $ref: '#/components/schemas/CreateQuoteResponse' + '400': + $ref: '#/components/responses/BadRequestError' + '401': + $ref: '#/components/responses/UnauthorizedError' + '500': + $ref: '#/components/responses/InternalServerError' + + /communications/quotes/{quote_id}: + get: + operationId: GetQuote + summary: Get Quote + description: ' Endpoint for getting a particular quote' + tags: + - communications + security: + - kalshiAccessKey: [] + kalshiAccessSignature: [] + kalshiAccessTimestamp: [] + parameters: + - $ref: '#/components/parameters/QuoteIdPath' + responses: + '200': + description: Quote retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/GetQuoteResponse' + '401': + $ref: '#/components/responses/UnauthorizedError' + '404': + $ref: '#/components/responses/NotFoundError' + '500': + $ref: '#/components/responses/InternalServerError' + + delete: + operationId: DeleteQuote + summary: Delete Quote + description: ' Endpoint for deleting a quote, which means it can no longer be accepted.' + tags: + - communications + security: + - kalshiAccessKey: [] + kalshiAccessSignature: [] + kalshiAccessTimestamp: [] + parameters: + - $ref: '#/components/parameters/QuoteIdPath' + responses: + '204': + description: Quote deleted successfully + '401': + $ref: '#/components/responses/UnauthorizedError' + '404': + $ref: '#/components/responses/NotFoundError' + '500': + $ref: '#/components/responses/InternalServerError' + + /communications/quotes/{quote_id}/accept: + put: + operationId: AcceptQuote + summary: Accept Quote + description: ' Endpoint for accepting a quote. This will require the quoter to confirm' + tags: + - communications + security: + - kalshiAccessKey: [] + kalshiAccessSignature: [] + kalshiAccessTimestamp: [] + parameters: + - $ref: '#/components/parameters/QuoteIdPath' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/AcceptQuoteRequest' + responses: + '204': + description: Quote accepted successfully + '400': + $ref: '#/components/responses/BadRequestError' + '401': + $ref: '#/components/responses/UnauthorizedError' + '404': + $ref: '#/components/responses/NotFoundError' + '500': + $ref: '#/components/responses/InternalServerError' + + /communications/quotes/{quote_id}/confirm: + put: + operationId: ConfirmQuote + summary: Confirm Quote + description: ' Endpoint for confirming a quote. This will start a timer for order execution' + tags: + - communications + security: + - kalshiAccessKey: [] + kalshiAccessSignature: [] + kalshiAccessTimestamp: [] + parameters: + - $ref: '#/components/parameters/QuoteIdPath' + requestBody: + required: false + content: + application/json: + schema: + $ref: '#/components/schemas/EmptyResponse' + responses: + '204': + description: Quote confirmed successfully + '401': + $ref: '#/components/responses/UnauthorizedError' + '404': + $ref: '#/components/responses/NotFoundError' + '500': + $ref: '#/components/responses/InternalServerError' + + # Multivariate Event Collections endpoints + /multivariate_event_collections/{collection_ticker}: + get: + operationId: GetMultivariateEventCollection + summary: Get Multivariate Event Collection + description: ' Endpoint for getting data about a multivariate event collection by its ticker.' + tags: + - multivariate + parameters: + - name: collection_ticker + in: path + required: true + description: Collection ticker + schema: + type: string + responses: + '200': + description: Collection retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/GetMultivariateEventCollectionResponse' + '400': + $ref: '#/components/responses/BadRequestError' + '404': + $ref: '#/components/responses/NotFoundError' + '500': + $ref: '#/components/responses/InternalServerError' + post: + operationId: CreateMarketInMultivariateEventCollection + summary: Create Market In Multivariate Event Collection + description: ' Endpoint for creating an individual market in a multivariate event collection. This endpoint must be hit at least once before trading or looking up a market.' + tags: + - multivariate + security: + - kalshiAccessKey: [] + kalshiAccessSignature: [] + kalshiAccessTimestamp: [] + parameters: + - name: collection_ticker + in: path + required: true + description: Collection ticker + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateMarketInMultivariateEventCollectionRequest' + responses: + '200': + description: Market created successfully + content: + application/json: + schema: + $ref: '#/components/schemas/CreateMarketInMultivariateEventCollectionResponse' + '400': + $ref: '#/components/responses/BadRequestError' + '401': + $ref: '#/components/responses/UnauthorizedError' + '500': + $ref: '#/components/responses/InternalServerError' + + /multivariate_event_collections: + get: + operationId: GetMultivariateEventCollections + summary: Get Multivariate Event Collections + description: ' Endpoint for getting data about multivariate event collections.' + tags: + - multivariate + parameters: + - name: status + in: query + description: Only return collections of a certain status. Can be unopened, open, or closed. + schema: + type: string + enum: [unopened, open, closed] + - name: associated_event_ticker + in: query + description: Only return collections associated with a particular event ticker. + schema: + type: string + - name: series_ticker + in: query + description: Only return collections with a particular series ticker. + schema: + type: string + - name: limit + in: query + description: Specify the maximum number of results. + schema: + type: integer + format: int32 + minimum: 1 + maximum: 200 + - name: cursor + in: query + description: The Cursor represents a pointer to the next page of records in the pagination. This optional parameter, when filled, should be filled with the cursor string returned in a previous request to this end-point. + schema: + type: string + responses: + '200': + description: Collections retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/GetMultivariateEventCollectionsResponse' + '400': + $ref: '#/components/responses/BadRequestError' + '500': + $ref: '#/components/responses/InternalServerError' + + /multivariate_event_collections/{collection_ticker}/lookup: + put: + operationId: LookupTickersForMarketInMultivariateEventCollection + summary: Lookup Tickers For Market In Multivariate Event Collection + description: ' Endpoint for looking up an individual market in a multivariate event collection. If CreateMarketInMultivariateEventCollection has never been hit with that variable combination before, this will return a 404.' + tags: + - multivariate + security: + - kalshiAccessKey: [] + kalshiAccessSignature: [] + kalshiAccessTimestamp: [] + parameters: + - name: collection_ticker + in: path + required: true + description: Collection ticker + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/LookupTickersForMarketInMultivariateEventCollectionRequest' + responses: + '200': + description: Market looked up successfully + content: + application/json: + schema: + $ref: '#/components/schemas/LookupTickersForMarketInMultivariateEventCollectionResponse' + '400': + $ref: '#/components/responses/BadRequestError' + '401': + $ref: '#/components/responses/UnauthorizedError' + '404': + $ref: '#/components/responses/NotFoundError' + '500': + $ref: '#/components/responses/InternalServerError' + get: + operationId: GetMultivariateEventCollectionLookupHistory + summary: Get Multivariate Event Collection Lookup History + description: ' Endpoint for retrieving which markets in an event collection were recently looked up.' + tags: + - multivariate + parameters: + - name: collection_ticker + in: path + required: true + description: Collection ticker + schema: + type: string + - name: lookback_seconds + in: query + required: true + description: Number of seconds to look back for lookup history. Must be one of 10, 60, 300, or 3600. + schema: + type: integer + format: int32 + enum: [10, 60, 300, 3600] + responses: + '200': + description: Lookup history retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/GetMultivariateEventCollectionLookupHistoryResponse' + '400': + $ref: '#/components/responses/BadRequestError' + '500': + $ref: '#/components/responses/InternalServerError' + + /series/{series_ticker}: + get: + operationId: GetSeries + summary: Get Series + description: ' Endpoint for getting data about a specific series by its ticker. A series represents a template for recurring events that follow the same format and rules (e.g., "Monthly Jobs Report", "Weekly Initial Jobless Claims", "Daily Weather in NYC"). Series define the structure, settlement sources, and metadata that will be applied to each recurring event instance within that series.' + tags: + - market + parameters: + - name: series_ticker + in: path + required: true + schema: + type: string + description: The ticker of the series to retrieve + - name: include_volume + in: query + required: false + schema: + type: boolean + default: false + x-go-type-skip-optional-pointer: true + description: If true, includes the total volume traded across all events in this series. + responses: + '200': + description: Series retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/GetSeriesResponse' + '400': + $ref: '#/components/responses/BadRequestError' + '500': + $ref: '#/components/responses/InternalServerError' + + /series: + get: + operationId: GetSeriesList + summary: Get Series List + description: ' Endpoint for getting data about multiple series with specified filters. A series represents a template for recurring events that follow the same format and rules (e.g., "Monthly Jobs Report", "Weekly Initial Jobless Claims", "Daily Weather in NYC"). This endpoint allows you to browse and discover available series templates by category.' + tags: + - market + parameters: + - name: category + in: query + required: false + schema: + type: string + x-go-type-skip-optional-pointer: true + - name: tags + in: query + required: false + schema: + type: string + x-go-type-skip-optional-pointer: true + - name: include_product_metadata + in: query + required: false + schema: + type: boolean + default: false + x-go-type-skip-optional-pointer: true + - name: include_volume + in: query + required: false + schema: + type: boolean + default: false + x-go-type-skip-optional-pointer: true + description: If true, includes the total volume traded across all events in each series. + responses: + '200': + description: Series list retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/GetSeriesListResponse' + '400': + $ref: '#/components/responses/BadRequestError' + '500': + $ref: '#/components/responses/InternalServerError' + + /markets: + get: + operationId: GetMarkets + summary: Get Markets + description: | + Filter by market status. Possible values: `unopened`, `open`, `closed`, `settled`. Leave empty to return markets with any status. + - Only one `status` filter may be supplied at a time. + - Timestamp filters will be mutually exclusive from other timestamp filters and certain status filters. + + | Compatible Timestamp Filters | Additional Status Filters| Extra Notes | + |------------------------------|--------------------------|-------------| + | min_created_ts, max_created_ts | `unopened`, `open`, *empty* | | + | min_close_ts, max_close_ts | `closed`, *empty* | | + | min_settled_ts, max_settled_ts | `settled`, *empty* | | + | min_updated_ts | *empty* | Incompatible with all filters besides `mve_filter=exclude` | + + tags: + - market + parameters: + - $ref: '#/components/parameters/MarketLimitQuery' + - $ref: '#/components/parameters/CursorQuery' + - $ref: '#/components/parameters/EventTickerQuery' + - $ref: '#/components/parameters/SeriesTickerQuery' + - $ref: '#/components/parameters/MinCreatedTsQuery' + - $ref: '#/components/parameters/MaxCreatedTsQuery' + - $ref: '#/components/parameters/MinUpdatedTsQuery' + - $ref: '#/components/parameters/MaxCloseTsQuery' + - $ref: '#/components/parameters/MinCloseTsQuery' + - $ref: '#/components/parameters/MinSettledTsQuery' + - $ref: '#/components/parameters/MaxSettledTsQuery' + - $ref: '#/components/parameters/MarketStatusQuery' + - $ref: '#/components/parameters/TickersQuery' + - $ref: '#/components/parameters/MveFilterQuery' + responses: + '200': + description: Markets retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/GetMarketsResponse' + '400': + description: Bad request + '401': + description: Unauthorized + '500': + description: Internal server error + + /markets/{ticker}: + get: + operationId: GetMarket + summary: Get Market + description: ' Endpoint for getting data about a specific market by its ticker. A market represents a specific binary outcome within an event that users can trade on (e.g., "Will candidate X win?"). Markets have yes/no positions, current prices, volume, and settlement rules.' + tags: + - market + parameters: + - $ref: '#/components/parameters/TickerPath' + responses: + '200': + description: Market retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/GetMarketResponse' + '401': + description: Unauthorized + '404': + description: Not found + '500': + description: Internal server error + + /markets/candlesticks: + get: + operationId: BatchGetMarketCandlesticks + summary: Batch Get Market Candlesticks + description: | + Endpoint for retrieving candlestick data for multiple markets. + + - Accepts up to 100 market tickers per request + - Returns up to 10,000 candlesticks total across all markets + - Returns candlesticks grouped by market_id + - Optionally includes a synthetic initial candlestick for price continuity (see `include_latest_before_start` parameter) + tags: + - market + parameters: + - name: market_tickers + in: query + required: true + description: Comma-separated list of market tickers (maximum 100) + schema: + type: string + - name: start_ts + in: query + required: true + description: Start timestamp in Unix seconds + schema: + type: integer + format: int64 + - name: end_ts + in: query + required: true + description: End timestamp in Unix seconds + schema: + type: integer + format: int64 + - name: period_interval + in: query + required: true + description: Candlestick period interval in minutes + schema: + type: integer + format: int32 + minimum: 1 + - name: include_latest_before_start + in: query + required: false + description: | + If true, prepends the latest candlestick available before the start_ts. This synthetic candlestick is created by: + 1. Finding the most recent real candlestick before start_ts + 2. Projecting it forward to the first period boundary (calculated as the next period interval after start_ts) + 3. Setting all OHLC prices to null, and `previous_price` to the close price from the real candlestick + schema: + type: boolean + default: false + responses: + '200': + description: Market candlesticks retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/BatchGetMarketCandlesticksResponse' + '400': + description: Bad request + '401': + description: Unauthorized + '500': + description: Internal server error + +components: + securitySchemes: + kalshiAccessKey: + type: apiKey + in: header + name: KALSHI-ACCESS-KEY + description: Your API key ID + kalshiAccessSignature: + type: apiKey + in: header + name: KALSHI-ACCESS-SIGNATURE + description: RSA-PSS signature of the request + kalshiAccessTimestamp: + type: apiKey + in: header + name: KALSHI-ACCESS-TIMESTAMP + description: Request timestamp in milliseconds + + responses: + BadRequestError: + description: Bad request - invalid input + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + UnauthorizedError: + description: Unauthorized - authentication required + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + InternalServerError: + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + NotFoundError: + description: Resource not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + ForbiddenError: + description: Forbidden - insufficient permissions + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + ConflictError: + description: Conflict - resource already exists or cannot be modified + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + RateLimitError: + description: Rate limit exceeded + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + + parameters: + LimitQuery: + name: limit + in: query + description: Number of results per page. Defaults to 100. Maximum value is 200. + schema: + type: integer + format: int64 + minimum: 1 + maximum: 200 + default: 100 + x-oapi-codegen-extra-tags: + validate: "omitempty,min=1,max=200" + + MarketLimitQuery: + name: limit + in: query + description: Number of results per page. Defaults to 100. Maximum value is 1000. + schema: + type: integer + format: int64 + minimum: 1 + maximum: 1000 + default: 100 + x-oapi-codegen-extra-tags: + validate: omitempty,gte=1,lte=1000 + + CursorQuery: + name: cursor + in: query + description: Pagination cursor. Use the cursor value returned from the previous response to get the next page of results. Leave empty for the first page. + schema: + type: string + x-go-type-skip-optional-pointer: true + + StatusQuery: + name: status + in: query + description: Filter by status. Possible values depend on the endpoint. + schema: + type: string + + OrderGroupIdPath: + name: order_group_id + in: path + required: true + description: Order group ID + schema: + type: string + + RfqIdPath: + name: rfq_id + in: path + required: true + description: RFQ ID + schema: + type: string + + QuoteIdPath: + name: quote_id + in: path + required: true + description: Quote ID + schema: + type: string + + MarketTickerQuery: + name: market_ticker + in: query + description: Filter by market ticker + schema: + type: string + x-go-type-skip-optional-pointer: true + + TickerQuery: + name: ticker + in: query + description: Filter by market ticker + schema: + type: string + x-go-type-skip-optional-pointer: true + + EventTickerQuery: + name: event_ticker + in: query + description: Event ticker of desired positions. Multiple event tickers can be provided as a comma-separated list (maximum 10). + schema: + type: string + x-go-type-skip-optional-pointer: true + + PositionsCursorQuery: + name: cursor + in: query + description: The Cursor represents a pointer to the next page of records in the pagination. Use the value returned from the previous response to get the next page. + schema: + type: string + + PositionsLimitQuery: + name: limit + in: query + description: Parameter to specify the number of results per page. Defaults to 100. + schema: + type: integer + format: int32 + minimum: 1 + maximum: 1000 + default: 100 + + CountFilterQuery: + name: count_filter + in: query + description: Restricts the positions to those with any of following fields with non-zero values, as a comma separated list. The following values are accepted - position, total_traded + schema: + type: string + + OrderIdQuery: + name: order_id + in: query + description: Filter by order ID + schema: + type: string + x-go-type-skip-optional-pointer: true + + MinTsQuery: + name: min_ts + in: query + description: Filter items after this Unix timestamp + schema: + type: integer + format: int64 + + MaxTsQuery: + name: max_ts + in: query + description: Filter items before this Unix timestamp + schema: + type: integer + format: int64 + + SubaccountQuery: + name: subaccount + in: query + description: Filter by subaccount number + schema: + type: integer + + OrderIdPath: + name: order_id + in: path + required: true + description: Order ID + schema: + type: string + + TickerPath: + name: ticker + in: path + required: true + description: Market ticker + schema: + type: string + + DepthQuery: + name: depth + in: query + description: Depth of the orderbook to retrieve (0 or negative means all levels, 1-100 for specific depth) + schema: + $ref: '#/components/schemas/DepthQuery' + + SeriesTickerQuery: + name: series_ticker + in: query + description: Filter by series ticker + schema: + type: string + x-go-type-skip-optional-pointer: true + + MinCreatedTsQuery: + name: min_created_ts + in: query + description: Filter items that created after this Unix timestamp + schema: + type: integer + format: int64 + + MaxCreatedTsQuery: + name: max_created_ts + in: query + description: Filter items that created before this Unix timestamp + schema: + type: integer + format: int64 + + MinUpdatedTsQuery: + name: min_updated_ts + in: query + description: Return markets updated later than this Unix timestamp. Incompatible with any other filters. + schema: + type: integer + format: int64 + + MaxCloseTsQuery: + name: max_close_ts + in: query + description: Filter items that close before this Unix timestamp + schema: + type: integer + format: int64 + + MinCloseTsQuery: + name: min_close_ts + in: query + description: Filter items that close after this Unix timestamp + schema: + type: integer + format: int64 + + MinSettledTsQuery: + name: min_settled_ts + in: query + description: Filter items that settled after this Unix timestamp + schema: + type: integer + format: int64 + + MaxSettledTsQuery: + name: max_settled_ts + in: query + description: Filter items that settled before this Unix timestamp + schema: + type: integer + format: int64 + + MarketStatusQuery: + name: status + in: query + description: Filter by market status. Leave empty to return markets with any status. + schema: + type: string + enum: [unopened, open, paused, closed, settled] + + TickersQuery: + name: tickers + in: query + description: Filter by specific market tickers. Comma-separated list of market tickers to retrieve. + schema: + type: string + + MveFilterQuery: + name: mve_filter + in: query + description: Filter by multivariate events (combos). 'only' returns only multivariate events, 'exclude' excludes multivariate events. + schema: + type: string + enum: ['only', 'exclude'] + + schemas: + # Common schemas + DepthQuery: + type: integer + description: Depth of the orderbook to retrieve (0 or negative means all levels, 1-100 for specific depth) + minimum: 0 + maximum: 100 + default: 0 + + FixedPointDollars: + type: string + description: US dollar amount as a fixed-point decimal string with exactly 4 decimal places + example: "0.5600" + + FixedPointCount: + type: string + description: Fixed-point contract count string (2 decimals, e.g., "10.00"; referred to as "fp" in field names). Requests accept 0–2 decimal places (e.g., "10", "10.0", "10.00"); responses always emit 2 decimals. Currently only whole contract values are permitted, but the format supports future fractional precision. Integer contract count fields are legacy and will be deprecated; when both integer and fp fields are provided, they must match. + example: "10.00" + + ErrorResponse: + type: object + properties: + code: + type: string + description: Error code + message: + type: string + description: Human-readable error message + details: + type: string + description: Additional details about the error, if available + service: + type: string + description: The name of the service that generated the error + + SelfTradePreventionType: + type: string + enum: ['taker_at_cross', 'maker'] + description: The self-trade prevention type for orders + + OrderStatus: + type: string + enum: ['resting', 'canceled', 'executed'] + description: The status of an order + + ExchangeInstance: + type: string + enum: ['event_contract', 'margined'] + description: The exchange instance type + + ApiKey: + type: object + required: + - api_key_id + - name + - scopes + properties: + api_key_id: + type: string + description: Unique identifier for the API key + name: + type: string + description: User-provided name for the API key + scopes: + type: array + description: List of scopes granted to this API key. Valid values are 'read' and 'write'. + items: + type: string + + GetApiKeysResponse: + type: object + required: + - api_keys + properties: + api_keys: + type: array + description: List of all API keys associated with the user + items: + $ref: '#/components/schemas/ApiKey' + + CreateApiKeyRequest: + type: object + required: + - name + - public_key + properties: + name: + type: string + description: Name for the API key. This helps identify the key's purpose + public_key: + type: string + description: RSA public key in PEM format. This will be used to verify signatures on API requests + scopes: + type: array + description: List of scopes to grant to the API key. Valid values are 'read' and 'write'. If 'write' is included, 'read' must also be included. Defaults to full access (['read', 'write']) if not provided. + items: + type: string + + CreateApiKeyResponse: + type: object + required: + - api_key_id + properties: + api_key_id: + type: string + description: Unique identifier for the newly created API key + + GenerateApiKeyRequest: + type: object + required: + - name + properties: + name: + type: string + description: Name for the API key. This helps identify the key's purpose + scopes: + type: array + description: List of scopes to grant to the API key. Valid values are 'read' and 'write'. If 'write' is included, 'read' must also be included. Defaults to full access (['read', 'write']) if not provided. + items: + type: string + + GenerateApiKeyResponse: + type: object + required: + - api_key_id + - private_key + properties: + api_key_id: + type: string + description: Unique identifier for the newly generated API key + private_key: + type: string + description: RSA private key in PEM format. This must be stored securely and cannot be retrieved again after this response + + GetTagsForSeriesCategoriesResponse: + type: object + required: + - tags_by_categories + properties: + tags_by_categories: + type: object + description: Mapping of series categories to their associated tags + additionalProperties: + type: array + items: + type: string + + ScopeList: + type: object + required: + - scopes + properties: + scopes: + type: array + description: List of scopes + items: + type: string + + SportFilterDetails: + type: object + required: + - scopes + - competitions + properties: + scopes: + type: array + description: List of scopes available for this sport + items: + type: string + competitions: + type: object + description: Mapping of competitions to their scope lists + additionalProperties: + $ref: '#/components/schemas/ScopeList' + + GetFiltersBySportsResponse: + type: object + required: + - filters_by_sports + - sport_ordering + properties: + filters_by_sports: + type: object + description: Mapping of sports to their filter details + additionalProperties: + $ref: '#/components/schemas/SportFilterDetails' + sport_ordering: + type: array + description: Ordered list of sports for display + items: + type: string + + GetAccountApiLimitsResponse: + type: object + required: + - usage_tier + - read_limit + - write_limit + properties: + usage_tier: + type: string + description: User's API usage tier + read_limit: + type: integer + description: Maximum read requests per second + write_limit: + type: integer + description: Maximum write requests per second + + ExchangeStatus: + type: object + required: + - exchange_active + - trading_active + properties: + exchange_active: + type: boolean + description: False if the core Kalshi exchange is no longer taking any state changes at all. This includes but is not limited to trading, new users, and transfers. True unless we are under maintenance. + trading_active: + type: boolean + description: True if we are currently permitting trading on the exchange. This is true during trading hours and false outside exchange hours. Kalshi reserves the right to pause at any time in case issues are detected. + exchange_estimated_resume_time: + type: string + format: date-time + description: Estimated downtime for the current exchange maintenance window. However, this is not guaranteed and can be extended. + nullable: true + + GetExchangeAnnouncementsResponse: + type: object + required: + - announcements + properties: + announcements: + type: array + description: A list of exchange-wide announcements. + items: + $ref: '#/components/schemas/Announcement' + + Announcement: + type: object + required: + - type + - message + - delivery_time + - status + properties: + type: + type: string + enum: [info, warning, error] + description: The type of the announcement. + message: + type: string + description: The message contained within the announcement. + delivery_time: + type: string + format: date-time + description: The time the announcement was delivered. + status: + type: string + enum: [active, inactive] + description: The current status of this announcement. + + GetExchangeScheduleResponse: + type: object + required: + - schedule + properties: + schedule: + $ref: '#/components/schemas/Schedule' + + Schedule: + type: object + required: + - standard_hours + - maintenance_windows + properties: + standard_hours: + type: array + description: The standard operating hours of the exchange. All times are expressed in ET. Outside of these times trading will be unavailable. + items: + $ref: '#/components/schemas/WeeklySchedule' + maintenance_windows: + type: array + description: Scheduled maintenance windows, during which the exchange may be unavailable. + items: + $ref: '#/components/schemas/MaintenanceWindow' + + WeeklySchedule: + type: object + required: + - start_time + - end_time + - monday + - tuesday + - wednesday + - thursday + - friday + - saturday + - sunday + properties: + start_time: + type: string + format: date-time + description: Start date and time for when this weekly schedule is effective. + end_time: + type: string + format: date-time + description: End date and time for when this weekly schedule is no longer effective. + monday: + type: array + description: Trading hours for Monday. May contain multiple sessions. + items: + $ref: '#/components/schemas/DailySchedule' + tuesday: + type: array + description: Trading hours for Tuesday. May contain multiple sessions. + items: + $ref: '#/components/schemas/DailySchedule' + wednesday: + type: array + description: Trading hours for Wednesday. May contain multiple sessions. + items: + $ref: '#/components/schemas/DailySchedule' + thursday: + type: array + description: Trading hours for Thursday. May contain multiple sessions. + items: + $ref: '#/components/schemas/DailySchedule' + friday: + type: array + description: Trading hours for Friday. May contain multiple sessions. + items: + $ref: '#/components/schemas/DailySchedule' + saturday: + type: array + description: Trading hours for Saturday. May contain multiple sessions. + items: + $ref: '#/components/schemas/DailySchedule' + sunday: + type: array + description: Trading hours for Sunday. May contain multiple sessions. + items: + $ref: '#/components/schemas/DailySchedule' + + DailySchedule: + type: object + required: + - open_time + - close_time + properties: + open_time: + type: string + description: Opening time in ET (Eastern Time) format HH:MM. + close_time: + type: string + description: Closing time in ET (Eastern Time) format HH:MM. + + MaintenanceWindow: + type: object + required: + - start_datetime + - end_datetime + properties: + start_datetime: + type: string + format: date-time + description: Start date and time of the maintenance window. + end_datetime: + type: string + format: date-time + description: End date and time of the maintenance window. + + GetUserDataTimestampResponse: + type: object + required: + - as_of_time + properties: + as_of_time: + type: string + format: date-time + description: Timestamp when user data was last updated. + + GetMarketCandlesticksResponse: + type: object + required: + - ticker + - candlesticks + properties: + ticker: + type: string + description: Unique identifier for the market. + candlesticks: + type: array + description: Array of candlestick data points for the specified time range. + items: + $ref: '#/components/schemas/MarketCandlestick' + + GetEventCandlesticksResponse: + type: object + required: + - market_tickers + - market_candlesticks + - adjusted_end_ts + properties: + market_tickers: + type: array + description: Array of market tickers in the event. + items: + type: string + market_candlesticks: + type: array + description: Array of market candlestick arrays, one for each market in the event. + items: + type: array + items: + $ref: '#/components/schemas/MarketCandlestick' + adjusted_end_ts: + type: integer + format: int64 + description: Adjusted end timestamp if the requested candlesticks would be larger than maxAggregateCandidates. + + BatchGetMarketCandlesticksResponse: + type: object + required: + - markets + properties: + markets: + type: array + description: Array of market candlestick data, one entry per requested market. + items: + $ref: '#/components/schemas/MarketCandlesticksResponse' + + MarketCandlesticksResponse: + type: object + required: + - market_ticker + - candlesticks + properties: + market_ticker: + type: string + description: Market ticker string (e.g., 'INXD-24JAN01'). + candlesticks: + type: array + description: Array of candlestick data points for the market. Includes an initial data point at the start timestamp when available. + items: + $ref: '#/components/schemas/MarketCandlestick' + + MarketCandlestick: + type: object + required: + - end_period_ts + - yes_bid + - yes_ask + - price + - volume + - volume_fp + - open_interest + - open_interest_fp + properties: + end_period_ts: + type: integer + format: int64 + description: Unix timestamp for the inclusive end of the candlestick period. + yes_bid: + $ref: '#/components/schemas/BidAskDistribution' + description: Open, high, low, close (OHLC) data for YES buy offers on the market during the candlestick period. + yes_ask: + $ref: '#/components/schemas/BidAskDistribution' + description: Open, high, low, close (OHLC) data for YES sell offers on the market during the candlestick period. + price: + $ref: '#/components/schemas/PriceDistribution' + description: Open, high, low, close (OHLC) and more data for trade YES contract prices on the market during the candlestick period. + volume: + type: integer + format: int64 + description: Number of contracts bought on the market during the candlestick period. + volume_fp: + $ref: '#/components/schemas/FixedPointCount' + description: String representation of the number of contracts bought on the market during the candlestick period. + open_interest: + type: integer + format: int64 + description: Number of contracts bought on the market by end of the candlestick period (end_period_ts). + open_interest_fp: + $ref: '#/components/schemas/FixedPointCount' + description: String representation of the number of contracts bought on the market by end of the candlestick period (end_period_ts). + + BidAskDistribution: + type: object + required: + - open + - open_dollars + - low + - low_dollars + - high + - high_dollars + - close + - close_dollars + properties: + open: + type: integer + description: Offer price on the market at the start of the candlestick period (in cents). + open_dollars: + $ref: '#/components/schemas/FixedPointDollars' + description: Offer price on the market at the start of the candlestick period (in dollars). + low: + type: integer + description: Lowest offer price on the market during the candlestick period (in cents). + low_dollars: + $ref: '#/components/schemas/FixedPointDollars' + description: Lowest offer price on the market during the candlestick period (in dollars). + high: + type: integer + description: Highest offer price on the market during the candlestick period (in cents). + high_dollars: + $ref: '#/components/schemas/FixedPointDollars' + description: Highest offer price on the market during the candlestick period (in dollars). + close: + type: integer + description: Offer price on the market at the end of the candlestick period (in cents). + close_dollars: + $ref: '#/components/schemas/FixedPointDollars' + description: Offer price on the market at the end of the candlestick period (in dollars). + + PriceDistribution: + type: object + properties: + open: + type: integer + nullable: true + description: First traded YES contract price on the market during the candlestick period (in cents). May be null if there was no trade during the period. + open_dollars: + $ref: '#/components/schemas/FixedPointDollars' + nullable: true + description: First traded YES contract price on the market during the candlestick period (in dollars). May be null if there was no trade during the period. + low: + type: integer + nullable: true + description: Lowest traded YES contract price on the market during the candlestick period (in cents). May be null if there was no trade during the period. + low_dollars: + $ref: '#/components/schemas/FixedPointDollars' + nullable: true + description: Lowest traded YES contract price on the market during the candlestick period (in dollars). May be null if there was no trade during the period. + high: + type: integer + nullable: true + description: Highest traded YES contract price on the market during the candlestick period (in cents). May be null if there was no trade during the period. + high_dollars: + $ref: '#/components/schemas/FixedPointDollars' + nullable: true + description: Highest traded YES contract price on the market during the candlestick period (in dollars). May be null if there was no trade during the period. + close: + type: integer + nullable: true + description: Last traded YES contract price on the market during the candlestick period (in cents). May be null if there was no trade during the period. + close_dollars: + $ref: '#/components/schemas/FixedPointDollars' + nullable: true + description: Last traded YES contract price on the market during the candlestick period (in dollars). May be null if there was no trade during the period. + mean: + type: integer + nullable: true + description: Mean traded YES contract price on the market during the candlestick period (in cents). May be null if there was no trade during the period. + mean_dollars: + $ref: '#/components/schemas/FixedPointDollars' + nullable: true + description: Mean traded YES contract price on the market during the candlestick period (in dollars). May be null if there was no trade during the period. + previous: + type: integer + nullable: true + description: Last traded YES contract price on the market before the candlestick period (in cents). May be null if there were no trades before the period. + previous_dollars: + $ref: '#/components/schemas/FixedPointDollars' + nullable: true + description: Last traded YES contract price on the market before the candlestick period (in dollars). May be null if there were no trades before the period. + min: + type: integer + nullable: true + description: Minimum close price of any market during the candlestick period (in cents). + min_dollars: + $ref: '#/components/schemas/FixedPointDollars' + nullable: true + description: Minimum close price of any market during the candlestick period (in dollars). + max: + type: integer + nullable: true + description: Maximum close price of any market during the candlestick period (in cents). + max_dollars: + $ref: '#/components/schemas/FixedPointDollars' + nullable: true + description: Maximum close price of any market during the candlestick period (in dollars). + + # Live Data schemas + LiveData: + type: object + required: + - type + - details + - milestone_id + properties: + type: + type: string + description: Type of live data + details: + type: object + additionalProperties: true + description: Live data details as a flexible object + milestone_id: + type: string + description: Milestone ID + + GetLiveDataResponse: + type: object + required: + - live_data + properties: + live_data: + $ref: '#/components/schemas/LiveData' + + GetLiveDatasResponse: + type: object + required: + - live_datas + properties: + live_datas: + type: array + items: + $ref: '#/components/schemas/LiveData' + + GetBalanceResponse: + type: object + required: + - balance + - portfolio_value + - updated_ts + properties: + balance: + type: integer + format: int64 + description: Member's available balance in cents. This represents the amount available for trading. + portfolio_value: + type: integer + format: int64 + description: Member's portfolio value in cents. This is the current value of all positions held. + updated_ts: + type: integer + format: int64 + description: Unix timestamp of the last update to the balance. + + CreateSubaccountResponse: + type: object + required: + - subaccount_number + properties: + subaccount_number: + type: integer + description: The sequential number assigned to this subaccount (1-32). + + ApplySubaccountTransferRequest: + type: object + required: + - client_transfer_id + - from_subaccount + - to_subaccount + - amount_cents + properties: + client_transfer_id: + type: string + format: uuid + description: Unique client-provided transfer ID for idempotency. + x-oapi-codegen-extra-tags: + validate: "required" + from_subaccount: + type: integer + description: Source subaccount number (0 for primary, 1-32 for numbered subaccounts). + to_subaccount: + type: integer + description: Destination subaccount number (0 for primary, 1-32 for numbered subaccounts). + amount_cents: + type: integer + format: int64 + description: Amount to transfer in cents. + + ApplySubaccountTransferResponse: + type: object + description: Empty response indicating successful transfer. + + GetSubaccountBalancesResponse: + type: object + required: + - subaccount_balances + properties: + subaccount_balances: + type: array + items: + $ref: '#/components/schemas/SubaccountBalance' + + SubaccountBalance: + type: object + required: + - subaccount_number + - balance + - updated_ts + properties: + subaccount_number: + type: integer + description: Subaccount number (0 for primary, 1-32 for subaccounts). + balance: + type: integer + format: int64 + description: Balance in centicents. + updated_ts: + type: integer + format: int64 + description: Unix timestamp of last balance update. + + GetSubaccountTransfersResponse: + type: object + required: + - transfers + properties: + transfers: + type: array + items: + $ref: '#/components/schemas/SubaccountTransfer' + cursor: + type: string + description: Cursor for the next page of results. + + SubaccountTransfer: + type: object + required: + - transfer_id + - from_subaccount + - to_subaccount + - amount_cents + - created_ts + properties: + transfer_id: + type: string + description: Unique identifier for this transfer. + from_subaccount: + type: integer + description: Source subaccount number (0 for primary, 1-32 for subaccounts). + to_subaccount: + type: integer + description: Destination subaccount number (0 for primary, 1-32 for subaccounts). + amount_cents: + type: integer + format: int64 + description: Transfer amount in cents. + created_ts: + type: integer + format: int64 + description: Unix timestamp when the transfer was created. + + # Portfolio schemas (specific to portfolio endpoints, not shared with IB) + GetSettlementsResponse: + type: object + required: + - settlements + properties: + settlements: + type: array + items: + $ref: '#/components/schemas/Settlement' + cursor: + type: string + + Settlement: + type: object + required: + - ticker + - event_ticker + - market_result + - yes_count + - yes_count_fp + - yes_total_cost + - no_count + - no_count_fp + - no_total_cost + - revenue + - settled_time + - fee_cost + properties: + ticker: + type: string + description: The ticker symbol of the market that was settled. + event_ticker: + type: string + description: The event ticker symbol of the market that was settled. + market_result: + type: string + enum: ['yes', 'no', 'scalar', 'void'] + description: The outcome of the market settlement. 'yes' = market resolved to YES, 'no' = market resolved to NO, 'scalar' = scalar market settled at a specific value, 'void' = market was voided/cancelled and all positions returned at original cost. + yes_count: + type: integer + format: int64 + description: Number of YES contracts owned at the time of settlement. + yes_count_fp: + $ref: '#/components/schemas/FixedPointCount' + description: String representation of the number of YES contracts owned at the time of settlement. + yes_total_cost: + type: integer + description: Total cost basis of all YES contracts in cents. + no_count: + type: integer + format: int64 + description: Number of NO contracts owned at the time of settlement. + no_count_fp: + $ref: '#/components/schemas/FixedPointCount' + description: String representation of the number of NO contracts owned at the time of settlement. + no_total_cost: + type: integer + description: Total cost basis of all NO contracts in cents. + revenue: + type: integer + description: Total revenue earned from this settlement in cents (winning contracts pay out 100 cents each). + settled_time: + type: string + format: date-time + description: Timestamp when the market was settled and payouts were processed. + fee_cost: + type: string + example: "0.3400" + description: Total fees paid in fixed point dollars. + value: + type: integer + nullable: true + description: Payout of a single yes contract in cents. + + GetPortfolioRestingOrderTotalValueResponse: + type: object + required: + - total_resting_order_value + properties: + total_resting_order_value: + type: integer + description: Total value of resting orders in cents + + # FCM schemas + Order: + type: object + required: + - order_id + - user_id + - client_order_id + - ticker + - side + - action + - type + - status + - yes_price + - no_price + - yes_price_dollars + - no_price_dollars + - fill_count + - fill_count_fp + - remaining_count + - remaining_count_fp + - initial_count + - initial_count_fp + - taker_fees + - maker_fees + - taker_fill_cost + - maker_fill_cost + - taker_fill_cost_dollars + - maker_fill_cost_dollars + - queue_position + properties: + order_id: + type: string + user_id: + type: string + description: Unique identifier for users + client_order_id: + type: string + ticker: + type: string + side: + type: string + enum: ['yes', 'no'] + action: + type: string + enum: [buy, sell] + type: + type: string + enum: [limit, market] + status: + $ref: '#/components/schemas/OrderStatus' + yes_price: + type: integer + no_price: + type: integer + yes_price_dollars: + $ref: '#/components/schemas/FixedPointDollars' + description: The yes price for this order in fixed-point dollars + no_price_dollars: + $ref: '#/components/schemas/FixedPointDollars' + description: The no price for this order in fixed-point dollars + fill_count: + type: integer + description: The number of contracts that have been filled + fill_count_fp: + $ref: '#/components/schemas/FixedPointCount' + description: String representation of the number of contracts that have been filled + remaining_count: + type: integer + remaining_count_fp: + $ref: '#/components/schemas/FixedPointCount' + description: String representation of the remaining contracts for this order + initial_count: + type: integer + description: The initial size of the order (contract units) + initial_count_fp: + $ref: '#/components/schemas/FixedPointCount' + description: String representation of the initial size of the order (contract units) + taker_fees: + type: integer + description: Fees paid on filled taker contracts, in cents + maker_fees: + type: integer + description: Fees paid on filled maker contracts, in cents + taker_fill_cost: + type: integer + description: The cost of filled taker orders in cents + maker_fill_cost: + type: integer + description: The cost of filled maker orders in cents + taker_fill_cost_dollars: + $ref: '#/components/schemas/FixedPointDollars' + description: The cost of filled taker orders in dollars + maker_fill_cost_dollars: + $ref: '#/components/schemas/FixedPointDollars' + description: The cost of filled maker orders in dollars + queue_position: + type: integer + description: |- + **DEPRECATED**: This field is deprecated and will always return 0. Please use the GET /portfolio/orders/{order_id}/queue_position endpoint instead + taker_fees_dollars: + $ref: '#/components/schemas/FixedPointDollars' + nullable: true + description: Fees paid on filled taker contracts, in dollars + maker_fees_dollars: + $ref: '#/components/schemas/FixedPointDollars' + nullable: true + description: Fees paid on filled maker contracts, in dollars + expiration_time: + type: string + format: date-time + nullable: true + created_time: + type: string + format: date-time + nullable: true + x-omitempty: false + last_update_time: + type: string + format: date-time + nullable: true + x-omitempty: true + description: The last update to an order (modify, cancel, fill) + self_trade_prevention_type: + $ref: '#/components/schemas/SelfTradePreventionType' + nullable: true + x-omitempty: false + order_group_id: + type: string + nullable: true + description: The order group this order is part of + cancel_order_on_pause: + type: boolean + description: If this flag is set to true, the order will be canceled if the order is open and trading on the exchange is paused for any reason. + + Milestone: + type: object + required: + - id + - category + - type + - start_date + - related_event_tickers + - title + - notification_message + - details + - primary_event_tickers + - last_updated_ts + properties: + id: + type: string + description: Unique identifier for the milestone. + category: + type: string + description: Category of the milestone. + type: + type: string + description: Type of the milestone. + start_date: + type: string + format: date-time + description: Start date of the milestone. + end_date: + type: string + format: date-time + nullable: true + description: End date of the milestone, if any. + related_event_tickers: + type: array + items: + type: string + description: List of event tickers related to this milestone. + title: + type: string + description: Title of the milestone. + notification_message: + type: string + description: Notification message for the milestone. + source_id: + type: string + nullable: true + description: Source id of milestone if available. + details: + type: object + additionalProperties: true + description: Additional details about the milestone. + primary_event_tickers: + type: array + items: + type: string + description: List of event tickers directly related to the outcome of this milestone. + last_updated_ts: + type: string + format: date-time + description: Last time this structured target was updated. + + GetMilestoneResponse: + type: object + required: + - milestone + properties: + milestone: + $ref: '#/components/schemas/Milestone' + description: The milestone data. + + GetMilestonesResponse: + type: object + required: + - milestones + properties: + milestones: + type: array + items: + $ref: '#/components/schemas/Milestone' + description: List of milestones. + cursor: + type: string + description: Cursor for pagination. + + GetOrdersResponse: + type: object + required: + - orders + - cursor + properties: + orders: + type: array + items: + $ref: '#/components/schemas/Order' + cursor: + type: string + + GetOrderQueuePositionResponse: + type: object + required: + - queue_position + properties: + queue_position: + type: integer + format: int32 + description: The position of the order in the queue + + OrderQueuePosition: + type: object + required: + - order_id + - market_ticker + - queue_position + properties: + order_id: + type: string + description: The order ID + market_ticker: + type: string + description: The market ticker + queue_position: + type: integer + format: int32 + description: The position of the order in the queue + + GetOrderQueuePositionsResponse: + type: object + required: + - queue_positions + properties: + queue_positions: + type: array + description: Queue positions for all matching orders + items: + $ref: '#/components/schemas/OrderQueuePosition' + + MarketPosition: + type: object + required: + - ticker + - total_traded + - total_traded_dollars + - position + - position_fp + - market_exposure + - market_exposure_dollars + - realized_pnl + - realized_pnl_dollars + - resting_orders_count + - fees_paid + - fees_paid_dollars + properties: + ticker: + type: string + description: Unique identifier for the market + x-go-type-skip-optional-pointer: true + total_traded: + type: integer + description: Total spent on this market in cents + total_traded_dollars: + $ref: '#/components/schemas/FixedPointDollars' + description: Total spent on this market in dollars + position: + type: integer + format: int32 + description: Number of contracts bought in this market. Negative means NO contracts and positive means YES contracts + position_fp: + $ref: '#/components/schemas/FixedPointCount' + description: String representation of the number of contracts bought in this market. Negative means NO contracts and positive means YES contracts + market_exposure: + type: integer + description: Cost of the aggregate market position in cents + market_exposure_dollars: + $ref: '#/components/schemas/FixedPointDollars' + description: Cost of the aggregate market position in dollars + realized_pnl: + type: integer + description: Locked in profit and loss, in cents + realized_pnl_dollars: + $ref: '#/components/schemas/FixedPointDollars' + description: Locked in profit and loss, in dollars + resting_orders_count: + type: integer + format: int32 + description: "[DEPRECATED] Aggregate size of resting orders in contract units" + fees_paid: + type: integer + description: Fees paid on fill orders, in cents + fees_paid_dollars: + $ref: '#/components/schemas/FixedPointDollars' + description: Fees paid on fill orders, in dollars + last_updated_ts: + type: string + format: date-time + description: Last time the position is updated + + EventPosition: + type: object + required: + - event_ticker + - total_cost + - total_cost_dollars + - total_cost_shares + - total_cost_shares_fp + - event_exposure + - event_exposure_dollars + - realized_pnl + - realized_pnl_dollars + - resting_orders_count + - fees_paid + - fees_paid_dollars + properties: + event_ticker: + type: string + description: Unique identifier for events + total_cost: + type: integer + description: Total spent on this event in cents + total_cost_dollars: + $ref: '#/components/schemas/FixedPointDollars' + description: Total spent on this event in dollars + total_cost_shares: + type: integer + format: int64 + description: Total number of shares traded on this event (including both YES and NO contracts) + total_cost_shares_fp: + $ref: '#/components/schemas/FixedPointCount' + description: String representation of the total number of shares traded on this event (including both YES and NO contracts) + event_exposure: + type: integer + description: Cost of the aggregate event position in cents + event_exposure_dollars: + $ref: '#/components/schemas/FixedPointDollars' + description: Cost of the aggregate event position in dollars + realized_pnl: + type: integer + description: Locked in profit and loss, in cents + realized_pnl_dollars: + $ref: '#/components/schemas/FixedPointDollars' + description: Locked in profit and loss, in dollars + fees_paid: + type: integer + description: Fees paid on fill orders, in cents + fees_paid_dollars: + $ref: '#/components/schemas/FixedPointDollars' + description: Fees paid on fill orders, in dollars + + GetPositionsResponse: + type: object + required: + - market_positions + - event_positions + properties: + cursor: + type: string + description: The Cursor represents a pointer to the next page of records in the pagination. Use the value returned here in the cursor query parameter for this end-point to get the next page containing limit records. An empty value of this field indicates there is no next page. + market_positions: + type: array + items: + $ref: '#/components/schemas/MarketPosition' + description: List of market positions + event_positions: + type: array + items: + $ref: '#/components/schemas/EventPosition' + description: List of event positions + + Trade: + type: object + required: + - trade_id + - ticker + - price + - count + - count_fp + - yes_price + - no_price + - yes_price_dollars + - no_price_dollars + - taker_side + properties: + trade_id: + type: string + description: Unique identifier for this trade + ticker: + type: string + description: Unique identifier for the market + price: + type: number + description: Trade price (deprecated - use yes_price or no_price) + count: + type: integer + description: Number of contracts bought or sold in this trade + count_fp: + $ref: '#/components/schemas/FixedPointCount' + description: String representation of the number of contracts bought or sold in this trade + yes_price: + type: integer + description: Yes price for this trade in cents + no_price: + type: integer + description: No price for this trade in cents + yes_price_dollars: + $ref: '#/components/schemas/FixedPointDollars' + description: Yes price for this trade in dollars + no_price_dollars: + $ref: '#/components/schemas/FixedPointDollars' + description: No price for this trade in dollars + taker_side: + type: string + enum: ['yes', 'no'] + x-enum-varnames: ['TradeTakerSideYes', 'TradeTakerSideNo'] + description: Side for the taker of this trade + created_time: + type: string + format: date-time + description: Timestamp when this trade was executed + + GetIncentiveProgramsResponse: + type: object + required: + - incentive_programs + properties: + incentive_programs: + type: array + items: + $ref: '#/components/schemas/IncentiveProgram' + next_cursor: + type: string + description: Cursor for pagination to get the next page of results + + IncentiveProgram: + type: object + required: + - id + - market_ticker + - incentive_type + - start_date + - end_date + - period_reward + - paid_out + properties: + id: + type: string + description: Unique identifier for the incentive program + market_ticker: + type: string + description: The ticker symbol of the market associated with this incentive program + incentive_type: + type: string + enum: ['liquidity', 'volume'] + description: Type of incentive program + start_date: + type: string + format: date-time + description: Start date of the incentive program + end_date: + type: string + format: date-time + description: End date of the incentive program + period_reward: + type: integer + format: int64 + description: Total reward for the period in centi-cents + paid_out: + type: boolean + description: Whether the incentive has been paid out + discount_factor_bps: + type: integer + format: int32 + nullable: true + description: Discount factor in basis points (optional) + target_size: + type: integer + format: int32 + nullable: true + description: Target size for the incentive program (optional) + target_size_fp: + $ref: '#/components/schemas/FixedPointCount' + nullable: true + description: String representation of the target size for the incentive program (optional) + + GetTradesResponse: + type: object + required: + - trades + - cursor + properties: + trades: + type: array + items: + $ref: '#/components/schemas/Trade' + cursor: + type: string + + Fill: + type: object + required: + - fill_id + - trade_id + - order_id + - ticker + - market_ticker + - side + - action + - count + - count_fp + - price + - yes_price + - no_price + - yes_price_fixed + - no_price_fixed + - is_taker + properties: + fill_id: + type: string + description: Unique identifier for this fill + trade_id: + type: string + description: Unique identifier for this fill (legacy field name, same as fill_id) + order_id: + type: string + description: Unique identifier for the order that resulted in this fill + client_order_id: + type: string + description: Client-provided identifier for the order that resulted in this fill + ticker: + type: string + description: Unique identifier for the market + market_ticker: + type: string + description: Unique identifier for the market (legacy field name, same as ticker) + side: + type: string + enum: ['yes', 'no'] + description: Specifies if this is a 'yes' or 'no' fill + action: + type: string + enum: ['buy', 'sell'] + description: Specifies if this is a buy or sell order + count: + type: integer + description: Number of contracts bought or sold in this fill + count_fp: + $ref: '#/components/schemas/FixedPointCount' + description: String representation of the number of contracts bought or sold in this fill + price: + type: number + description: Fill price (deprecated - use yes_price or no_price) + yes_price: + type: integer + description: Fill price for the yes side in cents + no_price: + type: integer + description: Fill price for the no side in cents + yes_price_fixed: + type: string + description: Fill price for the yes side in fixed point dollars + no_price_fixed: + type: string + description: Fill price for the no side in fixed point dollars + is_taker: + type: boolean + description: If true, this fill was a taker (removed liquidity from the order book) + created_time: + type: string + format: date-time + description: Timestamp when this fill was executed + ts: + type: integer + format: int64 + description: Unix timestamp when this fill was executed (legacy field name) + + GetFillsResponse: + type: object + required: + - fills + - cursor + properties: + fills: + type: array + items: + $ref: '#/components/schemas/Fill' + cursor: + type: string + + # Structured Target schemas + StructuredTarget: + type: object + properties: + id: + type: string + description: Unique identifier for the structured target. + name: + type: string + description: Name of the structured target. + type: + type: string + description: Type of the structured target. + details: + type: object + description: Additional details about the structured target. Contains flexible JSON data specific to the target type. + source_id: + type: string + description: External source identifier for the structured target, if available (e.g., third-party data provider ID). + last_updated_ts: + type: string + format: date-time + description: Timestamp when this structured target was last updated. + + GetStructuredTargetsResponse: + type: object + properties: + structured_targets: + type: array + items: + $ref: '#/components/schemas/StructuredTarget' + cursor: + type: string + description: Pagination cursor for the next page. Empty if there are no more results. + + GetStructuredTargetResponse: + type: object + properties: + structured_target: + $ref: '#/components/schemas/StructuredTarget' + + # Order Group schemas + EmptyResponse: + type: object + description: An empty response body + + IntraExchangeInstanceTransferRequest: + type: object + required: + - source + - destination + - amount + properties: + source: + $ref: '#/components/schemas/ExchangeInstance' + description: The source exchange instance + destination: + $ref: '#/components/schemas/ExchangeInstance' + description: The destination exchange instance + amount: + type: integer + format: int64 + description: The amount to transfer in centicents + + IntraExchangeInstanceTransferResponse: + type: object + required: + - transfer_id + properties: + transfer_id: + type: string + description: The ID of the transfer that was created + + OrderGroup: + type: object + required: + - id + - is_auto_cancel_enabled + properties: + id: + type: string + description: Unique identifier for the order group + x-go-type-skip-optional-pointer: true + contracts_limit: + type: integer + format: int64 + description: Current maximum contracts allowed over a rolling 15-second window (whole contracts only). + x-go-type-skip-optional-pointer: true + contracts_limit_fp: + $ref: '#/components/schemas/FixedPointCount' + description: String representation of the current maximum contracts allowed over a rolling 15-second window (whole contracts only). + x-go-type-skip-optional-pointer: true + is_auto_cancel_enabled: + type: boolean + description: Whether auto-cancel is enabled for this order group + x-go-type-skip-optional-pointer: true + + GetOrderGroupsResponse: + type: object + properties: + order_groups: + type: array + items: + $ref: '#/components/schemas/OrderGroup' + x-go-type-skip-optional-pointer: true + + GetOrderGroupResponse: + type: object + required: + - is_auto_cancel_enabled + - orders + properties: + is_auto_cancel_enabled: + type: boolean + description: Whether auto-cancel is enabled for this order group + contracts_limit: + type: integer + format: int64 + description: Current maximum contracts allowed over a rolling 15-second window (whole contracts only). + x-go-type-skip-optional-pointer: true + contracts_limit_fp: + $ref: '#/components/schemas/FixedPointCount' + description: String representation of the current maximum contracts allowed over a rolling 15-second window (whole contracts only). + x-go-type-skip-optional-pointer: true + orders: + type: array + items: + type: string + description: List of order IDs that belong to this order group + x-go-type-skip-optional-pointer: true + + CreateOrderGroupRequest: + type: object + properties: + contracts_limit: + type: integer + format: int64 + minimum: 1 + description: Specifies the maximum number of contracts that can be matched within this group over a rolling 15-second window. Whole contracts only. Provide contracts_limit or contracts_limit_fp; if both provided they must match. + x-go-type-skip-optional-pointer: true + x-oapi-codegen-extra-tags: + validate: omitempty,gte=1 + contracts_limit_fp: + $ref: '#/components/schemas/FixedPointCount' + nullable: true + description: String representation of the maximum number of contracts that can be matched within this group over a rolling 15-second window (whole contracts only). Provide contracts_limit or contracts_limit_fp; if both provided they must match. + + UpdateOrderGroupLimitRequest: + type: object + properties: + contracts_limit: + type: integer + format: int64 + minimum: 1 + description: New maximum number of contracts that can be matched within this group over a rolling 15-second window. Whole contracts only. Provide contracts_limit or contracts_limit_fp; if both provided they must match. + x-go-type-skip-optional-pointer: true + x-oapi-codegen-extra-tags: + validate: omitempty,gte=1 + contracts_limit_fp: + $ref: '#/components/schemas/FixedPointCount' + nullable: true + description: String representation of the new maximum number of contracts that can be matched within this group over a rolling 15-second window (whole contracts only). Provide contracts_limit or contracts_limit_fp; if both provided they must match. + + CreateOrderGroupResponse: + type: object + required: + - order_group_id + properties: + order_group_id: + type: string + description: The unique identifier for the created order group + + GetCommunicationsIDResponse: + type: object + required: + - communications_id + properties: + communications_id: + type: string + description: A public communications ID which is used to identify the user + + RFQ: + type: object + required: + - id + - creator_id + - contracts + - contracts_fp + - market_ticker + - status + - created_ts + properties: + id: + type: string + description: Unique identifier for the RFQ + creator_id: + type: string + description: Public communications ID of the RFQ creator. Exposed post-execution to the creator. + market_ticker: + type: string + description: The ticker of the market this RFQ is for + contracts: + type: integer + description: Number of contracts requested in the RFQ + contracts_fp: + $ref: '#/components/schemas/FixedPointCount' + description: String representation of the number of contracts requested in the RFQ + target_cost_centi_cents: + type: integer + format: int64 + description: Total value of the RFQ in centi-cents + status: + type: string + description: Current status of the RFQ (open, closed) + enum: [open, closed] + created_ts: + type: string + format: date-time + description: Timestamp when the RFQ was created + mve_collection_ticker: + type: string + description: Ticker of the MVE collection this market belongs to + x-go-type-skip-optional-pointer: true + mve_selected_legs: + type: array + x-omitempty: true + items: + $ref: '#/components/schemas/MveSelectedLeg' + description: Selected legs for the MVE collection + x-go-type-skip-optional-pointer: true + rest_remainder: + type: boolean + description: Whether to rest the remainder of the RFQ after execution + cancellation_reason: + type: string + description: Reason for RFQ cancellation if cancelled + x-go-type-skip-optional-pointer: true + creator_user_id: + type: string + description: User ID of the RFQ creator (private field) + x-go-type-skip-optional-pointer: true + cancelled_ts: + type: string + format: date-time + description: Timestamp when the RFQ was cancelled + updated_ts: + type: string + format: date-time + description: Timestamp when the RFQ was last updated + + GetRFQsResponse: + type: object + required: + - rfqs + properties: + rfqs: + type: array + items: + $ref: '#/components/schemas/RFQ' + description: List of RFQs matching the query criteria + cursor: + type: string + description: Cursor for pagination to get the next page of results + x-go-type-skip-optional-pointer: true + + GetRFQResponse: + type: object + required: + - rfq + properties: + rfq: + $ref: '#/components/schemas/RFQ' + description: The details of the requested RFQ + + CreateRFQRequest: + type: object + required: + - market_ticker + - rest_remainder + properties: + market_ticker: + type: string + description: The ticker of the market for which to create an RFQ + contracts: + type: integer + description: The number of contracts for the RFQ. Whole contracts only. Contracts may be provided via contracts or contracts_fp; if both provided they must match. + x-go-type-skip-optional-pointer: true + contracts_fp: + $ref: '#/components/schemas/FixedPointCount' + nullable: true + description: String representation of the number of contracts for the RFQ (whole contracts only). Contracts may be provided via contracts or contracts_fp; if both provided they must match. + target_cost_centi_cents: + type: integer + format: int64 + description: The target cost for the RFQ in centi-cents + x-go-type-skip-optional-pointer: true + rest_remainder: + type: boolean + description: Whether to rest the remainder of the RFQ after execution + replace_existing: + type: boolean + description: Whether to delete existing RFQs as part of this RFQ's creation + default: false + x-go-type-skip-optional-pointer: true + subtrader_id: + type: string + description: The subtrader to create the RFQ for (FCM members only) + x-go-type-skip-optional-pointer: true + + CreateRFQResponse: + type: object + required: + - id + properties: + id: + type: string + description: The ID of the newly created RFQ + + Quote: + type: object + required: + - id + - rfq_id + - creator_id + - rfq_creator_id + - market_ticker + - contracts + - contracts_fp + - yes_bid + - no_bid + - yes_bid_dollars + - no_bid_dollars + - created_ts + - updated_ts + - status + properties: + id: + type: string + description: Unique identifier for the quote + rfq_id: + type: string + description: ID of the RFQ this quote is responding to + creator_id: + type: string + description: Public communications ID of the quote creator + rfq_creator_id: + type: string + description: Public communications ID of the RFQ creator + x-go-type-skip-optional-pointer: true + market_ticker: + type: string + description: The ticker of the market this quote is for + contracts: + type: integer + description: Number of contracts in the quote + contracts_fp: + $ref: '#/components/schemas/FixedPointCount' + description: String representation of the number of contracts in the quote + yes_bid: + type: integer + description: Bid price for YES contracts, in cents + no_bid: + type: integer + description: Bid price for NO contracts, in cents + yes_bid_dollars: + $ref: '#/components/schemas/FixedPointDollars' + description: Bid price for YES contracts, in dollars + no_bid_dollars: + $ref: '#/components/schemas/FixedPointDollars' + description: Bid price for NO contracts, in dollars + created_ts: + type: string + format: date-time + description: Timestamp when the quote was created + updated_ts: + type: string + format: date-time + description: Timestamp when the quote was last updated + status: + type: string + description: Current status of the quote + enum: [open, accepted, confirmed, executed, cancelled] + accepted_side: + type: string + description: The side that was accepted (yes or no) + enum: ['yes', 'no'] + accepted_ts: + type: string + format: date-time + description: Timestamp when the quote was accepted + confirmed_ts: + type: string + format: date-time + description: Timestamp when the quote was confirmed + executed_ts: + type: string + format: date-time + description: Timestamp when the quote was executed + cancelled_ts: + type: string + format: date-time + description: Timestamp when the quote was cancelled + rest_remainder: + type: boolean + description: Whether to rest the remainder of the quote after execution + cancellation_reason: + type: string + description: Reason for quote cancellation if cancelled + x-go-type-skip-optional-pointer: true + creator_user_id: + type: string + description: User ID of the quote creator (private field) + x-go-type-skip-optional-pointer: true + rfq_creator_user_id: + type: string + description: User ID of the RFQ creator (private field) + x-go-type-skip-optional-pointer: true + rfq_target_cost_centi_cents: + type: integer + format: int64 + description: Total value requested in the RFQ in centi-cents + rfq_creator_order_id: + type: string + description: Order ID for the RFQ creator (private field) + x-go-type-skip-optional-pointer: true + creator_order_id: + type: string + description: Order ID for the quote creator (private field) + x-go-type-skip-optional-pointer: true + + GetQuotesResponse: + type: object + required: + - quotes + properties: + quotes: + type: array + items: + $ref: '#/components/schemas/Quote' + description: List of quotes matching the query criteria + cursor: + type: string + description: Cursor for pagination to get the next page of results + x-go-type-skip-optional-pointer: true + + GetQuoteResponse: + type: object + required: + - quote + properties: + quote: + $ref: '#/components/schemas/Quote' + description: The details of the requested quote + + CreateQuoteRequest: + type: object + required: + - rfq_id + - yes_bid + - no_bid + - rest_remainder + properties: + rfq_id: + type: string + description: The ID of the RFQ to quote on + yes_bid: + type: string + $ref: '#/components/schemas/FixedPointDollars' + description: The bid price for YES contracts, in dollars + no_bid: + type: string + $ref: '#/components/schemas/FixedPointDollars' + description: The bid price for NO contracts, in dollars + rest_remainder: + type: boolean + description: Whether to rest the remainder of the quote after execution + subtrader_id: + type: string + description: Optional subaccount ID to place the quote under + subaccount: + type: integer + description: Optional subaccount number to place the quote under (0 for primary, 1-32 for subaccounts) + + CreateQuoteResponse: + type: object + required: + - id + properties: + id: + type: string + description: The ID of the newly created quote + + AcceptQuoteRequest: + type: object + required: + - accepted_side + properties: + accepted_side: + type: string + description: The side of the quote to accept (yes or no) + enum: ['yes', 'no'] + + # Order schemas + GetOrderResponse: + type: object + required: + - order + properties: + order: + $ref: '#/components/schemas/Order' + + CreateOrderRequest: + type: object + required: + - ticker + - side + - action + properties: + ticker: + type: string + x-oapi-codegen-extra-tags: + validate: required,min=1 + client_order_id: + type: string + x-go-type-skip-optional-pointer: true + side: + type: string + enum: ['yes', 'no'] + x-oapi-codegen-extra-tags: + validate: required,oneof=yes no + action: + type: string + enum: ['buy', 'sell'] + x-oapi-codegen-extra-tags: + validate: required,oneof=buy sell + count: + type: integer + minimum: 1 + description: Order quantity in contracts (whole contracts only). Provide count or count_fp; if both provided they must match. + x-go-type-skip-optional-pointer: true + x-oapi-codegen-extra-tags: + validate: omitempty,gte=1 + count_fp: + $ref: '#/components/schemas/FixedPointCount' + nullable: true + description: String representation of the order quantity in contracts (whole contracts only). Provide count or count_fp; if both provided they must match. + type: + type: string + enum: ['limit', 'market'] + x-oapi-codegen-extra-tags: + validate: omitempty,oneof=limit market + yes_price: + type: integer + minimum: 1 + maximum: 99 + x-go-type-skip-optional-pointer: true + no_price: + type: integer + minimum: 1 + maximum: 99 + x-go-type-skip-optional-pointer: true + yes_price_dollars: + $ref: '#/components/schemas/FixedPointDollars' + description: Submitting price of the Yes side in fixed-point dollars + no_price_dollars: + $ref: '#/components/schemas/FixedPointDollars' + description: Submitting price of the No side in fixed-point dollars + expiration_ts: + type: integer + format: int64 + time_in_force: + type: string + enum: ['fill_or_kill', 'good_till_canceled', 'immediate_or_cancel'] + x-oapi-codegen-extra-tags: + validate: omitempty,oneof=fill_or_kill good_till_canceled immediate_or_cancel + x-go-type-skip-optional-pointer: true + buy_max_cost: + type: integer + description: Maximum cost in cents. When specified, the order will automatically have Fill-or-Kill (FoK) behavior. + post_only: + type: boolean + reduce_only: + type: boolean + sell_position_floor: + type: integer + description: "Deprecated: Use reduce_only instead. Only accepts value of 0." + self_trade_prevention_type: + allOf: + - $ref: '#/components/schemas/SelfTradePreventionType' + x-oapi-codegen-extra-tags: + validate: omitempty,oneof=taker_at_cross maker + x-go-type-skip-optional-pointer: true + order_group_id: + type: string + description: The order group this order is part of + x-go-type-skip-optional-pointer: true + cancel_order_on_pause: + type: boolean + description: If this flag is set to true, the order will be canceled if the order is open and trading on the exchange is paused for any reason. + subaccount: + type: integer + minimum: 0 + default: 0 + description: The subaccount number to use for this order. 0 is the primary subaccount. + x-go-type-skip-optional-pointer: true + + CreateOrderResponse: + type: object + required: + - order + properties: + order: + $ref: '#/components/schemas/Order' + + BatchCreateOrdersRequest: + type: object + required: + - orders + properties: + orders: + type: array + x-oapi-codegen-extra-tags: + validate: required,dive + items: + $ref: '#/components/schemas/CreateOrderRequest' + + BatchCreateOrdersResponse: + type: object + required: + - orders + properties: + orders: + type: array + items: + $ref: '#/components/schemas/BatchCreateOrdersIndividualResponse' + + BatchCreateOrdersIndividualResponse: + type: object + properties: + client_order_id: + type: string + nullable: true + order: + allOf: + - $ref: '#/components/schemas/Order' + nullable: true + error: + allOf: + - $ref: '#/components/schemas/ErrorResponse' + nullable: true + + BatchCancelOrdersRequest: + type: object + required: + - ids + properties: + ids: + type: array + items: + type: string + description: An array of order IDs to cancel + + BatchCancelOrdersResponse: + type: object + required: + - orders + properties: + orders: + type: array + items: + $ref: '#/components/schemas/BatchCancelOrdersIndividualResponse' + + BatchCancelOrdersIndividualResponse: + type: object + required: + - order_id + - reduced_by + - reduced_by_fp + properties: + order_id: + type: string + description: The order ID to identify which order had an error during batch cancellation + order: + allOf: + - $ref: '#/components/schemas/Order' + nullable: true + reduced_by: + type: integer + description: The number of contracts that were successfully canceled from this order + reduced_by_fp: + $ref: '#/components/schemas/FixedPointCount' + description: String representation of the number of contracts that were successfully canceled from this order + error: + allOf: + - $ref: '#/components/schemas/ErrorResponse' + nullable: true + + CancelOrderResponse: + type: object + required: + - order + - reduced_by + - reduced_by_fp + properties: + order: + $ref: '#/components/schemas/Order' + reduced_by: + type: integer + reduced_by_fp: + $ref: '#/components/schemas/FixedPointCount' + description: String representation of the number of contracts that were successfully canceled from this order + + AmendOrderRequest: + type: object + required: + - ticker + - side + - action + properties: + ticker: + type: string + description: Market ticker + side: + type: string + enum: ["yes", "no"] + description: Side of the order + action: + type: string + enum: ["buy", "sell"] + description: Action of the order + client_order_id: + type: string + description: The original client-specified order ID to be amended + x-go-type-skip-optional-pointer: true + updated_client_order_id: + type: string + description: The new client-specified order ID after amendment + x-go-type-skip-optional-pointer: true + yes_price: + type: integer + minimum: 1 + maximum: 99 + description: Updated yes price for the order in cents + no_price: + type: integer + minimum: 1 + maximum: 99 + description: Updated no price for the order in cents + yes_price_dollars: + $ref: '#/components/schemas/FixedPointDollars' + description: Updated yes price for the order in fixed-point dollars. Exactly one of yes_price, no_price, yes_price_dollars, and no_price_dollars must be passed. + no_price_dollars: + $ref: '#/components/schemas/FixedPointDollars' + description: Updated no price for the order in fixed-point dollars. Exactly one of yes_price, no_price, yes_price_dollars, and no_price_dollars must be passed. + count: + type: integer + minimum: 1 + description: Updated quantity for the order (whole contracts only). If updating quantity, provide count or count_fp; if both provided they must match. + count_fp: + $ref: '#/components/schemas/FixedPointCount' + nullable: true + description: String representation of the updated quantity for the order (whole contracts only). If updating quantity, provide count or count_fp; if both provided they must match. + + AmendOrderResponse: + type: object + required: + - old_order + - order + properties: + old_order: + $ref: '#/components/schemas/Order' + description: The order before amendment + order: + $ref: '#/components/schemas/Order' + description: The order after amendment + + DecreaseOrderRequest: + type: object + properties: + reduce_by: + type: integer + minimum: 1 + description: Number of contracts to reduce by (whole contracts only). Reduce-by may be provided via reduce_by or reduce_by_fp; if both provided they must match. Exactly one of reduce_by(/reduce_by_fp) or reduce_to(/reduce_to_fp) must be provided. + reduce_by_fp: + $ref: '#/components/schemas/FixedPointCount' + nullable: true + description: String representation of the number of contracts to reduce by (whole contracts only). Reduce-by may be provided via reduce_by or reduce_by_fp; if both provided they must match. Exactly one of reduce_by(/reduce_by_fp) or reduce_to(/reduce_to_fp) must be provided. + reduce_to: + type: integer + minimum: 0 + description: Number of contracts to reduce to (whole contracts only). Reduce-to may be provided via reduce_to or reduce_to_fp; if both provided they must match. Exactly one of reduce_by(/reduce_by_fp) or reduce_to(/reduce_to_fp) must be provided. + reduce_to_fp: + $ref: '#/components/schemas/FixedPointCount' + nullable: true + description: String representation of the number of contracts to reduce to (whole contracts only). Reduce-to may be provided via reduce_to or reduce_to_fp; if both provided they must match. Exactly one of reduce_by(/reduce_by_fp) or reduce_to(/reduce_to_fp) must be provided. + + DecreaseOrderResponse: + type: object + required: + - order + properties: + order: + $ref: '#/components/schemas/Order' + + # Multivariate Event Collection schemas + AssociatedEvent: + type: object + required: + - ticker + - is_yes_only + - active_quoters + properties: + ticker: + type: string + description: The event ticker. + is_yes_only: + type: boolean + description: Whether only the 'yes' side can be used for this event. + size_max: + type: integer + format: int32 + nullable: true + description: Maximum number of markets from this event (inclusive). Null means no limit. + size_min: + type: integer + format: int32 + nullable: true + description: Minimum number of markets from this event (inclusive). Null means no limit. + active_quoters: + type: array + items: + type: string + description: List of active quoters for this event. + + MultivariateEventCollection: + type: object + required: + - collection_ticker + - series_ticker + - title + - description + - open_date + - close_date + - associated_events + - associated_event_tickers + - is_ordered + - is_single_market_per_event + - is_all_yes + - size_min + - size_max + - functional_description + properties: + collection_ticker: + type: string + description: Unique identifier for the collection. + series_ticker: + type: string + description: Series associated with the collection. Events produced in the collection will be associated with this series. + title: + type: string + description: Title of the collection. + description: + type: string + description: Short description of the collection. + open_date: + type: string + format: date-time + description: The open date of the collection. Before this time, the collection cannot be interacted with. + close_date: + type: string + format: date-time + description: The close date of the collection. After this time, the collection cannot be interacted with. + associated_events: + type: array + items: + $ref: '#/components/schemas/AssociatedEvent' + description: List of events with their individual configuration. + associated_event_tickers: + type: array + items: + type: string + description: '[DEPRECATED - Use associated_events instead] A list of events associated with the collection. Markets in these events can be passed as inputs to the Lookup and Create endpoints.' + is_ordered: + type: boolean + description: Whether the collection is ordered. If true, the order of markets passed into Lookup/Create affects the output. If false, the order does not matter. + is_single_market_per_event: + type: boolean + description: '[DEPRECATED - Use associated_events instead] Whether the collection accepts multiple markets from the same event passed into Lookup/Create.' + is_all_yes: + type: boolean + description: '[DEPRECATED - Use associated_events instead] Whether the collection requires that only the market side of ''yes'' may be used.' + size_min: + type: integer + format: int32 + description: The minimum number of markets that must be passed into Lookup/Create (inclusive). + size_max: + type: integer + format: int32 + description: The maximum number of markets that must be passed into Lookup/Create (inclusive). + functional_description: + type: string + description: A functional description of the collection describing how inputs affect the output. + + GetMultivariateEventCollectionResponse: + type: object + required: + - multivariate_contract + properties: + multivariate_contract: + $ref: '#/components/schemas/MultivariateEventCollection' + description: The multivariate event collection. + + GetMultivariateEventCollectionsResponse: + type: object + required: + - multivariate_contracts + properties: + multivariate_contracts: + type: array + items: + $ref: '#/components/schemas/MultivariateEventCollection' + description: List of multivariate event collections. + cursor: + type: string + description: The Cursor represents a pointer to the next page of records in the pagination. Use the value returned here in the cursor query parameter for this end-point to get the next page containing limit records. An empty value of this field indicates there is no next page. + x-go-type-skip-optional-pointer: true + + TickerPair: + type: object + required: + - market_ticker + - event_ticker + - side + properties: + market_ticker: + type: string + description: Market ticker identifier. + event_ticker: + type: string + description: Event ticker identifier. + side: + type: string + enum: ['yes', 'no'] + description: Side of the market (yes or no). + + LookupTickersForMarketInMultivariateEventCollectionRequest: + type: object + required: + - selected_markets + properties: + selected_markets: + type: array + items: + $ref: '#/components/schemas/TickerPair' + description: List of selected markets that act as parameters to determine which market is produced. + + LookupTickersForMarketInMultivariateEventCollectionResponse: + type: object + required: + - event_ticker + - market_ticker + properties: + event_ticker: + type: string + description: Event ticker for the looked up market. + market_ticker: + type: string + description: Market ticker for the looked up market. + + LookupPoint: + type: object + required: + - event_ticker + - market_ticker + - selected_markets + - last_queried_ts + properties: + event_ticker: + type: string + description: Event ticker for the lookup point. + market_ticker: + type: string + description: Market ticker for the lookup point. + selected_markets: + type: array + items: + $ref: '#/components/schemas/TickerPair' + description: Markets that were selected for this lookup. + last_queried_ts: + type: string + format: date-time + description: Timestamp when this lookup was last queried. + + GetMultivariateEventCollectionLookupHistoryResponse: + type: object + required: + - lookup_points + properties: + lookup_points: + type: array + items: + $ref: '#/components/schemas/LookupPoint' + description: List of recent lookup points in the collection. + + CreateMarketInMultivariateEventCollectionRequest: + type: object + required: + - selected_markets + properties: + selected_markets: + type: array + items: + $ref: '#/components/schemas/TickerPair' + description: List of selected markets that act as parameters to determine which market is created. + with_market_payload: + type: boolean + description: Whether to include the market payload in the response. + + CreateMarketInMultivariateEventCollectionResponse: + type: object + required: + - event_ticker + - market_ticker + properties: + event_ticker: + type: string + description: Event ticker for the created market. + market_ticker: + type: string + description: Market ticker for the created market. + market: + $ref: '#/components/schemas/Market' + description: Market payload of the created market. + # Market Orderbook schemas + # PriceLevel is represented as a 2-element array: [price, count] + PriceLevel: + type: array + minItems: 2 + maxItems: 2 + items: + type: integer + description: A price level represented as [price_in_cents, count] where count is the legacy integer contract count (will be deprecated). + + OrderbookLevel: + type: array + items: + type: number + minItems: 2 + maxItems: 2 + description: Price level represented as [price, count] where price is in cents and count is the legacy integer contract count (will be deprecated). + + PriceLevelDollars: + type: array + minItems: 2 + maxItems: 2 + example: ["0.1500", 100] + description: Price level in dollars represented as [dollars_string, count] where dollars_string is like "0.1500" and count is the legacy integer contract count (will be deprecated). Use the *_fp variants for fixed-point contract counts. + + PriceLevelDollarsCountFp: + type: array + minItems: 2 + maxItems: 2 + example: ["0.1500", "100.00"] + items: + type: string + description: Price level in dollars represented as [dollars_string, fp] where dollars_string is like "0.1500" and fp is a FixedPointCount string (fixed-point contract count). The second element is the contract quantity (not price). + + Orderbook: + type: object + required: + - "yes" + - "no" + - yes_dollars + - no_dollars + description: Legacy integer-count orderbook (will be deprecated). Prefer OrderbookCountFp / orderbook_fp for fixed-point contract counts. + properties: + yes: + type: array + items: + $ref: '#/components/schemas/OrderbookLevel' + no: + type: array + items: + $ref: '#/components/schemas/OrderbookLevel' + yes_dollars: + type: array + items: + $ref: '#/components/schemas/PriceLevelDollars' + no_dollars: + type: array + items: + $ref: '#/components/schemas/PriceLevelDollars' + + OrderbookCountFp: + type: object + required: + - yes_dollars + - no_dollars + properties: + yes_dollars: + type: array + items: + $ref: '#/components/schemas/PriceLevelDollarsCountFp' + no_dollars: + type: array + items: + $ref: '#/components/schemas/PriceLevelDollarsCountFp' + description: Orderbook with fixed-point contract counts (fp) in all dollar price levels. + + GetMarketOrderbookResponse: + type: object + required: + - orderbook + - orderbook_fp + properties: + orderbook: + $ref: '#/components/schemas/Orderbook' + description: Legacy integer-count orderbook (will be deprecated). Prefer orderbook_fp for fixed-point contract counts. + orderbook_fp: + $ref: '#/components/schemas/OrderbookCountFp' + description: Orderbook with fixed-point contract counts (fp) in all price levels. + + GetEventsResponse: + type: object + required: + - events + - cursor + properties: + events: + type: array + description: Array of events matching the query criteria. + items: + $ref: '#/components/schemas/EventData' + milestones: + type: array + description: Array of milestones related to the events. + items: + $ref: '#/components/schemas/Milestone' + cursor: + type: string + description: Pagination cursor for the next page. Empty if there are no more results. + + GetMultivariateEventsResponse: + type: object + required: + - events + - cursor + properties: + events: + type: array + description: Array of multivariate events matching the query criteria. + items: + $ref: '#/components/schemas/EventData' + cursor: + type: string + description: Pagination cursor for the next page. Empty if there are no more results. + + GetEventResponse: + type: object + required: + - event + - markets + properties: + event: + $ref: '#/components/schemas/EventData' + description: Data for the event. + markets: + type: array + description: Data for the markets in this event. This field is deprecated in favour of the "markets" field inside the event. Which will be filled with the same value if you use the query parameter "with_nested_markets=true". + items: + $ref: '#/components/schemas/Market' + + MarketMetadata: + type: object + required: + - market_ticker + - image_url + - color_code + properties: + market_ticker: + type: string + description: The ticker of the market. + image_url: + type: string + description: A path to an image that represents this market. + color_code: + type: string + description: The color code for the market. + + GetEventMetadataResponse: + type: object + required: + - image_url + - settlement_sources + - market_details + properties: + image_url: + type: string + description: A path to an image that represents this event. + featured_image_url: + type: string + description: A path to an image that represents the image of the featured market. + market_details: + type: array + description: Metadata for the markets in this event. + items: + $ref: '#/components/schemas/MarketMetadata' + settlement_sources: + type: array + description: A list of settlement sources for this event. + items: + $ref: '#/components/schemas/SettlementSource' + competition: + type: string + nullable: true + x-omitempty: true + description: Event competition. + x-go-type-skip-optional-pointer: true + competition_scope: + type: string + nullable: true + x-omitempty: true + description: Event scope, based on the competition. + x-go-type-skip-optional-pointer: true + + GetEventForecastPercentilesHistoryResponse: + type: object + required: + - forecast_history + properties: + forecast_history: + type: array + description: Array of forecast percentile data points over time. + items: + $ref: '#/components/schemas/ForecastPercentilesPoint' + + ForecastPercentilesPoint: + type: object + required: + - event_ticker + - end_period_ts + - period_interval + - percentile_points + properties: + event_ticker: + type: string + description: The event ticker this forecast is for. + end_period_ts: + type: integer + format: int64 + description: Unix timestamp for the inclusive end of the forecast period. + period_interval: + type: integer + format: int32 + description: Length of the forecast period in minutes. + percentile_points: + type: array + description: Array of forecast values at different percentiles. + items: + $ref: '#/components/schemas/PercentilePoint' + + PercentilePoint: + type: object + required: + - percentile + - raw_numerical_forecast + - numerical_forecast + - formatted_forecast + properties: + percentile: + type: integer + format: int32 + description: The percentile value (0-10000). + raw_numerical_forecast: + type: number + description: The raw numerical forecast value. + numerical_forecast: + type: number + description: The processed numerical forecast value. + formatted_forecast: + type: string + description: The human-readable formatted forecast value. + + EventData: + type: object + required: + - event_ticker + - series_ticker + - sub_title + - title + - collateral_return_type + - mutually_exclusive + - category + - available_on_brokers + - product_metadata + properties: + event_ticker: + type: string + description: Unique identifier for this event. + series_ticker: + type: string + description: Unique identifier for the series this event belongs to. + sub_title: + type: string + description: Shortened descriptive title for the event. + title: + type: string + description: Full title of the event. + collateral_return_type: + type: string + description: Specifies how collateral is returned when markets settle (e.g., 'binary' for standard yes/no markets). + mutually_exclusive: + type: boolean + description: If true, only one market in this event can resolve to 'yes'. If false, multiple markets can resolve to 'yes'. + category: + type: string + description: Event category (deprecated, use series-level category instead). + strike_date: + type: string + format: date-time + nullable: true + x-omitempty: true + description: The specific date this event is based on. Only filled when the event uses a date strike (mutually exclusive with strike_period). + strike_period: + type: string + nullable: true + x-omitempty: true + description: The time period this event covers (e.g., 'week', 'month'). Only filled when the event uses a period strike (mutually exclusive with strike_date). + markets: + type: array + x-omitempty: true + description: Array of markets associated with this event. Only populated when 'with_nested_markets=true' is specified in the request. + items: + $ref: '#/components/schemas/Market' + x-go-type-skip-optional-pointer: true + available_on_brokers: + type: boolean + description: Whether this event is available to trade on brokers. + product_metadata: + type: object + nullable: true + x-omitempty: true + description: Additional metadata for the event. + x-go-type-skip-optional-pointer: true + + Series: + type: object + required: + - ticker + - frequency + - title + - category + - tags + - settlement_sources + - contract_url + - contract_terms_url + - fee_type + - fee_multiplier + - additional_prohibitions + properties: + ticker: + type: string + description: Ticker that identifies this series. + frequency: + type: string + description: Description of the frequency of the series. There is no fixed value set here, but will be something human-readable like weekly, daily, one-off. + title: + type: string + description: Title describing the series. For full context use you should use this field with the title field of the events belonging to this series. + category: + type: string + description: Category specifies the category which this series belongs to. + tags: + type: array + items: + type: string + description: Tags specifies the subjects that this series relates to, multiple series from different categories can have the same tags. + settlement_sources: + type: array + items: + $ref: '#/components/schemas/SettlementSource' + description: SettlementSources specifies the official sources used for the determination of markets within the series. Methodology is defined in the rulebook. + contract_url: + type: string + description: ContractUrl provides a direct link to the original filing of the contract which underlies the series. + contract_terms_url: + type: string + description: ContractTermsUrl is the URL to the current terms of the contract underlying the series. + product_metadata: + type: object + nullable: true + x-omitempty: true + description: Internal product metadata of the series. + fee_type: + type: string + enum: [quadratic, quadratic_with_maker_fees, flat] + description: "FeeType is a string representing the series' fee structure. Fee structures can be found at https://kalshi.com/docs/kalshi-fee-schedule.pdf. 'quadratic' is described by the General Trading Fees Table, 'quadratic_with_maker_fees' is described by the General Trading Fees Table with maker fees described in the Maker Fees section, 'flat' is described by the Specific Trading Fees Table." + fee_multiplier: + type: number + format: double + description: FeeMultiplier is a floating point multiplier applied to the fee calculations. + additional_prohibitions: + type: array + items: + type: string + description: AdditionalProhibitions is a list of additional trading prohibitions for this series. + volume: + type: integer + description: Total contracts traded across all events in this series. + volume_fp: + $ref: '#/components/schemas/FixedPointCount' + description: String representation of the total number of contracts traded across all events in this series. + + SeriesFeeChange: + type: object + required: + - id + - series_ticker + - fee_type + - fee_multiplier + - scheduled_ts + properties: + id: + type: string + description: Unique identifier for this fee change + series_ticker: + type: string + description: Series ticker this fee change applies to + fee_type: + type: string + enum: [quadratic, quadratic_with_maker_fees, flat] + description: New fee type for the series + fee_multiplier: + type: number + format: double + description: New fee multiplier for the series + scheduled_ts: + type: string + format: date-time + description: Timestamp when this fee change is scheduled to take effect + + GetSeriesResponse: + type: object + required: + - series + properties: + series: + $ref: '#/components/schemas/Series' + + GetSeriesListResponse: + type: object + required: + - series + properties: + series: + type: array + items: + $ref: '#/components/schemas/Series' + + GetSeriesFeeChangesResponse: + type: object + required: + - series_fee_change_arr + properties: + series_fee_change_arr: + type: array + items: + $ref: '#/components/schemas/SeriesFeeChange' + + SettlementSource: + type: object + properties: + name: + type: string + description: Name of the settlement source + x-go-type-skip-optional-pointer: true + url: + type: string + description: URL to the settlement source + x-go-type-skip-optional-pointer: true + + GetMarketsResponse: + type: object + required: + - markets + - cursor + properties: + markets: + type: array + items: + $ref: '#/components/schemas/Market' + cursor: + type: string + + GetMarketResponse: + type: object + required: + - market + properties: + market: + $ref: '#/components/schemas/Market' + + MveSelectedLeg: + type: object + properties: + event_ticker: + type: string + description: Unique identifier for the selected event + x-go-type-skip-optional-pointer: true + market_ticker: + type: string + description: Unique identifier for the selected market + x-go-type-skip-optional-pointer: true + side: + type: string + description: The side of the selected market + x-go-type-skip-optional-pointer: true + yes_settlement_value_dollars: + $ref: '#/components/schemas/FixedPointDollars' + nullable: true + x-omitempty: true + description: The settlement value of the YES/LONG side of the contract in dollars. Only filled after determination + + PriceRange: + type: object + required: + - start + - end + - step + properties: + start: + type: string + description: Starting price for this range in dollars + end: + type: string + description: Ending price for this range in dollars + step: + type: string + description: Price step/tick size for this range in dollars + + Market: + type: object + required: + - ticker + - event_ticker + - market_type + - title + - subtitle + - yes_sub_title + - no_sub_title + - created_time + - updated_time + - open_time + - close_time + - expiration_time + - latest_expiration_time + - settlement_timer_seconds + - status + - response_price_units + - notional_value + - notional_value_dollars + - yes_bid + - yes_bid_dollars + - yes_ask + - yes_ask_dollars + - no_bid + - no_bid_dollars + - no_ask + - no_ask_dollars + - last_price + - last_price_dollars + - previous_yes_bid + - previous_yes_bid_dollars + - previous_yes_ask + - previous_yes_ask_dollars + - previous_price + - previous_price_dollars + - volume + - volume_fp + - volume_24h + - volume_24h_fp + - liquidity + - liquidity_dollars + - open_interest + - open_interest_fp + - result + - can_close_early + - expiration_value + - rules_primary + - rules_secondary + - tick_size + - price_level_structure + - price_ranges + properties: + ticker: + type: string + event_ticker: + type: string + market_type: + type: string + enum: [binary, scalar] + description: Identifies the type of market + title: + type: string + deprecated: true + subtitle: + type: string + deprecated: true + yes_sub_title: + type: string + description: Shortened title for the yes side of this market + no_sub_title: + type: string + description: Shortened title for the no side of this market + created_time: + type: string + format: date-time + updated_time: + type: string + format: date-time + description: Time of the last market stats update + open_time: + type: string + format: date-time + close_time: + type: string + format: date-time + expected_expiration_time: + type: string + format: date-time + nullable: true + x-omitempty: true + description: Time when this market is expected to expire + expiration_time: + type: string + format: date-time + deprecated: true + latest_expiration_time: + type: string + format: date-time + description: Latest possible time for this market to expire + settlement_timer_seconds: + type: integer + description: The amount of time after determination that the market settles + status: + type: string + enum: [initialized, inactive, active, closed, determined, disputed, amended, finalized] + description: The current status of the market in its lifecycle. + response_price_units: + type: string + enum: [usd_cent] + deprecated: true + description: 'DEPRECATED: Use price_level_structure and price_ranges instead.' + yes_bid: + type: number + deprecated: true + description: 'DEPRECATED: Use yes_bid_dollars instead.' + yes_bid_dollars: + $ref: '#/components/schemas/FixedPointDollars' + description: Price for the highest YES buy offer on this market in dollars + yes_ask: + type: number + deprecated: true + description: 'DEPRECATED: Use yes_ask_dollars instead.' + yes_ask_dollars: + $ref: '#/components/schemas/FixedPointDollars' + description: Price for the lowest YES sell offer on this market in dollars + no_bid: + type: number + deprecated: true + description: 'DEPRECATED: Use no_bid_dollars instead.' + no_bid_dollars: + $ref: '#/components/schemas/FixedPointDollars' + description: Price for the highest NO buy offer on this market in dollars + no_ask: + type: number + deprecated: true + description: 'DEPRECATED: Use no_ask_dollars instead.' + no_ask_dollars: + $ref: '#/components/schemas/FixedPointDollars' + description: Price for the lowest NO sell offer on this market in dollars + last_price: + type: number + deprecated: true + description: 'DEPRECATED: Use last_price_dollars instead.' + last_price_dollars: + $ref: '#/components/schemas/FixedPointDollars' + description: Price for the last traded YES contract on this market in dollars + volume: + type: integer + volume_fp: + $ref: '#/components/schemas/FixedPointCount' + description: String representation of the market volume in contracts + volume_24h: + type: integer + volume_24h_fp: + $ref: '#/components/schemas/FixedPointCount' + description: String representation of the 24h market volume in contracts + result: + type: string + enum: ['yes', 'no', 'scalar', ''] + can_close_early: + type: boolean + open_interest: + type: integer + description: Number of contracts bought on this market disconsidering netting + open_interest_fp: + $ref: '#/components/schemas/FixedPointCount' + description: String representation of the number of contracts bought on this market disconsidering netting + notional_value: + type: integer + deprecated: true + description: 'DEPRECATED: Use notional_value_dollars instead.' + notional_value_dollars: + $ref: '#/components/schemas/FixedPointDollars' + description: The total value of a single contract at settlement in dollars + previous_yes_bid: + type: integer + deprecated: true + description: 'DEPRECATED: Use previous_yes_bid_dollars instead.' + previous_yes_bid_dollars: + $ref: '#/components/schemas/FixedPointDollars' + description: Price for the highest YES buy offer on this market a day ago in dollars + previous_yes_ask: + type: integer + deprecated: true + description: 'DEPRECATED: Use previous_yes_ask_dollars instead.' + previous_yes_ask_dollars: + $ref: '#/components/schemas/FixedPointDollars' + description: Price for the lowest YES sell offer on this market a day ago in dollars + previous_price: + type: integer + deprecated: true + description: 'DEPRECATED: Use previous_price_dollars instead.' + previous_price_dollars: + $ref: '#/components/schemas/FixedPointDollars' + description: Price for the last traded YES contract on this market a day ago in dollars + liquidity: + type: integer + deprecated: true + description: 'DEPRECATED: Use liquidity_dollars instead.' + liquidity_dollars: + $ref: '#/components/schemas/FixedPointDollars' + description: Value for current offers in this market in dollars + settlement_value: + type: integer + nullable: true + x-omitempty: true + description: The settlement value of the YES/LONG side of the contract in cents. Only filled after determination + settlement_value_dollars: + $ref: '#/components/schemas/FixedPointDollars' + nullable: true + x-omitempty: true + description: The settlement value of the YES/LONG side of the contract in dollars. Only filled after determination + settlement_ts: + type: string + format: date-time + nullable: true + x-omitempty: true + description: Timestamp when the market was settled. Only filled for settled markets + expiration_value: + type: string + description: The value that was considered for the settlement + fee_waiver_expiration_time: + type: string + format: date-time + nullable: true + x-omitempty: true + description: Time when this market's fee waiver expires + early_close_condition: + type: string + nullable: true + x-omitempty: true + description: The condition under which the market can close early + x-go-type-skip-optional-pointer: true + tick_size: + type: integer + deprecated: true + description: 'DEPRECATED: Use price_level_structure and price_ranges instead.' + strike_type: + type: string + enum: [greater, greater_or_equal, less, less_or_equal, between, functional, custom, structured] + x-omitempty: true + description: Strike type defines how the market strike is defined and evaluated + x-go-type-skip-optional-pointer: true + floor_strike: + type: number + format: double + nullable: true + x-omitempty: true + description: Minimum expiration value that leads to a YES settlement + cap_strike: + type: number + format: double + nullable: true + x-omitempty: true + description: Maximum expiration value that leads to a YES settlement + functional_strike: + type: string + nullable: true + x-omitempty: true + description: Mapping from expiration values to settlement values + custom_strike: + type: object + nullable: true + x-omitempty: true + description: Expiration value for each target that leads to a YES settlement + rules_primary: + type: string + description: A plain language description of the most important market terms + rules_secondary: + type: string + description: A plain language description of secondary market terms + mve_collection_ticker: + type: string + x-omitempty: true + description: The ticker of the multivariate event collection + x-go-type-skip-optional-pointer: true + mve_selected_legs: + type: array + x-omitempty: true + items: + $ref: '#/components/schemas/MveSelectedLeg' + x-go-type-skip-optional-pointer: true + primary_participant_key: + type: string + nullable: true + x-omitempty: true + price_level_structure: + type: string + description: Price level structure for this market, defining price ranges and tick sizes + price_ranges: + type: array + description: Valid price ranges for orders on this market + items: + $ref: '#/components/schemas/PriceRange' + is_provisional: + type: boolean + x-omitempty: true + description: If true, the market may be removed after determination if there is no activity on it + x-go-type-skip-optional-pointer: true + +tags: + - name: api-keys + description: API key management endpoints + - name: orders + description: Order management endpoints + - name: order-groups + description: Order group management endpoints + - name: portfolio + description: Portfolio and balance information endpoints + - name: communications + description: Request-for-quote (RFQ) endpoints + - name: multivariate + description: Multivariate event collection endpoints + - name: exchange + description: Exchange status and information endpoints + - name: live-data + description: Live data endpoints + - name: markets + description: Market data endpoints + - name: milestone + description: Milestone endpoints + - name: search + description: Search and filtering endpoints + - name: incentive-programs + description: Incentive program endpoints + - name: fcm + description: FCM member specific endpoints + - name: events + description: Event endpoints + - name: structured-targets + description: Structured targets endpoints \ No newline at end of file diff --git a/scripts/live_api_smoke.py b/scripts/live_api_smoke.py index 6251a19..750a3cf 100644 --- a/scripts/live_api_smoke.py +++ b/scripts/live_api_smoke.py @@ -51,7 +51,7 @@ # Path hints for failed calls. Shown when KALSHI_SMOKE_DEBUG=1. PATH_HINTS: dict[tuple[str, str], str] = { ("exchange", "get_series_fee_changes"): "GET /series/fee_changes", - ("exchange", "get_user_data_timestamp"): "GET /exchange/user-data-timestamp", + ("exchange", "get_user_data_timestamp"): "GET /exchange/user_data_timestamp", ( "markets", "get_market_candlesticks", @@ -666,8 +666,7 @@ async def _batch_create(client: RestClient, ctx: dict) -> Any: async def _create_subaccount(client: RestClient, ctx: dict) -> Any: - nick = f"smoke-{int(time.time())}" - r = await portfolio.create_subaccount(client, nickname=nick) + r = await portfolio.create_subaccount(client) ctx["created_subaccount_id"] = (r or {}).get("subaccount_number") return r @@ -677,7 +676,11 @@ async def _transfer_between_subaccounts(client: RestClient, ctx: dict) -> Any: if to_id is None: raise ValueError("skipped; subaccounts not enabled") return await portfolio.transfer_between_subaccounts( - client, from_subaccount=0, to_subaccount=int(to_id), amount=1 + client, + client_transfer_id=f"smoke-{int(time.time())}", + from_subaccount=0, + to_subaccount=int(to_id), + amount_cents=1, ) diff --git a/src/kyro/exceptions.py b/src/kyro/exceptions.py index 53de48f..670a8fa 100644 --- a/src/kyro/exceptions.py +++ b/src/kyro/exceptions.py @@ -31,8 +31,10 @@ class KyroHTTPError(KyroError): Attributes: status: HTTP status code (e.g. 400, 401, 404, 500). - response_body: Raw response body, if available (bytes or decoded str). - error_code: Optional API-level error code from Kalshi response. + response_body: Parsed response (dict) or raw bytes/str. + error_code: API error code from Kalshi (e.g. ``code``, ``error_code``). + error_message: Human-readable message from Kalshi when present. + error_details: Additional details from Kalshi when present. """ def __init__( @@ -42,12 +44,16 @@ def __init__( status: int, response_body: bytes | str | dict | None = None, error_code: str | None = None, + error_message: str | None = None, + error_details: str | None = None, **kwargs: Any, ) -> None: super().__init__(message, **kwargs) self.status = status self.response_body = response_body self.error_code = error_code + self.error_message = error_message + self.error_details = error_details def __str__(self) -> str: # Include status, error_code, and response_body so the traceback line diff --git a/src/kyro/models.py b/src/kyro/models.py new file mode 100644 index 0000000..a64d793 --- /dev/null +++ b/src/kyro/models.py @@ -0,0 +1,174 @@ +"""Type-safe models and enums aligned with the Kalshi OpenAPI spec. + +Use these for validation and IDE support. The API modules validate request +bodies and raise :exc:`KyroValidationError` when values are invalid. +""" + +from __future__ import annotations + +from enum import Enum + +from pydantic import BaseModel, ConfigDict, Field, model_validator + + +# --- Enums (str-valued for JSON and API compatibility) --- + + +class Side(str, Enum): + """Order side. Use in create_order, amend_order.""" + + YES = "yes" + NO = "no" + + +class Action(str, Enum): + """Order action. Use in create_order, amend_order.""" + + BUY = "buy" + SELL = "sell" + + +class OrderType(str, Enum): + """Order type.""" + + LIMIT = "limit" + MARKET = "market" + + +class TimeInForce(str, Enum): + """Order time-in-force.""" + + FILL_OR_KILL = "fill_or_kill" + GOOD_TILL_CANCELED = "good_till_canceled" + IMMEDIATE_OR_CANCEL = "immediate_or_cancel" + + +class OrderStatus(str, Enum): + """Order status filter for get_orders.""" + + RESTING = "resting" + CANCELED = "canceled" + EXECUTED = "executed" + + +class MarketStatus(str, Enum): + """Market status filter.""" + + UNOPENED = "unopened" + OPEN = "open" + PAUSED = "paused" + CLOSED = "closed" + SETTLED = "settled" + + +class PeriodInterval(int, Enum): + """Candlestick period in minutes. Use in get_market_candlesticks, get_event_candlesticks.""" + + ONE_MIN = 1 + ONE_HOUR = 60 + ONE_DAY = 1440 + + +class SelfTradePreventionType(str, Enum): + """Self-trade prevention for orders.""" + + TAKER_AT_CROSS = "taker_at_cross" + MAKER = "maker" + + +# --- Request models (validated before sending) --- + + +class CreateOrderRequest(BaseModel): + """Create order body. Required: ticker, side, action. Validated per OpenAPI.""" + + model_config = ConfigDict(extra="allow") + + ticker: str = Field(..., min_length=1) + side: str = Field(..., pattern="^(yes|no)$") + action: str = Field(..., pattern="^(buy|sell)$") + count: int | None = Field(None, ge=1) + count_fp: str | None = None + type: str | None = Field(None, pattern="^(limit|market)$") + yes_price: int | None = Field(None, ge=1, le=99) + no_price: int | None = Field(None, ge=1, le=99) + yes_price_dollars: str | None = None + no_price_dollars: str | None = None + client_order_id: str | None = None + expiration_ts: int | None = None + time_in_force: str | None = Field( + None, pattern="^(fill_or_kill|good_till_canceled|immediate_or_cancel)$" + ) + buy_max_cost: int | None = None + post_only: bool | None = None + reduce_only: bool | None = None + sell_position_floor: int | None = None + self_trade_prevention_type: str | None = Field( + None, pattern="^(taker_at_cross|maker)$" + ) + order_group_id: str | None = None + cancel_order_on_pause: bool | None = None + subaccount: int | None = Field(None, ge=0) + + +class AmendOrderRequest(BaseModel): + """Amend order body. Required: ticker, side, action. Validated per OpenAPI.""" + + model_config = ConfigDict(extra="allow") + + ticker: str + side: str = Field(..., pattern="^(yes|no)$") + action: str = Field(..., pattern="^(buy|sell)$") + yes_price: int | None = Field(None, ge=1, le=99) + no_price: int | None = Field(None, ge=1, le=99) + yes_price_dollars: str | None = None + no_price_dollars: str | None = None + count: int | None = Field(None, ge=1) + count_fp: str | None = None + client_order_id: str | None = None + updated_client_order_id: str | None = None + expiration_ts: int | None = None + + +class DecreaseOrderRequest(BaseModel): + """Decrease order body. Exactly one of (reduce_by|reduce_by_fp) or (reduce_to|reduce_to_fp).""" + + model_config = ConfigDict(extra="forbid") + + reduce_by: int | None = Field(None, ge=1) + reduce_by_fp: str | None = None + reduce_to: int | None = Field(None, ge=0) + reduce_to_fp: str | None = None + + @model_validator(mode="after") + def check_exactly_one_group(self) -> "DecreaseOrderRequest": + reduce_by_any = self.reduce_by is not None or self.reduce_by_fp is not None + reduce_to_any = self.reduce_to is not None or self.reduce_to_fp is not None + if reduce_by_any and reduce_to_any: + raise ValueError("Provide (reduce_by or reduce_by_fp) OR (reduce_to or reduce_to_fp), not both") + if not reduce_by_any and not reduce_to_any: + raise ValueError("Provide (reduce_by or reduce_by_fp) or (reduce_to or reduce_to_fp)") + return self + + +class ApplySubaccountTransferRequest(BaseModel): + """Transfer between subaccounts. Required: client_transfer_id, from_subaccount, to_subaccount, amount_cents.""" + + client_transfer_id: str = Field(..., min_length=1) + from_subaccount: int = Field(..., ge=0, le=32) + to_subaccount: int = Field(..., ge=0, le=32) + amount_cents: int + + +# --- Error response (for parsing 4xx/5xx bodies) --- + + +class ErrorResponse(BaseModel): + """Kalshi error body. code and message are common; details and service may be absent.""" + + model_config = ConfigDict(extra="ignore") + + code: str | None = None + message: str | None = None + details: str | None = None + service: str | None = None diff --git a/src/kyro/rest/api/events.py b/src/kyro/rest/api/events.py index 5a77738..84c7625 100644 --- a/src/kyro/rest/api/events.py +++ b/src/kyro/rest/api/events.py @@ -103,7 +103,22 @@ async def get_multivariate_events( *, limit: int | None = None, cursor: str | None = None, + series_ticker: str | None = None, + collection_ticker: str | None = None, + with_nested_markets: bool | None = None, ) -> Any: - """Get multivariate events. `GET /events/multivariate`.""" - params = _clean({"limit": limit, "cursor": cursor}) + """Get multivariate (combo) events. `GET /events/multivariate`. + + series_ticker and collection_ticker are mutually exclusive. with_nested_markets + includes markets in each event. + """ + params = _clean( + { + "limit": limit, + "cursor": cursor, + "series_ticker": series_ticker, + "collection_ticker": collection_ticker, + "with_nested_markets": with_nested_markets, + } + ) return await client.get("/events/multivariate", params=params or None) diff --git a/src/kyro/rest/api/exchange.py b/src/kyro/rest/api/exchange.py index a9c3757..0983fcb 100644 --- a/src/kyro/rest/api/exchange.py +++ b/src/kyro/rest/api/exchange.py @@ -56,6 +56,7 @@ async def get_series_fee_changes( async def get_user_data_timestamp(client: RestClient) -> Any: """Get user data timestamp (for sync/consistency). - `GET /exchange/user-data-timestamp` — auth required. + `GET /exchange/user_data_timestamp` — auth required. + Response: ``as_of_time`` (date-time) per OpenAPI. """ - return await client.get("/exchange/user-data-timestamp") + return await client.get("/exchange/user_data_timestamp") diff --git a/src/kyro/rest/api/markets.py b/src/kyro/rest/api/markets.py index 52d7493..6d50e78 100644 --- a/src/kyro/rest/api/markets.py +++ b/src/kyro/rest/api/markets.py @@ -144,9 +144,15 @@ async def get_market_candlesticks( async def get_series( client: RestClient, series_ticker: str, + *, + include_volume: bool | None = None, ) -> Any: - """Get a single series by ticker. `GET /series/{series_ticker}`.""" - return await client.get(f"/series/{series_ticker}") + """Get a single series by ticker. `GET /series/{series_ticker}`. + + include_volume: if true, includes total volume across all events in the series. + """ + params = _clean({"include_volume": include_volume}) + return await client.get(f"/series/{series_ticker}", params=params or None) async def get_series_list( @@ -154,9 +160,25 @@ async def get_series_list( *, limit: int | None = None, cursor: str | None = None, + category: str | None = None, + tags: str | None = None, + include_product_metadata: bool | None = None, + include_volume: bool | None = None, ) -> Any: - """Get list of series. `GET /series`.""" - params = _clean({"limit": limit, "cursor": cursor}) + """Get list of series. `GET /series`. + + category, tags: filters. include_product_metadata, include_volume: add those fields. + """ + params = _clean( + { + "limit": limit, + "cursor": cursor, + "category": category, + "tags": tags, + "include_product_metadata": include_product_metadata, + "include_volume": include_volume, + } + ) return await client.get("/series", params=params or None) diff --git a/src/kyro/rest/api/orders.py b/src/kyro/rest/api/orders.py index e3af402..1d2c5bf 100644 --- a/src/kyro/rest/api/orders.py +++ b/src/kyro/rest/api/orders.py @@ -1,12 +1,18 @@ """Order endpoints. Auth required. -Ref: https://docs.kalshi.com/api-reference/orders/create-order +Ref: OpenAPI 3.0 / Kalshi Trade API. Request bodies are validated; invalid +values raise KyroValidationError before the request is sent. """ from __future__ import annotations from typing import TYPE_CHECKING, Any +from pydantic import ValidationError + +from kyro.exceptions import KyroValidationError +from kyro.models import AmendOrderRequest, CreateOrderRequest, DecreaseOrderRequest + if TYPE_CHECKING: from kyro.rest.client import RestClient @@ -81,7 +87,7 @@ async def create_order( Required: ticker, side (yes|no), action (buy|sell). Provide count or count_fp. type: limit|market. time_in_force: fill_or_kill|good_till_canceled|immediate_or_cancel. - yes_price/no_price 1–99 (cents). subaccount default 0. + yes_price/no_price 1–99 (cents). subaccount ≥ 0. Invalid values raise KyroValidationError. """ body = _clean( { @@ -109,6 +115,15 @@ async def create_order( **extra, } ) + try: + model = CreateOrderRequest.model_validate(body) + body = model.model_dump(exclude_none=True) + if getattr(model, "model_extra", None): + body.update(model.model_extra) + except ValidationError as e: + raise KyroValidationError( + f"Invalid create_order body: {e}", details=e.errors() + ) from e return await client.post("/portfolio/orders", json=body) @@ -141,8 +156,9 @@ async def amend_order( ) -> Any: """Amend an order. `POST /portfolio/orders/{order_id}/amend`. - Kalshi requires ticker, side (yes|no), action (buy|sell) plus any of: - yes_price, no_price, yes_price_dollars, no_price_dollars, count, count_fp, etc. + Required: ticker, side (yes|no), action (buy|sell). Optionally yes_price, no_price + (1–99), yes_price_dollars, no_price_dollars, count (≥1), count_fp, client_order_id, + updated_client_order_id, expiration_ts. Invalid values raise KyroValidationError. """ body = _clean( { @@ -161,6 +177,15 @@ async def amend_order( **extra, } ) + try: + model = AmendOrderRequest.model_validate(body) + body = model.model_dump(exclude_none=True) + if getattr(model, "model_extra", None): + body.update(model.model_extra) + except ValidationError as e: + raise KyroValidationError( + f"Invalid amend_order body: {e}", details=e.errors() + ) from e return await client.post(f"/portfolio/orders/{order_id}/amend", json=body) @@ -172,12 +197,11 @@ async def decrease_order( reduce_by_fp: str | None = None, reduce_to: int | None = None, reduce_to_fp: str | None = None, - **extra: Any, ) -> Any: """Decrease an order size. `POST /portfolio/orders/{order_id}/decrease`. - Provide exactly one of: (reduce_by or reduce_by_fp) or (reduce_to or reduce_to_fp). - reduce_by: contracts to reduce by; reduce_to: contracts to reduce to. + Exactly one of: (reduce_by or reduce_by_fp) or (reduce_to or reduce_to_fp). + reduce_by ≥ 1; reduce_to ≥ 0. Invalid or missing group raises KyroValidationError. """ body = _clean( { @@ -185,18 +209,38 @@ async def decrease_order( "reduce_by_fp": reduce_by_fp, "reduce_to": reduce_to, "reduce_to_fp": reduce_to_fp, - **extra, } ) + try: + model = DecreaseOrderRequest.model_validate(body) + body = model.model_dump(exclude_none=True) + except ValidationError as e: + raise KyroValidationError( + f"Invalid decrease_order body: {e}", details=e.errors() + ) from e return await client.post(f"/portfolio/orders/{order_id}/decrease", json=body) async def batch_create_orders(client: RestClient, orders: list[dict[str, Any]]) -> Any: """Batch create orders. `POST /portfolio/orders/batched`. - orders: list of order payloads (same shape as create_order body). + orders: list of order payloads (same shape as create_order body). Each item + is validated with CreateOrderRequest; invalid values raise KyroValidationError + (message includes the failing index). Response (201): {orders: [{client_order_id?, order?, error?}, ...]}. """ - return await client.post("/portfolio/orders/batched", json={"orders": orders}) + validated: list[dict[str, Any]] = [] + for i, o in enumerate(orders): + try: + model = CreateOrderRequest.model_validate(o) + body = model.model_dump(exclude_none=True) + if getattr(model, "model_extra", None): + body.update(model.model_extra) + validated.append(body) + except ValidationError as e: + raise KyroValidationError( + f"Invalid order at index {i}: {e}", details=e.errors() + ) from e + return await client.post("/portfolio/orders/batched", json={"orders": validated}) async def batch_cancel_orders( diff --git a/src/kyro/rest/api/portfolio.py b/src/kyro/rest/api/portfolio.py index 4eb8dbd..262b541 100644 --- a/src/kyro/rest/api/portfolio.py +++ b/src/kyro/rest/api/portfolio.py @@ -1,12 +1,17 @@ """Portfolio endpoints. Auth required. -Ref: https://docs.kalshi.com/api-reference/portfolio/get-balance +Ref: OpenAPI 3.0 / Kalshi Trade API. Paths and request bodies aligned with spec. """ from __future__ import annotations from typing import TYPE_CHECKING, Any +from pydantic import ValidationError + +from kyro.exceptions import KyroValidationError +from kyro.models import ApplySubaccountTransferRequest + if TYPE_CHECKING: from kyro.rest.client import RestClient @@ -41,10 +46,12 @@ async def get_positions( ticker: str | None = None, event_ticker: str | None = None, subaccount: int | None = None, + settlement_status: str | None = None, ) -> Any: """Get positions. `GET /portfolio/positions`. count_filter: position, total_traded (comma-separated). limit 1–1000. + settlement_status: all, unsettled, settled (default unsettled). """ params = _clean( { @@ -54,6 +61,7 @@ async def get_positions( "ticker": ticker, "event_ticker": event_ticker, "subaccount": subaccount, + "settlement_status": settlement_status, } ) return await client.get("/portfolio/positions", params=params or None) @@ -63,6 +71,7 @@ async def get_fills( client: RestClient, *, ticker: str | None = None, + order_id: str | None = None, event_ticker: str | None = None, min_ts: int | None = None, max_ts: int | None = None, @@ -70,10 +79,14 @@ async def get_fills( cursor: str | None = None, subaccount: int | None = None, ) -> Any: - """Get fill history. `GET /portfolio/fills`.""" + """Get fill history. `GET /portfolio/fills`. + + Query: ticker, order_id, event_ticker, min_ts, max_ts, limit, cursor, subaccount. + """ params = _clean( { "ticker": ticker, + "order_id": order_id, "event_ticker": event_ticker, "min_ts": min_ts, "max_ts": max_ts, @@ -94,9 +107,11 @@ async def get_settlements( max_ts: int | None = None, limit: int | None = None, cursor: str | None = None, - subaccount: int | None = None, ) -> Any: - """Get settlements. `GET /portfolio/settlements`.""" + """Get settlements. `GET /portfolio/settlements`. + + Query: ticker, event_ticker, min_ts, max_ts, limit, cursor (OpenAPI: no subaccount). + """ params = _clean( { "ticker": ticker, @@ -105,7 +120,6 @@ async def get_settlements( "max_ts": max_ts, "limit": limit, "cursor": cursor, - "subaccount": subaccount, } ) return await client.get("/portfolio/settlements", params=params or None) @@ -119,31 +133,40 @@ async def get_total_resting_order_value(client: RestClient) -> Any: return await client.get("/portfolio/summary/total_resting_order_value") -async def create_subaccount(client: RestClient, *, nickname: str | None = None) -> Any: - """Create a subaccount. `POST /portfolio/subaccounts`.""" - body = _clean({"nickname": nickname}) if nickname is not None else {} - return await client.post("/portfolio/subaccounts", json=body) +async def create_subaccount(client: RestClient) -> Any: + """Create a subaccount. `POST /portfolio/subaccounts`. + + No request body per OpenAPI. Response: subaccount_number. + """ + return await client.post("/portfolio/subaccounts") async def transfer_between_subaccounts( client: RestClient, *, + client_transfer_id: str, from_subaccount: int, to_subaccount: int, - amount: int, + amount_cents: int, ) -> Any: - """Transfer between subaccounts. `POST /portfolio/transfers`. + """Transfer between subaccounts. `POST /portfolio/subaccounts/transfer`. - amount in cents. + Required: client_transfer_id (idempotency, e.g. UUID), from_subaccount (0–32), + to_subaccount (0–32), amount_cents. Invalid values raise KyroValidationError. """ - return await client.post( - "/portfolio/transfers", - json={ - "from_subaccount": from_subaccount, - "to_subaccount": to_subaccount, - "amount": amount, - }, - ) + try: + model = ApplySubaccountTransferRequest( + client_transfer_id=client_transfer_id, + from_subaccount=from_subaccount, + to_subaccount=to_subaccount, + amount_cents=amount_cents, + ) + body = model.model_dump() + except ValidationError as e: + raise KyroValidationError( + f"Invalid transfer_between_subaccounts: {e}", details=e.errors() + ) from e + return await client.post("/portfolio/subaccounts/transfer", json=body) async def get_all_subaccount_balances(client: RestClient) -> Any: diff --git a/src/kyro/rest/client.py b/src/kyro/rest/client.py index b1e4092..4d92095 100644 --- a/src/kyro/rest/client.py +++ b/src/kyro/rest/client.py @@ -62,18 +62,19 @@ def _ensure_session(self) -> aiohttp.ClientSession: raise KyroError("RestClient used outside async context manager") return self._session_mgr.session - def _parse_error_body(self, raw: bytes) -> tuple[Any, str | None]: - """Parse error response body; return (parsed, optional error_code).""" + def _parse_error_body(self, raw: bytes) -> tuple[Any, str | None, str | None, str | None]: + """Parse error body; return (parsed, error_code, message, details).""" try: data = loads(raw) except KyroValidationError: - return raw.decode("utf-8", errors="replace"), None + return raw.decode("utf-8", errors="replace"), None, None, None if isinstance(data, dict): code = data.get("error", data.get("error_code", data.get("code"))) - if isinstance(code, str): - return data, code - return data, None - return data, None + err_code = code if isinstance(code, str) else None + msg = data.get("message") if isinstance(data.get("message"), str) else None + details = data.get("details") if isinstance(data.get("details"), str) else None + return data, err_code, msg, details + return data, None, None, None async def _request( self, @@ -130,9 +131,14 @@ async def _request( raise KyroConnectionError(str(e)) from e if status >= 400: - parsed, err_code = self._parse_error_body(raw) + parsed, err_code, err_msg, err_details = self._parse_error_body(raw) raise KyroHTTPError( - "Kalshi API error", status=status, response_body=parsed, error_code=err_code + "Kalshi API error", + status=status, + response_body=parsed, + error_code=err_code, + error_message=err_msg, + error_details=err_details, ) if not raw: diff --git a/tests/conftest.py b/tests/conftest.py index 53b646f..46c9ed0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -110,16 +110,24 @@ async def _portfolio_order_decrease(r: web.Request) -> web.Response: ) +async def _portfolio_batch_create(r: web.Request) -> web.Response: + body = await r.json() + orders_in = body.get("orders", []) + return _mk_json( + {"orders": [{"order": {"order_id": f"b-{i+1}"}} for i in range(len(orders_in))]} + ) + + async def _event_candlesticks(r: web.Request) -> web.Response: return _mk_json({"candlesticks": []}) async def _search_filters_by_sport(_: web.Request) -> web.Response: - return _mk_json({"sports": []}) + return _mk_json({"filters_by_sports": {}, "sport_ordering": []}) async def _search_tags_by_categories(_: web.Request) -> web.Response: - return _mk_json({"categories": []}) + return _mk_json({"tags_by_categories": {}}) async def _portfolio_summary(_: web.Request) -> web.Response: @@ -133,7 +141,7 @@ async def _live_data(r: web.Request) -> web.Response: async def _exchange_user_data_timestamp(_: web.Request) -> web.Response: - return _mk_json({"timestamp": 1704067200}) + return _mk_json({"as_of_time": "2025-01-15T12:00:00Z"}) def create_kalshi_app() -> web.Application: @@ -141,7 +149,7 @@ def create_kalshi_app() -> web.Application: app = web.Application() # Exchange app.router.add_get("/exchange/status", _exchange_status) - app.router.add_get("/exchange/user-data-timestamp", _exchange_user_data_timestamp) + app.router.add_get("/exchange/user_data_timestamp", _exchange_user_data_timestamp) # Markets app.router.add_get("/markets", _markets_list) app.router.add_get(r"/markets/{ticker}", _market_detail) @@ -164,6 +172,7 @@ def create_kalshi_app() -> web.Application: app.router.add_get("/portfolio/orders", _portfolio_orders_list) app.router.add_get(r"/portfolio/orders/{order_id}", _portfolio_order_detail) app.router.add_post("/portfolio/orders", _portfolio_order_create) + app.router.add_post("/portfolio/orders/batched", _portfolio_batch_create) app.router.add_post(r"/portfolio/orders/{order_id}/decrease", _portfolio_order_decrease) app.router.add_delete(r"/portfolio/orders/{order_id}", _portfolio_order_delete) # Test helpers: empty, errors, echo, params, slow diff --git a/tests/test_api_modules.py b/tests/test_api_modules.py index df7bbba..cb9a23a 100644 --- a/tests/test_api_modules.py +++ b/tests/test_api_modules.py @@ -2,7 +2,10 @@ from __future__ import annotations +import pytest + from kyro import RestClient +from kyro.exceptions import KyroValidationError from kyro.rest.api import events, exchange, markets, orders, portfolio, search @@ -14,8 +17,8 @@ async def test_get_exchange_status(kyro_client: RestClient) -> None: async def test_get_user_data_timestamp(kyro_client: RestClient) -> None: data = await exchange.get_user_data_timestamp(kyro_client) - assert "timestamp" in data - assert isinstance(data["timestamp"], int) + assert "as_of_time" in data + assert isinstance(data["as_of_time"], str) async def test_get_markets(kyro_client: RestClient) -> None: @@ -84,13 +87,14 @@ async def test_get_event_candlesticks(kyro_client: RestClient) -> None: async def test_get_sports_filters(kyro_client: RestClient) -> None: data = await search.get_sports_filters(kyro_client) - assert "sports" in data - assert isinstance(data["sports"], list) + assert "filters_by_sports" in data + assert "sport_ordering" in data + assert isinstance(data["sport_ordering"], list) async def test_get_tags_by_categories(kyro_client: RestClient) -> None: data = await search.get_tags_by_categories(kyro_client) - assert "categories" in data + assert "tags_by_categories" in data async def test_get_orders(kyro_client: RestClient) -> None: @@ -142,3 +146,58 @@ async def test_get_balance(kyro_client: RestClient) -> None: assert "balance" in data assert "portfolio_value" in data assert "updated_ts" in data + + +async def test_create_order_invalid_side_raises(kyro_client: RestClient) -> None: + with pytest.raises(KyroValidationError): + await orders.create_order( + kyro_client, ticker="KXBTC", side="invalid", action="buy", count=1 + ) + + +async def test_decrease_order_both_groups_raises(kyro_client: RestClient) -> None: + with pytest.raises(KyroValidationError): + await orders.decrease_order( + kyro_client, "ord-123", reduce_by=1, reduce_to=0 + ) + + +async def test_decrease_order_neither_group_raises(kyro_client: RestClient) -> None: + with pytest.raises(KyroValidationError): + await orders.decrease_order(kyro_client, "ord-123") + + +async def test_transfer_invalid_subaccount_raises(kyro_client: RestClient) -> None: + with pytest.raises(KyroValidationError): + await portfolio.transfer_between_subaccounts( + kyro_client, + client_transfer_id="test-1", + from_subaccount=33, + to_subaccount=0, + amount_cents=1, + ) + + +async def test_batch_create_orders(kyro_client: RestClient) -> None: + data = await orders.batch_create_orders( + kyro_client, + [ + {"ticker": "KXBTC", "side": "yes", "action": "buy", "count": 1, "yes_price": 50}, + {"ticker": "KXBTC", "side": "no", "action": "buy", "count": 1, "no_price": 55}, + ], + ) + assert "orders" in data + assert len(data["orders"]) == 2 + assert data["orders"][0].get("order", {}).get("order_id") == "b-1" + + +async def test_batch_create_orders_invalid_at_index_raises(kyro_client: RestClient) -> None: + with pytest.raises(KyroValidationError) as exc_info: + await orders.batch_create_orders( + kyro_client, + [ + {"ticker": "KXBTC", "side": "yes", "action": "buy", "count": 1}, + {"ticker": "KXBTC", "side": "maybe", "action": "buy", "count": 1}, + ], + ) + assert "index 1" in str(exc_info.value) From 616997a21fea437d88fc308932424e00ae78d181 Mon Sep 17 00:00:00 2001 From: Brian Hartford Date: Fri, 6 Feb 2026 18:18:45 -0500 Subject: [PATCH 2/3] Remove get portfilo --- API_REFERENCE.md | 14 --------- README.md | 3 +- scripts/README.md | 53 ++++++++++++++++++++++++++++++++ scripts/live_api_smoke.py | 22 +++++++++++-- src/kyro/rest/api/orders.py | 56 ++++++++++++++++------------------ src/kyro/rest/api/portfolio.py | 9 ------ tests/conftest.py | 5 --- tests/test_api_modules.py | 6 ---- 8 files changed, 100 insertions(+), 68 deletions(-) create mode 100644 scripts/README.md diff --git a/API_REFERENCE.md b/API_REFERENCE.md index 4092435..3464fd2 100644 --- a/API_REFERENCE.md +++ b/API_REFERENCE.md @@ -951,20 +951,6 @@ All portfolio endpoints **require auth**. --- -### `get_portfolio` - -**HTTP:** `GET /portfolio` -**Auth:** Yes - -**Usage:** -```python -data = await portfolio.get_portfolio(client) -``` - -**Response (200):** portfolio summary (structure per Kalshi). If the endpoint is not available for an account, use `get_balance`, `get_positions`, `get_fills` instead. - ---- - ### `get_balance` **HTTP:** `GET /portfolio/balance` diff --git a/README.md b/README.md index ed3fe7d..99e543a 100644 --- a/README.md +++ b/README.md @@ -276,8 +276,7 @@ async with RestClient(KyroConfig()) as client: ) await orders.batch_cancel_orders(client, order_ids=["id1", "id2"]) - # Portfolio (auth) — filters: ticker, event_ticker, min_ts, max_ts, cursor, subaccount - await portfolio.get_portfolio(client) + # Portfolio (auth) bal = await portfolio.get_balance(client) pos = await portfolio.get_positions( client, ticker="KXBTC-24JAN15", limit=100 diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 0000000..394103a --- /dev/null +++ b/scripts/README.md @@ -0,0 +1,53 @@ +# Scripts + +## Live API smoke test + +**`live_api_smoke.py`** calls every kyro endpoint against the real Kalshi API. It discovers an open market, runs all read and mutating calls, and writes every request/response to an audit log for review. + +**Pass = 2xx only.** Any 4xx/5xx or exception is fail (we are not testing error handling). + +### Run + +From **repo root** (venv activated, network required): + +```bash +cp .env.example .env # edit with your production keys +pip install -e ".[dev]" +python scripts/live_api_smoke.py +``` + +Uses **production only** (`https://api.elections.kalshi.com/trade-api/v2`). Ignores `KALSHI_DEMO` / `KALSHI_BASE_URL` from `.env`. + +### Auth + +Auth is required. Put `KALSHI_ACCESS_KEY` and `KALSHI_PRIVATE_KEY` or `KALSHI_PRIVATE_KEY_PATH` in `.env` at project root. `KALSHI_PRIVATE_KEY_PATH` may be relative (e.g. `kal_key.pem` or `.kalshi/kal_key.pem`). + +### Audit log + +- **Path:** `live_smoke_audit.log` (or set `KALSHI_SMOKE_AUDIT_LOG`) +- **Location:** project root +- **Git:** `.gitignore`d + +Each request/response is logged. On 400/404, `ERROR_RESPONSE` and `PATH_HINT` are written to aid diagnosis. + +### Debug mode + +Set `KALSHI_SMOKE_DEBUG=1` for extra output on failures: + +- Path hints and full `response_body` on 4xx/5xx +- `request_info` and `ctx_keys` for each failing call +- Discovery try-log (get_market / get_market_candlesticks per ticker) + +```bash +KALSHI_SMOKE_DEBUG=1 python scripts/live_api_smoke.py +``` + +### Discovery + +Searches up to 100 open markets for one where `get_market` and `get_market_candlesticks` both return 200. If none, falls back to the first market where `get_market` returns 200. + +### Mutating calls + +Places 1-share limit orders @ 1¢, runs amend/cancel/decrease, batch create/cancel, subaccount create/transfer. All mutating endpoints are exercised. + +The script exits with code 1 if any call **fails**. diff --git a/scripts/live_api_smoke.py b/scripts/live_api_smoke.py index 750a3cf..9dec4aa 100644 --- a/scripts/live_api_smoke.py +++ b/scripts/live_api_smoke.py @@ -75,7 +75,6 @@ "events", "get_event_candlesticks", ): "GET /series/{series_ticker}/events/{event_ticker}/candlesticks?start_ts=&end_ts=&period_interval=1|60|1440", - ("portfolio", "get_portfolio"): "GET /portfolio (may 404 for some accounts)", } @@ -95,6 +94,12 @@ def _log_json(obj: Any) -> str: return json.dumps(obj, indent=2, default=str) +def _truncate(s: str, max_len: int) -> str: + if len(s) <= max_len: + return s + return s[: max_len - 3] + "..." + + def _resolve_request_info(request_info: dict | Callable[[dict], dict] | None, ctx: dict) -> dict: if request_info is None: return {} @@ -122,6 +127,9 @@ def _write_debug( if method == "cancel_order": oid = ctx.get("order_id") or ctx.get("order_id_2") parts.append(f" order_id_used={oid!r}") + parts.append(f" request_info={_log_json(req)}") + if ctx: + parts.append(f" ctx_keys={list(ctx.keys())}") if is_http and hasattr(e, "response_body") and e.response_body is not None: rb = e.response_body if isinstance(rb, dict): @@ -303,7 +311,16 @@ async def _run_and_log( ) else: _write_debug(log_file, module, method, req, ctx, e) - log_file.write(f"ERROR: {e.status} {getattr(e, 'error_code', '') or ''} {e}\n---\n\n") + log_file.write(f"ERROR: {e.status} {getattr(e, 'error_code', '') or ''} {e}\n") + # On 400/404, always log response_body (truncated) to aid diagnosis without DEBUG + if e.status in (400, 404) and getattr(e, "response_body", None) is not None: + rb = e.response_body + raw = _log_json(rb) if isinstance(rb, dict) else str(rb) + log_file.write(f"ERROR_RESPONSE: {_truncate(raw, 1200)}\n") + key = (module, method) + if key in PATH_HINTS: + log_file.write(f"PATH_HINT: {PATH_HINTS[key]}\n") + log_file.write("---\n\n") log_file.flush() results.append(Result(module, method, "fail", f"{e.status} {e.error_code or ''}")) except (KyroConnectionError, KyroTimeoutError) as e: @@ -428,7 +445,6 @@ async def _run_and_log( lambda ctx: {"tickers": _ticker(ctx)}, ), ("orders", "get_orders", lambda c, x: orders.get_orders(c, limit=5), {"limit": 5}), - ("portfolio", "get_portfolio", lambda c, x: portfolio.get_portfolio(c), {}), ("portfolio", "get_balance", lambda c, x: portfolio.get_balance(c), {}), ("portfolio", "get_positions", lambda c, x: portfolio.get_positions(c, limit=5), {"limit": 5}), ("portfolio", "get_fills", lambda c, x: portfolio.get_fills(c, limit=5), {"limit": 5}), diff --git a/src/kyro/rest/api/orders.py b/src/kyro/rest/api/orders.py index 1d2c5bf..5d641a5 100644 --- a/src/kyro/rest/api/orders.py +++ b/src/kyro/rest/api/orders.py @@ -89,37 +89,35 @@ async def create_order( type: limit|market. time_in_force: fill_or_kill|good_till_canceled|immediate_or_cancel. yes_price/no_price 1–99 (cents). subaccount ≥ 0. Invalid values raise KyroValidationError. """ - body = _clean( - { - "ticker": ticker, - "side": side, - "action": action, - "count": count, - "count_fp": count_fp, - "type": type, - "yes_price": yes_price, - "no_price": no_price, - "yes_price_dollars": yes_price_dollars, - "no_price_dollars": no_price_dollars, - "client_order_id": client_order_id, - "expiration_ts": expiration_ts, - "time_in_force": time_in_force, - "buy_max_cost": buy_max_cost, - "post_only": post_only, - "reduce_only": reduce_only, - "sell_position_floor": sell_position_floor, - "self_trade_prevention_type": self_trade_prevention_type, - "order_group_id": order_group_id, - "cancel_order_on_pause": cancel_order_on_pause, - "subaccount": subaccount, - **extra, - } - ) + raw = { + "ticker": ticker, + "side": side, + "action": action, + "count": count, + "count_fp": count_fp, + "type": type, + "yes_price": yes_price, + "no_price": no_price, + "yes_price_dollars": yes_price_dollars, + "no_price_dollars": no_price_dollars, + "client_order_id": client_order_id, + "expiration_ts": expiration_ts, + "time_in_force": time_in_force, + "buy_max_cost": buy_max_cost, + "post_only": post_only, + "reduce_only": reduce_only, + "sell_position_floor": sell_position_floor, + "self_trade_prevention_type": self_trade_prevention_type, + "order_group_id": order_group_id, + "cancel_order_on_pause": cancel_order_on_pause, + "subaccount": subaccount, + **extra, + } try: - model = CreateOrderRequest.model_validate(body) + model = CreateOrderRequest.model_validate(raw) body = model.model_dump(exclude_none=True) - if getattr(model, "model_extra", None): - body.update(model.model_extra) + model_extra = getattr(model, "model_extra", None) or {} + body.update({k: v for k, v in model_extra.items() if v is not None}) except ValidationError as e: raise KyroValidationError( f"Invalid create_order body: {e}", details=e.errors() diff --git a/src/kyro/rest/api/portfolio.py b/src/kyro/rest/api/portfolio.py index 262b541..7bfefbe 100644 --- a/src/kyro/rest/api/portfolio.py +++ b/src/kyro/rest/api/portfolio.py @@ -20,15 +20,6 @@ def _clean(params: dict[str, Any]) -> dict[str, Any]: return {k: v for k, v in params.items() if v is not None} -async def get_portfolio(client: RestClient) -> Any: - """Get portfolio summary. `GET /portfolio`. - - Auth required. May not be available for all accounts; prefer get_balance, - get_positions, get_fills for specific data. - """ - return await client.get("/portfolio") - - async def get_balance(client: RestClient) -> Any: """Get balance and portfolio value. `GET /portfolio/balance`. diff --git a/tests/conftest.py b/tests/conftest.py index 46c9ed0..d04bd90 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -130,10 +130,6 @@ async def _search_tags_by_categories(_: web.Request) -> web.Response: return _mk_json({"tags_by_categories": {}}) -async def _portfolio_summary(_: web.Request) -> web.Response: - return _mk_json({"portfolio_value": 10000, "balance": 10000}) - - async def _live_data(r: web.Request) -> web.Response: if "tickers" in r.query: return _mk_json({"tickers": r.query.get("tickers", "").split(",")}) @@ -167,7 +163,6 @@ def create_kalshi_app() -> web.Application: app.router.add_get("/search/filters_by_sport", _search_filters_by_sport) app.router.add_get("/search/tags_by_categories", _search_tags_by_categories) # Portfolio (auth-style; we don't enforce auth in tests) - app.router.add_get("/portfolio", _portfolio_summary) app.router.add_get("/portfolio/balance", _portfolio_balance) app.router.add_get("/portfolio/orders", _portfolio_orders_list) app.router.add_get(r"/portfolio/orders/{order_id}", _portfolio_order_detail) diff --git a/tests/test_api_modules.py b/tests/test_api_modules.py index cb9a23a..62fd993 100644 --- a/tests/test_api_modules.py +++ b/tests/test_api_modules.py @@ -135,12 +135,6 @@ async def test_decrease_order(kyro_client: RestClient) -> None: assert "reduced_by" in data -async def test_get_portfolio(kyro_client: RestClient) -> None: - data = await portfolio.get_portfolio(kyro_client) - assert "portfolio_value" in data - assert "balance" in data - - async def test_get_balance(kyro_client: RestClient) -> None: data = await portfolio.get_balance(kyro_client) assert "balance" in data From d4d956c9ca8f643e4840127452d68ddf31c08d2a Mon Sep 17 00:00:00 2001 From: Brian Hartford Date: Sat, 7 Feb 2026 08:46:39 -0500 Subject: [PATCH 3/3] Linting --- src/kyro/models.py | 11 +++++------ src/kyro/rest/api/orders.py | 16 ++++------------ tests/test_api_modules.py | 4 +--- 3 files changed, 10 insertions(+), 21 deletions(-) diff --git a/src/kyro/models.py b/src/kyro/models.py index a64d793..78920f3 100644 --- a/src/kyro/models.py +++ b/src/kyro/models.py @@ -10,7 +10,6 @@ from pydantic import BaseModel, ConfigDict, Field, model_validator - # --- Enums (str-valued for JSON and API compatibility) --- @@ -103,9 +102,7 @@ class CreateOrderRequest(BaseModel): post_only: bool | None = None reduce_only: bool | None = None sell_position_floor: int | None = None - self_trade_prevention_type: str | None = Field( - None, pattern="^(taker_at_cross|maker)$" - ) + self_trade_prevention_type: str | None = Field(None, pattern="^(taker_at_cross|maker)$") order_group_id: str | None = None cancel_order_on_pause: bool | None = None subaccount: int | None = Field(None, ge=0) @@ -141,11 +138,13 @@ class DecreaseOrderRequest(BaseModel): reduce_to_fp: str | None = None @model_validator(mode="after") - def check_exactly_one_group(self) -> "DecreaseOrderRequest": + def check_exactly_one_group(self) -> DecreaseOrderRequest: reduce_by_any = self.reduce_by is not None or self.reduce_by_fp is not None reduce_to_any = self.reduce_to is not None or self.reduce_to_fp is not None if reduce_by_any and reduce_to_any: - raise ValueError("Provide (reduce_by or reduce_by_fp) OR (reduce_to or reduce_to_fp), not both") + raise ValueError( + "Provide (reduce_by or reduce_by_fp) OR (reduce_to or reduce_to_fp), not both" + ) if not reduce_by_any and not reduce_to_any: raise ValueError("Provide (reduce_by or reduce_by_fp) or (reduce_to or reduce_to_fp)") return self diff --git a/src/kyro/rest/api/orders.py b/src/kyro/rest/api/orders.py index 5d641a5..0edb4f8 100644 --- a/src/kyro/rest/api/orders.py +++ b/src/kyro/rest/api/orders.py @@ -119,9 +119,7 @@ async def create_order( model_extra = getattr(model, "model_extra", None) or {} body.update({k: v for k, v in model_extra.items() if v is not None}) except ValidationError as e: - raise KyroValidationError( - f"Invalid create_order body: {e}", details=e.errors() - ) from e + raise KyroValidationError(f"Invalid create_order body: {e}", details=e.errors()) from e return await client.post("/portfolio/orders", json=body) @@ -181,9 +179,7 @@ async def amend_order( if getattr(model, "model_extra", None): body.update(model.model_extra) except ValidationError as e: - raise KyroValidationError( - f"Invalid amend_order body: {e}", details=e.errors() - ) from e + raise KyroValidationError(f"Invalid amend_order body: {e}", details=e.errors()) from e return await client.post(f"/portfolio/orders/{order_id}/amend", json=body) @@ -213,9 +209,7 @@ async def decrease_order( model = DecreaseOrderRequest.model_validate(body) body = model.model_dump(exclude_none=True) except ValidationError as e: - raise KyroValidationError( - f"Invalid decrease_order body: {e}", details=e.errors() - ) from e + raise KyroValidationError(f"Invalid decrease_order body: {e}", details=e.errors()) from e return await client.post(f"/portfolio/orders/{order_id}/decrease", json=body) @@ -235,9 +229,7 @@ async def batch_create_orders(client: RestClient, orders: list[dict[str, Any]]) body.update(model.model_extra) validated.append(body) except ValidationError as e: - raise KyroValidationError( - f"Invalid order at index {i}: {e}", details=e.errors() - ) from e + raise KyroValidationError(f"Invalid order at index {i}: {e}", details=e.errors()) from e return await client.post("/portfolio/orders/batched", json={"orders": validated}) diff --git a/tests/test_api_modules.py b/tests/test_api_modules.py index 62fd993..daea605 100644 --- a/tests/test_api_modules.py +++ b/tests/test_api_modules.py @@ -151,9 +151,7 @@ async def test_create_order_invalid_side_raises(kyro_client: RestClient) -> None async def test_decrease_order_both_groups_raises(kyro_client: RestClient) -> None: with pytest.raises(KyroValidationError): - await orders.decrease_order( - kyro_client, "ord-123", reduce_by=1, reduce_to=0 - ) + await orders.decrease_order(kyro_client, "ord-123", reduce_by=1, reduce_to=0) async def test_decrease_order_neither_group_raises(kyro_client: RestClient) -> None: