PUT /transfers/{transfer_id}/attachment

Upload the supporting document of a transfer — the invoice, contract or other document that justifies the payment.

An attachment is required on every transfer. POST /transfers/{id}/confirm refuses to submit a transfer that has no attachment (errorCode 10022), so upload it after creating the transfer and before confirming it.

Where it fits

  1. POST /transfers — create the transfer. It is created and nothing has been sent yet.
  2. PUT /transfers/{id}/attachment — upload the supporting document.
  3. POST /transfers/{id}/confirm — submit the payout.

All three steps must happen before the transfer's expiredAt.

Request

PUT /public/transfers/2WZF419iKXNzxkE2p9kEJI3knGS/attachment HTTP/1.1
Host: api.reasy.fr
x-api-key: YOUR_API_KEY
Content-Type: application/pdf

<the raw bytes of the file>
PartRequiredDescription
transfer_id (path)yesThe REasy id returned by POST /transfers. The ext-{externalId} form is not supported on this endpoint.
x-api-key (header)yesYour organization's API key.
Content-Type (header)yesThe type of the file: application/pdf, image/jpeg or image/png. It must match the file you send — it tells our payout network how to read the document.
BodyyesThe file itself, as raw bytes. Not JSON, not base64, not multipart/form-data. Maximum size: 10 MB.

Rules:

  • One attachment per transfer. Uploading again replaces the previous file, so you can correct a wrong document.
  • Only while the transfer is created. Once the transfer is confirmed the attachment can no longer be added or replaced (errorCode 11002).
  • If you have several documents for one payment, merge them into a single PDF.

Response

200 OK with an empty body. The attachment is stored as soon as you receive the 200; you can confirm the transfer right away.

Errors

Errors on this endpoint are returned with HTTP 403 and the usual JSON body:

{ "message": "Transfer has already been submitted to payout partner.", "errorCode": 11002 }
HTTPerrorCodeMeaningWhat to do
40311004No transfer with this id exists for your organizationUse the REasy id from POST /transfers, not your externalId
40311002The transfer is no longer created (already confirmed, paid or failed)Nothing to upload; attachments are frozen once the transfer is confirmed
40310006The API key is not linked to an organizationContact REasy
40311003Temporary error while authorizing the uploadRetry; contact REasy if it persists
401 / 403—Missing or invalid x-api-keyCheck the header
413—The file is larger than 10 MBCompress the file or reduce the scan resolution
5xx—The file could not be storedRetry the upload — it is safe to repeat

Confirming without an attachment

If you call POST /transfers/{id}/confirm before uploading, the confirmation is refused:

{ "errorCode": 10022, "message": "The required invoice file for transaction 2WZF419iKXNzxkE2p9kEJI3knGS was not found." }

This refusal is recoverable. Nothing has been sent and the transfer is not failed: it stays created, and sendAmount + feeAmount stays reserved on your balance. Upload the attachment, then call confirm again — before expiredAt.

Examples

cURL

curl -X PUT "https://<base_url>/transfers/$TRANSFER_ID/attachment" \
  -H "x-api-key: $REASY_API_KEY" \
  -H "Content-Type: application/pdf" \
  --data-binary "@invoice-10042.pdf"

Use --data-binary (not -d, which alters the file) and always set Content-Type yourself.

JavaScript (Node.js 18+)

import { readFile } from "node:fs/promises";

const BASE_URL = "https://<base_url>";
const API_KEY = process.env.REASY_API_KEY;

async function uploadAttachment(transferId, filePath, contentType) {
  const res = await fetch(`${BASE_URL}/transfers/${transferId}/attachment`, {
    method: "PUT",
    headers: { "x-api-key": API_KEY, "content-type": contentType },
    body: await readFile(filePath),
  });
  if (!res.ok) {
    throw new Error(`REasy attachment upload failed: ${res.status} ${await res.text()}`);
  }
}

await uploadAttachment(transfer.id, "invoice-10042.pdf", "application/pdf");
await reasy("POST", `/transfers/${transfer.id}/confirm`); // see Getting Started for reasy()

PHP (8.1+)

<?php
const BASE_URL = 'https://<base_url>';
$apiKey = getenv('REASY_API_KEY');

function uploadAttachment(string $transferId, string $filePath, string $contentType): void
{
    global $apiKey;
    $ch = curl_init(BASE_URL . "/transfers/$transferId/attachment");
    curl_setopt_array($ch, [
        CURLOPT_CUSTOMREQUEST  => 'PUT',
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER     => ["x-api-key: $apiKey", "content-type: $contentType"],
        CURLOPT_POSTFIELDS     => file_get_contents($filePath),
    ]);
    $raw    = curl_exec($ch);
    $status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
    curl_close($ch);
    if ($status !== 200) {
        throw new RuntimeException("REasy attachment upload failed: $status $raw");
    }
}

uploadAttachment($transfer['id'], 'invoice-10042.pdf', 'application/pdf');
reasy('POST', "/transfers/{$transfer['id']}/confirm"); // see Getting Started for reasy()

Python (3.9+, requests)

import os
import requests

BASE_URL = "https://<base_url>"
session = requests.Session()
session.headers["x-api-key"] = os.environ["REASY_API_KEY"]


def upload_attachment(transfer_id: str, file_path: str, content_type: str) -> None:
    with open(file_path, "rb") as f:
        res = session.put(
            f"{BASE_URL}/transfers/{transfer_id}/attachment",
            data=f,
            headers={"content-type": content_type},
        )
    if res.status_code != 200:
        raise RuntimeError(f"REasy attachment upload failed: {res.status_code} {res.text}")


upload_attachment(transfer["id"], "invoice-10042.pdf", "application/pdf")
reasy("POST", f"/transfers/{transfer['id']}/confirm")  # see Getting Started for reasy()

Common mistakes

  • Wrapping the file in JSON, base64 or a multipart form. The body must be the file's bytes and nothing else.
  • A missing or generic Content-Type (application/octet-stream, or the application/x-www-form-urlencoded that cURL sends by default). Only application/pdf and image/* files can be read by our payout network; a file sent with another type can make the payout fail.
  • Using your externalId in the path. This endpoint only accepts the REasy id.
  • Confirming first. Confirm returns 10022 until the attachment is there — and once a transfer is confirmed, the attachment can no longer be changed.