neosourceDocs
Search docs

Mint short-TTL signed URLs for referenced attachments

POST/api/attachments/sign

signAttachments

Each entry also carries the DERIVED renditions that exist right now (ADR 0108 §§4/6), established by an exact-key probe rather than assumed. **The whole async design rests on this telling the truth**: `<picture>` has no error fallback, so an advertised rendition whose bytes are absent is a broken image, not a slow one — and a rendition that exists but goes unadvertised is only a missed optimisation.

Requires authentication using a bearer token or a session cookie — see tokens and scopes.

curl

curl -X POST 'https://neosource.dev/api/attachments/sign' \
  -H 'Authorization: Bearer $NEOSOURCE_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{"ids":[]}'

fetch

fetch("https://neosource.dev/api/attachments/sign", {
  method: "POST",
  headers: {
    Authorization: "Bearer $NEOSOURCE_TOKEN",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"ids":[]}),
});

Request bodyrequired

application/json

SignAttachmentsRequest

object

Body of `POST /api/attachments/sign` — the attachment ids a client-rendered body references, to be resolved to short-TTL signed URLs.

idsrequired

array

Attachment ids (the `{id}` in a `/a/{id}` content URL). Ids the actor cannot read are silently omitted from the response — never an error, never a leak of existence.

items

string

Responses

200Signed URLs for the readable ids

application/json

SignAttachmentsResponse

object

Response of `POST /api/attachments/sign`. Only ids the actor may read appear; the SPA matches by `id` and leaves unresolved refs untouched.

urlsrequired

array

items

SignedAttachmentUrl

object

One resolved signed URL.

idrequired

string

The attachment id this URL is for (echoes back the requested id).

sourcesrequired

array

Derived renditions that EXIST at the moment this was asked, best-first. **Empty is the normal state** (ADR 0108 §5): every variant is absent for about a second after upload, indefinitely if no worker picks it up, and forever for every attachment that predates ADR 0108. Always present as `[]` rather than absent, so the client never branches on `undefined`.

urlrequired

string

The CANONICAL signed content URL (`https://{content_origin}/a/{id}?exp=…&kid=…&sig=…`) — unchanged, and still the `<img src>` fallback, so an un-migrated client keeps working byte for byte.

403Forbidden — one of: forbidden, needs_scope

application/json

one of
  • ErrorForbidden
  • NeedsScopeError

    object

    `403` body returned when listing private repos but the linked identity lacks the required provider scope. The SPA turns this into an incremental-authorization prompt (Tier 2) that calls the `/elevate` OAuth endpoint with this `scope`.

    errorrequired

    string

    Always `needs_scope` — this body exists to carry the extra fields that kind needs.

    "needs_scope"

    scoperequired

    string

    The provider scope to request via elevation (e.g. `"repo"`).

Standard errors

Bodies documented once for the whole API — see standard errors.

  • 400Bad Request — one of: invalid_input
  • 401Authentication required
  • 404Not Found — one of: not_found
  • 429Rate limited — retry after the `Retry-After` header
  • 500Internal server error
  • 503Service temporarily unavailable / at capacity — retry after the `Retry-After` header
  • 504Gateway timeout — the request exceeded the server's handling budget

Schemas

Referenced above. Listed here rather than expanded inline, so the same definition is not repeated at every level.

AttachmentSource

object

One DERIVED rendition of an attachment that exists right now (ADR 0108 §3). A client emits a `<source>` from this list and from nowhere else: `<picture>` selects on `type`/`media` only and has NO error fallback, so a `<source>` pointing at bytes that are not there renders a broken image rather than falling back to the `<img>`.

mimerequired

string

The rendition's media type — the `<source type=…>` selection hint.

urlrequired

string

Absolute signed content-origin URL for this rendition.

widthrequired

integerint32

The rendition's LONG-EDGE bound in px (the key's `w{W}`), NOT the rendered image's true width: a portrait image is narrower than this. Informational in v1 — the client selects on `mime`, and there is no `srcset`/`sizes` (a `srcset` width descriptor here would mis-select).