API-Dokumentation
Senden Sie Dateien aus Ihren eigenen Werkzeugen (Skripte, Programme, Server): Transfer erstellen, Dateien in Teilen hochladen, Link erhalten.
Schlüssel mit den Tarifen Pro und Business verfügbar, unter Mein Konto › API. Preise
Authentifizierung
Erstellen Sie einen Schlüssel unter Mein Konto › API und senden Sie ihn im Authorization-Header jeder Anfrage. Halten Sie ihn geheim: Er gibt Zugriff auf Ihre Transfers. Ein gelöschter Schlüssel funktioniert sofort nicht mehr.
Authorization: Bearer mlk_… Wenn Ihr Tool oder Hosting den Authorization-Header nicht weitergibt, senden Sie denselben Schlüssel im Header X-API-Key:
X-API-Key: mlk_… 120 Anfragen pro Minute und Schlüssel. Größe, Anzahl der Dateien und Verfügbarkeitsdauer: wie in Ihrem Tarif.
Dateien in drei Schritten senden
1. Transfer erstellen
Geben Sie die Liste der Dateien an (Name und Größe in Bytes). Mit „recipients“ erhält jeder Empfänger eine E-Mail; ohne Empfänger erhalten Sie einen Link zum Teilen. Optional: „message“, „password“, „expire_days“, „delete_after_download“.
curl -X POST https://misi.link/api/v1/transfers \
-H "Authorization: Bearer $MISILINK_KEY" -H "Content-Type: application/json" \
-d '{"files": [{"name": "film.mov", "size": 5368709120}],
"recipients": ["client@example.com"], "message": "…", "expire_days": 14}' {"ok": true, "id": "aB3dE5fG", "token": "…", "url": "https://misi.link/aB3dE5fG",
"files": [{"id": 101, "name": "film.mov", "size": 5368709120}], "chunk_size": 8388608} 2. Dateien hochladen
Senden Sie jede Datei in Teilen von höchstens „chunk_size“ Bytes, der Reihe nach: „offset“ ist die Anzahl der bereits empfangenen Bytes dieser Datei. Nach einer Unterbrechung zeigt „status“, wo es weitergeht (Antwort 409: bei „received“ fortfahren). Wenn Ihr Hosting oder Ihre Firewall Adressen filtert, senden Sie die Position im Header X-Upload-Offset statt in der Adresse.
curl -X POST "https://misi.link/api/upload/chunk?id=aB3dE5fG&token=…&file=101&offset=0" \
-H "Content-Type: application/octet-stream" --data-binary @morceau-1.bin
# {"ok": true, "received": 8388608, "done": false}
curl "https://misi.link/api/upload/status?id=aB3dE5fG&token=…" 3. Abschließen
Wenn alle Dateien empfangen sind, schließen Sie ab: Der Transfer geht online und die E-Mails werden versendet. Der Link ist der aus der ersten Antwort („url“). Dieser Schritt ist Pflicht. Danach legt der Server die Dateien ab: Währenddessen ist „status“ „finalizing“. Ein vollständig empfangener, aber nie abgeschlossener Transfer wird nach 30 Minuten ohne Aktivität automatisch abgeschlossen.
curl -X POST https://misi.link/api/upload/complete -H "Content-Type: application/json" \
-d '{"id": "aB3dE5fG", "token": "…"}'
Direkter Upload in den Speicher (empfohlen für große Dateien)
Mit „direct“: true sendet Ihre Software die Datei direkt in den Speicher von misi.link, ohne Umweg über den Server: maximale Geschwindigkeit und am Ende in Sekunden online. Eine Datei pro Transfer (mehrere: in ein Archiv packen). Ohne diese Option oder wenn die Antwort „direct“: null enthält, laden Sie die Teile wie oben mit „chunk“ hoch.
- Erstellen Sie den Transfer mit „direct“: true. Die Antwort enthält „direct.part_size“ (Größe jedes Teils, der letzte ist kürzer), „direct.parts“ (Anzahl der Teile) und „direct.sign“.
- Fordern Sie bei „sign“ die signierten Adressen an (ID, Token und Teilnummern, höchstens 100 pro Aufruf; 6 Stunden gültig) und senden Sie jedes Teil mit einem einfachen PUT an seine Adresse, ohne Authentifizierungs-Header. Teile können parallel gesendet und nach einem Fehler erneut gesendet werden.
- Schließen Sie mit „complete“ ab. Fehlt ein Teil, lautet die Antwort „incomplete“ mit der Liste „missing“: Senden Sie diese Teile erneut und rufen Sie „complete“ noch einmal auf. Ende-zu-Ende-Verschlüsselung: Jedes Teil ist eine Folge ganzer verschlüsselter Segmente, in der richtigen Reihenfolge (siehe Beispiel).
curl -X POST https://misi.link/api/v1/transfers -H "Authorization: Bearer $MISILINK_KEY" -H "Content-Type: application/json" \
-d '{"files": [{"name": "film.mov", "size": 5368709120}], "direct": true}'
# {…, "direct": {"part_size": 16777216, "parts": 320, "total": 5368709120, "sign": "https://misi.link/api/upload/sign"}}
curl -X POST https://misi.link/api/upload/sign -H "Content-Type: application/json" \
-d '{"id": "aB3dE5fG", "token": "…", "parts": [1, 2, 3]}'
# {"ok": true, "urls": {"1": "https://…", "2": "https://…", "3": "https://…"}}
curl -X PUT -H "Content-Type: application/octet-stream" --data-binary @part-1.bin "https://…"
curl -X POST https://misi.link/api/upload/complete -H "Content-Type: application/json" -d '{"id": "aB3dE5fG", "token": "…"}'
# {"ok": false, "error": "incomplete", "missing": [2]}
Ende-zu-Ende-Verschlüsselung (optional)
Ihre Software verschlüsselt die Datei vor dem Senden: misi.link empfängt und speichert nur unlesbare Daten. Der Schlüssel wird nie an den Server gesendet: Er steht im Link, nach „#“. Der Empfänger lädt herunter und entschlüsselt in seinem Browser, auf der gewohnten Seite.
- Erzeugen Sie einen zufälligen Schlüssel von 16 Bytes und erstellen Sie den Transfer mit „e2e“: true und einer einzigen Datei (ihre unverschlüsselte Größe in Bytes). Mehrere Dateien fassen Sie vorher in einem Archiv zusammen.
- Verschlüsselungsschlüssel: HKDF-SHA-256 des Schlüssels (Salt „misilink-e2e-v1“, Info = Transfer-ID, 16 Bytes), für AES-GCM.
- Teilen Sie die Datei in Segmente von 4 MiB (4.194.304 Bytes). Segment Nr. i: Nonce = 4 Null-Bytes, dann i auf 8 Bytes (Big-Endian); zusätzliche Daten = i auf 8 Bytes, dann 1 Byte (1 für das letzte Segment, sonst 0). Jedes verschlüsselte Segment ist 16 Bytes länger: Laden Sie diese Bytes mit „chunk“ der Reihe nach hoch („e2e.encrypted_size“ insgesamt) und schließen Sie mit „complete“ ab.
- Empfohlen: „e2e_verify“ = HKDF-SHA-256 des Schlüssels (gleiches Salt, Info „misilink-verify“, 32 Bytes) in base64url. misi.link verweigert dann einen Download mit falschem Schlüssel, ohne den richtigen je zu kennen.
- Link zum Teilen: „url“ + „#“ + der Schlüssel in base64url (22 Zeichen). Mit Empfängern („recipients“) fügen Sie „e2e_key“ (den Schlüssel) hinzu: misi.link bewahrt ihn versiegelt auf, um ihn in die Links der E-Mails einzufügen.
curl -X POST https://misi.link/api/v1/transfers \
-H "Authorization: Bearer $MISILINK_KEY" -H "Content-Type: application/json" \
-d '{"files": [{"name": "film.mov", "size": 5368709120}], "e2e": true, "e2e_verify": "…"}'
# {"ok": true, "id": "aB3dE5fG", …, "e2e": {"segment": 4194304, "tag": 16, "encrypted_size": 5368729600, "version": 1}} Vollständiges Beispiel in Python (Bibliothek cryptography):
#!/usr/bin/env python3
# misi.link: end-to-end encrypted upload through the API (Python 3.8+, pip install cryptography)
# The file goes straight to the storage (signed URLs); if direct upload is unavailable, through "chunk".
# Usage: MISILINK_KEY=mlk_... python3 misilink_e2e.py file.mov [https://misi.link]
import base64, json, os, sys, urllib.request
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.hazmat.primitives.hashes import SHA256
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
SEG, SALT = 4 * 1024 * 1024, b'misilink-e2e-v1'
b64 = lambda b: base64.urlsafe_b64encode(b).rstrip(b'=').decode()
hkdf = lambda ikm, info, n: HKDF(SHA256(), n, SALT, info).derive(ikm)
def call(url, body=None, raw=None, headers=None, method=None, auth=True):
h = dict(headers or {})
if auth:
h['Authorization'] = 'Bearer ' + os.environ['MISILINK_KEY']
data = raw if raw is not None else (json.dumps(body).encode() if body is not None else None)
if data is not None:
h.setdefault('Content-Type', 'application/octet-stream' if raw is not None else 'application/json')
req = urllib.request.Request(url, data=data, headers=h, method=method or ('POST' if data is not None else 'GET'))
with urllib.request.urlopen(req, timeout=600) as r:
body = r.read()
return json.loads(body) if body[:1] == b'{' else {}
def segments(path, key, size):
n = max(1, -(-size // SEG))
with open(path, 'rb') as f:
for i in range(n):
last = i == n - 1
nonce = bytes(4) + i.to_bytes(8, 'big')
aad = i.to_bytes(8, 'big') + (b'\x01' if last else b'\x00')
yield key.encrypt(nonce, f.read(SEG), aad) # SEG bytes in, SEG + 16 bytes out
def send(path, base='https://misi.link'):
size = os.path.getsize(path)
secret = os.urandom(16) # the key: never sent to the server
verify = b64(hkdf(secret, b'misilink-verify', 32)) # lets the server recognise the right key
t = call(base + '/api/v1/transfers', {'files': [{'name': os.path.basename(path), 'size': size}],
'e2e': True, 'e2e_verify': verify, 'direct': True})
key = AESGCM(hkdf(secret, t['id'].encode(), 16))
auth = {'id': t['id'], 'token': t['token']}
d = t.get('direct')
if d: # straight to the storage: parts of d['part_size'] bytes
part, n, urls = b'', 0, {}
def put(n, data):
if str(n) not in urls:
urls.update(call(d['sign'], {**auth, 'parts': list(range(n, min(n + 100, d['parts'] + 1)))})['urls'])
call(urls[str(n)], raw=data, method='PUT', auth=False)
for seg in segments(path, key, size):
part += seg
if len(part) == d['part_size']:
n += 1; put(n, part); part = b''
if part:
n += 1; put(n, part)
else: # through the server
sent = 0
for seg in segments(path, key, size):
r = call(t['upload']['chunk'] + '?id=' + t['id'] + '&token=' + t['token'] + '&file=' + str(t['files'][0]['id']),
raw=seg, headers={'X-Upload-Offset': str(sent)})
sent = r['received']
call(t['upload']['complete'], auth)
return t['url'] + '#' + b64(secret) # the key travels after "#", only in the link
if __name__ == '__main__':
print(send(sys.argv[1], *(sys.argv[2:3] or [])))
Weitere Anfragen
GET /api/v1/transfers- Ihre Transfers, 50 pro Seite (?page=2…).
GET /api/v1/transfers/{id}- Ein Transfer: Status, Link, Dateien, Anzahl der Downloads, Ablaufdatum.
DELETE /api/v1/transfers/{id}- Einen Transfer löschen (der Link funktioniert nicht mehr).
GET /api/v1/me- Ihr Konto, Ihr Tarif und seine Grenzen, Größe der Teile.
Datumsangaben im Format ISO 8601 (UTC), Größen in Bytes.
Antworten und Fehler
Alle Antworten sind JSON, mit „ok“. Bei einem Fehler: „error“ (kurzer Code) und „message“. HTTP-Codes: 401 Schlüssel fehlt oder ungültig, 403 Tarif ohne API, 404 nicht gefunden, 409 Teil an falscher Stelle, 422 Anfrage abgelehnt, 429 zu viele Anfragen (eine Minute warten).