For the complete documentation index, see llms.txt. This page is also available as Markdown.

Note Images

Notes can embed images. Images are stored in the case's file storage under Cases/{case}/Documents/Note Images/, tracked as note attachments (with MD5/SHA-1/SHA-256 hashes), and served back through short-lived signed URLs whenever a note is read.

Constraints (all upload paths):

  • Allowed types: image/png, image/jpeg, image/gif, image/webp

  • Maximum size: 25 MB

There are two ways images get into a note:

  1. Automatic ingestion — embed an external URL or a base64 data URI directly in the note content you write; the server converts it into an attachment for you. See External and inline images in note content.

  2. Explicit upload endpoints, from highest- to lowest-level:

Endpoint
Use when

POST /v1/notes/{uuid}/images

You have the image bytes locally. Recommended for API clients — one call does everything.


External and inline images in note content (automatic ingestion)

You don't have to call an upload endpoint to get an image into a note. Whenever note content is written — POST /v1/notes, PATCH /v1/notes/{uuid}, or PATCH /v1/notes/{uuid}/append — the server scans the incoming note_data for images it doesn't already track and converts them into normal note attachments:

  • <img src="https://..."> — downloaded server-side, with the same protections as download-image-from-url (public hosts only, image content types only, 25 MB cap)

  • <img src="data:image/png;base64,..."> — decoded and stored (base64 data URIs only)

Each ingested tag is rewritten in place to standard attachment markup (src="" + data-uuid + alt), and from then on behaves exactly like an uploaded image: stored under the note's case, hashed, and served through a fresh signed URL every time the note is read.

Why this happens: stored notes and their version history should only reference content the system controls. An external URL can change or vanish after the note is written — silently altering what a historical note appears to say — and every read of a hotlinked image tells the third-party host who is viewing the note, and when. Ingestion pins the image bytes at the moment of writing.

Details worth knowing:

  • Idempotent — images already carrying a data-uuid are left untouched, so re-sending content or appending to a note never re-downloads anything.

  • Deduplicated — the same src appearing multiple times in one write is stored once, with every tag pointing at the same attachment.

  • Bounded — at most 10 images per write are ingested, within an overall time budget of ~30 seconds. Images beyond the cap are reported (see below), not silently dropped.

  • Reads never fetch — ingestion only runs when content is written.

  • Unresolved external images render as broken/blank in PDF and DOCX exports; the exporters never fetch remote URLs.

When ingestion fails: unresolved_images

A failed image never fails the write. The original tag stays in the content exactly as sent, and the response gains an unresolved_images array describing each image that was left behind (the field is absent when everything ingested cleanly):

Common reasons: the host was unreachable or returned an error, the URL resolves to a blocked (internal) address, the content type is not an allowed image type, the image exceeds 25 MB, or the write contained more than 10 new images. data: URI srcs are truncated in the report to keep responses small.

An unresolved external image is retried the next time the note's full body is rewritten via PATCH (its tag still has no data-uuid, so it is a candidate again). Appends only scan the appended fragment, not the existing body.

Example — create a note with an external image

Example — append a locally generated image as a data URI

Useful for automation that produces charts or screenshots: no separate upload call, and the server extracts the base64 payload into an attachment instead of bloating the stored note.

Example — fall back to a direct upload when ingestion fails

If the server can't reach a source (for example, it requires credentials the server doesn't have), fetch the bytes yourself and use the upload endpoint, embedding the returned markup:


Uploads the image, hashes and stores it, records the attachment, and returns ready-to-embed <img> markup — one multipart request.

Request

Part
Type
Required
Description

file

file

yes

The image. The part's content type must be one of the allowed image types.

The note (path parameter uuid) must be linked to a case — the image is stored under that case.

Response — 201 Created

  • url — signed download URL for immediate use (expires).

  • markup — the <img> tag to place in the note body. Its src is intentionally empty: whenever the note is read with include_content=true, the server rewrites src to a fresh signed URL, matching the attachment by data-uuid. Embed the markup as-is; don't bake the signed url into the note.

Example — upload an image and embed it in the note


Errors

Automatic ingestion failures are not HTTP errors: the note write still succeeds (200/201) and each failed image is reported in the response's unresolved_images array instead. The statuses below apply to the explicit upload endpoints.

Status
Body
Cause

400

{"message": "Unsupported image type"}

Content type not in the PNG/JPEG/GIF/WebP allowlist.

400

{"message": "Image exceeds maximum allowed size"}

Larger than 25 MB.

400

{"message": "Image file is empty"}

Zero-byte upload.

400

{"message": "Invalid multipart upload"}

Malformed multipart body on /{uuid}/images.

400

{"message": "Note is not linked to a case"}

Upload target note has no case, so there is no storage location.

400

{"message": "Case storage not provisioned"}

The case has no storage path yet.

400

{"message": "filename must not contain path separators", ...}

filename contains /, \, or ...

400

Fetch-specific message

download-image-from-url: unreachable URL, non-image content, blocked internal address, too large.

404

{"message": "Note not found"} / {"message": "Note not found for case"} / {"message": "Case not found"}

Bad note_uuid / case_uuid, or the note is not in the given case.

401

Unauthorized

Missing or invalid API key.

Last updated

Was this helpful?