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

# Private S3 images

> Generate alt text for images stored in a private Amazon S3 bucket using presigned URLs.

The AltTextLab API requires a publicly accessible image URL. Images stored in a private S3 bucket cannot be accessed directly — but you can grant temporary, scoped access using an [S3 presigned URL](https://docs.aws.amazon.com/AmazonS3/latest/userguide/ShareObjectPreSignedURL.html).

A presigned URL embeds your credentials and an expiry time into the URL itself. You generate it server-side, pass it to the API, and it becomes invalid once the signature expires — no permanent exposure, no bucket policy changes required.

## How it works

1. Your server generates a presigned URL for the S3 object, valid for a short window (30–60 seconds is usually enough).
2. You send the presigned URL to the AltTextLab API as `imageUrl`.
3. The API fetches the image, generates alt text, and returns the result.
4. The presigned URL expires automatically.

Your bucket stays private throughout.

## Step 1: Generate a presigned URL

Use the AWS SDK for your language to sign a `GetObject` request. Set the expiry short enough that the URL cannot be reused — 60 seconds works for most cases.

<CodeGroup>
  ```javascript Node.js theme={null}
  import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3';
  import { getSignedUrl } from '@aws-sdk/s3-request-presigner';

  const client = new S3Client({ region: 'us-east-1' });

  const command = new GetObjectCommand({
    Bucket: 'your-private-bucket',
    Key: 'path/to/image.jpg',
  });

  const signedUrl = await getSignedUrl(client, command, { expiresIn: 60 });
  ```

  ```python Python theme={null}
  import boto3
  from botocore.config import Config

  s3 = boto3.client(
      's3',
      region_name='us-east-1',
      config=Config(signature_version='s3v4'),
  )

  signed_url = s3.generate_presigned_url(
      ClientMethod='get_object',
      Params={
          'Bucket': 'your-private-bucket',
          'Key': 'path/to/image.jpg',
      },
      ExpiresIn=60,
  )
  ```

  ```php PHP theme={null}
  require 'vendor/autoload.php';

  use Aws\S3\S3Client;

  $client = new S3Client([
      'version' => 'latest',
      'region'  => 'us-east-1',
  ]);

  $cmd = $client->getCommand('GetObject', [
      'Bucket' => 'your-private-bucket',
      'Key'    => 'path/to/image.jpg',
  ]);

  $request = $client->createPresignedRequest($cmd, '+60 seconds');
  $signedUrl = (string) $request->getUri();
  ```
</CodeGroup>

<Note>
  Your AWS credentials need `s3:GetObject` permission on the bucket or key. The credentials used to sign the URL are never exposed — only the signature is embedded in the URL.
</Note>

## Step 2: Pass the presigned URL to the API

Use the signed URL as `imageUrl` in your generation request. Everything else works the same as with a public URL.

<CodeGroup>
  ```javascript Node.js theme={null}
  const response = await fetch('https://app.alttextlab.com/api/v1/alt-text/generate', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'x-api-key': 'YOUR_API_KEY',
    },
    body: JSON.stringify({
      imageUrl: signedUrl,
      lang: 'en',
      style: 'neutral',
    }),
  });

  const data = await response.json();
  console.log(data.result);
  ```

  ```python Python theme={null}
  import requests

  resp = requests.post(
      'https://app.alttextlab.com/api/v1/alt-text/generate',
      headers={
          'Content-Type': 'application/json',
          'x-api-key': 'YOUR_API_KEY',
      },
      json={
          'imageUrl': signed_url,
          'lang': 'en',
          'style': 'neutral',
      },
  )

  data = resp.json()
  print(data['result'])
  ```

  ```php PHP theme={null}
  $payload = json_encode([
      'imageUrl' => $signedUrl,
      'lang'     => 'en',
      'style'    => 'neutral',
  ]);

  $ch = curl_init('https://app.alttextlab.com/api/v1/alt-text/generate');
  curl_setopt_array($ch, [
      CURLOPT_RETURNTRANSFER => true,
      CURLOPT_POST           => true,
      CURLOPT_POSTFIELDS     => $payload,
      CURLOPT_HTTPHEADER     => [
          'Content-Type: application/json',
          'x-api-key: YOUR_API_KEY',
      ],
  ]);

  $response = curl_exec($ch);
  curl_close($ch);

  $data = json_decode($response, true);
  echo $data['result'];
  ```
</CodeGroup>

## Complete example

<CodeGroup>
  ```javascript Node.js theme={null}
  import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3';
  import { getSignedUrl } from '@aws-sdk/s3-request-presigner';

  async function generateAltText(bucket, key) {
    const client = new S3Client({ region: 'us-east-1' });
    const command = new GetObjectCommand({ Bucket: bucket, Key: key });
    const signedUrl = await getSignedUrl(client, command, { expiresIn: 60 });

    const response = await fetch('https://app.alttextlab.com/api/v1/alt-text/generate', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'x-api-key': process.env.ALTTEXTLAB_API_KEY,
      },
      body: JSON.stringify({ imageUrl: signedUrl, lang: 'en' }),
    });

    const data = await response.json();
    return data.result;
  }
  ```

  ```python Python theme={null}
  import os
  import boto3
  import requests
  from botocore.config import Config

  def generate_alt_text(bucket: str, key: str) -> str:
      s3 = boto3.client(
          's3',
          region_name='us-east-1',
          config=Config(signature_version='s3v4'),
      )
      signed_url = s3.generate_presigned_url(
          ClientMethod='get_object',
          Params={'Bucket': bucket, 'Key': key},
          ExpiresIn=60,
      )

      resp = requests.post(
          'https://app.alttextlab.com/api/v1/alt-text/generate',
          headers={
              'Content-Type': 'application/json',
              'x-api-key': os.environ['ALTTEXTLAB_API_KEY'],
          },
          json={'imageUrl': signed_url, 'lang': 'en'},
      )
      return resp.json()['result']
  ```
</CodeGroup>

[//]: # "## Choosing an expiry time"

[//]: #

[//]: # "| Scenario | Recommended expiry |"

[//]: # "|---|---|"

[//]: # "| Single API call | 60 seconds |"

[//]: # "| Batch processing with retries | 5 minutes |"

[//]: # "| Async pipeline with queuing | 15 minutes |"

[//]: #

[//]: # "Keep expiry as short as your pipeline allows. A presigned URL grants read access to that object for anyone who holds it during the validity window."

## AWS documentation

* [Sharing objects with presigned URLs](https://docs.aws.amazon.com/AmazonS3/latest/userguide/ShareObjectPreSignedURL.html)
* [Presigned URL limitations and restrictions](https://docs.aws.amazon.com/AmazonS3/latest/userguide/using-presigned-url.html)
* [AWS SDK for JavaScript — `@aws-sdk/s3-request-presigner`](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/Package/-aws-sdk-s3-request-presigner/)
* [Boto3 — `generate_presigned_url`](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3/client/generate_presigned_url.html)
* [AWS SDK for PHP — Presigned requests](https://docs.aws.amazon.com/sdk-for-php/v3/developer-guide/s3-presigned-url.html)
