Misi.Link

API dokumentācija

Sūtiet failus no saviem rīkiem (skripti, programmas, serveri): izveidojiet pārsūtīšanu, augšupielādējiet failus pa daļām, saņemiet saiti.

Atslēga pieejama plānos Pro un Bizness sadaļā Mans konts › API. Cenas

Autentifikācija

Izveidojiet atslēgu sadaļā Mans konts › API un sūtiet to katra pieprasījuma Authorization galvenē. Glabājiet to noslēpumā: tā dod piekļuvi jūsu pārsūtīšanām. Izdzēsta atslēga uzreiz pārstāj darboties.

Authorization: Bearer mlk_…

Ja jūsu rīks vai mitināšana nenodod galveni Authorization, nosūtiet to pašu atslēgu galvenē X-API-Key:

X-API-Key: mlk_…

120 pieprasījumi minūtē vienai atslēgai. Lielums, failu skaits un pieejamības laiks: atbilstoši jūsu plānam.

Failu sūtīšana trīs soļos

1. Izveidojiet pārsūtīšanu

Norādiet failu sarakstu (nosaukums un lielums baitos). Ar „recipients” katrs saņēmējs saņem e-pastu; bez saņēmējiem jūs saņemat saiti kopīgošanai. Neobligāti: „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. Augšupielādējiet failus

Sūtiet katru failu daļās, kas nepārsniedz „chunk_size” baitus, pēc kārtas: „offset” ir šī faila jau saņemto baitu skaits. Pēc pārtraukuma „status” parāda, no kurienes turpināt (atbilde 409: turpiniet no „received”). Ja jūsu mitināšana vai ugunsmūris filtrē adreses, sūtiet pozīciju galvenē X-Upload-Offset, nevis 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. Pabeidziet

Kad visi faili ir saņemti, pabeidziet: pārsūtīšana tiek publicēta un e-pasti nosūtīti. Saite ir tā, kas pirmajā atbildē („url”). Šis solis ir obligāts. Pēc tam serveris saglabā failus: tikmēr „status” vērtība ir „finalizing”. Pārsūtīšana, kas saņemta pilnībā, bet nekad nav pabeigta, tiek pabeigta automātiski pēc 30 minūšu bezdarbības.

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

Tieša augšupielāde krātuvē (ieteicama lieliem failiem)

Ar „direct”: true jūsu programma sūta failu tieši uz misi.link krātuvi, apejot serveri: maksimāls ātrums, un beigās fails pieejams dažās sekundēs. Viens fails pārsūtīšanā (vairāki: apvienojiet arhīvā). Bez šīs iespējas vai ja atbildē ir „direct”: null, augšupielādējiet daļas ar „chunk” kā iepriekš.

  1. Izveidojiet pārsūtīšanu ar „direct”: true. Atbildē ir „direct.part_size” (katras daļas izmērs, pēdējā ir īsāka), „direct.parts” (daļu skaits) un „direct.sign”.
  2. Pieprasiet parakstītās adreses no „sign” (identifikators, marķieris un daļu numuri, līdz 100 vienā izsaukumā; derīgas 6 stundas), tad nosūtiet katru daļu ar parastu PUT uz tās adresi, bez autentifikācijas galvenes. Daļas var sūtīt paralēli un pēc kļūdas atkārtoti.
  3. Pabeidziet ar „complete”. Ja trūkst daļas, atbilde ir „incomplete” ar sarakstu „missing”: nosūtiet šīs daļas vēlreiz un vēlreiz izsauciet „complete”. Pilnīga šifrēšana: katra daļa ir veselu šifrētu segmentu virkne secībā (skatiet piemēru).
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]}

Pilnīga šifrēšana (pēc izvēles)

Jūsu programma šifrē failu pirms nosūtīšanas: misi.link saņem un glabā tikai nesalasāmus datus. Atslēga nekad netiek sūtīta uz serveri: tā ir saitē, pēc „#”. Saņēmējs lejupielādē un atšifrē savā pārlūkā, ierastajā lapā.

  1. Nejauši izveidojiet 16 baitu atslēgu un tad izveidojiet pārsūtīšanu ar „e2e”: true un vienu failu (tā nešifrētais izmērs baitos). Vairākus failus vispirms apvienojiet arhīvā.
  2. Šifrēšanas atslēga: atslēgas HKDF-SHA-256 (sāls „misilink-e2e-v1”, info = pārsūtīšanas identifikators, 16 baiti), AES-GCM vajadzībām.
  3. Sadaliet failu 4 MiB (4 194 304 baitu) segmentos. Segments nr. i: nonce = 4 nulles baiti un tad i 8 baitos (big-endian); saistītie dati = i 8 baitos un tad 1 baits (pēdējam segmentam 1, citādi 0). Katrs šifrētais segments ir par 16 baitiem garāks: augšupielādējiet šos baitus ar „chunk” secīgi (kopā „e2e.encrypted_size”) un pabeidziet ar „complete”.
  4. Ieteicams: „e2e_verify” = atslēgas HKDF-SHA-256 (tā pati sāls, info „misilink-verify”, 32 baiti) base64url formātā. Tad misi.link atteiks lejupielādi ar nepareizu atslēgu, nekad nezinot pareizo.
  5. Kopīgojamā saite: „url” + „#” + atslēga base64url formātā (22 rakstzīmes). Ar saņēmējiem („recipients”) pievienojiet „e2e_key” (atslēgu): misi.link to glabā aizzīmogotu, lai ievietotu e-pastu saitēs.
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}}

Pilns piemērs Python valodā (bibliotēka 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 [])))

Citi pieprasījumi

GET /api/v1/transfers
Jūsu pārsūtīšanas, 50 lapā (?page=2…).
GET /api/v1/transfers/{id}
Viena pārsūtīšana: stāvoklis, saite, faili, lejupielāžu skaits, derīguma termiņš.
DELETE /api/v1/transfers/{id}
Pārsūtīšanas dzēšana (saite pārstāj darboties).
GET /api/v1/me
Jūsu konts, plāns un tā ierobežojumi, daļu lielums.

Datumi ISO 8601 formātā (UTC), lielumi baitos.

Atbildes un kļūdas

Visas atbildes ir JSON formātā ar „ok”. Kļūdas gadījumā: „error” (īss kods) un „message”. HTTP kodi: 401 trūkst atslēgas vai tā nav derīga, 403 plāns bez API, 404 nav atrasts, 409 daļa nepareizā vietā, 422 pieprasījums noraidīts, 429 pārāk daudz pieprasījumu (pagaidiet minūti).