Use your own S3 bucket

Audy can read source files from your S3 bucket and write results back to it, so there's nothing to upload to Audy and nothing to download afterwards. You set this up once: create an IAM role in your AWS account that Audy can use, and tell Audy its ARN. It takes about five minutes.

How it works

The role you create trusts Audy's AWS account, but only when Audy presents an external ID that is unique to your Audy account. Audy always presents the external ID of the account whose job it is running. So your role can only ever be used for your jobs, and another Audy customer can't reach your bucket by typing its name into a job, even if they know it.

1. Get your values

Ask Audy what your role needs to trust:

curl -s https://audy.dev/v1/storage/aws \
  -H "X-API-Key: $AUDY_API_KEY"

The response contains everything the next step needs:

{
  "role_arn": null,
  "external_id": "acct_3f9c…",          // your Audy account id
  "trust_account_id": "123456789012",   // Audy's AWS account
  "trust_policy": { … },                // the exact trust policy, ready to paste
  "docs": "https://audy.dev/byo-s3.html"
}

Use the values from your response rather than copying numbers from this page. The external ID is different for every Audy account.

2. Create the role

Save the trust_policy from step 1 as trust.json. It looks like this:

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": { "AWS": "arn:aws:iam::<trust_account_id>:root" },
    "Action": "sts:AssumeRole",
    "Condition": { "StringEquals": { "sts:ExternalId": "<external_id>" } }
  }]
}

Then decide what the role may touch. Scope it to the prefixes you'll actually use, not the whole bucket. Save this as access.json:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AudyReadsSources",
      "Effect": "Allow",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::<your-bucket>/raw/*"
    },
    {
      "Sid": "AudyWritesResults",
      "Effect": "Allow",
      "Action": ["s3:PutObject", "s3:AbortMultipartUpload"],
      "Resource": "arn:aws:s3:::<your-bucket>/clean/*"
    }
  ]
}

Create the role and attach the policy:

aws iam create-role --role-name audy-access \
  --assume-role-policy-document file://trust.json

aws iam put-role-policy --role-name audy-access \
  --policy-name audy-bucket-access \
  --policy-document file://access.json

Note the Arn in the create-role output, e.g. arn:aws:iam::111122223333:role/audy-access.

Prefer infrastructure as code?

The same role in Terraform:

variable "audy_trust_account_id" {}   # trust_account_id from step 1
variable "audy_external_id" {}        # external_id from step 1

resource "aws_iam_role" "audy" {
  name = "audy-access"
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect    = "Allow"
      Principal = { AWS = "arn:aws:iam::${var.audy_trust_account_id}:root" }
      Action    = "sts:AssumeRole"
      Condition = { StringEquals = { "sts:ExternalId" = var.audy_external_id } }
    }]
  })
}

resource "aws_iam_role_policy" "audy" {
  role = aws_iam_role.audy.id
  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      { Effect = "Allow", Action = "s3:GetObject",
        Resource = "arn:aws:s3:::my-media/raw/*" },
      { Effect = "Allow", Action = ["s3:PutObject", "s3:AbortMultipartUpload"],
        Resource = "arn:aws:s3:::my-media/clean/*" },
    ]
  })
}

output "audy_role_arn" { value = aws_iam_role.audy.arn }

3. Register the role with Audy

curl -s -X PUT https://audy.dev/v1/storage/aws \
  -H "X-API-Key: $AUDY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"role_arn": "arn:aws:iam::111122223333:role/audy-access"}'

Audy checks the role before saving it. It assumes the role with a session that can't touch any data, so a mistake in the trust policy shows up here rather than in your first job. A 422 role_not_assumable means the trust policy doesn't match: check the account and external ID against step 1. The response includes AWS's own error code.

4. Run a job against your bucket

curl -s https://audy.dev/v1/jobs \
  -H "X-API-Key: $AUDY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"source": {"type": "s3", "uri": "s3://my-media/raw/episode-41.wav"},
       "output": {"type": "s3", "uri": "s3://my-media/clean/episode-41.wav"},
       "preset": "cleanup-v1"}'

The source and the output can be in different buckets, or one of them can be an Audy upload. A job that only reads from your bucket and leaves the output unset returns its result through Audy's short-lived scratch storage instead.

The cleaned file carries its provenance as object metadata: x-amz-meta-audy-input-format, x-amz-meta-audy-output-format and x-amz-meta-audy-lossy-source.

Prefer to watch it happen? examples/byo_s3_walkthrough.sh runs these four steps against a bucket of yours, pausing before each one, and --cleanup removes everything it created.

Good to know

If something goes wrong

You seeWhereWhat it means
s3_role_not_configured POST /v1/jobs (409), or a failed job No role is registered. Do step 3. A job fails with this if the role was removed after the job was queued.
role_not_assumable PUT /v1/storage/aws (422) The trust policy doesn't let Audy in. Compare the principal account and sts:ExternalId with step 1.
s3_role_not_assumable a failed job The role worked when you registered it and doesn't now. It was deleted or its trust policy changed.
source_unreadable a failed job The role could be assumed but can't read that object. Check the key exists and the role's s3:GetObject resource covers it (and KMS, if encrypted).
output_unwritable a failed job Same, for writing: s3:PutObject on the output key, and kms:GenerateDataKey if the bucket uses a KMS key.

Stuck? Write to hello@audy.dev with the job ID and the error code. We can see which step failed, but not your data.