---
title: SDK artifacts
description: >-
  ArtifactManager for downloading job outputs from presigned S3 URLs as blob,
  text, or JSON
---

The `ArtifactManager` — reached through `ivy.artifacts` — lets you download the file-based outputs a completed job produces, such as PDB structures, SDF molecules, CSV reports, and JSON results. Reach for it after a job finishes, when the inline result isn't enough and you need the actual output file as a blob, text string, or parsed JSON. It works from the presigned S3 URLs the [Jobs](/docs/api/sdk-jobs) reference surfaces on a completed job, and pairs with the [Client](/docs/api/sdk-client) you construct first.

## SDK Artifacts Reference

> **Planned specification.** `@ivybiosciences/sdk` is not released, and there is no package to install. This page describes the SDK's planned interface. To call the platform today, use the [REST API](/docs/api/rest-jobs), the [ivybloom CLI](/docs/cli), or the [MCP server](/docs/cli/mcp).

The `ArtifactManager` handles downloading file-based job outputs from time-limited presigned S3 URLs. Supports binary, text, and JSON formats.

Access via `ivy.artifacts`.

---

## Overview

Completed jobs may produce downloadable artifacts stored in AWS S3:
- **PDB files** — Protein structures from ESMFold, DiffDock
- **SDF files** — Molecular structures from docking, fragment growing
- **CSV reports** — Tabular analysis results
- **JSON files** — Structured prediction outputs

Artifacts are accessed via time-limited presigned URLs that typically expire after 1 hour.

---

## `artifacts.getDownloadUrl(jobId)`

Get the presigned download URL for a completed job's artifacts.

```ts
async getDownloadUrl(jobId: string): Promise<string | null>
```

**Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `jobId` | `string` | The job identifier |

**Returns:** The presigned S3 URL, or `null` if the job has no downloadable artifacts (results are inline JSON only).

**Throws:** `IvyApiError` with `isNotFound` if the job doesn't exist.

### Examples

**TypeScript**

```ts
const url = await ivy.artifacts.getDownloadUrl("job_abc123")
if (url) {
  window.open(url) // Direct browser download
}
```

**Python**

```python
# The REST API has no job-output download operation. Read the job's artifact
# references, then download with the CLI: `ivybloom jobs download job_abc123`.
job = requests.get(f"{BASE_URL}/api/v1/jobs/job_abc123", headers=headers).json()
for artifact in job.get("output_artifacts", []):
    print(artifact["type"], artifact["uri"])
```

**React**

```tsx
import { useIvyClient } from "@ivybiosciences/sdk/react"
import { useState } from "react"

function DownloadArtifact({ jobId }: { jobId: string }) {
  const ivy = useIvyClient()
  const [downloading, setDownloading] = useState(false)

  const handleDownload = async () => {
    setDownloading(true)
    try {
      const url = await ivy.artifacts.getDownloadUrl(jobId)
      if (url) window.open(url)
    } finally {
      setDownloading(false)
    }
  }

  return (
    <button onClick={handleDownload} disabled={downloading}>
      {downloading ? "Loading..." : "Download"}
    </button>
  )
}
```

> **Note:** If the URL has expired (403 on download), call this method again for a fresh URL.

---

## `artifacts.download(presignedUrl)`

Download artifact content as a Blob. Best for binary files or creating client-side download links.

```ts
async download(presignedUrl: string): Promise<Blob>
```

**Throws:** `IvyApiError` if the download fails (e.g., URL expired — status 403).

### Examples

**TypeScript**

```ts
// Create a download link in the browser
const blob = await ivy.artifacts.download(presignedUrl)
const url = URL.createObjectURL(blob)
const a = document.createElement("a")
a.href = url
a.download = "structure.pdb"
a.click()
URL.revokeObjectURL(url)
```

**Python**

```python
# Download binary artifact to file
response = requests.get(presigned_url)
response.raise_for_status()

with open("structure.pdb", "wb") as f:
    f.write(response.content)

print(f"Downloaded {len(response.content)} bytes")
```

**React**

```tsx
import { useIvyClient } from "@ivybiosciences/sdk/react"
import { useState } from "react"

function BlobDownload({ presignedUrl, filename }: { presignedUrl: string; filename: string }) {
  const ivy = useIvyClient()
  const [downloading, setDownloading] = useState(false)

  const handleDownload = async () => {
    setDownloading(true)
    try {
      const blob = await ivy.artifacts.download(presignedUrl)
      const url = URL.createObjectURL(blob)
      const a = document.createElement("a")
      a.href = url
      a.download = filename
      a.click()
      URL.revokeObjectURL(url)
    } finally {
      setDownloading(false)
    }
  }

  return (
    <button onClick={handleDownload} disabled={downloading}>
      {downloading ? "Downloading..." : `Download ${filename}`}
    </button>
  )
}
```

---

## `artifacts.downloadText(presignedUrl)`

Download artifact as a UTF-8 text string. Suitable for PDB, SDF, CSV, FASTA formats.

```ts
async downloadText(presignedUrl: string): Promise<string>
```

**Throws:** `IvyApiError` if the download fails.

### Examples

**TypeScript**

```ts
const pdb = await ivy.artifacts.downloadText(presignedUrl)
const lines = pdb.split("\n")
const atoms = lines.filter(l => l.startsWith("ATOM"))
console.log(`${atoms.length} atoms in structure`)
```

**Python**

```python
response = requests.get(presigned_url)
response.raise_for_status()

pdb_text = response.text
lines = pdb_text.splitlines()
atoms = [l for l in lines if l.startswith("ATOM")]
print(f"{len(atoms)} atoms in structure")
```

**React**

```tsx
import { useIvyClient } from "@ivybiosciences/sdk/react"
import { useState } from "react"

function PdbViewer({ presignedUrl }: { presignedUrl: string }) {
  const ivy = useIvyClient()
  const [atomCount, setAtomCount] = useState<number | null>(null)

  const analyze = async () => {
    const pdb = await ivy.artifacts.downloadText(presignedUrl)
    const atoms = pdb.split("\n").filter((l) => l.startsWith("ATOM"))
    setAtomCount(atoms.length)
  }

  return (
    <div>
      <button onClick={analyze}>Analyze PDB</button>
      {atomCount !== null && <p>{atomCount} atoms in structure</p>}
    </div>
  )
}
```

---

## Parsing JSON artifacts

The SDK has no JSON helper. Download the file as text and parse it:

```ts
interface DockingResult {
  poses: Array<{ score: number; rmsd: number; pdb: string }>
  metadata: { protein_name: string; ligand_smiles: string }
}

const results = JSON.parse(await ivy.artifacts.downloadText(url)) as DockingResult
console.log(`Best pose score: ${results.poses[0].score}`)
```

---

## Workflow Example

**TypeScript**

```ts
// 1. Submit job and wait for completion
const result = await ivy.jobs.submitAndWait({
  toolName: "esmfold",
  parameters: { protein_sequence: "MKFLILLFNILCLFPVLAADNH" },
})

// 2. Get the download URL
if (result.presignedUrl) {
  // 3a. Download as text (PDB file)
  const pdb = await ivy.artifacts.downloadText(result.presignedUrl)
  console.log(pdb.substring(0, 100))
} else {
  // 3b. Results are inline JSON
  console.log(result.result)
}
```

**Python**

```python
import time
import requests

# 1. Submit job
job = requests.post(f"{BASE_URL}/api/v1/jobs", json={
    "tool_name": "esmfold",
    "tool_input_parameters": {"protein_sequence": "MKFLILLFNILCLFPVLAADNH"},
}, headers=headers).json()

# 2. Poll until complete
while True:
    status = requests.get(
        f"{BASE_URL}/api/v1/jobs/{job['id']}", headers=headers
    ).json()
    if status["status"] in ("COMPLETED", "FAILURE"):
        break
    time.sleep(3)

# 3. Download artifact
if status.get("presignedUrl"):
    pdb = requests.get(status["presignedUrl"]).text
    print(pdb[:100])
else:
    print(status["result"])
```

**React**

```tsx
import { useIvyJob } from "@ivybiosciences/sdk/react"
import { useIvyClient } from "@ivybiosciences/sdk/react"
import { useState } from "react"

function FoldAndDownload() {
  const ivy = useIvyClient()
  const [pdbPreview, setPdbPreview] = useState<string | null>(null)

  const { submit, status, isLoading } = useIvyJob({
    onComplete: async (result) => {
      if (result.presignedUrl) {
        const pdb = await ivy.artifacts.downloadText(result.presignedUrl)
        setPdbPreview(pdb.substring(0, 200))
      }
    },
  })

  return (
    <div>
      <button
        disabled={isLoading}
        onClick={() => submit("esmfold", { protein_sequence: "MKFLILLFNILCLFPVLAADNH" })}
      >
        {isLoading ? `${status?.phase ?? "Running"}...` : "Fold & Download"}
      </button>
      {pdbPreview && <pre>{pdbPreview}</pre>}
    </div>
  )
}
```

---

## S3 Key Format

Artifacts are stored with the following S3 key structure:

```
{user_id}/jobs/{job_id}/{artifact_type}/{filename}
```

Example:
```
user_abc123/jobs/job_def456/structures/predicted_structure.pdb
```

You don't need to know the key — the presigned URL handles access.
