---
title: "List Follows"
method: GET
path: "/api/follows"
tags: ["follows"]
---

# List Follows

`GET /api/follows`

## Request

- Base URL: `https://api.parlel.com` (the document declares no server; this is the origin it was published from)
- URL: `https://api.parlel.com/api/follows`
- Auth: none declared

Everything this user follows: live agents first, then dormant ones.

A live entry is the *same* summary shape as the gallery, so the frontend
renders both surfaces through one component. ``following`` is ``true`` on
every one by definition, which is why no per-row lookup happens here.

``owned`` is carried too, and it is not dead weight even though the follow
rule makes it ``false`` on every row you could have followed today. It is a
property of the SHAPE, not of this list: the same card component reads it on
all three surfaces, so a row here that omitted it would be the one payload
where the client's "always present" rule did not hold. It is also not
permanently false — a self-follow that predates the 400, or a future transfer
of ownership, would surface here rather than rendering a Follow button on
your own agent. Free either way: ``user_id`` is on the row already.

A dormant entry — the agent was unpublished after being followed — is a
smaller, separate payload flagged ``dormant: true``. It used to be dropped
from this list entirely, which told the follower they followed nothing; see
the module docstring, and ``_dormant_follow`` for what it may and may not
carry.

**The ordering is server-side and total, so the client never sorts.** One
``ORDER BY`` does all three jobs: dormant last (``public_slug IS NULL``
sorts false-before-true on both SQLite and Postgres, and compiles to the
same SQL on each), then newest-followed first within each group, then row id
to break a same-instant tie. Live-first is also what makes
``FOLLOWS_PAGE_LIMIT`` safe — the truncation can only ever drop dormant
rows. ``ix_agent_follows_user_created`` is what the follow-time part reads.

Still THREE statements regardless of how many agents are followed, and
regardless of how many are dormant — the page, the batched finding previews,
and the batched finding totals. The dormant rows ride the page query rather
than adding one of their own (the ``public_slug IS NOT NULL`` filter became
part of the ORDER BY instead of a second SELECT), and the two batched
statements are handed only the live ids, so a list that is entirely dormant
costs ONE statement: ``_preview_findings`` and ``_shareable_counts`` both
return early on an empty id list. Budget pinned by
``tests/test_agents_query_budget.py``.

``plan_json`` is deferred with ``raiseload``: it is several KB of cached
execution plan per agent, nothing in either payload reads it, and a plain
defer would quietly convert a future reference into one SELECT per row.

## Headers

- `authorization` string, nullable

## Response `200`

Successful Response

- object

## Other responses

- `422`: Validation Error

---

[API](https://skmtc.dev/parlel/apis/parlel-api.md) · [All operations](https://skmtc.dev/parlel/apis/parlel-api/llms.txt) · [OpenAPI document](https://skmtc.dev/parlel/apis/parlel-api/revisions/4d41a8f5cfc9?raw)
