APIMODELSAPIMODELS
Документация API

apimodels.app

Free · API key only

Files API

One endpoint to host your reference images, source videos, or audio at a public R2 URL — then drop the URL into any /v1 generation call (image-to-image, video reference, dubbing, lipsync). No need to run your own storage.

At a glance

Endpoint
POST /api/v1/files
Auth
Authorization: Bearer <API_KEY>
Accepted types
image/*, video/*, audio/*
Size limit
image/audio ≤ 10 MB, video ≤ 100 MB
Retention
Auto-deleted after 30 days
Pricing
Free

Which mode to use?

The two modes are dispatched by your request Content-Type. Use mode 1 for video or any large file.

Mode 1: Presigned PUT (recommended)

  • • Client uploads directly to R2 — bypasses Vercel’s ~4.5 MB request-body cap
  • • Required for video and any image > ~4 MB
  • • Two round-trips (request URL + PUT body)

Mode 2: multipart direct

  • • One curl, single round-trip
  • • Good for images / audio under ~4 MB
  • • Larger payloads get rejected by Vercel — use mode 1

Mode 1: Presigned PUT

Request

POST /api/v1/files
Authorization: Bearer <API_KEY>
Content-Type: application/json

{
  "filename":    "ref.mp4",
  "contentType": "video/mp4",
  "fileSize":    12345678
}
FieldRequiredTypeDescription
filenamerequiredstringOriginal file name (used to pick the stored extension)
contentTyperequiredstringMIME type, e.g. image/png, video/mp4, audio/mpeg
fileSizeoptionalnumberByte count; if present, size cap is checked up-front

Success response (200)

{
  "code": 200,
  "msg": "success",
  "data": {
    "uploadUrl": "https://<account>.r2.cloudflarestorage.com/apimodels/uploads/<userId>/1717...xxxx.png?X-Amz-Signature=...",
    "publicUrl": "https://r2.apimodels.app/uploads/<userId>/1717...xxxx.png",
    "expiresAt": "2026-05-30T09:30:00.000Z"
  }
}

uploadUrl is valid for 10 minutes; publicUrl is reachable as soon as the PUT completes and stays for 30 days.

Step 2: PUT the file body to R2

curl -X PUT "$UPLOAD_URL" \
  -H "Content-Type: video/mp4" \
  --data-binary @ref.mp4

⚠️ The PUT Content-Type must match the contentType you requested — otherwise R2 will reject the signature.

Full example (image)

cURL

# 1) Request a presigned PUT URL
RESP=$(curl -s -X POST https://api.apimodels.app/v1/files \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "filename": "ref.png",
    "contentType": "image/png",
    "fileSize": '"$(wc -c < ref.png)"'
  }')
UPLOAD_URL=$(echo "$RESP" | jq -r .data.uploadUrl)
PUBLIC_URL=$(echo "$RESP" | jq -r .data.publicUrl)

# 2) PUT the file body directly to R2 (no Vercel size limit)
curl -X PUT "$UPLOAD_URL" \
  -H "Content-Type: image/png" \
  --data-binary @ref.png

# 3) Use PUBLIC_URL in any /v1 generation endpoint
echo "Public URL: $PUBLIC_URL"

Python

import requests, os

API_KEY = os.environ["API_KEY"]
with open("ref.png", "rb") as f:
    data = f.read()

# 1) Request presigned URL
r = requests.post(
    "https://api.apimodels.app/v1/files",
    headers={"Authorization": f"Bearer {API_KEY}"},
    json={
        "filename": "ref.png",
        "contentType": "image/png",
        "fileSize": len(data),
    },
)
upload_url = r.json()["data"]["uploadUrl"]
public_url = r.json()["data"]["publicUrl"]

# 2) PUT directly to R2
requests.put(upload_url, data=data, headers={"Content-Type": "image/png"})

# 3) public_url is your reference for any generation call
print("Public URL:", public_url)

Node.js

import fs from 'node:fs/promises'

const API_KEY = process.env.API_KEY
const file = await fs.readFile('ref.png')

// 1) Request presigned URL
const r = await fetch('https://api.apimodels.app/v1/files', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    filename: 'ref.png',
    contentType: 'image/png',
    fileSize: file.length,
  }),
})
const { data: { uploadUrl, publicUrl } } = await r.json()

// 2) PUT to R2 directly
await fetch(uploadUrl, {
  method: 'PUT',
  headers: { 'Content-Type': 'image/png' },
  body: file,
})

console.log('Public URL:', publicUrl)

Mode 2: multipart direct upload

Request

POST /api/v1/files
Authorization: Bearer <API_KEY>
Content-Type: multipart/form-data; boundary=...

(form field "file" with the raw bytes)
FieldRequiredTypeDescription
filerequiredbinaryForm file field; its Content-Type is read from the multipart part

Success response (200)

{
  "code": 200,
  "msg": "success",
  "data": {
    "publicUrl": "https://r2.apimodels.app/uploads/<userId>/1717...xxxx.png"
  }
}

Full example

cURL

curl -X POST https://api.apimodels.app/v1/files \
  -H "Authorization: Bearer $API_KEY" \
  -F "file=@ref.png"

Python

import requests, os

r = requests.post(
    "https://api.apimodels.app/v1/files",
    headers={"Authorization": f"Bearer {os.environ['API_KEY']}"},
    files={"file": open("ref.png", "rb")},
)
print(r.json()["data"]["publicUrl"])

Node.js

import fs from 'node:fs'

const form = new FormData()
form.append(
  'file',
  new Blob([fs.readFileSync('ref.png')], { type: 'image/png' }),
  'ref.png',
)

const r = await fetch('https://api.apimodels.app/v1/files', {
  method: 'POST',
  headers: { 'Authorization': `Bearer ${process.env.API_KEY}` },
  body: form,
})
console.log((await r.json()).data.publicUrl)

Chain with a generation endpoint

The returned publicUrl drops straight into the image_url / video_url / images[] field of any /v1 endpoint:

# 1) Upload your reference photo
PUBLIC_URL=$(curl -s -X POST https://api.apimodels.app/v1/files \
  -H "Authorization: Bearer $API_KEY" \
  -F "file=@photo.jpg" | jq -r .data.publicUrl)

# 2) Pass it into an image-to-image call
curl -X POST https://api.apimodels.app/v1/images/generations \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d "{
    \"model\": \"nanobanana2/gemini-3.1-flash-image-preview\",
    \"prompt\": \"Make it Studio-Ghibli-style\",
    \"image_url\": \"$PUBLIC_URL\"
  }"

Same pattern works for /v1/video/generations video_url, lipsync video_url, dubbing audio_url, and similar fields.

Error responses

Errors return a uniform { "code": <status>, "msg": "..." } body.

HTTPmsgWhen
400filename and contentType are requiredMissing required field in JSON mode
400Only image/, video/, or audio/ content types are acceptedContent-Type not in the allow-list
400File too large (max 100MB) / (max 10MB)Exceeds the per-type size cap
400No file provided (expected form field "file")multipart body missing the "file" field
400Invalid JSON bodyJSON body failed to parse
400Expected multipart/form-data or application/jsonUnrecognized request Content-Type
401Invalid or missing API keyMissing key, wrong key, or key is disabled
500Storage not configuredR2 env vars not configured (should not happen in prod)

Notes

  • Uploads are auto-deleted from R2 after 30 days — persist them to your own storage in that window if you need long-term access.
  • Object keys are uploads/<userId>/<timestamp>_<random>.<ext> for auditability.
  • publicUrl is publicly readable — don't upload anything sensitive.
  • Free with no rate limit today; per-key quotas will be added if abuse appears.