Skip to content

fix(api/offers): return Content-Type: application/json #1595

Description

@Harsh23Kashyap

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions