Misi.Link

Documentația API

Trimiteți fișiere din propriile instrumente (scripturi, programe, servere): creați un transfer, încărcați fișierele pe bucăți, primiți linkul.

Cheie disponibilă cu planurile Pro și Business, în Contul meu › API. Prețuri

Autentificare

Creați o cheie în Contul meu › API și trimiteți-o în antetul Authorization al fiecărei cereri. Păstrați-o secretă: dă acces la transferurile dumneavoastră. O cheie ștearsă nu mai funcționează imediat.

Authorization: Bearer mlk_…

Dacă instrumentul sau găzduirea dvs. nu transmite antetul Authorization, trimiteți aceeași cheie în antetul X-API-Key:

X-API-Key: mlk_…

120 cereri pe minut pe cheie. Dimensiune, număr de fișiere și durată de disponibilitate: conform planului dumneavoastră.

Trimiterea fișierelor în trei pași

1. Creați transferul

Indicați lista fișierelor (nume și dimensiune în octeți). Cu „recipients”, fiecare destinatar primește un e-mail; fără destinatari, primiți un link de partajat. Opțional: „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. Încărcați fișierele

Trimiteți fiecare fișier pe bucăți de cel mult „chunk_size” octeți, în ordine: „offset” este numărul de octeți deja primiți pentru acel fișier. După o întrerupere, „status” arată de unde să continuați (răspuns 409: continuați de la „received”). Dacă găzduirea sau firewallul filtrează adresele, trimiteți poziția în antetul X-Upload-Offset în loc de 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. Finalizați

Când toate fișierele au fost primite, finalizați: transferul devine disponibil și e-mailurile pleacă. Linkul este cel din primul răspuns („url”). Acest pas este obligatoriu. Apoi serverul stochează fișierele: între timp, „status” are valoarea „finalizing”. Un transfer primit integral, dar niciodată finalizat, este finalizat automat după 30 de minute fără activitate.

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

Trimitere directă în spațiul de stocare (recomandată pentru fișiere mari)

Cu „direct”: true, programul dvs. trimite fișierul direct în spațiul de stocare misi.link, fără a trece prin server: viteză maximă și disponibil în câteva secunde la final. Un singur fișier pe transfer (mai multe: puneți-le într-o arhivă). Fără această opțiune, sau dacă răspunsul conține „direct”: null, trimiteți bucățile cu „chunk” ca mai sus.

  1. Creați transferul cu „direct”: true. Răspunsul indică „direct.part_size” (dimensiunea fiecărei părți, ultima e mai scurtă), „direct.parts” (numărul de părți) și „direct.sign”.
  2. Cereți adresele semnate de la „sign” (identificator, token și numerele părților, cel mult 100 pe apel; valabile 6 ore), apoi trimiteți fiecare parte cu un simplu PUT la adresa ei, fără antet de autentificare. Părțile pot fi trimise în paralel și retrimise în caz de eșec.
  3. Finalizați cu „complete”. Dacă lipsește o parte, răspunsul este „incomplete” cu lista „missing”: retrimiteți acele părți, apoi apelați din nou „complete”. Criptare end-to-end: fiecare parte este o succesiune de segmente criptate întregi, în ordine (vedeți exemplul).
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]}

Criptare end-to-end (opțională)

Programul dvs. criptează fișierul înainte de trimitere: misi.link primește și stochează doar date ilizibile. Cheia nu este trimisă niciodată la server: se află în link, după „#”. Destinatarul descarcă și decriptează în browser, pe pagina obișnuită.

  1. Generați la întâmplare o cheie de 16 octeți, apoi creați transferul cu „e2e”: true și un singur fișier (dimensiunea sa necriptată, în octeți). Pentru mai multe fișiere, puneți-le întâi într-o arhivă.
  2. Cheia de criptare: HKDF-SHA-256 al cheii (sare „misilink-e2e-v1”, info = identificatorul transferului, 16 octeți), pentru AES-GCM.
  3. Împărțiți fișierul în segmente de 4 MiB (4.194.304 octeți). Segmentul nr. i: nonce = 4 octeți nuli, apoi i pe 8 octeți (big-endian); date asociate = i pe 8 octeți, apoi 1 octet (1 pentru ultimul segment, altfel 0). Fiecare segment criptat are cu 16 octeți mai mult: trimiteți acești octeți cu „chunk”, în ordine („e2e.encrypted_size” în total), apoi finalizați cu „complete”.
  4. Recomandat: „e2e_verify” = HKDF-SHA-256 al cheii (aceeași sare, info „misilink-verify”, 32 de octeți) în base64url. misi.link refuză atunci o descărcare cu o cheie greșită, fără să o cunoască vreodată pe cea corectă.
  5. Link de partajat: „url” + „#” + cheia în base64url (22 de caractere). Cu destinatari („recipients”), adăugați „e2e_key” (cheia): misi.link o păstrează sigilată pentru a o pune în linkurile din e-mailuri.
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}}

Exemplu complet în Python (biblioteca 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 [])))

Alte cereri

GET /api/v1/transfers
Transferurile dumneavoastră, 50 pe pagină (?page=2…).
GET /api/v1/transfers/{id}
Un transfer: stare, link, fișiere, număr de descărcări, dată de expirare.
DELETE /api/v1/transfers/{id}
Ștergerea unui transfer (linkul nu mai funcționează).
GET /api/v1/me
Contul, planul și limitele lui, dimensiunea bucăților.

Date în format ISO 8601 (UTC), dimensiuni în octeți.

Răspunsuri și erori

Toate răspunsurile sunt în JSON, cu „ok”. În caz de eroare: „error” (cod scurt) și „message”. Coduri HTTP: 401 cheie lipsă sau nevalidă, 403 plan fără API, 404 negăsit, 409 bucată în locul greșit, 422 cerere refuzată, 429 prea multe cereri (așteptați un minut).