# Add Throttling to a Django REST Framework API from a Linear Issue

Yes. An agent reads the Linear issue, adds DRF anonymous, user and login throttles to your settings and views, proves them with pytest and opens a pull request.

[Run this on Rush](https://cloud.getrush.ai/run/add-throttling/django-rest-framework/), in a Project that has your repository.

## What the agent does

The agent clones the repository into its own cloud sandbox and reads how requests are authenticated, because DRF decides between anonymous and user throttles from `request.user`. In a DRF API like `HackSoftware/Django-Styleguide-Example`, `requirements/base.txt` pins `djangorestframework==3.16.1` on Django 5.2, and the `REST_FRAMEWORK` dictionary in `config/django/base.py` sets an exception handler and a filter backend but `DEFAULT_AUTHENTICATION_CLASSES` is an empty list. Authentication is opted into per view through `ApiAuthMixin` in `styleguide_example/api/mixins.py`, which adds session, session-as-header and JWT authentication with `IsAuthenticated`.

With that in mind the agent adds `DEFAULT_THROTTLE_CLASSES` with `AnonRateThrottle` and `UserRateThrottle`, and `DEFAULT_THROTTLE_RATES` of `20/minute` for `anon` and `1000/day` for `user`, to the existing `REST_FRAMEWORK` dictionary rather than a new settings module. Views without `ApiAuthMixin` count against the anonymous rate by client IP, and views with it count per user.

The login endpoints get the stricter scope. `styleguide_example/authentication/urls.py` routes `session/login/` to `UserSessionLoginApi` and `jwt/login/` to `UserJwtLoginApi`, which extends drf-jwt's `ObtainJSONWebTokenView`. The agent sets `throttle_classes = [ScopedRateThrottle]` and `throttle_scope = "login"` on both and adds a `login` rate to the settings. Throttle counts live in Django's cache; `config/django/test.py` already sets `LocMemCache`, and the base settings configure no shared cache, which the agent notes for production.

Tests sit beside the existing ones in `styleguide_example/authentication/tests/apis/`, which use Django's `TestCase` with DRF's `APIClient` and `reverse("api:authentication:session:login")`. The agent adds tests that clear the cache in `setUp`, override the rates with `override_settings` so the suite stays fast, send one more request than the limit and assert `429` with a `Retry-After` header, and check that a different scope is unaffected. Then it installs `requirements/local.txt`, starts PostgreSQL in the sandbox because `DATABASES` reads `DATABASE_URL` with a Postgres default, and runs `pytest`.

## Start it with one API call

`POST https://api.prix.dev/o/{org}/sessions`

```json
{
  "agent": "codex",
  "input": "A Linear issue labeled agent says: \"Add Throttling to a Django REST Framework API. Add DRF throttling: 20 requests per minute for anonymous users and 1,000 per day for authenticated ones, with a stricter scope on the login endpoint.\" Do the work in this repository, add tests for it, run pytest, and open a pull request that links back to the issue.",
  "resources": [
    {
      "url": "https://github.com/your-org/your-app"
    },
    {
      "url": "https://linear.app/your-workspace/issue/ENG-42"
    }
  ]
}
```

1. **Get an API key.** In the console, open Settings, then API keys, and create a key for your org. Send it in the Authorization header as a Bearer token.
2. **Send the request.** POST the body in request.json to https://api.prix.dev/o/{org}/sessions, with your repository as the resources URL. The run starts in its own cloud sandbox.
3. **Read what came back.** Follow the run on its live stream or wait for the run.completed webhook, then read the result and its trajectory.

## What comes back

What comes back is a pull request. In an API shaped like the styleguide example, the diff changes `config/django/base.py` (the throttle classes and rates inside the existing `REST_FRAMEWORK` dictionary), `styleguide_example/authentication/apis.py` (the `login` scope on the session and JWT login views), and adds a throttling test module under `styleguide_example/authentication/tests/apis/`. The description quotes the issue, links back to it and states the chosen login rate.

A reviewer checks the numbers against the issue, then the two things throttling depends on that the code does not show: which cache backend production uses, since a per-process cache gives each worker its own count, and whether the API sits behind a proxy, since `AnonRateThrottle` identifies clients by address and DRF's `NUM_PROXIES` setting decides which address that is. The pull request names both.

The checks are the repository's own: in the example, the GitHub workflow runs `ruff check`, `mypy` and `pytest`. The session keeps every command the agent ran and its output, including the pytest run against the sandbox PostgreSQL.

## Run it on every Linear issue labelled agent

In the console, an automation on the Project with a Linear trigger on `linear.issue.labeled`, filtered to the `agent` label, starts one run for each issue that gains the label. The issue is the run's context and the run ends in a pull request that links back to it.

Your own server can start the same run with this request and the issue's URL in `resources`. With a webhook URL set on the API key, Rush POSTs `run.completed` when the turn finishes, and the `session_url` in it reads the pull request.

## Why run it on Rush?

Throttling tests need a database, the full dependency set and a cache, and they are easy to get subtly wrong when the cache carries counts between tests. On Rush each run gets its own sandbox with PostgreSQL started inside it, runs the real pytest suite, and keeps every command and its output, so the reviewer sees the 429s the tests actually produced.

Security-flavoured issues like this one tend to arrive in batches after an audit. Each labelled issue becomes its own run in its own sandbox and its own pull request, so throttling, password rules and header changes can be reviewed and merged separately instead of as one large branch.

## Frequently asked questions

### My views set authentication per view, not globally. Does throttling still work?

Yes, and the agent reads that first. DRF picks the anonymous or user throttle from `request.user` after authentication, so a view that uses `ApiAuthMixin` counts per user and a view without it counts per IP. The tests cover one of each.

### What does the login scope do that the anonymous rate does not?

Login views are anonymous by nature, so without a scope they would share the general 20 per minute. `ScopedRateThrottle` with `throttle_scope = "login"` gives the session and JWT login views their own lower rate, counted separately from every other anonymous request.

### Will the tests be slow because of the per-day rate?

No. The tests override `DEFAULT_THROTTLE_RATES` with small numbers through `override_settings` and clear the cache before each case, so they send only a handful of requests. The production rates stay in settings exactly as the issue states them.

### Does it change how errors look?

The project routes errors through its own `EXCEPTION_HANDLER`. The agent checks that a throttled request still comes out with status 429 and `Retry-After` through that handler, and adds an assertion on the response body shape if the handler reformats it.
