🎉 Tickety V3 has now been released! Read more →
API

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-dashboard

A 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

StatusMeaning
302Redirect to the image. This is the success case
401Missing or invalid Authorization token
404No such upload for your server
429Rate 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

ScopeLimit
Per IP15 requests every 10 seconds
Per server60 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.

On this page