POST Position State
/position-state
position-state/{start}/{size}Example Query:
https://base-hedger82.rasa.capital/position-state/{start}/{size}Overview
This endpoint gives insights into historical lifecycle updates for positions based on a quote ID or counterparty address. Results are sourced from the Notifications, Positions, and Symbol tables, then mapped into the response schema.
Endpoint Specification
URL: https://base-hedger82.rasa.capital/position-state/{start}/{size}
Method: POST
Headers
Content-Type: application/jsonOptional:
App-Name:(client identifier)
cURL Example (Limit Open — Reserved but not yet Opened)
curl -X POST "https://base-hedger82.rasa.capital/position-state/0/10" \
     -H "Content-Type: application/json" \
     -H "App-Name: Cloverfield" \
     -d '{"quote_id": "131388"}'Response (HTTP 200):
{
    "count": 1,
    "position_state": [
        {
            "id": "7d0c10a4-1d7e-44b9-8712-77c863bfab55",
            "create_time": 1745970777,
            "modify_time": 1745970777,
            "quote_id": 131388,
            "temp_quote_id": null,
            "counterparty_address": "0xEb42F3b1aC3b1552138C7D30E9f4e0eF43229542",
            "filled_amount_open": "0",
            "filled_amount_close": "0",
            "avg_price_open": "0",
            "avg_price_close": "0",
            "last_seen_action": "SendQuote",
            "action_status": "seen",
            "failure_type": null,
            "error_code": 0,
            "order_type": 0,
            "state_type": "alert"
        }
    ]
}Path Parameters
start
integer
0-based pagination offset
size
integer
Number of records to return (max 100)
Body Parameters (JSON)
Provide at least quote_id or address of the following to filter results; other fields are optional:
Field
Type
Description
quote_id
string
Exact quote ID (string). One of quote_id or address is required.
address
string
Counterparty address (Ethereum hex string). One of quote_id or address is required.
symbols
string[]
Optional filter by trading symbols (e.g., ["ETHUSD"]).
states
string[]
Optional filter by position state enums (e.g., ["alert"]).
create_time_gte
integer
Optional: include records with Notifications.create_time >= epoch seconds UTC.
modify_time_gte
integer
Optional: include records with Notifications.modify_time >= epoch seconds UTC.
Request & Response Schemas
Define Schemas
Create PositionsStateRequestSchema and PositionStateResponseSchema + PositionsStateOutputSchema in the schema file.
class PositionStateResponseSchema(BaseModel):
    id: UUID
    create_time: int
    modify_time: int
    quote_id: int
    temp_quote_id: Optional[int] = None
    counterparty_address: str
    filled_amount_open: Decimal = 0
    filled_amount_close: Decimal = 0
    avg_price_open: Decimal = 0
    avg_price_close: Decimal = 0
    last_seen_action: str
    action_status: str
    failure_type: Optional[str] = None
    error_code: Optional[int] = None
    order_type: int
    state_type: PositionStateType
class PositionsStateOutputSchema(BaseModel):
    count: int
    position_state: List[PositionStateResponseSchema]Data Sources & Field Mapping
id
Notifications.id
Primary key (UUID) -> in some solver implementations this is also the quote ID.
create_time
Notifications.create_time
Epoch seconds UTC
modify_time
Notifications.modify_time
Epoch seconds UTC
quote_id
Notifications.quote_id
quote_id.
temp_quote_id
Notifications.temp_quote_id
Used for instant trades or null.
counterparty_address
Notifications.counterparty_address
partyA address
filled_amount_open
Notifications.filled_amount_open
Recorded open fill amount
avg_price_open
Notifications.avg_price_open
Recorded open fill price
filled_amount_close
Notifications.filled_amount_close
Recorded close fill amount
avg_price_close
Notifications.avg_price_close
Recorded close fill price
last_seen_action
Notifications.last_seen_action
e.g., SendQuote, FillLimitOrderOpen
action_status
Notifications.action_status
success, seen, etc.
failure_type
Notifications.failure_type
If action failed
error_code
Notifications.error_code
Numeric code
order_type
Notifications.order_type
Limit = 0, Market = 1
state_type
Notifications.state_type
"alert" or "report"
Implementation Details (Annotated)
This section explains how each piece fits together.
Router Layer (routers.py)
routers.py)@common_router.post(
    '/position-state/{start}/{size}',
    responses={status.HTTP_200_OK: {"model": PositionsStateOutputSchema}},
    response_model=PositionsStateOutputSchema
)
@with_context_db_session
async def search_position_state(
    request: PositionsStateRequestSchema,
    start: NonNegativeInt = 0,
    size: PositiveInt = 100
):
    # Map incoming HTTP payload to internal search DTO
    new_request = NotificationsRequestSchema()
    new_request.counterparty_address = request.address
    new_request.quote_id = request.quote_id
    new_request.symbols = request.symbols
    new_request.states = request.states
    new_request.timestamp_gte = request.modify_time_gte
    # Call service layer to fetch and map results
    count, data = search_notification_by_counterparty(
        new_request, start, size, map_to_positions_state=True
    )
    # Return raw dict form; FastAPI serializes to schema
    return dict(count=count, position_state=data)Controller Layer (controllers.py)
controllers.py)# Located in controllers.py
# Handles fetching and mapping notifications to position state
def search_notification_by_counterparty(
    request: NotificationsRequestSchema,
    start: int,
    size: int,
    map_to_positions_state: bool = False
):
    # a. Sanitize page size
    size = min(size, 100)
    # b. Handle relative timestamps (e.g., -3600 for last hour)
    if request.timestamp_gte and request.timestamp_gte < 0:
        request.timestamp_gte = get_now_epoch() + request.timestamp_gte
    # c. Build base SQLAlchemy query with necessary joins
    stmt = (
        Notifications.select()
        .outerjoin(
            Positions,
            or_(
                Positions.quote_id == Notifications.quote_id,
                Positions.temp_quote_id == Notifications.temp_quote_id
            )
        )
        .outerjoin(
            Symbol, Positions.symbol_id == Symbol.symbol_id
        )
        .where(Notifications.should_send == true())
    )
    # d. Apply filters based on request fields
    if request.quote_id:
        if is_quote_id_temp(request.quote_id):
            stmt = stmt.where(Notifications.temp_quote_id == request.quote_id)
        else:
            stmt = stmt.where(Notifications.quote_id == request.quote_id)
    if request.counterparty_address:
        stmt = stmt.where(
            Notifications.counterparty_address == request.counterparty_address
        )
    if request.states:
        stmt = stmt.where(Positions.state.in_(request.states))
    if request.symbols:
        stmt = stmt.where(Symbol.title.in_(request.symbols))
    if request.timestamp_gte:
        stmt = stmt.where(Notifications.create_time >= request.timestamp_gte)
    # e. Count and paginate
    count = session.count_star(stmt)
    records = (
        session.scalars_all(
            stmt.order_by(desc(Notifications.create_time))
                .offset(start)
                .limit(size)
        )
    )
    # f. Map to DTOs
    result = []
    for row in records:
        data = row.to_dict()
        if map_to_positions_state:
            result.append(map_notification_to_positions_state(data))
        else:
            result.append(prepare_notification_to_send(data))
    return count, resultExample (Full Open Position -> Close Position Flow)
cURL Query
curl -X POST "https://base-hedger82.rasa.capital/position-state/0/10" \
     -H "Content-Type: application/json" \
     -H "App-Name: Cloverfield" \
     -d '{"quote_id": "131391"}'fResponse (HTTP 200):
{
    "count": 6,
    "position_state": [
        {
            "id": "000be762-2592-43b0-937f-32a75fa4587a", //last notification
            "create_time": 1745976098,
            "modify_time": 1745976098,
            "quote_id": 131391,
            "temp_quote_id": null,
            "counterparty_address": "0xEb42F3b1aC3b1552138C7D30E9f4e0eF43229542",
            "filled_amount_open": "0",
            "filled_amount_close": "0",
            "avg_price_open": "0",
            "avg_price_close": "0",
            "last_seen_action": "FillLimitOrderClose",
            "action_status": "success",
            "failure_type": null,
            "error_code": 0,
            "order_type": 0,
            "state_type": "alert"
        },
        {
            "id": "4763fe3d-e3ed-40f9-9821-2b501342579b",
            "create_time": 1745976092,
            "modify_time": 1745976092,
            "quote_id": 131391,
            "temp_quote_id": null,
            "counterparty_address": "0xEb42F3b1aC3b1552138C7D30E9f4e0eF43229542",
            "filled_amount_open": "0",
            "filled_amount_close": "6.70000000",
            "avg_price_open": "0",
            "avg_price_close": "2.2345",
            "last_seen_action": "RequestToClosePosition",
            "action_status": "success",
            "failure_type": null,
            "error_code": 0,
            "order_type": 0,
            "state_type": "report"
        },
        {
            "id": "c99b51b8-a18e-4271-af05-3ed37f3805b9",
            "create_time": 1745976091,
            "modify_time": 1745976091,
            "quote_id": 131391,
            "temp_quote_id": null,
            "counterparty_address": "0xEb42F3b1aC3b1552138C7D30E9f4e0eF43229542",
            "filled_amount_open": "0",
            "filled_amount_close": "0",
            "avg_price_open": "0",
            "avg_price_close": "0",
            "last_seen_action": "RequestToClosePosition",
            "action_status": "seen",
            "failure_type": null,
            "error_code": 0,
            "order_type": 0,
            "state_type": "alert"
        },
        {
            "id": "8ff29bbd-6ca6-42cd-973f-20b6f88bfdea",
            "create_time": 1745976010,
            "modify_time": 1745976010,
            "quote_id": 131391,
            "temp_quote_id": null,
            "counterparty_address": "0xEb42F3b1aC3b1552138C7D30E9f4e0eF43229542",
            "filled_amount_open": "0",
            "filled_amount_close": "0",
            "avg_price_open": "0",
            "avg_price_close": "0",
            "last_seen_action": "FillLimitOrderOpen",
            "action_status": "success",
            "failure_type": null,
            "error_code": 0,
            "order_type": 0,
            "state_type": "alert"
        },
        {
            "id": "98620b7c-c752-42ed-8611-34bde40a4b28",
            "create_time": 1745976004,
            "modify_time": 1745976004,
            "quote_id": 131391,
            "temp_quote_id": null,
            "counterparty_address": "0xEb42F3b1aC3b1552138C7D30E9f4e0eF43229542",
            "filled_amount_open": "6.70000000",
            "filled_amount_close": "0",
            "avg_price_open": "2.2367",
            "avg_price_close": "0",
            "last_seen_action": "SendQuote",
            "action_status": "success",
            "failure_type": null,
            "error_code": 0,
            "order_type": 0,
            "state_type": "report"
        },
        {
            "id": "2f383602-054e-46a2-96e6-b39a7c483c49", //first notification
            "create_time": 1745975999,
            "modify_time": 1745975999,
            "quote_id": 131391,
            "temp_quote_id": null,
            "counterparty_address": "0xEb42F3b1aC3b1552138C7D30E9f4e0eF43229542",
            "filled_amount_open": "0",
            "filled_amount_close": "0",
            "avg_price_open": "0",
            "avg_price_close": "0",
            "last_seen_action": "SendQuote",
            "action_status": "seen",
            "failure_type": null,
            "error_code": 0,
            "order_type": 0,
            "state_type": "alert"
        }
    ]
}By Address
curl -X POST "https://base-hedger82.rasa.capital/position-state/0/50" \
     -H "Content-Type: application/json" \
     -H "App-Name: Cloverfield" \
     -d '{
       "address": "0x20F764F49bf8A2c653942dA29FeD1D7A7BAefD20"
     }'
Response (200 OK):
{
  "count": 383,
  "position_state": [ /* items */ ]
}Full Lifecycle Flow
Below is the end-to-end sequence of events and how they map to the records you see in the /position-state response:
RFQ Observed
On-Chain Event Poller listens for
SendQuoteevents.Creates a “alert” notification (
state_type = "alert") withaction_status = "seen"and zeros in all fill fields.At this point the hedger can lock the quote. The quote is seen but not yet filled.
Hedger Confirmation & Opening of the position
The hedger’s risk engine executes the quote on-chain.
Updates the
Notificationstable with actualfilled_amount_openandavg_price_openvalues.Emits a “report” notification (
state_type = "report") withaction_status = "success"and real fill data.
On-Chain Fill Event
Almost immediately thereafter, the contract itself emits an
OpenPositionevent when the transaction finally lands on-chain.Poller picks it up and creates another “alert” (
state_type = "alert") with zeros in fill fields until the DB update completes.The same poller picks up that on-chain event and writes a fresh Notification with
last_seen_action = "FillLimitOrderOpen"action_status = "success"
Last updated
