Documentação da API
Envie ficheiros a partir das suas próprias ferramentas (scripts, programas, servidores): crie uma transferência, carregue os ficheiros por partes, obtenha a ligação.
Chave disponível com os planos Pro e Empresas, em A minha conta › API. Preços
Autenticação
Crie uma chave em A minha conta › API e envie-a no cabeçalho Authorization de cada pedido. Mantenha-a secreta: dá acesso às suas transferências. Uma chave eliminada deixa de funcionar de imediato.
Authorization: Bearer mlk_… Se a sua ferramenta ou o seu alojamento não transmitir o cabeçalho Authorization, envie a mesma chave no cabeçalho X-API-Key:
X-API-Key: mlk_… 120 pedidos por minuto e por chave. Tamanho, número de ficheiros e duração: os do seu plano.
Enviar ficheiros em três passos
1. Criar a transferência
Indique a lista de ficheiros (nome e tamanho em bytes). Com «recipients», cada destinatário recebe um e-mail; sem destinatários, obtém uma ligação para partilhar. Opcional: «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. Carregar os ficheiros
Envie cada ficheiro por partes de no máximo «chunk_size» bytes, por ordem: «offset» é o número de bytes já recebidos desse ficheiro. Após uma interrupção, «status» indica onde retomar (resposta 409: retome em «received»). Se o seu alojamento ou firewall filtrar os endereços, envie a posição no cabeçalho X-Upload-Offset em vez de no endereço.
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. Concluir
Quando todos os ficheiros forem recebidos, conclua: a transferência fica disponível e os e-mails são enviados. A ligação é a da primeira resposta («url»). Este passo é obrigatório. Depois, o servidor guarda os ficheiros: entretanto, «status» vale «finalizing». Uma transferência recebida por completo mas nunca concluída é concluída automaticamente após 30 minutos sem atividade.
curl -X POST https://misi.link/api/upload/complete -H "Content-Type: application/json" \
-d '{"id": "aB3dE5fG", "token": "…"}'
Envio direto para o armazenamento (recomendado para ficheiros grandes)
Com «direct»: true, o seu programa envia o ficheiro diretamente para o armazenamento do misi.link, sem passar pelo servidor: velocidade máxima e disponível em poucos segundos no fim. Um ficheiro por transferência (vários: junte-os num arquivo). Sem esta opção, ou se a resposta contiver «direct»: null, envie as partes com «chunk» como acima.
- Crie a transferência com «direct»: true. A resposta indica «direct.part_size» (tamanho de cada parte, a última é mais curta), «direct.parts» (número de partes) e «direct.sign».
- Peça os endereços assinados a «sign» (identificador, token e números das partes, no máximo 100 por chamada; válidos 6 horas) e envie cada parte com um simples PUT para o seu endereço, sem cabeçalho de autenticação. As partes podem ser enviadas em paralelo e reenviadas em caso de falha.
- Termine com «complete». Se faltar uma parte, a resposta é «incomplete» com a lista «missing»: reenvie essas partes e chame «complete» de novo. Encriptação ponto a ponto: cada parte é uma sequência de segmentos encriptados inteiros, por ordem (ver o exemplo).
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]}
Encriptação ponto a ponto (opcional)
O seu programa encripta o ficheiro antes de o enviar: o misi.link só recebe e guarda dados ilegíveis. A chave nunca é enviada ao servidor: está na ligação, depois de «#». O destinatário descarrega e desencripta no navegador, na página habitual.
- Gere ao acaso uma chave de 16 bytes e crie a transferência com «e2e»: true e um único ficheiro (o seu tamanho em claro, em bytes). Para vários ficheiros, junte-os primeiro num arquivo.
- Chave de encriptação: HKDF-SHA-256 da chave (sal «misilink-e2e-v1», info = identificador da transferência, 16 bytes), para AES-GCM.
- Divida o ficheiro em segmentos de 4 MiB (4 194 304 bytes). Segmento n.º i: nonce = 4 bytes nulos e depois i em 8 bytes (big-endian); dados associados = i em 8 bytes e depois 1 byte (1 para o último segmento, senão 0). Cada segmento encriptado tem mais 16 bytes: envie estes bytes com «chunk», por ordem («e2e.encrypted_size» no total), e termine com «complete».
- Recomendado: «e2e_verify» = HKDF-SHA-256 da chave (mesmo sal, info «misilink-verify», 32 bytes) em base64url. O misi.link recusa então uma transferência com uma chave errada, sem nunca conhecer a certa.
- Ligação a partilhar: «url» + «#» + a chave em base64url (22 caracteres). Com destinatários («recipients»), acrescente «e2e_key» (a chave): o misi.link guarda-a selada para a incluir nas ligações dos 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}} Exemplo completo em 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 [])))
Outros pedidos
GET /api/v1/transfers- As suas transferências, 50 por página (?page=2…).
GET /api/v1/transfers/{id}- Uma transferência: estado, ligação, ficheiros, número de transferências, data de expiração.
DELETE /api/v1/transfers/{id}- Eliminar uma transferência (a ligação deixa de funcionar).
GET /api/v1/me- A sua conta, o seu plano e os respetivos limites, tamanho das partes.
Datas no formato ISO 8601 (UTC), tamanhos em bytes.
Respostas e erros
Todas as respostas são JSON, com «ok». Em caso de erro: «error» (código curto) e «message». Códigos HTTP: 401 chave ausente ou inválida, 403 plano sem API, 404 não encontrado, 409 parte no sítio errado, 422 pedido recusado, 429 demasiados pedidos (aguarde um minuto).