# Soleil Clusters — complete documentation Generated from https://clusters.soleilpictures.com/docs. Terminology: a "cluster" in the UI is a "board" in the API and database. ======================================================================== # Soleil Clusters documentation URL: https://clusters.soleilpictures.com/docs Updated: 2026-08-08 Soleil Clusters is an infinite-canvas creative workspace for film, photo, design and brand teams. You collect references, storyboards, shot lists, scripts and schedules onto shared boards called clusters, and you can read and write all of it from your own code through the REST API or an MCP server. Everything Soleil Clusters does, written down. If you are looking for something specific, the sidebar is grouped by what you are trying to do rather than by where the button lives. Start here If you have never opened Clusters, read Getting started — it walks the path from signing up to a board you would actually show someone, in about five minutes. If you have used it for a week and want the vocabulary straight, read Core concepts. It is short, and it explains the one thing that confuses everybody: the interface says cluster, the API says board, and they mean the same object. For developers and AI agents The REST API reads and writes your boards from your own software. Authentication is a personal access token minted in the app; it acts as you and reaches exactly what your account can reach, under the same row-level security the app itself runs on. There is no second permission system to keep in sync. If you are wiring up an AI assistant, start at MCP instead — it is the same API behind a set of tools an agent can call directly. Three machine-readable entry points exist for agents: Resource | What it is /llms.txt | Curated index of every documentation page, with descriptions /llms-full.txt | The entire documentation corpus in one plain-text file Any page + .md | The raw Markdown for that page — e.g. /docs/api.md Guides, by what you are making These are walkthroughs rather than reference — the whole workflow for one kind of board, start to finish. What you can make with Clusters — the full index Mood board maker · Storyboard maker · Shot list maker · Look book maker Building a board with an AI assistant — what it can and cannot do for you Coming from another tool — PureRef, Milanote, Miro and the rest How to read these docs Every page opens with a short, self-contained answer to the question the page exists to answer. If that paragraph is all you needed, stop there. Facts that are also limits — card caps, file size ceilings, rate limits, prices — are not typed into these pages by hand. They are injected at build time from the code that enforces them, so a number here cannot drift from the number the product actually applies. Screenshots are deliberately sparse. The interface moves faster than screenshots can be maintained, and a stale screenshot is worse than none. Where a control is hard to find, the docs name the menu path instead. ## Frequently asked questions Q: Is Soleil Clusters free? A: Yes. The free Demo plan gives you 100 cards, unlimited clusters, and unlimited collaborators. Creator removes the card limit and adds any-file-type uploads on a 100GB drive. Q: What is a cluster? A: A cluster is a board. The interface says "cluster" everywhere; the API and database call the same object a board. They are the same thing, and the two words are used interchangeably in these docs where the API is involved. Q: Can AI agents use Soleil Clusters? A: Yes. Mint a personal access token under Settings, then use the REST API at /api/v1 or the MCP server. The token acts as you, under the same permissions your account has in the app. ======================================================================== # Getting started URL: https://clusters.soleilpictures.com/docs/getting-started Updated: 2026-08-08 Sign in with your email — there is no password, you get a one-time code. Create a cluster, drag images straight onto the canvas from your desktop, arrange them, and send a view-only link to anyone. No account is needed to view a shared board, and the free plan includes 100 cards and unlimited collaborators. From an empty account to a board you would show someone, in about five minutes. 1. Sign in Go to clusters.soleilpictures.com and enter your email address. You will get a one-time code — there is no password. New accounts land on a starter cluster with a few cards already on it, so the canvas is never a blank intimidating rectangle. You can delete all of it. 2. Make a cluster Click New cluster in the sidebar, or press ⌘K and type "create cluster". Name it after the thing it is for — a scene, a project, a pitch. Names are searchable later and "Untitled cluster" is not. 3. Get things onto the canvas The fastest way is to drag files straight from your desktop onto the canvas. Drop twelve images at once and they arrange themselves rather than landing in a pile. Everything else lives in two places: The tool rail down the left edge — select, pan, image, note, doc, cluster, grid, arrow. Right-click the canvas — an Add menu grouped into Cards, Visual, Web and Annotate, plus paste, select all, background colour and export. A few things worth knowing on day one: To do this | Do this Add a note | Press N, or right-click where you want it Add an image | Drag it in, or paste from the clipboard Add a link | Paste a URL onto the canvas — it unfurls into a real card Pan | Hold Space and drag, or press H Fit everything on screen | ⇧1 Undo | ⌘Z Double-clicking empty canvas opens an add menu right at your cursor, which is usually faster than crossing the screen to the rail. 4. Arrange it Drag cards around. Alignment guides appear as you move, and cards snap to each other — hold Alt to bypass snapping when you want something deliberately off. Select several cards and press ⌘G to group them. A group gets a name and an outline, moves as one, and can be commented on as a unit. Draw connections with the arrow tool (A). Arrows anchor to cards, so they stay attached when you move things. 5. Share it Click Share. You have two different things there, and the difference matters: Invite by email gives a named person Editor or Viewer access. Editors can change the board. This does not require them to pay — collaboration is free on every plan. Create a public link produces a view-only URL that works for anyone, with no account and no sign-in. You can set it to expire, and choose whether sub-clusters are included. Where to go next Core concepts — the vocabulary, in two minutes The canvas — every tool and gesture Cards — what each card type does Sharing — links, roles, expiry and public boards The API — read and write all of this from your own code ## Frequently asked questions Q: Do I need to install anything? A: No. Clusters runs in the browser. There are also iOS and Android builds and it installs as a PWA, but nothing is required to start. Q: Is there a password? A: No. Sign-in is a one-time code sent to your email address. There is no password to lose or rotate. Q: Can people I share with edit my board? A: Only if you invite them as an editor. A public link is view-only. Editors do not need a paid plan — collaboration is free on every tier. ======================================================================== # Core concepts URL: https://clusters.soleilpictures.com/docs/concepts Updated: 2026-08-08 Soleil Clusters has four nested ideas. A workspace holds your clusters. A cluster is a board, which holds cards and can hold other clusters without limit. A card is a single thing on that board — an image, a note, a document, a file. Every cluster can be viewed as an infinite canvas or as a file list, and both views show the same contents. Four ideas, nested inside each other. Once these are straight, the rest of the product is discoverable. Workspace A workspace is the outermost container. It holds clusters and it holds people. Your personal workspace is created for you automatically — the first time you sign in, and also the first time you create a board through the API without naming a workspace. You never have to set one up. You can belong to more than one workspace. The switcher at the top of the sidebar moves between them, and separates ones you own from ones shared with you. Cluster (board) A cluster is the unit of work: a moodboard, a scene, a project, a shot list. The interface calls it a cluster. The API, the database and the developer pages in these docs call the same object a board. This is a naming change that landed in the product before it landed in the code, and rather than pretend otherwise, both words appear here — cluster when describing the interface, board when describing an API payload. Clusters nest without limit. A cluster inside a cluster is just a card on the parent's canvas that happens to open into its own canvas. This is how a project becomes a folder tree without anybody deciding to build a folder tree. Nesting is cycle-safe. Dragging a cluster into one of its own descendants is refused rather than silently creating a loop. Card A card is one thing on a board. Images, notes, links, documents, PDFs, video, audio, colour palettes, shapes, grids, schedules, votes, and other clusters are all cards. Cards carry a position, a size and a stacking order, which is what makes the canvas a canvas rather than a list. Everything else about a card depends on what kind it is — see Cards. The free Demo plan allows 100 cards in total across every cluster you create. Clusters themselves are unlimited, and so are collaborators. Two views of the same cluster Every cluster has two views, and they are not two places: Canvas — the infinite surface. Position matters. This is where you arrange, connect, draw and compare. List — the same contents as a sortable, searchable file browser with table and gallery modes. This is where you find a specific thing. Switching views does not move or convert anything. A cluster is one set of contents with two ways to look at it, which is why the product describes every cluster as also being a drive. What is not a container Two things look like containers and are not: Groups are a selection on a canvas given a name and an outline. They travel together and can be commented on and tagged as a unit, but they do not have their own inside — the cards remain cards on the same board. Tags cut across everything. A tag is not a location; the same card can carry several, and a tag's detail view gathers every board, group and card that carries it from anywhere in the workspace. ## Frequently asked questions Q: What is the difference between a cluster and a board? A: Nothing. They are the same object. The interface says "cluster" and the API, database and these developer docs say "board". The word changed in the product before it changed in the code. Q: How deep can clusters nest? A: There is no depth limit. A cluster can contain clusters that contain clusters, as far as you want. Cycles are prevented — you cannot make a cluster its own ancestor. Q: Do I need a workspace before I can make anything? A: No. A personal workspace is created for you the first time you need one, including the first time you call the API. ======================================================================== # Keyboard shortcuts URL: https://clusters.soleilpictures.com/docs/keyboard-shortcuts Updated: 2026-08-08 Press ? anywhere in Soleil Clusters to see the shortcut list without leaving the page. The essentials are V to select, H or Space to pan, N for a note, A for an arrow, Cmd-K to search everything, Cmd-Z to undo, and Shift-1 to fit the whole board on screen. Press ? on any canvas to see this list in the app, where it shows the right modifier key for your platform. On Windows and Linux, every ⌘ below is Ctrl. Tools Key | Action V | Select / move H or Space | Pan the canvas N | Add a note D | Free-draw A | Arrow G | Add a grid Esc | Back to select · dismiss whatever is open Navigate Key | Action ⌘K or / | Search and commands ⌘B | Toggle sidebar ⌘. | Clean mode — hide all chrome ⌘K is the fastest way to get anywhere. It searches cluster names, card contents, notes, docs and tags, and it also runs commands — "share", "trash", "theme", "invite" all work as typed input. Edit Key | Action ⌘Z / ⌘⇧Z | Undo · redo ⌘C / ⌘X / ⌘V | Copy · cut · paste ⌘D | Duplicate ⌘A | Select all ⌘G | Group selection [ / ] | Send backward · forward ⌫ | Delete selection Undo is per-session and per-board. It undoes your own edits, not a collaborator's — in a shared board, undo that could revert someone else's work would be worse than no undo at all. View Key | Action ⌘0 | Reset zoom ⌘+ / ⌘− | Zoom in · out ⇧1 | Fit everything ⇧2 | Fit selection Space-drag | Pan with any tool active Zoom and pan are remembered per cluster. Reopening a board puts you back where you were rather than at the origin. Notes Key | Action Double-click | Edit a note or a title ⌘B / ⌘I / ⌘U | Bold · italic · underline @ | Mention a board, doc, or card Document These apply inside a document, not on the canvas. Key | Action ⌘⌥1 / ⌘⌥0 | Heading 1 · body ⌘⇧7 / ⌘⇧8 / ⌘⇧9 | Numbered · bullet · task list ⌘⇧H / ⌘E | Highlight · inline code ⌘⇧L / ⌘⇧E / ⌘⇧R | Align left · center · right ⌘K / ⌘⌥M | Link · comment ⌘F | Find and replace Gestures Not keys, but worth knowing: Double-click empty canvas opens an add menu at your cursor. Right-click anything for its full menu — cards, groups, and the canvas background each have their own. Hold Alt while dragging to bypass snapping. Paste a URL onto the canvas to create a link card that unfurls its preview. Drag files from your desktop straight onto the canvas. ## Frequently asked questions Q: How do I see shortcuts without leaving what I am doing? A: Press ? on any canvas, or click the help button in the corner of the canvas. The overlay lists every shortcut grouped by what it affects. Q: Do these work on Windows? A: Yes. Every Cmd shortcut listed here is Ctrl on Windows and Linux. The in-app overlay detects your platform and shows the right modifier. ======================================================================== # Coming from another tool URL: https://clusters.soleilpictures.com/docs/migrating Updated: 2026-08-08 There is no importer. Moving to Soleil Clusters means dragging your files in, which for a reference board is usually a single drag of a folder. What differs by tool is what you lose and what you gain — this page maps the concepts across from the six tools people most often arrive from. There is no import button. For a reference board that is less of a problem than it sounds — dragging a folder of images in takes about as long as an importer would, and they auto-arrange on arrival. What is worth knowing is how the concepts map. What transfers | Images and files | Drag them in. Any type on Creator. Screenplays | Import Final Draft .fdx or Fountain — see screenplay mode Text | Paste into notes or documents Layout | Does not transfer from anything Comments and history | Does not transfer from anything Concept mapping Their idea | Here Board / canvas / project | Cluster Folder | A nested cluster — nesting is unlimited Frame / panel | A grid cell Sticky note | A note Tag / label | A tag, which can also be a typed entity Comment | A comment, anchored to a card, group, point or text range Share link | A public link, always view-only By tool PureRef — the closest match for pure reference work. You gain collaboration, sharing and cloud storage; the tradeoff is that Clusters is a browser app rather than a native always-on-top window. Milanote — very similar shape. Boards become clusters, nesting maps directly, and the card model is comparable. Miro — the whiteboard-to-canvas mapping is direct. Clusters is built around media and reference rather than diagramming and facilitation. Boords and Storyboarder — storyboard tools. Panels become grid cells, and the script side becomes a screenplay document on the same board. StudioBinder — production management. Clusters covers the visual and document side and the schedule; it is not a call-sheet-and-crew-management system. Comparisons and alternatives Roundups of what else is out there, with pricing checked at the date shown: The best PureRef alternatives The best Milanote alternatives The best mood board apps Practical route in Pick one live project rather than migrating an archive. Drag its reference folder onto a new cluster; let it auto-arrange. Give things names — names are what search works on. Nest sub-clusters where the old tool had folders. Invite the team. Editors are free, so there is no seat maths. Archives are usually not worth moving. Leave them where they are and start with work that is live. Moving out again Worth knowing before you commit: board exports as PNG and PDF, document exports as PDF, Markdown, HTML, .fdx and Fountain, every original file downloadable as uploaded, and the REST API can read everything programmatically. ## Frequently asked questions Q: Is there an importer? A: No. Reference boards move by dragging the images in, which takes about as long as an importer would. Structured formats are the exception — screenplays import as Final Draft or Fountain files. Q: Will my layout survive? A: No. Positions do not transfer from any other tool. Dropped images auto-arrange, and auto-arrange by colour is usually a faster route to a good layout than reproducing the old one. Q: What actually transfers cleanly? A: Files, and screenplays. Everything else is a rebuild — which is why this page is about mapping concepts rather than migrating data. ======================================================================== # The canvas URL: https://clusters.soleilpictures.com/docs/canvas Updated: 2026-08-08 Every cluster opens as an infinite canvas. You pan with Space or H, zoom with Cmd and plus or minus, and place cards anywhere. A tool rail runs down the left edge, right-clicking gives you a full menu wherever you clicked, and your zoom and pan position are remembered per cluster so reopening resumes where you left off. The canvas is the default view of every cluster. It is an unbounded surface — position means something here, which is the whole point. Two images side by side are being compared; an image with a note under it is being annotated. If you want the same contents as a sortable file list instead, every cluster also has a list view. Moving around Action | How Pan | Hold Space and drag, or press H for the pan tool Zoom | ⌘+ / ⌘−, or pinch, or scroll with a modifier Reset zoom | ⌘0 Fit everything | ⇧1 Fit selection | ⇧2 Your position is saved per cluster. Close a board deep in a corner of the canvas and it reopens there, not at the origin. The tool rail Down the left edge: Select / move (V) — the default. Click to select, drag to move, drag on empty space to marquee-select. Pan (H) — grab the canvas itself. Add image — file picker. Dragging from your desktop is usually faster. Add note (N) — a rich-text note. Add doc — a document card. Add cluster — a nested board. Add grid (G) — a grid. Arrow (A) — connect two things. The + at the end of the rail opens the rest, grouped: Tools — Draw (D), Shape, Palette Create — File, Link, Schedule, Linked cluster Annotate — Comment, Vote Right-click Right-clicking is the fastest path to almost everything, and the menu differs by what is under the cursor. On empty canvas: Add (Cards / Visual / Web / Annotate), Paste, Select all, Background colour, Reset zoom, Export to PNG or PDF, and Clear all drawings. On a card: everything specific to that card kind, plus the universal operations — duplicate, delete, layer order, tag, comment, copy link. On a group: rename, outline shape and colour, add to group, ungroup, group comment, group tag. Double-clicking empty canvas opens the add menu right at your cursor. On a long canvas this beats travelling to the rail and back. Selection Click to select. Shift-click to add. Drag on empty canvas to marquee. With several cards selected you can move them as one, align them, delete them together, or press ⌘G to make them a group — which is a selection that persists, gets a name, and can be commented on as a unit. Drawing Press D for the freehand tool. Draw straight onto the canvas over and around cards; strokes are their own layer, so they do not attach to a card and do not move when you move one. The eraser removes whole strokes rather than nibbling at them, which is what you want when annotating in a hurry. Clear all drawings in the canvas right-click menu removes them in one go. For a bigger drawing surface, the sketch pad overlay covers the whole viewport. For a drawing that belongs to the board as an object you can move and resize, use an art canvas card instead. Background Right-click → Background colour. Seven presets plus a custom picker, set per cluster. Useful for separating a scratch board from a client-facing one at a glance. Getting it out Right-click → Export gives you the whole board as a PNG or a PDF. See Exporting a board for what is included and what is not. If you are building a specific kind of board, these walk through the whole workflow: mood board maker · reference board maker. ## Frequently asked questions Q: How big is the canvas? A: There is no boundary. Cards can go anywhere, and Shift-1 fits everything you have made back on screen no matter how far it spread. Q: Can I change the background? A: Yes. Right-click the canvas and pick a background colour — seven presets plus a custom picker. The setting is per cluster. Q: Does the canvas work on a tablet? A: Yes. Touch gestures pan and zoom, long-press opens the context menu, and the tool rail adapts. Drawing works with a stylus or a finger. ======================================================================== # Cards URL: https://clusters.soleilpictures.com/docs/canvas/cards Updated: 2026-08-08 A card is one thing on a board. Soleil Clusters has around fifteen kinds — image, note, link, document, PDF, file, video, audio, colour palette, shape, art canvas, grid, schedule, vote and nested cluster. Every card shares the same position, size, layer and selection behaviour, so what you learn on one applies to all of them. Everything on a canvas is a card. They differ in what they hold and nothing else — position, size, stacking order, selection, grouping, tagging, commenting and duplication work identically on all of them. Every kind Card | What it holds Image | A picture, with non-destructive adjustments Note | Rich text — checklists, mentions, formatting Link | A URL, unfurled into a real preview with title and thumbnail Doc | A whole multi-page document PDF | Page one as a thumbnail, opening into a full viewer File | Any other file — type icon, size, download Video | An inline player Audio | A waveform player with cover art Palette | A set of colours you can pull from Shape | Rectangle, ellipse, line, arrow, diamond, triangle, hexagon, star Art canvas | A bounded drawing surface that lives as a card Grid | A split-cell layout — storyboards, contact sheets Schedule | A real-date calendar you can drop things into Vote | An up/down poll anchored anywhere Cluster | A nested board, opening into its own canvas Linked cluster | A reference to a cluster that lives elsewhere Adding one Four ways, in rough order of speed: Drag a file in from your desktop. The type is detected and the right card kind is created — see Files and media. Paste. A URL becomes a link card, an image in your clipboard becomes an image card, text becomes a note. Double-click empty canvas for an add menu at your cursor. The tool rail or its + menu. Cards you add without specifying a position are placed in free space, so a batch of twelve images arranges rather than stacking on top of what is already there. What every card does Move by dragging. Resize from the corners. Hold Alt while dragging to ignore snapping. Layer order — [ and ] send backward and forward. Also in the right-click menu. Duplicate — ⌘D. Delete — ⌫. A toast offers undo; ⌘Z also works. Group — select several and press ⌘G. See Groups. Tag — right-click → tag. See Tags. Comment — right-click → comment, which anchors a bubble to that card. See Comments. Copy link — a deep link that opens the board with that card selected. Useful in a message or a doc. Titles Most cards have a title. Double-click it to edit. Titles are searchable in ⌘K and they are what shows in list view, so a card called "Untitled" is a card you will not find later. Shape cards are the exception — they have no title. If you need a labelled shape, put a note on top of it or use a group, which does have a name. The card limit The free Demo plan allows 100 cards in total across every cluster you create. Cards on clusters someone else owns do not count against you — if you are invited as an editor, you are spending their allowance, not yours. Creator removes the limit entirely. See Plans. Cards through the API The REST API exposes a deliberately narrower card model than the canvas: doc, file, image, link, note, video. An API caller can set position, size, title, body, HTML, URL, image key and colour, and nothing else — interior state that belongs to the editor is not writable from outside. ## Frequently asked questions Q: How do I change what kind a card is? A: You do not convert cards between kinds. Create the kind you want and move the content across. The one exception is dropping a file, which picks the right kind for you automatically. Q: Is there a limit on cards? A: The free Demo plan allows 100 cards across every cluster you create. Creator removes the limit. Clusters themselves are never capped. Q: What happens if I delete a card by accident? A: Cmd-Z undoes it. Deletions also show an undo toast. Deleting a whole cluster is a soft delete that stays in the trash for 30 days. ======================================================================== # Images and photo editing URL: https://clusters.soleilpictures.com/docs/canvas/images Updated: 2026-08-08 Drag images onto a canvas and they upload and arrange themselves. Every image carries non-destructive adjustments — exposure, contrast, saturation and the rest — that never touch the original file. Click an image to open it full screen, and download it either as shot or with your adjustments baked in. Images are the reason most boards exist. Getting them in is meant to be thoughtless: drag a folder's worth onto the canvas and they upload in parallel and lay themselves out rather than landing in a heap. Adding images Drag from the desktop — many at once is fine. Paste from the clipboard. The image tool in the rail, for a file picker. Soleil Scout — text them from your phone. Uploads go straight to storage from your browser. Large batches run a few at a time so one enormous file cannot block the rest. HEIC and HEIF from an iPhone are handled, including the awkward case where the browser reports no MIME type at all. Progressive loading An image appears in three stages: a tiny blurred placeholder that arrives almost instantly, then a preview, then the full file. On a board with hundreds of images this is the difference between usable and not. Full resolution is fetched as you zoom in, and released again as you zoom out, so a board stays responsive no matter how much is on it. Adjustments Every image card has a full set of non-destructive adjustments — the kind you would expect in a photo tool, applied live on the canvas. Three ways in, depending on how much room you need: Edit popover — a compact panel beside the image with the essentials. Full screen editor — everything, with the image large. Lightbox — click an image to fill the screen; click again for 1:1 and drag to pan. Adjustments are stored as settings on the card. The uploaded file is never touched, so Reset returns you to the original exactly, and a collaborator who downloads the image gets to choose whether your edits come with it. Adjustments live on the card, not the file. Duplicate the card and you get an independent copy of the settings — useful for comparing two grades of the same still side by side. Downloading From the lightbox or the card menu. You choose between the original file and a version with your adjustments baked in. Auto-arranging a moodboard Select a set of images and use auto-arrange to lay them out as a colour-ordered masonry grid — images sorted so that neighbouring ones relate tonally rather than by the order you happened to drop them. It is the fastest way to turn a pile of references into something presentable. For a fixed layout with defined cells instead, use a grid. Cropping and thumbnails Cluster cover images use the same machinery: right-click a cluster → Upload custom thumbnail to pick your own, with a 16:9 crop and reposition step. Reset to auto thumbnail returns to the generated miniature of the board itself. Limits Images are not size-capped on any plan. The caps that exist are on video, audio and PDF for free accounts. Storage is counted against your account quota — Creator accounts get 100GB. The meter is in Settings → Billing. Building a reference wall specifically? The mood board maker guide walks the whole workflow. ## Frequently asked questions Q: Do adjustments change my original file? A: No. Adjustments are stored as settings on the card and applied at display time. The uploaded file is never modified, and resetting returns you to the original exactly. Q: Why does an image look blurry for a moment? A: Images load in tiers — a tiny blurred placeholder first, then a preview, then full resolution. On a board with hundreds of images this is what keeps it usable. Q: Does it handle photos straight off an iPhone? A: Yes, including HEIC and HEIF, even when the browser reports no file type at all. ======================================================================== # Notes URL: https://clusters.soleilpictures.com/docs/canvas/notes Updated: 2026-08-08 A note is a rich-text card you can put anywhere on a canvas. Press N to add one. Notes support formatting, checklists and @-mentions of other boards, docs and cards, they auto-link URLs into previews, and two people can type in the same note at once and see each other's cursors. A note is the general-purpose card: a caption, a brief, a to-do list, a paragraph of context beside a set of images. Press N, or right-click where you want it. For anything longer than a few paragraphs, use a document instead — it has pages, a proper toolbar and export. Writing Double-click a note to start editing. Click away to commit; press Esc to cancel. Formatting works as you would expect: ⌘B, ⌘I, ⌘U for bold, italic and underline. A toolbar appears at the bottom of the canvas while a note is focused, carrying colour, size, font and alignment. With the caret merely placed in a note and nothing selected, formatting applies to the whole note rather than doing nothing. This is deliberate — it is almost always what you meant. Checklists Start a line with a checkbox from the toolbar to make a task list. Boxes are clickable directly on the canvas without entering edit mode, so a note can serve as a live shot list or a packing list that anyone on the board can tick off. Mentions Type @ to mention another cluster, document, card, tag or person. The mention becomes a real link with a hover preview, and it registers as a backlink on the thing you mentioned — so that item's panel can show everywhere it is referenced. See Links and mentions. Automatic links Paste or type a URL into a note and it becomes a link, with a preview card on hover. You do not have to do anything to make this happen. Names that match an existing tag or entity are also detected and quietly underlined, so a note that says "Diner" connects itself to the Diner entity you already created elsewhere. Working together Notes are collaborative in real time. Two people can type in the same note at once, with visible carets showing who is where. Edits merge — there is no last-write-wins overwrite and no lock. Colours and defaults A note's background and text colour are set from the toolbar or the right-click menu. Text colour is always resolved for readability, so a note stays legible if someone switches between light and dark themes. To stop setting the same thing every time, Settings → Defaults sets the background, text colour, font and size every new note starts with. Spellcheck Browser spellcheck and Grammarly are switched off inside notes on purpose. Their injected markup conflicted with the collaborative editor and could corrupt formatting mid-sentence. This is not a setting. ## Frequently asked questions Q: Can two people edit the same note at once? A: Yes. Notes are collaborative in real time, with visible carets for whoever else is typing. Edits merge rather than overwriting each other. Q: Why is there no spellcheck underline? A: Browser and Grammarly spellcheck are deliberately disabled inside notes. Their injected markup fought with the collaborative editor and corrupted formatting. Q: How do I resize a note? A: Drag its corner. Notes also grow downward on their own as you type past the bottom edge. ======================================================================== # Shapes and drawing URL: https://clusters.soleilpictures.com/docs/canvas/shapes-and-drawing Updated: 2026-08-08 Press D to draw freehand anywhere on a canvas. Shapes are proper cards — rectangle, ellipse, line, arrow, diamond, triangle, hexagon and star — with stroke, fill, width and dash controls. There is also a full-screen sketch pad for a bigger surface, and an art canvas card for a drawing that belongs to the board as a movable object. Four different things that all involve making marks, for four different situations. Free-draw Press D. Draw anywhere — over cards, between them, across the whole board. Strokes live on the board's own drawing layer. They do not attach to a card, so moving a card does not drag your annotation with it. This is the right behaviour for circling three images and writing "these" beside them, and the wrong behaviour if you wanted the mark to belong to one image — use an art canvas for that. The eraser removes whole strokes rather than parts of them. Clear all drawings in the canvas right-click menu wipes the layer. Shapes From the rail's + menu → Tools → Shape. Eight kinds: rectangle, ellipse, line, arrow, diamond, triangle, hexagon and star. Each is a real card, so it moves, resizes, layers, groups and gets tagged like anything else. Per-shape controls: stroke colour, fill colour, stroke width, and solid or dashed. Settings → Defaults sets what new shapes start with, so you are not restyling every one. Shapes are the tool for boxing off regions of a board, drawing a rough frame, or building a simple diagram. For connecting two specific cards, use an arrow instead — arrows anchor to cards and stay attached when things move. Shapes have no title text. To label one, put a note on top of it, or wrap the area in a group, which does have a name. The sketch pad The rail's + → Tools → Draw also offers a full-screen sketch pad: an overlay covering the whole viewport, with the same stroke behaviour as inline drawing but without having to fight for canvas space. Useful for a quick diagram you want to think through at size before placing. Art canvas An art canvas is a card with a drawing surface inside it. Unlike free-draw, it has edges, and it behaves like every other card — move it, resize it, group it, tag it, put it in a grid cell. Reach for it when the drawing is a thing on the board rather than an annotation over the board. Colours Everything here shares one colour picker: saturation-value pad, hue slider, hex field, presets, an eyedropper, and the swatches from any palette cards on the board. Recently used colours are remembered, so a board built in one palette stays in that palette without effort. Tablets and styluses Pointer handling treats stylus, finger and mouse the same. Drawing gestures are guarded so the canvas will not start panning halfway through a stroke, which is the failure that makes drawing on a touch device unusable. ## Frequently asked questions Q: What is the difference between free-draw and an art canvas? A: Free-draw strokes sit on the board's own drawing layer and do not move when you move cards. An art canvas is a card with its own bounded drawing surface, so it moves, resizes and can be grouped like anything else. Q: How do I erase just part of a stroke? A: You cannot. The eraser removes whole strokes. Annotating quickly is the use case, and partial erasing was consistently slower than redrawing. Q: Can I draw with a stylus on a tablet? A: Yes. Pointer input is handled the same for stylus, finger and mouse, and the canvas will not pan out from under you mid-stroke. ======================================================================== # Arrows URL: https://clusters.soleilpictures.com/docs/canvas/arrows Updated: 2026-08-08 Press A and drag from one card to another to connect them. Arrows anchor to the cards rather than to fixed points, so they re-route automatically when you move things. Each arrow carries its own colour, thickness, head style, dashed or solid line, curve style and an optional label. Arrows are for showing relationships: this shot follows that one, this reference informed that frame, this note explains that image. Drawing one Press A, then drag from the source to the target. Card to card — the arrow anchors to both and follows them. Empty space to empty space — a free-floating arrow, behaving like a shape. Card to empty space — anchored at one end only. Press Esc or switch back to select (V) when you are done. Routing Arrows anchor to cards, not to coordinates. When you move a card, the arrow re-computes: it chooses which side of each card to leave from and arrive at, and curves to avoid cutting through the middle of things. Several arrows between the same pair fan out rather than stacking into one line, so each stays individually selectable and visible. You do not configure any of this. The point is that a board stays legible after you rearrange it, which is the moment hand-placed connectors normally fall apart. Styling Select an arrow and an inline toolbar appears: Control | Options Colour | Full picker, presets, board palette swatches Thickness | Thin through heavy Head style | Which ends carry a head, and its shape Line | Solid or dashed Curve | How much the path bends Label | Text carried on the arrow itself A label rides the arrow, so it moves with the connection instead of drifting away from it. Good for "then", "cut to", "same location". Deleting Select and press ⌫, or use the toolbar's delete. Deleting a card deletes the arrows anchored to it — an arrow to nothing is not a thing worth keeping. When to use something else Grouping several cards that belong together — use a group. A rough circle or box drawn over a region — use free-draw. A fixed sequence of frames in order — use a grid, which has a defined reading order. ## Frequently asked questions Q: Do arrows stay attached when I move a card? A: Yes. An arrow anchors to the card, not to a coordinate. Move either end and the arrow re-routes, picking the sides that give the cleanest path. Q: Can I draw an arrow that is not attached to anything? A: Yes. Drag from empty canvas to empty canvas for a free-floating arrow. It behaves like a shape. Q: Why do several arrows between the same two cards fan out? A: So they stay individually visible instead of overlapping into one thick line. The spread is automatic. ======================================================================== # Groups URL: https://clusters.soleilpictures.com/docs/canvas/groups Updated: 2026-08-08 Select several cards and press Cmd-G to make a group. A group has a name and a visible outline, moves as one object, and can be commented on and tagged as a unit. It is not a container — the cards stay on the same board, they are just marked as belonging together. A group says "these belong together" without moving anything anywhere. Select several cards and press ⌘G. You get a named, outlined region that travels as one object. Groups are not containers This is the distinction worth being clear about: A group marks cards on the current canvas as related. They remain cards on that canvas. A nested cluster is a different board. Its contents are somewhere else and you open into them. Use a group for the four frames that make up one beat. Use a nested cluster for a whole scene that deserves its own surface. What a group has A name. Shown on the outline. Searchable in ⌘K. An outline whose shape and colour you set from the right-click menu — useful for colour-coding sections of a big board at a glance. Group operations, all in the right-click menu: Action | Effect Rename | Change the label Outline shape / colour | Restyle the boundary Add to group | Pull another card in Ungroup | Remove the grouping; cards stay put Group comment | A comment anchored to the whole group Group tag | A tag applied to the group as a unit Group info | What is inside it Moving and selecting Dragging any member moves the whole group. To move one card out, drag it clear of the outline — it leaves the group. Clicking selects the group. Click again on a specific card to select just that card inside it. Comments and tags on a group Both attach to the group rather than to any one card. A comment on a group reads as being about the set — "this sequence runs long" — which is usually what someone reviewing a board wants to say. A tag on a group means the tag's detail view lists the group as a unit, rather than listing eight cards individually. When the canvas gets crowded Grouping is the main tool for keeping a large board readable. The app notices when a board's root canvas is getting crowded and will suggest it once — that prompt is the power reveal for groups, and it appears a single time, ever. ## Frequently asked questions Q: Is a group the same as a nested cluster? A: No. A group is a labelled selection on the same canvas — the cards never leave. A nested cluster is a separate board that opens into its own canvas. Use a group to say "these belong together", a cluster to say "this is its own thing". Q: Can a card be in two groups? A: No. A card belongs to at most one group. Overlapping membership made selection ambiguous in every case we tried. Q: What happens to the cards if I ungroup? A: Nothing. They stay exactly where they are and keep everything else about them. Only the grouping is removed. ======================================================================== # Grids URL: https://clusters.soleilpictures.com/docs/canvas/grids Updated: 2026-08-08 A grid is a card divided into cells, and every cell holds any kind of content — an image, text, a link, a video, a file, even another cluster. Split and merge cells by dragging the dividers, start from a preset like storyboard or 2x2, or generate an empty matrix of any size. Grids have a defined reading order, so a storyboard reads in sequence. A grid is a card that is divided up. Every cell holds real content, and the divisions are yours to move. Press G, or use the tool rail. What a cell holds Not just images. A cell takes an image, a piece of text, a link, a video, a file, or a nested cluster. A storyboard panel can be the frame in one cell and the action line in the cell beneath it. Drop a file directly into a cell to fill it. Each cell has its own menu for replacing, clearing, or — for images — the full set of photo controls. Building a layout Grids are not fixed rows and columns. The structure is a tree of splits, which is why an asymmetric storyboard layout is as easy as a regular one. Drag a divider to resize the cells either side of it. Split a cell to divide it further, horizontally or vertically. Merge adjacent cells back together. Everything reflows around what you changed. Presets Rather than starting from nothing: Preset | Layout single | One cell 2x2 | Four equal cells 3up | Three across storyboard-1-2 | One wide panel on top, two beneath db-row-1-3 | One, then a row of three Generate matrix builds an empty N×M grid at whatever size you name — the fast path to a contact sheet. Reading order Cells have a defined order, which is what makes a grid different from a set of images that happen to be arranged in rows. A storyboard reads in sequence, and that sequence is what exports and what a reader follows. Grid families Grids created from the same template stay linked as a family. Change the layout in one and the change can carry to the others — the mechanism that keeps a twelve-page storyboard from drifting into twelve slightly different layouts. Discovering grids Put four or more images on a board and the app suggests a grid, once. That suggestion is a power reveal: it fires a single time, ever, and never again. Grids versus schedules They look similar and are not the same thing: A grid is a spatial layout. Cells mean position. A schedule is a calendar. Cells mean dates — real ones, with month, week, day and hour views. If the cells represent time, you want a schedule. Building a storyboard specifically? The storyboard maker guide covers the whole workflow. ## Frequently asked questions Q: What can go in a grid cell? A: Images, text, links, videos, files and nested clusters. A cell is not restricted to pictures. Q: How do I change the layout after I have made it? A: Drag the dividers between cells. Cells split and merge as you go, and the rest of the grid reflows around what you changed. Q: Can several grids share a layout? A: Yes. Grids created from the same template stay linked as a family, so a layout change can carry across them — useful for a storyboard where every page should match. ======================================================================== # Schedules URL: https://clusters.soleilpictures.com/docs/canvas/schedule Updated: 2026-08-08 A schedule card is a real calendar living on your canvas. It has month, week, day and hour views, you can break a day down into hours and an hour into minutes, and you can drop any card into a time slot. The grid itself is read-only — all editing happens in the peek panel that opens when you click a slot. A schedule card is a calendar with real dates, sitting on the canvas next to everything it refers to. Shooting days beside the location photos; a release plan beside the assets. Add one from the rail's + menu → Create → Schedule. Views Four zoom levels of the same schedule: View | Shows Month | The whole month in a grid Week | Seven days side by side Day | One day, broken into hours Hour | One hour, broken into minutes Break a day down into hours, and an hour down into minutes, when a day needs that resolution. A prep week can sit at month view while the shoot day sits at hour view in the same board. Go to today returns you to now. The mini-calendar in the header jumps to any date without scrolling. Putting things in it Drop any card into a slot — a note, an image, a file, a link, a nested cluster. A schedule holds content, not just text labels, which is what separates it from a calendar app. The peek panel The calendar grid itself is read-only. Clicking a day or an hour opens the peek — a panel where all editing happens. This is deliberate. A dense calendar grid with inline editing produced constant mis-clicks: reaching for a slot and accidentally editing the one next to it. All edits going through the peek means the grid can be scanned and clicked confidently. What a schedule is for Anything where the cells mean time: Shoot days and unit moves Call sheets, with the location photo attached to the hour Delivery and release timelines A prep calendar with references attached to the day they are needed If the cells mean position rather than time — a storyboard, a contact sheet, a comparison — you want a grid. ## Frequently asked questions Q: Can I drop images and notes into a schedule? A: Yes. Any card can go into a time slot, not just text. A call sheet with the location photo attached to the right hour works exactly as you would expect. Q: Why can I not type directly into the calendar grid? A: The grid is deliberately read-only. Clicking a slot opens a peek panel and every edit happens there. Inline editing in a dense calendar produced constant mis-clicks. Q: How do I get back to today? A: The "Go to today" control, or the mini-calendar in the header to jump to any date. ======================================================================== # Vote cards URL: https://clusters.soleilpictures.com/docs/canvas/vote-cards Updated: 2026-08-08 A vote card is a small up/down poll you anchor anywhere on a canvas — beside an image, over a group, on empty space. Everyone with access can vote once, counts are live, and it exists so a team can settle a choice between options without turning it into a comment thread nobody reads. A vote card is deliberately the smallest possible decision tool: a ✓ and an ✗ with live counts, anchored wherever the decision actually is. Add one from the rail's + menu → Annotate → Vote. Anchoring Like comments, a vote can attach to: A card — "do we use this take?" A group — "does this sequence work?" An empty point on the canvas The board as a whole The anchor is the point. A vote floating in a sidebar, detached from the thing being voted on, is exactly what this avoids. Voting Anyone who can open the board can vote, including viewers. Asking for an opinion should not require handing over edit access. One vote per person. Vote again to switch; click your current choice to withdraw it. Counts update live for everyone looking. What it is good for Choosing between two takes or two grades A quick temperature check on a direction before anyone writes a brief Letting a client register a preference without giving them edit rights What it is not There is no deadline, no quorum, no result-locking and no notification when a vote lands. If a decision needs a record and a rationale, use a comment — those thread, resolve, and notify the people you mention. ## Frequently asked questions Q: Who can vote? A: Anyone who can open the board, including viewers. Voting does not require edit access, because asking someone's opinion should not require giving them the ability to change your board. Q: Can I change my vote? A: Yes. Vote again to switch, or click your current choice to withdraw it. Q: Is voting anonymous? A: The count is what is shown. Vote cards are designed for a quick read of the room, not a formal ballot. ======================================================================== # Colour palettes and the picker URL: https://clusters.soleilpictures.com/docs/canvas/palettes-and-color Updated: 2026-08-08 A palette card holds a set of colours on your board that everything else can pull from — notes, shapes, arrows, backgrounds and outlines. The picker has a saturation-value pad, hue slider, hex field, presets, an eyedropper, and swatches from any palette on the board. Recently used colours are remembered so a board stays in one scheme. Colour shows up in two places: as a palette card that holds a scheme, and as the picker that appears anywhere a colour is set. Palette cards A palette card is a set of swatches living on the board. Add one from the rail's + menu → Tools → Palette. It does two jobs. It documents the scheme — visible to anyone who opens the board — and it feeds the picker: every palette on a board contributes its swatches to the colour picker everywhere else on that board. Set a scheme once and the notes, shapes and arrows you make afterwards can be built from it in one click. The picker The same picker, everywhere a colour is chosen: Saturation-value pad and hue slider for picking by eye Hex field for an exact value Presets — the standard set Palette swatches — from every palette card on this board Recent colours — what you have been using Eyedropper — sample any pixel on screen, including from a reference image on the canvas Recent colours are the quiet one that matters most: a board built in one scheme stays in that scheme without anybody maintaining a system. Where colour applies Surface | What you can set Notes | Background, text Shapes | Stroke, fill Arrows | Line colour Groups | Outline colour Canvas | Background — seven presets plus custom Clusters | Cover colour You | Your presence colour Readability Text colour in notes and documents is resolved against the actual background it sits on, so a note authored in a light theme stays readable to someone reading in dark mode. You do not have to think about it and there is no setting. One reserved colour The gold accent is reserved for the interface: active state, current selection, focus. It is not part of the content palette. This keeps "this thing is selected" visually distinct from "this thing is gold", which matters on a board where the content is itself colourful. Building a colour-led look book? The look book maker guide covers the workflow end to end. ## Frequently asked questions Q: Where does the eyedropper sample from? A: Anywhere on your screen, using the browser's native colour picker. Sampling a colour straight out of a reference image on the canvas is the usual case. Q: Will my colours be unreadable in dark mode? A: No. Note and document text colours are resolved for contrast against their actual background, so text stays legible whichever theme a reader is using. Q: Can I reuse a palette across boards? A: Copy the palette card and paste it into another cluster. Its swatches then feed that board's picker. ======================================================================== # Snapping and alignment URL: https://clusters.soleilpictures.com/docs/canvas/snapping-and-alignment Updated: 2026-08-10 As you drag a card, guides appear showing where it lines up with the cards around it, and it snaps to their edges, centres and spacing. Hold Alt while dragging to switch snapping off for that drag. Nothing needs enabling — the guides only appear while you are actually moving something. Boards look better when things line up, and lining things up by hand is tedious. Guides do it while you drag. What happens as you drag Move a card and thin guide lines appear showing where it aligns with its neighbours: Edges — left, right, top, bottom Centres — horizontal and vertical Equal spacing — when three or more cards are evenly distributed, the calipers show it The card snaps to those positions as you approach them. Release and it lands exactly aligned. Turning it off for one drag Hold Alt while dragging. Snapping is suppressed for that drag only. This is deliberately momentary rather than a persistent setting: the case for disabling snapping is almost always "just this one card, just now", and a setting you have to remember to turn back on is a setting that stays wrong. Aligning a selection Select several cards and right-click. Under Arrange you get: Align — Left, Center, Right, Top, Middle, Bottom. Every selected card moves to that edge of the selection's own bounding box. Nothing is resized. Distribute — Horizontally or Vertically. The two outermost cards stay put and the ones between them are spread so the gaps are equal. Equal gaps rather than equal centres, because with cards of different sizes equal centres still looks uneven, which is the thing you were trying to fix. Needs at least three cards — two have no gap between them to even out. Tidying up Arrange → Tidy up repacks cards rather than nudging them: | Justified rows | Rows of equal height with every picture at its true shape, flush on both edges. The best answer for photographs — there are no holes in it. Masonry columns | Even columns, ordered by colour, so the board reads as a deliberate sweep rather than as the order things arrived. Grid | One uniform cell for everything. Best when the cards are different kinds — an image beside a PDF beside an audio clip. Single row / Single column | One line. With cards selected it tidies those; with nothing selected it tidies the whole board. Either way the block stays where it already was and keeps clear of anything it is not moving, so tidying part of a board cannot bury the rest. It is one undo step. Justified rows is the only one that resizes cards — fitting a row to a width is a resize. The rest only move things. The same layouts are available over the API, so an assistant can tidy a board too. Dropping a lot of files at once Drop a folder of images onto the canvas and they arrive as a block centred on where you dropped, laid out as justified rows, rather than in a line running off the side of the screen. On a crowded board Guides are culled to cards actually near the one you are moving. Without that, a board with hundreds of cards would produce a screen full of lines and no useful signal. Where snapping does not apply Free-draw strokes — freehand marks are freehand. Inside a grid — cells are defined by dividers, and dragging a divider resizes rather than snapping. Schedule slots — a slot is a date, and dropping into it is exact by definition. ## Frequently asked questions Q: How do I place something deliberately off-grid? A: Hold Alt while dragging. Snapping is suppressed for that drag only, so you do not have to remember to switch it back on. Q: Does snapping work with many cards on screen? A: Yes. Candidate guides are culled to what is actually near the card you are moving, so a board with hundreds of cards does not turn into a screen full of lines. Q: Can I align a selection all at once? A: Yes. Select several cards, right-click, and use Arrange → Align or Distribute. To repack a whole board, Arrange → Tidy up. ======================================================================== # Exporting a board URL: https://clusters.soleilpictures.com/docs/canvas/export Updated: 2026-08-08 Right-click the canvas and choose Export to save the whole board as a PNG or a PDF. Individual images download from the lightbox, either as the original file or with your adjustments baked in. Documents have their own exports including PDF, Markdown, HTML and screenplay formats. Three different things get exported, and they are separate mechanisms. The whole board Right-click the canvas → Export: PNG — a single image of the board PDF — the board as a printable document Both capture the board as it appears: cards, arrows, drawings, group outlines and the background. There is no region export. If you want part of a board, either fit that part on screen first, or move those cards onto their own nested cluster and export that — which is usually the better answer, because it leaves you with something reusable. A single image Open an image in the lightbox and use Download. You choose between: The original file, exactly as uploaded A version with your adjustments baked in The original is never modified by adjusting a card, so both options are always available. A document Documents have their own export menu with a wider set of formats — PDF, Markdown, HTML, and for screenplays, Final Draft (.fdx) and Fountain. See Exporting documents. Export or share a link? An export is a snapshot. A share link is live. Send a link when the recipient may need to see the board again later, or when you want them to be able to comment. Export when you need a file to attach, to print, or to hand to a system that only takes files. A public share link is view-only and needs no account to open. It is usually a better answer than emailing a PDF, and it does not go stale. On mobile and in the native apps Exports are delivered through the platform's own share sheet on iOS and Android rather than a browser download, so they land in Files, Photos, or whichever app you send them to. ## Frequently asked questions Q: Does the export include drawings and arrows? A: Yes. A board export is a picture of the board — cards, arrows, drawings, groups and backgrounds all appear as they do on screen. Q: Can I export just part of a board? A: Not as a region selection. Fit the selection on screen first, or move the cards you want onto their own cluster and export that. Q: Should I export or share a link? A: Share a link if the recipient might need to see updates. An export is a snapshot from the moment you made it. ======================================================================== # Files and uploads URL: https://clusters.soleilpictures.com/docs/files Updated: 2026-08-08 Drag any file onto a canvas and it becomes a card. Images, video, audio and PDFs get real players and viewers; everything else becomes a file card with a type icon and a download. Free accounts can upload standard media within size caps, and Creator adds any file type at all — .psd, .fig, .zip — with no size limit on a 100GB drive. Drag a file onto the canvas. The type is detected and the right kind of card is created — you never pick "upload as image" from a menu. What each type becomes You drop | You get Image (incl. HEIC/HEIF) | An image card with adjustments Video | An inline player Audio | A waveform player with cover art PDF | A page-one thumbnail opening into a full viewer Anything else | A file card — type icon, name, size, download Small text files get an inline preview on the card rather than only a download. Limits Two different things are limited, and they are limited for different reasons. File type. Free accounts can upload standard media. Non-standard types — .psd, .fig, .zip, project files, archives — require Creator. This is the "upload anything" feature and it is the paid one. Size. On the free plan: Type | Cap Video | 30 MB Audio | 50 MB PDF | 50 MB Images | No cap on any plan Creator removes the size caps entirely. These are the real, enforced differences between free and paid — along with the card cap. Clusters, collaborators and editing are not limited on any plan. Large uploads Files beyond roughly a gigabyte upload in parts automatically, so a big video or a project archive is not one fragile request. If an upload is interrupted it can resume rather than starting over. Batches upload a few at a time so one huge file does not block eleven small ones behind it. Storage Uploads count against your account's storage quota. Creator accounts get 100GB. The meter is in Settings → Billing. Storage is counted against the owner of the cluster, not the person who uploaded. If you are an editor on someone else's board, your uploads use their quota — which is the same rule that governs the card cap. How files are stored Files go directly from your browser into private object storage, and are served back through signed URLs that expire. Nothing is in a public bucket and nothing is reachable by guessing a path. Files referenced by a board are protected from cleanup for as long as the board references them. Removing the last card that uses a file makes it eligible for deletion later, not immediately. Downloading Every file card has a download. Images additionally offer a version with adjustments applied. PDFs can be downloaded from the viewer. ## Frequently asked questions Q: What file types can I upload on the free plan? A: Standard media — images, video, audio and PDFs — within the size caps listed below. Images have no size cap on any plan. Non-standard types like .psd, .fig and .zip require Creator. Q: How large a file can I upload? A: On Creator there is no per-file limit; very large files upload in parts automatically. On the free plan video is capped at 30 MB, audio and PDF at 50 MB. Q: Where do my files actually live? A: In private object storage. Files are served through signed URLs that expire, so a file cannot be reached by guessing a URL. ======================================================================== # PDFs URL: https://clusters.soleilpictures.com/docs/files/pdf Updated: 2026-08-08 Drop a PDF onto a canvas and you get a card showing its first page and how many pages it has. Click it to open a full-screen viewer that scrolls through the whole document and offers a download. Free accounts can upload PDFs up to 50 MB; Creator removes the cap. A PDF on a board is a real document, not a download link. The card Dropping a PDF gives you a card showing the first page as a thumbnail, plus the page count. At a glance you can tell a two-page treatment from a ninety-page script. The card behaves like any other — move, resize, group, tag, comment, put it in a grid cell. The viewer Click the card to open full screen. The viewer scrolls the whole document and renders pages as you reach them, so a long PDF opens immediately rather than after processing every page. Download is in the viewer, and gives you the original file untouched. What you cannot do The PDF is not editable in place. There is no in-document annotation, no form filling, no page reordering. What you can do instead: Draw on the canvas over the card — see Shapes and drawing Comment on the card — see Comments Put a note beside it with the feedback Size limit Free accounts cap PDFs at 50 MB. Creator removes the cap. See Plans. A script or a treatment is comfortably under that. A print-resolution deck with embedded images often is not. Making PDFs Clusters produces PDFs as well as reading them: A whole board — Exporting a board A document — Exporting documents A screenplay in correct industry format, where the on-screen pages match the PDF pages exactly ## Frequently asked questions Q: Can I annotate a PDF? A: Not inside the document. You can draw on the canvas over the PDF card, and attach comments to the card, but the PDF file itself is not edited. Q: Does the viewer load the whole file at once? A: No. Pages render as you reach them, so a long document opens immediately instead of waiting for every page. Q: Can I turn a PDF into images? A: Not automatically. Export the pages you want from your PDF tool and drop them in as images if you need them as separate cards. ======================================================================== # Video and audio URL: https://clusters.soleilpictures.com/docs/files/video-and-audio Updated: 2026-08-08 Video and audio files become playable cards on the canvas. Audio cards draw a real waveform and show cover art, and only one plays at a time so a board full of takes never becomes a wall of noise. Free accounts cap video at 30 MB and audio at 50 MB; Creator removes both caps. Both play in place on the canvas. No lightbox, no separate player window — the clip is a card among the reference stills it belongs with. Video A video card plays inline. Move it, resize it, group it and comment on it like anything else. Free accounts cap video at 30 MB. That is enough for a reference clip or a cut-down, not for a full-resolution master. Creator removes the cap and handles very large files by uploading them in parts. Audio Audio cards do more than provide a play button: A real waveform, drawn from the file — so you can see where the loud part is before playing it Cover art, when the file carries it Standard transport controls Only one audio card plays at a time across the whole board. Starting a second stops the first. On a board holding a dozen takes of the same cue, this is the only behaviour that makes sense. Free accounts cap audio at 50 MB. Formats Whatever the browser can play. H.264 in an MP4 container, and MP3 or WAV, are the reliable choices. A file the browser cannot play still uploads — it becomes a file card with a download, rather than failing. What this is not Clusters plays media. It does not edit it: no trimming, no cutting, no adjusting audio levels. Do that in your editor and upload the version you want people to see. The photo adjustments available on image cards have no video equivalent. Storage Media counts against your storage quota like everything else — 100GB on Creator, with the meter in Settings → Billing. Video is usually what fills it. ## Frequently asked questions Q: Why does starting one audio card stop another? A: Deliberate. Boards commonly hold a dozen takes, and having several play over each other is never what someone wanted. Playback is exclusive across the board. Q: Can I trim a clip? A: No. Clusters plays media, it does not edit it. Trim in your editor and upload the version you want to show. Q: What formats work? A: Whatever the browser can play — H.264 MP4 and MP3 or WAV are the safe choices. An unplayable file still uploads and becomes a downloadable file card. ======================================================================== # Clusters URL: https://clusters.soleilpictures.com/docs/clusters Updated: 2026-08-08 A cluster is a board, and clusters nest inside each other without limit — which is how a project becomes a folder tree without anyone building one. Clusters are never capped on any plan. Each one gets an automatic thumbnail rendered from its actual contents, or a cover image you choose yourself. A cluster is the unit of work: a project, a scene, a pitch, a moodboard. The code and the API call the same object a board. Creating one New cluster in the sidebar ⌘K → "create cluster" The Add cluster tool on any canvas, which creates it nested inside the current one Right-click a canvas → Add → Cluster Clusters are unlimited on every plan, including free. Only cards are capped. Nesting A cluster inside a cluster appears as a card on the parent's canvas that opens into its own canvas. There is no depth limit. This is how structure emerges without anybody designing it: a film becomes scenes, a scene becomes setups, a setup becomes a reference wall — each a real board you can open, share and work on independently. Moving a cluster: drag it in the sidebar tree, or drag it onto another cluster's card on a canvas. Moves are cycle-safe — dragging a cluster into one of its own descendants is refused rather than creating a loop. Linked clusters are different: a reference to a cluster that lives elsewhere, placed on this canvas. The board itself does not move. Use it when something belongs in two places. Thumbnails Every cluster gets a thumbnail rendered from what is actually on it — a miniature of the board, not a generic icon. It updates as the board changes. To override it, right-click the cluster: Cover colour — a flat colour instead of a render Upload custom thumbnail — your own image, with a 16:9 crop and reposition step Reset to auto thumbnail — back to the generated render A custom thumbnail is respected — editing the board afterwards will not silently overwrite the image you chose. Views Every cluster has two views of the same contents: Canvas — the infinite surface, where position means something List — a sortable, searchable file browser Switching does not convert anything. Settings → Defaults sets which view new clusters open in. Finding clusters The sidebar tree expands lazily, so a deep hierarchy stays fast ⌘K searches cluster names alongside everything else Recents surfaces what you have had open Home shows the whole workspace as a relationship graph Shared with me groups clusters other people have invited you to, by whose workspace they came from Deleting Deleting a cluster is a soft delete. It goes to the trash and stays restorable for 30 days. See Trash and recovery, which also covers version history and rolling a whole workspace back to a point in time. ## Frequently asked questions Q: Is there a limit on how many clusters I can make? A: No. Clusters are unlimited on every plan, including free. Only cards are capped, at 100 on the free Demo plan. Q: How do I move a cluster somewhere else? A: Drag it in the sidebar tree, or drag it onto another cluster's card on a canvas. Moves are cycle-safe — you cannot make a cluster its own ancestor. Q: Where do deleted clusters go? A: To the trash, for 30 days, from which they can be restored. See Trash and recovery. ======================================================================== # List view URL: https://clusters.soleilpictures.com/docs/clusters/list-view Updated: 2026-08-08 Every cluster has a list view as well as a canvas — the same contents as a sortable, searchable file browser. It has table and gallery modes, sorting by name, type, size or date, filters by content type, and a detail panel with a large preview and metadata. Nothing is converted; it is one set of contents with two ways to look at it. The canvas is for arranging. List view is for finding. Same cluster, same contents, different question being asked. Switch with the view control in the cluster header. Table and gallery Table — a row per item, with type, size and dates. Dense, scannable, sortable. Gallery — a tile per item with a real preview. Better when you are looking for something you would recognise by sight. Sorting and filtering Sort by name, type, size, date modified or date added. Filter to a single content type: Images, PDFs, Video, Audio, Files, Notes, Links, Docs, Palettes, Other. Search filters as you type, against names and content. The combination is the answer to "where is that PDF someone dropped in here last week" — filter to PDFs, sort by date added, done. On the canvas that is a hunting expedition. Previews Everything gets a real preview, not a generic icon. Images and video show themselves; grids, docs, schedules, shapes, notes and links get schematic marks that indicate their shape and content. The detail panel Select an item for a large preview plus its metadata — type, size, dimensions, dates, and where it lives. From there: Action | What it does Open on canvas | Jumps to the card, selected, on the canvas Download | The original file Copy link | A deep link that opens the board with the card selected Delete | Removes it, with undo Open on canvas is the important one: it connects the two views, so finding something in list view puts you in front of it in context. Adding files here Drag files into list view, or use Add files in the toolbar. They land on the cluster exactly as if dropped on the canvas, auto-placed in free space. Presence List view shows who else is in the cluster, and what they have selected — the same presence information the canvas shows, so switching views does not mean losing sight of your collaborators. Every cluster is a drive This is the framing worth internalising. A cluster is not "a canvas that also has a list" — it is a set of contents that can be arranged spatially or browsed like a folder. Teams that come from a shared-drive workflow can use Clusters that way from day one and discover the canvas later. That discovery is nudged exactly once, the first time a cluster gets full enough for it to matter. Coming from a reference tool that only does one of these? See how Clusters compares. ## Frequently asked questions Q: Does switching to list view change my board? A: No. It is a different view of the same contents. Positions on the canvas are untouched. Q: Can I upload from list view? A: Yes. Drag files into it, or use Add files in the toolbar. They land on the cluster exactly as if you had dropped them on the canvas. Q: Can I make list the default? A: Yes, in Settings under Defaults. You can also set the view per cluster. ======================================================================== # Home graph URL: https://clusters.soleilpictures.com/docs/clusters/home-graph Updated: 2026-08-08 Home shows your workspace as a relationship graph rather than a list — clusters, documents, cards and URLs as connected nodes you can orbit and fly through. Hover a node to preview it, right-click to open, and use the detail drawer for what connects to what. A 2D view is used automatically where 3D would not perform. Home is the workspace seen as a graph: every cluster, document, card and URL as a node, with edges for the relationships between them. Why a graph A sidebar tree shows containment and nothing else. It cannot show that two unrelated projects both reference the same location, or that one document is mentioned from six places. The graph shows those. On a workspace with real history it surfaces connections you did not know were there. On a small workspace, the sidebar is faster and there is no shame in using it. Moving around Orbit, zoom and fly through with the mouse or trackpad. Nodes cluster by relatedness, so things that belong together end up near each other without being arranged. Hover a node to preview it — the underlying board also starts loading, so opening it is instant Right-click to open Detail drawer shows what a node connects to, and why The HUD filters what is shown by type, which matters once the graph is dense. What the edges mean Edges are derived from your content, not drawn by hand: Nesting — a cluster inside a cluster Links and mentions — an @ mention, a document linking to a board Shared URLs — two boards referencing the same external page Documents and the boards they are embedded in 2D fallback The 3D view needs a capable GPU. Where that is not available — some laptops, most tablets — a 2D graph renders instead automatically, with the same nodes, edges and interactions. Performance The graph is a heavy piece of code and is loaded only when you open Home, so it costs nothing on any other screen. When to use something else Looking for one specific thing you can name — ⌘K Browsing the contents of one cluster — list view Finding everything on a theme — tags ## Frequently asked questions Q: What do the connections represent? A: Real relationships — nesting, links between documents, mentions, and shared URLs. The graph is derived from your content, not arranged by hand. Q: Is this just decorative? A: It is genuinely useful on a large workspace, where a tree hides the fact that two projects reference the same material. On a small workspace the sidebar is faster. Q: What if 3D is slow on my machine? A: A 2D fallback renders automatically. The graph and its interactions are the same. ======================================================================== # Trash and recovery URL: https://clusters.soleilpictures.com/docs/clusters/trash-and-recovery Updated: 2026-08-08 Deleting a cluster is a soft delete — it sits in the trash for 30 days and can be restored. Individual boards keep version snapshots you can look through and roll back to. If something goes badly wrong, workspace recovery rewinds every board in the workspace to a chosen moment, with a preview of exactly what would change before anything happens. Four layers, from smallest mistake to worst day. Undo ⌘Z. Per-session and per-board, covering your own edits. It deliberately does not revert a collaborator's changes. In a shared board, an undo that silently reversed someone else's work would be worse than no undo. Deletions also show a toast with an undo button. Trash Deleting a cluster is a soft delete. It goes to the workspace trash and stays restorable for 30 days, then is purged. The trash is workspace-wide — open it from the sidebar or ⌘K → "trash". From there, restore puts a cluster back where it was, or remove permanently if you are sure. Version history Boards keep snapshots over time. You can list them, look through them, and roll back to one. This is the tool for "the board was right yesterday and is wrong now", including when the change was someone else's. Workspace recovery The heavy one, for when something has gone wrong across many boards at once — a bad bulk operation, or an integration that misbehaved. Pick a target moment Review the impact preview — every board that would change, and how Confirm The preview step is the point. A rewind is atomic across the workspace, so seeing exactly what it would do before it does it is not optional. Open it from an alert banner, or from the workspace menu. Anomaly alerts An unusual number of deletions in a short window raises an alert banner in the workspace, unacknowledged until someone looks at it. The alert links straight into recovery with that moment pre-selected as the target. This exists because the failure mode that actually hurts is not a single wrong delete — it is a bulk deletion nobody notices for three weeks, by which point the 30-day trash window is closing. Files Deleting a card does not immediately destroy the underlying file. Files remain protected while any board still references them, and become eligible for cleanup only after nothing does. Restoring a board within the trash window restores its images intact. See Data and privacy for retention and deletion in full. ## Frequently asked questions Q: How long do I have to restore a deleted cluster? A: 30 days. After that it is purged permanently. Q: Can I undo a collaborator's changes? A: Cmd-Z only undoes your own edits. To reverse someone else's work, use version history on that board or workspace recovery. Q: What is the alert banner about? A: An unusual number of deletions in a short window raises an alert. It is there so a mass deletion gets noticed the same day rather than three weeks later. ======================================================================== # Documents URL: https://clusters.soleilpictures.com/docs/documents Updated: 2026-08-08 A document is a multi-page rich-text editor that lives as a card on your canvas. It has a page tree, a full formatting toolbar, tables, images, code blocks and embedded boards, real-time co-editing with visible cursors, inline comments, and find and replace. Open it full screen or docked beside the canvas. A document is where the writing goes. It lives as a card on a canvas, so a treatment sits beside the references it came from rather than in a different application. Add one with the doc tool in the rail. Opening Two modes: Full screen — the document fills the window Side — docked beside the canvas, resizable, so you can write while looking at the board The three panes Pages on the left — a hierarchical tree. Documents are genuinely multi-page, and pages nest. A new page is created automatically when the current one fills up, so long-form writing does not stop to manage pagination. The body in the middle, with the formatting toolbar above it. Bookmarks on the right — durable anchors into specific places in the text, which survive editing around them. Zoom runs from 25% to 200%. Writing and formatting The toolbar carries the usual: headings, bold, italic, underline, highlight, lists, task lists, alignment, colour, font and size. Every one has a keyboard shortcut. ⌘F opens find and replace. The footer shows a live word and character count, and an honest save status — it reports what has actually been persisted rather than an optimistic tick. Insert The Insert menu adds: Image Table Divider Code block Embed board — a whole cluster, rendered inside the document Blocks can be reordered by dragging the handle in the left margin. Working together Documents are collaborative in real time, with peer cursors and selections visible as people type. Edits merge rather than overwriting. Comments, tags and links The margins do a lot of work: Inline comments on a text range, with dots in the right margin Tag ranges underlining text, with dots in the left margin Suggested entities — names the app noticed, with a tick or a cross in the margin to confirm or dismiss Links and mentions — @ anything, with hover previews Backlinks — everywhere this document is referenced from Screenplays Documents have a dedicated screenplay mode with industry-standard formatting, correct pagination, a scene navigator, a title page, and Final Draft and Fountain import and export. Exporting PDF, Markdown, HTML, and for screenplays .fdx and .fountain. See Exporting documents. Documents versus notes | Note | Document Lives | On the canvas | As a card, opens full screen or docked Length | A paragraph or a list | Pages Structure | None | Page tree, bookmarks Comments | On the card | On a text range Export | With the board | Its own formats If you find yourself scrolling inside a note, you wanted a document. ## Frequently asked questions Q: When should I use a document instead of a note? A: A note is for a caption or a short list on the canvas. A document is for anything with structure — a treatment, a brief, a script. If you want pages, use a document. Q: Can two people write in the same document? A: Yes, with visible cursors and selections. Edits merge; there is no locking. Q: Can a document contain a board? A: Yes. Insert a board embed and the cluster renders inside the document. ======================================================================== # Screenplay mode URL: https://clusters.soleilpictures.com/docs/documents/screenplay Updated: 2026-08-08 Screenplay mode turns a document into a properly formatted script — Courier, correct margins, scene headings, action, character, dialogue and parenthetical elements, with Tab and Enter moving between them the way script software does. Pagination is line-accurate, so the pages you see are the pages that print, and you can import and export Final Draft and Fountain files. A document in screenplay mode behaves like script software: Courier, industry margins, and the element model everyone writing a script expects. Elements Scene heading, action, character, dialogue, parenthetical, transition. Tab and Enter move between them the way they do in script software — Enter after a character name goes to dialogue, Tab from action goes to character. You should not have to think about formatting while writing. Scene headings, character names and transitions autocomplete from what you have already used, so the third INT. DINER - NIGHT matches the first exactly. Pagination Pagination is computed with real line metrics rather than approximated. The pages on screen are the pages in the PDF. This matters more than it sounds. A page is roughly a minute of screen time, and that estimate is worthless if the page count changes when you export. Title page An editable 8.5×11 title page, laid out the way a title page is laid out — title, byline, contact — rendered on the page rather than in a settings dialog. Scene navigator A rail listing every scene heading with its scene number. Click to jump. On a feature-length script this is the primary means of navigation. Import and export Format | In | Out Final Draft .fdx | ✓ | ✓ Fountain .fountain | ✓ | ✓ PDF | | ✓ Bring in a script from Final Draft, work on it here, take it back out. Nothing is trapped in the format. PDF export is real vector output in Courier — selectable, searchable text at the correct metrics, not a screenshot of the editor. Alongside the board The reason to write a script here rather than in dedicated software is what surrounds it: the references, the storyboard grid, the shot list and the schedule are all on the same board, and the document can be docked beside the canvas while you work. Scene headings can be tagged and locations mentioned, which connects a scene in the script to the reference wall for that location. Working out the shots as well? See the shot list maker guide. ## Frequently asked questions Q: Do the on-screen pages match the exported PDF? A: Yes. Pagination is computed with real line metrics, so page breaks on screen are the page breaks in the PDF. Page count is meaningful, which matters when a page is a minute. Q: Can I bring in an existing script? A: Yes. Import Final Draft (.fdx) or Fountain (.fountain) files. Both are also export formats, so nothing is trapped. Q: Is there a scene list? A: Yes. The scene navigator lists every scene heading with its number, and clicking one jumps to it. ======================================================================== # Exporting documents URL: https://clusters.soleilpictures.com/docs/documents/export Updated: 2026-08-08 Documents export as PDF, Markdown or HTML, and screenplays additionally as Final Draft .fdx and Fountain. Screenplay PDFs are real vector output in Courier with page breaks matching what you saw on screen. On iOS and Android, exports go through the system share sheet rather than a browser download. The export menu is in the document toolbar. Formats Format | Use it for PDF | Sending to someone who should not edit it Markdown | Moving text into another tool, or into version control HTML | Publishing, or pasting into a CMS Final Draft .fdx | Screenplays, into industry software Fountain .fountain | Screenplays, as plain text that opens anywhere The two screenplay formats appear only for documents in screenplay mode. PDF Real text, not a rendered image — selectable and searchable. For screenplays it is vector output in Courier at correct industry metrics, and the page breaks match what you saw while writing, because pagination is computed the same way for both. Markdown Plain text with structure preserved: headings, lists, emphasis, tables, code blocks and links. Image references are preserved as links; the files themselves stay in Clusters storage. The right choice for moving writing into another tool without reformatting it by hand. HTML Standalone HTML with inline styling, for publishing or pasting into a CMS. On mobile and in the native apps On iOS and Android the export goes through the system share sheet rather than a browser download, so it can go straight to Files, Mail, or another app. Exporting the board instead To export the whole cluster rather than one document — the canvas with its cards, arrows and drawings — see Exporting a board. ## Frequently asked questions Q: Is the PDF selectable text or an image? A: Real text. Screenplay PDFs are vector output in Courier, so the text is selectable and searchable. Q: Does Markdown export keep my images? A: Image references are preserved. The images themselves stay in Clusters storage and are linked, not embedded. Q: Why would I export Fountain instead of Final Draft? A: Fountain is plain text — it opens anywhere, diffs cleanly in version control, and does not depend on any one application. ======================================================================== # Comments, tags and backlinks in documents URL: https://clusters.soleilpictures.com/docs/documents/comments-and-tags Updated: 2026-08-08 Select text in a document and comment on it — the comment attaches to that range and shows as a dot in the right margin. Tags applied to a range underline the text and show in the left margin. The app also suggests entity names it notices, which you confirm or dismiss with a tick or a cross, and backlinks show everywhere the document is referenced from. Documents carry annotation in the margins, so the text stays clean and the apparatus stays visible. Comments on a range Select text and add a comment. It anchors to that range, not to a character position, so editing around it keeps it attached to the right passage. Commented text is marked inline, with a gold dot in the right margin. Click a dot to open the thread. Comments thread, resolve, and support @ mentions that notify the person you name. See Comments for the full model, which is shared with the canvas. Tags on a range A tag can apply to a passage rather than the whole document. Tagged text is underlined, with dots in the left margin. This is what lets a script's mention of a location connect to the reference wall for that location — the tag's detail view will list this passage among everything else carrying it. Suggested entities As you write, names that look like entities — characters, locations, organizations — are detected and offered in the margin with a tick and a cross. Nothing is applied until you accept it. A suggestion is a suggestion; the document is not silently tagged behind you. Accepting one creates or links the entity, so the next occurrence anywhere is recognised automatically. The detector is on by default and can be switched off in settings if you would rather work without it. Links and mentions Type @ to reference a cluster, document, card, tag or person. Mentions render as links with hover previews and register as backlinks on the target. URLs typed or pasted become links automatically. See Links and mentions. Backlinks The Referenced by panel lists everywhere this document is linked or mentioned from — other documents, notes, cards, messages. Backlinks are the reason mentioning things is worth the keystroke: a location's entity page ends up listing the script scene, the reference board and the message thread that all pointed at it, without anybody maintaining an index. Bookmarks Not annotation but adjacent: bookmarks are durable anchors into specific places in a document, listed in the right pane. They survive edits around them, so a bookmark on "the end of act two" stays there as the act changes length. ## Frequently asked questions Q: What happens to a comment if I edit the text it is attached to? A: It follows the text. Comments anchor to the range, not to a character offset, so editing around them keeps them attached to the right passage. Q: What are the dots in the margins? A: Right margin is comments. Left margin is tags and suggested entities. Click one to open it. Q: Can I turn off the entity suggestions? A: Yes. The AI tagger is on by default and can be switched off in settings. Suggestions never apply themselves — nothing is tagged until you accept it. ======================================================================== # Tags and entities URL: https://clusters.soleilpictures.com/docs/organize/tags Updated: 2026-08-08 A tag cuts across everything — apply one to a card, a group, a whole cluster or a passage of text, and the tag's detail view gathers every one of them from anywhere in the workspace. Tags can be typed as entities like character, setting, organization, concept or thing, and the app suggests tags automatically without ever applying one on its own. Tags are the cross-cutting layer. Clusters give you hierarchy; tags give you everything that does not fit a hierarchy. Tagging Right-click almost anything → tag. You can tag: A card A group — as a unit, not card by card A whole cluster A passage of text in a document The picker suggests as you type, and Enter creates a tag that does not exist yet. Multi-select is supported — most things carry more than one. A tag is not a location. Tagging moves nothing. The tag detail view Open a tag from the sidebar and you get everything carrying it, from anywhere in the workspace, arranged hierarchically — boards, then groups, then cards. This is the payoff. "Show me everything about the diner" spans a script scene, a reference wall, a schedule entry and a message thread, and no folder structure could have anticipated that grouping. Entity types A tag can be typed: Type | For Character | People in the work Setting | Locations Organization | Companies, departments, crews Concept | Themes, ideas, looks Thing | Objects, props, assets Typed tags get an appropriate colour and drive the hover previews and backlinks in links and mentions. Automatic tagging Two layers, neither of which applies a tag on its own. The matcher recognises names you have already used. Write "Diner" in a note after creating a Diner tag, and it is detected and offered. The AI tagger goes further, suggesting tags for content it has not seen a name for — including from image content. It is on by default and can be switched off. In both cases suggestion is the whole behaviour. Nothing is tagged until you accept it. A workspace silently filling with tags nobody chose would be worse than no tagging at all. Emergent themes Beyond individual suggestions, the app looks for clusters of related content and proposes a name for the theme it found — material that clearly belongs together but that nobody had a word for yet. Accepting one creates the tag and applies it to the group that suggested it. Propagation and backfill Creating a tag offers to apply it to existing content that matches, so a tag created late is not empty. A tag applied to a group propagates to what is in it, and in documents a tag on a paragraph can cascade to related passages. Finding by tag The sidebar lists workspace tags ⌘K searches tags alongside everything else The tag detail view is the full hierarchical browse ## Frequently asked questions Q: Do tags move things? A: No. A tag is not a location. The same card can carry several tags and stays exactly where it is. Q: Does the AI tagger tag things without asking? A: No. It only ever suggests. Nothing is tagged until you accept a suggestion. Q: What is an entity type? A: A tag can be typed as a character, setting, organization, concept or thing. Typed tags get an appropriate colour and behaviour, and are what make hover previews and backlinks useful. ======================================================================== # Links and mentions URL: https://clusters.soleilpictures.com/docs/organize/links-and-mentions Updated: 2026-08-08 Type @ anywhere you can write to mention a cluster, document, card, tag or person. Mentions become real links with hover previews, and they register as backlinks so the thing you mentioned can show everywhere it is referenced from. URLs and names of things you already created are detected and linked automatically. Mentions are how separate parts of a workspace find out about each other. Mentioning Type @ in a note, a document, a card title or a message, then start typing a name. You can mention a cluster, document, card, note, image, palette, group, tag, URL or person. The result is a real link. Hovering shows a preview appropriate to the type — an image shows the image, a board shows its thumbnail, a person shows who they are. Clicking navigates. Automatic links Two things happen without you asking. URLs typed or pasted become links, with previews on hover. Pasted onto an empty canvas, a URL becomes a full link card instead. Known names are detected. Once a tag or entity exists, later occurrences of its name in your writing are quietly underlined and connected. Writing "Diner" in a note after creating a Diner entity links the two without any @. Backlinks The reverse direction, and the reason mentions are worth the keystroke. Anything that gets mentioned can show a Referenced by panel listing everywhere it was mentioned from — documents, notes, cards, messages, boards. A location entity ends up listing the script scene, the reference wall, the schedule entry and the message thread that all pointed at it. Nobody built that index; it is a by-product of writing normally. Deep links Copy link on any card gives a URL that opens the board with that card selected and in view. Paste it into a message, a document or an email. Deep links carry the target through sign-in, so a recipient who is not logged in lands in the right place after authenticating rather than at the home screen. Where mentions work Surface | @ mentions | Auto-links | Backlinks Notes | ✓ | ✓ | ✓ Documents | ✓ | ✓ | ✓ Card titles | ✓ | ✓ | ✓ Messages | ✓ | ✓ | ✓ Comments | ✓ | ✓ | — Mentioning a person in a comment or a note notifies them. See Notifications. The graph Everything described here feeds the Home graph, which renders these relationships as something you can fly through. ## Frequently asked questions Q: What can I mention? A: Clusters, documents, cards, notes, images, palettes, groups, tags, URLs and people. Each gets a preview appropriate to what it is. Q: What are backlinks? A: The reverse direction. If a document mentions a location, that location's panel lists the document. You get an index of what references what without maintaining one. Q: Can I link to a specific card? A: Yes. Copy link on any card gives a URL that opens the board with that card selected. ======================================================================== # Search and the command palette URL: https://clusters.soleilpictures.com/docs/organize/search Updated: 2026-08-08 Press Cmd-K or forward slash to open the command palette. It searches across cluster names, card contents, notes, documents and tags, and it also runs commands — create a cluster, share this one, open messages, toggle the theme, open trash, sign out. It is the fastest route to almost anything in the app. ⌘K, or / when you are not typing into something. What it searches Full content, not just names: Recents — what you have had open Clusters — by name Cards and notes — by content Tags Docs — by content Results are grouped by kind, so a query matching a board and a passage inside a document shows you both, labelled. What it does The palette also runs commands. Type the verb rather than the noun: Command | Effect Create cluster | A new board New note | A note on the current board Go to Home | The relationship graph Link a cluster onto canvas | Place a reference to another board Open split view | Two boards side by side Share this cluster | The share dialog Messages | The messages drawer Toggle theme | Light / dark Toggle sidebar | Open trash | Deleted clusters Open settings | Account and billing | Plans Invite friends | Referrals Sign out | Pickers The same interface is reused wherever the app needs you to choose something — a board to link, a destination to move to, a document to embed. If you know how to use ⌘K, you already know how to use those. When to use something else Browsing one cluster's contents by type, size or date — list view Everything on a theme, across the workspace — tags Seeing how things relate rather than finding one — Home graph Inside one long document — ⌘F find and replace ## Frequently asked questions Q: Does search look inside my notes and documents? A: Yes. It searches full content, not just titles — cluster names, card contents, note text, document text and tags. Q: Can I run actions from it? A: Yes. Type what you want to do rather than what you want to find. "share", "invite", "trash", "theme", "billing" all work. Q: Is there a shortcut other than Cmd-K? A: Forward slash opens it too, when you are not typing into something. ======================================================================== # Collaborating URL: https://clusters.soleilpictures.com/docs/collaborate Updated: 2026-08-08 Invite people to a cluster as an editor or a viewer, by email or with a link. Collaboration is free on every plan — an editor does not need a paid account, and there is no seat count. Cards and storage always count against the cluster owner's quota, not the person who added them. Collaboration is deliberately not a paid feature. Inviting people is how boards become useful, and charging per seat would make the product worse at the thing it is for. Roles Role | Can Owner | Everything, including deleting the cluster and managing shares Editor | Add, change and remove content; comment; invite if permitted Viewer | Open, read, and vote — nothing else Workspace member | Access granted at the workspace level rather than per board Editors are free on every plan. A free-tier account can be an editor on any cluster it is invited to, with no restriction on what it can do there. Whose quota Always the cluster owner's. Cards added to someone else's cluster count against their card allowance; files uploaded there use their storage. This is what makes free editors workable: the person who owns the work pays for it, and inviting help costs nothing extra. Inviting Share on any cluster opens the dialog. Two mechanisms: By email — name a person and a role. They get an invitation. By link — a URL carrying a role, which grants that access to whoever opens it and confirms. Also in the dialog: current workspace members, existing per-board shares, and public links — which are a different thing, and always view-only. Workspaces Access can be granted at the workspace level instead of per board, which is the right shape for a team that works together continuously rather than on one project. Clusters shared with you appear under Shared with me in the sidebar, grouped by whose workspace they came from. What collaboration looks like in use Presence — live cursors, who is here, jump to where someone is Comments — anchored to a card, a group, a point, or a text range Messages — direct and group conversations with anyone you share a board with Notifications — mentions, replies, invitations Everything is real time. Two people can move cards on the same canvas, type in the same note, or write in the same document at once, and edits merge rather than overwriting. Undoing someone else's change ⌘Z undoes your own edits only. To reverse a collaborator's work, use version history or workspace recovery — see Trash and recovery. ## Frequently asked questions Q: Do collaborators need to pay? A: No. Editors and viewers are free on every plan, and there is no seat limit. Whoever owns the cluster pays for what it holds. Q: Whose card allowance do collaborator uploads use? A: The owner's. If you add cards to someone else's cluster, they count against their limit, not yours. Q: Can a viewer comment? A: Viewers can read and vote. Commenting and editing require editor access. ======================================================================== # Sharing and public links URL: https://clusters.soleilpictures.com/docs/collaborate/sharing Updated: 2026-08-08 A public link makes a cluster viewable by anyone with the URL, with no account and no sign-in. Links are always view-only, can be set to expire after 7 or 30 days, can include or exclude nested clusters, and can be marked as not indexable by search engines. Inviting by email is separate and is how you grant edit access. Two different mechanisms live behind the Share button, and mixing them up is the main thing to avoid. Public links — view only, no account A public link is a URL anyone can open. No account, no sign-in, no request for access. It is always read-only. Options when you create one: Option | Choices Expiry | Never · 7 days · 30 days Include sub-clusters | Whether nested clusters can be opened too Allow indexing | Whether search engines may index it — off by default With sub-clusters included, viewers can navigate into nested boards with breadcrumbs. With it off, the link shows exactly one board. Links can be revoked at any time from the same dialog. By default a shared board tells search engines not to index it. It is reachable by anyone with the URL, but it will not turn up in a search unless you allow indexing or publish it to Explore. The board renders in a real canvas — viewers pan, zoom and open images at full size. It is not a flattened image. Inviting people — this is how editing is granted Separate from public links, and the only way anyone gets edit access. By email — name a person and a role of editor or viewer. By invite link — a URL carrying a role. Whoever opens it confirms and gets that access. Use when you do not have addresses. Editors are free on every plan. See Collaborating. Quick copy The board header has a one-click copy view-only link for the common case. Boards you are viewing without edit rights show a clear VIEW ONLY badge, so there is never ambiguity about whether your changes will stick. Link previews A shared link unfurls properly in Slack, Messages and email — the board's own name and a thumbnail of its actual contents, not a generic logo. Publishing instead To make a board genuinely public and discoverable — listed in the directory and indexable — see Explore. That is a separate, reviewed step, not something a share link does by accident. Sharing a file instead of a link If the recipient needs a file rather than a live view, see Exporting a board. A link is usually better: it does not go stale. ## Frequently asked questions Q: Does someone need an account to open a shared link? A: No. A public link opens the board read-only for anyone, with no sign-in. Q: Can a public link be edited by whoever has it? A: Never. Public links are always view-only. Edit access is granted only by inviting a specific person or using a role-bearing invite link. Q: Will a shared board show up in Google? A: Only if you allow indexing, or publish it to Explore. By default shared boards carry a noindex instruction. ======================================================================== # Presence URL: https://clusters.soleilpictures.com/docs/collaborate/presence Updated: 2026-08-08 When several people are in a cluster you see each other's cursors, selections and text carets live. A facepile shows who is on the current board, and a workspace facepile shows who is anywhere in the workspace — clicking someone jumps you to whatever board they are on. Everyone has a stable colour that stays the same across sessions. Presence answers "who else is here and what are they doing" without anyone having to say. On a board Live cursors, each labelled with a name Selection halos — what someone has selected is outlined in their colour Text carets inside notes and documents, so you can see where someone is typing before the words appear A facepile in the header, with the full roster on click Across the workspace A second facepile shows everyone active anywhere in the workspace, not just on your board. Click someone to jump to them — you land on whatever board they are on. On a call, this replaces "which board are you on, send me the link". Colours Each person has a stable presence colour. It does not change between sessions, so a colour becomes a person you recognise. Set your own in Settings → Profile. At scale On a busy board, cursors are capped and culled rather than all rendered — a board with twenty people on it stays readable and stays fast. Connections survive reconnects and token rotation, so a laptop lid closing and reopening does not drop you out and duplicate you in the roster. What is real time Effectively everything: Surface | Live Moving, resizing, adding, deleting cards | ✓ Typing in a note | ✓, with carets Writing in a document | ✓, with carets Comments and votes | ✓ List view selections | ✓ Messages | ✓ Concurrent edits merge. There is no locking and no last-write-wins overwrite — two people editing the same note produce a note containing both edits. Working offline Changes made while briefly disconnected are reconciled on reconnect. The status in the header reports what has actually saved rather than assuming. ## Frequently asked questions Q: Can I see what someone is looking at without asking? A: Yes. Click their avatar in the workspace facepile and you jump to the board they are on. Q: Do presence colours change? A: No. Each person keeps a stable colour, and you can set your own in Settings under Profile. Q: What happens when a lot of people are on one board? A: Cursors are capped and culled so a busy board stays readable and fast rather than filling with labels. ======================================================================== # Comments URL: https://clusters.soleilpictures.com/docs/collaborate/comments Updated: 2026-08-08 A comment anchors to whatever it is about — a card, a group, an empty point on the canvas, the board as a whole, or a range of text inside a document. Comments thread, resolve, and support @ mentions that notify the person named. An eye toggle hides all of them when you want to see the board clean. Comments attach to the thing they are about. This is the whole design: feedback that lives in a sidebar, ordered by time, loses the connection to what it referred to within a day. What you can comment on Anchor | Reads as A card | "this image" A group | "this sequence" An empty point | "something should go here" The board | "the whole thing" A text range in a document | "this sentence" Right-click → Comment, or the rail's + → Annotate → Comment. Threads Comments thread with replies. Resolve when the thing is dealt with — resolved comments are archived rather than deleted, so the record of why something changed survives. Mentions Type @ in a comment to mention someone. They are notified. See Notifications. You can also mention clusters, documents and cards, which turns a comment into a navigable link rather than a description of where to look. Hiding them The eye toggle hides every comment bubble on the board — for you only, not for everyone. Use it when presenting or when the feedback has piled up and you want to see the work. Bubbles on a crowded board Comment bubbles lay themselves out to avoid covering the content they refer to and to avoid stacking on each other, so a heavily-reviewed board stays readable. Who can comment Editors can. Viewers cannot — a viewer can read the board and vote, but leaving comments requires edit access. If you want feedback from someone, invite them as an editor; it costs nothing on any plan. See Collaborating. Comments versus votes A comment is a sentence: reasoning, a request, a decision, with a thread and a record. A vote is a count: which of these, quickly, with no discussion. If you want to know why, use a comment. ## Frequently asked questions Q: Can I comment on empty canvas? A: Yes. A comment can anchor to a point rather than an object, which is how you leave "something should go here". Q: How do I hide comments for a presentation? A: The eye toggle hides every comment bubble on the board. It affects only your view. Q: Do resolved comments disappear? A: They are archived rather than deleted, so the record of a decision survives. ======================================================================== # Messages URL: https://clusters.soleilpictures.com/docs/collaborate/messages Updated: 2026-08-08 Messages is a drawer for direct and group conversations with anyone you share a board with. Threads support attachments you can drag straight onto a canvas, @ mentions that link to real boards and cards, per-thread drafts, and unread counts that show in the browser tab title. Messaging exists so that the conversation about a board can happen next to the board. Open it from the sidebar or ⌘K → "messages". Who you can message Anyone you share at least one board with. Sharing is the address book — there is no separate contact list to maintain, and no way to message someone you have no working relationship with. Click any collaborator's avatar to start a direct message with them. Threads Direct and group conversations in one list, ordered by recency. Attachments — send files, and drag one from a thread straight onto a canvas to make it a card Markdown and auto-links — formatting is rendered, URLs unfurl Mentions — @ a person, board, document or card; boards and cards become navigable links Per-thread drafts — a half-written message survives closing the drawer Permalinks — a link to one specific message, which opens the thread scrolled to it Unread Unread counts appear on the sidebar and in the browser tab title — (3) for unread, (@2) when at least one mentions you directly, so you notice from another tab. Messages versus comments The distinction worth keeping: A comment is attached to a thing on a board and stays with it. It is a record. A message is a conversation with a person. It is correspondence. Feedback on a specific image belongs in a comment, where whoever opens that board next will find it. Coordination belongs in a message. ## Frequently asked questions Q: Who can I message? A: Anyone you share at least one board with. There is no separate contact list to maintain and no way to message a stranger. Q: Can I get a link to one specific message? A: Yes. Every message has a permalink that opens the thread scrolled to it. Q: What do the numbers in the tab title mean? A: Unread count. An @ prefix means at least one of them mentions you directly. ======================================================================== # Notifications URL: https://clusters.soleilpictures.com/docs/collaborate/notifications Updated: 2026-08-08 Soleil Clusters notifies you about @ mentions, replies to your comments, workspace invitations, boards shared with you, and occasional product tips. Each of the five is a separate switch in Settings under Notifications, and every email carries a one-click unsubscribe that works without signing in. Five categories, five independent switches, in Settings → Notifications. Category | Fires when @-mentions | Someone mentions you in a note, comment, document or message Comment replies | Someone replies in a thread you are part of Workspace invites | You are added to a workspace Board shares | A cluster is shared with you Product tips and check-ins | Occasional messages about the product The last one is the one to switch off if you only want to hear about your own work. The other four are all consequences of something a person did that involves you. Where they appear In the app — unread indicators on the sidebar, and in the browser tab title for messages. Browser notifications — only if you grant permission. Nothing is requested until there is an actual reason to ask. Email — for things you would want to know about while not looking at the app. Unsubscribing Every email carries a one-click unsubscribe that works without signing in. Unsubscribing from email does not turn off in-app notifications; those are separate. Mentions The mention notification is the important one. When someone @s you, the notification links to the exact card, comment or passage, not just to the board — see Links and mentions. ## Frequently asked questions Q: Can I turn off product emails but keep mentions? A: Yes. The five categories are independent switches. Product tips and check-ins are the one to turn off if you only want the ones about your work. Q: Do I get browser notifications? A: Only if you grant permission. Nothing is requested until there is a reason. Q: How do I unsubscribe without logging in? A: Every email has a one-click unsubscribe link that works logged out. ======================================================================== # Publishing to Explore URL: https://clusters.soleilpictures.com/docs/publish/explore Updated: 2026-08-08 Explore is the public directory of boards at /explore. Publishing is opt-in and reviewed — you submit from the share dialog and it appears once approved. A published board gets a clean permanent URL, is indexable by search engines, and appears in the directory with search, sorting and topic filters. Explore is the public directory of boards people have chosen to publish. Publishing In the Share dialog, the Publish to Explore section submits the board for review. Review is a real step: a human looks before it appears. Nothing becomes public because somebody clicked the wrong control, and the directory does not fill with half-finished boards. Publishing is reversible from the same place. What a published board gets | A clean URL | A permanent, readable address rather than a token Indexable | Search engines may index it — unlike a plain share link Listed | Appears in the directory with a thumbnail Link previews | Unfurls with its own name and a thumbnail of its contents The board renders in a real canvas — visitors pan, zoom and open images at full size — with no account required. The directory /explore has search, sorting by featured, newest or alphabetical, and topic filters. Published versus shared | Share link | Published Who can see it | Anyone with the URL | Anyone Discoverable | No | Yes, in the directory Indexed by search | No, unless you allow it | Yes Review | None | Reviewed before appearing Reversible | Revoke the link | Unpublish Use a share link for a client or a collaborator. Publish when you want the work found. Before you publish A published board is a portfolio piece. Worth checking: Names. "Untitled cluster" is visible to everyone. Comments. Internal feedback is visible to viewers. Resolve or remove what should not be public. Nested clusters. Decide whether sub-clusters should be reachable. Rights. Reference images you do not own become publicly visible and indexable. Curated showcases Some boards in Explore are editorially curated showcase boards, which pair the canvas with a written article about how the board was made. Those are produced in-house rather than submitted. ## Frequently asked questions Q: How is publishing different from a share link? A: A share link is private-by-obscurity and not indexed. Publishing is deliberate and public — the board is listed in a directory, gets a clean URL, and is indexable. Q: Is publishing automatic? A: No. Submissions are reviewed before they appear. Nothing becomes public because you clicked the wrong thing. Q: Can I unpublish? A: Yes. Publishing is reversible from the same place you submitted it. ======================================================================== # Soleil Scout URL: https://clusters.soleilpictures.com/docs/scout Updated: 2026-08-08 Soleil Scout is a text-message ingest bot. You text photos, links or notes from your phone and they land arranged on a Soleil Clusters canvas, grouped by what you said about them. There is no app to install and no signup — an account and a board are created behind you the first time you text. The web half is live; the phone line is not connected yet, so signups are queued. Scout exists because nobody installs an app in a parking lot on one bar of signal. It lives in the messages app already open on the phone. Scout is not fully live. The web half — the signup box, the instant sign-in links, the account plumbing — is deployed. The bot itself has no phone line connected, so no message is sent or received today. Joining at /scout puts you on the list and you are texted when the line is live. This page describes how it behaves once it is. The idea Text what you are looking at. It lands on an infinite canvas your whole team can open. Two hundred photos in an afternoon all land in one undifferentiated camera roll. Saying what a thing is while standing in front of it is the cheapest possible moment to organize it — and it is the only moment when you actually know. How it works Text the number. Send your first photo. A board and an account are created behind you — no form, no password. Say what it is. Add "Scene 4 diner" or "power drops look sketchy". Scout reads it and titles the group. Keep shooting. Send twelve more. They batch into one tidy grid instead of twelve replies and twelve piles. Tap the link. Land on your canvas, signed in, with exactly the photos you just sent already selected. File it later. Everything collects in your Scout Bin. Say "put these in Diner Recce" and Scout confirms what will move before it moves anything. Batching Twelve photos means twelve messages seconds apart. Scout waits until you have stopped — roughly twenty seconds of quiet — then lays everything out once and sends one reply. While it works, a single message is edited in place through the stages rather than sending a new one each time: Got 12 photos — working on it… → Uploading 3 of 12… → Arranging on Scout Inbox… → the confirmation. The Scout Bin Unfiled things collect in your Scout Bin. It is the default destination, so you never have to decide where something goes at the moment you are shooting it. Say /board Diner Recce and that thread's target changes — the target is sticky, so everything after goes there until you change it again. Moves are always confirmed before they happen, and a rendered contact sheet of what is about to move is sent before the text asking you to confirm. Moves can be undone. Commands Command | Effect /help (or /start) | What Scout can do /board | Send everything after this to that cluster /bin (or /inbox) | Back to the Scout Bin /link | A signed link into your canvas /code | Connect this phone to an existing account Anything that is not a command is treated as content — or, if it is a question, answered. Connecting to an existing account Binding a phone to an account you already have is built but not switched on, for the same reason as everything else here: there is no line to text. When it is, you will get a connect code from your settings and text it once. If you started from Scout with no account, a claim flow attaches an email address to the shell account created behind your number. Links Confirmations include a link into the canvas with the cards you just sent already selected. For accounts with no email yet, that is a signed link that signs you in directly — valid for 30 minutes, then it expires and you request another. Limits Texted cards count against your card allowance like any other. On the free plan that is 100 cards. Scout warns you once, at 75% of the cap, and tells you where you stand in each confirmation past halfway. At the cap it says so plainly rather than silently dropping photos. Transport iMessage is the confirmed transport. SMS and RCS are not confirmed, and nothing here promises Android until they are. This page will say so when that changes. ## Frequently asked questions Q: Can I use Scout right now? A: Not yet end to end. You can join the list at /scout and the web side is live, but Scout has no phone line connected, so nothing is sent or received. Signups are queued and you are texted when the line goes live. Q: Do I need an account first? A: No. That is the point. An account, a workspace and a board are created behind your phone number the first time you text, with no form and no password. You can attach an email address later. Q: Does it work on Android? A: iMessage is the confirmed transport. SMS and RCS are not confirmed yet, and nothing here promises Android until they are. ======================================================================== # Settings URL: https://clusters.soleilpictures.com/docs/account/settings Updated: 2026-08-08 Settings is split into things about you and things about the workspace. Account tabs cover Profile, Scout, API, Invite and earn, Billing and Notifications. Workspace tabs cover Defaults, Theme and Display. Open it from the sidebar or with Cmd-K and typing settings. Open with ⌘K → "settings", or from the sidebar. Settings are stored on your account, not the device, so they follow you everywhere you sign in. Account Profile Profile picture, display name, email, and your presence colour — the colour your cursor and selection halos appear in to everyone else. Scout The connect code for Soleil Scout, which binds a phone number to this account so texted photos land in your workspace. Also shows identities already connected. API Create and revoke personal access tokens for the REST API and MCP. Each token gets a name and an Allow writes checkbox — leave it off for a read-only token. The token value is shown once, at creation. It is stored only as a hash and cannot be recovered; a lost token is revoked and replaced. Invite & earn Your referral link, share targets, and the stats: friends joined, how many got started, cards earned, free months. Billing Current plan, the Stripe customer portal for payment details and cancellation, and the storage meter showing what you have used against your quota. See Plans. Notifications Five independent switches — mentions, comment replies, workspace invites, board shares, and product tips. See Notifications. Workspace Defaults What new things start as, so you are not restyling every one: Notes — background, text colour, font, size Shapes — font, stroke colour, fill colour, stroke width Default view — whether clusters open as canvas or list Theme Light or dark mode, accent colour, and body font — a curated list of around twenty-five families plus anything from Google Fonts. See Theme and defaults. Display Clean mode — hide interface chrome for presenting (⌘.) Sidebar open by default Not in Settings A few things live where they apply rather than in a settings screen: Setting | Where Cluster cover / thumbnail | Right-click the cluster Canvas background colour | Right-click the canvas Public link expiry and indexing | The Share dialog Hiding comments | The eye toggle on the board ## Frequently asked questions Q: Are settings per device? A: No. They are stored on your account and follow you to any browser or device you sign in on, including the theme. Q: Where do I find my storage usage? A: Under Billing, as a meter showing used against your quota. Q: Where are API tokens? A: Under the API tab. Tokens are shown once at creation and cannot be recovered afterwards. ======================================================================== # Plans and pricing URL: https://clusters.soleilpictures.com/docs/account/plans Updated: 2026-08-08 The free Demo plan gives you 100 cards, unlimited clusters and unlimited free collaborators. Creator costs $25/mo billed monthly or $20/mo billed annually, and changes exactly three things — the card cap is removed, any file type can be uploaded, and per-file size limits are gone, on a 100GB drive. Two plans. The difference between them is deliberately small and deliberately honest — three enforced limits, listed below, and nothing else. Demo — free 100 cards to build with Unlimited clusters and boards Free collaboration — invite editors to any cluster Creator $25/mo billed monthly, or $20/mo billed annually ($240/yr, saving $60/yr). Unlimited cards — build without a ceiling Any file type — .psd, .fig, .zip, video, audio, docs No size limits, on your own 100GB drive What is genuinely limited Exactly three things: | Demo | Creator Total cards | 100 | Unlimited File types | Standard media | Anything Per-file size | Video 30 MB · audio 50 MB · PDF 50 MB | No limit Everything else is the same on both plans. Clusters are not capped. Neither are collaborators, editing, sharing, public links, comments, documents, screenplay mode or the API. Images have no size cap on any plan. Whose allowance The cluster owner's, always. Cards and files added to someone else's cluster count against them, not you. This is what makes free editors work — see Collaborating. Hitting the cap You are told when you reach it, and offered the upgrade. Nothing you have already made is removed, locked, or hidden. You can also raise your own cap without paying: referrals grant bonus cards when someone you invited actually gets started. Billing Settings → Billing shows your plan, the storage meter, and a link to the Stripe customer portal for payment details, invoices and cancellation. Annual and monthly are the same product. Annual is cheaper per month. Free forever The Demo plan is not a trial. It does not expire and it does not degrade. If 100 cards is enough for what you do, that is a complete account. More on the free tier specifically: free mood board maker. ## Frequently asked questions Q: What is actually limited on the free plan? A: Three things and only three — total cards, which file types you can upload, and per-file size caps on video, audio and PDF. Clusters, collaborators and editing are not limited. Q: Do collaborators need to pay? A: No. Editors are free on every plan. Whatever they add counts against the cluster owner's allowance, not theirs. Q: What happens when I hit the card cap? A: You are told, and asked to upgrade. Nothing already made is taken away or locked. ======================================================================== # Theme and defaults URL: https://clusters.soleilpictures.com/docs/account/theme-and-defaults Updated: 2026-08-08 Theme covers light or dark mode, an accent colour and a body font chosen from a curated list or anything on Google Fonts. Defaults set what new notes and shapes start as — colours, font, size, stroke — and whether clusters open as a canvas or a list. All of it is stored on your account and follows you between devices. Theme Settings → Theme. Mode — light or dark. ⌘K → "toggle theme" switches it without opening settings. Accent — the highlight colour used through the interface. Body font — a curated list of around twenty-five families, plus anything available on Google Fonts. Theme is stored on your account, not the browser, so it follows you to any device you sign in on. Fonts you have used recently are pre-loaded so text does not flash in a fallback face on a cold load. Readability across themes Text colour in notes and documents is resolved against the background it actually sits on. A note authored in light mode stays readable to a collaborator working in dark mode, and to anyone opening a public link. The reserved accent The gold accent means active, selected or focused. It is reserved for the interface and is not part of the content palette — so "this is selected" never gets confused with "this is gold" on a colourful board. Defaults Settings → Defaults. What new things start as, so you are not restyling every one. Default | Applies to Note background, text colour, font, size | Every new note Shape font, stroke colour, fill colour, stroke width | Every new shape Default view | Whether clusters open as canvas or list Defaults are workspace-scoped, so a workspace can have a consistent look without anyone enforcing it by hand. Display Settings → Display. Clean mode (⌘.) — hides interface chrome for presenting or screenshots Sidebar open by default Custom fonts Beyond the curated list, any Google Fonts family can be added and is then available everywhere a font is chosen — notes, documents, shapes and the body font. Screenplays are the exception: they are always Courier, because the format requires it and page count depends on it. ## Frequently asked questions Q: Does my theme follow me between devices? A: Yes. Theme is a per-user account setting, not a browser one, so it syncs everywhere you sign in. Q: Can I use any font? A: A curated list of about twenty-five is built in, and beyond that you can pull any family from Google Fonts. Q: Will notes made in light mode be unreadable in dark mode? A: No. Text colours are resolved against the actual background, so notes stay legible in either theme. ======================================================================== # Referrals URL: https://clusters.soleilpictures.com/docs/account/referrals Updated: 2026-08-08 Settings has an Invite and earn tab with your referral link. When someone you invited signs up and actually starts using Clusters, you both get bonus cards added on top of the free plan's allowance. The reward is activation-gated, so a signup that never makes anything does not earn anything. Settings → Invite and earn. How it works Share your referral link. When someone signs up through it and actually gets started, you both get bonus cards. The activation gate is the important detail. The reward is not for a signup, it is for someone who used the product — which means the number in your stats reflects people who are actually there. Bonus cards Bonus cards raise your card limit above the free plan's 100. They stack across referrals and they do not expire. They apply on top of your allowance, so a free account with referrals genuinely has more room rather than a temporary boost. See Cards. Stats The tab tracks four things: Stat | Meaning Friends joined | Signed up through your link Got started | Of those, how many actually used it Cards earned | Bonus cards credited to you Free months | Any subscription time earned The gap between the first two is the honest one. It is shown rather than hidden. Sharing the link Share targets are provided for the usual places. On mobile the system share sheet is used; on desktop, where browsers have no equivalent, pre-filled links are offered instead. Referrals versus collaboration Two different things: Inviting a collaborator to a cluster is free, unlimited, and needs no referral link. That is how you work with someone. A referral is inviting someone to have their own account and workspace. Inviting a collaborator to your board does not consume or produce referral credit — it is not a growth mechanism, it is how the product works. ## Frequently asked questions Q: When does the reward land? A: When the person you invited actually gets started, not when they sign up. A dormant signup earns nothing for either of you. Q: What do bonus cards do? A: They raise your card limit above the free plan's 100. They stack, and they do not expire. Q: Where do I see how it is going? A: Settings, under Invite and earn — friends joined, how many got started, cards earned, free months. ======================================================================== # Mobile and tablet URL: https://clusters.soleilpictures.com/docs/account/mobile Updated: 2026-08-08 Clusters works in a mobile browser, installs as a PWA, and ships as iOS and Android apps. On touch devices the canvas gets pinch-zoom and long-press menus, a bottom navigation bar replaces the sidebar, and a focus view strips everything back to the board. Tablets with a stylus get full drawing support. Three ways to run it on a phone or tablet: the mobile browser, an installed PWA, or the native iOS and Android apps. They are the same app. Touch on the canvas Gesture | Does One finger drag | Pan Pinch | Zoom Tap | Select Long press | Context menu Double tap | Add menu at that point Drawing gestures are guarded so the canvas cannot start panning halfway through a stroke — the failure that makes drawing on touch unusable. Layout differences Bottom navigation replaces the sidebar — Home, the current board, Messages, and more. A drawer holds navigation that does not fit. The add sheet replaces the tool rail's + menu with a full-width sheet, sized for thumbs. Focus view — touch only — strips everything back to the board itself, for reviewing on a phone without chrome in the way. Tablets Tablets get closer to the desktop layout — the rail rather than the sheet — plus stylus drawing. On iPad, drawing, annotating and reviewing all work properly; long document editing is still better on a laptop. The Home graph renders its 2D fallback on most tablets, which has the same nodes and interactions. Installing As a PWA — "Add to Home Screen" from the browser. It runs standalone, without browser chrome. Native apps for iOS and Android handle the status bar, keyboard behaviour, the splash screen, deep links, the Android back button, and the system share sheet — so exports land in Files or another app rather than a downloads folder. Getting photos in from a phone Three routes: Upload from the add sheet — the camera roll picker. Share sheet from Photos. Soleil Scout — text them, with no app at all. Note that Scout's phone line is not live yet. What is worse on a small screen Honestly: dense surfaces. Long documents, screenplay writing, big grid layouts and the 3D graph are all usable and all better on a large screen. What is genuinely good on mobile is capture and review — getting photos in, and looking at a board someone sent you. ## Frequently asked questions Q: Is there a real app? A: Yes, for iOS and Android, with native handling of the status bar, keyboard, splash screen, deep links and the Android back button. It can also be installed from the browser as a PWA. Q: Can I draw on a tablet? A: Yes, with a stylus or a finger. Drawing gestures are guarded so the canvas will not pan mid-stroke. Q: Is anything unavailable on mobile? A: Nothing is removed, but dense surfaces like long documents and the 3D home graph are much better on a large screen. The graph falls back to 2D on tablets. ======================================================================== # Data and privacy URL: https://clusters.soleilpictures.com/docs/account/data-and-privacy Updated: 2026-08-08 Boards are private by default and only reachable by people you invite. Files live in private storage and are served through signed URLs that expire, never from a public bucket. Deleted clusters are recoverable for 30 days, then purged. Everything you put in can be exported or read back out through the API. Who can see a board Private by default. A new cluster is visible only to you until you do one of two things: Invite someone — a named person, as editor or viewer Create a public link — view-only, and by default carrying a noindex instruction so search engines skip it Making a board genuinely public and discoverable is a separate, reviewed step — see Explore. Nothing becomes public by accident. Where files live In private object storage, served back through signed URLs that expire. There is no public bucket and nothing is reachable by guessing a path. Uploads go from your browser straight to storage. A file referenced by a board is protected from cleanup for as long as the board references it. Retention Thing | Kept Deleted cluster | 30 days in the trash, then purged Board version snapshots | For rollback and workspace recovery Files no longer referenced by any board | Eligible for cleanup after a grace period Resolved comments | Archived, not deleted Restoring a cluster within the trash window restores its images with it. Getting your data out Nothing is trapped: Board export — PNG or PDF Document export — PDF, Markdown, HTML, and .fdx / Fountain for screenplays Original files — downloadable exactly as uploaded REST API — read every board and card programmatically Accounts and access Sign-in is a one-time emailed code. There is no password to be reused or leaked. API tokens are stored only as a hash — the value is shown once and cannot be recovered. A token acts as you, reaching exactly what your account reaches under the same access rules the app uses, and can be revoked at any time with immediate effect. Error reporting Errors are recorded first-party, in Clusters' own infrastructure, to fix crashes. There is no Google Analytics and no third-party error monitoring service in the app. Legal The full policies: Privacy · Terms · Cookies. Those documents govern; this page is a plain-language summary of how the product behaves. ## Frequently asked questions Q: Are my boards private by default? A: Yes. A new cluster is visible only to you until you invite someone or create a public link. Q: Can someone guess the URL of my image? A: No. Files are in private storage and served through signed URLs that expire. There is no public bucket to enumerate. Q: How do I get all my data out? A: Export boards and documents, download original files, or read everything programmatically through the REST API. ======================================================================== # The REST API URL: https://clusters.soleilpictures.com/docs/api Updated: 2026-08-10 The Soleil Clusters REST API lives at /api/v1 and is authenticated with a personal access token sent as a bearer token. A token acts as you — every call runs under exactly the permissions your account has in the app. Responses are JSON, errors carry both a machine-readable code and a human sentence, list endpoints paginate, and a full OpenAPI description is published at /api/v1/openapi.json. Base URL: https://clusters.soleilpictures.com/api/v1 Every response is JSON. Every error carries a machine-readable code and a human-readable error. A full OpenAPI description is at /api/v1/openapi.json, served without authentication — a spec you need a credential to read is not discoverable. The authorization model The part worth reading before anything else. A token is not a capability. It resolves to you, and then every read and write runs as your user under the same row-level security the app uses. The consequence: a token reaches exactly what your account reaches, and nothing more. Boards you were invited to as an editor are writable. Boards you can only view are not. Boards you cannot see return 404 rather than 403, so the API never confirms the existence of something you have no business knowing about. There is no per-resource permission list on a token, and no way for API permissions to drift out of step with app permissions. Endpoints Method and path | Does GET /me | Who this token belongs to, its scopes, and its rate-limit state GET /workspaces | Workspaces you can see GET /search | Search boards and cards by text GET /boards | List boards; filter and paginate POST /boards | Create a board GET /boards/:id | One board PATCH /boards/:id | Rename, change view, or reparent DELETE /boards/:id | Soft-delete — restorable POST /boards/:id/restore | Undo a soft delete GET /boards/:id/cards | Cards on a board, paginated POST /boards/:id/cards | Add cards PATCH /boards/:id/cards/:cardId | Change a card DELETE /boards/:id/cards/:cardId | Remove a card POST /boards/:id/cards/move | Move cards to another board POST /uploads | Upload an image, get a key back GET /images/:key | Read an image back GET /resolve | Find an object by a foreign identifier GET /boards/tree | A whole board hierarchy in one call GET /boards/:id/export | Export a board, as JSON or MovieLabs OMC POST /boards/:id/import | Import from URLs — safe to re-run POST /boards/:id/arrange | Lay a board out — justified, masonry, grid GET /boards/:id/groups | The groups on a board POST /boards/:id/groups | Say a set of cards belongs together PATCH /boards/:id/cards | Change many cards in one call DELETE /boards/:id/cards | Remove many cards in one call POST /boards/move | Reparent many boards, cycle-safe DELETE /boards | Soft-delete many boards GET /audit | Audit log of writes and image reads POST /webhooks | Webhooks — get told when things change GET /webhooks/:id/deliveries | Every attempt, with its result GET /service-accounts | Service accounts in a workspace POST /service-accounts | Create one, with its first token DELETE /service-accounts/:id | Retire one and revoke its tokens POST /service-accounts/:id/tokens | Mint another token — rotate without downtime GET /service-accounts/:id/tokens | Its tokens and when each was last used DELETE /service-accounts/:id/tokens/:tokenId | Revoke one token GET /api/v1 returns this list plus your current scopes, so the one URL a person types by hand answers usefully. Authentication Two ways in, both ending at a bearer token that resolves to one person's own session. For your own scripts, mint a token under Settings → API: curl https://clusters.soleilpictures.com/api/v1/me \ -H "Authorization: Bearer undefined…" For an application other people connect — including any MCP client — use OAuth, so nobody is asked to paste a credential into somebody else's software. Registration is open and dynamic; discovery starts from the WWW-Authenticate header on any 401. Three scopes: delete · read · write. Tokens are stored only as a hash — the value is shown once and cannot be recovered. See Authentication. Pagination List endpoints take limit and offset. The default page is 100 and the maximum is 500. { "boards": [ … ], "limit": 100, "offset": 0, "has_more": true, "next_offset": 100 } has_more is computed by fetching one row beyond the page, so it costs nothing extra. Follow next_offset until it is null. Rate limits 1000 requests per hour per token. Every response — not only refusals — carries the current state: Header | Meaning x-ratelimit-limit | Your ceiling x-ratelimit-remaining | What is left in the window x-ratelimit-reset | Unix seconds when the window resets retry-after | Seconds to wait — only on 429 GET /me reports the same numbers in its body, so a client can check its budget without spending a request on a real call. Idempotency Send an Idempotency-Key header on any POST and a retry with the same key replays the original response rather than doing the work twice: -H "Idempotency-Key: $(uuidgen)" Keys are remembered for 24 hours. PATCH and DELETE are idempotent by construction. A retry arriving while the first attempt is still in flight gets 409 rather than racing it. A replayed response carries idempotent-replay: true. CORS Access-Control-Allow-Origin is *, so the API is callable from a browser. That it works from a browser does not mean you should. A undefined token in front-end code is readable by anyone who opens dev tools, and it acts as you. Call the API from a server. For AI agents Quickstart — working code in curl, TypeScript and Python MCP — the same API as tools an assistant can call directly OpenAPI — generate a client /llms.txt and /llms-full.txt — this documentation, machine-readable Every docs page is available as raw Markdown by appending .md ## Frequently asked questions Q: What can the API do? A: Read your workspaces, search across everything, create and manage boards and cards, upload images, and read images back. Effectively everything the app does to board structure. Q: Does a token have its own permissions? A: No, and this is the important design point. A token resolves to your user and every call runs under the same row-level security the app runs under. A token cannot reach anything your account cannot reach. Q: Is there a machine-readable spec? A: Yes, OpenAPI at /api/v1/openapi.json. It is served without authentication, because a spec you need a credential to read is not discoverable. ======================================================================== # API quickstart URL: https://clusters.soleilpictures.com/docs/api/quickstart Updated: 2026-08-08 Mint a token with write access under Settings then API, export it, and you can create a board and add cards in two requests. This page has the whole loop — authenticate, create, add, upload an image, read, update, move, delete — as copy-pasteable curl, TypeScript and Python, with the retry and error handling an agent needs. 1. Get a token In the app: Settings → API → New token. Tick Allow writes if you intend to change anything. Copy it immediately — it is shown once. export SOLEIL_TOKEN="undefined…" export SOLEIL_API="https://clusters.soleilpictures.com/api/v1" 2. Check it works curl -s "$SOLEIL_API/me" -H "Authorization: Bearer $SOLEIL_TOKEN" { "user_id": "…", "display_name": "Andrew", "tier": "paid", "scopes": ["read", "write"], "rate_limit": { "limit": 1000, "remaining": 993, "reset": 1786000000 } } Always do this first. It confirms the token is live, tells you which scopes you have before you find out the hard way, and reports your remaining rate budget. 3. Create a board and add cards BOARD=$(curl -s -X POST "$SOLEIL_API/boards" \ -H "Authorization: Bearer $SOLEIL_TOKEN" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{"name":"Scene 4 — Diner"}' | jq -r .board.id) curl -s -X POST "$SOLEIL_API/boards/$BOARD/cards" \ -H "Authorization: Bearer $SOLEIL_TOKEN" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{"cards":[ {"kind":"note","title":"Tone","body":"Warm, low-key, practicals only"}, {"kind":"link","url":"https://example.com/reference"} ]}' Cards without x/y are placed in free space, so they cannot land on top of what is already there. 4. Add an image Two requests: bytes up, then a card referencing what came back. KEY=$(curl -s -X POST "$SOLEIL_API/uploads?board=$BOARD" \ -H "Authorization: Bearer $SOLEIL_TOKEN" \ -H "Content-Type: image/jpeg" \ --data-binary @frame.jpg | jq -r .image_key) curl -s -X POST "$SOLEIL_API/boards/$BOARD/cards" \ -H "Authorization: Bearer $SOLEIL_TOKEN" -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d "{\"kind\":\"image\",\"image_key\":\"$KEY\",\"alt\":\"Diner counter, night\"}" See Images API for formats, the 25 MB ceiling and quota behaviour. TypeScript const API = "https://clusters.soleilpictures.com/api/v1"; const TOKEN = process.env.SOLEIL_TOKEN!; // never ship this to a browser async function soleil(path: string, init: RequestInit = {}): Promise { const res = await fetch(`${API}${path}`, { ...init, headers: { authorization: `Bearer ${TOKEN}`, "content-type": "application/json", // Idempotency-Key makes a retried POST replay rather than duplicate. ...(init.method === "POST" ? { "idempotency-key": crypto.randomUUID() } : {}), ...init.headers, }, }); const body = await res.json().catch(() => ({})); if (!res.ok) throw new Error(`${res.status} ${body.error ?? res.statusText}`); return body as T; } const { board } = await soleil<{ board: { id: string } }>("/boards", { method: "POST", body: JSON.stringify({ name: "Scene 4 — Diner" }), }); await soleil(`/boards/${board.id}/cards`, { method: "POST", body: JSON.stringify({ cards: [{ kind: "note", title: "Tone", body: "Warm, low-key" }], }), }); Python import os, uuid, requests API = "https://clusters.soleilpictures.com/api/v1" TOKEN = os.environ["SOLEIL_TOKEN"] def soleil(method, path, json=None): headers = {"Authorization": f"Bearer {TOKEN}"} if method == "POST": headers["Idempotency-Key"] = str(uuid.uuid4()) r = requests.request(method, f"{API}{path}", json=json, headers=headers) body = r.json() if r.content else {} if not r.ok: raise RuntimeError(f"{r.status_code} {body.get('error', r.reason)}") return body board = soleil("POST", "/boards", {"name": "Scene 4 — Diner"})["board"] soleil("POST", f"/boards/{board['id']}/cards", { "cards": [{"kind": "note", "title": "Tone", "body": "Warm, low-key"}] }) The rest of the loop # Read everything on the board curl -s "$SOLEIL_API/boards/$BOARD/cards" -H "Authorization: Bearer $SOLEIL_TOKEN" # Change one card curl -s -X PATCH "$SOLEIL_API/boards/$BOARD/cards/$CARD" \ -H "Authorization: Bearer $SOLEIL_TOKEN" -H "Content-Type: application/json" \ -d '{"title":"Tone — revised"}' # Move cards to another board curl -s -X POST "$SOLEIL_API/boards/$BOARD/cards/move" \ -H "Authorization: Bearer $SOLEIL_TOKEN" -H "Content-Type: application/json" \ -d "{\"to_board_id\":\"$OTHER\",\"card_ids\":[\"$CARD\"]}" # Delete — the response body IS the undo curl -s -X DELETE "$SOLEIL_API/boards/$BOARD/cards/$CARD" \ -H "Authorization: Bearer $SOLEIL_TOKEN" DELETE on a card returns the full card it removed. There is no undo toast on an HTTP call, so the response body is the undo — POST it back to restore it. Notes for agents Check scopes from /me first. Writing needs write; deleting needs delete, which is a separate grant. Always send Idempotency-Key on POST. Network retries are otherwise duplicate writes. Batch card creation. Up to 1000 cards per call; one call with fifty cards beats fifty calls. Watch x-ratelimit-remaining on every response rather than waiting for the 429. On a 429, honour retry-after. Paginate. List endpoints return 100 by default; follow next_offset until it is null. Do not assume live: true. A false means saved-but-not-pushed to open canvases. *404 means "not found or not yours".* Do not retry it as if it were transient. Branch on code, not the message. Error prose may be reworded; codes are the contract. Full error semantics: Errors and status codes. ## Frequently asked questions Q: What is the smallest useful request? A: GET /me with your bearer token. It confirms the token works and tells you which scopes it has and how much rate budget is left, before you try to write anything. Q: Do I need to create a workspace first? A: No. Creating a board without a workspace_id puts it in your personal workspace, creating one if it does not exist. Q: How do I add an image? A: Two requests. POST the bytes to /uploads to get an image_key, then create a card with kind image and that key. ======================================================================== # Authentication URL: https://clusters.soleilpictures.com/docs/api/authentication Updated: 2026-08-10 Two ways in. For your own scripts, create a personal access token under Settings then API and send it as a bearer token; for an application other people connect, use OAuth instead. Tokens start with sk_live_ and are stored only as a SHA-256 hash, so the value is shown exactly once. Three scopes exist — read, write and delete — and deleting is deliberately separate from writing. A token acts as you and revoking one takes effect immediately. Which one do you want | A personal access token | Your own scripts, your own pipeline, a cron job. You make it, you hold it. OAuth | An application that other people connect — including any MCP client. Nobody pastes a credential. Both end at the same place: a bearer token that resolves to one person's own session. Everything on this page about scopes, what a credential can reach, and the audit trail applies to both. Minting a token Settings → API → New token. Give it a name that says where it will be used, so you know which one to revoke later. Allow writes is the only scope control. Leave it off for anything that only needs to read. The token is displayed once, at creation. It is the prefix undefined followed by 40 hexadecimal characters — 160 bits of randomness: undefined<40 hex characters> Only a SHA-256 hash is stored. There is no "show token" and no recovery — this is deliberate, and it means a database compromise does not yield working credentials. This format is identical in shape to a Stripe live secret key. Secret scanners — GitHub push protection among them — will flag a Soleil token as a Stripe key and block the commit. That is useful when a real token leaks, and a nuisance when you paste an example into a repo. Write examples as undefined… rather than spelling out 40 plausible characters. Using it curl https://clusters.soleilpictures.com/api/v1/me \ -H "Authorization: Bearer undefined…" The scheme must be Bearer. A missing or malformed header gets 401 with a WWW-Authenticate header. Scopes Three: delete · read · write. Scope | Allows read | Every GET write | Creating and changing — POST, PATCH delete | Removing — DELETE Deleting is deliberately separate from writing. "Can add cards to my moodboard" and "can destroy my moodboard" are different levels of trust to hand out, and the distinction matters most when the caller is a language model driving MCP tools. A token missing the scope it needs gets 403 with code: "insufficient_scope" and a required_scope field naming what was missing, rather than a generic refusal. GET /me reports the scopes a token actually has — check it before assuming. A token acts as you The design point worth understanding: The token resolves to your user, and then every request runs as that user, under the same row-level security the app runs under. Boards you own — full access Boards you were invited to as an editor — writable Boards you can only view — reads succeed, writes get 403 Boards you cannot see — 404, never a confirmation they exist There is no per-resource permission list on a token, because there does not need to be. This is also why a token cannot be used to escalate: it has no authority of its own. Revoking Settings → API → Revoke, effective immediately. The next request with that token gets 401. Unknown, revoked and expired tokens all return the same 401 invalid token — the API does not distinguish, so a probe cannot learn which tokens once existed. Up to 20 active tokens per account. Revoked ones do not count against it. Expiry Tokens do not expire by default. An optional lifetime can be set at creation. Keeping a token safe A undefined token is equivalent to your account for everything the API can reach. Treat it like a password. Never put one in front-end code. CORS is open so it will work, and anyone who opens dev tools will have it. Never commit one. Use an environment variable. One token per integration, so you can revoke one without breaking the others. Read-only unless you need writes. Rotate by creating the new token, deploying it, then revoking the old one — both work simultaneously, so there is no downtime. When a token should not be a person A personal token acts as you, which means an integration built on one stops working the day you leave the workspace. For anything a team depends on, create a service account instead: a credential owned by the workspace, scoped to it, and unaffected by who comes and goes. Rate limiting 1000 requests per hour, per token. (Service tokens default to 10000.) Every response carries the current state, not just refusals: Header | Meaning x-ratelimit-limit | Your ceiling x-ratelimit-remaining | What is left in this window x-ratelimit-reset | Unix seconds when the window resets retry-after | Seconds to wait — only on 429 A client that can only learn its budget by being rejected has to hit the wall to find it, so the numbers ride along on every call. GET /me reports the same figures in its body. Separate tokens have separate budgets, which is another reason to give each integration its own. Tokens and MCP The MCP server uses the same token — it holds no credentials of its own and simply forwards yours. Everything on this page applies to it. ## Frequently asked questions Q: I lost my token. Can I recover it? A: No. Only a hash is stored, by design. Revoke it and create a new one. Q: Why is delete a separate scope from write? A: Because "can add cards to my moodboard" and "can destroy my moodboard" are different levels of trust, especially when the caller is a language model. Q: How many tokens can I have? A: Up to 20 active at once. Revoked ones do not count. ======================================================================== # Boards API URL: https://clusters.soleilpictures.com/docs/api/boards Updated: 2026-08-08 Boards are what the app calls clusters. List them with GET /boards, filtered by workspace or parent and paginated, create with POST /boards, read one with GET /boards/:id, rename or reparent with PATCH, soft-delete with DELETE and put it back with POST to the restore endpoint. Reparenting is cycle-safe and refused rather than allowed to create a loop. A board is what the interface calls a cluster. Same object, two words. The board object { "id": "9f1c…", "name": "Scene 4 — Diner", "workspace_id": "3b7e…", "parent_board_id": null, "view": "canvas", "created_at": "2026-08-01T09:12:44.000Z", "updated_at": "2026-08-03T14:02:10.000Z" } Field | Notes id | UUID name | Up to 200 characters; trimmed workspace_id | UUID parent_board_id | UUID, or null at the top level view | "canvas" or "list" GET /workspaces { "workspaces": [ { "id": "…", "name": "Personal", "created_at": "…" } ] } GET /boards Query | Effect workspace= | Only boards in that workspace parent= | Only children of that board parent=root | Only top-level boards deleted= | Include soft-deleted boards since= | Only boards changed at or after that time — see below cursor= | Continue a since walk include= | props, identifiers — see Identifiers and properties limit / offset | Page size (default 100, max 500) and start curl "$SOLEIL_API/boards?parent=root&limit=50" -H "Authorization: Bearer $SOLEIL_TOKEN" { "boards": [ … ], "limit": 50, "offset": 0, "has_more": true, "next_offset": 50 } Follow next_offset until it comes back null. Asking what changed Pass since and the listing becomes a change feed: ordered by updated_at rather than created_at, and paged by cursor rather than offset. curl "$SOLEIL_API/boards?workspace=$WS&since=2026-08-09T00:00:00Z" \ -H "Authorization: Bearer $SOLEIL_TOKEN" { "boards": [ … ], "limit": 100, "has_more": true, "next_cursor": "2026-08-09T12:00:00Z|3b7e…" } Pass next_cursor back as cursor until has_more is false, then keep the last updated_at you saw as the since for your next run. Offset paging is wrong for this and cursor paging is right, for a specific reason: rows are being written while you walk, so offsets shift under you and a page boundary both skips and repeats. The cursor carries a board id alongside the timestamp because two boards touched in the same transaction share a timestamp exactly, and a timestamp alone would drop whichever came second. GET /boards/tree The hierarchy in one call, rather than one call per level. curl "$SOLEIL_API/boards/tree?root=$BOARD&depth=6" -H "Authorization: Bearer $SOLEIL_TOKEN" { "root": "3b7e…", "count": 214, "boards": [ { "id": "3b7e…", "parent_board_id": null, "name": "THE FALL", "depth": 0, "card_count": 0, "updated_at": "…", "deleted": false }, { "id": "9f1c…", "parent_board_id": "3b7e…", "name": "Costume", "depth": 1, "card_count": 42, "updated_at": "…", "deleted": false } ] } Pass root (a board id) or workspace, and optionally depth (default 10, maximum 20). Results are flat with a depth and a parent_board_id, so you can rebuild the tree in whatever shape you need without the response nesting for you. A show's structure — title, department, sequence, shot — is the first thing any integration walks, and ?parent= costs a request per node. POST /boards { "name": "Scene 4 — Diner", "workspace_id": "optional uuid", "parent_board_id": "optional uuid", "view": "canvas" } Only name is required. No workspace_id → your personal workspace, created if needed. No parent_board_id → top level. view is "canvas" unless you pass exactly "list". Returns 201 with { "board": … }. Creating many at once Pass a boards array instead, up to 500 per call: { "workspace_id": "optional uuid — the default for every entry", "boards": [ { "name": "Scene 4 — Diner", "parent_board_id": "…" }, { "name": "Scene 5 — Motel", "parent_board_id": "…" } ] } Returns 201 with { "boards": [ … ], "created": 2 }. This matters when you are importing an existing library, because a large one is a tree — a board per scene, reel or shoot — so the first thing an import does is create thousands of boards. One request each is the slowest possible way to do that; this is two inserts however many you pass. Every entry is validated before anything is written, so a bad entry at index 900 is a clean 400 rather than 900 boards and an error. The whole batch is one insert as you, so a workspace you cannot write refuses the batch rather than half-applying it. Creating the same boards twice Give each board an identifiers array and pass "on_conflict": "identifier", and a board already carrying one of those identifiers is updated instead of created again — with its id unchanged: { "on_conflict": "identifier", "boards": [ { "name": "SEQ 0100", "identifiers": [{ "scope": "shotgrid", "value": "Sequence:88" }] } ] } The response adds updated, and each board carries created: true|false. This is what makes an import re-runnable — see Identifiers and properties. GET /boards/:id { "board": … }, or 404 — which is also what you get for a board that exists but is not yours. The API never confirms the existence of something you cannot see. PATCH /boards/:id Every field optional: { "name": "Scene 4 — Diner (rev)", "view": "list", "parent_board_id": "…" } Reparenting is cycle-safe: moving a board into one of its own descendants is refused with 409 conflict, not allowed to create a loop. parent_board_id: null moves it to the top level. DELETE /boards/:id A soft delete, requiring the delete scope. The board goes to the trash and stays restorable for 30 days. { "deleted": true, "board": { … }, "restorable": true } Descendants are deliberately not touched — deleting a parent does not cascade. POST /boards/move — restructure in one call { "board_ids": ["3b7e…", "9f1c…"], "parent_board_id": "a2c4…" } Pass "parent_board_id": null to move boards to the top level. Up to 500 at a time. Reparenting always goes through the app's own move path — the only thing that checks for cycles, because a board made its own ancestor detaches that whole subtree from every view there is. Anything it refuses comes back in skipped with a named reason (cycle, cross-workspace, no-write, same-parent, missing, self) rather than as one opaque failure for the batch. DELETE /boards — many at once { "board_ids": ["3b7e…", "9f1c…"] } Soft, and restorable one at a time, exactly like the single delete. Sent as a JSON body on DELETE so a destructive call stays a DELETE. POST /boards/:id/restore Puts a soft-deleted board back. Find deleted boards with GET /boards?deleted=. Worked example # A project with two scenes under it PROJECT=$(curl -s -X POST "$SOLEIL_API/boards" \ -H "Authorization: Bearer $SOLEIL_TOKEN" -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{"name":"Feature — Act One"}' | jq -r .board.id) for scene in "Scene 3 — Motel" "Scene 4 — Diner"; do curl -s -X POST "$SOLEIL_API/boards" \ -H "Authorization: Bearer $SOLEIL_TOKEN" -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d "{\"name\":\"$scene\",\"parent_board_id\":\"$PROJECT\"}" done curl -s "$SOLEIL_API/boards?parent=$PROJECT" -H "Authorization: Bearer $SOLEIL_TOKEN" Next: Cards API · Images · Search. ## Frequently asked questions Q: What is the difference between a board and a cluster? A: Nothing. The interface says cluster, the API says board. Same object. Q: How do I create a board at the top level? A: Omit parent_board_id. Omit workspace_id too and it goes in your personal workspace, which is created if it does not exist. Q: Can I undo a delete? A: Yes. DELETE is a soft delete and POST /boards/:id/restore puts it back. ======================================================================== # Connecting an app (OAuth) URL: https://clusters.soleilpictures.com/docs/api/oauth Updated: 2026-08-10 Soleil Clusters is its own OAuth 2.1 authorization server, so an MCP client or any other application can connect without anyone copying a token. Discovery is at /.well-known/oauth-protected-resource and /.well-known/oauth-authorization-server, client registration is dynamic and open, PKCE with S256 is required, and access tokens last 60 minutes with a rotating refresh token. The person approves the connection on one screen and can disconnect it at any time under Settings then API. There are two ways into this API. A personal access token is right for your own scripts — you make it, you hold it, you paste it once. OAuth is right for an application other people connect, because nobody should ever be asked to paste a credential into somebody else's software. If you are wiring up an MCP client, you almost certainly want this one, and you probably do not have to implement any of it — see MCP. Discovery Everything below is discoverable. Start from a 401: HTTP/1.1 401 Unauthorized WWW-Authenticate: Bearer realm="soleil", error="invalid_token", scope="read write", resource_metadata="https://clusters.soleilpictures.com/.well-known/oauth-protected-resource/api/v1/mcp" That header is the entry point. Follow it: | /.well-known/oauth-protected-resource/api/v1/mcp | What this resource is, and who authorizes it (RFC 9728) /.well-known/oauth-authorization-server | The endpoints and what they support (RFC 8414) Both are public and cacheable. A discovery document you need a credential to read is not discovery. Registering Registration is open and dynamic — no review, no application form, no key to request: curl -X POST https://clusters.soleilpictures.com/oauth/register \ -H 'content-type: application/json' \ -d '{ "client_name": "Shot Planner", "redirect_uris": ["https://shotplanner.example/callback"], "token_endpoint_auth_method": "none" }' { "client_id": "soleil_a1b2…", "client_id_issued_at": 1786000000, "client_name": "Shot Planner", "redirect_uris": ["https://shotplanner.example/callback"] } At most 10 redirect URIs. They must be https, http on loopback (127.0.0.1, ::1, localhost — for a command-line client with no domain), or a private scheme like shotplanner://. A URI with a fragment is refused: the response is appended to the query, and a URI that already has a fragment cannot be extended safely. Leave token_endpoint_auth_method as none unless your client can genuinely keep a secret. A desktop app, a CLI and a browser extension cannot — PKCE is what proves identity there, and a "secret" shipped inside a download is not one. The flow client browser Soleil │ │ │ ├─ 401 + resource_metadata ─────────────────────────────┤ ├─ GET /.well-known/… ─────────────────────────────────┤ ├─ POST /oauth/register ────────────────────────────────┤ ├─ open /oauth/authorize ─▶│ │ │ ├─ sign in (or sign up) ────▶│ │ ├─ approve ─────────────────▶│ │◀─ redirect_uri?code=…&state=…&iss=… ──────────────────┤ ├─ POST /oauth/token ───────────────────────────────────┤ │◀─ access_token + refresh_token ───────────────────────┤ Authorization. Send the person to: https://clusters.soleilpictures.com/oauth/authorize ?response_type=code &client_id=soleil_a1b2… &redirect_uri=https://shotplanner.example/callback &scope=read%20write &state= &code_challenge= &code_challenge_method=S256 &resource=https%3A%2F%2Fclusters.soleilpictures.com%2Fapi%2Fv1%2Fmcp code_challenge_method must be S256. resource names what the token is for (RFC 8707) — it is how a token minted here can never be valid anywhere else. If the person has no account, they get one: our sign-in is a single email box that creates the account if there isn't one. They never have to visit the site first. The response carries code, your state, and iss: https://shotplanner.example/callback?code=ac_…&state=…&iss=https://clusters.soleilpictures.com iss is RFC 9207 and we always send it, including on errors. Compare it to the issuer you recorded from the metadata before you send the code anywhere. That single comparison is what defeats a mix-up between two authorization servers. The exchange: curl -X POST https://clusters.soleilpictures.com/oauth/token \ -d grant_type=authorization_code \ -d code=ac_… \ -d redirect_uri=https://shotplanner.example/callback \ -d client_id=soleil_a1b2… \ -d code_verifier= { "access_token": "sk_mcp_…", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "rt_…", "scope": "read write" } The code is good for 120 seconds and is single-use. It is bound to the client, the redirect URI and the PKCE challenge it was issued against, and all four are checked in the same statement that consumes it — so a code that leaks is worth nothing without the verifier, and a replay finds nothing. Tokens | Access token | 60 minutes, sent as Authorization: Bearer … Refresh token | Rotates on every use; valid 90 days from last use Scopes | delete · read · write — the same three scopes a token has curl -X POST https://clusters.soleilpictures.com/oauth/token \ -d grant_type=refresh_token -d refresh_token=rt_… -d client_id=soleil_a1b2… Refresh tokens are single-use: the old one is replaced in the same statement that claims it, so a replayed refresh finds nothing. Store the new one. delete is never granted by default. Ask for it in scope only if the application genuinely needs to destroy things — the product's rule is that an assistant can be allowed to build without being allowed to destroy, and a default that quietly included delete would undo that for every connection. What the person sees One screen: who is asking, what each scope means in plain words, and two buttons. Afterwards the connection appears under Settings → API → Connected apps with its scopes, when it was connected, and how many calls it has made. Disconnecting revokes the access token in the same statement, so it stops working immediately rather than at its next expiry. Everything the app did is in the audit log, with the tool it used. At most 20 connected apps per account. What a token can reach Exactly what the person can reach. An OAuth access token is resolved to their own database session, under ordinary row-level security, by the same code path a personal access token uses. There is no separate permission model to get wrong: if they cannot see a cluster, neither can anything they connect. Errors Failures use the OAuth shape, so a client can branch on them: { "error": "invalid_grant", "error_description": "that authorization code is not valid" } | invalid_client | Unknown client_id, or a confidential client failed to authenticate invalid_grant | Code or refresh token expired, already used, or not yours invalid_request | A required parameter is missing or malformed invalid_redirect_uri | Registration was refused — see the shape rules above invalid_target | The resource named is not served here unsupported_grant_type | Only authorization_code and refresh_token exist A bad redirect_uri is never redirected to. If the URI is not one the client registered, the person sees an error page and nothing is sent anywhere — bouncing an error to an unverified address is the open redirect the check exists to prevent. Revoking curl -X POST https://clusters.soleilpictures.com/oauth/revoke -d token=rt_… -d client_id=soleil_a1b2… Always answers 200, even for a token that was never valid, per RFC 7009. Saying "no such token" would turn the endpoint into a way to test whether a stolen string is live. ## Frequently asked questions Q: Do I need to register an application first? A: No. Registration is dynamic and open — POST your client metadata to /oauth/register and you get a client_id back immediately. There is no review queue and no key to request. Q: Is PKCE required? A: Yes, with S256. The plain method is not supported at all, because OAuth 2.1 removes it. Q: How long do tokens last? A: An access token lasts 60 minutes. The refresh token rotates on every use and is good for 90 days from its last use. Q: What happens if someone disconnects the app? A: The access token is revoked in the same statement, so it stops working immediately rather than at its next expiry. Q: Can I still use a personal access token instead? A: Yes. Nothing about tokens has changed. OAuth is for applications that other people connect; a token is for your own scripts. ======================================================================== # Cards API URL: https://clusters.soleilpictures.com/docs/api/cards Updated: 2026-08-10 Read a board's cards with GET /boards/:id/cards, add up to 1000 at a time with POST, change one with PATCH, move a set with the move endpoint, and remove one with DELETE — which returns the whole card it deleted, so the response body is your undo. Six card kinds are accepted and an unknown kind is rejected rather than silently coerced. Bulk PATCH and DELETE take a batch in one call. The card object { "id": "api-m8x2p1-7fq3ka", "kind": "note", "x": 120, "y": 340, "z": 3, "w": 280, "h": 180, "title": "Tone", "body": "Warm, low-key, practicals only", "url": null, "image_key": null, "alt": null, "color": null, "created_at": "2026-08-01T09:14:02.000Z", "updated_at": "2026-08-01T09:19:55.000Z" } Interior state the editor owns — grid layouts, adjustment settings, collaborative document structure — is deliberately not exposed. An API caller should not be able to write arbitrary internals into everyone's board. Kinds The API accepts doc, file, image, link, note, video. Kind | Carries note | title, body, html image | image_key, alt, body as the caption link | url, title, body doc | title, body, html video | file_key, optional poster_key file | file_key, file_name, mime, ext, size_bytes video and file exist because multipart upload accepts ProRes, MXF, DPX and camera raw — so without them you could upload a two-terabyte camera master and then have no way to put it on a board. An upload you cannot place is not an upload. An unrecognised kind gets a 400 naming the valid ones. It is not coerced — silently turning an unknown kind into a note produced boards full of notes that were meant to be links. This is still narrower than what the canvas supports. Grids, schedules, palettes and shapes are created in the app; you can read them here, and ?include=raw gives you their full contents. Writable fields Field | Type | Limit kind | string | one of the six above title | string | 300 chars body | string | 20000 chars html | string | 40000 chars url | string | 2000 chars image_key | string | 500 chars — from POST /uploads file_key | string | 500 chars — for video and file poster_key | string | 500 chars — a still for a video file_name | string | 300 chars mime | string | 200 chars ext | string | 20 chars size_bytes | number | rounded alt | string | 300 chars — image description color | string | 40 chars props, identifiers | see Identifiers and properties | x, y | number | rounded; omit for auto-placement w, h | number | clamped to 40–4000; default 280 × 180 z | number | stacking; higher is in front, fractional is fine rotation | number | degrees, wrapped to −180…180 group_id | string | a group from POST /boards/:id/groups section_header | boolean | render as a full-width heading sub | string | 300 chars — the line under a section heading Anything else in the payload is ignored. A client-supplied id is ignored — the server generates one. GET /boards/:id/cards Paginated with limit and offset — default 100, maximum 500. { "board_id": "9f1c…", "cards": [ … ], "limit": 100, "offset": 0, "has_more": false, "next_offset": null } Follow next_offset until it is null. Do not assume one page is the whole board. ?source=index — for large boards The default reads the live board, so a card a collaborator added seconds ago is already there. The cost is that it loads the whole board to answer: limit and offset are applied afterwards, so paging a large board re-reads it once per page, and total is only knowable by reading all of it. Fine for a hundred cards; wasteful for a hundred thousand. ?source=index reads the row-per-card mirror instead, paged by cursor: { "board_id": "9f1c…", "source": "index", "cards": [ { "id": "…", "kind": "image", "title": null, "x": 120, "y": 340, "image_key": "…", "updated_at": "2026-08-08T12:00:00Z" } ], "limit": 100, "has_more": true, "next_cursor": "api-m8x2p1-7fq3ka" } Pass next_cursor back as cursor. Use this to verify what exists — after a bulk import, say. It is a projection, not the card: enough to reconcile, not enough to rebuild one. Fetch without source for the real thing. Add since= and it becomes a change feed, ordered by updated_at instead of by id, with a cursor that carries both. That is the cheap way to ask "what moved on this board since I last looked" without reading the board. ?include= Value | Adds props | The card's properties identifiers | Its foreign identifiers raw | The card exactly as the canvas stores it raw exists because the card object above is a deliberately narrow projection, and the app has kinds it does not describe — a grid carries its cells and template, a palette its swatches, a schedule its rows. Those read back through the projection with their interiors missing, which for anyone taking a backup is data loss that looks like success. raw is the card's internal shape: field names in it are not part of this API's contract and can change with the app. Use it to preserve or reconstruct, not to build logic on. /export includes it always. A misspelled include is a 400 rather than a silent omission. POST /boards/:id/cards Either shape works: { "cards": [ { "kind": "note", "title": "Tone" } ] } { "kind": "note", "title": "Tone" } Up to 1000 cards per call; more gets 400. Auto-placement. Cards without x/y are placed in free space, so a batch cannot land on top of existing content. Pass both to place one yourself. Laying cards out Pass layout — justified, masonry, grid, row or column — to arrange the whole batch as it lands instead of appending it in free space, and use POST /boards/:id/arrange to lay out cards that already exist. A named layout arranges everything you sent, including cards that carried their own x and y. Pass coordinates when you are importing Placing cards for you means reading the whole board first, to know what to place them around. That is the right default — but it means the call costs more the more the board already holds. A batch where every card carries its own x and y skips that read, and costs the same on an empty board as on one with a hundred thousand cards. If you are importing a library, you already know your layout, so send it: { "cards": [ { "kind": "image", "image_key": "…", "x": 0, "y": 0, "w": 300, "h": 200 }, { "kind": "image", "image_key": "…", "x": 320, "y": 0, "w": 300, "h": 200 } ] } All or nothing: one card missing coordinates puts the whole batch on the read-the-board path. live. true means open canvases received the change immediately. false means it is saved but a canvas someone already has open needs a reload. Never treat false as failure. Adding the same cards twice Give each card an identifiers array and pass "on_conflict": "identifier", and a card already carrying one is updated in place rather than added again: { "on_conflict": "identifier", "cards": [ { "kind": "image", "image_key": "…", "x": 0, "y": 0, "identifiers": [{ "scope": "shotgrid", "value": "Asset:12345" }] } ] } The response carries created and updated counts. Run your importer twice over three million assets and you get three million cards, not six. See Identifiers and properties. Every card write also accepts props and identifiers directly, whether or not you are upserting. PATCH /boards/:id/cards — many at once { "cards": [ { "id": "api-m8x2p1-7fq3ka", "title": "Approved" }, { "id": "api-m8x2p1-9wq2lb", "props": { "status": "final" } } ] } Up to 1000 per call. Each entry needs an id; everything else is an ordinary partial patch. The board is opened once for the whole batch, which is the difference between a five-hundred-card update taking a second and taking a minute — patching one at a time opens, syncs, commits and closes each time. Ids that were not on the board come back in not_found rather than being silently skipped, because a bulk write that quietly does nothing for part of its input is worse than one that fails. DELETE /boards/:id/cards — many at once { "card_ids": ["api-m8x2p1-7fq3ka", "api-m8x2p1-9wq2lb"] } Sent as a JSON body on DELETE, which is unusual but deliberate: a thousand card ids do not fit in a query string, and making a destructive call look like a POST would mislead every proxy, log and permission check between you and it. Every removed card comes back in full, so the response is the undo. PATCH /boards/:id/cards/:cardId A partial patch — only the fields you send change. No defaults are applied, so patching title alone will not resize the card. curl -X PATCH "$SOLEIL_API/boards/$BOARD/cards/$CARD" \ -H "Authorization: Bearer $SOLEIL_TOKEN" -H "Content-Type: application/json" \ -d '{"title":"Tone — revised","x":400}' POST /boards/:id/cards/move { "to_board_id": "…", "card_ids": ["api-…", "api-…"] } Write access is checked on both boards. Returns { "moved": 2, "cards": [ … ], "live": true }. DELETE /boards/:id/cards/:cardId Requires the delete scope — see Authentication. { "deleted": true, "card": { …the entire card… }, "restore_with": "POST /api/v1/boards/:id/cards" } The response body is the undo. There is no undo toast on an HTTP call, so the full card comes back — POST it to the cards endpoint to restore it. const { card } = await soleil(`/boards/${board}/cards/${id}`, { method: "DELETE" }); // …later, if that turns out to have been wrong: await soleil(`/boards/${board}/cards`, { method: "POST", body: JSON.stringify(card) }); Images Two requests: upload the bytes, then create the card. See Images API. Card caps Card creation is subject to the account's card allowance — 100 on the free plan, counted against the board owner. Exceeding it returns 402 limit_reached, not 403. See Errors. ## Frequently asked questions Q: What happens if I send an unrecognised kind? A: A 400 naming the kinds that are valid. It used to fall back to note silently, which produced boards full of notes that should have been links. Q: How do I add an image? A: Upload the bytes to /uploads first, then create a card with kind image and the image_key it returned. Q: What does live false mean in the response? A: The cards are saved, but a canvas someone already has open will not show them until it reloads. It is not a failure. ======================================================================== # Images API URL: https://clusters.soleilpictures.com/docs/api/images Updated: 2026-08-08 POST raw image bytes to /uploads with a board id and you get back an image key, which you then pass as image_key when creating a card. It is one request rather than a presign dance. Files larger than the one-request ceiling go through /uploads/multipart, where you PUT the parts straight to storage and the bytes never pass through the API. Either way the upload is charged against the board owner's storage quota. Uploading is one request. No presign, no multi-step S3 dance — which is what makes it usable from a single tool call. POST /uploads?board= Raw bytes in the body, Content-Type set to the image's real type. curl -X POST "$SOLEIL_API/uploads?board=$BOARD" \ -H "Authorization: Bearer $SOLEIL_TOKEN" \ -H "Content-Type: image/jpeg" \ --data-binary @frame.jpg { "image_key": "3b7e…/9f1c….jpg", "width": 3024, "height": 4032, "bytes": 2841923, "content_type": "image/jpeg", "next": "POST /api/v1/boards/…/cards with {\"kind\":\"image\",\"image_key\":\"…\"}" } The response spells out the next call, because it is not guessable from the key alone. The ?board= parameter is required: an upload is charged to a board, and write access to that board is checked before anything is stored. Then place it curl -X POST "$SOLEIL_API/boards/$BOARD/cards" \ -H "Authorization: Bearer $SOLEIL_TOKEN" -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{"kind":"image","image_key":"3b7e…/9f1c….jpg","alt":"Diner counter, night"}' Pass alt for a description. Omit x/y and the card is placed in free space. Limits | Types | JPEG, PNG, GIF, WebP, HEIC, AVIF Size | 25 MB per image through the API Storage | Counted against the board owner's quota A missing or unrecognised Content-Type gets 415 — the file extension is derived from the declared type, so it has to be right. An empty body gets 400. Over the size ceiling gets 413. Past the owner's storage quota gets 402. See Errors. 25 MB is the ceiling on this one-request form, because the whole body is held in memory to read its header. Larger files go through the multipart endpoints below, which have no such limit. Large files POST /uploads is the convenient path. For anything bigger than 25 MB — camera media, ProRes, a scan of a whole lookbook — use multipart, where the bytes never pass through the API at all. You get signed URLs and PUT directly to storage, in parallel, at whatever speed your connection allows. Four calls, and only the first and last are API requests. 1. Start curl -X POST "$SOLEIL_API/uploads/multipart?board=$BOARD" \ -H "Authorization: Bearer $SOLEIL_TOKEN" -H "Content-Type: application/json" \ -d '{"bytes":53687091200,"content_type":"video/quicktime","filename":"reel_01.mov"}' { "key": "3b7e…/9f1c….mov", "upload_id": "2~abc…", "part_size": 8388608, "part_count": 6400 } bytes is required: the storage quota is checked against the total size up front, so a file that will not fit is refused before you send any of it. 2. Get signed URLs curl -X POST "$SOLEIL_API/uploads/multipart/parts" \ -H "Authorization: Bearer $SOLEIL_TOKEN" -H "Content-Type: application/json" \ -d '{"board_id":"'$BOARD'","key":"…","upload_id":"…","part_numbers":[1,2,3]}' Up to 1000 part numbers per call. Ask for them in batches as you go — signed URLs are time-limited, so requesting 6,400 at once is worse than requesting them as you need them. 3. PUT each part to the returned URL Not to this API. Each response carries an ETag; keep it with its part number. Parts may be uploaded in any order and in parallel. Upload many parts at once. Nothing sits between you and storage on this path, so your throughput is your own connection — but only if you keep it busy. Each part is a separate HTTPS request, and a few in flight spends most of its time in handshakes and TCP ramp-up rather than sending bytes. Measured over one 324 Mbit link, same code, varying only how many parts were in flight: in flight | throughput | share of the link 4 | 18.5 MB/s | 46% 15 | 31.8 MB/s | 78% 30 | 36.9 MB/s | 91% Around 30 concurrent parts saturates a link; the remainder is protocol overhead. Uploading several files at once counts the same way — it is total requests in flight that matters, not how they are grouped. 4. Finish curl -X POST "$SOLEIL_API/uploads/multipart/complete" \ -H "Authorization: Bearer $SOLEIL_TOKEN" -H "Content-Type: application/json" \ -d '{"board_id":"'$BOARD'","key":"…","upload_id":"…", "parts":[{"part_number":1,"etag":"\"a1b2…\""}]}' Returns the image_key, which you place as a card exactly as above. Dimensions come back for formats that carry them in a header; other files report null. POST /uploads/multipart/abort with the same key and upload_id discards an upload you have given up on, so the parts are not billed as storage. Multipart needs a paid account on the workspace that owns the board — the same rule the app applies. Without one the first call returns 403. Every step re-checks write access to the board. A signed part URL is a capability, so nothing relies on a check made three calls earlier. GET /images Lists what is already stored, so an interrupted bulk upload can be resumed without re-sending everything. curl "$SOLEIL_API/images?workspace=$WS&limit=500" -H "Authorization: Bearer $SOLEIL_TOKEN" { "images": [ { "image_key": "3b7e…/9f1c….jpg", "bytes": 2841923, "width": 3024, "height": 4032, "board_id": "…", "workspace_id": "…", "created_at": "2026-08-08T12:00:00Z" } ], "limit": 500, "has_more": true, "next_cursor": "2026-08-08T12:00:00Z|9f1c…" } Filter with workspace, board and since (an ISO timestamp). Paging is by cursor, not offset: pass next_cursor back as cursor. Offset paging makes the database walk and discard every row it skips, so it gets slower the further in you go — which only bites once a listing is long, which is exactly when you need it. GET /images/:key Reads an image back. Access is authorized the same way as everything else — you get the image if your account can see a board that references it. Smaller renditions Add ?variant=preview for the downscaled copy the app stores when an image is uploaded — roughly 900px and about 48 kB, against ~470 kB for a typical original. It is more than enough to look at, and about ten times cheaper to move, which matters if you are reading a whole moodboard or handing images to a model. Not every image has one, so this falls back to the original rather than failing. The X-Image-Variant response header tells you which you got: curl -sD- -o shot.webp \ "https://clusters.soleilpictures.com/api/v1/images/$KEY?variant=preview" \ -H "Authorization: Bearer $TOKEN" | grep -i x-image-variant # x-image-variant: preview The MCP server's view_image asks for this by default. Why the images row matters An upload does two things: it stores the object, and it records a row that authorizes reads and marks the object as in use. If the row cannot be written the upload fails and the object is deleted, rather than returning a key that would never resolve and would be swept away later. An upload that returns 201 is an upload that is fully durable. Storage lifecycle Files referenced by a board are protected from cleanup for as long as the board references them. Removing the last card that uses an image makes it eligible for reclamation later, not immediately — so deleting a card and restoring it from the delete response works. ## Frequently asked questions Q: How do I add a photo to a board from code? A: Two requests. POST the bytes to /uploads?board= to get an image_key, then POST a card with kind image and that key. Q: What image formats are accepted? A: JPEG, PNG, GIF, WebP, HEIC and AVIF for the one-request upload. Multipart accepts any file type, including video, audio and PDFs. Q: How do I upload a very large file? A: Use POST /uploads/multipart. You declare the total size, get signed URLs for each part, PUT the parts directly to storage in parallel, then call complete. There is no size ceiling on this path. Q: How do I resume an upload run that failed halfway? A: GET /images lists what is already stored, with cursor paging. Diff it against your local manifest and upload only what is missing. Q: Whose storage does an upload use? A: The board owner's, exactly as in the app. Going past it returns 402. ======================================================================== # Import URL: https://clusters.soleilpictures.com/docs/api/import Updated: 2026-08-10 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, 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. 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 is | You get An image format the API stores (doc, file, image, link, note, 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 video | A 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. 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. 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 Q: What happens if I run the same import twice? A: 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. Q: Can it import a PDF or a video? A: 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. Q: What happens if one URL is dead? A: That item fails and the rest still import. Every item reports its own outcome, and the envelope counts what landed. Q: Can I check a long list before running it? A: Yes. Pass dry_run to validate every URL without fetching or creating anything. ======================================================================== # Arrange URL: https://clusters.soleilpictures.com/docs/api/arrange Updated: 2026-08-10 POST /boards/:id/arrange lays out cards that already exist, and any card write accepts a layout to arrange what it adds. The default, justified, fits rows flush on both edges at each picture's true aspect ratio. For placing things yourself, cards carry x, y, w, h, z, rotation, a group_id, and a section_header flag. Placing cards is half of an API that works. The other half is that what lands looks composed rather than spilled. Automatic curl -X POST https://clusters.soleilpictures.com/api/v1/boards/$BOARD/arrange \ -H "Authorization: Bearer undefined…" \ -H "Content-Type: application/json" \ -d '{ "layout": "justified" }' Omit card_ids to arrange the whole board, or pass a subset to tidy part of it. The same layout works on adding cards and on import, so a batch can land arranged instead of needing a second call. The layouts column · grid · justified · masonry · row justified — the default. Rows of equal height, each picture at its true aspect ratio, flush on both edges. There are no holes in it, which is why it is the right answer for photographs. ┌────────┐┌───┐┌─────────┐ │ ││ ││ │ └────────┘└───┘└─────────┘ ┌─────┐┌──────────┐┌─────┐ │ ││ ││ │ └─────┘└──────────┘└─────┘ masonry — columns of equal width, balanced by height, ordered by colour so the board reads as a deliberate palette sweep rather than as arrival order. Leaves a ragged bottom edge, which is the trade for never cropping anything. grid — one uniform cell for everything, each card centred in its cell. The right answer for mixed kinds — an image beside a PDF beside an audio clip — where a shared cell reads as a clean matrix. For photographs of different shapes it leaves a hole around every portrait, which is what justified fixes. row and column — a single line, cross-axis centred. Knobs Field | Meaning gap | Space between cards. Default 24. width | The width the block is solved against. Omit it and a roughly square block is chosen from the number of items — a canvas has no edges, so there is no container to infer one from. row_height | Target row height. justified only. columns | Column cap. grid and masonry only. dry_run | Compute everything, write nothing. card_ids | Arrange a subset. At most 1000 per call. Two things worth knowing. justified resizes cards — fitting a row to a width is a resize, and it is the only layout that does. And a named layout arranges the whole batch including cards that carried their own x and y: "lay these out as rows" and "put this one at x=40" are contradictory, and the one you asked for by name wins. By hand Every card carries the full geometry: Field | Meaning x, y | Position, in canvas units w, h | Size z | Stacking. Higher is in front; fractional values are fine rotation | Degrees group_id | Membership of a group section_header | Render as a full-width heading z was readable but not writable until now — you could see which of two overlapping cards was on top and had no way to swap them. Groups A group is how a board says these belong together: a labelled outline round a set of cards, which then move as one. curl -X POST https://clusters.soleilpictures.com/api/v1/boards/$BOARD/groups \ -H "Authorization: Bearer undefined…" \ -H "Content-Type: application/json" \ -d '{ "name": "Costume — Act II", "shape": "box" }' The full set: Endpoint | What it does GET /boards/:id/groups | The groups on a board POST /boards/:id/groups | Create one PATCH /boards/:id/groups/:groupId | Rename or restyle it DELETE /boards/:id/groups/:groupId | Ungroup — removes the group, never its cards Pass the returned id as group_id on any card. shape is box (one rectangle round everything) or hug (following each card). Deleting a group ungroups; it never deletes the cards — there is no undo on an HTTP call, and a group holding forty cards must not be a way to lose forty cards. Section headings A card with section_header: true becomes a full-width heading, and if the board is published it renders as an

in the article. Add sub for a line underneath. This is what turns a wall of references into something with a shape somebody else can read. ## Frequently asked questions Q: How do I make a dump of images look like a moodboard? A: Pass layout when you import or add them, or call POST /boards/:id/arrange afterwards. The default, justified, gives rows of equal height flush on both edges, with every picture at its real aspect ratio. Q: Can I see where things would land before moving anything? A: Yes. Pass dry_run and the response contains the computed positions with nothing written, so you can compare two layouts before touching a board. Q: Will arranging move my cards somewhere else on the canvas? A: No. The block is re-anchored on its own current top-left, and pushed clear of any cards it is not moving, so tidying part of a board cannot bury the rest. Q: How do I say that a set of cards belongs together? A: Create a group with POST /boards/:id/groups and pass its id as group_id on each card. A group draws a labelled outline round its cards and moves them as one. ======================================================================== # Identifiers and properties URL: https://clusters.soleilpictures.com/docs/api/metadata Updated: 2026-08-09 An identifier is a scope and value pair assigned by another system, like shotgrid and Shot:12345. Attach as many as you like to a board or a card, look objects up by them with GET /resolve, and pass on_conflict identifier when creating so an object that already carries one is updated instead of duplicated. That is what makes an import re-runnable. Properties are a free-form JSON object on the same objects, for whatever fields your pipeline needs. Everything in your pipeline already has a name. The shot is ABC_0100_0010, the asset is Shot:12345 in production tracking, the file has a checksum. An API that cannot record any of that leaves you maintaining a mapping table on the side, forever, and re-running an import creates a second copy of everything. Two things fix that, and they apply to boards, cards and images alike. Identifiers An identifier is a scope and a value — the system that assigned it, and what it assigned. An object can carry several, from several systems. curl -X POST "$SOLEIL_API/boards" \ -H "Authorization: Bearer $SOLEIL_TOKEN" -H "Content-Type: application/json" \ -d '{ "name": "Costume — Sven, fall sequence", "workspace_id": "'$WS'", "identifiers": [ {"scope": "shotgrid", "value": "Sequence:88"}, {"scope": "ftrack", "value": "a3f1-…"} ] }' scope is case-folded, because "ShotGrid" and "shotgrid" are the same system and letting both exist would quietly defeat the uniqueness below. value is kept exactly as you gave it. An identifier is unique per workspace, per object type. Two boards cannot both claim shotgrid / Sequence:88. That constraint is the whole feature — it is what turns "create the board for this sequence" from a hopeful operation into a deterministic one. Up to 20 per object. Re-runnable imports Pass "on_conflict": "identifier" when creating, and anything already carrying one of the identifiers you supplied is updated in place instead of created again. curl -X POST "$SOLEIL_API/boards" \ -H "Authorization: Bearer $SOLEIL_TOKEN" -H "Content-Type: application/json" \ -d '{"on_conflict":"identifier","boards":[ {"name":"SEQ 0100","identifiers":[{"scope":"shotgrid","value":"Sequence:88"}]}, {"name":"SEQ 0200","identifiers":[{"scope":"shotgrid","value":"Sequence:89"}]} ]}' { "boards": [ { "id": "3b7e…", "name": "SEQ 0100", "created": false }, … ], "created": 1, "updated": 1 } Each item reports whether it was created. The id of an updated object does not change, so your own record of what you made last time stays valid. The same flag works on POST /boards/:id/cards. Run your importer twice over three million assets and you get three million objects, not six. Two refusals rather than a guess 409 identifier_conflict in two cases, both of which have no correct answer: one item whose identifiers match two different existing objects — picking one would silently merge two records a card whose identifier already lives on a different board — the response names the board, so you can move it or use a different identifier. Quietly doing nothing would leave you believing the card is on your board when it is not. Looking things up curl "$SOLEIL_API/resolve?scope=shotgrid&value=Sequence:88" \ -H "Authorization: Bearer $SOLEIL_TOKEN" { "scope": "shotgrid", "value": "Sequence:88", "matches": [ { "object_type": "board", "object_id": "3b7e…", "board_id": "3b7e…", "workspace_id": "9f1c…", "url": "/api/v1/boards/3b7e…", "created_at": "2026-08-09T12:00:00Z" } ] } matches is a list because an identifier is unique per workspace, not globally — if you belong to two workspaces that both track the same upstream record, you get both and choose. Filter with type (board, card, image) and workspace. You only ever see what you could already see: this reads the same table under the same permissions as everything else, so an identifier on someone else's board simply is not there. Properties A free-form JSON object on the same objects, for the fields your pipeline actually has. curl -X PATCH "$SOLEIL_API/boards/$BOARD" \ -H "Authorization: Bearer $SOLEIL_TOKEN" -H "Content-Type: application/json" \ -d '{"props":{"scene":"14A","department":"costume","status":"approved","version":3}}' Deliberately untyped. The shape a production needs — scene, department, camera roll, clearance status — is not a shape this product can guess, and getting it wrong would be worse than not having one. Props are patched, not replaced. Send only the keys you own; the rest are left alone. A null value removes a key, which is the only way to say "remove" when leaving it out already means "don't touch": {"props": {"status": "final", "draft_note": null}} That matters because several systems usually write to the same object, and an integration that owns one field should not have to read-modify-write the whole bag and race everything else. | Keys | 100 per object Size | 16384 bytes per object, serialized Reserved | Keys beginning soleil. Types | Any JSON — strings, numbers, booleans, arrays, objects Reading them back Neither is returned by default, because most callers do not use them and every field on a list response is paid for a thousand times over. Ask: curl "$SOLEIL_API/boards/$BOARD?include=props,identifiers" -H "Authorization: Bearer $SOLEIL_TOKEN" curl "$SOLEIL_API/boards/$BOARD/cards?include=identifiers" -H "Authorization: Bearer $SOLEIL_TOKEN" A misspelled include is a 400, not a silent omission — otherwise the difference between "my props are missing" and "I typed propz" is undebuggable. Where this data lives Not in the board document. Three reasons, each sufficient on its own: the document's edit history never shrinks, so a field bag per card would grow it permanently; the card search index is rebuilt from the card on every write, so anything written there is destroyed by the next edit; and looking things up by identifier needs a real index, which a collaborative document cannot provide. The practical consequence is a good one — identifiers and properties survive edits to the card, including edits made by someone dragging it around in the app. ## Frequently asked questions Q: How do I stop a re-run of my import from duplicating everything? A: Give each board and card an identifier from your own system, and pass "on_conflict":"identifier" when you create. Anything already carrying that identifier is updated in place, and the response tells you which were created and which were updated. Q: How do I find the board for a shot I have the ID of? A: GET /resolve?scope=shotgrid&value=Shot:12345. You do not have to keep your own map of which board you made for which shot. Q: Can two boards claim the same identifier? A: No. An identifier is unique per workspace, per object type. That is what makes create-or-update deterministic rather than hopeful. Q: Are properties typed or validated? A: No. They are free-form JSON, because the fields a production needs are not fields this product can guess. Up to 100 keys and 16KB per object. Q: Do properties show up in the app? A: Not yet. They are stored, queryable and exportable today; surfacing them on the canvas is separate work. ======================================================================== # Search API URL: https://clusters.soleilpictures.com/docs/api/search Updated: 2026-08-08 GET /search finds boards and cards by text across everything your account can see. Pass q with at least two characters, optionally narrow to boards or cards with kind, scope to one workspace, and paginate with limit and offset. Results respect your permissions, so nothing you cannot open appears. The programmatic equivalent of ⌘K. GET /search Parameter | Meaning q | The query. Minimum two characters — shorter gets 400. kind | board or card. Omit for both. workspace | UUID, to scope to one workspace limit | Page size, default 100, max 500 offset | Where to start curl -s "$SOLEIL_API/search?q=diner&kind=card&limit=20" \ -H "Authorization: Bearer $SOLEIL_TOKEN" Results Boards and cards come back in separate lists, each carrying enough to act on without a second lookup — a card result includes its board_id, so you can PATCH it straight away. Everything runs under your permissions. Boards you cannot open do not appear, and there is no mode that searches beyond what your account can see. Notes for agents Two characters minimum. A one-character query is rejected rather than scanning a workspace to return everything. Narrow with kind when you know what you are after; it halves the work. Paginate. The result set is capped per page like every list endpoint — follow next_offset rather than assuming one page is everything. Search then act. The usual loop is GET /search to find a card, then PATCH /boards/:id/cards/:cardId to change it. ## Frequently asked questions Q: What does search look at? A: Board names, and card titles and text. It is the programmatic equivalent of the Cmd-K palette in the app. Q: Why does a one-character query fail? A: A single character matches most of a workspace and costs a full scan to say so. The minimum is two characters, and shorter queries get 400. Q: Can I search someone else's boards? A: Only ones shared with you. Search runs under your permissions like every other call. ======================================================================== # Export URL: https://clusters.soleilpictures.com/docs/api/export Updated: 2026-08-09 GET /boards/:id/export returns a whole board in one call. The default format is complete JSON, including the internal form of every card, so nothing is lost for kinds the API does not otherwise describe. Pass format=omc for MovieLabs OMC-JSON, which models the board as an ordered assetGroup of assets using the film industry's own controlled vocabulary for creative reference material. curl "$SOLEIL_API/boards/$BOARD/export" -H "Authorization: Bearer $SOLEIL_TOKEN" One call, one board, everything on it. format=json — complete The default. Every card exactly as stored, alongside the normal card projection, plus identifiers and properties: { "format": "soleil.board.v1", "exported_at": "2026-08-09T12:00:00Z", "board": { "id": "3b7e…", "name": "Costume — fall sequence", "props": {…}, "identifiers": […] }, "cards": [ { "id": "c1", "kind": "image", "title": "Blaster dodge", "image_key": "…", "raw": { … the card as the canvas stores it … }, "props": {…}, "identifiers": […] } ], "count": 128 } raw is there because the ordinary card read is a deliberately narrow twelve-field projection, and the app has kinds it does not describe — a grid carries its cells and template, a palette its swatches, a schedule its rows. Those read back through the projection with their interiors missing, which for anyone taking a backup is data loss that looks like success. raw is the card's internal shape. Field names in it are not part of this API's contract and can change with the app. Use it to preserve or reconstruct; do not build logic on it. The same thing is available per-request on card reads with ?include=raw. format=omc — MovieLabs OMC-JSON curl "$SOLEIL_API/boards/$BOARD/export?format=omc" -H "Authorization: Bearer $SOLEIL_TOKEN" The Ontology for Media Creation is the film industry's own standard for describing production material, published by MovieLabs. A board maps onto it directly, because OMC already has the vocabulary: { "schemaVersion": "https://movielabs.com/omc/json/schema/v2.8", "entityType": "Asset", "identifier": [ { "identifierScope": "soleil", "identifierValue": "board/3b7e…" }, { "identifierScope": "shotgrid", "identifierValue": "Sequence:88" } ], "name": "Costume — fall sequence", "AssetSC": { "structuralType": "assetGroup", "structuralProperties": { "assetGroup": { "isOrdered": true } } }, "assetFC": { "functionalType": "creativeReferenceMaterial" }, "Asset": [ { "entityType": "Asset", "identifier": [{ "identifierScope": "soleil", "identifierValue": "card/c1" }], "name": "Blaster dodge", "AssetSC": { "structuralType": "digital.image", "structuralProperties": { "linkset": { "recordType": "item", "mediaType": "image/*", "url": "https://…/api/v1/images/…" } } }, "assetFC": { "functionalType": "creativeReferenceMaterial" } } ] } Three things worth pointing out. *It is an ordered assetGroup.* A board is a composition, and the arrangement carries meaning — an unordered set would discard the one thing that separates a board from a folder. Order is reading order: rows top to bottom, then left to right within a row. Your identifiers are preserved, not replaced. MovieLabs is explicit that a system should keep the identifiers other participants assigned. Ours comes first so the document is self-referential; everything you attached follows. Image references resolve. Each asset carries a real URL, not a bare storage key, because an archival document whose references only mean something to the system that wrote it is precisely the problem this format exists to avoid. Saying what the board is Set omc.functionalType in the board's properties: {"props": {"omc.functionalType": "artwork.storyboard"}} Value | For creativeReferenceMaterial | The default — mood, tone, reference technicalReferenceMaterial | Specifications, plates, technical notes artwork | Artwork not otherwise specified artwork.conceptArt | Concept art artwork.storyboard | A storyboard artwork.animatedStoryboard | An animatic A value outside that list is refused with 400 rather than passed through. Emitting something outside the controlled vocabulary produces a document that claims to validate and does not, and the failure would surface much later, in someone else's validator. On a storyboard, cards become artwork.storyboard.frame — which is exactly how MovieLabs' own reference example models one. Properties other than omc.functionalType ride along as customData, the ontology's own escape hatch for what it does not model. What export is not It is a read, so it is charged against your rate limit like anything else, and it loads the whole board. For walking a large library, ?source=index with since is the cheaper instrument — export is for taking one board somewhere else. ## Frequently asked questions Q: How do I take a full backup of a board? A: GET /boards/:id/export. The default JSON format carries every card exactly as stored, plus its identifiers and properties. Q: What is OMC-JSON? A: The MovieLabs Ontology for Media Creation, the film industry's own standard for describing production material. A board maps onto it as an ordered assetGroup, which is how MovieLabs' own examples model a storyboard. Q: Why does the default card read lose information? A: It is a deliberate twelve-field projection, so kinds with structured interiors — grids, palettes, schedules — do not round-trip through it. Export, and include=raw on card reads, both give you the untruncated form. Q: Can I say what kind of material a board represents? A: Set omc.functionalType in the board's properties. Values outside the OMC controlled vocabulary are refused rather than passed through. ======================================================================== # MCP server URL: https://clusters.soleilpictures.com/docs/mcp Updated: 2026-08-10 Soleil Clusters ships an MCP server exposing the API as tools an AI assistant can call directly. Point a client at https://clusters.soleilpictures.com/api/v1/mcp and approve it in the browser — it signs you in over OAuth, so there is no token to paste and no account needed beforehand. Run it locally with npx only for tools that need your filesystem. Either way it holds no credentials of its own, so an agent reaches exactly what your account reaches and no more. The MCP server puts Soleil Clusters in reach of Claude and any other Model Context Protocol client, so an assistant can read and build boards directly. If you are weighing up whether that is useful rather than how to wire it up, the AI mood board maker page covers what an assistant can actually do with a board — and what it deliberately does not do, which is invent images. What it is A layer over the REST API. It holds no credentials and implements no permissions of its own — it forwards whatever credential you connected with, so everything about the authorization model applies here unchanged. An OAuth access token and a personal access token resolve to the same thing: your own session, under ordinary row-level security. Two ways to connect Hosted — nothing to install, nothing to paste { "mcpServers": { "soleil-clusters": { "type": "http", "url": "https://clusters.soleilpictures.com/api/v1/mcp" } } } A URL. That is the whole setup. The first call comes back 401 with a pointer to our OAuth metadata; the client registers itself, opens a browser, and you approve the connection on one screen. No token is ever copied, and if you do not have an account yet you get one on that screen — signing in is a single email box. Afterwards the connection is listed under Settings → API → Connected apps, where you can see what it has done and disconnect it. If your client cannot do OAuth, a personal access token still works exactly as before: "headers": { "Authorization": "Bearer undefined…" } For a team rather than a person, use a service account — a credential that does not stop working when someone leaves. Local — for files on your machine { "mcpServers": { "soleil-clusters": { "command": "npx", "args": ["-y", "soleil-clusters-mcp"], "env": { "SOLEIL_API_TOKEN": "undefined…" } } } } SOLEIL_API_BASE overrides the host. Without a token the server exits at startup rather than running in a state where every call fails. The local server has one extra tool, upload_file, which reads a file from your disk and uploads it — including large ones like video, which cannot fit through an assistant's message. Everything else is identical: both servers are built from one registry, so a tool cannot exist on one and not the other. Protocol versions Both servers accept 2026-07-28 · 2025-11-25 · 2025-06-18. 2026-07-28 is the revision that removed the initialize handshake: protocol version, client identity and client capabilities now travel in _meta on every request, and there is no session to establish or keep alive. Nothing needs to be configured to use it — the server decides per request: A request whose params._meta carries io.modelcontextprotocol/protocolVersion is served under that revision. An initialize request is served under the older, session-based rules. So a client built on the current official SDK — which tops out at 2025-11-25 — connects exactly as it always did, and a newer one gets the newer behaviour from the same URL. If you ask for a version the server does not implement, it answers -32022 and lists the ones it does, so a client can retry rather than guess. server/discover returns the supported versions, the capabilities and the server identity in a single call. On the local server it doubles as the probe that tells a client which era it is talking to, because stdio has no HTTP status code to branch on. Under 2026-07-28 the hosted transport also requires the mirrored request headers — MCP-Protocol-Version, Mcp-Method, and Mcp-Name for tools/call and prompts/get. They must agree with the request body; a mismatch is refused with -32020 rather than served, because a proxy is allowed to route on the header without reading the body, and two components acting on different values is exactly the confusion the rule exists to prevent. Older clients send no such headers and are not held to them. Two things that changed with the revision and may surprise you: ping is gone (a stateless protocol has no connection to keep alive), and a POST body must be a single message — JSON-RPC batching is no longer accepted. Both still work for clients connecting under an older version. Protocol errors These are JSON-RPC error codes, distinct from the REST API's error codes — a client branches on the number. Code | Means | HTTP -32700 | The body was not JSON | 400 -32600 | Not a valid single JSON-RPC message | 400 -32601 | No such method | 404 -32602 | Bad params — also an unknown tool or prompt | 200 -32603 | The server failed | 200 -32020 | A mirrored header disagrees with the body | 400 -32021 | The request needs a capability the client did not declare | 400 -32022 | Unsupported protocol version — the answer lists the supported ones | 400 A tool that runs and fails is not an error here. It returns a normal result with isError: true and the API's own sentence in the content, because a model that reads "this token cannot delete" can correct itself, while one that gets a transport error only learns that something broke. Choosing scopes for an agent The three scopes (delete · read · write) exist mainly for this. "Can add cards to my moodboard" and "can delete my moodboard" are different levels of trust to place in a language model, and they are separate grants. Give it | For read | Research, summarising, answering questions about your boards read + write | Building boards, adding references — the usual choice all three | Housekeeping and cleanup agents An agent without delete gets a 403 naming the missing scope rather than quietly failing. Tools Orientation and finding things Tool | Input whoami | — · the account, its scopes, rate limit, and whether it is a service account list_workspaces | — search | q (min 2 chars), kind? (board\ | card), workspace_id?, paging list_boards | workspace_id?, parent? ("root" for top level), since?, cursor?, paging board_tree | root? or workspace_id?, depth? — a whole hierarchy in one call get_board | board_id — one board, with how much of the card allowance is used resolve_identifier | scope, value — find something by an id from another system list_deleted_boards | workspace_id? list_groups | board_id — the labelled sets on a board Reading Tool | Input read_board | board_id, full?, include?, source?, since?, paging view_image | image_key — fetch an image card's actual picture list_images | workspace_id?, board_id?, since?, cursor? export_board | board_id, format? (json\ | omc) get_metadata | board_id, cards? — identifiers and properties list_audit | since?, cursor? — recent writes and image fetches Writing Tool | Input create_board | name, workspace_id?, parent_board_id?, identifiers?, props? create_boards | boards[], on_conflict? — build a structure in one call add_cards | board_id, cards[] — up to 1000, on_conflict? upload_image | board_id, data (base64), content_type upload_file | board_id, path — local server only; handles large files import_urls | board_id, urls[], titles?, dry_run? — bring reference in from the web; safe to re-run arrange_board | board_id, layout?, card_ids?, dry_run? — lay a board out create_group | board_id, name, color?, shape? — say a set of cards is one thing rename_board | board_id, name?, view?, parent_board_id? move_boards | board_ids[], parent_board_id update_card | board_id, card_id, plus any writable field update_cards | board_id, cards[] — many at once move_cards | from_board_id, to_board_id, card_ids[] set_metadata | board_id, card_id?, identifiers?, props? restore_board | board_id Deleting — requires the delete scope Tool | Input delete_card | board_id, card_id delete_cards | board_id, card_ids[] delete_board | board_id Card kind is doc, file, image, link, note, video, defaulting to note. Every tool carries annotations — readOnlyHint, destructiveHint, idempotentHint — which is what a client reads when deciding whether a call needs confirming. They are structured, so unlike a warning in a description they actually participate in that decision. Prompts Three starting points, offered by name rather than buried in a tool description: Prompt | Does describe_board | Looks at every image and writes what the board is reaching for organize_board | Proposes a grouping into child boards, without moving anything import_plan | Turns a file listing into a plan of boards and cards, with identifiers so it can be re-run Adding an image Two calls: upload_image with the base64 bytes and a content_type — returns an image_key add_cards with {"kind": "image", "image_key": "…"} Images are limited to 25 MB through the API and are charged to the board owner's storage. See Images API. What agents should know read_board truncates by default. Pass full: true for untruncated bodies, and expect it to be large. Both read_board and list_boards paginate — check for more rather than assuming one page is everything. Deleting returns the card. delete_card responds with the full card it removed. That object is the undo — pass it back to add_cards to restore it. Worth keeping in context before destructive work. Deleting a board is destructive and confirmable. delete_board removes a whole board; the tool description says to confirm with the user first, and restore_board exists because that is not always heeded. Writes are idempotent by key. Every POST the server issues carries a generated idempotency key, so a retried call replays rather than duplicating. Batch. One add_cards call with forty cards is one request against the rate limit; forty calls are forty. live: false is not an error. The change is saved; a canvas someone already has open will not show it until reload. Start with whoami. It reports the scopes and remaining rate budget, which is cheaper than discovering both through failures. Reading the docs as an agent /llms.txt — an index of every page with descriptions /llms-full.txt — the entire corpus in one file OpenAPI — the machine-readable API spec Any page plus .md — for example /docs/api/cards.md ## Frequently asked questions Q: Do I have to install anything? A: No. Point an MCP client at https://clusters.soleilpictures.com/api/v1/mcp. It discovers the sign-in flow on its own and opens a browser; you approve once. Running it locally is only needed for uploading files from your own machine. Q: Do I need an account before connecting? A: No. Sign-in is a single email box that creates the account if there isn't one, so you can go from Connect to a working assistant without visiting the site first. Q: Do I still need a personal access token? A: Only for a client that cannot do OAuth, or for your own scripts. Assistants that speak current MCP handle it themselves. Q: Does the MCP server have its own permissions? A: No. It forwards whatever credential you connected with — an OAuth access token or a personal access token — to the same API, so it inherits your account's permissions exactly. A credential without the delete scope cannot delete. Q: How do I stop an agent deleting things? A: Deleting is a separate grant from writing precisely so an agent can be allowed to build without being allowed to destroy, and it is never granted by default. Over OAuth, simply do not approve it; with a token, mint one without the delete scope. Q: Can an agent upload a video? A: Only the local server can, with upload_file, because the file has to be read from a disk. The hosted one handles images with upload_image. Q: Is it the same set of tools either way? A: Yes, apart from upload_file, which needs a filesystem. Both servers are built from one registry so they cannot drift apart. ======================================================================== # Errors and status codes URL: https://clusters.soleilpictures.com/docs/api/errors Updated: 2026-08-08 Every error is JSON carrying a machine-readable code and a human sentence. Branch on the code, not the prose. Only 429 and 5xx are worth retrying; everything in the 400 range is a permanent statement about the request. A retried POST must reuse the same Idempotency-Key or it becomes a second real write. Every error looks like this: { "error": "at most 100 cards per call", "code": "bad_request" } Branch on code. The sentence is written for a person reading a log and may be reworded; the code is the contract. Codes Status | code | Means | Retry? 400 | bad_request | Malformed — bad UUID, missing field, unknown card kind, too many cards | No 401 | — | Missing, malformed, unknown, revoked or expired token | No 402 | limit_reached | A quota — card cap or storage | No 403 | forbidden / insufficient_scope | Not permitted, or the token lacks the scope | No 404 | not_found | Not found, or not visible to you | No 405 | method_not_allowed | Wrong method for that path | No 409 | conflict | Idempotency key in flight, or a refused reparent | Sometimes 409 | identifier_conflict | An identifier already belongs to something else | No 413 | payload_too_large | An image over 25 MB | No 415 | unsupported_media_type | Upload with a missing or unrecognised Content-Type | No 429 | — | Rate limited | Yes, after retry-after 502 | session_unavailable | An upstream dependency is unreachable | Yes 502 | upstream_error | A dependency answered, but not successfully | Yes 503 | storage_unavailable | Image storage is temporarily unavailable | Yes The ones worth explaining 401 — unknown, revoked and expired tokens are indistinguishable on purpose, so probing cannot reveal which tokens existed. 403 insufficient_scope — the response names the scope required in required_scope. Deleting needs the delete scope, which is separate from write precisely so "can add cards to my moodboard" and "can destroy my moodboard" are different grants. 402 versus 403 — 402 is a quota you could pay to lift; 403 is a permission you cannot. 404 — returned both for things that do not exist and for things you cannot see. Do not retry it and do not treat it as transient. 409 — two causes. An Idempotency-Key whose first attempt is still in flight, in which case waiting briefly and retrying with the same key is correct; or a reparent that would create a cycle, which is permanent. 429 — respect the retry-after header, in seconds. Every response carries x-ratelimit-remaining and x-ratelimit-reset, so a well-behaved client never has to hit the wall to discover the wall. Retrying safely async function withRetry(fn: () => Promise, tries = 4): Promise { for (let i = 0; ; i++) { const res = await fn(); if (res.ok) return res.json() as Promise; const body = await res.json().catch(() => ({} as any)); const retryable = res.status === 429 || res.status >= 500; if (!retryable || i >= tries - 1) { throw new Error(`${res.status} ${body.code ?? ""} ${body.error ?? ""}`); } // Honour the server's own answer before guessing. const after = Number(res.headers.get("retry-after")); await new Promise((r) => setTimeout(r, Number.isFinite(after) && after > 0 ? after * 1000 : 2 ** i * 1000)); } } A retried POST must carry the same Idempotency-Key as the original. A fresh key on every attempt turns one intended write into several real ones. Idempotency and errors A key is stored with its response when the request finishes with any status below 500, so a retry replays that response — including a 4xx. A request that failed validation keeps reporting the same failure under that key, which is correct: the request was bad and still is. On a 5xx the key is released, so the retry genuinely re-runs. A replayed response carries idempotent-replay: true. Successes that are not quite successes Two responses mean less than they look: "live": false on a card write — saved, but open canvases will not show it until reload. Not a failure. has_more: true on a list — you have one page, not the answer. Follow next_offset. ## Frequently asked questions Q: Should I branch on the code or the message? A: The code. The human sentence is written for a person reading a log and may be reworded; the code is the contract. Q: Why do I get 404 for a board I know exists? A: Because it is not yours. The API returns 404 rather than 403 for invisible resources so it cannot be used to confirm what exists. Q: What is the difference between 402 and 403? A: 402 means a quota — the card cap or storage. 403 means permission — an insufficient scope, or a board you cannot write to. ======================================================================== # Webhooks URL: https://clusters.soleilpictures.com/docs/api/webhooks Updated: 2026-08-09 Register an HTTPS endpoint and Soleil Clusters posts to it when boards and cards change — including changes made by people working in the app, not only changes made through the API. Every delivery is signed with HMAC-SHA256 over the timestamp and body, retried with exponential backoff for over twelve hours, and recorded in a delivery log you can inspect and replay. Register an HTTPS endpoint and we post to it when something changes. Including changes made in the app. That is the part worth stating plainly: events come from the database, not from the API request handler, so a card someone drags on the canvas fires the same event as one added by your importer. A webhook that only saw API traffic would miss almost everything that actually happens to a board. Register one curl -X POST "$SOLEIL_API/webhooks" \ -H "Authorization: Bearer $SOLEIL_TOKEN" -H "Content-Type: application/json" \ -d '{"workspace_id":"'$WS'","url":"https://hooks.example.com/soleil", "events":["card.created","card.updated","board.created"], "name":"pipeline sync"}' { "webhook": { "id": "a2c4…", "url": "https://hooks.example.com/soleil", "events": ["card.created","card.updated","board.created"], "active": true }, "secret": "whsec_…", "next": "Verify each delivery: HMAC-SHA256 of \"v0:{timestamp}:{body}\", compared with the X-Soleil-Signature header." } secret is returned once and never again. There is no read path for it, over the API or in the database. The URL must be public HTTPS on the default port. We fetch it from our own address on a schedule, which is the shape of a server-side request forgery, so private ranges, loopback, metadata hosts and non-default ports are refused. Pass "events": ["*"] to subscribe to everything, including event types added later. An unknown event name is a 400 rather than a subscription that never fires — "subscribed and silent" is the hardest webhook failure to diagnose. Events Event | When board.created | A board is created board.updated | Its name, view or parent changes board.deleted | Soft-deleted, or removed outright board.restored | Brought back from the trash card.created | Cards are added to a board card.updated | Their text or contents change card.deleted | Cards are removed card.moved | Cards move to another board image.created | An upload completes Card events are grouped. A single operation on a board produces one event carrying a count, not one per card — so importing a thousand cards is one delivery, not a thousand. data.card_ids carries the first 25 as a courtesy; past that, read the board. The payload { "type": "card.created", "resource": { "type": "card", "id": "…" }, "workspace": { "id": "3b7e…" }, "board": { "id": "9f1c…" }, "data": { "count": 412, "card_ids": ["…", "…"] }, "occurred_at": "2026-08-09T12:00:00.412Z" } Deliberately thin. A payload is a copy of the truth, and a copy goes stale between being sent and being read — worse, it goes stale silently. You get enough to know what to look at; call back for current state, with ?since= if you want everything that moved. Verifying a delivery Header | X-Soleil-Signature | v0= X-Soleil-Request-Timestamp | Unix seconds X-Soleil-Event | The event type X-Soleil-Delivery | This delivery's id — useful in your own logs Compute HMAC-SHA256(secret, "v0:" + timestamp + ":" + rawBody) and compare it with the signature. Use the raw body, before any JSON parsing — re-serializing changes the bytes and the signature will not match. import hmac, hashlib, time def verify(secret, headers, raw_body): ts = headers["X-Soleil-Request-Timestamp"] if abs(time.time() - int(ts)) > 300: # five-minute replay window return False expected = "v0=" + hmac.new( secret.encode(), f"v0:{ts}:".encode() + raw_body, hashlib.sha256 ).hexdigest() return hmac.compare_digest(expected, headers["X-Soleil-Signature"]) This is the same scheme Frame.io uses, on purpose — if you have integrated with them, the verifier you already wrote works here. Reject anything outside the five-minute window, and compare in constant time. Retries Six attempts: after 1 minute, 5 minutes, 25 minutes, ~2 hours and ~10 hours. That is over twelve hours in total, so an endpoint that is down overnight still receives its events. Any non-2xx, or a connection failure, counts as a failure. Respond 2xx as soon as you have durably accepted the delivery and do your work afterwards — a slow receiver is a retried receiver. We give up on a single delivery after ten seconds. After 20 consecutive failures across all deliveries a webhook is switched off, with disabled_reason saying why. A single success resets the counter, so this only ever fires for a genuinely dead endpoint. Re-enable with PATCH /webhooks/:id {"active": true}, which also clears the failure state. The delivery log curl "$SOLEIL_API/webhooks/$HOOK/deliveries?limit=50" -H "Authorization: Bearer $SOLEIL_TOKEN" Every attempt, with its status, duration, error and the exact payload sent. "We sent it" without a record is not an answer, so there is a record. # Send a real delivery through the real path, to check signing end to end curl -X POST "$SOLEIL_API/webhooks/$HOOK/test" -H "Authorization: Bearer $SOLEIL_TOKEN" # Replay one you missed curl -X POST "$SOLEIL_API/webhooks/$HOOK/deliveries/$DELIVERY/redeliver" \ -H "Authorization: Bearer $SOLEIL_TOKEN" POST /webhooks/:id/test sends through the delivery path everything else uses, so what it proves is what will actually happen. POST /webhooks/:id/deliveries/:deliveryId/redeliver requeues rather than sending inline, so a replay is retried and recorded exactly like any other delivery. Deliveries are kept for 30 days. Managing them curl "$SOLEIL_API/webhooks?workspace=$WS" -H "Authorization: Bearer $SOLEIL_TOKEN" curl -X PATCH "$SOLEIL_API/webhooks/$HOOK" -H "Authorization: Bearer $SOLEIL_TOKEN" \ -H "Content-Type: application/json" -d '{"events":["*"],"active":true}' curl -X DELETE "$SOLEIL_API/webhooks/$HOOK" -H "Authorization: Bearer $SOLEIL_TOKEN" Up to 20 active webhooks per workspace. Listing never returns the secret. Timing An event caused by an API call is delivered immediately. An event caused by someone working in the app is picked up within a minute. Both go through the same queue and the same retry policy; only the pickup differs. ## Frequently asked questions Q: Do webhooks fire for changes made in the app? A: Yes. That is the main point. Events come from the database itself, so a card someone drags on the canvas fires the same event as one added through the API. Q: How do I verify a delivery is really from you? A: HMAC-SHA256 of "v0:{timestamp}:{body}" using the secret from the create response, compared with the X-Soleil-Signature header. Reject anything with a timestamp more than five minutes old. Q: Does a thousand-card import send a thousand webhooks? A: No. Card events are grouped per board per operation, so that batch is one delivery carrying a count. Q: What if my endpoint is down? A: Six attempts over more than twelve hours. Every attempt is recorded, and you can replay any delivery from the log once you are back. Q: Why is there so little in the payload? A: A payload is a copy, and a copy goes stale. You get the type and the ids; call back for current state. ======================================================================== # Service accounts URL: https://clusters.soleilpictures.com/docs/api/service-accounts Updated: 2026-08-09 A personal access token belongs to one person, so an integration built on it stops the day that person leaves the workspace. A service account is a credential owned by the workspace itself. It is a real member of exactly one workspace, subject to the same permissions as anyone else, and its tokens keep working regardless of who comes and goes. Only the workspace owner can create one, and a token can never grant more than the token that created it. A personal access token is you. Everything it can do, it does as your account, under your permissions. That is the right design for a script you wrote for yourself, and the wrong one for an integration a team depends on — because the day you leave the workspace, or your account changes, every pipeline built on that token stops, with a permission error a long way from its cause. A service account is a credential owned by the workspace instead. It is a real member of one workspace. It is subject to exactly the same permissions as a person — there is no bypass and no elevated mode — and its access ends at the edge of that workspace. What it does not have is a dependency on any particular human still being around. Create one curl -X POST "$SOLEIL_API/service-accounts" \ -H "Authorization: Bearer $SOLEIL_TOKEN" -H "Content-Type: application/json" \ -d '{"workspace_id":"'$WS'","name":"Pipeline sync","scopes":["read","write"]}' { "service_account": { "id": "9f1c…", "name": "Pipeline sync", "workspace_id": "3b7e…", "created_at": "2026-08-09T12:00:00Z", "disabled": false }, "token": { "id": "a2c4…", "token": "undefined…", "prefix": "undefineda1b2", "scopes": ["read", "write"], "rate_limit": 10000 }, "next": "Use this token as the Bearer credential. It is shown once." } The account and its first token are created together, because an account with no credential cannot do anything and you would have to ask for one immediately anyway. token is shown once. It is not stored anywhere it can be read back. If it is lost, mint another and revoke the old one. Only the workspace owner can create a service account. An editor cannot — a credential that keeps working after its creator loses access would be a way around losing it. Scopes, and the ceiling on them Same three scopes as a personal token: read, write, delete. delete implies write, and every token can read. A service token can never grant more than the token that created it. Asking for delete from a write token is refused with 403 insufficient_scope rather than quietly granted — otherwise the weaker credential would be a way to manufacture the stronger one. Rate limit A service token's default is 10000 requests/hour, against 1000 for a personal one. The two numbers exist for different reasons: a personal limit bounds one person's scripting mistake, and a machine identity is the case a rate limit exists to permit. Pass rate_limit to set your own. Every response carries X-RateLimit-Remaining and X-RateLimit-Reset, so there is no need to discover the ceiling by hitting it. Managing them # What exists curl "$SOLEIL_API/service-accounts?workspace=$WS" -H "Authorization: Bearer $SOLEIL_TOKEN" # Another token for the same account — mint the new one BEFORE revoking the old, # and the integration never has a moment without a working credential curl -X POST "$SOLEIL_API/service-accounts/$SA/tokens" \ -H "Authorization: Bearer $SOLEIL_TOKEN" -H "Content-Type: application/json" \ -d '{"name":"rotated 2026-08","scopes":["read","write"],"ttl_days":90}' # Its tokens, with when each was last used curl "$SOLEIL_API/service-accounts/$SA/tokens" -H "Authorization: Bearer $SOLEIL_TOKEN" # Retire one token curl -X DELETE "$SOLEIL_API/service-accounts/$SA/tokens/$TOKEN_ID" \ -H "Authorization: Bearer $SOLEIL_TOKEN" # Retire the whole account — revokes every token and ends its membership curl -X DELETE "$SOLEIL_API/service-accounts/$SA" -H "Authorization: Bearer $SOLEIL_TOKEN" Deleting a service account is immediate: every token is revoked and the membership is removed, so the credential is dead by the time the call returns. The record itself is kept, so past entries in the audit log still resolve to a name rather than to a deleted id. Limits | Per workspace | 10 active service accounts Per account | 20 active tokens Reach | The one workspace it belongs to Requests | 10000/hour by default, per token What it deliberately cannot do A service account cannot create or manage service accounts, including itself. If it could, a single leaked credential could clone itself indefinitely and revoking the original would achieve nothing. Account management always needs the owner's own token. It also cannot be made an owner, and it cannot reach a second workspace. If an integration spans two workspaces it needs two service accounts — which is the honest representation of what it is doing. Which credential to use | Personal token | Service account Belongs to | You | The workspace Survives you leaving | No | Yes Reach | Everything you can see | One workspace Created by | Anyone, in Settings → API | The workspace owner, over the API Good for | Your own scripts, trying things out, MCP on your machine | Anything a team depends on A useful rule: if losing access to it would interrupt someone other than you, it should be a service account. ## Frequently asked questions Q: Why not just use a personal access token? A: It works, until the person who minted it leaves the workspace or their account changes. Then every integration built on it stops with a permission error far from the cause. A service account has no such dependency. Q: What can a service account see? A: Exactly one workspace, and only what a member of that workspace can see. It cannot reach boards shared to the person who created it from somewhere else. Q: Who is allowed to create one? A: The workspace owner. An editor cannot, because a credential that outlives its creator's own access would be a way around losing it. Q: How do I rotate a token without downtime? A: Mint the new one first, move the integration onto it, then revoke the old one. A service account can hold several tokens at once. Q: Does a service account count against my plan? A: No. Storage and card limits are charged to the workspace owner no matter who does the writing, which is the same rule the app applies to collaborators. ======================================================================== # Audit log URL: https://clusters.soleilpictures.com/docs/api/audit Updated: 2026-08-10 GET /audit returns a record of every write made through the API and every fetch of image bytes, newest first, cursor-paged. You see your own activity, and if you own a workspace you also see everything its service accounts did. Entries carry the actor, the token used, the method and templated route, the MCP tool where one was called, the object touched, the status and the duration. curl "$SOLEIL_API/audit?limit=100" -H "Authorization: Bearer $SOLEIL_TOKEN" { "entries": [ { "id": "80421", "at": "2026-08-09T12:00:04.120Z", "actor": "Pipeline sync", "actor_id": "9f1c…", "token_id": "a2c4…", "token_name": "Pipeline sync token", "method": "POST", "route": "/boards/:id/cards", "tool": null, "target_id": "3b7e…", "status": 201, "ms": 214 }, { "id": "80420", "at": "2026-08-09T11:59:58.004Z", "actor": "Ana", "actor_id": "1d0b…", "token_id": "77fe…", "token_name": "Claude Desktop", "method": "POST", "route": "/mcp", "tool": "add_cards", "target_id": null, "status": 200, "ms": 331 } ], "limit": 100, "has_more": true, "next_cursor": "80421", "covers": "writes made through /api/v1 and reads of image bytes" } Newest first. Pass next_cursor back as cursor, and since to bound it by time. What is in it | Writes | Every POST, PATCH and DELETE through /api/v1 Image reads | Every fetch of GET /images/:key Not included | Ordinary reads, and anything done in the app Retention | 30 days Ordinary reads are left out because they are the bulk of API traffic and are mostly noise. Image bytes are the exception, and deliberately so: that request is content leaving, which is the thing a security review actually asks about. route is the templated path — /boards/:id/cards, not the specific board — with the object in target_id. That way the log groups by operation and you can still see what each one touched. MCP calls The whole MCP server is one route, POST /mcp, so the route alone would tell you nothing about what an assistant did. tool carries the name of the tool that ran — add_cards, arrange_board, import_urls — or the JSON-RPC method for calls that are not tool runs, such as tools/list. It is null for REST calls, which have a route instead, and null on MCP entries recorded before the field existed. That is deliberate: those rows genuinely do not know, and a guessed value would be worse than an empty one. Whose activity Your own, plus every service account belonging to a workspace you own. actor is the service account's name, or the person's display name. That pairing is the point. A service account is a credential a team depends on, and being unable to see what it did would make it exactly the kind of anonymous shared secret it exists to replace. Disabling a service account does not remove it from the log: the record is kept so past entries still resolve to a name rather than a deleted id. What this is not It is not a complete history of a board. Changes made on the canvas do not appear here, because this records API traffic, not edits. If you need to know that a board changed — whoever changed it — use GET /boards?since=, or subscribe to a webhook, both of which see app activity too. ## Frequently asked questions Q: Does this cover changes made in the app? A: No. It records writes through /api/v1 and reads of image bytes. Edits someone makes on the canvas are not in it. Q: Can I tell which MCP tool an assistant used? A: Yes. MCP entries carry a tool field with the tool name, or the JSON-RPC method for calls that are not tool runs. It is null for REST calls, and null for MCP entries recorded before the field existed. Q: Whose activity can I see? A: Your own, plus every service account belonging to a workspace you own. Q: Why are ordinary reads not recorded? A: They are the bulk of API traffic and mostly noise. Fetching image bytes is the exception, because that is content leaving. Q: How long is it kept? A: 30 days.