> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sutro.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# Getting a Results Download URL

> Materialize a cached unified results artifact in R2 and return presigned GET/HEAD URLs

<Warning>Using the API directly is not recommended for most users. Instead, we recommend using the [Python
SDK](/python-sdk/setup).</Warning>

This endpoint creates (or reuses) **one unified results artifact** for a job in object storage (R2) and returns **presigned URLs** you can use to download it.

This route is built for **large results** and “real download tooling”:

* Use **`urls.head`** to fetch metadata (`Content-Length`, `ETag`) without downloading the file.
* Use **`urls.get`** to download the artifact, including **HTTP Range** requests for resumable downloads.

<Note>
  Once you have the presigned URLs, **do not** send your Sutro `Authorization` header to R2.
  The presigned URL already contains the credentials.
</Note>

## Path Parameters

<ParamField path="job_id" type="string" required>
  The job\_id returned when you submitted the batch inference job.
</ParamField>

## Query Parameters

<ParamField query="format" type="enum" default="parquet">
  The artifact format.

  **Currently supported values:**

  * `parquet` (only)

  Any other value returns **400**.
</ParamField>

<ParamField query="include_inputs" type="boolean" default="false">
  Whether to include the input prompts as columns in the unified artifact.
</ParamField>

<ParamField query="include_cumulative_logprobs" type="boolean" default="false">
  Whether to include cumulative log probabilities in the unified artifact.
</ParamField>

<ParamField query="expires_in_seconds" type="integer" default="3600">
  TTL for the returned presigned URLs.

  * Minimum: 1
  * Maximum: 604800 (7 days)
</ParamField>

## Headers

<ParamField header="Authorization" type="string" required>
  Your Sutro API key using Key authentication scheme.

  Format: `Key YOUR_API_KEY`

  Example: `Authorization: Key sk_live_abc123...`
</ParamField>

## Response

Returns a JSON payload that describes the artifact and provides method-specific presigned URLs.

<ResponseField name="job_id" type="string">
  The job ID you requested.
</ResponseField>

<ResponseField name="format" type="string">
  The artifact format (currently `parquet`).
</ResponseField>

<ResponseField name="include_inputs" type="boolean">
  Echoes whether inputs were included in the artifact.
</ResponseField>

<ResponseField name="include_cumulative_logprobs" type="boolean">
  Echoes whether cumulative logprobs were included in the artifact.
</ResponseField>

<ResponseField name="expires_in_seconds" type="integer">
  TTL (in seconds) for the returned presigned URLs.
</ResponseField>

<ResponseField name="artifact" type="object">
  Metadata describing the stored object (bucket/key/filename/size).
</ResponseField>

<ResponseField name="urls" type="object">
  Presigned URLs:

  * `urls.get` — use with **GET** (supports `Range` requests)
  * `urls.head` — use with **HEAD** (metadata only)
</ResponseField>

<ResponseExample>
  ```json Success Response theme={null}
  {
    "job_id": "batch_job_12345",
    "format": "parquet",
    "include_inputs": true,
    "include_cumulative_logprobs": false,
    "expires_in_seconds": 3600,
    "artifact": {
      "bucket": "sutro-data",
      "key": "jobs/user_abc/batch_job_12345/results/sutro-results~batch_job_12345~inputs=1~logprobs=0.parquet",
      "filename": "sutro-results~batch_job_12345~inputs=1~logprobs=0.parquet",
      "size_bytes": 987654321
    },
    "urls": {
      "get": "https://<r2-endpoint>/<bucket>/<key>?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=...&X-Amz-Signature=...",
      "head": "https://<r2-endpoint>/<bucket>/<key>?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=...&X-Amz-Signature=..."
    }
  }
  ```
</ResponseExample>

## Download behavior

### HEAD (metadata)

Use `urls.head` with the **HEAD** method to read headers like:

* `Content-Length` — total bytes
* `ETag` — object hash identifier (useful to detect changes)

### GET (download)

Use `urls.get` with **GET** to download:

* Supports `Range: bytes=...` for partial reads
* Enables resumable downloads (append remaining bytes)

<Warning>
  Treat presigned URLs like credentials. Anyone with the URL can download until it expires.
</Warning>

## Code Examples

<CodeGroup>
  ```python Python (requests) - Create URLs, HEAD metadata, resumable GET theme={null}
  import os
  import requests

  API_KEY = os.environ["SUTRO_API_KEY"]
  JOB_ID = "batch_job_12345"

  # 1) Ask Sutro for presigned URLs
  meta = requests.get(
      f"https://api.sutro.sh/jobs/{JOB_ID}/results-url",
      headers={"Authorization": f"Key {API_KEY}"},
      params={
          "format": "parquet",
          "include_inputs": True,
          "include_cumulative_logprobs": False,
          "expires_in_seconds": 3600,
      },
  ).json()

  get_url = meta["urls"]["get"]
  head_url = meta["urls"]["head"]
  filename = meta["artifact"]["filename"]

  # 2) HEAD for size + etag (no download)
  head = requests.head(head_url)
  head.raise_for_status()
  size_bytes = int(head.headers["Content-Length"])
  etag = head.headers.get("ETag")
  print("size_bytes:", size_bytes)
  print("etag:", etag)

  # 3) Resumable download using Range
  out_path = filename
  already = os.path.getsize(out_path) if os.path.exists(out_path) else 0

  headers = {}
  if already > 0:
      headers["Range"] = f"bytes={already}-"

  with requests.get(get_url, headers=headers, stream=True) as r:
      r.raise_for_status()
      mode = "ab" if already > 0 else "wb"
      with open(out_path, mode) as f:
          for chunk in r.iter_content(chunk_size=8 * 1024 * 1024):
              if chunk:
                  f.write(chunk)

  print("downloaded:", out_path)
  ```

  ```javascript Node.js (fetch) - Create URLs + download to disk theme={null}
  import { createWriteStream } from "node:fs";
  import { Readable } from "node:stream";
  import { pipeline } from "node:stream/promises";

  const jobId = "batch_job_12345";
  const apiKey = process.env.SUTRO_API_KEY;

  // 1) Ask Sutro for presigned URLs
  const metaRes = await fetch(
    `https://api.sutro.sh/jobs/${jobId}/results-url?format=parquet&include_inputs=true`,
    {
      headers: { Authorization: `Key ${apiKey}` },
    }
  );

  if (!metaRes.ok) {
    throw new Error(`Failed: ${metaRes.status} ${await metaRes.text()}`);
  }

  const meta = await metaRes.json();
  const { get: getUrl, head: headUrl } = meta.urls;
  const filename = meta.artifact.filename;

  // 2) HEAD for metadata
  const headRes = await fetch(headUrl, { method: "HEAD" });
  console.log("content-length:", headRes.headers.get("content-length"));
  console.log("etag:", headRes.headers.get("etag"));

  // 3) Download (stream to disk)
  const dlRes = await fetch(getUrl);
  if (!dlRes.ok) throw new Error(`Download failed: ${dlRes.status}`);

  await pipeline(Readable.fromWeb(dlRes.body), createWriteStream(filename));
  console.log("saved:", filename);
  ```

  ```bash cURL - Create URLs, HEAD metadata, Range + resume theme={null}
  # 1) Get the presigned URLs
  RESP="$(curl -s \
    -H "Authorization: Key YOUR_SUTRO_API_KEY" \
    "https://api.sutro.sh/jobs/batch_job_12345/results-url?format=parquet&include_inputs=true&expires_in_seconds=3600")"

  GET_URL="$(echo "$RESP" | jq -r '.urls.get')"
  HEAD_URL="$(echo "$RESP" | jq -r '.urls.head')"
  FILENAME="$(echo "$RESP" | jq -r '.artifact.filename')"

  # 2) HEAD (metadata only)
  curl -sI "$HEAD_URL"

  # 3) Download
  curl -L "$GET_URL" -o "$FILENAME"

  # 4) Resume an interrupted download
  curl -L -C - "$GET_URL" -o "$FILENAME"

  # 5) Download the first 1 MiB (Range request)
  curl -L -H "Range: bytes=0-1048575" "$GET_URL" -o first_1MiB.bin
  ```
</CodeGroup>

## Notes

* Only `format=parquet` is supported on this route today.
