Import

POST /boards/:id/import takes a list of https URLs and brings them onto a board. Images are downloaded and stored; anything else becomes a link card pointing at the original, and the response says which happened to each item. Every card is stamped with a source_url identifier and the import resolves on it, so running the same manifest twice updates the same cards rather than duplicating them.

Reference rarely starts life in Clusters. It is in a shared drive folder, on a CDN, in somebody's export from another tool. import is how it gets here without being re-uploaded by hand.

curl -X POST https://clusters.soleilpictures.com/api/v1/boards/$BOARD/import \
  -H "Authorization: Bearer undefined…" \
  -H "Content-Type: application/json" \
  -d '{
    "items": [
      { "url": "https://cdn.example.com/ref/diner-01.jpg", "title": "Diner counter" },
      { "url": "https://cdn.example.com/ref/diner-02.jpg" },
      { "url": "https://example.com/treatment-v4.pdf" }
    ]
  }'
{
  "board_id": "…",
  "imported": 2,
  "updated": 0,
  "failed": 0,
  "items": [
    { "url": "https://cdn.example.com/ref/diner-01.jpg", "ok": true, "kind": "image", "card_id": "…" },
    { "url": "https://cdn.example.com/ref/diner-02.jpg", "ok": true, "kind": "image", "card_id": "…" },
    { "url": "https://example.com/treatment-v4.pdf", "ok": true, "kind": "link",
      "note": "application/pdf is not an image — linked instead of imported", "card_id": "…" }
  ]
}

It is safe to run twice

This is the point of the endpoint, not a footnote. Every imported card is stamped with an identifier in the source_url scope holding the URL it came from (an address longer than 200 characters is held as sha256: and the hex of its digest, and /resolve hashes it the same way, so you still look it up by the address), and the import resolves on that identifier. Run the same manifest again and the same cards are updated rather than added a second time — the response tells you which, in imported versus updated.

That means an import that half-failed does not need unpicking. Fix the dead links and run the whole thing again.

A picture or link that is already on another cluster in the workspace — a second moodboard with the same references, or a cluster that was deleted and can still be restored — is still imported here. An identifier belongs to one card, so it stays with the first one and /resolve keeps finding that card; the copy here is found by its source address instead, so running the import again on this cluster updates it too. The item's note says it is a copy when the other cluster is one you can open.

It also means you can find anything you brought in:

curl "https://clusters.soleilpictures.com/api/v1/resolve?scope=source_url&value=https://cdn.example.com/ref/diner-01.jpg" \
  -H "Authorization: Bearer undefined…"

What each source becomes

The source isYou get
An image format the API stores (audio, doc, file, image, link, note, palette, pdf, video covers the card kinds; see Images)An image card — the bytes are copied into your workspace
Anything else — a PDF, a web page, a videoA link card pointing at the original, with a note saying why

Only images are copied. Large media goes through the multipart upload endpoints, which stream from the client rather than pulling gigabytes through the API — a link card is the honest answer here rather than a silent half-import.

Limits and refusals

  • At most 100 items per call.
  • Each source must be an https URL on a public host. Internal and link-local addresses are refused, and one bad address fails the whole manifest before anything is fetched — an import that silently skipped it would still have told you the manifest was fine.
  • Each image is subject to the same 25 MB ceiling as a direct upload, and the same storage allowance.
  • Redirects are followed, but every hop has to pass the same public-host rule. A source that redirects to an internal address fails that item with source_refused; the rest still import.
  • A source that does not answer within 15 seconds fails that item; the rest still import.
  • The same URL twice in one manifest is refused: both entries would race for the same identifier and one would silently win.

Checking a list first

curl -X POST https://clusters.soleilpictures.com/api/v1/boards/$BOARD/import \
  -H "Authorization: Bearer undefined…" \
  -H "Content-Type: application/json" \
  -d '{ "items": [ … ], "dry_run": true }'

Validates every URL and creates nothing. Worth doing before pointing a hundred-item manifest at a board.

How it is laid out

Imported cards are arranged as justified rows by default — equal-height rows at each picture's true aspect ratio, flush on both edges. Pass layout to choose another, or re-run POST /boards/:id/arrange afterwards to try a different one.

Name a section in an item's group and it goes in the board's group of that name, made with it when the board has none, as a card's does. An import into sections is placed as cards naming them are: a new section laid out as auto lays one out, under what is already there, and pictures for a section the board has beside that section's cards. Bringing in a moodboard a section at a time — streets, faces, light — is one import each.

Items as plain strings

An item can be just the address: "items": ["https://cdn.example.com/ref/diner-01.jpg"] is the same as { "url": "…" }, and the two can be mixed in one list. A title on an item becomes the picture's caption, and a group puts it in that section. The MCP tool import_urls takes the same, and its titles list by position.

Positioning and metadata

Items accept the same x, y, w, h as cards, and the same props and identifiers as metadata. Anything without coordinates is arranged around what is already on the board.

Your own properties are kept, but the import always records where a card came from and that record wins — an import cannot be made to misreport its own source.

Frequently asked questions

What happens if I run the same import twice?

The same cards are updated, not duplicated. Each imported card carries a source_url identifier and the import resolves on it, so a run that half-failed can simply be repeated.

What if a picture is already on another cluster?

It is still imported here. The source_url identifier stays with the first card, so /resolve keeps finding that one, and the copy here is found again by its source address, so running the import again on this cluster updates it too. The item's note says it is a copy when the other cluster is one you can open.

Can it import a PDF or a video?

Not the file itself. Only images are downloaded and stored; everything else becomes a link card pointing at the original, and the response says so per item. Use the multipart upload endpoints for large media, which stream from the client instead of through the API.

What happens if one URL is dead?

That item fails and the rest still import. Every item reports its own outcome, and the envelope counts what landed.

Can I check a long list before running it?

Yes. Pass dry_run to validate every URL without fetching or creating anything.

Machine-readable: /docs/api/import.md · /llms.txt
What changed and when: /changelog