Skip to content

feat(api): Make API usage programmatically accessible#7215

Open
ERosendo wants to merge 3 commits into
mainfrom
7114-add-api-usage-endpoint
Open

feat(api): Make API usage programmatically accessible#7215
ERosendo wants to merge 3 commits into
mainfrom
7114-add-api-usage-endpoint

Conversation

@ERosendo

@ERosendo ERosendo commented Apr 13, 2026

Copy link
Copy Markdown
Contributor

Fixes

This PR fixes #7114

Summary

This PR adds GET /api/rest/v4/api-usage/, a read-only endpoint that lets authenticated users inspect their own API consumption programmatically. Primarily motivated by MCP integrations that want to monitor usage.

The response includes:

  • current_usage: per-scope throttle status (requests made, remaining, reset time) sorted by utilization descending, so the limit closest to being hit appears first. Scopes are discovered from DEFAULT_THROTTLE_RATES, so adding a new rate limit to settings makes it visible here automatically.

  • historical_usage: 14-day daily request counts plus a running total, sourced from existing Redis stats via invert_user_logs.

  • membership: NeonMembership level and active status, or null.

Here's an example:

{
    "current_usage": [
        {
            "scope": "user",
            "rate": "5000/hour",
            "requests_made": 17,
            "requests_allowed": 5000,
            "requests_remaining": 4983,
            "window_duration_seconds": 3600,
            "reset_at": "2026-04-13T17:35:10.122831+00:00",
            "blocked": false
        },
        {
            "scope": "citations",
            "rate": "60/min",
            "requests_made": 0,
            "requests_allowed": 60,
            "requests_remaining": 60,
            "window_duration_seconds": 60,
            "reset_at": null,
            "blocked": false
        }
    ],
    "historical_usage": {
        "2026-04-02": 7,
        "total": 7
    },
    "membership": null
}

Deployment

This PR should:

  • skip-deploy (skips everything below)
    • skip-web-deploy
    • skip-celery-deploy
    • skip-cronjob-deploy
    • skip-daemon-deploy

Fixes an issue where rates like "5000/hour" and "60/min" (as used by DRF's DEFAULT_THROTTLE_RATES) incorrectly hit the multiplier branch, causing a ValueError (e.g., int("hou")).

`parse_rate` now uses the first character when the period is alphabetic, while preserving support for multiplier formats like "5d" and "10s" used by existing callers (e.g., Celery task throttling).

Also fixes a bug in the fallback branch (period[-1] -> period[0]), which only worked for single-character inputs.
Adds a read-only endpoint that allows authenticated users to inspect their API usage programmatically. The response includes:

- current_usage: Per-scope throttle status (requests made, remaining, reset time), sorted by utilization in descending order so the most constrained limits appear first. Scopes are derived from DEFAULT_THROTTLE_RATES, so new rate limits added in settings are automatically reflected.
- historical_usage: 14-day daily request counts with a running total, sourced from existing Redis stats via invert_user_logs.
- membership: The user’s NeonMembership level and active status, or null.
Resolves type errors reported by pre-commit following the feature addition. No functional changes.

@albertisfu albertisfu left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @ERosendo this looks good.

I just have one comment and some additional notes here:

  • ApiUsageViewSet is itself rate-limited by the same user throttle it reports on. This means that if the user gets throttled, they won’t be able to see their usage. For instance, if a user is using this endpoint to check reset_at after being throttled in order to retry requests, they won’t be able to call this endpoint once they are throttled.

    I’m not sure if this is intended behavior, but I think it could be confusing for users.

    Maybe we can allow some extra requests to ApiUsageViewSet before it gets throttled itself, so the endpoint remains available when users need it most, right after they’ve been throttled on regular endpoints.

  • Also, we should update this PR with main and ensure the updated APIThrottle constraint takes effect in this PR: ["user", "throttle_type", "rate"]. Currently, this PR only supports one throttle type per user, as the constraint is ["user", "throttle_type"].

    We can also expand the tests to cover multidimensional throttle usage reports.

Comment thread cl/api/views.py
num_requests, duration = parse_rate(rate)
cache_key = f"throttle_{scope}_{user.pk}"
history = cache.get(cache_key, [])
valid = [ts for ts in history if ts > current_time - duration]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you make a citations request and there are entries in the citations throttle history, this will raise:

TypeError: '>' not supported between instances of 'list' and 'float'

This happens because the throttle history is a two-dimensional array like:

[[1, 1777779929.627806]]

So, we should properly extract the entries from the citations count and compute the current and remaining bucket, since the budget for citations is based on matched citations.

We might also need to rename these keys:

"requests_made"
"requests_allowed"
"requests_remaining"

to something that represents the number of matched citations instead of requests.

we should cover the described issues in a test to prevent regresessions.

@albertisfu albertisfu assigned ERosendo and unassigned albertisfu May 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: To Do

Development

Successfully merging this pull request may close these issues.

Make API usage API-accessible

2 participants