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

# Run a SQL SELECT across the datasets

> Execute a read-only SQL query over the datasets in DuckDB. Only `SELECT`
statements are accepted; anything else is rejected by the validator.

- Table names are the source names (`gutenberg`, `arxiv`, `edgar`, …).
- Connected Google Sheets can be joined in by passing their ids in
  `datasetIds` and referencing them by their **alias** (never the internal
  view name).
- Queries are capped at 10,000 characters, 1,000 rows and 30 seconds.
- Charged **per row returned**.

```bash
curl -X POST https://api.dataville.com/api/v1/query \
  -H "Authorization: Bearer $DATAVILLE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"sql":"SELECT title, author FROM gutenberg LIMIT 10"}'
```




## OpenAPI

````yaml /openapi/openapi.yaml post /api/v1/query
openapi: 3.0.3
info:
  title: Dataville API
  version: 1.0.0
  description: >
    Dataville is a unified data API: a single REST surface over Wikipedia,
    arXiv,

    Project Gutenberg, the US Census, USDA FoodData, Papers with Code, SEC
    EDGAR,

    OpenAlex, PyPI and Stack Exchange — plus bulk CSV/Parquet/DuckDB export, a

    SQL endpoint, and "bring your own data" Google Sheets connections.


    ## Authentication


    Data, export and query endpoints never hard-reject at the middleware level —
    an

    unauthenticated caller simply gets the anonymous tier (50 requests per day
    per

    IP).


    ### API key


    ```

    Authorization: Bearer dataville_xxxxxxxxxxxxxxxx

    ```


    Create and manage keys from the **API Keys** page in the

    [Dataville dashboard](https://app.dataville.com). Keys start with
    `dataville_`

    (legacy keys start with `datax_`). For clients that cannot set headers —
    DuckDB

    WASM, the DuckDB CLI's `httpfs` reader — the same key may be passed as the

    `?token=` query parameter.


    The Dataville web app authenticates with its own session tokens; those
    routes

    are internal and not part of the public API surface.


    ## Credits and metering


    Authenticated usage is metered against a monthly **Dataville credit**
    balance in

    USD, shared across an account's sessions and API keys. Each tier is granted

    credit every month — Free **$5**, Developer **$29**, Pro **$99** — and each

    query deducts a per-source, per-tier amount:


    | Class | Free | Developer | Pro |

    | --- | --- | --- | --- |

    | Open/public datasets | $0.0050 | $0.0050 | $0.0040 |

    | Wikipedia (premium) | $0.0250 | $0.0150 | $0.0125 |


    A `POST /api/v1/query` call is charged per row returned. When the balance

    reaches zero the API returns **402 Payment Required** until the monthly
    reset,

    a plan upgrade, or a credit top-off. Every metered response carries:


    - `X-Credits-Remaining` — remaining balance, USD

    - `X-Credit-Cost` — amount charged for this request, USD


    Check your request quota any time via `GET /api/v1/auth/usage`; your credit

    balance is reported on every metered response in `X-Credits-Remaining` and
    on

    the dashboard's **Billing** page.


    ## Rate limiting


    Anonymous callers are limited by IP (50 requests per 24 hours) and receive

    **429** when they exceed it, with `X-RateLimit-Limit`,
    `X-RateLimit-Remaining`

    and `X-RateLimit-Reset` headers. Authenticated callers are governed by
    credits

    rather than a request count. Export and SQL routes carry their own stricter

    limit (1,000 per day per account), as does support submission (5 per hour).


    ## Errors


    Every error body carries a human-readable `error`/`message` string that is
    safe

    to show a user verbatim, and data-source errors additionally carry a stable

    `data.code` (`UPSTREAM_RATE_LIMITED`, `UPSTREAM_UNAVAILABLE`,

    `UPSTREAM_FORBIDDEN`, `UPSTREAM_BAD_RESPONSE`, `SOURCE_UNAVAILABLE`,

    `INTERNAL_ERROR`) that clients should branch on instead of parsing prose.
    When

    an upstream tells us how long to wait, the response sets `Retry-After`.


    ## Stale results


    A live source that is down is answered from Dataville's own stored copy
    where

    one exists. Those responses include a `data.stale` object naming the source
    and

    when the copy was taken — the answer is real, but may be out of date.
  contact:
    name: Dataville Support
    email: support@dataville.com
  license:
    name: MIT
    url: https://opensource.org/licenses/MIT
servers:
  - url: https://api.dataville.com
    description: Production
security:
  - ApiKeyAuth: []
tags:
  - name: Data sources
    description: Keyword search and article lookup across every supported dataset.
  - name: Export and SQL
    description: Bulk CSV/Parquet/DuckDB download and SELECT-only SQL over the datasets.
  - name: Connected sheets
    description: Connect a Google Sheet and query it alongside the public datasets.
  - name: Usage
    description: |-
      Usage and quota for your API key (or your IP when anonymous). Keys
      are created and revoked from the dashboard's **API Keys** page.
paths:
  /api/v1/query:
    post:
      tags:
        - Export and SQL
      summary: Run a SQL SELECT across the datasets
      description: |
        Execute a read-only SQL query over the datasets in DuckDB. Only `SELECT`
        statements are accepted; anything else is rejected by the validator.

        - Table names are the source names (`gutenberg`, `arxiv`, `edgar`, …).
        - Connected Google Sheets can be joined in by passing their ids in
          `datasetIds` and referencing them by their **alias** (never the internal
          view name).
        - Queries are capped at 10,000 characters, 1,000 rows and 30 seconds.
        - Charged **per row returned**.

        ```bash
        curl -X POST https://api.dataville.com/api/v1/query \
          -H "Authorization: Bearer $DATAVILLE_API_KEY" \
          -H "Content-Type: application/json" \
          -d '{"sql":"SELECT title, author FROM gutenberg LIMIT 10"}'
        ```
      operationId: runSqlQuery
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - sql
              properties:
                sql:
                  type: string
                  maxLength: 10000
                  description: A single SELECT statement.
                  example: SELECT title, author FROM gutenberg LIMIT 10
                datasetIds:
                  type: array
                  description: >-
                    Ids of the caller's connected sheets to make available to
                    the query.
                  items:
                    type: string
      responses:
        '200':
          description: Query results.
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: success
                  row_count:
                    type: integer
                    example: 10
                  rows:
                    type: array
                    items:
                      type: object
                      additionalProperties: true
                  sql_executed:
                    type: string
                  available_tables:
                    type: array
                    items:
                      type: string
                  usage:
                    type: object
                    properties:
                      rows_charged:
                        type: integer
                      request_cost:
                        type: number
                        format: float
                      credits_remaining:
                        type: number
                        format: float
        '400':
          description: Missing/oversized SQL, a rejected statement, or a DuckDB error.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SimpleError'
              example:
                status: error
                message: Only SELECT statements are allowed.
        '401':
          $ref: '#/components/responses/ExportUnauthorized'
        '402':
          $ref: '#/components/responses/InsufficientCredit'
        '403':
          description: A dataset id in `datasetIds` is not owned by the caller.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SimpleError'
        '408':
          description: Query exceeded the 30-second limit.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SimpleError'
        '429':
          $ref: '#/components/responses/ExportRateLimited'
components:
  schemas:
    SimpleError:
      type: object
      properties:
        status:
          type: string
          enum:
            - error
        message:
          type: string
    InsufficientCreditResponse:
      type: object
      properties:
        status:
          type: string
          enum:
            - error
        account_state:
          type: string
          example: authenticated
        error:
          type: string
          example: insufficient_credit
        data:
          type: object
          properties:
            error:
              type: string
            code:
              type: string
              enum:
                - INSUFFICIENT_CREDIT
        credits:
          type: object
          properties:
            remaining_usd:
              type: number
              format: float
            request_cost_usd:
              type: number
              format: float
  responses:
    ExportUnauthorized:
      description: Authentication required.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/SimpleError'
          example:
            status: error
            message: >-
              Authentication required. Include your API key in the Authorization
              header.
    InsufficientCredit:
      description: Monthly credit exhausted — upgrade, top off, or wait for the reset.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/InsufficientCreditResponse'
    ExportRateLimited:
      description: Export/query limit exceeded (1,000 per day per account).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/SimpleError'
          example:
            status: error
            message: >-
              Export rate limit exceeded. Maximum 1,000 exports per day per
              account.
  securitySchemes:
    ApiKeyAuth:
      type: http
      scheme: bearer
      description: >
        A Dataville API key, sent as `Authorization: Bearer dataville_...`.
        Clients

        that cannot set headers may pass the same key as `?token=`.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.