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

# Custom S3 Write Destination

> Write task outputs directly to your own S3 bucket

By default, AudioShake serves task outputs from AudioShake-hosted storage with short-lived download links. With a **storage integration**, you can instead write outputs directly to a prefix in your own S3 bucket.

This is useful for keeping audio in your own storage, meeting data-residency requirements, or wiring outputs straight into an existing pipeline.

<Info>S3 write destinations are supported today. Google Cloud Storage and Azure Blob Storage are planned. Reading inputs from `s3://` URIs is not yet supported — continue providing inputs via `assetId` or a public HTTPS `url`.</Info>

## How it works

1. You create an IAM role in your AWS account that AudioShake can assume.
2. You register the integration in AudioShake — this returns an **External ID** unique to your integration.
3. You add the External ID to your role's trust policy.
4. You include `writeDestination` in your [`/tasks`](/api-reference/tasks/create) requests.

At runtime, AudioShake assumes your role via STS and uses those short-lived credentials to write your outputs. AudioShake never stores long-lived AWS keys for your account.

## Setup

### 1. Create an S3 bucket

In the AWS console, create the bucket AudioShake should write to (or use an existing one). Bucket names are globally unique — pick something like `acme-audioshake-outputs`.

### 2. Create an IAM role in your AWS account

In the AWS IAM console, create a new role — for example, `AudioshakeS3AccessRole`. The role needs two policies: a **trust policy** that lets AudioShake assume the role, and a **permissions policy** that grants write access to your bucket prefix.

**Trust policy.** On the first step of the role wizard ("Select trusted entity"), choose **Custom trust policy** — not "AWS account" — so you can trust AudioShake's specific role rather than its entire account. Paste the JSON below into the editor. The `Principal` is AudioShake's published service ARN — copy it verbatim. The External ID is a placeholder for now; you'll fill it in after registering the integration in step 4.

```json Trust policy theme={null}
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AudioshakeServicesS3Access",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::874456856160:role/AudioshakeServicesS3AccessRole"
      },
      "Action": "sts:AssumeRole",
      "Condition": {
        "StringEquals": {
          "sts:ExternalId": "<EXTERNAL_ID_PLACEHOLDER>"
        }
      }
    }
  ]
}
```

**Permissions policy.** Continue through the wizard. On the "Add permissions" step you can either skip it and add an inline policy after the role is created, or attach the policy now by clicking **Create policy**, switching to the JSON editor, and pasting the block below. Replace `<BUCKET_NAME>` and `<PREFIX>` with your bucket and the prefix you want AudioShake to write under (e.g. `outputs`):

```json Permissions policy theme={null}
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "WriteObjectsInAssignedBucket",
      "Effect": "Allow",
      "Action": [
        "s3:PutObject",
        "s3:AbortMultipartUpload"
      ],
      "Resource": "arn:aws:s3:::<BUCKET_NAME>/<PREFIX>/*"
    }
  ]
}
```

### 3. (Optional) Restrict writes via a bucket policy

For defense-in-depth, you can also add a bucket policy that allows only this role to write to the prefix. In the bucket's **Permissions** tab, set the bucket policy to:

```json Bucket policy theme={null}
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowWriterRolePutObject",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::<YOUR_ACCOUNT_ID>:role/AudioshakeS3AccessRole"
      },
      "Action": [
        "s3:PutObject",
        "s3:AbortMultipartUpload"
      ],
      "Resource": "arn:aws:s3:::<BUCKET_NAME>/<PREFIX>/*"
    }
  ]
}
```

### 4. Register the integration with AudioShake

You can register the integration through the Dashboard or via the API.

<Tabs>
  <Tab title="Dashboard">
    In the [AudioShake Dashboard](https://dashboard.audioshake.ai), go to **Settings > Storage Integrations** and click **Add storage integration**. Fill in:

    * **Type** — AWS S3
    * **Bucket / Storage name** — your bucket name (no `s3://` prefix or path)
    * **AWS region** — e.g. `us-east-1`
    * **Role ARN** — the ARN of the role you created in step 2

    After creating the integration, the Dashboard displays a generated **External ID**. Copy it — you'll need it in step 5.
  </Tab>

  <Tab title="API">
    Send a `POST` to `/storage-integrations`:

    ```json theme={null}
    {
      "type": "aws",
      "storageName": "<BUCKET_NAME>",
      "authConfig": {
        "roleArn": "arn:aws:iam::<YOUR_ACCOUNT_ID>:role/AudioshakeS3AccessRole",
        "region": "us-east-1"
      }
    }
    ```

    The response includes a generated `externalId`:

    ```json theme={null}
    {
      "id": "<storage-integration-id>",
      "clientId": "<client-id>",
      "type": "aws",
      "storageName": "<BUCKET_NAME>",
      "authConfig": {
        "roleArn": "arn:aws:iam::<YOUR_ACCOUNT_ID>:role/AudioshakeS3AccessRole",
        "region": "us-east-1",
        "externalId": "<audioshake-generated-id>"
      },
      "createdAt": "2026-04-21T23:44:21.346Z",
      "updatedAt": "2026-04-21T23:44:21.346Z"
    }
    ```

    Save the `externalId` — you'll need it in step 5.
  </Tab>
</Tabs>

### 5. Add the External ID to your trust policy

Back in the AWS IAM console, open your role's **Trust relationships** tab and replace `<EXTERNAL_ID_PLACEHOLDER>` with the External ID from step 4:

```json theme={null}
"Condition": {
  "StringEquals": {
    "sts:ExternalId": "<AUDIOSHAKE_GENERATED_EXTERNAL_ID>"
  }
}
```

Save the policy. The integration is now ready to use.

### 6. (Optional) Test the integration

Verify that AudioShake can write to your bucket by sending a `POST` to `/storage-integrations/test`:

```json theme={null}
{
  "writeDestination": "s3://<BUCKET_NAME>/<PREFIX>"
}
```

AudioShake looks up the matching integration, assumes your role, and writes a small test file at `<writeDestination>/.verify/<timestamp>_<uuid>.json`. A `200` response means the integration is working.

<Tip>The `writeDestination` you test must start with the prefix you granted in your permissions policy. If your policy's `Resource` is `arn:aws:s3:::my-bucket/outputs/*`, the verify object must land under `outputs/` — test with `"s3://my-bucket/outputs"`, not `"s3://my-bucket"`.</Tip>

## Using a custom write destination

Once the integration is active, include `writeDestination` in your [Create Task](/api-reference/tasks/create) request as an `s3://` URI under your registered bucket:

```json theme={null}
{
  "url": "https://example.com/song.wav",
  "writeDestination": "s3://acme-audioshake-outputs/outputs",
  "targets": [
    { "model": "vocals", "formats": ["wav"] },
    { "model": "drums", "formats": ["mp3"] }
  ]
}
```

<Warning>If you omit `writeDestination`, outputs are written to AudioShake-hosted storage by default — your custom integration is not used.</Warning>

### Where outputs land

AudioShake organizes outputs under your `writeDestination` by task and target:

```
s3://<bucket>/<prefix>/<taskId>/targets/<targetId>/output/<filename>
```

This keeps each task's outputs isolated and avoids accidental overwrites between tasks. The full S3 keys are returned on the Task object alongside the standard `output[].link` download URLs.

### Path rules

* `writeDestination` must begin with `s3://` followed by a bucket and a prefix.
* The bucket in `writeDestination` must match a registered storage integration.

## Security

* AudioShake assumes your role only at task runtime, with a session named `audioshake-task-<taskId>` for auditability in CloudTrail.
* Access is bounded by the permissions policy you attach to the role — AudioShake can only do what you grant.
* The External ID condition prevents the [confused-deputy problem](https://docs.aws.amazon.com/IAM/latest/UserGuide/confused-deputy.html) — without it, another AudioShake customer could not be tricked into assuming your role.
* You can revoke access immediately by removing the trust relationship, detaching the permissions policy, or deleting the role.

## Troubleshooting

| Symptom                                                   | Likely cause                                                                                                              |
| --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| Task is rejected on submit                                | The bucket in `writeDestination` doesn't match a registered storage integration                                           |
| Task fails with an `AssumeRole` error                     | Trust policy missing AudioShake's principal, or the External ID doesn't match                                             |
| Task creates but writes fail with access denied           | Permissions policy doesn't grant `s3:PutObject` on the `writeDestination` prefix, or the bucket policy restricts the role |
| Outputs land in AudioShake storage instead of your bucket | `writeDestination` was omitted from the `/tasks` request                                                                  |
