> ## Documentation Index
> Fetch the complete documentation index at: https://docs.datadash.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# Analytical, not transactional

> Why Datadash returns ranked, aggregated result sets instead of resources you have to assemble yourself.

export const Versus = ({left, right, rows = []}) => <div style={{
  border: '1px solid rgba(128,128,128,0.22)',
  borderRadius: '10px',
  overflow: 'hidden',
  margin: '2rem 0'
}}>
    <div style={{
  display: 'grid',
  gridTemplateColumns: '1fr 1fr'
}}>
      {[left, right].map((head, i) => <div key={i} style={{
  padding: '0.7rem 1rem',
  background: 'rgba(128,128,128,0.06)',
  borderLeft: i === 1 ? '1px solid rgba(128,128,128,0.22)' : undefined,
  borderBottom: '1px solid rgba(128,128,128,0.22)',
  fontSize: '0.6875rem',
  fontWeight: 700,
  letterSpacing: '0.06em',
  textTransform: 'uppercase',
  color: i === 1 ? '#8b5cf6' : undefined,
  opacity: i === 1 ? 1 : 0.6
}}>
          {head}
        </div>)}
    </div>
    {rows.map((row, i) => <div key={i} style={{
  display: 'grid',
  gridTemplateColumns: '1fr 1fr',
  borderBottom: i === rows.length - 1 ? undefined : '1px solid rgba(128,128,128,0.22)'
}}>
        <div style={{
  padding: '0.85rem 1rem',
  fontSize: '0.9375rem',
  lineHeight: 1.5,
  opacity: 0.72
}}>
          {row[0]}
        </div>
        <div style={{
  padding: '0.85rem 1rem',
  borderLeft: '1px solid rgba(128,128,128,0.22)',
  fontSize: '0.9375rem',
  lineHeight: 1.5
}}>
          {row[1]}
        </div>
      </div>)}
  </div>;

export const PullQuote = ({children, cite}) => <figure style={{
  margin: '2.25rem 0',
  paddingLeft: '1.25rem',
  borderLeft: '2px solid #8b5cf6'
}}>
    <div style={{
  fontSize: '1.25rem',
  lineHeight: 1.45,
  fontWeight: 500,
  letterSpacing: '-0.01em'
}}>
      {children}
    </div>
    {cite && <figcaption style={{
  marginTop: '0.6rem',
  fontSize: '0.8125rem',
  opacity: 0.6
}}>
        {cite}
      </figcaption>}
  </figure>;

export const Takeaways = ({title = 'The short version', items = []}) => <div style={{
  borderLeft: '2px solid #8b5cf6',
  background: 'rgba(128,128,128,0.06)',
  borderRadius: '0 8px 8px 0',
  padding: '1.1rem 1.25rem',
  margin: '2rem 0'
}}>
    <div style={{
  fontSize: '0.6875rem',
  fontWeight: 700,
  letterSpacing: '0.08em',
  textTransform: 'uppercase',
  color: '#8b5cf6',
  marginBottom: '0.7rem'
}}>
      {title}
    </div>
    <ul style={{
  margin: 0,
  paddingLeft: '1.1rem',
  display: 'grid',
  gap: '0.4rem'
}}>
      {items.map((item, i) => <li key={i} style={{
  lineHeight: 1.55
}}>
          {item}
        </li>)}
    </ul>
  </div>;

export const PostHeader = ({date, author, readingTime, tags = []}) => <div style={{
  display: 'flex',
  flexWrap: 'wrap',
  alignItems: 'center',
  gap: '0.6rem',
  paddingBottom: '1.1rem',
  marginBottom: '2rem',
  borderBottom: '1px solid rgba(128,128,128,0.22)',
  fontSize: '0.8125rem'
}}>
    {tags.map(tag => <span key={tag} style={{
  padding: '0.2rem 0.6rem',
  borderRadius: '999px',
  border: '1px solid rgba(139,92,246,0.45)',
  color: '#8b5cf6',
  fontSize: '0.6875rem',
  fontWeight: 600,
  letterSpacing: '0.04em',
  textTransform: 'uppercase'
}}>
        {tag}
      </span>)}
    <span style={{
  opacity: 0.62
}}>
      {[date, author, readingTime].filter(Boolean).join('  ·  ')}
    </span>
  </div>;

<PostHeader tags={["Engineering"]} date="2 September 2026" author="Datadash Engineering" readingTime="6 min read" />

<Takeaways
  items={[
"Transactional APIs return resources. Analytical APIs return the result of a question.",
"A leaderboard that costs thousands of requests elsewhere is one request here.",
"Rows arrive with identifiers already resolved, so there is no join table on your side.",
"Cohorts only work this way — a query result other queries take as input is not a resource.",
]}
/>

Most prediction-market APIs — struct.to among them — are **transactional**. They hand you resources: a market, an order, a position, a user. Each response is a faithful record of one thing, and the shape of the API follows the shape of the database behind it. That is exactly right if you are placing trades, reconciling fills, or keeping a local mirror in sync.

Datadash is **analytical**. Nothing we return corresponds to a single stored record. Every response is the result of a query — filtered, joined, aggregated and ranked before it leaves the server.

The distinction matters more than it sounds like it should, because it decides where the work happens.

## The same question, both ways

Take a question you'd actually ask: *which wallets made the most money in Sports last month, and what are they holding now?*

On a transactional API, that is a program:

1. Page through trades for the period. There are millions.
2. For each trade, resolve its market to find the category. That's a second endpoint, and a cache you now have to maintain.
3. Group by wallet. Sum cost basis and proceeds. Handle partial exits, and decide what you do about positions that were open at the start of the window.
4. Sort what you computed. Take the top 100.
5. For each of those 100 wallets, fetch current positions. That's 100 more requests.
6. Resolve each position's market. More requests, or a bigger cache.

<PullQuote>
  You have written an analytics engine. It runs on your infrastructure, it is
  slow because the data crossed the network before being reduced, and every
  consumer of your data writes step 3 slightly differently.
</PullQuote>

On Datadash it is one request:

```json theme={null}
{
  "page": { "limit": 100 },
  "orderBy": [{ "field": "totalPnl", "direction": "desc" }],
  "filter": [
    { "field": "tagIds", "operator": "in", "value": [12] },
    {
      "field": "timestamp",
      "operator": "last",
      "value": { "unit": "month", "length": 1 }
    }
  ]
}
```

Sent to `/api/v1/profiles/user-tag`. The ranking is the response.

## What that buys you

**Aggregation happens next to the data.** Our storage is columnar, and the expensive rollups — per-wallet profiles, per-category breakdowns, position accounting — are precomputed by a pipeline rather than derived per request. Summing PnL across a million fills is a column scan, not a million objects serialized over HTTP so your process can add them up.

**Rows arrive resolved.** Identifier columns come back already joined to the entity they name, so a row carries the market's question, the event's title and the category labels outright:

```json theme={null}
{
  "token": {
    "positionId": 2469,
    "marketQuestion": "Will X win?",
    "eventId": 88
  },
  "tags": [
    { "id": 7, "label": "Politics" },
    { "id": 9, "label": "US" }
  ]
}
```

There is no second request and no join table on your side. This is the single largest difference in practice — most of the code people write against transactional market APIs is join maintenance.

**Ranking is a parameter, not a post-processing step.** `orderBy` takes a prioritized list of sort keys and applies them before pagination, so "page 2 of the top traders" means what you'd expect. Sorting after you paginate — which is what you're forced into when the server won't rank — silently gives you the wrong answer.

**Filtering is typed and composable.** Conditions carry a field, an operator appropriate to that field's type, and a value; groups combine them with AND/OR. Numbers get comparisons, text gets matching, timestamps get windows, and wallet addresses get `inCohort` — membership in a group you defined, evaluated server-side.

Cohorts are the clearest case of something that only works this way. "The 400 wallets matching these performance criteria" is not a resource a transactional API can hand you, because it is not a stored entity. It is a query result that other queries take as input.

## Where transactional still wins

We are not claiming the analytical shape is better at everything. It isn't:

<Versus
  left="Reach for transactional"
  right="Reach for Datadash"
  rows={[
[
  "Placing orders and reconciling fills",
  "Deciding what to place in the first place",
],
[
  "Fetching one record by transaction hash",
  "Ranking thousands of wallets by a computed metric",
],
["Sub-second order book state", "Aggregates over months of history"],
[
  "Mirroring the raw feed into your own store",
  "Asking a question without storing anything",
],
]}
/>

The honest framing is that these are complementary. Plenty of teams use a transactional API for execution and Datadash for the analysis that decides what to execute.

## The cost model is different too

A transactional API charges you per resource fetched, and the leaderboard above costs thousands of requests. An analytical API charges you per question asked, and it costs one. That gap widens with every wallet you add to the analysis — which is the direction analytical work always goes.

It also changes what is worth building. When a cohort leaderboard is a single request, you put it on a page and let users change the window. When it is a six-step pipeline with a cache to invalidate, you compute it nightly and ship a stale number.

## Try the comparison

Pick something you currently assemble client-side and see what it takes here.

<CardGroup cols={2}>
  <Card title="Filtering" icon="funnel" href="/filters">
    The condition and group grammar behind every example above.
  </Card>

  <Card title="Generating a Client" icon="terminal" href="/openapi-clients">
    Typed TypeScript and Python clients from the OpenAPI spec.
  </Card>
</CardGroup>
