Deliver Large Agent Artifacts With Signed URLs, Not Giant JSON Responses

typescript dev.to

An agent finishes a task and produces a 180 MB archive. Returning it inside the task JSON makes the API slow, retries expensive, and browser failure states unclear.

Keep task metadata small. Deliver large artifacts through a separate, expiring download contract.

The API shape

type Artifact = {
  id: string;
  taskId: string;
  filename: string;
  contentType: string;
  sizeBytes: number;
  sha256: string;
  state: "building" | "ready" | "expired" | "deleted";
};

type DownloadGrant = {
  url: string;
  expiresAt: string;
  artifactId: string;
  sha256: string;
};
Enter fullscreen mode Exit fullscreen mode

The task endpoint returns artifact metadata. A separate authorized endpoint creates a short-lived download grant only when the artifact is ready and the current user may access that task.

GET  /tasks/:id                 -> metadata
POST /artifacts/:id/downloads   -> signed grant
GET  signed object URL          -> bytes
Enter fullscreen mode Exit fullscreen mode

Bind the grant

A signed URL should bind at least the object key and expiration. Depending on the storage provider, also bind the response filename and content type. Never let a client submit an arbitrary object key for the server to sign.

Before issuing the grant:

  1. load the artifact by ID;
  2. authorize through its task and tenant;
  3. require state === "ready";
  4. sign the server-owned object key;
  5. return the expected digest.

The digest is not authorization. It lets clients or downstream systems detect corruption after download.

Make browser states explicit

type DownloadState =
  | { kind: "idle" }
  | { kind: "requesting-grant" }
  | { kind: "downloading"; received: number; total?: number }
  | { kind: "verifying" }
  | { kind: "expired" }
  | { kind: "failed"; retryable: boolean }
  | { kind: "complete" };
Enter fullscreen mode Exit fullscreen mode

If a grant expires, request a new grant after authorization. Do not blindly retry the same URL. If the artifact itself is expired or deleted, show that terminal state instead of an endless retry button.

Test the complete path

Use a disposable object store and assert:

  • another tenant cannot obtain a grant;
  • changing the object path invalidates the signature;
  • an expired grant fails while a newly issued grant works;
  • downloaded bytes match sha256;
  • deleting task access prevents future grants even if metadata was cached.

A signed URL reduces load on your application servers, but it is still a bearer capability until expiration. Keep lifetimes short, avoid logging full query strings, and do not confuse “unguessable” with “revocable.”

This separation keeps the control plane small and the data plane efficient—the task API describes what exists, while storage delivers the bytes.

Source: dev.to

arrow_back Back to Tutorials