# Storage API integration quickstart

Use REST V2 for new storage integrations where an equivalent exists. The [REST reference](filesfm_api_v2.md) also covers V1 capabilities that do not have V2 equivalents, including restore, version history, ZIPs, and thumbnails.

## Authentication and request format

Base URL: `https://api.files.fm`. V2 actions use **POST form fields**, not a JSON request body. Use `user` and `user_access_token` from a dedicated integration account. File uploads use multipart form data. MCP Bearer and DVS JSON examples are separate contracts.

## Create, upload, list, and download

```bash
# Examples contain placeholders. These commands create and upload data.
curl --fail-with-body --silent --show-error --max-time 30 \
  'https://api.files.fm/api_v2/create_folder' \
  --data-urlencode 'user=YOUR_USER' \
  --data-urlencode 'user_access_token=YOUR_API_TOKEN' \
  --data-urlencode 'folder_name=Integration test' --data-urlencode 'access_type=PRIVATE'
```

Retain the returned `folder_hash` and capability keys. Hashes and keys are opaque strings. A private folder is an appropriate default for business data.

```bash
curl --fail-with-body --silent --show-error --max-time 120 \
  'https://api.files.fm/api_v2/upload_file' \
  -F 'user=YOUR_USER' -F 'user_access_token=YOUR_API_TOKEN' \
  -F 'folder_hash=RETURNED_FOLDER_HASH' -F 'file=@local-file.txt'
```

Retain the returned `file_hash`. Upload one file per request; account limits apply. List with `get_folder_content` using the folder hash and form auth. The result is an array, not a DVS `items/total` envelope. This V2 listing has no documented pagination parameters; do not invent `page` or `limit` support.

```bash
curl --fail-with-body --silent --show-error --max-time 30 \
  'https://api.files.fm/api_v2/download_file' \
  --data-urlencode 'user=YOUR_USER' --data-urlencode 'user_access_token=YOUR_API_TOKEN' \
  --data-urlencode 'file_hash=RETURNED_FILE_HASH' --output downloaded-file.txt
```

Download returns raw file bytes. Verify the HTTP result before treating the output as a successful file.

## JavaScript on a trusted server

```js
async function storage(action, params) {
  const body = new URLSearchParams({ ...params, user: process.env.FILESFM_USER,
    user_access_token: process.env.FILESFM_API_TOKEN });
  const response = await fetch(`https://api.files.fm/api_v2/${action}`, {
    method: 'POST', body, signal: AbortSignal.timeout(30000)
  });
  if (!response.ok) throw new Error(`Storage HTTP ${response.status}`);
  return response.json();
}
// Creates a private folder; run only when authorized.
const folder = await storage('create_folder', { folder_name: 'Integration test', access_type: 'PRIVATE' });
const items = await storage('get_folder_content', { folder_hash: folder.folder_hash });
```

For multipart upload use `FormData` with `file` as a Blob and do not manually set its Content-Type boundary. Use `response.arrayBuffer()` for download instead of `.json()`.

## PHP 7.4+

```php
<?php
$params = ['user' => getenv('FILESFM_USER'), 'user_access_token' => getenv('FILESFM_API_TOKEN'),
    'folder_hash' => getenv('FILESFM_FOLDER_HASH')];
$curl = curl_init('https://api.files.fm/api_v2/get_folder_content');
curl_setopt_array($curl, [CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POSTFIELDS => http_build_query($params), CURLOPT_CONNECTTIMEOUT => 10, CURLOPT_TIMEOUT => 30]);
$body = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);
if ($body === false || $status !== 200) { throw new RuntimeException('Storage request failed: ' . $status); }
$items = json_decode($body, true, 512, JSON_THROW_ON_ERROR);
```

## Errors, limits, and retries

V2 failures may have an empty body despite the JSON Content-Type. Some permission and not-found conditions use `405`, not `403/404`. Check the endpoint's documented statuses; legacy scripts can return plain text or endpoint-specific error objects.

No universal REST quota is published. Anonymous V1 folder listing is separately throttled and delayed; authenticated V2 listing does not inherit that anonymous interface. Observe actual `429` and retry guidance. Retry reads with bounded backoff; reconcile create/upload outcomes before retrying writes. No general idempotency-key contract is documented.
