API-dokumentation
Send filer fra dine egne værktøjer (scripts, programmer, servere): opret en overførsel, upload filerne i dele, få linket.
Nøgler fås med abonnementerne Pro og Erhverv under Min konto › API. Priser
Godkendelse
Opret en nøgle under Min konto › API, og send den i Authorization-headeren i hver forespørgsel. Hold den hemmelig: den giver adgang til dine overførsler. En slettet nøgle holder straks op med at virke.
Authorization: Bearer mlk_… Hvis dit værktøj eller din hosting ikke videresender Authorization-headeren, så send den samme nøgle i headeren X-API-Key:
X-API-Key: mlk_… 120 forespørgsler pr. minut pr. nøgle. Størrelse, antal filer og tilgængelighed: som i dit abonnement.
Send filer i tre trin
1. Opret overførslen
Angiv listen over filer (navn og størrelse i byte). Med “recipients” får hver modtager en e-mail; uden modtagere får du et link, du kan dele. Valgfrit: “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. Upload filerne
Send hver fil i dele på højst “chunk_size” byte i rækkefølge: “offset” er antallet af byte, der allerede er modtaget for filen. Efter en afbrydelse viser “status”, hvor du fortsætter (svar 409: fortsæt ved “received”). Hvis dit webhotel eller din firewall filtrerer adresser, så send positionen i headeren X-Upload-Offset i stedet for i adressen.
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. Afslut
Når alle filer er modtaget, afslutter du: overførslen går online, og e-mailene sendes. Linket er det fra det første svar (“url”). Dette trin er obligatorisk. Derefter gemmer serveren filerne: imens er “status” “finalizing”. En overførsel, der er modtaget helt, men aldrig afsluttet, afsluttes automatisk efter 30 minutter uden aktivitet.
curl -X POST https://misi.link/api/upload/complete -H "Content-Type: application/json" \
-d '{"id": "aB3dE5fG", "token": "…"}'
Direkte upload til lageret (anbefales til store filer)
Med »direct«: true sender dit program filen direkte til misi.links lager uden om serveren: maksimal hastighed og online på få sekunder til sidst. Én fil pr. overførsel (flere: saml dem i et arkiv). Uden denne mulighed, eller hvis svaret indeholder »direct«: null, uploader du delene med »chunk« som ovenfor.
- Opret overførslen med »direct«: true. Svaret angiver »direct.part_size« (størrelsen på hver del, den sidste er kortere), »direct.parts« (antal dele) og »direct.sign«.
- Hent de signerede adresser fra »sign« (ID, token og delnumre, højst 100 pr. kald; gyldige i 6 timer), og send hver del med en almindelig PUT til dens adresse uden godkendelses-header. Delene kan sendes parallelt og sendes igen efter en fejl.
- Afslut med »complete«. Mangler en del, er svaret »incomplete« med listen »missing«: send de dele igen, og kald »complete« igen. End-to-end-kryptering: hver del er en række hele krypterede segmenter i rækkefølge (se eksemplet).
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]}
End-to-end-kryptering (valgfrit)
Dit program krypterer filen, før den sendes: misi.link modtager og gemmer kun ulæselige data. Nøglen sendes aldrig til serveren: den står i linket efter »#«. Modtageren downloader og dekrypterer i sin browser på den sædvanlige side.
- Lav en tilfældig nøgle på 16 byte, og opret overførslen med »e2e«: true og én enkelt fil (dens ukrypterede størrelse i byte). Flere filer samler du først i et arkiv.
- Krypteringsnøgle: HKDF-SHA-256 af nøglen (salt »misilink-e2e-v1«, info = overførslens ID, 16 byte), til AES-GCM.
- Del filen i segmenter på 4 MiB (4.194.304 byte). Segment nr. i: nonce = 4 nul-byte og derefter i på 8 byte (big-endian); tilknyttede data = i på 8 byte og derefter 1 byte (1 for det sidste segment, ellers 0). Hvert krypteret segment er 16 byte længere: upload disse byte med »chunk« i rækkefølge (»e2e.encrypted_size« i alt), og afslut med »complete«.
- Anbefalet: »e2e_verify« = HKDF-SHA-256 af nøglen (samme salt, info »misilink-verify«, 32 byte) i base64url. misi.link afviser så en download med en forkert nøgle uden nogensinde at kende den rigtige.
- Link til deling: »url« + »#« + nøglen i base64url (22 tegn). Med modtagere (»recipients«) tilføjer du »e2e_key« (nøglen): misi.link gemmer den forseglet for at sætte den ind i linkene i e-mailene.
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}} Komplet eksempel i Python (biblioteket 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 [])))
Andre forespørgsler
GET /api/v1/transfers- Dine overførsler, 50 pr. side (?page=2…).
GET /api/v1/transfers/{id}- Én overførsel: status, link, filer, antal downloads, udløbsdato.
DELETE /api/v1/transfers/{id}- Slet en overførsel (linket holder op med at virke).
GET /api/v1/me- Din konto, dit abonnement og dets grænser, delenes størrelse.
Datoer i ISO 8601-format (UTC), størrelser i byte.
Svar og fejl
Alle svar er JSON med “ok”. Ved fejl: “error” (kort kode) og “message”. HTTP-koder: 401 nøgle mangler eller er ugyldig, 403 abonnement uden API, 404 ikke fundet, 409 del det forkerte sted, 422 forespørgsel afvist, 429 for mange forespørgsler (vent et minut).