NuminaDevelopers
Dashboard
Upload attachment

Upload attachment

POST
/v1/journal-entries/{id}/attachments

Uploads 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

  • idstring (uuid)Required
    Journal entry ID

Body

multipart/form-data

  • filefileRequired
    File to upload (PDF, PNG, JPG, or JPEG; max 10MB)
  • If "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. With journal: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_ocrstring
    If "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 oftruefalse

Errors

  • 400The upload is missing, too large, or not a valid file
  • 401Invalid or missing authentication
  • 403Missing scope, or the company is archived
  • 404The resource does not exist in this company
  • 409The 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 exceeded
  • 500Internal Server Error

Every error has the same shape. See Errors.

cURL
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.pdf
JavaScript
import { 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();
Python
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
201
{
  "attachment": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "file_name": "string",
    "status": "uploaded",
    "created_at": "string"
  }
}