ASPERN History API

Series over time. Every point is a measurement taken then, never a backfill of what we believe now.

Operations 1

GET /v1/history/capital Daily measured capital, split by who decides #

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/aspern-org:aspern-org-history-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Discovery needs no key. Ratings and market analysis are Pro.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

aspern-org-history-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: ASPERN History API
  version: '0.2'
  summary: The independent record of what machine-managed capital actually does.
  x-performance:
    simpleReads:
      p95Ms: 500
      meaning: A list or a lookup, warm.
    heavyAnalytics:
      p95Ms: 2000
      meaning: Correlation, the index, stress and cascade, warm.
    coldPath:
      meaning: 'The first call after a cache expires. Not covered by the figures above. The deploy and a five-minute timer warm the heavy free endpoints, so a caller should rarely meet one there. `/v1/correlation` is the exception and is the slowest: it needs a capability, so the warmer cannot reach past the gate without holding a key, and putting a credential in the web process to solve a latency problem is a trade we have not made. Expect several seconds on the first call after an expiry.'
    timeoutSeconds: 60
    maxWindowDays: 400
  description: 'Ratings are free. The data, the analytics and the certification are not.


    Every response states what it cannot see. Coverage, sample size and the mechanism behind a figure travel with the figure rather than sitting in documentation, because a number read without them is the failure this platform exists to prevent.'
  x-aspern-environment: production
  x-aspern-build: 20261101T2237Z-27da4c8c29
  x-contract-hash: 6c89cbaf2e2c50bd2b65bddf516e17b855704d01e471a6c06c19354fedc7367d
  x-stability:
    policy: additive-within-major
    promise: 'Within /v1 the contract only grows: new routes, optional parameters, fields and warning codes. Routes and fields are never removed or renamed, and no type, unit or status meaning changes.'
    deprecation:
      marker: 'x-lifecycle: deprecated, with the replacement named'
      minimumDays: 180
      removal: only in a new major version; /v1 keeps answering through the deprecation period
    verification: checked daily against the approved operation inventory; every change of x-contract-hash is recorded
    changelog: https://aspern.org/data/reference#changelog
servers:
- url: https://aspern.org/api
security:
- apiKey: []
tags:
- name: History
  description: Series over time. Every point is a measurement taken then, never a backfill of what we believe now.
paths:
  /v1/history/capital:
    get:
      tags:
      - History
      summary: Daily measured capital, split by who decides
      description: '`reconstructed: true` marks a day REBUILT from stored value history rather than observed on the day itself, and it is a different measurement wearing the same name: a rebuilt day carries today’s cohort and classification, so it is survivorship-biased backwards. Compare a run of reconstructed days with a run of observed ones and the shape of the series is partly our own construction.


        Requires `crowding.summary` — PUBLIC or PRO or ENTERPRISE. Without it the call answers 402 with the amount and a deposit address.'
      operationId: getHistoryCapital
      responses:
        '200':
          description: The answer, with its own limitations stated inline.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/RequestId'
            RateLimit-Limit:
              $ref: '#/components/headers/RateLimitLimit'
            RateLimit-Remaining:
              $ref: '#/components/headers/RateLimitRemaining'
            RateLimit-Reset:
              $ref: '#/components/headers/RateLimitReset'
            RateLimit-Policy:
              $ref: '#/components/headers/RateLimitPolicy'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HistoryCapitalData'
        '400':
          description: A parameter is present and unreadable. `invalid_params` names every one at fault, not just the first.
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Problem'
        '401':
          description: 'A key was supplied and is unknown, revoked or expired. Sending no key at all is not this — that is the free tier. This response carries the full rate-limit headers for the ANONYMOUS bucket, which is the bucket the caller is in: an unrecognised key is throttled by address before it is refused, and no customer plan is named because there is no customer.'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Problem'
          headers:
            X-Request-Id:
              $ref: '#/components/headers/RequestId'
            RateLimit-Limit:
              $ref: '#/components/headers/RateLimitLimit'
            RateLimit-Remaining:
              $ref: '#/components/headers/RateLimitRemaining'
            RateLimit-Reset:
              $ref: '#/components/headers/RateLimitReset'
            RateLimit-Policy:
              $ref: '#/components/headers/RateLimitPolicy'
        '402':
          description: This capability is not carried by the caller’s tier. The body is the x402 payment offer — capability, amount and where to send it — not an error document.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/RequestId'
            RateLimit-Limit:
              $ref: '#/components/headers/RateLimitLimit'
            RateLimit-Remaining:
              $ref: '#/components/headers/RateLimitRemaining'
            RateLimit-Reset:
              $ref: '#/components/headers/RateLimitReset'
            RateLimit-Policy:
              $ref: '#/components/headers/RateLimitPolicy'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaymentRequired'
        '429':
          description: Rate or allowance exhausted. `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset` are on every response; `Retry-After` is on this one.
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Problem'
          headers:
            X-Request-Id:
              $ref: '#/components/headers/RequestId'
            RateLimit-Limit:
              $ref: '#/components/headers/RateLimitLimit'
            RateLimit-Remaining:
              $ref: '#/components/headers/RateLimitRemaining'
            RateLimit-Reset:
              $ref: '#/components/headers/RateLimitReset'
            RateLimit-Policy:
              $ref: '#/components/headers/RateLimitPolicy'
            Retry-After:
              $ref: '#/components/headers/RetryAfter'
        '500':
          description: Something failed at our end. Quote `request_id` — it is on every response and in our logs.
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Problem'
      parameters:
      - name: from
        in: query
        required: false
        description: ISO date. Defaults to a year back.
        schema:
          type: string
      - name: to
        in: query
        required: false
        description: ISO date. Defaults to today.
        schema:
          type: string
      x-lifecycle: active
components:
  headers:
    RateLimitPolicy:
      description: 'Both policies, comma-separated, in the order a client needs them: `<burst>;w=<seconds to refill>;name="burst", <perMinute>;w=60;name="<plan>"`. The first is what may be sent right now and is what the counters above describe; the second is what may be sustained and is the figure on the price list.'
      schema:
        type: string
      required: true
    RateLimitLimit:
      description: How many requests the burst bucket holds when full. This is the limit that refuses a request, so it is the one these counters describe. The sustained per-minute allowance is the second entry in `RateLimit-Policy`, and the monthly quota is on `GET /v1/account`.
      schema:
        type: integer
      required: true
    RequestId:
      description: The id of this request, also on every problem document as `request_id`. Quote it and we can find the call.
      schema:
        type: string
      required: true
    RetryAfter:
      description: Seconds to wait before retrying. Sent on a refusal.
      schema:
        type: integer
    RateLimitRemaining:
      description: Requests left in the bucket right now. Send more than this and the next one is refused.
      schema:
        type: integer
      required: true
    RateLimitReset:
      description: SECONDS FROM NOW until the bucket is FULL again, not a timestamp, and not the wait for a single request — that is `Retry-After`, sent only on a refusal. Zero means it is already full and there is nothing to wait for. A client treating it as epoch seconds sleeps until 1970 and hammers us instead.
      schema:
        type: integer
      required: true
  schemas:
    HistoryCapitalData:
      type: object
      description: Observed, not designed. This shape was derived by calling the operation and recording what came back, and it is re-derived and compared on every deploy. Types are what was seen. Fields listed under `required` are the ones this API commits to returning, declared by hand rather than inferred — everything else is what was observed, and an absent measurement is a real answer here rather than a fault. Required means present, never non-null.
      required:
      - data
      - meta
      - warnings
      properties:
        data:
          type: object
          properties:
            from:
              type: string
            to:
              type: string
            days:
              type: number
              x-unit: days
            reconstructedDays:
              type: number
              x-unit: days
            caveat:
              type: string
            items:
              type: array
              items:
                type: object
                properties:
                  date:
                    type: string
                  machineUsd:
                    type: number
                    x-unit: usd
                  otherUsd:
                    type: number
                    x-unit: usd
                  defiTotalUsd:
                    type: number
                    x-unit: usd
                  vaultCount:
                    type: number
                    x-unit: count
                  machineCount:
                    type: number
                    x-unit: count
                  reconstructed:
                    type: boolean
            asOf:
              type: string
            oldestObservationAt:
              type: string
        meta:
          $ref: '#/components/schemas/Meta'
        warnings:
          type: array
          items:
            $ref: '#/components/schemas/Warning'
    Meta:
      type: object
      required:
      - requestId
      - snapshotId
      - servedAt
      - methodologyVersion
      - coverage
      properties:
        requestId:
          type: string
          description: Also the X-Request-Id header.
        environment:
          type: string
          description: production, staging or development. Also the X-ASPERN-Environment header and info.x-aspern-environment in the OpenAPI document, so a client can tell which contract answered.
        build:
          type:
          - string
          - 'null'
          description: The deploy that served this. Also the X-ASPERN-Build header.
        snapshotId:
          type: string
          description: Which state of our data answered. Two responses with the same snapshotId saw the same completed ingest runs — so a figure that differs between two surfaces under the SAME snapshot is a defect, and under different ones is a clock.
        observedAt:
          type:
          - string
          - 'null'
          format: date-time
          description: When the source was observed.
        computedAt:
          type:
          - string
          - 'null'
          format: date-time
          description: When we computed this from it.
        servedAt:
          type: string
          format: date-time
          description: When this response was built.
        methodologyVersion:
          type: string
          description: What the figures mean. Changes are listed at /changelog.
        source:
          type:
          - string
          - 'null'
        chainId:
          type:
          - integer
          - 'null'
          x-unit: chain-id
        blockNumber:
          type:
          - integer
          - 'null'
          x-unit: block-number
        coverage:
          type: object
          properties:
            scope:
              type:
              - string
              - 'null'
              description: What this covers and, explicitly, what it does not.
            population:
              type:
              - string
              - 'null'
              description: The population these figures were measured over, as a key you can join on rather than a sentence you have to read. Every key is declared once and resolves to a definition at /reconciliation#<key>, where the ones that narrow show every row that falls out and why. The same key appears beside the figure on the website, so an integrator and a reader are talking about one population rather than two descriptions of it. Null only when `populationAbsence` says why.
            populationAbsence:
              type:
              - string
              - 'null'
              description: Why there is no single population, where there is none. A lookup of one address measures nothing; a document carrying several rails in units that are never summed carries several populations. A null population with nothing here would be an absence you could not tell from an oversight, which is the defect this field exists to close.
            knownExclusions:
              type:
              - array
              - 'null'
              items:
                type: string
              description: THREE VALUES. A list is what this route deliberately leaves out. `[]` is a CLAIM that nothing is excluded. `null` is “we have not said”, which is our gap and must never be read as the second. It was hardcoded to `[]` on every route until 2026-09-27, so every response claimed to exclude nothing while its own `scope` sentence named exclusions.
        confidence:
          type:
          - object
          - 'null'
          description: Never a bare number; it names what it is a confidence in.
        freshness:
          anyOf:
          - type: object
            required:
            - oldestObservedAt
            - newestObservedAt
            - readings
            - fields
            - complete
            - state
            - newestAgeHours
            description: When the readings behind this answer were taken, where they differ. A page of a hundred vaults read over four hours has no single observation time — `observedAt` is filled in only where every reading agrees, and this reports the spread otherwise.
            properties:
              oldestObservedAt:
                type: string
                format: date-time
              newestObservedAt:
                type: string
                format: date-time
              readings:
                type: integer
                minimum: 1
                description: How many readings the range was taken over.
                x-unit: count
              fields:
                type: array
                items:
                  type: string
                description: The fields it was read from, so a caller can go and check any of them directly.
              complete:
                type: boolean
                description: Whether every reading in the payload was seen. False means the range is a LOWER BOUND — the true spread is at least this wide and may be wider — because a partial walk always returns the narrower range, which would read as fresher and more uniform than the data is.
              state:
                enum:
                - fresh
                - mixed
                - stale
                description: '`stale` when the NEWEST reading is more than 26 hours old at the moment the response was built — every reader runs at least daily, so that means a reader has stopped. `mixed` when the newest is inside that window and the OLDEST is not: parts of this answer are current and parts are not, which is the usual shape of a document joining several sources. It was summarised as `fresh` until 2026-10-10, computed from the newest reading alone, so one current field hid a catalogue two months old. A source that could not be read is not a state here: it is a check that could not run (`/v1/checks`) or an analysis whose `availability` says so, and never a zero.'
              newestAgeHours:
                type: number
                minimum: 0
                description: Hours between the newest reading and the moment this response was built, to one decimal.
                x-unit: hours
              oldestAgeHours:
                type: number
                minimum: 0
                description: The same for the OLDEST reading. The other end of the spread as a number, because a caller comparing two ISO strings to find out whether part of an answer is two months old will mostly not bother.
                x-unit: hours
          - type: object
            required:
            - reason
            - state
            - says
            - readings
            description: No range, and which of the two reasons it is.
            properties:
              reason:
                enum:
                - not-applicable
                - no-dated-field
                description: '`not-applicable` is reference material: a plan, a capability list, a schema. Nothing in it was read from anywhere at a time. `no-dated-field` means the response carries data and nothing in it names when that data was read, which is a gap in that endpoint.'
              state:
                enum:
                - not_applicable
                - not_measured
                description: 'The reason in the vocabulary shared with `fresh`/`stale`: `not-applicable` is `not_applicable`, `no-dated-field` is `not_measured`.'
              says:
                type: string
                description: The same thing in a sentence.
              readings:
                const: 0
        pagination:
          anyOf:
          - $ref: '#/components/schemas/Page'
          - type: 'null'
    Warning:
      type: object
      required:
      - code
      - message
      properties:
        code:
          type: string
        message:
          type: string
    Problem:
      type: object
      description: An RFC 9457 problem document. Served as `application/problem+json` on every failure except 402, which carries the x402 payment offer instead.
      required:
      - type
      - title
      - status
      - code
      - detail
      properties:
        type:
          type: string
          format: uri
          description: A stable URI for this kind of failure.
        title:
          type: string
          description: What went wrong, in a sentence that does not change per request.
        status:
          type: integer
          minimum: 400
          maximum: 599
          description: The HTTP status, repeated here so the body stands alone.
        code:
          type: string
          description: The machine token to switch on, e.g. invalid_parameter, not_found, rate_limited.
        detail:
          type: string
          description: What went wrong with THIS request, and where possible what to do instead.
        instance:
          type: string
          description: The path that produced it.
        request_id:
          type: string
          description: Also sent as X-Request-Id. Quote it and we can find the request in our logs.
        invalid_params:
          type: array
          description: Present on 400. Every parameter at fault, not only the first.
          items:
            type: object
            required:
            - name
            - reason
            properties:
              name:
                type: string
              value:
                type: string
                description: The value we could not use, echoed back.
              reason:
                type: string
    PaymentRequired:
      type: object
      required:
      - error
      - capability
      - tier
      - reason
      description: 'Not an RFC 9457 problem document. The request was well formed and the answer is a price, so the body is an offer: what is missing, why, and where to settle it.'
      properties:
        forSale:
          type: boolean
          description: 'Whether this capability can be bought at all right now. False means the endpoint is real, the refusal is correct, and no plan on sale carries it: a product decision rather than something you have not bought yet. An agent should stop rather than retry. The message says the same in words.'
        perCall:
          type:
          - object
          - 'null'
          description: 'The same call bought one at a time over x402, where this route is sold that way — null where it is not. Carries the full offer: `accepts` with the amount, asset, CAIP-2 network and address to pay, plus `priceUsd`. Present BESIDE `eligiblePlans` rather than instead of it: an agent cannot buy a monthly plan (it needs a person, a card and a key) and a person evaluating the platform should not be sold a tenth of a dollar without learning a plan exists. Neither reader is steered past the road that suits them.'
          properties:
            x402Version:
              type: integer
            accepts:
              type: array
              items:
                type: object
            priceUsd:
              type: number
              x-unit: usd
            says:
              type: string
              description: When a plan is the cheaper road, in words.
        eligiblePlans:
          type: array
          description: Plans on sale that carry the missing capability, cheapest first, each with a checkout URL. An empty array means no plan carries it — a capability we do not sell rather than one you have not bought.
          items:
            type: object
            properties:
              slug:
                type: string
              name:
                type: string
              monthlyUsd:
                type: number
                x-unit: usd
              annualUsd:
                type: number
                x-unit: usd
              quotaPerMonth:
                type:
                - integer
                - 'null'
                description: Requests a month, or null where the plan is unmetered.
                x-unit: count
              checkoutUrl:
                type: string
        checkoutUrl:
          type: string
          description: Where a person goes. Agents should read eligiblePlans.
        error:
          type: string
          enum:
          - payment_required
        message:
          type: string
          description: Prose, for a person. Branch on `capability` and `reason`.
        capability:
          type: string
          description: The capability the caller lacks, e.g. `cascade.read`. This is the field that says what to buy.
        tier:
          type: string
          description: The tier the caller resolved to when the refusal was decided.
        reason:
          type: string
          enum:
          - tier
          - arrears
          - quota
          description: tier — the plan does not carry it. arrears — it does, and something is owed, so the paid surface is closed until settled. quota — the monthly allowance is spent. Only the first is a reason to buy anything.
        network:
          type: string
          description: Which chain a settlement would be read on.
        accepts:
          type: array
          description: Per-request payment offers, where any are open. Empty means this capability is sold by plan rather than by the call.
          items:
            type: object
        settlement:
          type: object
          description: How to pay, and what is missing if the caller cannot yet.
          properties:
            supported:
              type: string
            note:
              type: string
            address:
              type:
              - string
              - 'null'
        docs:
          type: string
          format: uri
    Page:
      type: object
      required:
      - total
      - limit
      - offset
      - hasMore
      properties:
        total:
          type: integer
          minimum: 0
          description: Rows matching the filter, not rows returned.
          x-unit: count
        limit:
          type: integer
          minimum: 0
          description: Applied page size. May be below what was asked — X-Page-Limit says so when it is.
          x-unit: count
        offset:
          type: integer
          minimum: 0
          x-unit: count
        hasMore:
          type: boolean
        nextOffset:
          type:
          - integer
          - 'null'
          description: Null on the last page.
          x-unit: count
        orderedBy:
          type: string
          description: The sort, including its tie-breaker. The order is total, so paging cannot show a row twice or skip one.
  securitySchemes:
    apiKey:
      type: apiKey
      in: header
      name: X-API-Key
      description: Issued from the account page.
    payment:
      type: apiKey
      in: header
      name: X-PAYMENT
      description: 'Settle in-request: `<chain>:<txHash>` for a transfer already sent. Read on chain during the call and credited immediately.'
x-aspern-environment: production