Enterprise pack

Programmatic API

Mint unique, branded upload links from your own server. Each upload is forwarded directly into your storage and announced via HMAC-signed webhook — agnostic to images, video, PDFs, or raw binary.

POST/api/v1/links

Overview

The programmatic API is a server-to-server surface for enterprise integrators — event platforms collecting photo uploads, legal firms ingesting PDFs, customer-portal vendors embedding upload links inside their own product. You call POST /api/v1/links with your custom metadata and branding; we hand back a public uploadUrlfor your end-user. When the file lands, it's piped straight into your S3 / R2 bucket and we POST your webhook with the same metadata you tagged the link with at creation time.

Base URL

text
https://fileaway.io/api/v1

Response envelope

All responses follow the same wrapper: a top-level success flag, the payload under data, and a meta object with requestId for support traceability.

200 OKjson
{
  "success": true,
  "data": { ... },
  "meta": {
    "timestamp": "2026-04-29T12:00:00.000Z",
    "requestId": "req-7c3f...",
    "version": "v1"
  }
}

Subscription gating

API access is part of the Business pack. Every /api/v1/* request re-checks entitlement at request time, so cancelling the pack revokes access on the next call without any key rotation.

QuotaEffect
apiAccessRequired to call any /api/v1/* route
maxApiKeysCap on concurrent active keys
maxLinksCap on total links per account
apiRateLimitPerMinuteSliding-window cap, per key
webhooksEnabledRequired to attach a webhook to a link
brandingEnabledRequired to attach branding to a link

Issuing API keys

Issue keys from the dashboard or via the management endpoint below. The plaintext key is returned once in data.plaintextKey — store it immediately. After that only the public keyPrefix is retrievable.

POST/api/management/api-keys
cURLbash
curl -X POST https://fileaway.io/api/management/api-keys \
  -H "Authorization: Bearer <your JWT>" \
  -H "Content-Type: application/json" \
  -d '{
    "label": "Production server",
    "scopes": ["links:read", "links:write", "uploads:read", "webhooks:write"]
  }'
201 Createdjson
{
  "success": true,
  "data": {
    "apiKey": {
      "_id": "65f0...",
      "label": "Production server",
      "keyPrefix": "fa_live_a1b2c3d4",
      "scopes": ["links:read", "links:write", "uploads:read", "webhooks:write"],
      "requestCount": 0,
      "createdAt": "2026-04-29T12:00:00.000Z"
    },
    "plaintextKey": "fa_live_a1b2c3d4_e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0"
  }
}

Available scopes: links:read, links:write, uploads:read, webhooks:write. Omit scopes to grant all four.

Revoke a key

DELETE/api/management/api-keys/{id}

Authentication

Send the plaintext key in the X-API-Keyheader. Bearer-token form also works for compatibility with HTTP clients that don't support custom headers.

http
X-API-Key: fa_live_a1b2c3d4_e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0

Key format

fa_<env>_<prefix>_<secret> where env is live or test, the prefix is 8 hex chars (safe to display), and the secret is 32 hex chars (only the SHA-256 is persisted on our side).

Listing uploads

GET/api/v1/links/{linkId}/uploads

Every file that came through the link, newest first. Returns metadata only — file bytes are never stored on our side, they live in your bucket under storageKey.

json
{
  "success": true,
  "data": [
    {
      "_id": "65f0c2...",
      "originalFilename": "IMG_8423.jpg",
      "storedFilename":   "IMG_8423.jpg",
      "mimeType": "image/jpeg",
      "sizeBytes": 4823100,
      "storageKey": "links/a1b2c3d4.../9f7e_IMG_8423.jpg",
      "destinationType": "S3",
      "createdAt": "2026-04-29T12:00:00.000Z"
    }
  ],
  "pagination": { "page": 1, "limit": 20, "total": 1, "totalPages": 1, "hasNext": false, "hasPrev": false }
}

Webhooks

When a file lands on a link with a configured webhook, we POST a JSON envelope to that URL. The signing scheme follows Stripe / GitHub conventions: HMAC-SHA256 over {timestamp}.{rawBody} with the secret you supplied at link-creation time.

Headers

http
X-Webhook-Id:        a4d2e8f0-...
X-Webhook-Event:     upload.created
X-Webhook-Timestamp: 1709300000
X-Webhook-Signature: 8d0f1a3c4b...

Payload

json
{
  "id": "a4d2e8f0-...",
  "event": "upload.created",
  "deliveredAt": "2026-04-29T12:00:00.000Z",
  "linkSlug": "a1b2c3d4...",
  "linkId":   "65f0c1...",
  "metadata": { "eventId": "evt_2026_acme_d1", "customerId": "cust_acme" },
  "data": {
    "uploadId":         "65f0c2...",
    "originalFilename": "IMG_8423.jpg",
    "storedFilename":   "IMG_8423.jpg",
    "mimeType":         "image/jpeg",
    "sizeBytes":        4823100,
    "storageKey":       "links/a1b2c3d4.../9f7e_IMG_8423.jpg",
    "destinationType":  "S3",
    "uploadedAt":       "2026-04-29T12:00:00.000Z"
  }
}

Verifying the signature (Node.js)

javascript
import crypto from "node:crypto";

export function verifyWebhook(req, secret) {
  const sig = req.headers["x-webhook-signature"];
  const ts  = req.headers["x-webhook-timestamp"];
  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${ts}.${req.rawBody}`)
    .digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(sig, "hex"),
    Buffer.from(expected, "hex")
  );
}

Retries

Failed deliveries are retried up to 3 times with exponential backoff (1s → 4s → 16s) and a 10s per-call timeout. Persistent failures are stored and visible in the dashboard's deliveries view.

Events

  • upload.created — file accepted and proxied to your bucket.
  • upload.failed — upload was rejected (bad MIME, oversize, password failure).
  • link.limit_reached — the link's maxUploads ceiling was hit.

Rate limits

Per-key sliding-window throttle. The cap is read from your pack's apiRateLimitPerMinute at request time, so an upgrade lifts the ceiling on the very next call. Every response carries:

http
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 287
X-RateLimit-Reset: 1709300060

Hitting the cap returns 429 RATE_LIMITED; respect X-RateLimit-Reset (unix seconds) before retrying.

Error codes

Errors use the same envelope as success responses but with success: false and a top-level error object containing code, message, and optional details.

CodeHTTPMeaning
API_KEY_MISSING401No X-API-Key / Authorization header
API_KEY_INVALID401Key not found or wrong secret
API_KEY_REVOKED401Key has been revoked
API_KEY_EXPIRED401Key is past its expiresAt
API_KEY_INSUFFICIENT_SCOPE403Key lacks the scope this route requires
API_ACCESS_NOT_SUBSCRIBED403Owner has no pack with apiAccess
FEATURE_NOT_AVAILABLE403Pack does not enable the requested feature
QUOTA_EXCEEDED403Pack quota would be exceeded
VALIDATION_ERROR400Request body failed schema validation
NOT_FOUND404Link or upload doesn't exist
RATE_LIMITED429Per-key rate limit hit; respect X-RateLimit-Reset

Ready to integrate?

Subscribe to the Business pack and issue your first key from the dashboard.