> For the complete documentation index, see [llms.txt](https://academy.shade.inc/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://academy.shade.inc/developers/get-started/integrating-with-shade.md).

# Integrating with Shade

Pick the right Shade interface for your integration, authenticate, and build a complete upload-to-metadata workflow.

Shade has several developer interfaces. Most integrations use two or three of them together: for example, the S3 API to upload files, a webhook to learn when they're processed, and the REST API to write metadata back.

## Choose an interface

| Interface                                                                     | Use it to                                                                                   | Base URL                        | Credential                             |
| ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | ------------------------------- | -------------------------------------- |
| [REST API](/developers/using-the-api/using-the-api.md)                        | Read and change workspaces, drives, assets, metadata, collections, sharing, and permissions | `https://api.shade.inc`         | API key (`sk_...`)                     |
| [S3 API](/developers/guides/using-shades-s3-api.md)                           | Upload, download, list, copy, and delete files with any S3 client                           | `https://s3.shade.inc`          | S3 access key                          |
| File system API                                                               | Upload from your own app at full speed, and read a drive's change history                   | `https://fs.shade.inc`          | ShadeFS token (expires after one hour) |
| [Webhooks](/developers/guides/webhooks.md)                                    | Get notified when files, comments, collections, links, or members change                    | Your endpoint                   | Svix signing secret                    |
| [Automation webhooks](/developers/guides/webhooks.md#webhooks-in-automations) | Start a Shade automation from another service, or call an external API from one             | Webhook URL from the automation | Bearer token you set on the trigger    |
| [MCP server](/developers/guides/using-the-shade-mcp-server.md)                | Let AI agents search, browse, and act on a workspace                                        | `https://mcp.shade.inc/mcp`     | OAuth sign-in                          |

{% hint style="info" %}
Every credential acts as the user who created it. Your integration can see and change exactly what that user can in the Shade app, no more and no less.
{% endhint %}

## Understand the data model

* A **workspace** is your organization. It holds members, billing, and drives.
* A **drive** holds files and folders. Most calls are drive-scoped and need a `drive_id`. With the S3 API, the drive ID is the bucket name.
* An **asset** is a file in a drive. In the REST API, paths start with the drive ID, for example `/{drive_id}/Project/Ep 1/clip.mov`. The S3 API and the MCP server use paths relative to the drive root instead, such as `Project/Ep 1/clip.mov`.
* **Metadata attributes** are the custom fields defined on a drive. Values are set per asset.

Find your IDs with two calls:

```bash
# Workspaces you belong to
curl -H "Authorization: $SHADE_API_KEY" https://api.shade.inc/workspaces

# Drives in a workspace
curl -H "Authorization: $SHADE_API_KEY" https://api.shade.inc/workspaces/$WORKSPACE_ID/drives
```

## Authenticate

### API keys

Create an API key in the app under **Settings → Integrate → API keys** (see [Intro](/developers/using-the-api/using-the-api.md#api-key)). Send it as the raw value of the `Authorization` header, with no `Bearer` prefix:

```bash
curl -H "Authorization: sk_..." https://api.shade.inc/workspaces
```

### ShadeFS tokens

The file system API at `fs.shade.inc` takes a short-lived token instead of your API key. Exchange your API key for one, then send it as `Authorization: Bearer <token>`:

```bash
curl -H "Authorization: $SHADE_API_KEY" \
  https://api.shade.inc/workspaces/drives/$DRIVE_ID/shade-fs-token
```

The key's owner needs edit access to the drive. Tokens expire after one hour, so read the JWT's `exp` claim and fetch a new token before it runs out.

### S3 keys

S3 access keys are separate from API keys. Create them under **Settings → Integrate → S3 API keys**. An S3 key can reach every drive its owner has edit access to. See [Using Shade's S3 API](/developers/guides/using-shades-s3-api.md).

### Use a dedicated account for production

For a long-running integration, create keys on a dedicated Shade user rather than a person's account. Give that user only the drive roles the integration needs, and set an expiration on its keys so you rotate them regularly. This way your integration keeps working when someone leaves, and its access is easy to audit.

## Example: tag files automatically when they're uploaded

This example listens for uploads, reads each new asset, and writes a metadata value back. It uses a webhook, the REST API, and an existing metadata attribute on the drive.

**1. Find the metadata attribute to write.** List the drive's attributes and note the `id` of the one you want, for example a text field called "Source".

```bash
curl -H "Authorization: $SHADE_API_KEY" \
  https://api.shade.inc/workspaces/drives/$DRIVE_ID/metadata
```

**2. Subscribe to `asset.uploaded`.** In the webhooks dashboard, add your endpoint URL and subscribe it to `asset.uploaded`. See [Webhooks](/developers/guides/webhooks.md) to open the dashboard and copy the signing secret.

**3. Handle the event.** Verify the signature, then use `resource.id` (the asset ID) and `drive.id` from the event to update the asset.

```python
import os

import requests
from flask import Flask, request
from svix.webhooks import Webhook, WebhookVerificationError

SHADE_API = "https://api.shade.inc"
API_KEY = os.environ["SHADE_API_KEY"]
WEBHOOK_SECRET = os.environ["SHADE_WEBHOOK_SECRET"]
SOURCE_ATTRIBUTE_ID = os.environ["SOURCE_ATTRIBUTE_ID"]

app = Flask(__name__)


@app.post("/shade-webhook")
def shade_webhook():
    try:
        event = Webhook(WEBHOOK_SECRET).verify(request.get_data(), dict(request.headers))
    except WebhookVerificationError:
        return "", 400

    if event["event_type"] != "asset.uploaded":
        return "", 204

    asset_id = event["resource"]["id"]
    drive_id = event["drive"]["id"]

    asset = requests.get(
        f"{SHADE_API}/assets/{asset_id}",
        headers={"Authorization": API_KEY},
        params={"drive_id": drive_id},
        timeout=30,
    )
    asset.raise_for_status()

    requests.put(
        f"{SHADE_API}/assets/{asset_id}/metadata/{SOURCE_ATTRIBUTE_ID}/value",
        headers={"Authorization": API_KEY},
        json={"drive_id": drive_id, "metadata_attribute_value": "Ingest pipeline"},
        timeout=30,
    ).raise_for_status()

    return "", 204
```

Respond with a 2xx status quickly. If your endpoint fails, Svix retries delivery, so the same event can arrive more than once. Use the `svix-id` header to skip events you've already handled.

## Wait for processing before using AI data

Uploading a file starts background processing: previews, video proxies, transcription, and AI indexing. These finish at different times. If your integration needs one of them, wait for its event instead of polling:

| You need                     | Wait for                  |
| ---------------------------- | ------------------------- |
| Thumbnails or preview images | `preview.completed`       |
| A playable video proxy       | `proxy.completed`         |
| A transcript                 | `transcription.completed` |

See the full list of events in [Webhooks](/developers/guides/webhooks.md).

## Keep an external system in sync

Webhooks are best for reacting to individual changes. To mirror a whole drive's file tree into a database, spreadsheet, or another storage system, use the drive's change history instead. You can replay it from any point, so a missed event never leaves you out of sync. See [Syncing & reacting to file changes](/developers/guides/synchronizing-file-state.md).

## Production checklist

* Store API keys, S3 keys, and webhook secrets in a secret manager, never in source control.
* Set key expirations and rotate them before they expire.
* Refresh ShadeFS tokens before their `exp` time. Reusing an expired token is the most common upload failure.
* Verify every webhook signature against the raw request body.
* Make webhook handlers idempotent, keyed on `svix-id`.
* Treat `403` responses as a permissions problem: check the key owner's role on the drive.

## Next steps

* [Uploading to Shade](/developers/guides/uploading-to-shade.md): choose the right upload method for your use case.
* [Using the Shade MCP server](/developers/guides/using-the-shade-mcp-server.md): connect AI agents to your workspace.
* [API reference](https://academy.shade.inc/developers/workspaces): every REST endpoint.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://academy.shade.inc/developers/get-started/integrating-with-shade.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `build a script that syncs our docs to a CMS` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
