Misi.Link

API-dokumentation

Skicka filer från dina egna verktyg (skript, program, servrar): skapa en överföring, ladda upp filerna i delar, få länken.

Nycklar finns med abonnemangen Pro och Företag, under Mitt konto › API. Priser

Autentisering

Skapa en nyckel under Mitt konto › API och skicka den i Authorization-huvudet i varje anrop. Håll den hemlig: den ger åtkomst till dina överföringar. En borttagen nyckel slutar fungera direkt.

Authorization: Bearer mlk_…

Om ditt verktyg eller din hosting inte skickar vidare Authorization-huvudet, skicka samma nyckel i huvudet X-API-Key:

X-API-Key: mlk_…

120 anrop per minut och nyckel. Storlek, antal filer och tillgänglighet: enligt ditt abonnemang.

Skicka filer i tre steg

1. Skapa överföringen

Ange listan över filer (namn och storlek i byte). Med ”recipients” får varje mottagare ett mejl; utan mottagare får du en länk att dela. Valfritt: ”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. Ladda upp filerna

Skicka varje fil i delar om högst ”chunk_size” byte, i ordning: ”offset” är antalet byte som redan tagits emot för filen. Efter ett avbrott visar ”status” var du fortsätter (svar 409: fortsätt vid ”received”). Om ditt webbhotell eller din brandvägg filtrerar adresser skickar du positionen i rubriken X-Upload-Offset i stället för 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. Avsluta

När alla filer har tagits emot avslutar du: överföringen publiceras och mejlen skickas. Länken är den från det första svaret (”url”). Det här steget är obligatoriskt. Sedan lagrar servern filerna: under tiden är ”status” ”finalizing”. En överföring som tagits emot helt men aldrig avslutats avslutas automatiskt efter 30 minuter utan aktivitet.

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

Direktuppladdning till lagringen (rekommenderas för stora filer)

Med ”direct”: true skickar ditt program filen direkt till misi.links lagring, utan att gå via servern: maximal hastighet och online på några sekunder i slutet. En fil per överföring (flera: lägg dem i ett arkiv). Utan detta alternativ, eller om svaret innehåller ”direct”: null, laddar du upp delarna med ”chunk” som ovan.

  1. Skapa överföringen med ”direct”: true. Svaret anger ”direct.part_size” (storleken på varje del, den sista är kortare), ”direct.parts” (antal delar) och ”direct.sign”.
  2. Hämta de signerade adresserna från ”sign” (id, token och delnummer, högst 100 per anrop; giltiga i 6 timmar) och skicka varje del med en vanlig PUT till dess adress, utan autentiseringsrubrik. Delarna kan skickas parallellt och skickas igen efter ett fel.
  3. Avsluta med ”complete”. Saknas en del blir svaret ”incomplete” med listan ”missing”: skicka de delarna igen och anropa ”complete” på nytt. Totalsträckskryptering: varje del är en följd av hela krypterade segment, i ordning (se exemplet).
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]}

Totalsträckskryptering (valfritt)

Ditt program krypterar filen innan den skickas: misi.link tar bara emot och lagrar oläsliga data. Nyckeln skickas aldrig till servern: den finns i länken, efter ”#”. Mottagaren laddar ner och dekrypterar i sin webbläsare, på den vanliga sidan.

  1. Skapa en slumpmässig nyckel på 16 byte och skapa överföringen med ”e2e”: true och en enda fil (dess okrypterade storlek i byte). Flera filer samlar du först i ett arkiv.
  2. Krypteringsnyckel: HKDF-SHA-256 av nyckeln (salt ”misilink-e2e-v1”, info = överföringens id, 16 byte), för AES-GCM.
  3. Dela upp filen i segment om 4 MiB (4 194 304 byte). Segment nr i: nonce = 4 nollbyte och sedan i på 8 byte (big-endian); associerade data = i på 8 byte och sedan 1 byte (1 för det sista segmentet, annars 0). Varje krypterat segment är 16 byte längre: ladda upp dessa byte med ”chunk” i ordning (”e2e.encrypted_size” totalt) och avsluta med ”complete”.
  4. Rekommenderas: ”e2e_verify” = HKDF-SHA-256 av nyckeln (samma salt, info ”misilink-verify”, 32 byte) i base64url. misi.link vägrar då en nedladdning med fel nyckel, utan att någonsin känna till den rätta.
  5. Länk att dela: ”url” + ”#” + nyckeln i base64url (22 tecken). Med mottagare (”recipients”) lägger du till ”e2e_key” (nyckeln): misi.link förvarar den förseglad för att lägga in den i länkarna i mejlen.
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}}

Komplett exempel 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 [])))

Andra anrop

GET /api/v1/transfers
Dina överföringar, 50 per sida (?page=2…).
GET /api/v1/transfers/{id}
En överföring: status, länk, filer, antal nedladdningar, utgångsdatum.
DELETE /api/v1/transfers/{id}
Ta bort en överföring (länken slutar fungera).
GET /api/v1/me
Ditt konto, ditt abonnemang och dess gränser, delarnas storlek.

Datum i ISO 8601-format (UTC), storlekar i byte.

Svar och fel

Alla svar är JSON, med ”ok”. Vid fel: ”error” (kort kod) och ”message”. HTTP-koder: 401 nyckel saknas eller är ogiltig, 403 abonnemang utan API, 404 hittades inte, 409 del på fel plats, 422 anrop nekat, 429 för många anrop (vänta en minut).