Uploads
Fetch images that users uploaded as answers to ticket forms and applications.
Overview
When a user answers an upload question on a ticket form or an application, Tickety stores that image privately. It is not publicly readable, and there is no permanent link to it.
Instead, HTTP events that contain an upload answer include an upload object with a URL you can
call using your existing HTTP events token. That call returns the image.
This applies to ticket form questions with the Image Upload style, and to application questions of type UPLOAD.
Endpoint
GET https://tickety.top/api/uploads/{scope}/{id}/{filename}Prop
Type
You never need to construct this URL yourself. Every event that carries an upload gives you the
full URL in upload.url.
Authentication
Send your HTTP events token in the Authorization header. This is the same token your webhook
endpoint receives, found under Advanced Settings in your dashboard settings.
Authorization: your-token-from-dashboardA Bearer your-token-from-dashboard form is accepted too.
Your server is identified by the token alone. Nothing in the URL names a server, so a token can only ever reach uploads belonging to the server it was issued for.
Response
By default the endpoint responds with a 302 redirect to a short-lived link to the image. Most
HTTP clients follow this automatically, so you get the image bytes back.
const response = await fetch(upload.url, {
headers: { Authorization: process.env.TICKETY_TOKEN },
});
const imageBuffer = Buffer.from(await response.arrayBuffer());Pass ?format=json if you would rather have the link itself, for example to hand it to another
service:
const response = await fetch(`${upload.url}?format=json`, {
headers: { Authorization: process.env.TICKETY_TOKEN },
});
const { url } = await response.json();{
"scope": "applications",
"id": "APP-5678",
"filename": "a1b2c3d4.webp",
"url": "https://..."
}The returned link expires after a few minutes and is meant to be used immediately. Do not store
it or embed it in a page. Store the upload object instead and request a fresh link whenever
you need the image again.
Status codes
| Status | Meaning |
|---|---|
302 | Redirect to the image. This is the success case |
401 | Missing or invalid Authorization token |
404 | No such upload for your server |
429 | Rate limited |
404 is returned for anything that is not yours, so the endpoint never reveals whether some
other server's ticket or file exists.
Upload object
Events that include an upload answer carry this object on the question:
Prop
Type
For an upload question, answer is set to the same URL as upload.url so that a consumer which
only reads answer still gets something usable. answerRaw is null, because there is no raw
text answer to report.
Example
app.post("/tickety-webhook", async (req, res) => {
const { type, payload } = req.body;
if (type === "application.submit") {
for (const question of payload.questions) {
if (!question.upload) continue;
const response = await fetch(question.upload.url, {
headers: { Authorization: process.env.TICKETY_TOKEN },
});
if (!response.ok) {
console.error(`Could not fetch ${question.upload.filename}`);
continue;
}
await saveAttachment({
applicationId: payload.applicationId,
filename: question.upload.filename,
body: Buffer.from(await response.arrayBuffer()),
});
}
}
res.status(200).send("OK");
});Rate limits
| Scope | Limit |
|---|---|
| Per IP | 15 requests every 10 seconds |
| Per server | 60 requests every 60 seconds |
Fetch uploads as you process each event rather than in one large batch. A single event carries at
most a few uploads, so these limits are well clear of normal use. If you are backfilling, spread
the requests out and handle 429 by retrying later.