Misi.Link

API dokumentacija

Siųskite failus iš savo įrankių (scenarijų, programų, serverių): sukurkite perdavimą, įkelkite failus dalimis, gaukite nuorodą.

Raktas prieinamas su Pro ir Verslo planais, skiltyje Mano paskyra › API. Kainos

Autentifikavimas

Sukurkite raktą skiltyje Mano paskyra › API ir siųskite jį kiekvienos užklausos Authorization antraštėje. Laikykite jį paslaptyje: jis suteikia prieigą prie jūsų perdavimų. Ištrintas raktas iškart nustoja veikti.

Authorization: Bearer mlk_…

Jei jūsų įrankis ar prieglobos paslauga neperduoda antraštės Authorization, siųskite tą patį raktą antraštėje X-API-Key:

X-API-Key: mlk_…

120 užklausų per minutę vienam raktui. Dydis, failų skaičius ir prieinamumo trukmė: pagal jūsų planą.

Failų siuntimas trimis žingsniais

1. Sukurkite perdavimą

Nurodykite failų sąrašą (pavadinimas ir dydis baitais). Su „recipients“ kiekvienas gavėjas gauna el. laišką; be gavėjų gaunate nuorodą, kuria galite dalytis. Neprivaloma: „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. Įkelkite failus

Siųskite kiekvieną failą ne didesnėmis kaip „chunk_size“ baitų dalimis, iš eilės: „offset“ – jau gautų to failo baitų skaičius. Po pertrūkio „status“ parodo, nuo kur tęsti (atsakymas 409: tęskite nuo „received“). Jei jūsų prieglobos paslauga ar ugniasienė filtruoja adresus, siųskite poziciją antraštėje X-Upload-Offset, o ne adrese.

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. Užbaikite

Kai visi failai gauti, užbaikite: perdavimas paskelbiamas, el. laiškai išsiunčiami. Nuoroda – pirmojo atsakymo „url“. Šis žingsnis privalomas. Tada serveris išsaugo failus: tuo metu „status“ reikšmė yra „finalizing“. Visiškai gautas, bet neužbaigtas perdavimas užbaigiamas automatiškai po 30 minučių neveiklumo.

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

Tiesioginis įkėlimas į saugyklą (rekomenduojama dideliems failams)

Su „direct“: true jūsų programa siunčia failą tiesiai į misi.link saugyklą, aplenkdama serverį: didžiausias greitis, o pabaigoje failas pasiekiamas per kelias sekundes. Vienas failas perdavime (keli: sudėkite į archyvą). Be šios parinkties arba jei atsakyme yra „direct“: null, įkelkite dalis per „chunk“ kaip aukščiau.

  1. Sukurkite perdavimą su „direct“: true. Atsakyme nurodomi „direct.part_size“ (kiekvienos dalies dydis, paskutinė trumpesnė), „direct.parts“ (dalių skaičius) ir „direct.sign“.
  2. Pasirašytus adresus gaukite per „sign“ (identifikatorius, žetonas ir dalių numeriai, iki 100 per užklausą; galioja 6 valandas), tada kiekvieną dalį siųskite paprastu PUT jos adresu, be autentifikavimo antraštės. Dalis galima siųsti lygiagrečiai ir po klaidos pakartotinai.
  3. Užbaikite per „complete“. Jei trūksta dalies, atsakymas yra „incomplete“ su sąrašu „missing“: dar kartą išsiųskite tas dalis ir vėl iškvieskite „complete“. Šifravimas nuo galo iki galo: kiekviena dalis yra iš eilės einančių visų užšifruotų segmentų seka (žr. pavyzdį).
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]}

Šifravimas nuo galo iki galo (nebūtinas)

Jūsų programa užšifruoja failą prieš siųsdama: misi.link gauna ir saugo tik neįskaitomus duomenis. Raktas niekada nesiunčiamas į serverį: jis yra nuorodoje, po „#“. Gavėjas atsisiunčia ir iššifruoja savo naršyklėje, įprastame puslapyje.

  1. Atsitiktinai sukurkite 16 baitų raktą, tada sukurkite perdavimą su „e2e“: true ir vienu failu (jo nešifruotas dydis baitais). Kelis failus pirmiausia sudėkite į archyvą.
  2. Šifravimo raktas: rakto HKDF-SHA-256 (druska „misilink-e2e-v1“, info = perdavimo identifikatorius, 16 baitų), skirtas AES-GCM.
  3. Padalykite failą į 4 MiB (4 194 304 baitų) segmentus. Segmentas nr. i: nonce = 4 nuliniai baitai, tada i 8 baituose (big-endian); susieti duomenys = i 8 baituose, tada 1 baitas (paskutiniam segmentui 1, kitaip 0). Kiekvienas užšifruotas segmentas yra 16 baitų ilgesnis: įkelkite šiuos baitus per „chunk“ iš eilės (iš viso „e2e.encrypted_size“) ir užbaikite per „complete“.
  4. Rekomenduojama: „e2e_verify“ = rakto HKDF-SHA-256 (ta pati druska, info „misilink-verify“, 32 baitai) base64url formatu. Tada misi.link atmes atsisiuntimą su neteisingu raktu, niekada nežinodamas teisingo.
  5. Dalijimosi nuoroda: „url“ + „#“ + raktas base64url formatu (22 simboliai). Su gavėjais („recipients“) pridėkite „e2e_key“ (raktą): misi.link jį saugo užantspauduotą, kad įterptų į el. laiškų nuorodas.
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}}

Visas pavyzdys Python kalba (biblioteka 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 [])))

Kitos užklausos

GET /api/v1/transfers
Jūsų perdavimai, po 50 puslapyje (?page=2…).
GET /api/v1/transfers/{id}
Vienas perdavimas: būsena, nuoroda, failai, atsisiuntimų skaičius, galiojimo pabaiga.
DELETE /api/v1/transfers/{id}
Perdavimo ištrynimas (nuoroda nustoja veikti).
GET /api/v1/me
Jūsų paskyra, planas ir jo ribos, dalių dydis.

Datos ISO 8601 formatu (UTC), dydžiai baitais.

Atsakymai ir klaidos

Visi atsakymai pateikiami JSON formatu su „ok“. Įvykus klaidai: „error“ (trumpas kodas) ir „message“. HTTP kodai: 401 trūksta rakto arba jis netinkamas, 403 planas be API, 404 nerasta, 409 dalis netinkamoje vietoje, 422 užklausa atmesta, 429 per daug užklausų (palaukite minutę).