# attachment_upload

Upload Files (presigned / inline / fetch)

## Beschreibung

Uploads files and returns per file an `att_…` attachment_id plus
a `file_url` (short link, viewable for 15 min). Pass either form
to `chat_send_message` / `task_save` / `task_comment_save` /
`email_send` (`attachment_ids`) or `drive_file_upload`
(`attachment_id`). Up to 10 files per call;
transports may be mixed per file.

**Whenever you can run curl, use the presigned transport —
everything else is slow and token-hungry.**

1. PRESIGNED (preferred, ≤ 1 GB): pass exact `byte_size`
   and base64 MD5 `checksum`:
     openssl dgst -md5 -binary <file> | openssl base64
   You get `upload_url` + `headers` + a ready `curl_command`;
   PUT the raw bytes (2xx = done). `status` is
   "awaiting_upload" until then — consumers reject the handle
   with upload_incomplete before your PUT succeeded.

2. INLINE (≤ 1 MB decoded): pass `base64_bytes`
   (RFC 4648, no data-URI prefix). ONLY for clients that cannot
   run shell commands, or truly tiny text files. DO NOT
   hand-encode base64 — use a shell:
     openssl base64 -A -in <file>    # portable
     base64 -i <file>                # macOS

3. FETCH (≤ 50 MB): pass `source_url` — the server
   downloads the http(s) resource (25 s timeout, private hosts
   refused). `filename`/`mime_type` optional here (derived from
   the URL / response Content-Type).

Finalize (attach/store) within 24 h — uploads never attached to
anything are purged after that.

## Zugriff

Zugriffsprofil: `basic`. Module: `none`.
Die Freigabe im Profil ersetzt keine Account-, Workspace- oder Rollenberechtigung.

## Parameter

| Name | Typ | Pflicht | Beschreibung |
| --- | --- | --- | --- |
| `account_id` | string | Ja | Account prefix ID (acct_…). |
| `files` | array | Ja | Up to 10 files. Each item uses EXACTLY ONE transport: byte_size+checksum (presigned, preferred) \| base64_bytes (inline) \| source_url (fetch). |

## Eingabeschema

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "properties": {
    "account_id": {
      "type": "string",
      "description": "Account prefix ID (acct_…)."
    },
    "files": {
      "type": "array",
      "minItems": 1,
      "maxItems": 10,
      "description": "Up to 10 files. Each item uses EXACTLY ONE transport: byte_size+checksum (presigned, preferred) | base64_bytes (inline) | source_url (fetch).",
      "items": {
        "type": "object",
        "properties": {
          "filename": {
            "type": "string",
            "description": "No path components. Required for presigned/inline; optional for source_url (derived from the URL path)."
          },
          "mime_type": {
            "type": "string",
            "description": "Required for presigned/inline; optional for source_url (falls back to the response Content-Type)."
          },
          "byte_size": {
            "type": "integer",
            "minimum": 1,
            "maximum": 1073741824,
            "description": "PRESIGNED: exact file size in bytes; storage rejects a PUT of any other size."
          },
          "checksum": {
            "type": "string",
            "description": "PRESIGNED: base64 MD5 (24 chars ending '=='): openssl dgst -md5 -binary <file> | openssl base64"
          },
          "base64_bytes": {
            "type": "string",
            "description": "INLINE: RFC 4648 base64, whitespace tolerated, no data-URI prefix. Max 1 MB decoded."
          },
          "source_url": {
            "type": "string",
            "description": "FETCH: http(s) URL fetched server-side. Max 50 MB, 25 s timeout, private/internal hosts refused."
          }
        }
      }
    }
  },
  "required": [
    "account_id",
    "files"
  ],
  "type": "object"
}
```

## Ausgabeschema

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "properties": {
    "files": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "attachment_id": {
            "type": "string",
            "description": "Blob prefix id (att_…) — one of the two handle forms."
          },
          "filename": {
            "type": "string"
          },
          "transport": {
            "type": "string",
            "enum": [
              "presigned",
              "inline",
              "fetch"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "awaiting_upload",
              "stored"
            ],
            "description": "presigned entries stay awaiting_upload until your PUT succeeds."
          },
          "file_url": {
            "type": "string",
            "format": "uri",
            "description": "Short view link (/f/att_…) — the other handle form; browser-openable for 15 min."
          },
          "file_url_expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "mime_type": {
            "type": "string"
          },
          "byte_size": {
            "type": "integer"
          },
          "upload_url": {
            "type": "string",
            "format": "uri",
            "description": "presigned only: PUT target."
          },
          "headers": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "presigned only: send these exact headers with the PUT."
          },
          "curl_command": {
            "type": "string",
            "description": "presigned only: ready-to-run PUT."
          },
          "upload_url_expires_at": {
            "type": "string",
            "format": "date-time",
            "description": "presigned only."
          }
        },
        "required": [
          "attachment_id",
          "filename",
          "transport",
          "status",
          "file_url",
          "file_url_expires_at",
          "mime_type",
          "byte_size"
        ]
      }
    }
  },
  "required": [
    "files"
  ],
  "type": "object"
}
```
