Skip to content

docs(api): write down the API conventions - #676

Merged
alukach merged 3 commits into
mainfrom
docs/api-conventions
Oct 16, 2026
Merged

alukach merged 3 commits into
mainfrom
docs/api-conventions

Conversation

@alukach

@alukach alukach commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Follows #667, now merged. Writes down the API conventions this round of PRs settled on, so the next endpoint follows them without anyone reconstructing them from the code.

What

  • API.md at the repository root, beside CONTRIBUTING.md:
    • One operation, thin adapters: what an operation decides, what a route and a server action may do, and the exceptions for routes no session can call.
    • Documented where it's defined: registerPath beside the handler, the coverage test and its UNREGISTERED allowlist, published prose, explicit HEAD, bearer and optionalBearer, internal, and deprecated.
    • Resources and methods: URL and field naming, and a table of what each method means and answers. Deactivating is PATCH { "disabled": true }, never DELETE, and one resource has one edit endpoint.
    • Lists: {items, next_cursor}, never a bare array.
    • Errors: the shared shape and when each status applies, the 500 internal error and the trust exchange's {trusted: false} 403, authorizing before validating where the permission doesn't depend on the input, and answering 404 for things the caller may not know of.
    • Writes: carry only the fields that changed, and what accountsTable.update always writes.
    • Compatibility: the data proxy is pinned by a test, the CLI follows the reference, and anyone else gets a traffic check, then deprecation rather than removal.
  • CLAUDE.md (and AGENTS.md, which links to it) gets a short API section telling an agent to read API.md before touching /api/v1.
  • CONTRIBUTING.md's "Server Actions & API Routes" paragraph said both call isAuthorized() themselves. It now describes them as adapters over one operation and points at API.md.

Where it lives

A root API.md rather than more of CLAUDE.md: these are conventions for whoever writes an endpoint, person or agent, and much of it is visible in the code once you know where to look, which is what CLAUDE.md says it isn't for. CLAUDE.md carries the pointer, so agents still load it at the right moment. CONTRIBUTING.md links to it.

Merge order

The guide describes the API as it stands once the open PRs from this round merge, and names things they add:

Merge it after them, or trim the line that names whichever of them doesn't land.

Where the code doesn't follow the guide yet

The guide states the conventions, not the code's progress toward them. Known gaps, tracked outside the docs:

Docs, ADRs and llms.txt

Repository-internal guidance only: no user-facing flow changes, so docs.source.coop and llms.txt are unaffected, and it records no decision shared with the data proxy beyond the existing contract test, so no data.source.coop ADR changes.

Testing

Documentation only; no code changed. Each claim in API.md was checked against the code after a review pass found several the code didn't hold to (route-coverage.test.ts, accountsTable.update, withApiSession's error handling, the revocations and trust-exchange routes). Checked that AGENTS.md still resolves to CLAUDE.md and that the prose in API.md wraps at 80, as the repository's Markdown does.

🤖 Generated with Claude Code

API.md covers how an /api/v1 endpoint is built on the operations layer, how it documents itself, what each method, list and error looks like, and which consumers it has to stay compatible with. CLAUDE.md points agents at it, and CONTRIBUTING.md's description of routes and server actions now matches the operations layer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vercel

vercel Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
source-coop-ui Ready Ready Preview Oct 16, 2026 2:55am UTC
source-cooperative Ready Ready Preview Oct 16, 2026 2:55am UTC

Request Review

@claude

claude Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @alukach's task in 16s —— View job


✅ No blocking issues — safe to merge.

This is a documentation-only PR (API.md, plus pointers in CLAUDE.md and CONTRIBUTING.md). I read the diff but did not check each API.md claim against the code. The PR description says that was done.

Simplify (ponytail)

  • API.md Documented-where-defined, HEAD bullet: the export { GET as HEAD } slips-past-the-test aside describes a test gap. Fix the coverage test to catch it and drop the sentence.
  • CLAUDE.md API section: the second sentence only restates the API.md headings. Cut it to the first sentence.
    · branch docs/api-conventions

💰 Estimated review cost: $0.11 · 0m16s · 4 turns

…ntions

Code review of #676 found the guide stating rules the code doesn't yet follow. Name the exceptions instead of implying they're closed: the data-connections routes and older actions that still call isAuthorized, the revocations route, the trust exchange's 403 body, the 500 `internal` error, the coverage test's allowlist and regex, HEAD /accounts answering anyone, operations that validate before authorizing, and accountsTable.update writing name/type and metadata whole. Drop the unverified `vercel metrics --group-by route` command.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The guide states each rule in the present tense. The paths that don't follow it yet are tracked in #592 and #593 and named in the PR description, not in the docs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@alukach
alukach marked this pull request as ready for review October 16, 2026 03:19
@alukach
alukach merged commit 637c549 into main Oct 16, 2026
10 checks passed
@alukach
alukach deleted the docs/api-conventions branch October 16, 2026 03:19
alukach added a commit that referenced this pull request Oct 16, 2026
…ody (#678)

`updateProduct`, `deleteProduct` and `updateMembership` parsed their
input before checking whether the caller could act. A signed-in stranger
who sent `PATCH /products/{a}/{p}` with a bad body got `field_errors`
back instead of a 403, which tells them the product exists and what its
schema accepts. Found in review of #676, whose guide says to authorize
before validating wherever the permission doesn't depend on the input.

## What

- **`updateProduct` and `deleteProduct`** fetch the product and
authorize first, then parse the body or query. An unknown product is now
`not_found` whatever the body, and a caller who may not act is
`forbidden` whatever the body.
- **`deleteProduct`** answers an empty account or product ID as
`not_found` without asking DynamoDB, as `updateProduct` already does.
- **`updateMembership`** refuses, before reading the body, a caller who
may make no change at all to the membership: one holding none of
`UpdateMembershipRole`, `AcceptMembership` or `RevokeMembership` over
it. Since #679 the body is `{ role }` or `{ state }`, and each needs a
different right, so the right that change needs is checked once the body
is parsed: the manager's for a role (against the current role and the
requested one), the invitee's to accept, the member's or a manager's to
revoke. A caller who may change a membership can still be told its body
is invalid.
- **`changeMembership`** takes one `decide(m)` callback that answers
`ok(change)` or a failure, in place of a `refuse` callback plus a fixed
`change`. That lets `updateMembership` build its change from input it
parses after authorization. `acceptInvitation`, `rejectInvitation` and
`revokeMembership` move onto it with the same behavior; the accept and
revoke decisions are shared with `updateMembership`.

`createProduct` and `inviteMember` stay as they are: their permission
depends on the parsed input. `withApiSession` still answers a body that
isn't JSON at all with 400 before any operation runs; that says nothing
about the resource, and moving it would change every route, so it is out
of scope here.

## Docs, ADRs and `llms.txt`

No user-facing flow or endpoint changes, so docs.source.coop and
`llms.txt` still hold. The data-proxy ADRs describe who may act, not the
order of checks, so none change. `API.md` in #676 already states this
convention; once this merges, the matching line in #676's "Where the
code doesn't follow the guide yet" list can go.

## Testing

- The refusal and not-found tests for all three operations send an
invalid body, so they fail if parsing moves back above the lookup or the
authorization.
- A new `updateMembership` test gives the caller one right at a time. It
fails if the pre-parse check goes back to the role right alone, which
would refuse an invitee accepting.
- On the head after merging `main` (with #679): `jest src/lib/operations
src/app/api src/lib/actions/memberships.test.ts`, 16 suites, 201 tests
pass.
- `npm run type-check` passes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

This branch was successfully deployed

2 active deployments
Preview – source-cooperative — 8b61c795 Deployed Oct 16, 2026 by vercel[bot]
Preview – source-coop-ui — 8b61c795 Deployed Oct 16, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant