DA
Log ind Opret konto

API documentation

Create and manage syntransfer.ro transfers from your own applications, scripts and servers. Version 1.

Overview

  • Base URL: https://www.syntransfer.ro/api/v1 (always use the www address; other addresses redirect and the request body is lost).
  • Available on the Business and Corporate plans, including team members (each member uses their own keys).
  • Requests and responses are JSON (Content-Type: application/json), except chunk uploads, which send the raw bytes.
  • Sizes are in bytes, dates are ISO 8601 (for example 2026-11-13T18:33:40+02:00).
  • Your plan limits (size per transfer, transfers and volume per month, expiry) are the same as on the website.

Authentication

Create a key in Account → API (up to 5 keys per account). The full key is shown only once; we store only its hash. Send it with every request:

Authorization: Bearer syt_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

If your HTTP client cannot set the Authorization header, use X-API-Key: syt_... instead.

  • Allowed IPs. When you create a key you can restrict it to a list of IP addresses or networks (IPv4 and IPv6, for example 203.0.113.5, 198.51.100.0/24, up to 20 entries). Requests from other addresses get 403 ip_not_allowed.
  • Revoking. A revoked key stops working immediately.
  • Read-only accounts. If the account is suspended or the subscription has ended, only GET requests work (403 account_read_only for the rest).

Limits

LimitValue
Requests60 per minute per key
Chunk uploads600 per minute per key (counted separately)
Invalid keysmore than 30 requests with an invalid key in one minute block that IP for 15 minutes (429 ip_blocked)
Files per transfer100
Chunk sizeup to 64 MB (8 MB recommended)

Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (seconds). A 429 response also has Retry-After.

Errors

Errors use the HTTP status code and a JSON body. code is stable and meant for programs; message is for people.

{"error": {"code": "transfer_not_found", "message": "Transfer not found."}}
StatusCodeMeaning
400invalid_jsonThe body is not a JSON object.
400invalid_chunkThe chunk body does not match Content-Length (retry).
401unauthorizedMissing, invalid or revoked API key.
403plan_requiredThe account is not on Business or Corporate.
403ip_not_allowedThe key is restricted to other IP addresses.
403account_read_onlySuspended account or ended subscription.
403feature_not_in_planAn option (password, custom expiry, ...) is not included in your plan.
404transfer_not_found, file_not_found, not_foundUnknown transfer, file index or endpoint.
409transfer_not_pendingThe transfer is already finalized.
409upload_incompleteFinalize was called before every chunk arrived (the response lists the files).
411length_requiredChunk upload without Content-Length.
413transfer_too_largeThe transfer exceeds the plan size limit.
413chunk_too_large, upload_exceeds_declared_sizeThe chunk is over 64 MB, or the data exceeds the declared sizes.
422invalid_parameterA request field is missing or invalid (see message).
422checksum_mismatchX-Chunk-SHA256 does not match the received data (retry).
422invalid_e2ee_formatIn e2ee mode, chunk 0 of a file does not start with SYNE.
429rate_limited, ip_blockedToo many requests; wait Retry-After seconds.
429monthly_transfer_limit, monthly_volume_limitThe monthly plan limit is reached.
500storage_error, server_errorServer problem; retry later.

Upload flow

  1. POST /transfers declares the files (name, size, number of chunks). The transfer is created as pending.
  2. PUT /transfers/{id}/files/{index}/chunks/{n} uploads each chunk. Chunks can be sent in parallel and in any order; sending the same chunk again is safe.
  3. POST /transfers/{id}/finalize activates the transfer and sends the emails.
  4. Share the url from the response. In e2ee mode, append #key= and your key (see End-to-end encryption).

Unfinished transfers are deleted automatically after 2 days.

Account

GET/me

Account, plan, limits (null means unlimited), usage in the current month and the options included in the plan.

curl -H "Authorization: Bearer $KEY" https://www.syntransfer.ro/api/v1/me
{
  "id": 42, "name": "Jane Doe", "email": "jane@company.com",
  "plan": {"slug": "business", "name": "Business"},
  "team_member": false, "read_only": false,
  "limits": {"max_transfer_size": null, "monthly_transfers": null, "monthly_volume": null,
             "expiry_days": 30, "max_files_per_transfer": 100, "max_chunk_size": 67108864},
  "usage": {"transfers_this_month": 17, "volume_this_month": 268744704},
  "features": {"password_protection": true, "self_destroy": true, "preview": true,
               "custom_expiry": true, "scheduled_send": true, "max_downloads": true, "download_stats": true},
  "rate_limit": {"requests_per_minute": 60, "chunks_per_minute": 600}
}

List transfers

GET/transfers?status=all&page=1&per_page=50
ParameterDescription
statusall (default), active, expired or pending
page, per_pagepagination, up to 100 per page (default 50); newest first
{
  "data": [
    {"id": 1022, "token": "XoIpz6WLicyxE45l", "url": "https://www.syntransfer.ro/download.php?t=XoIpz6WLicyxE45l",
     "status": "active", "mode": "e2ee", "title": "Q3 report", "message": null, "recipient_email": null,
     "file_count": 4, "total_size": 31984467, "download_count": 2, "max_downloads": null,
     "password_protected": false, "self_destroy": false,
     "created_at": "2026-10-14T19:31:39+03:00", "expires_at": "2026-11-13T19:31:39+02:00", "scheduled_at": null}
  ],
  "page": 1, "per_page": 50, "total": 1
}

Create a transfer

POST/transfers
FieldDescription
files *1 to 100 objects: name, size (original size in bytes), chunks (how many chunks you will send), optional mime_type
modee2ee (default: you encrypt the files), or an unencrypted mode: preview_download, preview_watermark, preview_only
title, messageshown on the download page and in emails
recipient_emailemail the link to this address (not available with e2ee: the server never has the key)
passwordprotect the download with a password
self_destroytrue: delete after every file was downloaded once
expires_atearlier expiry than the plan default
scheduled_atsend the recipient email later (requires recipient_email)
max_downloadsmaximum number of downloads
langlanguage of the emails (en, ro, de, ...); default: your account language

Options that are not included in your plan are refused with 403 feature_not_in_plan.

curl -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
     -d '{"mode":"preview_download","title":"Contract","recipient_email":"client@company.com",
          "files":[{"name":"contract.pdf","size":2400000,"chunks":1,"mime_type":"application/pdf"}]}' \
     https://www.syntransfer.ro/api/v1/transfers

Response 201: the transfer (status pending) with files[] (each with its index) and:

"upload": {
  "chunk_url": "https://www.syntransfer.ro/api/v1/transfers/1031/files/{index}/chunks/{n}",
  "finalize_url": "https://www.syntransfer.ro/api/v1/transfers/1031/finalize",
  "max_chunk_size": 67108864
}

Upload a chunk

PUT/transfers/{id}/files/{index}/chunks/{n}

The body is the raw chunk (Content-Type: application/octet-stream, Content-Length required). index is the file index from the create response, n goes from 0 to chunks - 1. Optional header X-Chunk-SHA256 (hex) lets the server reject damaged chunks.

curl -H "Authorization: Bearer $KEY" -X PUT \
     -H "X-Chunk-SHA256: $(sha256sum part0 | cut -d' ' -f1)" \
     --data-binary @part0 https://www.syntransfer.ro/api/v1/transfers/1031/files/0/chunks/0
{"file_index": 0, "chunk": 0, "chunks_received": 1, "chunks_total": 1, "file_complete": true, "stored_size": 2400000}
The data you send may not exceed the declared sizes. A small tolerance covers the overhead of end-to-end encryption.

Finalize

POST/transfers/{id}/finalize

Activates the transfer and sends the confirmation email (and the recipient email, unless scheduled). Returns the transfer with files[]. If a chunk is missing you get 409 upload_incomplete:

{"error": {"code": "upload_incomplete", "message": "Some files are not completely uploaded.",
           "files": [{"index": 0, "chunks_received": 1, "chunks_total": 2}]}}

Transfer details

GET/transfers/{id}

The transfer with files[] (id, index, name, size, stored_size, mime_type, downloads). If your plan includes download statistics, downloads[] lists the latest 100 downloads (file_id, ip, user_agent, at).

Delete a transfer

DELETE/transfers/{id}

Deletes the files from storage and the transfer link stops working.

{"deleted": true, "id": 1031}

End-to-end encryption

In e2ee mode you encrypt the files before upload, in the same format the website uses, so the download page can decrypt them in the browser:

  • generate one random AES-256-GCM key (32 bytes) per transfer;
  • split each file into plaintext chunks (8 MB recommended) and encrypt each one with a new random 12-byte IV;
  • each encrypted chunk is [4-byte big-endian length][12-byte IV][ciphertext + 16-byte tag], where length = 12 + ciphertext length (tag included);
  • the first chunk of every file starts with the 4 bytes SYNE;
  • declare the original (plaintext) sizes when you create the transfer;
  • the link is url + #key= + the key in base64url without padding. The part after # is never sent to the server.
# Python (cryptography)
import os, struct
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
key = AESGCM.generate_key(bit_length=256)
def encrypt_chunk(plain, first):
    iv = os.urandom(12)
    ct = AESGCM(key).encrypt(iv, plain, None)          # ciphertext + tag
    out = struct.pack(">I", 12 + len(ct)) + iv + ct
    return b"SYNE" + out if first else out
The recipient email is not available in e2ee mode: the server cannot send a working link without the key. Send the link yourself.

Command line app

syn uses this API and handles chunking, end-to-end encryption, parallel upload and retries for you. One file, nothing to install.

syn login                                   # paste your API key
syn whoami                                  # plan, limits, usage
syn send report.pdf photos/                 # end-to-end encrypted, prints the link
syn send contract.pdf --no-encrypt --to client@company.com --title "Contract"
syn send backup.tar.gz -q                   # print only the link (for scripts)
syn list --status active
syn info 1022
syn delete 1022 --yes
syn --json list                             # JSON output for integrations

The key can also come from the SYN_API_KEY environment variable (useful on servers and in CI). Run syn --help or syn send --help for every option.

Complete example

An unencrypted transfer of one file, with curl and jq:

KEY=syt_...
API=https://www.syntransfer.ro/api/v1
FILE=report.pdf
SIZE=$(stat -c %s "$FILE")

ID=$(curl -s -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d "{\"mode\":\"preview_download\",\"files\":[{\"name\":\"$FILE\",\"size\":$SIZE,\"chunks\":1}]}" \
  $API/transfers | jq -r .id)

curl -s -H "Authorization: Bearer $KEY" -X PUT --data-binary @"$FILE" \
  $API/transfers/$ID/files/0/chunks/0 > /dev/null

curl -s -H "Authorization: Bearer $KEY" -X POST $API/transfers/$ID/finalize | jq -r .url

Files larger than 64 MB must be split into several chunks (split -b 8M), with chunks set accordingly.