Misi.Link

API-documentatie

Verstuur bestanden vanuit je eigen tools (scripts, software, servers): maak een overdracht aan, upload de bestanden in delen, ontvang de link.

Sleutel beschikbaar met de abonnementen Pro en Zakelijk, via Mijn account › API. Prijzen

Authenticatie

Maak een sleutel aan via Mijn account › API en stuur hem mee in de Authorization-header van elk verzoek. Houd hem geheim: hij geeft toegang tot je overdrachten. Een verwijderde sleutel werkt meteen niet meer.

Authorization: Bearer mlk_…

Als uw tool of hosting de Authorization-header niet doorgeeft, stuur dan dezelfde sleutel in de header X-API-Key:

X-API-Key: mlk_…

120 verzoeken per minuut per sleutel. Grootte, aantal bestanden en beschikbaarheid: die van je abonnement.

Bestanden versturen in drie stappen

1. De overdracht aanmaken

Geef de lijst met bestanden (naam en grootte in bytes). Met „recipients” krijgt elke ontvanger een e-mail; zonder ontvangers krijg je een link om te delen. Optioneel: „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. De bestanden uploaden

Stuur elk bestand in delen van maximaal „chunk_size” bytes, op volgorde: „offset” is het aantal bytes dat al van dat bestand is ontvangen. Na een onderbreking geeft „status” aan waar je verdergaat (antwoord 409: ga verder bij „received”). Filtert je hosting of firewall adressen, stuur de positie dan in de header X-Upload-Offset in plaats van in het adres.

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. Afronden

Als alle bestanden zijn ontvangen, rond je af: de overdracht gaat online en de e-mails worden verstuurd. De link is die uit het eerste antwoord („url”). Deze stap is verplicht. Daarna slaat de server de bestanden op: intussen is „status” „finalizing”. Een overdracht die volledig is ontvangen maar nooit afgerond, wordt na 30 minuten zonder activiteit automatisch afgerond.

curl -X POST https://misi.link/api/upload/complete -H "Content-Type: application/json" \
  -d '{"id": "aB3dE5fG", "token": "…"}'

Direct uploaden naar de opslag (aanbevolen voor grote bestanden)

Met „direct”: true stuurt je software het bestand rechtstreeks naar de opslag van misi.link, zonder via de server te gaan: maximale snelheid en aan het eind binnen enkele seconden online. Eén bestand per overdracht (meerdere: zet ze in een archief). Zonder deze optie, of als het antwoord „direct”: null bevat, upload je de delen met „chunk” zoals hierboven.

  1. Maak de overdracht met „direct”: true. Het antwoord geeft „direct.part_size” (grootte van elk deel, het laatste is korter), „direct.parts” (aantal delen) en „direct.sign”.
  2. Vraag bij „sign” de ondertekende adressen op (ID, token en deelnummers, maximaal 100 per aanroep; 6 uur geldig) en stuur elk deel met een gewone PUT naar zijn adres, zonder authenticatieheader. Delen kunnen parallel worden verstuurd en na een fout opnieuw.
  3. Rond af met „complete”. Ontbreekt er een deel, dan is het antwoord „incomplete” met de lijst „missing”: stuur die delen opnieuw en roep „complete” nogmaals aan. End-to-end-versleuteling: elk deel is een reeks hele versleutelde segmenten, op volgorde (zie het voorbeeld).
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-versleuteling (optioneel)

Je software versleutelt het bestand voor het verzenden: misi.link ontvangt en bewaart alleen onleesbare gegevens. De sleutel wordt nooit naar de server gestuurd: hij staat in de link, na „#”. De ontvanger downloadt en ontsleutelt in zijn browser, op de gewone pagina.

  1. Maak een willekeurige sleutel van 16 bytes en maak de overdracht met „e2e”: true en één bestand (de onversleutelde grootte, in bytes). Meerdere bestanden zet je eerst in een archief.
  2. Versleutelingssleutel: HKDF-SHA-256 van de sleutel (salt „misilink-e2e-v1”, info = ID van de overdracht, 16 bytes), voor AES-GCM.
  3. Verdeel het bestand in segmenten van 4 MiB (4.194.304 bytes). Segment nr. i: nonce = 4 nulbytes en dan i in 8 bytes (big-endian); bijbehorende gegevens = i in 8 bytes en dan 1 byte (1 voor het laatste segment, anders 0). Elk versleuteld segment is 16 bytes langer: upload deze bytes met „chunk”, op volgorde („e2e.encrypted_size” in totaal), en rond af met „complete”.
  4. Aanbevolen: „e2e_verify” = HKDF-SHA-256 van de sleutel (zelfde salt, info „misilink-verify”, 32 bytes) in base64url. misi.link weigert dan een download met een verkeerde sleutel, zonder de juiste ooit te kennen.
  5. Link om te delen: „url” + „#” + de sleutel in base64url (22 tekens). Met ontvangers („recipients”) voeg je „e2e_key” (de sleutel) toe: misi.link bewaart hem verzegeld om hem in de links van de e-mails te zetten.
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}}

Volledig voorbeeld in Python (bibliotheek 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 [])))

Andere verzoeken

GET /api/v1/transfers
Je overdrachten, 50 per pagina (?page=2…).
GET /api/v1/transfers/{id}
Eén overdracht: status, link, bestanden, aantal downloads, vervaldatum.
DELETE /api/v1/transfers/{id}
Een overdracht verwijderen (de link werkt niet meer).
GET /api/v1/me
Je account, je abonnement en de limieten, grootte van de delen.

Datums in ISO 8601-formaat (UTC), groottes in bytes.

Antwoorden en fouten

Alle antwoorden zijn JSON, met „ok”. Bij een fout: „error” (korte code) en „message”. HTTP-codes: 401 sleutel ontbreekt of ongeldig, 403 abonnement zonder API, 404 niet gevonden, 409 deel op de verkeerde plek, 422 verzoek geweigerd, 429 te veel verzoeken (wacht een minuut).