Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

datasette-llm-limits

PyPI Changelog Tests License

A Datasette plugin for enforcing windowed spending caps on LLM usage.

datasette-llm-limits plugs into datasette-llm-accountant as an Accountant implementation, so every prompt issued through datasette-llm is checked against your configured caps before it is allowed to run.

It is designed for the case of time-windowed caps that reset automatically — "no single user may spend more than $1.00 per day", or "the whole instance is capped at $50 per calendar month".

For a top-up-style refillable balance, see the sibling project datasette-llm-allowance.

Installation

Install this plugin in the same environment as Datasette:

datasette install datasette-llm-limits

This will also install datasette-llm-accountant, which is required.

Configuration

All configuration lives in datasette.yaml under plugins.datasette-llm-limits.limits. Each entry is a named limit; on every reservation the plugin checks every matching limit and rejects the call if any one would be exceeded.

plugins:
  datasette-llm-limits:
    limits:
      # Per-user, rolling 24 hours
      per-user-daily:
        scope: actor
        window: rolling-24h
        amount_usd: 1.00

      # Per-user, calendar month (resets at UTC midnight on day 1)
      per-user-monthly:
        scope: actor
        window: calendar-month
        amount_usd: 20.00

      # Per-actor cap that only applies to one purpose
      enrichments-per-user-daily:
        scope: actor
        window: rolling-24h
        amount_usd: 5.00
        purpose: enrichments

      # Instance-wide cap, any actor, any purpose
      instance-monthly:
        scope: instance
        window: calendar-month
        amount_usd: 250.00

      # Per-model cap (e.g. limit expensive models per actor)
      gpt5-pro-per-user-weekly:
        scope: actor
        window: rolling-7d
        amount_usd: 10.00
        model_id: gpt-5-pro

Field reference

Field Type Required Description
scope string yes One of actor or instance. actor partitions usage by actor_id; instance aggregates across every caller.
window string yes One of rolling-24h, rolling-7d, rolling-30d, calendar-day, calendar-week, calendar-month. Rolling windows look back N seconds from now. Calendar windows reset at UTC midnight on the appropriate boundary.
amount_usd number yes Cap in US dollars. Stored internally in nanocents.
purpose string no If set, the limit only applies when the prompt's purpose matches. Omit to apply regardless of purpose.
model_id string no If set, the limit only applies when the prompt's model_id matches. Omit to apply regardless of model.

A limit "matches" a reservation when:

  • purpose is unset OR equals the prompt's purpose, AND
  • model_id is unset OR equals the prompt's model id, AND
  • scope is instance, OR (scope is actor AND the reservation has a non-empty actor_id).

Reservations made by unauthenticated callers (no actor_id) skip any scope: actor limits but still count toward scope: instance limits.

Validation

Configuration is validated at startup. Unknown fields, unknown scope or window values, missing required fields, or non-positive amount_usd raise ValueError before Datasette starts.

How rejections look

When a reservation would exceed any matching limit, the call is rejected with an InsufficientBalanceError whose message follows this format:

Limit "per-user-daily" exceeded: $0.95 used of $1.00 in rolling-24h.

For calendar windows the message also tells the caller when the window resets:

Limit "per-user-monthly" exceeded: $19.80 used of $20.00 in calendar-month.
Try again after 2026-04-01T00:00:00Z.

datasette-llm will surface this message to the end user.

The /-/llm-limits inspection view

The plugin registers a read-only view at /-/llm-limits that lists every configured limit. Instance-scoped limits include their current usage, remaining headroom, and the time of the next reset (for calendar windows). Actor-scoped limits show their cap as a per-actor cap, with usage broken out in a separate actor usage table. The view also shows the 50 most recent transactions.

The view supports both HTML (default) and JSON:

curl -H "Accept: application/json" http://localhost:8001/-/llm-limits
# or:
curl http://localhost:8001/-/llm-limits?_format=json

Permissions

The view is gated by a plugin-defined permission, datasette-llm-limits-view, which defaults to deny. Grant it in datasette.yaml:

permissions:
  datasette-llm-limits-view:
    id: "*"          # any signed-in actor
    # or: id: ["github:9599"] for specific actor ids

Anyone without this permission gets a 403.

Storage

The plugin keeps its state in Datasette's internal database (the path passed via --internal). On startup it creates an llm_limits_tx table that records every reservation and settlement. Rows for rolled-back transactions remain in the audit trail with settled_nanocents = 0.

CREATE TABLE llm_limits_tx (
    id TEXT PRIMARY KEY,                -- ULID for monotonic ordering
    created_at TEXT NOT NULL,           -- ISO-8601 UTC
    settled_at TEXT,                    -- ISO-8601 UTC; NULL while reserved
    actor_id TEXT,                      -- NULL for unauthenticated
    purpose TEXT,                       -- NULL when not provided
    model_id TEXT,                      -- NULL when not provided
    reserved_nanocents INTEGER NOT NULL,
    settled_nanocents INTEGER,          -- NULL while reserved
    matched_limits TEXT NOT NULL        -- JSON array of limit names
);

If you have view-database on _internal, you can query llm_limits_tx directly to build your own reports.

How it fits with datasette-llm-accountant

datasette-llm-limits does not perform pricing or wrap LLM calls itself — that is the job of datasette-llm-accountant. The accountant calls into this plugin's reserve / settle / rollback methods, and this plugin enforces the configured caps.

You may stack multiple accountants: for example, datasette-llm-allowance for a refillable balance plus datasette-llm-limits for a daily cap. A prompt only goes through if every registered accountant approves it.

Development

To set up this plugin locally, check out the code, then:

cd datasette-llm-limits
# Confirm the plugin is visible
uv run datasette plugins

To run the tests:

uv run pytest

To format and lint:

uv run --with black black --target-version py311 .
uv run --with ruff ruff check .

Metadata

Release files for datasette-llm-limits 0.1a2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for datasette-llm-limits 0.1a2
File Size Uploaded
datasette_llm_limits-0.1a2.tar.gz 22.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for datasette-llm-limits 0.1a2
File Interpreter ABI Platform
datasette_llm_limits-0.1a2-py3-none-any.whl Python 3 none any Details

Total release size: 38.4 kB

Release files / datasette_llm_limits-0.1a2.tar.gz

Download URL datasette_llm_limits-0.1a2.tar.gz
Size 22.0 kB
Tags Source
SHA-256 checksum
How to use checksums
eb08f70c87b252c356278b2738dc518d1e38aa1fb48ff990a36e3bfcf27c84fd
BLAKE2b-256 checksum
How to use checksums
dba6686de4e106c33805cb292c1f29a0583de757a3a823be23ede771d9c6c64f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 30, 2026.

Transparency log

Release files / datasette_llm_limits-0.1a2-py3-none-any.whl

Download URL datasette_llm_limits-0.1a2-py3-none-any.whl
Size 16.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
31464ad56e77acd9408965b098144d4d28b9cef22c8735d3595771a18bb65733
BLAKE2b-256 checksum
How to use checksums
1c74ccdeb8ec61d0c05c9991e41c5547fb2d82437d478269cfc8b1e88c57a5e9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 30, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1a2 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page