fix(api): give the shared write ceiling a code a client can branch on

The eleven single-record writes answered their shared 429 with prose and
nothing else, while the envelope's own description tells a client to
branch on `meta.errorCode`. On `POST /api/medications/[id]/intake` and
`POST /api/cycle/day-logs` a second, narrower bucket can answer 429 as
well, so the presence of the rate-limit headers was the only thing
telling the two refusals apart.

All eleven now carry `record_write.rate_limited`, the catalogue lists
it, the published 429 names it, and the contract test fails if a site
stops emitting it or the sentence stops publishing it.

The same description now states what a client could not predict from
"300 requests per 60 seconds": refused requests count too, because the
check runs before the body is read.
This commit is contained in:
Marc-André Bombeck committed 2026-09-10 08:39:09 +02:00
1 parent 68964b4bac
commit 3ffdfe15a1
15 files changed
+70 -32

No files matched your search

+3 -1
View File
@@ -93,7 +93,9 @@ async function postAllergy(request: NextRequest): Promise<Response> {
// client hits were not.
const writeRl = await checkRecordWriteRateLimit(actor.id);
if (!writeRl.allowed) {
return apiError("Too many writes, try again later", 429);
return apiError("Too many writes, try again later", 429, {
errorCode: "record_write.rate_limited",
});
}
const { data: rawBody, error: jsonError } = await safeJson(request, {
+3 -1
View File
@@ -103,7 +103,9 @@ async function postBiomarker(request: NextRequest) {
// client hits were not.
const writeRl = await checkRecordWriteRateLimit(actor.id);
if (!writeRl.allowed) {
return apiError("Too many writes, try again later", 429);
return apiError("Too many writes, try again later", 429, {
errorCode: "record_write.rate_limited",
});
}
const { data: body, error: jsonError } = await safeJson(request, {
@@ -112,7 +112,9 @@ async function postCustomMetricEntry(
// client hits were not.
const writeRl = await checkRecordWriteRateLimit(user.id);
if (!writeRl.allowed) {
return apiError("Too many writes, try again later", 429);
return apiError("Too many writes, try again later", 429, {
errorCode: "record_write.rate_limited",
});
}
const { id } = await params;
+3 -1
View File
@@ -105,7 +105,9 @@ async function postDayLog(request: NextRequest): Promise<Response> {
// client hits were not.
const writeRl = await checkRecordWriteRateLimit(actor.id);
if (!writeRl.allowed) {
return apiError("Too many writes, try again later", 429);
return apiError("Too many writes, try again later", 429, {
errorCode: "record_write.rate_limited",
});
}
const { data: rawBody, error: jsonError } = await safeJson(request, {
+3 -1
View File
@@ -162,7 +162,9 @@ async function postEncounter(request: NextRequest): Promise<Response> {
// client hits were not.
const writeRl = await checkRecordWriteRateLimit(actor.id);
if (!writeRl.allowed) {
return apiError("Too many writes, try again later", 429);
return apiError("Too many writes, try again later", 429, {
errorCode: "record_write.rate_limited",
});
}
const { data: rawBody, error: jsonError } = await safeJson(request, {
+3 -1
View File
@@ -156,7 +156,9 @@ async function postLabResult(request: NextRequest) {
// client hits were not.
const writeRl = await checkRecordWriteRateLimit(actor.id);
if (!writeRl.allowed) {
return apiError("Too many writes, try again later", 429);
return apiError("Too many writes, try again later", 429, {
errorCode: "record_write.rate_limited",
});
}
const { data: body, error: jsonError } = await safeJson(request, {
+3 -1
View File
@@ -807,7 +807,9 @@ async function postMeasurement(request: NextRequest) {
// client hits were not.
const writeRl = await checkRecordWriteRateLimit(auth.actor.id);
if (!writeRl.allowed) {
return apiError("Too many writes, try again later", 429);
return apiError("Too many writes, try again later", 429, {
errorCode: "record_write.rate_limited",
});
}
const { data: body, error: jsonError } = await safeJson(request, {
+3 -1
View File
@@ -64,7 +64,9 @@ async function postIntake(request: NextRequest, { params }: RouteParams) {
// client hits were not.
const writeRl = await checkRecordWriteRateLimit(actor.id);
if (!writeRl.allowed) {
return apiError("Too many writes, try again later", 429);
return apiError("Too many writes, try again later", 429, {
errorCode: "record_write.rate_limited",
});
}
// v1.36.1 follow-up — the sibling route `POST /api/medications/intake`
+3 -1
View File
@@ -209,7 +209,9 @@ export const POST = apiHandler(async (request: NextRequest) => {
// client hits were not.
const writeRl = await checkRecordWriteRateLimit(actor.id);
if (!writeRl.allowed) {
return apiError("Too many writes, try again later", 429);
return apiError("Too many writes, try again later", 429, {
errorCode: "record_write.rate_limited",
});
}
const { data: body, error } = await safeJson(request, {
+3 -1
View File
@@ -111,7 +111,9 @@ async function postMoodEntry(request: NextRequest) {
// client hits were not.
const writeRl = await checkRecordWriteRateLimit(actor.id);
if (!writeRl.allowed) {
return apiError("Too many writes, try again later", 429);
return apiError("Too many writes, try again later", 429, {
errorCode: "record_write.rate_limited",
});
}
const { data: body, error: jsonError } = await safeJson(request, {
+3 -1
View File
@@ -120,7 +120,9 @@ async function postVaccination(request: NextRequest): Promise<Response> {
// client hits were not.
const writeRl = await checkRecordWriteRateLimit(actor.id);
if (!writeRl.allowed) {
return apiError("Too many writes, try again later", 429);
return apiError("Too many writes, try again later", 429, {
errorCode: "record_write.rate_limited",
});
}
const { data: rawBody, error: jsonError } = await safeJson(request, {