Documentation de l’API
Envoyez des fichiers depuis vos propres outils (scripts, logiciels, serveurs) : créez un transfert, déposez les fichiers par morceaux, récupérez le lien.
Clé disponible avec les offres Pro et Entreprise, dans Mon compte › API. Tarifs
Authentification
Créez une clé dans Mon compte › API, puis envoyez-la dans l’en-tête Authorization de chaque requête. Gardez-la secrète : elle donne accès à vos transferts. Une clé supprimée ne fonctionne plus aussitôt.
Authorization: Bearer mlk_… Si votre outil ou votre hébergement ne transmet pas l’en-tête Authorization, envoyez la même clé dans l’en-tête X-API-Key :
X-API-Key: mlk_… 120 requêtes par minute et par clé. Taille, nombre de fichiers et durée de mise en ligne : ceux de votre offre.
Envoyer des fichiers en trois étapes
1. Créer le transfert
Donnez la liste des fichiers (nom et taille en octets). Avec « recipients », chaque destinataire reçoit un e-mail ; sans destinataire, vous obtenez un lien à partager. Facultatif : « 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. Déposer les fichiers
Envoyez chaque fichier par morceaux de « chunk_size » octets au plus, dans l’ordre : « offset » est le nombre d’octets déjà reçus pour ce fichier. Après une coupure, « status » indique où reprendre (réponse 409 : reprenez à « received »). Si votre hébergement ou votre pare-feu filtre les adresses, envoyez la position dans l’en-tête X-Upload-Offset plutôt que dans l’adresse.
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. Terminer
Quand tous les fichiers sont reçus, terminez : le transfert est mis en ligne et les e-mails partent. Le lien est celui de la première réponse (« url »). Cette étape est obligatoire. Le serveur range ensuite les fichiers : pendant ce temps, « status » vaut « finalizing ». Un transfert reçu en entier mais jamais terminé est terminé automatiquement après 30 minutes sans activité.
curl -X POST https://misi.link/api/upload/complete -H "Content-Type: application/json" \
-d '{"id": "aB3dE5fG", "token": "…"}'
Envoi direct dans le stockage (conseillé pour les gros fichiers)
Avec « direct »: true, votre logiciel envoie le fichier directement dans le stockage de misi.link, sans passer par le serveur : débit maximal et mise en ligne en quelques secondes à la fin. Un seul fichier par transfert (plusieurs : réunissez-les dans une archive). Sans cette option, ou si la réponse contient « direct »: null, déposez les morceaux avec « chunk » comme plus haut.
- Créez le transfert avec « direct »: true. La réponse indique « direct.part_size » (taille de chaque partie, la dernière est plus courte), « direct.parts » (nombre de parties) et « direct.sign ».
- Demandez les adresses signées à « sign » (identifiant, jeton et numéros de parties, 100 au plus par appel ; valables 6 heures), puis envoyez chaque partie avec un simple PUT à son adresse, sans en-tête d’authentification. Les parties peuvent partir en parallèle et être renvoyées en cas d’échec.
- Terminez avec « complete ». S’il manque une partie, la réponse est « incomplete » avec la liste « missing » : renvoyez ces parties puis rappelez « complete ». Chiffrement de bout en bout : chaque partie est une suite de segments chiffrés entiers, dans l’ordre (voir l’exemple).
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]}
Chiffrement de bout en bout (facultatif)
Votre logiciel chiffre le fichier avant de l’envoyer : misi.link ne reçoit et ne stocke que des données illisibles. La clé n’est jamais envoyée au serveur : elle se trouve dans le lien, après « # ». Le destinataire télécharge et déchiffre dans son navigateur, sur la page habituelle.
- Tirez au hasard une clé de 16 octets, puis créez le transfert avec « e2e »: true et un seul fichier (sa taille en clair, en octets). Pour plusieurs fichiers, réunissez-les d’abord dans une archive.
- Clé de chiffrement : HKDF-SHA-256 de la clé (sel « misilink-e2e-v1 », info = identifiant du transfert, 16 octets), pour AES-GCM.
- Découpez le fichier en segments de 4 Mio (4 194 304 octets). Segment n° i : nonce = 4 octets nuls puis i sur 8 octets (big-endian) ; données associées = i sur 8 octets puis 1 octet (1 pour le dernier segment, sinon 0). Chaque segment chiffré fait 16 octets de plus : déposez ces octets avec « chunk », dans l’ordre (« e2e.encrypted_size » au total), puis terminez avec « complete ».
- Conseillé : « e2e_verify » = HKDF-SHA-256 de la clé (même sel, info « misilink-verify », 32 octets) en base64url. misi.link refuse alors un téléchargement avec une mauvaise clé, sans jamais connaître la bonne.
- Lien à partager : « url » + « # » + la clé en base64url (22 caractères). Avec des destinataires (« recipients »), ajoutez « e2e_key » (la clé) : misi.link la garde scellée pour l’insérer dans les liens des e-mails.
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}} Exemple complet en Python (bibliothèque 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 [])))
Autres requêtes
GET /api/v1/transfers- Vos transferts, 50 par page (?page=2…).
GET /api/v1/transfers/{id}- Un transfert : état, lien, fichiers, nombre de téléchargements, date d’expiration.
DELETE /api/v1/transfers/{id}- Supprimer un transfert (le lien ne fonctionne plus).
GET /api/v1/me- Votre compte, votre offre et ses limites, taille des morceaux.
Dates au format ISO 8601 (UTC), tailles en octets.
Réponses et erreurs
Toutes les réponses sont en JSON, avec « ok ». En cas d’erreur : « error » (code court) et « message ». Codes HTTP : 401 clé absente ou non valable, 403 offre sans API, 404 introuvable, 409 morceau au mauvais endroit, 422 requête refusée, 429 trop de requêtes (attendez une minute).