Upload attachment
POST
/v1/journal-entries/{id}/attachmentsUploads an attachment (PDF, PNG, JPG, or JPEG) and links it to the specified transaction, a draft or a booked one, as long as it has no attachment yet (409 attachment_exists otherwise, even for an empty attachment). To give an existing empty attachment its file, use POST /v1/attachments/{id}/files. An entry linked to an invoice, or one Numina's bookkeeping agent is working on, returns 409 conflict. A file whose content does not match its declared type returns 400 invalid_file. The attachment is stored and can be viewed in the Numina UI. Numina does not read the file unless run_ocr or run_bookkeeping is "true".
Requires one ofjournal:writedocuments:writewrite:draftwrite:ledger
Path parameters
- Journal entry ID
Body
multipart/form-data
- File to upload (PDF, PNG, JPG, or JPEG; max 10MB)
- run_bookkeepingstringIf "true", Numina reads the file (OCR) and then runs the AI bookkeeping agent against the linked draft when the company has Numina AI on. The agent may change the draft's lines. What happens next depends on your scopes. Without
journal:book, the agent only reviews and never books. Withjournal:book, Autopilot can book the draft if Autopilot is on and its conditions are met, including that the draft is fully matched to a bank line. To book a specific entry, use POST /v1/journal-entries/{id}/book. The agent does not run on a booked entry. Defaults to "false".One oftruefalse - run_ocrstringIf "true", Numina reads the file (OCR) so its amounts, dates and parties show in the UI and the document becomes searchable. Defaults to "false", or "true" when run_bookkeeping is "true"; "false" together with run_bookkeeping "true" is rejected.One of
truefalse
Errors
400The upload is missing, too large, or not a valid file401Invalid or missing authentication403Missing scope, or the company is archived404The resource does not exist in this company409The entry already has an attachment (`attachment_exists`), is linked to an invoice, or Numina's bookkeeping agent is working on it (`conflict`; retry in a few minutes)429Rate limit exceeded500Internal Server Error
Every error has the same shape. See Errors.
curl -X POST https://api.numina.app/v1/journal-entries/550e8400-e29b-41d4-a716-446655440000/attachments \
-H "Authorization: Bearer numi_YOUR_API_KEY" \
-F file=@receipt.pdfimport { openAsBlob } from "node:fs";
const form = new FormData();
form.append("file", await openAsBlob("receipt.pdf"), "receipt.pdf");
const response = await fetch("https://api.numina.app/v1/journal-entries/550e8400-e29b-41d4-a716-446655440000/attachments", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.NUMINA_API_KEY}`,
},
body: form,
});
const data = await response.json();import os
import requests
response = requests.post(
"https://api.numina.app/v1/journal-entries/550e8400-e29b-41d4-a716-446655440000/attachments",
headers={
"Authorization": f"Bearer {os.environ['NUMINA_API_KEY']}",
},
files={"file": open("receipt.pdf", "rb")},
)
data = response.json()Response
{
"attachment": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"file_name": "string",
"status": "uploaded",
"created_at": "string"
}
}