# Files and attachments

> List attachment metadata, inspect one attachment, and issue short-lived download URLs without exposing storage paths.

URL: https://www.agencytitan.com/help/guides/files-and-attachments/

The attachments API separates metadata reads from file access. List and get operations never expose an internal storage path or download credential. When you need the bytes, request a short-lived bearer URL for one attachment.

## List attachments for a resource

[`GET /v1/attachments`](/docs/tag/attachments/GET/v1/attachments) requires both `entity_type` and `entity_id`:

```bash
curl "https://api.agencytitan.com/v1/attachments?entity_type=task&entity_id=11111111-1111-4111-8111-111111111111&sort=created_at&order=desc" \
  --header "Authorization: Bearer $AGENCYTITAN_API_KEY" \
  --header "Accept: application/json"
```

The operation supports the standard `limit`, `offset`, `sort`, and `order` parameters plus case-insensitive `search` over filename and content type. The current `entity_type` enum is published in the operation schema; use that enum instead of sending an arbitrary resource name.

Before returning metadata, AgencyTitan reads the parent resource through its normal public authorization path. If the caller cannot read the parent, it cannot enumerate the parent's attachments.

Each item contains:

- `id`, used by metadata and download-URL operations
- `filename`, `content_type`, and `size_bytes`
- `created_at` and nullable `created_by`
- `parent_entity.type` and `parent_entity.id`

## Get attachment metadata

Use [`GET /v1/attachments/{id}`](/docs/tag/attachments/GET/v1/attachments/{id}) when you already know the attachment ID. AgencyTitan re-evaluates read access to the parent before returning the same metadata shape. The response does not contain file bytes or a URL.

## Issue a download URL

[`POST /v1/attachments/{id}/download-url`](/docs/tag/attachments/POST/v1/attachments/{id}/download-url) mints a short-lived bearer credential. It uses POST because it issues a credential, although it does not change product data.

```bash
curl --request POST "https://api.agencytitan.com/v1/attachments/22222222-2222-4222-8222-222222222222/download-url" \
  --header "Authorization: Bearer $AGENCYTITAN_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{"expires_in": 300}'
```

`expires_in` is measured in seconds. It defaults to 300, with a minimum of 60 and a maximum of 3600.

The response uses the standard single-item envelope:

```json
{
  "data": {
    "url": "https://storage.example.com/signed/file",
    "expires_at": "2026-08-08T14:35:00.000Z",
    "filename": "kickoff-notes.pdf",
    "content_type": "application/pdf",
    "size_bytes": 24576
  }
}
```

Read access to the parent is checked again when the URL is issued. The returned URL is transferable and grants temporary access without the AgencyTitan authorization header, so treat it as a secret:

- Request it only when the download is ready to begin.
- Use the shortest practical TTL.
- Do not log, persist, cache, or send it to an untrusted client.
- Issue a new URL after `expires_at`; do not attempt to refresh or modify the signed URL.
- If parent access is revoked after issuance, assume the URL can remain usable until its TTL expires.

Attachment references in [`GET /v1/ticket-messages`](/docs/tag/ticket-messages/GET/v1/ticket-messages) contain metadata only. Pass the referenced attachment ID to the download-URL operation when the caller is ready to fetch it.
