Mint a view token for the caller's workspace
Returns a short-lived token that authenticates getAttachment, which cannot read an Authorization header because a browser sends none on an <img src>. The token carries the CALLER'S own reach — their active workspace and which of its teams they may read — and is never chosen by the client. One token covers every attachment they can already reach, so a page of markdown needs one call rather than one per image. It grants nothing beyond what the caller could open through the issues and comments those files hang on: a team-scoped team's attachments stay unreachable to someone who is not on that team. Append it to a stored attachment URL as `t` at render time. Attachment URLs are persisted inside issue and comment markdown, so they cannot themselves carry anything that expires; that is the whole reason the token is separate from the URL. Valid for 5 minutes. Re-mint rather than caching past `expires` — an expired token is refused as a 404, indistinguishable from a missing file. 501 when the signer is not configured, which fails closed: no unsigned URL is ever served in its place.
View as MarkdownAuthorization
bearer In: header
Response Body
application/json
application/json
application/json
application/json
curl -X GET "https://example.com/v1/attachments/token"{ "token": "string", "expires": "2019-08-24T14:15:22Z"}Upload a file (multipart, "file" field, ≤15MB)
multipart/form-data upload to R2. Any linked issue (directly via issueId, or via the target comment's issue) must belong to the caller's org (else 404). 400 when the file is missing or larger than ~15MB; 501 when storage is not configured. Returns 200 (not 201).
Fetch an attachment (presigned redirect, token-authenticated)
302-redirects to a presigned R2 URL valid for 60 seconds. Registered outside the authenticated group because an <img src> cannot send a bearer header — but NOT open. The caller must present a `t` view token from getAttachmentToken whose scope reaches the file: the same workspace, and either an org-wide team or one the bearer belongs to. The attachment's owner is found through its parent: its issue, or the issue its comment sits on. A bearer token is NOT an alternative here, and sending one has no effect: this route runs outside the authentication middleware, so there are no verified claims for it to read. An API caller fetches a view token first and appends it — one way in, verified in one place. Everything else is a 404, never a 403 — a wrong workspace, another team's file, an expired or forged token, no credentials at all, a deleted attachment, and an orphan whose parent is gone all answer identically, so the response never confirms that an id names a real file somewhere. 501 when storage is not configured. Limited per caller IP (EAS-77): a caller that sends too many requests gets 429 with a Retry-After header. The limit is generous, sized for a page that loads every image on an issue at once. If the rate-limit store is unavailable the request is served without a limit.