Docs

API REFERENCE

Upload a public file

On this page
POST/api/upload

Upload one file of at most 4 MiB (4,194,304 bytes) to public Supabase Storage. Authenticate with exactly one x-api-key or Authorization: Bearer credential for an existing Recoup account; authentication precedes multipart parsing. The uploader is derived from that account and recorded as uploaded_by metadata, never a client-supplied account override.

The returned URL is public: anyone with it can read the file without authentication. Use only content intended for public access. This replaces the public upload destination; it does not move private user-files, release-pack source audio, or existing files. Previously issued Arweave URLs remain readable; no new Arweave write is part of this operation.

Send multipart/form-data with exactly one file field and let your HTTP client set the multipart boundary. Supported MIME types are image/jpeg, image/png, image/gif, image/webp, application/pdf, text/csv, text/markdown, text/x-markdown, text/plain, application/json, audio/mpeg, audio/wav, audio/x-m4a, audio/webm. The server checks binary signatures, UTF-8 text and JSON syntax as appropriate; a filename extension or MIME declaration alone does not establish a valid file. HTML, SVG, executable files and unspecified binary types are not supported. The streamed request body is limited to 4,259,840 bytes (4 MiB plus 64 KiB); the file itself remains limited to 4 MiB.

Authentication

x-api-key in header

bearerAuth bearer

Request

cURL
curl --request POST \
  --url 'https://api.recoupable.dev/api/upload' \
  --header 'x-api-key: YOUR_API_KEY' \
  --form 'file=@YOUR_FILE_PATH'

Try it

Fill in the fields, send the request from your browser, and read the live response. The curl below updates as you type.

Kept in this browser tab only and cleared when it closes.

cURL for this request
curl --request POST \
  --url 'https://api.recoupable.dev/api/upload' \
  --header 'Content-Type: multipart/form-data' \
  --data '{
  "file": "string"
}'

Run this one from the terminal with the curl above.

Request body required

multipart/form-data

filestringrequired

Exactly one nonempty file, at most 4 MiB (4,194,304 bytes). The MIME type must be supported; filename extensions do not override validation.

format: binary

Responses

200The file was stored in public Supabase Storage. The returned URL does not require authentication to read.

application/json

successbooleanrequired
fileNamestringrequired

Name of the uploaded file.

fileTypestring · enumrequired

Accepted MIME type of the uploaded file.

Values: "image/jpeg", "image/png", "image/gif", "image/webp", "application/pdf", "text/csv", "text/markdown", "text/x-markdown", "text/plain", "application/json", "audio/mpeg", "audio/wav", "audio/x-m4a", "audio/webm"

fileSizeintegerrequired

Uploaded file size in bytes.

minimum: 1 · maximum: 4194304

urlstringrequired

Public Supabase Storage URL. Anyone with this URL can read the object without authentication; this is not a signed private URL.

format: uri

200 example
{
  "success": true,
  "fileName": "cover.png",
  "fileType": "image/png",
  "fileSize": 1024,
  "url": "https://exampleproject.supabase.co/storage/v1/object/public/public-uploads/example/cover.png"
}

400Invalid multipart request (including a non-multipart Content-Type), missing file, empty file, or unsupported form fields. Submit exactly one nonempty file field.

application/json

successbooleanrequired
errorstringrequired

Human-readable error. Storage-provider details are not returned.

401Missing, invalid or conflicting credentials, or no existing account for the credential. Authentication is checked before multipart parsing.

application/json

successbooleanrequired
errorstringrequired

Human-readable error. Storage-provider details are not returned.

413The file exceeds 4 MiB (4,194,304 bytes), or the full streamed request exceeds 4,259,840 bytes (4 MiB plus 64 KiB of multipart overhead).

application/json

successbooleanrequired
errorstringrequired

Human-readable error. Storage-provider details are not returned.

415Unsupported file MIME type, mismatched binary signature, invalid UTF-8 text, or invalid JSON.

application/json

successbooleanrequired
errorstringrequired

Human-readable error. Storage-provider details are not returned.

500Upload failed. The response does not expose storage-provider details.

application/json

successbooleanrequired
errorstringrequired

Human-readable error. Storage-provider details are not returned.

Full specification

Download the OpenAPI file for complete schemas, constraints, and examples.

Download uploads.json
View operation source
json
{
  "operationId": "uploadPublicFile",
  "summary": "Upload a public file",
  "description": "Upload one file of at most 4 MiB (4,194,304 bytes) to public Supabase Storage. Authenticate with exactly one x-api-key or Authorization: Bearer credential for an existing Recoup account; authentication precedes multipart parsing. The uploader is derived from that account and recorded as uploaded_by metadata, never a client-supplied account override.\n\nThe returned URL is public: anyone with it can read the file without authentication. Use only content intended for public access. This replaces the public upload destination; it does not move private user-files, release-pack source audio, or existing files. Previously issued Arweave URLs remain readable; no new Arweave write is part of this operation.\n\nSend multipart/form-data with exactly one file field and let your HTTP client set the multipart boundary. Supported MIME types are `image/jpeg`, `image/png`, `image/gif`, `image/webp`, `application/pdf`, `text/csv`, `text/markdown`, `text/x-markdown`, `text/plain`, `application/json`, `audio/mpeg`, `audio/wav`, `audio/x-m4a`, `audio/webm`. The server checks binary signatures, UTF-8 text and JSON syntax as appropriate; a filename extension or MIME declaration alone does not establish a valid file. HTML, SVG, executable files and unspecified binary types are not supported. The streamed request body is limited to 4,259,840 bytes (4 MiB plus 64 KiB); the file itself remains limited to 4 MiB.",
  "security": [
    {
      "apiKeyAuth": []
    },
    {
      "bearerAuth": []
    }
  ],
  "requestBody": {
    "required": true,
    "content": {
      "multipart/form-data": {
        "schema": {
          "$ref": "#/components/schemas/PublicUploadRequest"
        }
      }
    }
  },
  "responses": {
    "200": {
      "description": "The file was stored in public Supabase Storage. The returned URL does not require authentication to read.",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/PublicUploadResponse"
          },
          "example": {
            "success": true,
            "fileName": "cover.png",
            "fileType": "image/png",
            "fileSize": 1024,
            "url": "https://exampleproject.supabase.co/storage/v1/object/public/public-uploads/example/cover.png"
          }
        }
      }
    },
    "400": {
      "description": "Invalid multipart request (including a non-multipart Content-Type), missing file, empty file, or unsupported form fields. Submit exactly one nonempty file field.",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/PublicUploadError"
          }
        }
      }
    },
    "401": {
      "description": "Missing, invalid or conflicting credentials, or no existing account for the credential. Authentication is checked before multipart parsing.",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/PublicUploadError"
          }
        }
      }
    },
    "413": {
      "description": "The file exceeds 4 MiB (4,194,304 bytes), or the full streamed request exceeds 4,259,840 bytes (4 MiB plus 64 KiB of multipart overhead).",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/PublicUploadError"
          }
        }
      }
    },
    "415": {
      "description": "Unsupported file MIME type, mismatched binary signature, invalid UTF-8 text, or invalid JSON.",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/PublicUploadError"
          }
        }
      }
    },
    "500": {
      "description": "Upload failed. The response does not expose storage-provider details.",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/PublicUploadError"
          }
        }
      }
    }
  }
}

Use this endpoint for files intended to be publicly accessible, including public profile images and attachments that may be shared by URL. It accepts one file up to 4 MiB (4,194,304 bytes) and returns its name, MIME type, byte size and URL.

Send a file

Send exactly one credential: an API key or an existing Bearer token. The uploader comes from the authenticated account; do not include account_id, uploaded_by or another ownership override. This route requires an existing account.

bash
curl --request POST 'https://api.recoupable.dev/api/upload' \
  --header "x-api-key: $RECOUP_API_KEY" \
  --form 'file=@cover.png;type=image/png'

Let your HTTP client set Content-Type and its multipart boundary. Send exactly one file part and no other form fields. The streamed request body is limited to 4,259,840 bytes (4 MiB plus 64 KiB of multipart overhead); the 4 MiB limit applies to the file itself.

Supported types are JPEG, PNG, GIF, WebP, PDF, CSV, Markdown, plain text, JSON, MP3, WAV, M4A and audio WebM. The OpenAPI reference lists the exact accepted MIME types. HTML, SVG, executables and unspecified binary types are not supported. Server signature, UTF-8 and JSON checks still apply even if a browser accepts the file locally.

Existing files and private media

New uploads use Supabase Storage, not Arweave. Previously issued Arweave links remain readable; this endpoint does not migrate or delete existing objects. Private Files in user-files and release-pack source audio continue to use their separate private-storage workflows.

The public response includes success, fileName, fileType, fileSize and url. A handled error includes success: false and error. A failed request does not establish whether a previous upload succeeded; do not treat this as an idempotent replacement API.