Title: fix(api/offers): return Content-Type: application/json
The GET /api/offers route in packages/web/src/app/api/(server)/offers/route.ts returns a JSON body but does not set the Content-Type: application/json response header. As a result, the response is served with Content-Type: text/plain;charset=UTF-8, even though the body is a JSON document.
Reproduction
A request to /api/offers returns the offers array as JSON, but the response header is text/plain;charset=UTF-8 because new Response(JSON.stringify(offers)) does not set a content type (Node.js / Next.js default for a string body is text/plain;charset=UTF-8).
curl -i https://your-sourcebot-instance.com/api/offers
HTTP/1.1 200 OK
content-type: text/plain;charset=UTF-8
cache-control: public, max-age=300
[...]
This is the only public API route in packages/web/src/app/api/(server)/ that uses new Response(JSON.stringify(...)) without setting Content-Type. Every other public JSON route — /api/health, /api/version, /api/blame, /api/source, /api/connections, /api/ee/audit, /api/ee/scoped_access_token (response side) — either uses Response.json(...) (which sets the header automatically) or sets 'Content-Type': 'application/json' explicitly. See:
packages/web/src/lib/apiHandler.ts (uses Response.json in its docstring example)
packages/web/src/app/api/(server)/repos/route.ts:38 — explicit 'Content-Type': 'application/json'
packages/web/src/app/api/(server)/ee/audit/route.ts:67 — explicit 'Content-Type': 'application/json'
packages/web/src/app/api/(server)/health/route.ts:12 — uses Response.json({ status: 'ok' })
packages/web/src/app/api/(server)/version/route.ts:16 — uses Response.json({ version: SOURCEBOT_VERSION })
Why this matters
- The in-app
getOffers client uses response.json() and happens to parse the body regardless of Content-Type (packages/web/src/app/api/(client)/client.ts:310-322). It works in practice but is a coincidence of the Web fetch API, not because the response is technically correct.
- Strict API clients (CLI tools, OpenAPI-generated SDKs, third-party integrations) typically inspect
Content-Type before parsing. A client that sees text/plain;charset=UTF-8 will refuse to parse the body, even though the body is valid JSON. This makes the public API endpoint less interoperable than every other public endpoint in the same product.
- The endpoint is documented in the OpenAPI spec (
docs/api-reference/sourcebot-public.openapi.json) as returning application/json. The actual response violates the documented contract.
Proposed fix
In packages/web/src/app/api/(server)/offers/route.ts, change the return value from
return new Response(JSON.stringify(offers), {
headers: {
'Cache-Control': 'public, max-age=300'
}
})
to
return new Response(JSON.stringify(offers), {
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'public, max-age=300'
}
})
or, equivalently, replace the whole return with
return Response.json(offers, {
headers: {
'Cache-Control': 'public, max-age=300'
}
})
which sets Content-Type: application/json automatically and is the pattern used by the other public routes. The body is unchanged, so no client is broken.
Acceptance criteria
curl -I https://your-sourcebot-instance.com/api/offers shows content-type: application/json (or application/json; charset=utf-8).
- The body is unchanged.
- All other routes' responses are unaffected.
yarn workspace @sourcebot/web test --run still returns 1154/1154 tests pass.
- No OpenAPI or docs change is needed (the spec already declares
application/json).
Risks
None. The body is unchanged; only the response header is corrected. Strict clients that were already failing to parse will start working; clients that were lenient enough to parse the body (the in-app getOffers is one) continue to work.
Title: fix(api/offers): return Content-Type: application/json
The
GET /api/offersroute inpackages/web/src/app/api/(server)/offers/route.tsreturns a JSON body but does not set theContent-Type: application/jsonresponse header. As a result, the response is served withContent-Type: text/plain;charset=UTF-8, even though the body is a JSON document.Reproduction
A request to
/api/offersreturns the offers array as JSON, but the response header istext/plain;charset=UTF-8becausenew Response(JSON.stringify(offers))does not set a content type (Node.js / Next.js default for a string body istext/plain;charset=UTF-8).This is the only public API route in
packages/web/src/app/api/(server)/that usesnew Response(JSON.stringify(...))without settingContent-Type. Every other public JSON route —/api/health,/api/version,/api/blame,/api/source,/api/connections,/api/ee/audit,/api/ee/scoped_access_token(response side) — either usesResponse.json(...)(which sets the header automatically) or sets'Content-Type': 'application/json'explicitly. See:packages/web/src/lib/apiHandler.ts(usesResponse.jsonin its docstring example)packages/web/src/app/api/(server)/repos/route.ts:38— explicit'Content-Type': 'application/json'packages/web/src/app/api/(server)/ee/audit/route.ts:67— explicit'Content-Type': 'application/json'packages/web/src/app/api/(server)/health/route.ts:12— usesResponse.json({ status: 'ok' })packages/web/src/app/api/(server)/version/route.ts:16— usesResponse.json({ version: SOURCEBOT_VERSION })Why this matters
getOffersclient usesresponse.json()and happens to parse the body regardless ofContent-Type(packages/web/src/app/api/(client)/client.ts:310-322). It works in practice but is a coincidence of the WebfetchAPI, not because the response is technically correct.Content-Typebefore parsing. A client that seestext/plain;charset=UTF-8will refuse to parse the body, even though the body is valid JSON. This makes the public API endpoint less interoperable than every other public endpoint in the same product.docs/api-reference/sourcebot-public.openapi.json) as returningapplication/json. The actual response violates the documented contract.Proposed fix
In
packages/web/src/app/api/(server)/offers/route.ts, change the return value fromto
or, equivalently, replace the whole return with
which sets
Content-Type: application/jsonautomatically and is the pattern used by the other public routes. The body is unchanged, so no client is broken.Acceptance criteria
curl -I https://your-sourcebot-instance.com/api/offersshowscontent-type: application/json(orapplication/json; charset=utf-8).yarn workspace @sourcebot/web test --runstill returns 1154/1154 tests pass.application/json).Risks
None. The body is unchanged; only the response header is corrected. Strict clients that were already failing to parse will start working; clients that were lenient enough to parse the body (the in-app
getOffersis one) continue to work.