# API \[Railnet indexer GraphQL API — endpoint, query conventions, and core entities]

Railnet indexes every Vehicle, MultiVehicle and Conduit it deploys, and exposes the indexed state as a read-only GraphQL API. A [REST API](https://docs.api.railnet.org) built on top of it serves ready-made answers (conduit details, wallet positions, daily rewards) without writing queries.

## Endpoint

| Environment | URL |
|---|---|
| Production | `https://graphql.railnet.org/query` |
| Staging | `https://graphql.staging.railnet.org/query` |

Production indexes the live deployments, staging the test ones including the [test Conduit](/developers/integrate-conduit#test-conduit). Both expose the same schema. The API is read-only and unauthenticated: send a GraphQL `query` as an HTTP `POST`, or open it in [Apollo Sandbox](https://studio.apollographql.com/sandbox/explorer?endpoint=https%3A%2F%2Fgraphql.railnet.org%2Fquery).

## Query conventions

The API follows Hasura's grammar, not The Graph's. Each entity gets two root fields named exactly like the entity type in `PascalCase`: `Vehicle` returns matching rows and takes `where`, `order_by`, `limit`, `offset` and `distinct_on`; `Vehicle_by_pk` takes an `id` and returns one row or `null`.

**Filtering.** `where` takes one input object per entity with a key per scalar field, per relationship, and the combinators `_and`, `_or`, `_not`. Operators are `_eq`, `_neq`, `_in`, `_nin`, `_gt`, `_gte`, `_lt`, `_lte`, `_is_null`, and `_like` / `_ilike` / `_nlike` / `_nilike` on strings. Relationships are filterable, so you can select through the graph instead of chaining round-trips. Addresses are stored lowercase, so filter them with `_ilike` and a checksummed address still matches.

**Sorting and pagination.** `order_by` takes an object, or a list of objects for multi-key sorts, with `asc` or `desc`; nested relationships are sortable. `limit` and `offset` work as expected, but for large result sets page on the last `id` you saw (`where: { id: { _gt: $cursor } }, order_by: { id: asc }`) rather than a growing offset.

**Value types.** Addresses and byte strings are lowercase `0x` `String`s. Token amounts, share amounts, prices and rates are `numeric`, always returned as decimal strings (`"3000000"`), never JSON numbers. Enums such as `mode` and `state` are uppercase strings (`"DEPOSIT"`, `"SETTLED"`).

## Core entities

| Entity | Contents |
|---|---|
| `Vehicle` | One yield source, single-protocol or MultiVehicle (see `vehicleType`): `address`, `name`, `symbol`, `asset`, `assetDecimals`, `assetSymbol`, `assetPriceUSD`, `supply` (18 decimals), `enabled`, `weeklyYield` / `monthlyYield` / `inceptionYield`. |
| `Query` | One STEAM deposit or redeem: `mode`, `state`, `owner`, `receiver`, `input`, `output`, `vehicle`. |
| `Balance` | Share balance of one holder in one Vehicle: `owner`, `value`, `vehicle`. |
| `Conduit` | A distributor's product: Vehicle fields plus `transferEnabled`, `vehicle`, `feeManager`, `accountList`. |
| `ConduitQuery` | A deposit or redeem through a Conduit: `owner`, `conduit`, `query`. |
| `ConduitBalance` | Conduit share balance of one holder: `owner`, `value`, `conduit`. |

`Yield` carries `netApr` and `netApy` in percentage points (`4.25` = 4.25%), null until enough history exists. `Query.state` is updated in place: `SETTLED` and `REJECTED` are terminal, everything else is in-flight. See the [STEAM standard](/developers/contracts/steam-standard) for the state machine. The schema covers more (engines, queues, roles, fees, automation) and has introspection enabled, so any GraphQL client can discover the rest.

## Example queries

Product state for one Conduit, including the Vehicle behind it:

```graphql [GraphQL]
query ConduitOverview($address: String!) {
  Conduit(where: { address: { _ilike: $address } }) {
    address
    name
    symbol
    assetSymbol
    assetDecimals
    supply
    enabled
    transferEnabled
    vehicle { address name vehicleType }
    inceptionYield { netApr netApy }
  }
}
```

Every position one holder has, across Vehicles and Conduits:

```graphql [GraphQL]
query HolderPosition($owner: String!) {
  Balance(where: { owner: { _ilike: $owner }, value: { _gt: 0 } }) {
    value
    vehicle { address symbol assetSymbol assetDecimals }
  }
  ConduitBalance(where: { owner: { _ilike: $owner }, value: { _gt: 0 } }) {
    value
    conduit { address symbol assetSymbol assetDecimals }
  }
}
```

In-flight queries on one Vehicle, newest first:

```graphql [GraphQL]
query OpenQueries($address: String!, $states: [state!]) {
  Query(
    where: { vehicle: { address: { _ilike: $address } }, state: { _in: $states } }
    order_by: { event: { tx: { block: { number: desc } } } }
    limit: 50
  ) {
    id
    mode
    state
    owner
    input { address value }
  }
}
```

```json [Variables]
{
  "address": "0x6eea217dd6bdd3bcb4db0f20a846d66afd00963e",
  "states": ["PROCESSING", "PAUSED", "UNLOCKING", "RECOVERING"]
}
```

Before trusting a result as current, check `_meta { chainId isReady progressBlock sourceBlock readyAt }`. The difference between `sourceBlock` and `progressBlock` is the indexing lag in blocks, and `isReady: false` means rows are still backfilling.

## Next steps

<Cards>
  <Card title="Integrate a Conduit" description="Combine on-chain calls with indexed data to build an earn experience." icon="plug" to="/developers/integrate-conduit" />

  <Card title="STEAM standard" description="The state machine behind Query.state and Query.mode." icon="workflow" to="/developers/contracts/steam-standard" />
</Cards>
