# Welcome to sanda > sanda brings your business systems into a warehouse of your own, gives the data a shared business language, and puts it to work in answers, search and reports. sanda is a managed data platform for Australian businesses. You connect the systems your business already runs, such as your accounting package, your shop, your CRM or a database. sanda lands that data in a warehouse that belongs to your workspace, describes it in the terms your team uses, and then lets you ask questions of it, search it, hand it to an AI assistant and report on it. You do not need to write SQL to use it. cherry, sanda's AI data agent, writes and runs the queries for you and shows you the query behind every answer. If you or your technical colleague prefer SQL, the warehouse is a standard PostgreSQL database and you can work in it directly. A trial runs for 7 days, includes the whole product, and comes with A$10 of free credit. It needs no card. ## How the layers fit together Data moves down the page, from your systems to the ways you use it. ```text Your systems and CSV files | | managed sync, on a schedule v sanda warehouse (your own PostgreSQL database, in Sydney) raw | derived | mapping | | what you put on the map is published to mapping v Semantic fluid (tables, relationships, metrics, dimensions, filters) | v cherry | sanda search | MCP assistants | Reports | Excel and Power BI ``` | Layer | What it does | Read more | |---|---|---| | **Connections** | Bring data from your systems, or from a CSV file, into the warehouse on a schedule you choose. | [Connections](https://docs.sanda-os.com.au/connections) | | **sanda warehouse** | Holds everything that lands. Each workspace gets its own dedicated PostgreSQL database, held in Sydney. | [Warehouse](https://docs.sanda-os.com.au/warehouse) | | **Semantic fluid** | The shared business language on top of the warehouse. You put tables on a map, join them, and define what a number such as `net revenue` means once. | [Semantic fluid](https://docs.sanda-os.com.au/semantic-fluid) | | **cherry** | The AI data agent in the console. It answers questions, builds views and reports, and walks a new workspace through setup. | [cherry](https://docs.sanda-os.com.au/cherry) | | **sanda search** | Finds records by meaning as well as by words, across the text in your tables. | [Search](https://docs.sanda-os.com.au/search) | | **MCP** | Lets AI assistants you already use, such as Claude, read your semantic fluid through an open standard. | [MCP](https://docs.sanda-os.com.au/mcp) | | **Reports** | Canvases of charts, figures, tables and written analysis, emailed to your team or published to a portal for your readers. | [Reports](https://docs.sanda-os.com.au/reports) | sanda search is included from the Standard edition, and on every trial. The rest of the product, MCP included, is in every edition. See [editions](https://docs.sanda-os.com.au/billing/editions) for the detail. Because cherry, search, MCP and reports all read through the semantic fluid, they agree with each other. A `net revenue` figure in a report is the same figure cherry quotes and the same figure an assistant returns. ## Who sanda is for sanda is for businesses that keep their numbers in several systems and want one place to ask questions of them. The people who use it day to day are usually a finance lead, an operations manager, or the one technical person on the team. Your data is held in Sydney, and the price is a flat monthly fee for the workspace plus what you use. Nothing is priced per seat. [How sanda works](https://docs.sanda-os.com.au/get-started/how-becca-works) explains where data lives and what is metered. ## How these docs are organised The tabs across the top of the site each answer a different question. | Tab | Use it to | |---|---| | **Guides** | Do a task or understand a concept, one product area at a time: warehouse, connections, modelling, semantic fluid, search, cherry, reports, workspace and billing. | | **Connectors** | Look up a source system and how to connect it. | | **Developers** | Connect AI assistants over MCP, or open the semantic fluid in Excel and Power BI over OData. | | **Reference** | Look up an exact fact: the glossary, limits, permission scopes, sync modes, filters, sheet formulas, SQL and keyboard shortcuts. | | **Release notes** | See what has changed. | ## Where to start :::links - [Quickstart](https://docs.sanda-os.com.au/get-started/quickstart): From sign-up to your first answer and your first report. - [How sanda works](https://docs.sanda-os.com.au/get-started/how-becca-works): Where your data lives, how it flows, and what is metered. - [Core concepts](https://docs.sanda-os.com.au/get-started/concepts): A short definition of every term you will meet, with a link to where it is covered. - [A tour of the console](https://docs.sanda-os.com.au/get-started/console-tour): Every area of the left navigation, and the cherry pane. - [Set up with cherry](https://docs.sanda-os.com.au/get-started/setup-with-cherry): The setup checklist cherry draws, and when each step counts as done. - [Your first week](https://docs.sanda-os.com.au/get-started/first-week): A practical plan for a trial. - [Getting help](https://docs.sanda-os.com.au/help): Support, troubleshooting and frequently asked questions. ::: --- # Quickstart > Go from sign-up to your first answer and your first report, in the order sanda needs the steps. Setup runs in the order sanda needs it: a warehouse first, then data, then meaning, then answers. It is the same ladder cherry draws as a checklist in the console, so you can follow either one. See [set up with cherry](https://docs.sanda-os.com.au/get-started/setup-with-cherry) for the checklist itself. You need a work email address and one thing to load: a system you can connect, or a CSV file. A trial lasts 7 days, includes every feature, and comes with A$10 of free credit. It needs no card. :::steps 1. **Sign up.** Go to [sanda-os.com.au/signup](https://sanda-os.com.au/signup) and fill in **Your name**, **Work email** and **Company**. **Your role** is optional. Under **Data region**, choose where your workspace's data is held. Tick the box to accept the terms of service and the privacy policy, then press **Start free trial**. Your company name becomes your workspace name, and you become its owner. An email address belongs to one workspace, so use the address you want to sign in with. The workspace stays in the [data region](https://docs.sanda-os.com.au/reference/glossary#data-region) you choose: its warehouse is created there, and the region cannot be changed later. Workspaces can be created in Australia (Sydney). 2. **Sign in from the email.** sanda emails you a link titled "Your sanda sign-in link". Open it and press **Continue to sanda**. There is no password. The link works once and expires after 30 minutes, and if it does not arrive, [the troubleshooting page](https://docs.sanda-os.com.au/help/troubleshooting) says what to check. You land on the **Overview** page, with cherry's message box in the middle. 3. **Provision your warehouse.** Go to **Data · Warehouse** and press **Provision warehouse**. sanda creates a dedicated PostgreSQL database for your workspace, which takes under a minute. Every connection and CSV lands in it, so nothing else can finish until it is ready. Provisioning needs an owner or admin. See [provision a warehouse](https://docs.sanda-os.com.au/warehouse/provision). 4. **Bring your data in.** Go to **Data · Connections**. You have two routes, and either one completes this step. - **Connect a system.** Press **New connection**, choose the source, enter its credentials, choose the streams to sync, then press **Sync now** on the connection's page, or wait for its schedule. See [add a connection](https://docs.sanda-os.com.au/connections/add-a-connection). - **Load a CSV.** Press **Load a CSV**, choose a file with a header row, name the table, and press **Load it**. The rows land straight in the warehouse. See [upload a CSV](https://docs.sanda-os.com.au/connections/upload-a-csv). The step counts as done once rows have landed, not when the connection is saved. Connecting a system needs an owner or admin. Loading a CSV is open to anyone who is not read-only. 5. **Learn from my data.** Go to **Intelligence · Semantic fluid** and press **Ask cherry to learn**. cherry reads your tables and proposes the tables, relationships, metrics and dimensions they hold. It then tells you what stood out and what it could not settle. This is the first step that uses AI, so it draws on your daily AI limit and, on a trial, your free credit. See [learn from my data](https://docs.sanda-os.com.au/semantic-fluid/learn). 6. **Review and confirm.** cherry opens the suggestions on the map, headed "N suggestions waiting on you". Nothing reaches an answer until you accept it. Click a row to correct it first, or press **accept all** and confirm with **yes, accept all**. You can also ask cherry to go through them with you. In its default mode it proposes each change as a card, and **Confirm all** accepts them together. Afterwards, press **tidy the map** to lay the tables out by how they join. See [review suggestions](https://docs.sanda-os.com.au/semantic-fluid/review). 7. **Ask cherry a question.** Open cherry with ⌘ J (Ctrl J on Windows) and ask something your data can answer, such as "What changed in revenue this month?" cherry shows each step it takes and the query behind the answer. See [ask cherry](https://docs.sanda-os.com.au/cherry/ask). 8. **Build a first report.** Go to **Activate · Reports** and press **Build one with cherry**. cherry asks what to call the report and what it is for. Answer with a name, such as "Monthly trading", and a purpose, such as "what the owners read each month: sales, margin and the best sellers". cherry lays out the whole report (figures, trends, breakdowns and a written analysis) and opens it. See [build a report with cherry](https://docs.sanda-os.com.au/reports/build-with-cherry). ::: ## What to check - The top bar shows **Trial** with your credit left and the days remaining. Press it to open **Settings · Plan & budget**, which shows what the credit has gone on. - Your connection's page shows **Rows synced** above zero, or your CSV table is in the warehouse. - **Intelligence · Semantic fluid** shows tables on the map, and no suggestions waiting. - **Activate · Reports** lists your report. :::tip Look around first If the Warehouse page offers the sanda learning dataset, you can add it before your own data lands. It is free and read-only, so you can ask cherry questions straight away. ::: ## Next steps :::links - [Set up with cherry](https://docs.sanda-os.com.au/get-started/setup-with-cherry): The checklist, and when each step counts as done. - [Your first week](https://docs.sanda-os.com.au/get-started/first-week): What to set up and try across a trial. - [Schedules](https://docs.sanda-os.com.au/connections/schedules): Keep your data fresh without pressing anything. - [Report delivery](https://docs.sanda-os.com.au/reports/delivery): Email a report to your team on a schedule. - [Invite your team](https://docs.sanda-os.com.au/workspace/members-and-roles): Add people, and choose what each one can do. ::: --- # How sanda works > What each layer of sanda does, where your data lives, how it flows from a source to an answer, what is computed when, and what is metered. sanda is a chain of layers, and each one has a single job. Your data passes through them in order, from a source system to an answer. Where it is held, when work happens and what you are charged for follow from that path. ## The path from source to answer ```text Your systems and CSV files | | managed sync, on a schedule v sanda warehouse (your own PostgreSQL database, in Sydney) raw | derived | mapping | | what you put on the map is published to mapping v Semantic fluid (tables, relationships, metrics, dimensions, filters) | v cherry | sanda search | MCP assistants | Reports | Excel and Power BI ``` 1. **Connections** read your source systems on a schedule and land the rows in your warehouse. A CSV upload skips the schedule and lands straight in. 2. **The warehouse** stores them. It is a dedicated PostgreSQL database that belongs to your workspace and to no other. Data arrives in a schema called `raw`. 3. **The [semantic fluid](https://docs.sanda-os.com.au/reference/glossary#semantic-fluid)** is the meaning layer. You put tables on the map, join them, and define what `net revenue` or `active customer` means once. Each table you put on the map is published as a view in the `mapping` schema, so what can be read is what you chose to include. 4. **Everything that answers questions** works from the fluid: cherry, sanda search, assistants connected over [MCP](https://docs.sanda-os.com.au/reference/glossary#mcp), reports, and the Excel and Power BI feed. They agree with one another because they share one set of definitions. ### The schemas in your warehouse These are the main schemas. You can browse them in the console's **Explorer**. | Schema | What is in it | |---|---| | `raw` | Data as it landed from your sources and CSV files. | | `derived` | Views, materialized views and procedures you build in **Modelling**. | | `mapping` | What the semantic fluid publishes for reading: one view per table on the map. | | `search` | The indexes behind sanda search, if you use it. | Questions from cherry, from connected assistants, from reports and from the Excel and Power BI feed run through a read-only role that can read `mapping` and cannot read `raw`. Owners and admins can also run their own SQL in the console's SQL shell, and build views in Modelling. Those run with wider access, and the SQL shell records every statement. ## Where your data lives | What | Where it is held | |---|---| | The data you sync or upload | Your workspace's own warehouse, in the [data region](https://docs.sanda-os.com.au/reference/glossary#data-region) chosen when the workspace was created. Workspaces can be created in Australia (Sydney). | | Search indexes | The same warehouse, beside the rows they came from. | | Definitions, reports, schedules, settings and chat history | sanda's own database, in Sydney, Australia. | | Credentials you enter for a source | The managed sync service. They are not kept in sanda's own database. | Each workspace has its own database and its own credentials, rather than sharing tables with other workspaces. sanda checks that this separation is in force before it serves any workspace data. Some work happens outside Australia, and sanda says so plainly in the [privacy policy](https://sanda-os.com.au/privacy): - **AI features.** When cherry answers a question or drafts a definition, the part of your data it needs is sent to an AI model. That can include table and column names, your definitions, small samples of values, and the rows a query returns. Those models run outside Australia, mostly in the United States. Models are called on a zero-data-retention basis, and your data is not used to train anything. - **sanda search.** It is off until a workspace owner switches it on. Once on, the text of the columns you choose is sent to an embedding model in the United States to build the index. - **Web traffic.** Requests to the site and the console travel over a global network, so they may be handled at a location outside Australia. ## What is computed when This is when work happens, and so when the meters move. | When | What happens | |---|---| | **A sync runs**, on its schedule or when you press **Sync now** | The connection reads its source and lands rows in `raw`. Schedules run on a 15 minute grid. | | **You press Learn from my data** | cherry reads your tables and proposes what they mean. This uses AI. | | **You ask a question, open a report, or an assistant calls a tool** | The fluid composes a query from your definitions and runs it on the warehouse's compute. This is the moment compute wakes up. | | **A report is delivered** | Every block re-runs at the moment of sending, so the email carries current numbers. | | **A search index builds** | After the connection that feeds it lands rows, or on its own schedule. Indexing uses AI. | | **A task runs** | The views and procedures you built in Modelling refresh, on a schedule, after a connection lands, or by hand. | A report stores the question each block asks, not the rows. The canvas asks again when you open it, which is why a report, a cherry answer and an emailed pack show the same number. ## What is metered sanda charges a flat monthly platform fee for the workspace, plus what you use. The console shows usage as it accrues, so an invoice is never the first time you see a number. | Meter | What it counts | |---|---| | **Storage** | The gigabytes your warehouses hold, averaged across the month. | | **Compute** | The hours your warehouse is working. It scales to zero when idle, and a question wakes it for a minute, so five questions inside that minute count as one. | | **AI** | Every AI call sanda makes for your workspace, priced by the token: your questions to cherry, and sanda's own work on your data, such as learning and search indexing. A daily limit caps it. | | **Sync runs** | Each successful run of a managed sync. | Nothing is priced per seat, report, schedule or connector. A trial draws its A$10 of free credit down on the same four meters and bills nothing. Your edition sets the platform fee, how many warehouses you can hold, and how large each can grow. See [editions](https://docs.sanda-os.com.au/billing/editions), [usage](https://docs.sanda-os.com.au/billing/usage) and [budgets and limits](https://docs.sanda-os.com.au/billing/budgets-and-limits). ## Go further :::links - [Core concepts](https://docs.sanda-os.com.au/get-started/concepts): The terms above, each defined and linked. - [The semantic fluid](https://docs.sanda-os.com.au/semantic-fluid): How the map, metrics and relationships work. - [Warehouse](https://docs.sanda-os.com.au/warehouse): Provisioning, exploring, SQL and export. - [Workspace privacy](https://docs.sanda-os.com.au/workspace/privacy): What is held, who can see it, and how to have it deleted. ::: --- # Core concepts > The ideas behind sanda in plain terms, from workspace and warehouse to the semantic fluid, cherry, reports and MCP, each linked to its page. Each entry below is a short definition and a pointer to where the topic is covered in full. For a strictly alphabetical list of every term, see the [glossary](https://docs.sanda-os.com.au/reference/glossary). ## Your workspace ### Workspace A workspace is your business's home in sanda. It holds your warehouse, connections, semantic fluid, reports, cherry conversations, settings and team, and it is what you are billed for. It is named after your company when you sign up, and it has a short name that you type to confirm closing it. An email address belongs to one workspace. See [workspace](https://docs.sanda-os.com.au/workspace). ### Member and role A member is a person with a seat in your workspace. Seats are not priced. A member's role decides what they can do: | Role | What it can do | |---|---| | **owner** | Everything, including closing or deleting the workspace and handing it over. You are the owner when you sign up. | | **admin** | Everything except the owner's decisions: closing, deleting or handing over the workspace, changing an owner, switching sanda search on, and requiring two-step sign-in. | | **read-only** | Open every page and ask every question. Change nothing. | | **reader** | Read reports on the reports portal and ask cherry about them. No console. | See [members and roles](https://docs.sanda-os.com.au/workspace/members-and-roles) for what each role can do. ### Edition and trial An edition (Basic, Standard or Enterprise) sets your platform fee, how many warehouses you can hold, how large each can grow, and whether sanda search and included support are on. A trial has every feature, A$10 of free credit and lasts 7 days. See [editions](https://docs.sanda-os.com.au/billing/editions) and [trial](https://docs.sanda-os.com.au/billing/trial). ## Getting data in ### Warehouse Your sanda warehouse is a dedicated PostgreSQL database that belongs to your workspace, held in Sydney. Every connection and CSV lands in it. sanda provisions it in under a minute, and you can read it in the **Explorer** or with SQL. See [warehouse](https://docs.sanda-os.com.au/warehouse). ### Connection A connection links one source system to one warehouse. It records which connector it uses, which streams it syncs, and when it runs. You add one from **Data · Connections**. See [connections](https://docs.sanda-os.com.au/connections) and the [connector catalogue](https://docs.sanda-os.com.au/connectors). ### Stream A stream is one table or object that a connection syncs from its source, such as invoices or contacts. You choose which streams to sync, and how each is read. See [choose streams](https://docs.sanda-os.com.au/connections/choose-streams). ### Sync A sync is one run of a connection: it reads the source and lands the rows in your warehouse. Each stream has a sync mode that says whether the whole table is read every time (full refresh) or only rows that changed (incremental). See [sync modes](https://docs.sanda-os.com.au/connections/sync-modes) and [monitor syncs](https://docs.sanda-os.com.au/connections/monitor-syncs). ### Schedule A schedule says when something runs: a connection's sync, a task in Modelling, or the delivery of a report. You pick a time of day in your own timezone, and runs fall on a 15 minute grid. See [schedules](https://docs.sanda-os.com.au/connections/schedules). ## Giving data meaning ### Semantic fluid The semantic fluid is the shared business language on top of your warehouse: the tables you allow, how they join, and what your numbers mean. cherry, sanda search, MCP, reports and the Excel feed all read through it, so they agree. See [semantic fluid](https://docs.sanda-os.com.au/semantic-fluid). ### Table In the semantic fluid, a table is a table or view from your warehouse that you have put on the map. It is either a **fact** (it records something that happened and carries measures, such as invoices) or a **dimension** (it records something that exists, such as customers). See [tables](https://docs.sanda-os.com.au/semantic-fluid/tables). ### Dataset A dataset is a group of tables. By default it is every stream from one connector, and it tints those tables on the map. See [tables](https://docs.sanda-os.com.au/semantic-fluid/tables). ### Metric A metric is a number defined once, with a name, a definition and optional conditions, such as `net revenue`. Every answer that uses it uses the same definition. See [metrics](https://docs.sanda-os.com.au/semantic-fluid/metrics). ### Dimension A dimension is a way to slice a number, such as `customer`, `region` or `month`. A time dimension can roll up from day to month to year. See [dimensions and filters](https://docs.sanda-os.com.au/semantic-fluid/dimensions-and-filters). ### Filter A filter is a named condition that says which rows count, such as `approved invoices`. You write it once and reuse it. See [dimensions and filters](https://docs.sanda-os.com.au/semantic-fluid/dimensions-and-filters). ### Relationship A relationship says how two tables join. You draw it between two columns on the map, and it records which side holds a single row. See [relationships](https://docs.sanda-os.com.au/semantic-fluid/relationships). ### The map The map is the canvas view of the semantic fluid. Tables are cards, relationships are lines, and a rail down the left opens each kind of definition. See [the map](https://docs.sanda-os.com.au/semantic-fluid/the-map). ### Learn from my data Learn from my data asks cherry to read your warehouse and propose the tables, relationships, metrics and dimensions it holds. It is the fastest way to build the map. See [learn from my data](https://docs.sanda-os.com.au/semantic-fluid/learn). ### Proposals and review What cherry proposes arrives as suggestions, and nothing reaches an answer until you accept it. You can accept one at a time, correct it first, reject it, or accept all. See [review suggestions](https://docs.sanda-os.com.au/semantic-fluid/review). ## Asking and searching ### cherry cherry is sanda's AI data agent. It sits in a pane beside your work in the console, and it has its own full-screen page. It answers questions, defines metrics and relationships, builds views and reports, starts syncs, and walks a new workspace through setup. See [cherry](https://docs.sanda-os.com.au/cherry). ### Held actions By default cherry asks first. A change it proposes waits as a card with **Confirm** and **Decline**, and nothing runs until you press one. In **Autonomous** mode it acts at once, but it still asks before emailing a report, dropping a view, retiring a table, or deleting a definition or a delivery. See [actions and approvals](https://docs.sanda-os.com.au/cherry/actions-and-approvals). ### Search service A search service tells sanda search which table on your map to index, which column identifies a row, and which text columns to search. sanda builds the index in your warehouse and keeps it fresh. A workspace owner must switch sanda search on first, and it is included from Standard. See [search](https://docs.sanda-os.com.au/search). ## Reporting ### Report A report is a canvas of blocks that you arrange on a page. It stores the questions its blocks ask, not the rows, so it shows current numbers each time it opens. See [reports](https://docs.sanda-os.com.au/reports). ### Block A block is one piece of a report: a figure (kpi), a bar chart, a line chart, a table, a written analysis, or a header. See [blocks](https://docs.sanda-os.com.au/reports/blocks). ### Delivery A delivery sends a report on a schedule. It goes by email, or as a summary to Slack, or as JSON to a webhook of your own. See [report delivery](https://docs.sanda-os.com.au/reports/delivery). ### Reports portal The reports portal is a place for the people you build reports for. They sign in, read the reports you publish to them, and can ask cherry about the numbers. They never see the console, and each reader can be limited to their own rows. See [the reports portal](https://docs.sanda-os.com.au/reports/portal). ## Connecting other tools ### Agent token An agent token is a secret that lets a program read your workspace without a person signing in, such as Claude Code, an agent of your own, Excel or Power BI. An owner or admin creates it under **Settings · Integrations**, where it is called an integration token. sanda shows it once, every optional permission starts off, and you can revoke it at any time. See [agent tokens](https://docs.sanda-os.com.au/mcp/agent-tokens). ### MCP MCP, the Model Context Protocol, is an open standard for letting AI assistants use tools. sanda is an MCP server, so an assistant such as Claude can search your definitions and query your data through the semantic fluid, with only the permissions you grant. MCP is included from Standard. See [MCP](https://docs.sanda-os.com.au/mcp). ## Keep reading :::links - [Quickstart](https://docs.sanda-os.com.au/get-started/quickstart): Put these concepts to work with your own data. - [How sanda works](https://docs.sanda-os.com.au/get-started/how-becca-works): Where data lives, and when work happens. - [Glossary](https://docs.sanda-os.com.au/reference/glossary): Every term, in alphabetical order. ::: --- # A tour of the console > Every area in the console's left navigation and what it is for, plus the top bar, the usage meter and the cherry pane. The console is where you work in sanda. Sign in at [console.sanda-os.com.au](https://console.sanda-os.com.au). It has three parts: a navigation rail on the left, a top bar, and the page itself, with cherry in a pane on the right. ![The Reports page in the console, with the left navigation, the top bar and the cherry pane open on the right.](https://docs.sanda-os.com.au/media/console-tour-layout.png "The left navigation, the top bar and the cherry pane, here beside the Reports page.") ## The left navigation The rail is grouped in the order the work happens: data comes in, meaning is added, results go out. Your workspace name sits at the top. Press the panel icon beside the sanda wordmark to fold the rail down to icons. sanda remembers the choice in that browser. On a phone the rail becomes a drawer behind the menu button. | Group | Area | What it is for | |---|---|---| | | **Overview** | Your home page. cherry's message box, your recent conversations, headline counts (connections, rows synced, semantics, reports, live schedules, conversations), recent activity, and account usage and spend. On a new workspace it offers the setup steps. | | **Data** | **Warehouse** | Provision warehouses and watch their storage, compute and limits. Each warehouse card opens the Explorer, the Optimise page and the SQL shell. See [warehouse](https://docs.sanda-os.com.au/warehouse). | | | **Connections** | Every connection and its sync status. Add one with **New connection**, or load a file with **Load a CSV**. See [connections](https://docs.sanda-os.com.au/connections). | | | **Explorer** | Browse every schema, table, view and procedure in a warehouse, with columns and the first rows. It is read-only. See [the explorer](https://docs.sanda-os.com.au/warehouse/explorer). | | | **Modelling** | Build views, materialized views and procedures in your warehouse, and the tasks that keep them fresh. See [modelling](https://docs.sanda-os.com.au/modelling). | | **Intelligence** | **Semantic fluid** | The map of your tables, relationships, metrics, dimensions and filters, and the queue of suggestions to review. See [semantic fluid](https://docs.sanda-os.com.au/semantic-fluid). | | | **Vector search** | Search the text inside your records by meaning, and manage search services. This is sanda search. See [search](https://docs.sanda-os.com.au/search). | | | **Agents** | AI assistants connected over MCP: what they can reach, what they did, and an approvals queue. See [MCP](https://docs.sanda-os.com.au/mcp). | | **Activate** | **Reports** | Your reports, in a **Reports** tab, and the reports portal, in a **Portal** tab. **Report schedules** sets up email deliveries. See [reports](https://docs.sanda-os.com.au/reports). | | | **Push** | sanda's numbers written into fields on your Salesforce records, and what each run sent. Listed once Push is switched on for your console. See [Push](https://docs.sanda-os.com.au/push). | | | **cherry chat** | The same conversation as the cherry pane, full screen. See [cherry](https://docs.sanda-os.com.au/cherry). | | **Monitor** | **Pipeline** | One timeline of what runs and when: syncs, task runs and report deliveries. | | **System** | **Settings** | Workspace, Appearance, Plan & budget, cherry, Security, Integrations, Team, and Close or delete. | | | **Support** | Send a message to sanda and see your tickets. See [getting help](https://docs.sanda-os.com.au/help). | **Vector search** and **Push** show a small edition tag on Basic, because sanda search and Push are included from Standard. The pages still open, and explain what your edition includes. **cherry chat** sits on its own between Activate and Monitor. At the foot of the rail, **Documentation** opens these docs and **Explore the platform** opens sanda's public site. Your name and role sit beside the sign-out button. ## The top bar ![The top bar on the Connections page, showing the trial pill with credit left and days remaining, the Docs link, the AI usage ring, and the cherry button.](https://docs.sanda-os.com.au/media/console-tour-topbar.png "The top bar during a trial.") From left to right, the top bar shows: - **Where you are.** A breadcrumb such as Data, then Connections. - **The trial pill**, on a trial only. It reads "Trial", then the free credit you have left, then the days remaining. It turns amber when the credit is nearly spent or few days remain, and red when the credit is used up or the trial has ended. Press it to open **Settings · Plan & budget**, where the credit meter shows what it has gone on. - **Docs.** It opens the guide to the page you are on, here on docs.sanda-os.com.au, in a new tab. On a phone it is the book icon. - **The AI usage ring.** A ring that fills as you use your daily AI limit, with today's spend beside it on a wide screen. Press it to see today's use against the limit, the month so far, and when the day resets, with a link to change the limit. All amounts in the console are Australian dollars. - **The cherry button.** It opens and closes the cherry pane. A small dot appears when cherry has replied while the pane was closed. ## The cherry pane cherry sits in a pane on the right of every page except **Overview** and **cherry chat**, which are cherry already. Open and close it from the top bar, or with ⌘ J (Ctrl J on Windows). The pane keeps its conversation as you move between pages, so you can ask about a table on the map, then go to Reports and carry on. When cherry takes you somewhere, the pane opens beside the page. Along the top of the pane: - **new chat** starts a fresh conversation. - The history icon lists your conversations. They are yours, and you can search them. - The expand icon opens the conversation full screen. - The close icon closes the pane. Drag the pane's left edge to resize it. On a phone the pane takes the whole screen, and the close button is the way back. See [ask cherry](https://docs.sanda-os.com.au/cherry/ask). ## Related pages :::links - [Quickstart](https://docs.sanda-os.com.au/get-started/quickstart): Use the console end to end with your own data. - [Set up with cherry](https://docs.sanda-os.com.au/get-started/setup-with-cherry): The setup checklist cherry draws. - [Keyboard shortcuts](https://docs.sanda-os.com.au/reference/keyboard-shortcuts): Every shortcut the console has. ::: --- # Set up with cherry > The five-step setup checklist cherry draws in a new workspace, what each step does, and exactly when it counts as done. A new workspace climbs five steps, in order: a warehouse, a source, learning, review, and a first report. cherry reads where your workspace is up to and draws those steps as a checklist, so you always know the next one. ![The setup checklist in a new workspace, headed "setting up your workspace" with 0 of 5 steps done, and the first step, provision a warehouse, showing an open warehouse button.](https://docs.sanda-os.com.au/media/setup-checklist.png "The checklist in a brand-new workspace. The next step carries its button.") ## Get the checklist Ask cherry to help you set up. On a new workspace the **Overview** page offers the suggestion **help me set up my account**, and you can type the same request in the message box or in the cherry pane at any time. Later in setup, the same suggestion reads **finish setting up**. The checklist appears under cherry's reply, headed "setting up your workspace", with a count such as "0 of 5". As the conversation grows and the checklist scrolls out of sight, a folded copy stays pinned to the top with the next step and its main button. Unpin it with the pin icon, and the checklist's own card then offers **pin** to bring it back. When all five steps are done, the heading changes to "your workspace is set up". Three things to know about how it behaves: - **You press every step.** Nothing moves on by itself, and cherry takes no setup step you did not ask for. - **It stays current.** The card reads your workspace again while it is on screen, so a step you finish on the page beside it is ticked there. An older checklist in a past conversation shows where your workspace is today, not where it was. - **The suggestions follow it.** The suggestions in an empty conversation change with the next step, such as **connect a source** or **review the suggestions**. Once setup is complete, they turn to everyday asks such as "what changed this month?". ## The five steps Each step is **done**, **next** (it shows its buttons), **under way** (a spinner, while something it is waiting on finishes), or **to do**. | Step | It counts as done when | Buttons | |---|---|---| | **1 · provision a warehouse** | A warehouse of yours is ready to receive rows. | **open warehouse** | | **2 · connect a source** | Rows have landed: a connection's first sync has finished with rows, or a CSV has been loaded. A map that already has tables counts too. | **connect a source**, **upload a csv** | | **3 · learn from my data** | A learn pass has run, or your map already has metrics. Tables you add by hand do not count on their own. | **learn from my data** | | **4 · review the suggestions** | Learning is done and no suggestions are waiting. | **review the suggestions**, **accept them with cherry** | | **5 · build a first report** | Your workspace has at least one report. | **build a first report** | ### Provision a warehouse Every connection and CSV lands in a warehouse, so this comes first. **open warehouse** takes you to **Data · Warehouse**, where **Provision warehouse** creates one in under a minute. While it builds, the step is under way. If provisioning fails, the step says so and points you to the warehouse page to retry. See [provision a warehouse](https://docs.sanda-os.com.au/warehouse/provision). ### Connect a source The step is done when rows have landed, not when a connection is saved. Until then it tells you what it is waiting on, and its button opens the connection so you can act: - The first sync is running. Rows land when it finishes. - The first sync failed. The connection's page says why and runs it again. - The connection is paused before its first sync. Only sanda support can resume it: raise a ticket under **System · Support**. - The connection has not synced yet. Run its first sync from its page, or let its schedule start it. - A sync finished but no rows landed. The connection's page shows which streams it syncs. If you have a file rather than a system, **upload a csv** loads it straight in. See [add a connection](https://docs.sanda-os.com.au/connections/add-a-connection) and [upload a CSV](https://docs.sanda-os.com.au/connections/upload-a-csv). ### Learn from my data **learn from my data** takes you to the Semantic fluid page and asks cherry to read what landed and propose the tables, metrics, dimensions and relationships. You watch the proposals arrive on the map. This step uses AI, so it counts against your daily AI limit and, on a trial, your free credit. See [learn from my data](https://docs.sanda-os.com.au/semantic-fluid/learn). ### Review the suggestions Learning proposes; you decide. **review the suggestions** opens the queue on the map. **accept them with cherry** asks cherry to go through the queue with you, accepting the ones that look right and flagging the ones it is unsure about. A relationship counts as one suggestion even though it joins two tables. See [review suggestions](https://docs.sanda-os.com.au/semantic-fluid/review). ### Build a first report **build a first report** opens a short form in the checklist. Enter a **Name** and **What it is for**, then press **build the report**. cherry builds the whole report from what is on your map (figures, trends, breakdowns and a written analysis) and takes you to it. If your map has more than one table, the step also offers **tidy the map**, which lays the tables out by how they join. It suggests doing that first, so you can check the model before a report is built on it. See [build a report with cherry](https://docs.sanda-os.com.au/reports/build-with-cherry). ## Who can do each step Provisioning a warehouse and connecting a system need an owner or admin. Loading a CSV, learning, reviewing and building a report are open to anyone who is not read-only. When a step needs a role you do not have, the checklist says so beside it. ## Related pages :::links - [Quickstart](https://docs.sanda-os.com.au/get-started/quickstart): The same steps as a walkthrough. - [cherry](https://docs.sanda-os.com.au/cherry): What else cherry can do once you are set up. - [Actions and approvals](https://docs.sanda-os.com.au/cherry/actions-and-approvals): Why cherry sometimes waits for you to press Confirm. ::: --- # Your first week > A practical plan for a trial, from your first data to your first scheduled report, with a decision about your edition before the credit runs out. A trial runs for 7 days, has every feature, and comes with A$10 of free credit that compute, storage, AI and sync runs draw down. That is enough to connect something real and see sanda work on it, so the plan below keeps the scope small: one source, one question worth answering, one report your team will read. You do not have to follow it day by day. It shows the order that works, and what to check at each point. ## Before you start - **Pick the question.** Start with the pack you assemble by hand every month. It is the one most worth automating, and it tells you which tables you need. - **Pick one source.** A single accounting package or database is plenty. Have its credentials ready. If it sits behind a firewall, [allow sanda's sync address](https://docs.sanda-os.com.au/connections/allowlist) first, or the first sync will fail. - **Choose who signs up.** The person who signs up becomes the workspace owner, which is the role that can close the workspace and hand it over. ## Day 1: warehouse and first data 1. Sign up and sign in. The [quickstart](https://docs.sanda-os.com.au/get-started/quickstart) covers both. 2. Provision your warehouse from **Data · Warehouse**. 3. Connect your source from **Data · Connections**, or load a CSV if that is quicker. Choose only the streams you need. Every stream starts on a full refresh, which reads the whole table each run. Leave small tables on it, and switch large ones to incremental. See [sync modes](https://docs.sanda-os.com.au/connections/sync-modes). 4. Run the first sync with **Sync now**, and let it finish. ## Day 2: check what landed, then learn 1. Open the connection's **Timeline** tab to confirm the run succeeded, and **Explorer** to look at the tables in `raw`. 2. Go to **Intelligence · Semantic fluid** and press **Ask cherry to learn**. 3. Review the suggestions. Accept the ones that look right, correct any that are close, and reject the rest. Then use the arrange button on the map, or **tidy the map** in cherry's reply, to lay the tables out by how they join. The learn pass is AI work, so it draws on your credit. It is worth doing once your data has landed, rather than repeatedly while you are still loading it. ## Days 3 and 4: ask, test and build 1. **Ask three questions whose answers you already know**, such as last month's sales. Compare cherry's numbers with the ones you trust, and open the query behind each answer. 2. **Fix the definitions where they differ.** Add an alias for a column your team names differently, define your own `net revenue` as a metric, or add a filter such as `approved invoices`. Ask again and check the number moves. See [metrics](https://docs.sanda-os.com.au/semantic-fluid/metrics). 3. **Build your first report.** From **Activate · Reports**, press **Build one with cherry**, name it after the pack you chose on day one, and say what it is for. Then open the report and adjust the blocks. See [build a report with cherry](https://docs.sanda-os.com.au/reports/build-with-cherry). The daily AI limit (A$5 by default) caps how much AI a day can use. If you reach it, AI waits until the day resets, and everything else keeps working. ## Days 5 and 6: automate and share 1. **Schedule the data.** On the connection's **Settings** tab, choose how often it syncs. Runs fall on a 15 minute grid. See [schedules](https://docs.sanda-os.com.au/connections/schedules). 2. **Schedule the report.** Go to **Activate · Reports**, press **Report schedules** and then **Schedule a report**, and send it to yourself first. Once it looks right, add your colleagues. See [report delivery](https://docs.sanda-os.com.au/reports/delivery). 3. **Check the timeline.** **Monitor · Pipeline** shows the syncs and deliveries you have set up, and when each runs. 4. **Bring in your team.** In **Settings · Team**, press **Invite someone**. Give a colleague who should only look the read-only role, or the reader role if they only need the reports portal. See [members and roles](https://docs.sanda-os.com.au/workspace/members-and-roles). 5. **Try one more way in.** Publish the report to the [reports portal](https://docs.sanda-os.com.au/reports/portal), connect Claude over [MCP](https://docs.sanda-os.com.au/mcp), or open your data in Excel with the feed under **Settings · Integrations**. If you have a table full of text, such as notes or descriptions, a workspace owner can switch on sanda search and index it. See [search](https://docs.sanda-os.com.au/search). ## Before the trial ends 1. **Read the credit meter.** Press the trial pill in the top bar to open **Settings · Plan & budget**. It shows how much credit is left and what it went on: compute, storage, AI and sync runs. 2. **Choose an edition.** The same page compares Basic, Standard and Enterprise. Press **Ask to move to** the one you want, and sanda's team makes the change. Nothing renews into a paid plan on its own. See [editions](https://docs.sanda-os.com.au/billing/editions). 3. **Set your own limits.** In **Budget & alerts**, set a monthly budget and a daily AI limit, so a busy month cannot surprise you. See [budgets and limits](https://docs.sanda-os.com.au/billing/budgets-and-limits). When the trial ends, or sooner if the credit runs out first, the workspace freezes and everything in it is kept. Queries, reports, AI, syncs and scheduled runs wait until you move to an edition, and then carry on where they stopped. See [the trial](https://docs.sanda-os.com.au/billing/trial). :::note Stuck at any point? **System · Support** reaches sanda's team, and faults and billing questions are free on every edition. See [getting help](https://docs.sanda-os.com.au/help). ::: ## Related pages :::links - [Set up with cherry](https://docs.sanda-os.com.au/get-started/setup-with-cherry): The checklist that tracks the first five steps for you. - [Troubleshooting](https://docs.sanda-os.com.au/help/troubleshooting): Fixes for the problems a trial most often meets. - [Console tour](https://docs.sanda-os.com.au/get-started/console-tour): Find your way around. ::: --- # sanda warehouse > A standard PostgreSQL database in Sydney where everything you connect or upload lands, and where your models are built. Your sanda warehouse is a dedicated PostgreSQL database that sanda provisions, tunes and runs for you. Every connection and CSV upload lands in it. Your own views and procedures are built in it. The semantic fluid, cherry, reports and sanda search all read from it. It runs in Sydney, and it is a standard database on purpose: the tables are ordinary PostgreSQL tables, the SQL you write is ordinary PostgreSQL, and you can [take a full copy with you](https://docs.sanda-os.com.au/warehouse/export). ## How many warehouses you can have A workspace holds one or more warehouses, depending on its edition. Each connection chooses which warehouse its rows land in. A trial has the limits of Standard. | | Basic | Standard | Enterprise | |---|---|---|---| | Warehouses in a workspace | 1 | 3 | 50 | | Storage in each warehouse | 10 GB | 100 GB | 16 TB | | Concurrent queries in each | 60 | 60 | 300 | | Longest single statement | 60 seconds | 60 seconds | 5 minutes | These are the most a warehouse can be set to. You can set any of them lower on a warehouse as a spend guard (see [Usage, budgets and suspension](https://docs.sanda-os.com.au/warehouse/usage-and-suspension)). The full list of limits is in [Limits](https://docs.sanda-os.com.au/reference/limits). The sanda learning dataset does not count towards your warehouse limit. It is a free, read-only sample that sanda attaches to every workspace: a specialty-foods importer with ten years of trading behind it, already modelled. It appears in the [Explorer](https://docs.sanda-os.com.au/warehouse/explorer) tagged **sample**, it is not metered, and you cannot run SQL, build models or optimise on it. Remove it with **Remove dataset** on its own card, and add it back with **Add it** at the bottom of the **Warehouse** page. ## How a warehouse is laid out Every warehouse has the same shape, so you and cherry never have to guess where something is. Each schema has one job. | Schema | What it holds | Who writes to it | |---|---|---| | `raw` | Landing tables: every stream a connection syncs, as it arrived, and every CSV you upload. | sanda's managed sync and CSV loader. You can read it. | | `derived` | Yours: the views, materialized views and procedures you define, and any tables your own SQL creates. | You, through [Modelling](https://docs.sanda-os.com.au/modelling) or the [SQL shell](https://docs.sanda-os.com.au/warehouse/sql). | | `mapping` | Modelled: the views the semantic fluid publishes so that cherry, reports and search can query them. | sanda, when you put a table on the fluid. | | `search` | Indexed: the text that sanda search keeps searchable, one table per search service. | sanda. | | `meta` | Bookkeeping: the cursors and version records sanda keeps for itself. | sanda. | A landing table is named `raw.__`: the connection's name in lowercase with anything that is not a letter or digit turned into an underscore, two underscores, then the stream. A connection called **Xero** with an `invoices` stream lands as `raw.xero__invoices`. A CSV called `sales` lands as `raw.csv__sales`. The [SQL reference](https://docs.sanda-os.com.au/reference/sql#names) has the full naming rules. :::note The semantic fluid does not query `raw` directly. When you add a table to the fluid, sanda publishes a view of it in `mapping`, and cherry and reports read only those published views. That is what keeps what cherry can see exactly equal to what a person chose to put on the map. ::: ## Storage and compute You pay for two things a warehouse uses, and the console shows both on the **Warehouse** page. **Storage** is the size of the whole database, including indexes and search indexes, in GB. sanda measures it exactly and bills the GB-months you actually held, at A$1.06 per GB-month. A warehouse that held 40 GB for half a month and 60 GB for the rest is billed 50 GB-months. **Compute** is the work your warehouse does, counted in compute hours. One compute hour is one vCPU running for an hour. The warehouse scales up under load and down to nothing when nobody is asking. sanda counts the minutes it was kept awake for you (each awake minute counts at half a vCPU, the floor), plus the larger of two figures: the work your queries did, or the seconds recorded for builds, procedure calls and scheduled tasks. Compute is billed at A$0.483 per compute hour used. Readings are taken when anyone opens the **Overview** or **Warehouse** page (a warehouse read in the last 45 seconds is skipped), when an owner or admin presses **Measure now**, and about hourly in the background, so a workspace nobody signs in to is still measured. [Billing usage](https://docs.sanda-os.com.au/billing/usage) shows how these readings become an invoice. ## The Warehouse page Open **Data · Warehouse**. Each warehouse is a card. | On the card | What it does | |---|---| | Name and status | The database name, and a status: `provisioning`, `healthy` or `error`. A `capacity · warning` pill appears when it is past 80% of a limit and `capacity · over` when it reaches one. A `suspended · budget` or `frozen · trial` pill appears when the workspace is suspended. | | **Explore** | Opens the [Explorer](https://docs.sanda-os.com.au/warehouse/explorer) on this warehouse. | | **Optimise** | Recommends [index, vacuum and statistics changes](https://docs.sanda-os.com.au/warehouse/optimise). | | **SQL shell** | Opens [sanda sql](https://docs.sanda-os.com.au/warehouse/sql). | | **Storage** and **Compute** meters | What this warehouse holds and runs today, against its limits, with the number of rows and tables and when it was last measured. | | **Run rate** | What the warehouse is running up, carried across a month at today's usage. It is an estimate; the invoice adds up the real readings. | | **Measure now** | Takes a reading immediately. | | **Limits** | Sets this warehouse's own storage, compute, concurrency and statement limits, at or below your edition's. | | **Sample model** | For a warehouse that holds sanda's sample tables. It adds their relationships, metrics and layout to your workspace's model. | | **Delete warehouse** | Drops the database and everything in it. Disabled while a connection still lands there. | | **Storage held** and **Compute run** charts | The last 30 days, one reading per day. | Above the cards, **New warehouse** [provisions another one](https://docs.sanda-os.com.au/warehouse/provision), and **Explore** opens the Explorer across all of them. Every member can read this page. Owners and admins provision, measure, change limits and delete. ## Reaching your data sanda connects to your warehouse for you and never hands out a database password, so there is no connection string to paste into other tools. You reach the data through: - the [Explorer](https://docs.sanda-os.com.au/warehouse/explorer), to browse schemas, columns and sample rows; - [sanda sql](https://docs.sanda-os.com.au/warehouse/sql), to run your own SQL as an owner or admin; - [Modelling](https://docs.sanda-os.com.au/modelling), to build views and procedures; - the [OData feed](https://docs.sanda-os.com.au/odata), to pull tables into Excel or Power BI; - a [full copy](https://docs.sanda-os.com.au/warehouse/export), if you want to leave with your data. :::links - [Provision a warehouse](https://docs.sanda-os.com.au/warehouse/provision): Create your first warehouse, or add another. - [Explore your warehouse](https://docs.sanda-os.com.au/warehouse/explorer): Browse schemas, tables, columns and rows. - [sanda sql](https://docs.sanda-os.com.au/warehouse/sql): Run SQL directly, read-only by default. - [SQL reference](https://docs.sanda-os.com.au/reference/sql): The dialect, naming, limits and worked examples. ::: --- # Provision a warehouse > Create your first sanda warehouse, add more, and recover if provisioning stops partway. Nothing can land in your workspace until a warehouse exists, so provisioning is the first step after signing up. sanda creates the database for you; there is no host, password or database name to fill in. ## Before you start - You need to be an owner or admin. Other members see the **Warehouse** page but cannot provision. - Your edition sets how many warehouses you can hold: 1 on Basic, 3 on Standard (and on a trial) and 50 on Enterprise. See [sanda warehouse](https://docs.sanda-os.com.au/warehouse) for the full comparison. ## Create your first warehouse :::steps 1. **Open the Warehouse page.** In the console, go to **Data · Warehouse**. Before you have a warehouse, the page shows **Let sanda provision it**, and a **Pricing** panel with the storage and compute rates and the most a warehouse can be set to. ![The Let sanda provision it panel with the Provision warehouse button](https://docs.sanda-os.com.au/media/warehouse-provision.png "The first-run panel on the Warehouse page") 2. **Press Provision warehouse.** The button reads **Provisioning…** while sanda works. The console says it takes under a minute. 3. **Check the status.** When it finishes, the new card shows the status `healthy`, and buttons for **Explore**, **Optimise** and **SQL shell** appear on it. 4. **Bring data in.** A healthy warehouse with nothing in it shows **Your warehouse is ready**, with two ways on: **Connect a source** and **Upload a CSV**. ::: ## What sanda sets up While the status is `provisioning`, sanda: 1. creates a new PostgreSQL database in your workspace's [data region](https://docs.sanda-os.com.au/reference/glossary#data-region); 2. creates its schemas: `raw`, `derived`, `mapping`, `search` and `meta` (see [sanda warehouse](https://docs.sanda-os.com.au/warehouse)), plus a private working area the managed sync uses; 3. creates separate logins for loading, modelling, reading and your own building, each with only the access its job needs, and stores their credentials encrypted; 4. applies your edition's limits from the first minute, so a runaway query is stopped rather than reviewed afterwards; 5. takes a first storage and compute reading, which is the baseline every later reading is measured from. None of the credentials is shown to you. That is deliberate: the console, cherry and the sync all connect on your behalf. ## Names You do not name a warehouse. sanda names the database after your workspace: - your first warehouse is `becca_` followed by eight characters (digits and the letters a to f), for example `becca_1a2b3c4d`; - each further one adds an underscore and four more, for example `becca_1a2b3c4d_9f3e`. The name is what you see in the Explorer, the SQL shell and the Modelling warehouse picker. ## Add another warehouse Press **New warehouse** at the top of the **Warehouse** page. It appears once you have a warehouse of your own. If your workspace is already at its limit, sanda tells you how many your edition holds and how many you have. Delete one you no longer need, or move to a larger edition. Three things to know before you add another: - **Every warehouse is in your workspace's data region.** A new one is created in the same region as the first, and a workspace cannot hold a warehouse anywhere else. - **Each connection lands in one warehouse.** You choose it on the **Configure** step when you add the connection. - **The semantic fluid reads one warehouse: the newest healthy one you own.** Views and tables in your other warehouses are real and readable in the Explorer and the SQL shell, but you cannot put them on the fluid. ## If provisioning fails A failed attempt leaves a card behind with the reason, rather than nothing at all. The card shows the status `error` and a **Retry provisioning** button. A healthy warehouse with no credentials on file shows the same button under the message **No credentials on file**. :::steps 1. **Read the reason on the card.** A refusal such as an edition limit says what to do. A failure partway through says **Provisioning stopped partway** and gives a short reference. 2. **Press Retry provisioning.** sanda re-runs the setup on the same database, with the same name and new passwords. It never creates a second warehouse, so a retry cannot use up a second slot. 3. **If it fails again, contact sanda support** and quote the reference from the card. ::: If you would rather start again, use **Delete warehouse** on the failed card. ## Delete a warehouse :::danger Deleting a warehouse drops its database and everything landed in it. There is no undo. Take a copy of anything you need first: see [Take your data with you](https://docs.sanda-os.com.au/warehouse/export). ::: **Delete warehouse** is disabled while any connection still lands in the warehouse. The card says how many, for example **3 connections land here: repoint or delete them before deleting this warehouse**. Once none remain, press **Delete warehouse**, then **Yes, delete it** (or **Keep it** to back out). Usage already measured stays in your billing history, and metering stops when the warehouse is deleted. :::links - [Upload a CSV](https://docs.sanda-os.com.au/connections/upload-a-csv): Put a file in your new warehouse. - [Add a connection](https://docs.sanda-os.com.au/connections/add-a-connection): Sync a business system into it. - [Usage, budgets and suspension](https://docs.sanda-os.com.au/warehouse/usage-and-suspension): How limits and budgets protect your bill. ::: --- # Explore your warehouse > Browse schemas, tables, views and columns, and preview real rows, without writing any SQL. The Explorer shows what is actually in your warehouse, read live from its own catalogue: these schemas, these tables and views, these columns, these rows. Other pages show a reading of the warehouse (the semantic map shows the tables someone put on it, the capacity card shows a byte count). The Explorer is the one that answers "what is in there?" the way a database console does. It is read-only. Nothing you do here changes your data. Changes are made in [Modelling](https://docs.sanda-os.com.au/modelling) or the [SQL shell](https://docs.sanda-os.com.au/warehouse/sql), and the Explorer links to both. ## Open the Explorer Go to **Data · Explorer**. You can also press **Explore** in the header of the **Warehouse** page, or **Explore** on a warehouse's card to open that warehouse's tree. Every member of the workspace can use it. ![The Explorer with the mapping schema open and the columns of a table shown](https://docs.sanda-os.com.au/media/explorer-columns.png "The tree on the left, the selected table on the right") ## Read the tree The tree on the left has four levels: the warehouse (the database), a schema, a folder, then the object. - **Warehouses.** Every warehouse in your workspace is a root in the same tree, your own first and the sanda sample dataset last, tagged **sample**. A warehouse that is not `healthy` shows its status instead. - **Schemas.** `raw`, `derived`, `mapping`, `search` and `meta`, each with the number of objects in it. See [sanda warehouse](https://docs.sanda-os.com.au/warehouse) for what each one holds. - **Folders.** **Tables**, **Views**, **Materialized views** and **Procedures**. Every schema shows **Tables** and **Views**. `derived` always shows all four, because it is the one you build in. Other schemas show **Materialized views** and **Procedures** only when they hold something. - **Objects.** Tables and materialized views show an approximate row count on the right, so you can see where the data is without opening anything. A small **!** means the object's last build failed. A layers icon means the object is on the semantic fluid. Use the search box, **Find a table, view or procedure**, to filter by name. Search opens every branch that matches, but it only searches warehouses whose tree you have already opened, so open a warehouse first. ## The warehouse summary Select a warehouse to see its summary: | Fact | What it tells you | |---|---| | **Storage** | The size of the whole database, and when it was last measured. | | **Tables** | How many tables there are across every schema. | | **Views** | How many views and materialized views there are. | | **On the map** | How many relations the semantic fluid may query. | Below the facts, a table lists each schema with its number of tables, views, materialized views and procedures, its estimated rows and its size. Row counts are the database's own estimates, and sizes include indexes. A table nobody has counted yet shows no count in the tree, and **not counted yet** in its header. The summary also says how much of `derived` you have modelled, and links to **Modelling** and **SQL shell**. ## Inspect a table or view Select an object to open it. The header shows its kind, and, where they apply: - its version, for anything sanda defined in `derived`; - **last build failed**, if a materialized view's most recent build did not finish; - **on the map**, with a link to the semantic fluid, and the name it has there; - **not readable by sanda**, if the modelling login cannot select from it, which can happen to a table created some other way. Its columns are still listed. Under that, a line of facts: rows (`about 12.5K`), size, number of columns, the primary key, and what a view reads. A view holds no rows of its own, so it shows **as many as it reads**. A materialized view shows when it was last built and how long the build took. ### Columns The **Columns** tab lists each column in the table's own order: its position, name, type, whether it allows nulls, and its default. A key icon marks a primary key column, because the key is the grain of the table. A lock marks a column whose values the preview will not show (see below). ### Data preview The **Data preview** tab reads the first rows of the table when you open it, and again when you press **Refresh**. A preview shows: - up to 50 rows and the first 60 columns (the **Columns** tab lists the rest); - no particular order. The preview asks for rows without sorting so that a huge table answers at once, so it is a look at the shape of the data, not a report; - long values cut at 300 characters. A preview is a real query on your warehouse, run inside a read-only transaction. It happens when you open the tab, not when you click a table to read its columns. ### Definition For a view, a materialized view or a procedure, the **Definition** tab shows the SQL it is, and what it reads. A view runs its statement over what it reads each time it is asked. A materialized view holds the rows its statement produced when it was last built. ### History Anything sanda defined in `derived` has a **History** tab, with the same content as in Modelling: - **Runs**: each build or call, when it ran, what started it, the outcome, how long it ran, what it was billed as, and the rows and size afterwards. - **Definitions**: every version, what changed and who asked, with the statement sanda ran. - **Activity**: the audit records for the object. An object in `derived` that was made some other way, for example by a table created in the [SQL shell](https://docs.sanda-os.com.au/warehouse/sql), is listed but has no history. The pane says **Made outside sanda**, and sanda will not drop it for you. ## What the Explorer hides Two kinds of column are counted but never listed or fetched: - **Bookkeeping columns.** The managed sync and the CSV loader add columns of their own to landing tables, whose names start with an underscore and a reserved prefix. The Explorer leaves them out everywhere, and the header says how many were not shown, for example **2 of the loader's own not shown**. See [SQL reference](https://docs.sanda-os.com.au/reference/sql) for what they hold. - **Columns whose names look like secrets.** A name with a word like `password`, `token`, `secret` or `api_key` in it is listed with a lock, and its values are withheld from previews. The **Data preview** tab says how many columns were withheld. The Explorer also does not list the managed sync's own working area, which is not yours to read. ## Row counts are estimates The number next to each object, and on the summary, comes from the database's statistics, not from counting the rows. It is quick, and it can be a little behind after a large sync. If you need an exact figure, run `select count(*)` in the [SQL shell](https://docs.sanda-os.com.au/warehouse/sql). ## Where to make changes | To do this | Go to | |---|---| | Draw, write or redefine a view, or schedule a rebuild | [Modelling](https://docs.sanda-os.com.au/modelling) | | Run your own statement, read-only or read-write | [sanda sql](https://docs.sanda-os.com.au/warehouse/sql) | | See or change storage and compute limits | The **Warehouse** page: **Capacity and limits** takes you there | | Put a table on the semantic fluid | [The semantic fluid](https://docs.sanda-os.com.au/semantic-fluid) | :::links - [sanda sql](https://docs.sanda-os.com.au/warehouse/sql): Run your own SQL when browsing is not enough. - [Views and modelled tables](https://docs.sanda-os.com.au/modelling/views): Turn what you find here into something the fluid can use. - [SQL reference](https://docs.sanda-os.com.au/reference/sql): Schemas, naming and the columns sanda adds. ::: --- # sanda sql > Run your own SQL directly on a warehouse. Read-only by default, capped, timed and recorded in the audit trail. sanda sql is the SQL shell: a box where you type one PostgreSQL statement and run it on one of your warehouses. Use it for a question the [Explorer](https://docs.sanda-os.com.au/warehouse/explorer) cannot answer, a quick check on a landing table, or a one-off fix. It is the one console page that opens on a warning rather than on its content. Modelling refuses to drop a view something depends on, and the Explorer cannot write at all. Here you are the parser: sanda runs what you type, and in read-write mode it cannot undo it. ## Open the shell Only owners and admins can open it, because what it runs costs compute and what it changes cannot be undone by sanda. Other members see a note saying so. 1. Go to **Data · Warehouse** and press **SQL shell** on a healthy warehouse card. You can also press **SQL shell** on a warehouse's summary in the Explorer. 2. Choose the warehouse from the **warehouse** menu at the top right if you have more than one. The menu shows each warehouse of your own. The sanda learning dataset is shared and read-only, so it is not offered. If a warehouse is not `healthy`, the shell says so and opens once it is. ## Read the warning The first time you open the shell on a warehouse in a browser session, it shows **For advanced users. Read this once before you open it.** and nothing else. ![The SQL shell warning, with the I understand button](https://docs.sanda-os.com.au/media/sql-shell-warning.png "The warning comes first, and the shell opens only after you accept it") It makes five points, and each is a fact about how the shell works: - **Statements run as the warehouse's builder role.** That role reads every landing table in `raw`, everything in `derived` and the tables in `mapping`, and writes only in `derived`. It cannot touch a published `mapping` view, the search indexes or sanda's own bookkeeping, whatever the statement says. - **Read-only is the default, and the database enforces it.** A read-only transaction refuses every change. Read-write runs your statement exactly as written and commits it. There is no undo, no confirmation on each statement, and no backup taken first. - **Modelling protects you here and the shell does not.** From [Modelling](https://docs.sanda-os.com.au/modelling), dropping a view refuses when something depends on it. From the shell, a `drop` or a `delete` does what you told it to, and the views, schedules and fluid definitions built on it break at their next run. - **Every statement costs your compute and is recorded.** It runs under your warehouse's statement ceiling, it is billed like any other work in the warehouse, and it lands in the audit trail with your name and its text. - **One statement per press, and at most 500 rows come back.** Larger results are cut off and the page says so. Columns whose names look like secrets are withheld. Press **I understand. Open the shell** to carry on, or **I wanted Modelling instead** to go to Modelling. sanda remembers that you accepted for this warehouse until you close the browser tab. Opening a different warehouse asks again, because the reach of a statement is a different database. ## Run a statement :::steps 1. **Check the mode.** The shell opens on **Read-only**. Leave it there unless you mean to change something. 2. **Choose how many rows.** The **rows** menu offers 50, 200 (the default) and 500. 3. **Type one statement.** Name tables with their schema, as the placeholder does: `select * from raw.__ limit 20`. 4. **Run it.** Press **Run**, or CtrlEnter (⌘Enter on a Mac). **Clear** empties the editor. 5. **Read the result.** A bar above the rows shows the mode (**read-only** or **committed**), how many rows and columns came back, how long it took, and the ceiling it ran under. ::: ![The shell editor in read-only mode](https://docs.sanda-os.com.au/media/sql-shell-editor.png "The editor, with the Read-only and Read-write switch and the rows menu") ## Read-only and read-write | | Read-only | Read-write | |---|---|---| | How it runs | Inside a read-only transaction, so PostgreSQL refuses any change, including a writable `with` clause or a function with side effects | Exactly as written, and committed as soon as it finishes | | Run button | **Run** | **Run and commit** | | Editor | Normal | Drawn in red, with a banner: **Read-write. Each statement commits as soon as it finishes. There is no undo.** | | Undo | Not needed | None | Switching to **Read-write** asks you to confirm. The page then stays in read-write until you switch back or leave it. In read-write mode you can create and change objects in `derived` only. For example, to keep a rollup as a table: ```sql title="Read-write: a rollup table in derived" create table derived.monthly_revenue as select date_trunc('month', sale_date)::date as month, region, sum(revenue) as revenue from raw.csv__sales group by 1, 2; ``` An object you make this way is an ordinary table in `derived`. sanda does not record it as a modelled object: it has no versions, no history and no schedule, the Explorer marks it **Made outside sanda**, and sanda will not drop it for you. If you want a definition sanda keeps versioned and can rebuild on a schedule, build a [view or materialized view in Modelling](https://docs.sanda-os.com.au/modelling/views) instead. A statement that writes anywhere else fails with a permission error that names what the builder role may do. ## What a statement can reach | | | |---|---| | **Can read** | `raw.*`, `derived.*`, the tables in `mapping.*`, and the database catalogue (`information_schema` and `pg_catalog`). | | **Can write** | `derived.*`, in read-write mode only. | | **Cannot reach** | Views the semantic fluid publishes in `mapping`, sanda search's indexes, sanda's bookkeeping, and the managed sync's working area. | Because published `mapping` views are out of reach, a shell statement cannot read the tables cherry and reports read. Read the landing table behind one instead, for example `raw.xero__invoices` rather than `mapping.invoices`. ## Limits | Limit | What happens | |---|---| | One statement per press | A second statement after a semicolon is refused with a sentence. A trailing semicolon, and comments after it, are fine. A semicolon inside a string, a quoted name, dollar-quoted text or a comment is just text. | | 20,000 characters | Longer text is refused. | | 500 rows back | The **rows** menu chooses 50, 200 or 500. A statement that returns rows is read through a cursor and stops one row past the cap, so a `select *` over millions of rows costs the warehouse only the cap. The result says **Cut off at N rows. Add a LIMIT or a WHERE to see the rest.** | | Statement ceiling | Your warehouse's ceiling applies, which is at most 60 seconds on Basic, 60 seconds on Standard and 5 minutes on Enterprise. The shell never waits longer than 55 seconds, so a longer ceiling is lowered to 55 seconds here. A lower ceiling that you set under **Limits** is kept. | | 300 characters a value | Longer values are cut with an ellipsis. | | Secret-looking columns | A column whose name looks like a secret comes back with its values left empty, and the result says **Withheld because the name looks like a secret** and lists them. See the [SQL reference](https://docs.sanda-os.com.au/reference/sql) for the words that trigger it. | A statement that runs past its ceiling is stopped, with **That ran past its time ceiling and was stopped.** ## Results A statement that returns rows shows them in a table, with **No rows.** when there are none. A statement that changes something shows its command (for example `INSERT` or `CREATE TABLE`) and the number of rows it affected. Values are shown as text: dates as ISO strings, JSON as JSON text. The shell has no download button. To take data out of sanda, see [Take your data with you](https://docs.sanda-os.com.au/warehouse/export). Below the results, **This session** lists your last 20 statements in this tab, newest first, each marked **read**, **write** or **failed**. Press one to put it back in the editor. The list lives in the tab only and is gone when you close it. The audit trail keeps every statement. ## Cost and record - **Cost.** A statement is billed like any other work in the warehouse, as compute. Heavy use of the shell therefore adds to your monthly spend and counts towards a budget. See [Usage, budgets and suspension](https://docs.sanda-os.com.au/warehouse/usage-and-suspension). - **Record.** Each statement is written to the audit trail with your name, the mode, whether it succeeded, how long it took, how many rows it touched and its text (the first 500 characters of a long statement). ## Agents can use it too An assistant connected over MCP with the `sql:write` scope can run the same kind of statement with the `execute_sql` tool. It runs through the same path as the shell, with the same limits, and it must give a reason with each statement. If an owner or admin has asked for approval of risky actions, a read-write statement waits for a person to approve it. The audit trail marks the statement as coming from an agent and names the credential and the reason. See [MCP tools](https://docs.sanda-os.com.au/mcp/tools), [scopes](https://docs.sanda-os.com.au/reference/scopes) and [permissions and approvals](https://docs.sanda-os.com.au/mcp/permissions#approvals). :::links - [SQL reference](https://docs.sanda-os.com.au/reference/sql): The dialect, naming, the columns sanda adds, error messages and worked examples. - [Views and modelled tables](https://docs.sanda-os.com.au/modelling/views): Keep a query as something sanda versions and rebuilds. - [Explore your warehouse](https://docs.sanda-os.com.au/warehouse/explorer): Browse before you write. ::: --- # Optimise > Find what is slowing a warehouse down or costing it storage, read the evidence and the exact statement, and apply the changes you choose. Optimise reads your warehouse's own statistics, together with what the semantic fluid joins and filters on, and lists changes that would make questions faster or give storage back. Each finding shows the figures behind it and the exact statement sanda would run. You choose which to apply. There is no AI in the analysis. The same warehouse gives the same advice twice, so when the list changes, your data or your semantic fluid has changed. Opening the page costs one read of the warehouse's catalogue and nothing from your AI limit. Applying a change runs a statement on your compute and is billed as the seconds it takes. Optimise is for owners and admins, and it is not offered on the sanda learning dataset. ## Open it Go to **Data · Warehouse** and press **Optimise** on a healthy warehouse card. Pick another warehouse from the **warehouse** menu if you have more than one. The header line says how many findings there are, how much storage you could give back, and when the warehouse was read. **Read again** takes a fresh reading, for example after a big sync. ## What it recommends Findings sit in three groups. ### Make questions faster | Finding | Appears when | What it runs | |---|---|---| | **Index** a column | The fluid joins or glues datasets on the column, filters by it as a table's time column, or it is the table's key. The table has at least 50,000 rows and no index that starts with that column. | `create index concurrently if not exists` on the column. The index name ends `_becca_idx`. | | **Analyze** a table | At least 10,000 rows, and at least a tenth of the table, have changed since the planner last looked. | `analyze` on the table. It refreshes the statistics the planner uses to choose a query plan. | | **Vacuum** a table | At least 10,000 rows, and at least a fifth of the table, are dead: deleted or replaced rows that have not been cleaned up. | `vacuum (analyze)` on the table. It makes the dead space reusable and refreshes statistics with it. | The reason on an index finding is written from your fluid, for example that it joins on the column, and from the table, for example how many rows would have to be read to find one without an index. Indexes are proposed for what you ask questions about. A column that is only ever grouped by is left out on purpose, because grouping reads the whole table anyway and an index would cost storage for no gain. ### Give storage back Storage is billed on what the warehouse holds, so an index nobody reads is a line on the bill. | Finding | Appears when | What it runs | |---|---|---| | **Drop the unused index** | The index is at least 10 MB, has not been used at all since the database's statistics began, and its table has been read at least 500 times. Primary keys, unique indexes and indexes that back a constraint are never proposed. | `drop index concurrently if exists` | | **Drop the duplicate index** | Two indexes cover the same columns. The one that carries a promise, such as a key, is kept. | `drop index concurrently if exists` | | **Drop the failed index** | An index build was cancelled or timed out and left an unfinished index behind. PostgreSQL will not use it, and it still holds space. | `drop index concurrently if exists` | | **Rewrite** a table to give back space | A vacuum is due, and rewriting would return at least 500 MB. | `vacuum (full, analyze)`, described below. | A plain vacuum makes dead space reusable but does not return it. Only a rewrite hands the bytes back, and you are billed for those bytes, so sanda offers it, marked **locks the table**. The rewrite locks the table against everything, readers and any sync writing to it, for as long as it takes, and needs room for a second copy while it runs. It is never ticked for you, and pressing **Run** with one selected asks you to confirm. ### Worth knowing **Partitioning** appears when a table is at least 10 GB or 50 million rows. sanda will not do it from a page and marks the finding **sanda will not run this**. Partitioning rewrites a table that syncs write to, with published views hanging off it. The finding says what it would buy, and points to the route that does not stop the world: a partitioned table in `derived`, filled by a [procedure](https://docs.sanda-os.com.au/modelling/procedures) on a [task](https://docs.sanda-os.com.au/modelling/tasks). For a landing table, sanda puts the index on the table where the rows are actually stored, which is the managed sync's copy of it, and names that table on the finding. ## Apply changes :::steps 1. **Read the findings.** Each one shows a title, the table it is about, the reason with its figures, and the statement. The statement is shown in full because it is the thing you are agreeing to. 2. **Choose what to run.** Every finding except a rewrite is ticked when the page opens, because a rewrite locks a table and is never ticked for you. Untick what you do not want. 3. **Press Run.** The button reads **Run 3 statements** (or however many you chose). It reads **Nothing selected** when there is nothing to run. 4. **Read the outcome.** The bar above the list says how many statements ran, how long they took, and that they were billed as that much compute. A statement that failed shows its error underneath. A finding that has gone stale, because autovacuum or someone else got there first, is reported as no longer current: read again. ::: One press applies at most 8 findings. If you need more, run it again for the rest. sanda matches what you ticked against a fresh analysis of the warehouse before it runs anything, so what executes is always a statement sanda generated from what it just read. ## What sanda has run The bottom of the page lists the last ten maintenance statements for the warehouse: the statement, its outcome, how long it took, when, and who asked. These are the same records that a materialized view rebuild writes, and they feed the same compute meter. ## Nothing to do **Nothing worth doing** means the warehouse has no bloat worth a vacuum, no stale statistics, no missing index that your fluid's joins ask for, and no index going unused. Come back after a large sync. ## Cost Analysis is free. Each statement you apply is compute: a `create index` on a large table can take minutes, and it counts towards your compute and towards a [budget](https://docs.sanda-os.com.au/warehouse/usage-and-suspension). A statement that runs longer than a page request allows is stopped, and for an index build PostgreSQL can leave a half-built index behind. Optimise then offers to drop it the next time you read. :::links - [Usage, budgets and suspension](https://docs.sanda-os.com.au/warehouse/usage-and-suspension): How compute is metered and capped. - [Procedures](https://docs.sanda-os.com.au/modelling/procedures): Build a partitioned or pre-aggregated table on a schedule. - [Explore your warehouse](https://docs.sanda-os.com.au/warehouse/explorer): See table sizes and row counts. ::: --- # Usage, budgets and suspension > How storage and compute accrue, what warehouse limits do at 80% and 100%, and what happens when a budget suspends your warehouses. A warehouse is billed for what it holds and what it runs, and sanda gives you three separate ways to keep that under control. They behave differently, and it helps to know which one you are looking at. | | Warehouse limits | Monthly budget | Trial credit | |---|---|---|---| | Set | Per warehouse, from **Limits** on its card, at or below your edition's | For the workspace, in **Settings · Plan & budget** | By sanda, for a trial only | | Measured against | Storage held, compute per day, concurrent queries, statement length | The month's metered spend: storage, compute, sync runs and AI, priced as the invoice prices it | A trial's usage, against its free credit of A$10 | | At the ceiling | Loading pauses and queries are throttled on that warehouse. Reads and reports keep working. | **Every warehouse in the workspace is suspended** | The workspace is **frozen** | | Comes back | By itself, when usage falls under the limit or the limit is raised | When an owner or admin resumes it | When the workspace moves to an edition | ## How usage accrues **Storage** is a level, not a flow. A reading is held until the next one, and each day's figure is the time-weighted average of what the warehouse held that day. The bill is the GB-months held across the month, at A$1.06 per GB-month. **Compute** is a flow. Each reading records the work done since the one before it. That work is the sum of the minutes the warehouse was kept awake for you (each counts at half a vCPU), plus the larger of what your queries executed and what your recorded runs took. Compute is billed at A$0.483 per compute hour used. Both are shown in the console on each warehouse's card: - the **Storage** and **Compute** meters show today against the warehouse's limits; - **Storage held** and **Compute run** show the last 30 days, with a note under them of the dates covered and how many days had no reading; - **Compute run** reads **measuring** until a warehouse has a second reading, because compute is the difference between two readings; - **Run rate** carries today's usage across a month. It is an estimate for planning. The invoice adds up the real readings. A reading is taken when someone opens the **Overview** or **Warehouse** page (a warehouse read in the last 45 seconds is skipped), when an owner or admin presses **Measure now**, and about hourly in the background. See [Billing usage](https://docs.sanda-os.com.au/billing/usage) for how readings become the lines on an invoice. ## Warehouse limits Every warehouse has four limits, shown under **Limits** on its card: | Field | What it limits | |---|---| | **Storage limit (GB)** | How much the warehouse may hold. | | **Compute limit (hours/day)** | The compute the warehouse may use in a day. At the limit, sanda throttles queries and pauses loading (see the states below). | | **Concurrent queries** | How many queries can run at once. | | **Statement ceiling (ms)** | How long one statement may run before it is stopped. | A warehouse starts at your edition's limits: for storage 10 GB on Basic, 100 GB on Standard and 16 TB on Enterprise. A limit is not a price. You pay for what is held and what runs, so setting a limit lower is a way to hold a month's spend down. :::steps 1. **Open Limits.** On **Data · Warehouse**, press **Limits** on the card. Only owners and admins can change them. 2. **Change a field.** Values above your edition's are refused with a sentence naming what your edition allows. 3. **Read the worst case.** Under the form, sanda shows the most a month could cost at those limits, split into storage and compute. 4. **Press Apply limits.** They take effect straight away. sanda does not wait for the next reading. ::: ### What the states mean sanda compares the warehouse's storage and its daily compute pace with its limits after every reading, and shows the worst of the two as the state. | State | Meaning | What sanda does | |---|---|---| | `ok` | **Inside its limits.** | Nothing. | | `warning` | **Past 80% of a limit: worth a look before it bites.** The card shows a `capacity · warning` pill. | Nothing changes, and it emails your alert recipients if capacity alerts are on. | | `over` | **At a limit: syncs are paused and queries are throttled until it comes back under, or the limit moves.** | Loading is paused: syncs and CSV uploads cannot write. Concurrent queries are cut to a quarter of the limit, and queries are limited to 15 seconds. Reads and reports keep working, more slowly. | | `frozen` | **Held by sanda. Talk to us.** | sanda holds it, and a reading does not clear it. | `warning` and `over` clear on their own at the next reading once usage is back under the limit or the limit has moved. Nothing is deleted at any state. ## Budgets and suspension A monthly budget is a ceiling on the workspace's spend. You set it in **Settings · Plan & budget**, in the **Budget & alerts** panel, as **Monthly budget (AUD)**. Leave it empty for no budget and no ceiling. Below 100% the thresholds you tick are reminders by email and nothing more. When the month's metered spend reaches the budget, sanda suspends every warehouse in the workspace. sanda checks budgets regularly, so a workspace can spend a little past its budget before the check catches it. ![The suspension banner shown across the console](https://docs.sanda-os.com.au/media/warehouse-suspended.png "The banner that appears on every page while a workspace is suspended") ### What stops, and what is kept Suspension stops everything that opens a warehouse on your behalf: queries, reports, cherry chat, syncs, CSV uploads, scheduled tasks and rebuilds, sanda search indexing, the OData feed, connected assistants, the Explorer, Modelling and the SQL shell. Each shows a sentence saying the warehouse is suspended, and since when. What is kept: - **Your data.** Nothing is deleted. - **Metering.** Storage keeps being measured and billed, because it is still held. - **The console.** You can still sign in, open the **Warehouse** page and change settings. - **The sanda learning dataset**, which is free and shared. Scheduled tasks are not queued while the workspace is suspended, and they do not replay the ticks they missed: once resumed, a task carries on from its next scheduled time. A run that was queued before the suspension ends with the suspension message and does not count towards the task's failures. The suspension does not lift on its own: not at midnight, not at the start of the month, not when you raise the budget. Someone has to resume it. ### Resume a suspended warehouse :::steps 1. **Open Budget & alerts.** Go to **Settings · Plan & budget**. The panel shows **Warehouse suspended since** the date it happened. 2. **Change what caused it.** Raise or remove the **Monthly budget** and press **Save budget & alerts**, or wait for a new month to begin. sanda only lets you resume once the month's spend is under the budget, because a warehouse resumed while still over would be suspended again at the next check. 3. **Press Resume warehouse.** The button is disabled while the month is at or over the budget, and the panel says why. Only an owner or admin can press it. ::: When it works, the panel says **Resumed. Queries, syncs and scheduled runs are back.** ## A trial's free credit A trial has a free credit. Compute, storage, AI and sync runs draw it down at the same prices an invoice would use. When it is spent, or when the trial reaches its end date, the workspace is **frozen**: the same suspension as above, with the same list of what stops plus AI, and the data is kept. The card shows `frozen · trial`. **Resume does not lift a freeze.** Moving the workspace to an edition does, and it then carries on where it stopped. An owner can ask for that under **Settings · Plan & budget**. See [Trial](https://docs.sanda-os.com.au/billing/trial) and [Editions](https://docs.sanda-os.com.au/billing/editions). ## Get told before it happens In **Settings · Plan & budget**, the **Budget & alerts** panel lets you tick the thresholds you want a reminder at, add up to ten extra recipients (owners and admins always receive alerts), and switch on emails for capacity warnings and for failed scheduled runs. The budget itself is set there too. [Budgets and limits](https://docs.sanda-os.com.au/billing/budgets-and-limits) covers the panel in full. :::links - [Billing usage](https://docs.sanda-os.com.au/billing/usage): How readings become an invoice. - [Budgets and limits](https://docs.sanda-os.com.au/billing/budgets-and-limits): The Budget & alerts panel. - [Optimise](https://docs.sanda-os.com.au/warehouse/optimise): Give storage back and cut wasted compute. ::: --- # Take your data with you > Your warehouse is a standard PostgreSQL database, so you can leave with a full copy. What sanda offers today, and what it does not. Everything you connect, upload or generate in sanda is yours. Your warehouse is a PostgreSQL database, and that is a commitment in sanda's terms, not an implementation detail: it is what makes an export a database anyone can open, and it is what means leaving sanda does not mean leaving your data behind. This page says exactly what you can do to take data out, and where the limits are. ## A full copy of the warehouse The console has no button that exports a whole warehouse, and sanda does not hand out database credentials, so you cannot point a tool like `pg_dump` at it yourself. The route to a full copy is to ask sanda. Email [hello@sanda-os.com.au](mailto:hello@sanda-os.com.au) and ask for a PostgreSQL dump of your workspace. Say which workspace, and which warehouses if you have more than one. - **When you close a workspace, the dump is a commitment.** The terms say that if you ask before the read-only period ends, sanda will hand you a PostgreSQL dump of the whole workspace. It is not a courtesy sanda can withdraw. - **While the workspace is open,** you can ask in the same way. The commitment in the terms is written for a workspace you are closing. A dump is a standard PostgreSQL file, so standard PostgreSQL tooling can restore it into a database you run yourself. Your semantic fluid's definitions are stored by sanda separately from the warehouse's tables, so say in your email if you want those too. ## When you close or delete a workspace An owner can close a workspace from **Settings · Workspace**, at the foot of the tab. Closing makes it read-only: every page still opens, every question can still be asked, and an export can still be taken. After the read-only period, sanda destroys the warehouse and its contents, and backups containing them are overwritten within a further 30 days. An owner can reopen the workspace at any point before then. An owner can instead delete the workspace straight away. That skips the read-only period. The warehouse and its contents are destroyed at once and **cannot be recovered or exported afterwards**. :::danger Deleting a single warehouse from **Data · Warehouse**, or deleting a whole workspace, cannot be undone. If you need the data, ask for your copy before you delete. See [Close or delete a workspace](https://docs.sanda-os.com.au/workspace/close-or-delete) for the read-only period and the steps. ::: ## Getting parts of your data out yourself These need no help from sanda. They export what you have modelled, not raw database files. | To get | Use | |---|---| | Tables and metrics into Excel, Power BI or Power Query | The [OData feed](https://docs.sanda-os.com.au/odata). Nothing is installed, and every refresh reads the warehouse live. | | A report's numbers as a spreadsheet or a PDF | [Reports](https://docs.sanda-os.com.au/reports): sheets can be downloaded as `xlsx`, and a report prints to PDF from your browser. | | Your semantic fluid's definitions as a document | The **Download** and **JSON** links in the **What the agent is told** panel on the semantic fluid page, once you press **Show**. This is your model of the business, not your rows. See [agent context](https://docs.sanda-os.com.au/semantic-fluid/agent-context). | The [SQL shell](https://docs.sanda-os.com.au/warehouse/sql) shows results on screen and has no download button, and it returns at most 500 rows a statement, so it is not an export tool. :::links - [sanda warehouse](https://docs.sanda-os.com.au/warehouse): What is in a warehouse and how it is laid out. - [Close or delete a workspace](https://docs.sanda-os.com.au/workspace/close-or-delete): The read-only period and what closing does. - [OData feed](https://docs.sanda-os.com.au/odata): Pull tables into a spreadsheet. ::: --- # Connections > How data gets into sanda. Connect a system, load a CSV or let an assistant add rows, and see where each one lands in your warehouse. Everything in sanda starts as rows in your warehouse. There are three ways to put them there. | Way in | Use it for | How it stays fresh | Lands as | |---|---|---|---| | [**Connection**](https://docs.sanda-os.com.au/connections/add-a-connection) | A system that keeps producing rows: an accounting package, a shop, a database | On a schedule you set, or when you press **Sync now** | `raw.__` | | [**CSV upload**](https://docs.sanda-os.com.au/connections/upload-a-csv) | A file you already have | It does not. You load it again when it changes | `raw.csv__
` | | **Rows an assistant adds** | A running log you keep by talking to an assistant | Each time the assistant adds a row | New rows in a CSV table you opened for intake | Connections are the main way in, and most of this section is about them. ## What a connection is A connection is a saved link between one source and your warehouse. It records four things: - **The source**, and the details sanda needs to sign in to it. - **What to sync**: which tables the source offers, called streams, and how each one is read (its [sync mode](https://docs.sanda-os.com.au/connections/sync-modes)). - **When to sync**: a [schedule](https://docs.sanda-os.com.au/connections/schedules), or manual. - **Where it lands**: which warehouse the rows go to. sanda's managed sync does the reading. The details you enter go straight to it over TLS, and sanda's own database never stores them. You never paste a warehouse connection string: sanda wires the destination itself. ## Where rows land Your warehouse is a standard PostgreSQL database. Rows from a connection land in its `raw` schema, one table per stream, named `raw.__`. `` is the connection's name in lowercase, with each run of other characters turned into one underscore and the result cut at 40 characters. A connection named `Acme · Xero (AU)` lands its invoices in `raw.acme_xero_au__invoices`. Two connections whose names reduce to the same prefix write to the same tables, so keep names distinct once the punctuation is gone. Landing does not put anything on your semantic map. Once rows have landed, ask cherry to learn from your data, or add the tables yourself. See [Learn from your data](https://docs.sanda-os.com.au/semantic-fluid/learn) and [Explorer](https://docs.sanda-os.com.au/warehouse/explorer). ## The connections list Open **Data · Connections** to see every connection. ![The connections list with two connections: one syncing, one healthy.](https://docs.sanda-os.com.au/media/connections-list.png "One row per connection: source, destination, frequency, last sync, rows and status.") Each row shows the connection's name and source, where it lands, its frequency, when it last synced, the rows it has synced in total and its status. A row that is syncing shows the run's progress in place of the total. The search box filters by name or source. Three buttons sit above the list: - **New connection** starts the [add a connection](https://docs.sanda-os.com.au/connections/add-a-connection) flow. - **Load a CSV** opens the [CSV upload](https://docs.sanda-os.com.au/connections/upload-a-csv). - **Sync times** opens the account schedule, which draws every scheduled sync, task and delivery on one clock. See [Sync schedules](https://docs.sanda-os.com.au/connections/schedules#see-everything-on-one-clock). Under the list, sanda reminds you of the fixed address it syncs from. See [Allowlist sanda's address](https://docs.sanda-os.com.au/connections/allowlist). :::note Owners and admins add, change, run and delete connections. Members can open a connection and see its history, and can load CSVs. Read-only people can look but not change anything. ::: ## Connection statuses Every connection shows one status, on the list and at the top of its own page. | Status | What it means | What to do | |---|---|---| | **Setup incomplete** | The connection is saved but sanda has no sign-in details for the source. You skipped them, or the connector is set up by hand | Open the connection and press **Enter connection details**. For a **Concierge** connector, wait for sanda to get in touch | | **Needs streams** | sanda connected and read the source's schema, and you have not chosen what to sync yet | Open the connection, choose tables under **Streams**, and save | | **Pending** | Ready to run, and no run has finished yet | Press **Sync now**, or wait for the schedule | | **Syncing** | A run is in progress | Watch it on the connection's **Status** tab | | **Healthy** | The last run finished and its rows have landed | Nothing | | **Error** | Connecting failed, a run failed, or rows landed but could not be made readable. The reason is on the connection's **Status** tab | See [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting) | | **Paused** | The connection is not running on its schedule | Contact sanda support if you did not expect this. To stop scheduled runs yourself, set the frequency to **Manual** | ## Connectors offered by arrangement The catalogue lists the systems sanda can connect to. Most have a setup form you fill in yourself. A few are set up with you instead: they carry a **Concierge** badge, and there is nothing to fill in beyond a name and a frequency. A sanda engineer wires the source and confirms the first load with you. - [MYOB](https://docs.sanda-os.com.au/connectors/myob) - [Something else](https://docs.sanda-os.com.au/connectors/other) **Something else** is for a system that is not in the catalogue. Create the connection, describe what you need, and sanda takes it from there. See the full [connector catalogue](https://docs.sanda-os.com.au/connectors) for every source sanda offers. ## Rows an assistant adds An owner or admin can open a CSV table for intake. A connected assistant holding the intake permission can then add rows to that table, matching its columns, and can do nothing else to it. It is meant for a log someone keeps by talking: a daily log, a form, a running list. You open a table for intake when you [load the CSV](https://docs.sanda-os.com.au/connections/upload-a-csv#let-an-assistant-add-rows), and close it in the **Intake tables** panel on the **Settings** page. See [Agent tokens](https://docs.sanda-os.com.au/mcp/agent-tokens) and [MCP tools](https://docs.sanda-os.com.au/mcp/tools) for the assistant's side. ## Keep reading :::links - [Add a connection](https://docs.sanda-os.com.au/connections/add-a-connection): Choose a source, sign in, name it and choose what to sync. - [Allowlist sanda's address](https://docs.sanda-os.com.au/connections/allowlist): Let sanda through the firewall of a database you host. - [Load a CSV](https://docs.sanda-os.com.au/connections/upload-a-csv): Put a file straight into your warehouse. - [Choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams): Streams, cursors and primary keys. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs, and how time zones are handled. - [Monitor syncs](https://docs.sanda-os.com.au/connections/monitor-syncs): Follow a run and read the history. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What each error means and how to fix it. ::: --- # Add a connection > Connect a system to your warehouse. Choose the source, enter its details, name it and set a frequency, then choose what to sync and run the first sync. A new connection takes four steps: define the source, connect it, name it and pick a frequency, then choose what to sync. Nothing moves until the last step is saved, so you can look at what a source offers before a single row lands. ## Before you start - **A warehouse that is ready.** Every connection lands its rows in a warehouse. If none is ready, the first step shows **A warehouse comes first** with a **Provision a warehouse** button. See [Provision your warehouse](https://docs.sanda-os.com.au/warehouse/provision). - **A way in to the source.** For a database, a login that can read the tables you want. For a SaaS system, an API key or app credentials. The setup guide beside the form says what to create. - **A clear network path, for a database you host.** If the database sits behind a firewall, [allow sanda's address](https://docs.sanda-os.com.au/connections/allowlist) first. ## Add the connection :::steps 1. **Open Connections.** Go to **Data · Connections** and press **New connection**. The steps run along the top: **Define source**, **Connect**, **Configure**, and **Streams** once you have entered the source's details. 2. **Define the source.** Search the catalogue, or pick a card. The search box reads **Search sources · Postgres, Xero, Stripe…** and matches a connector's name. Popular sources are listed first under **Popular**, then everything else under **All sources**. Each card carries a badge for how the connector is supported, such as **Certified**. **Concierge** marks one a sanda engineer sets up with you, with no form to fill in. If the system you need is not listed, pick **Something else**. Got a one-off file instead? **Load a CSV** on this step goes straight to the [CSV upload](https://docs.sanda-os.com.au/connections/upload-a-csv), with no connector and no schedule. 3. **Connect it.** The form for the source is on the left, and its **Setup guide** is beside it, so you do not need another tab. Fill in the details, then press **Continue**. The form is different for every source, because each connector describes its own fields. See [The connection form](#the-connection-form) below. If you do not have the details to hand, press **Skip for now** and add them later from the connection's page. 4. **Name it and choose a frequency.** The **Configure the connection** step asks for: - **Connection name.** What you will call it in sanda, for example `Acme · Xero (AU)`. The name also decides where the rows land: the line below the fields shows `raw.__*` as you type. - **Sync frequency.** **Manual**, **Hourly**, **Daily** or **Weekly**. A new connection starts on **Manual**, so nothing runs until you press **Sync now** or pick a schedule. See [Sync schedules](https://docs.sanda-os.com.au/connections/schedules). - **Destination warehouse.** Shown when you have a warehouse ready. With one warehouse there is only one choice. 5. **Read the schema.** Press **Read schema**. This is where sanda signs in to the source with your details and reads which tables it offers. It is the test: if a password is wrong or a firewall is closed, this step fails and says why. It can take a minute. If it fails, the connection is saved anyway. Fix the cause and press **Retry connecting**, or press **Open connection** and continue from its page. See [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting). 6. **Choose what to sync.** sanda lists every table the source offers. Tick the ones you want, and pick how each one is read. Press the save button when you are done. It shows the count, for example **Save 5 streams**. See [Choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). Saving is what turns the plan into a working pipeline. Until you save, the connection reads **Needs streams**. 7. **Run the first sync.** Saving takes you to the connection's page, where the status reads **Pending**. Press **Sync now**, or leave it and let its schedule start the run. The **Status** tab shows the run as it happens. See [Monitor syncs](https://docs.sanda-os.com.au/connections/monitor-syncs). ::: ## The connection form The fields come from the connector, so a database asks for a host and a login while a SaaS system asks for a key or an app credential. A few rules hold for all of them. - **Required fields come first.** Optional ones are marked **(optional)**. - **Secrets are masked.** Passwords, keys and tokens are shown as dots as you type. - **Less common options wait under Advanced settings.** They carry sensible defaults, and sanda sends those defaults whether or not you open the section. It opens itself if something in it still needs an answer. - **A choice can open more fields.** An authentication method or an update method changes which fields appear beneath it. - **Pasted files go in as text.** A field that wants a whole JSON document, such as a service-account key, takes it pasted in. - **The button tells you what is missing.** **Continue** stays disabled until every required field is filled, and **Still needed** lists the ones that are not. For a database that offers an **Update method**, the form opens on the option that needs nothing configured at the source: a cursor you choose per table. Reading changes from the database's own change log needs setup on your database first (on PostgreSQL, a replication slot and a publication), so choose it only when that is in place. Your details are sent straight to sanda's managed sync over TLS and are never written to sanda's database. :::tip Enter the host on its own Enter the hostname or IP address alone in the host field, and the port in the port field. A pasted connection string, `host:port` in one field, or an address that only exists on your own network is refused before sanda tries to connect, with a message that says what to put where. See [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting#host-errors-before-connecting). ::: ## If you skip the details **Skip for now** on the **Connect** step saves the connection without signing in to anything. The **Configure** step marks it **credentials later**, and it appears in the list as **Setup incomplete**. To finish it: :::steps 1. **Open the connection.** Select it in **Data · Connections**. 2. **Enter the details.** On the **Status** tab, press **Enter connection details**, fill in the form and press **Connect**. 3. **Choose what to sync.** The **Streams** panel opens with **Choose what to sync: saving here is what starts the pipeline.** Tick tables and save. ::: Connectors marked **Concierge** work the same way, without the form: you create the connection with a name and a frequency, and sanda gets in touch to wire it up. ## After the first sync Rows land in `raw.__`, one table per stream. Nothing is added to your semantic map by itself. When rows have landed, the connection's page offers **Learn from my data**, which asks cherry to read the new tables and propose metrics, dimensions and relationships for you to accept. See [Learn from your data](https://docs.sanda-os.com.au/semantic-fluid/learn). :::links - [Allowlist sanda's address](https://docs.sanda-os.com.au/connections/allowlist): Open your firewall to sanda before you connect a database. - [Choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams): Pick tables and how each one is read. - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): Full refresh or incremental, and how to choose. - [Manage a connection](https://docs.sanda-os.com.au/connections/manage): Change the frequency, run a sync, or delete it. ::: --- # Allowlist sanda's address > Every sync reaches your systems from one fixed IP address. Allow it through your firewall before you connect a database or a locked-down store. Every sync reaches your systems from one fixed address: ```text title="sanda's sync address" 203.76.224.154 ``` If the system you are connecting sits behind a firewall, a cloud security group or an IP allowlist, add this address before you connect. Otherwise sanda cannot get through, and the connection fails at the first step. The address is the same for every workspace and every connection. sanda only ever reads from your systems, and it connects from this address for setup checks as well as for syncs. ## Where to find it in the console You can copy the address from three places in the console, so you never need to leave the connection flow to find it. - **The setup guide** beside every connector's form. It shows the address in a callout, headed **Allowlist sanda before connecting** for sources that dial into your own infrastructure and **Behind a firewall or IP allowlist?** for the rest. - **The list of connections.** A line under the table reads **Connecting something behind a firewall? sanda syncs from one fixed address: allowlist** followed by the address. - **A connection's Settings tab.** The **Network** panel, described as "For sources behind a firewall, VPC rule or IP allowlist", shows it too. ## Which sources need it You need to allow the address wherever the source lets you restrict who can connect. | Source | What to allow | |---|---| | PostgreSQL | The address, on your database port (5432 by default). Set it in your cloud provider's network rules, in `pg_hba.conf`, or both | | MySQL and MariaDB | The address, on your database port (3306 by default), in your provider's network rules or security group | | SQL Server | The address, on port 1433 or your instance's port. On Azure SQL, add it under the server's networking rules | | S3 | Only if the bucket policy restricts access by IP range. Add the address to that range | | SaaS systems such as Xero or Stripe | Usually nothing. Some sit behind gateways that restrict by IP, in which case add the address there | Database connectors show the prominent version of the callout because sanda dials straight into your infrastructure. For the exact steps to create a read-only login and open the port, follow the setup guide beside the connector's form, or the page for your connector in the [catalogue](https://docs.sanda-os.com.au/connectors). ## Do it in this order :::steps 1. **Allow the address first.** Add it to the firewall or security group that protects the database. 2. **Add the connection.** Fill in the form and press **Read schema**. See [Add a connection](https://docs.sanda-os.com.au/connections/add-a-connection). 3. **Check the result.** If sanda cannot get through, the step fails with a message about the network. See [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting#the-connection-is-refused-or-times-out). ::: ## Hosts sanda can reach sanda connects from the internet, so a database host has to be reachable from the internet. Before it tries, sanda checks the host you entered and refuses the ones that can never work: - `localhost` and other addresses that mean "this computer". - Private network addresses, such as `10.x.x.x`, `172.16.x.x` to `172.31.x.x` and `192.168.x.x`. - Names with no domain, and names ending in `.local`, `.internal`, `.lan` or another suffix that only resolves inside a private network. - A name that does not exist on the public internet, or one that resolves to a private address. If your database is only reachable inside your network, give it a public address and lock that address down to sanda's, or route it through something that is reachable from outside. Some database connectors also offer an SSH tunnel option under **Advanced settings**. The tunnel host is held to the same rule: it has to be reachable from the internet, and it needs sanda's address allowed. ## Database encryption Your sign-in details reach sanda's managed sync over TLS, and sanda never writes them to its own database. For the connection from sanda to your database, the setup guides recommend the strongest option your server supports: - **PostgreSQL:** keep SSL on `require` unless the server cannot do TLS at all. - **MySQL:** prefer SSL where the server supports it. - **SQL Server:** leave encryption on unless the server predates it. The SSL choices on offer come from the connector's own form, so they differ by source. :::note Allowing the address does not give sanda any more access than the login you create for it. Give sanda a read-only login of its own, so its access is easy to see, audit and revoke. ::: ## If the address ever changes The console always shows the current address. If a connection that used to work starts failing with a network error, compare the address in the setup guide with the one in your firewall rules. :::links - [Add a connection](https://docs.sanda-os.com.au/connections/add-a-connection): The full flow, step by step. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): Network errors and what they mean. - [Connector catalogue](https://docs.sanda-os.com.au/connectors): Every source, with its own setup notes. ::: --- # Load a CSV > Put a file straight into your warehouse with no connector and no schedule. sanda reads the header, types every column and lands the rows in a table. A CSV upload is the quickest way to get a file into your warehouse: a budget, a price list, a lookup table, an export from a system sanda does not connect to. There is no connector and no schedule. sanda reads the header row, works out a type for each column and lands the rows as a table in your warehouse's `raw` schema. Owners, admins and members can load a CSV. Your warehouse has to be ready first. See [Provision your warehouse](https://docs.sanda-os.com.au/warehouse/provision). ## Load a file :::steps 1. **Open the upload page.** Go to **Data · Connections** and press **Load a CSV**. You can also reach it from the first step of **New connection**. 2. **Choose the file.** Drop it on the panel that reads **Drop a CSV here, or click to choose**, or click to browse. The panel notes what is accepted: **Comma, semicolon or tab separated · header row required · up to 8 MB**. 3. **Name the table.** **Table name** starts as the file's name without its extension. The hint underneath shows where the rows will land, for example **Lands as raw.csv__monthly_budget in your warehouse.** 4. **Say what to do if the table exists.** **If the table already exists** offers **Replace it** and **Append to it**. See [Replace or append](#replace-or-append). 5. **Choose a destination.** If you have more than one warehouse ready, **Destination warehouse** appears. With one, there is nothing to choose. 6. **Press Load it.** sanda reads the file, types it and lands the rows. When it finishes, the page reports how many rows landed and lists the columns with their types. ::: The result page offers **Load another file**, and **Learn from my data**, which asks cherry to read the new table and propose what it means for you to accept. See [Learn from your data](https://docs.sanda-os.com.au/semantic-fluid/learn). ## What the file has to look like | | Limit | |---|---| | File types | The file chooser offers `.csv`, `.tsv` and `.txt` | | Size | 8 MB | | Rows | 250,000 | | Columns | 250 | | Header | The first row must name the columns | | Delimiter | Comma, semicolon or tab. sanda reads it from the header row | | Encoding | UTF-8 | A few things that real files do are handled for you. Quoted fields can contain the delimiter, line breaks and doubled quotes. Both Unix and Windows line endings are read. A byte order mark at the start is ignored. Rows shorter than the header are padded with empty cells. Completely blank lines are skipped. For a file over the limits, use the [S3](https://docs.sanda-os.com.au/connectors/s3) or [SFTP](https://docs.sanda-os.com.au/connectors/sftp) connector, which read files from storage on a schedule. ## Column names sanda cleans every header into a name that is safe to query. It turns letters into lowercase, replaces each run of characters other than `a` to `z` and `0` to `9` with one underscore, and trims underscores from the ends. It does not keep the original. | In the file | Lands as | |---|---| | `Revenue ($)` | `revenue` | | `2024 total` | `c_2024_total` | | A blank header | `column_5`, numbered by its position | | A second `date` beside the first | `date_2` | Accented and other non-English letters are replaced too, so `Ünïcode` becomes `n_code`. Columns are limited to 58 characters. Rename anything important in the file before you load it. ## How types are chosen sanda reads the whole file, not a sample, and gives each column the strongest type that every value in it supports. One value that does not fit makes the column text, so nothing is ever lost and a late surprise cannot fail a load halfway through. | If every non-empty value is | The column is | |---|---| | A whole number of up to 18 digits | `bigint` | | A number, with an optional decimal point or exponent | `numeric` | | `true` or `false`, in any case | `boolean` | | A date written `2026-09-30` | `date` | | Timestamps such as `2026-09-30 14:30` or `2026-09-30T14:30:00Z`, alone or mixed with plain dates | `timestamptz` | | Anything else | `text` | Empty cells land as nulls, not as empty strings. A column with nothing in it at all is text. The rules are strict, so a few common formats stay as text: - Numbers with thousands separators or currency symbols, such as `1,200` or `$12.50`. - Dates written `30/09/2026` or `30 Sep 2026`. - `yes` and `no`. A column of `1` and `0` becomes `bigint`, not `boolean`. And one goes the other way: a column of values with leading zeros, such as postcodes or account numbers, is read as whole numbers, so `0800` lands as `800`. If the zeros matter, put a character in front of every value so the column is read as text, or load the table through an assistant, which can declare a column's type. If you want a number or a date, write it plainly in the file: `1200`, `12.50`, `2026-09-30`. You can also cast text columns in a [view](https://docs.sanda-os.com.au/modelling/views) after loading. ## Where the table lands The table is named `raw.csv__`. sanda builds the name from the **Table name** you gave it: lowercase, non-alphanumeric characters turned into underscores, a `.csv`, `.tsv` or `.txt` ending removed, and cut at 40 characters. A name that starts with a digit gets `t_` in front, so `2026 budget` lands as `raw.csv__t_2026_budget`. The `csv__` prefix keeps hand-loaded tables apart from tables a connection lands, so a CSV can never collide with a sync's table. Every table gets one extra column of sanda's own, `_becca_loaded_at`, holding when the row was loaded. Like the other loading columns, it stays out of your semantic fluid. If your file has a column with that name, sanda renames it `becca_loaded_at`. A CSV table counts toward your warehouse's storage in the same way a synced table does, and the storage limit applies to it. The load is recorded in **Overview · Recent activity** as **CSV loaded into warehouse**. ## Replace or append **If the table already exists** decides what happens when a table with that name is already there. - **Replace it** swaps the table's rows for the file's. The old rows are gone. The whole load is one transaction, so a file that fails to load leaves the existing table as it was. - **Append to it** adds the file's rows to what is there. The columns must match the table's: the same names, none missing and none extra. If they do not, nothing loads and the message names the differences, for example **These rows don't match raw.csv__budget (new columns region; missing owner). Load with Replace, or use another table name.** Append also creates the table when it does not exist yet. Append casts each value into the type the existing column already has. A value that cannot be cast, such as text in a `bigint` column, fails the whole load. ### Replacing a table that is in use A table nothing reads yet is dropped and made again in the file's shape. Once something reads it, such as your semantic map or a view of your own under **Data · Modelling**, sanda keeps the table and replaces its rows in place, so the map and your views go on working. If the new file's columns differ from the table's: - **A column the file adds** goes on the end of the table. The map keeps showing the columns it had, so a new column is in the table but not yet on the map. - **A column the file no longer has** comes off the table, and **a column whose type changed** takes its new type. If the map reads that column, sanda rebuilds the map's view over the new columns for you. Anything on the map built on a column that has gone stops working until you change it. - **A view of your own that reads a column the file drops or retypes** stops the load, because sanda never removes your views. Nothing changes, and the message names the view, for example **derived.budget_by_region reads owner of raw.csv__budget, and this file leaves that column out or gives it a different type. Keep it as it was, or change that view under Data · Modelling, then load again.** If sanda cannot rebuild the map's view after a replace, the rows still land and the page says so: questions on the table fail until the view is rebuilt. Load the file again, or ask sanda support. ## Let an assistant add rows The checkbox **Open it for intake** turns the table into a place an assistant can keep adding to. A connected assistant holding the intake permission may then add rows to this table, matching its columns, and do nothing else to it: it cannot replace the table, delete rows or touch any other table. It suits a log someone keeps by talking, such as a daily habit list, a form or a running list of jobs. Only owners and admins can open a table for intake. If a member ticks the box, the rows still land, and the page says only an owner or admin can open the table. Close a table for intake at any time in the **Intake tables** panel, under **Settings · Integrations**. Closing it keeps the rows and stops the assistant adding more. Someone with the right permission can also land a table, and add to one, by talking to an assistant that is connected to sanda. See [Agent tokens](https://docs.sanda-os.com.au/mcp/agent-tokens) and [MCP tools](https://docs.sanda-os.com.au/mcp/tools). ## When a load fails | Message | What to do | |---|---| | The file is empty. | Check you chose the right file | | The first row must name the columns. | Add a header row | | There are no data rows under the header. | The file has a header and nothing else | | That's 300 columns, and uploads cap at 250. | Split the file or drop columns | | That's 300,000 rows, and uploads cap at 250,000. For bigger drops, use the S3 or SFTP connector. | Use a connector, or split the file | | Couldn't parse the CSV: a quoted field never closes, so the file may be truncated. | Reopen the file and check it was saved whole | | This warehouse is at its storage ceiling, so ingest is paused. | Raise the storage limit or clear space. See [Usage and suspension](https://docs.sanda-os.com.au/warehouse/usage-and-suspension) | | derived.… reads … of raw.csv__…, and this file leaves that column out or gives it a different type. | A view of yours reads that column. See [Replacing a table that is in use](#replacing-a-table-that-is-in-use) | | Loading failed: … | The rest of the message says why | For anything else, see [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting). :::links - [Learn from your data](https://docs.sanda-os.com.au/semantic-fluid/learn): Have cherry propose what the new table means. - [Explorer](https://docs.sanda-os.com.au/warehouse/explorer): Look at the table and its columns. - [Add a connection](https://docs.sanda-os.com.au/connections/add-a-connection): For a system that keeps producing rows. ::: --- # Choose what to sync > Pick the tables a connection lands, how each one is read, and the cursor and primary key that incremental and deduplicated modes need. A source offers tables, and you decide which of them land in your warehouse. sanda calls them streams: a table in a database, a tab in a spreadsheet, a kind of record in a SaaS system such as invoices or contacts. Each stream you turn on lands as one table, `raw.__`. You choose streams once, at the last step of [adding a connection](https://docs.sanda-os.com.au/connections/add-a-connection), and you can change the choice at any time. ## Open the picker - **For a new connection**, the picker is the last step of the flow, headed **Choose what to sync**. - **For a connection that already exists**, open it, go to the **Status** tab and press **Edit streams** in the **Streams** panel. sanda asks the source what it offers each time the picker opens, and the message **Asking your source what it offers: this runs a live check and can take a minute** shows while it waits. During the new-connection flow it reuses the answer it already has, so it does not ask twice. On a new connection every stream starts on. On an existing one the picker shows what you saved last time. Nothing changes until you press the save button. ![The stream picker, with five streams, a mode for each and a bar to set a mode, cursor or key on all of them.](https://docs.sanda-os.com.au/media/stream-picker.png "One row per stream: a tick, its mode, and a cursor and primary key where the mode needs one.") ## The picker, row by row | Part | What it does | |---|---| | **Search N streams** | Filters the list by name. Useful for a source with hundreds of tables | | **N of M on** | How many streams are ticked | | **All** and **None** | Tick or untick every stream you can see. With a search active they act only on the matches | | Tick box | Turns a stream on or off. An off stream stays at the source and is not sent | | **mode** | How the stream is read on each run. See [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes) | | **cursor** | The column that tells sanda which rows are new or changed. Only for incremental modes | | **primary key** | The column that identifies a row. Only for deduplicated modes | A stream that is off keeps its row but goes quiet, so ticking it back on does not move the list. ## Choose a mode The **mode** list offers the sync modes the source supports for that table. A stream that supports only one shows it as fixed text, and hovering explains that it is the only mode the source offers for the table. A stream you have not chosen for starts on **Full refresh · Overwrite**. It is the one mode every table supports that needs nothing answered, and a table replaced whole on every run is the easiest to reason about. Move a stream to an incremental mode when the table is big enough to be worth it. [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes) explains the five modes and how to choose between them. ## Cursors and primary keys Two of the five modes need extra information, and the picker asks for it in the same row. - **A cursor** is a column that only moves forward, such as `updated_at` or an incrementing id. An incremental mode reads only the rows whose cursor has moved since the last run. Choose the cursor from the list. If the source suggests one, it is marked **suggested**. If the source has already decided the cursor for a table, the cell reads **source-defined** and you have nothing to pick. - **A primary key** is the column that uniquely identifies a row. A deduplicated mode uses it to keep one row per key. If the source declares the key, the cell shows it and you have nothing to pick. Otherwise choose a column. A cell shows a dash when the stream's mode does not use it. The save button stays disabled until every stream that is on has what its mode needs. Beside it, the picker says what is missing, for example **1 stream needs a cursor and 2 streams need a primary key.** It also refuses to save with nothing ticked: **Leave at least one stream on.** To stop a connection altogether, [set it to manual or delete it](https://docs.sanda-os.com.au/connections/manage) rather than turning everything off. ## Set many streams at once A source with sixty tables is not sixty decisions. The bar above the list, headed **Set on all N on** (or **shown**, when a search is active), sets a mode, a cursor or a primary key on every ticked stream in view. 1. Pick a value in any of the three lists: **sync mode**, **cursor** or **primary key**. Each starts on **keep each**, which changes nothing. The cursor and key lists offer the columns that most streams share, with the number of streams that have each in brackets, so `updated_at (40)` is one pick. 2. Press **Apply**. sanda applies each value only where it can. A stream that does not offer the mode, does not have the column, or has a cursor or key the source already decided keeps what it had. Afterwards a sentence says what happened, for example **Set on 38 of 40 streams. 2 could not take all of it (a mode they do not offer, or a column they do not have) and kept their own.** A cursor or key is set only on streams whose mode uses one. ## Whole tables, not columns You choose tables, not columns. sanda lands every column of a table you turn on, and a column added at the source later arrives with the next sync. This is deliberate. Leaving a column out of the sync means a re-sync to get it back, and the column nobody reports on today is often the one next quarter's question needs. Narrowing is cheap where it belongs: when you add a table to the semantic fluid, you choose which of its columns the fluid exposes, and you can change that whenever you like. See [Tables](https://docs.sanda-os.com.au/semantic-fluid/tables). One exception is made for a source that was itself filled by a loading pipeline. Such a table carries loading columns of its own, which would collide with the ones sanda adds. sanda leaves them behind, and the row says so with a note such as **−4 loading columns**. ## Save and what happens next Press the save button. It shows the count of streams that are on, for example **Save 12 streams**. - **On a new connection**, saving creates the pipeline. The status changes from **Needs streams** to **Pending**, and **Sync now** appears. Nothing has run yet. You choose whether to run the first sync yourself or wait for its schedule. - **On an existing connection**, the change applies from the next sync. The list on the **Status** tab then shows each selected stream with its mode, and its cursor and primary key where it has them. The mode appears in short form: **full refresh**, **full refresh · dedup**, **full refresh · append**, **incremental** and **incremental · dedup**. If you turn a stream off, sanda stops syncing it. The table it landed earlier stays in your warehouse and is no longer refreshed. Turning a stream on that was off adds a table the next time the connection runs. ## Get a recommendation Once a connection is set up, the **Streams** panel has a **What should I sync?** button. It asks sanda for advice on a source with many streams, given what you told sanda when you signed up. The answer lists the streams worth landing with a priority (**core**, **useful** or **optional**), a suggested way to read each and a reason, and names the ones to leave off. It is advice only. Nothing changes until you tick streams and save. Like other AI features, it counts toward your workspace's AI use. See [Usage](https://docs.sanda-os.com.au/billing/usage). :::tip Start small, then widen Begin with the handful of tables you need for the first report, on the default mode. Widening later costs one edit and a sync. Landing all sixty tables of a source on day one costs storage and run time for tables nobody has looked at. ::: :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): Full refresh, incremental, and how to choose. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often the connection runs. - [Learn from your data](https://docs.sanda-os.com.au/semantic-fluid/learn): Have cherry read the tables once they land. ::: --- # Sync modes > How sanda reads a source on each run and writes it to your warehouse, and how to pick the right mode for each table. A sync mode says what happens to one table on every run. It is a pair of decisions: - **How sanda reads the source.** Read the whole table, or read only the rows that are new or changed since the last run. - **How sanda writes the warehouse table.** Replace what is there, add to it, or keep one row per key. There are five modes, and you choose one per stream. This page explains the idea and helps you choose. The [sync modes reference](https://docs.sanda-os.com.au/reference/sync-modes) lists each mode precisely. ## Full refresh and incremental The two families answer the first decision. **Full refresh** reads the whole table on every run. It needs no setup, and the result always reflects the source as it is now. The cost is that every run reads and writes everything, so a big table takes longer each time it grows. **Incremental** reads only the rows whose cursor has moved since the last run. The first run reads everything, as any first run must, and later runs move only what changed, which is fast even for a very large table. In return an incremental mode needs a cursor: a column that moves forward whenever a row is created or changed, such as `updated_at`. It cannot see a row that has been deleted at the source, because a deleted row has no cursor to read. ## The five modes | Mode | Reads | Writes | Needs | |---|---|---|---| | **Full refresh · Overwrite** | The whole table | Replaces the table | Nothing | | **Full refresh · Overwrite + Deduped** | The whole table | Replaces the table, one row per key | A primary key | | **Full refresh · Append** | The whole table | Adds a full copy beside the earlier ones | Nothing | | **Incremental · Append** | New and changed rows | Adds them | A cursor | | **Incremental · Append + Deduped** | New and changed rows | Keeps the latest version of each key | A cursor and a primary key | The default is **Full refresh · Overwrite**. It is the one mode every table supports that needs nothing answered, and a table that is replaced whole on every run is the easiest to reason about. You move a table to an incremental mode when it is big enough to be worth the extra setup. The picker offers only the modes a source supports for each table. Some tables cannot be read incrementally, because the source gives no way to tell what changed. ## How to choose Start from what the table is. | The table is | Use | Because | |---|---|---| | Small and changes in place: a chart of accounts, a contact list, a price list | **Full refresh · Overwrite** | It always matches the source, and deleted rows disappear. Reading it whole is cheap | | Large, with rows that are created and then edited: invoices, orders, tickets | **Incremental · Append + Deduped** | Each run moves only what changed, and the table holds the latest version of each row | | Large and only ever grows: events, log lines, transactions that never change | **Incremental · Append** | Each run adds the new rows and nothing is rewritten | | Something whose past you want to keep: a stock level, a balance, a price | **Full refresh · Append** | Each run adds a snapshot, so you can see how the table looked on any run | | Whole, but the source may hand back the same key more than once | **Full refresh · Overwrite + Deduped** | You get the whole table with one row per key | A few things settle most choices. - **If you are unsure, leave it on the default.** You can change a stream's mode at any time from **Edit streams**. The change applies from the next sync. - **Check for a cursor you trust.** An incremental mode is only as good as its cursor. Pick a column that is set when a row is created and updated whenever it changes. A cursor that is not updated on every edit means those edits are missed. - **Deletes need a full refresh.** A cursor cannot see a row that has been removed, so an incremental table keeps rows that no longer exist at the source. A source read by its database's change log is the exception: it reports deletions as a flag on the row instead of removing it. If deleted rows must disappear, use a full refresh mode that replaces the table. - **Snapshots grow.** **Full refresh · Append** adds the whole table on every run. On a large table that adds up fast, and it counts toward your storage. ## What each mode does to history and deletes | Mode | A row edited at the source | A row deleted at the source | |---|---|---| | **Full refresh · Overwrite** | Shows its new value | Disappears at the next run | | **Full refresh · Overwrite + Deduped** | Shows its new value | Disappears at the next run | | **Full refresh · Append** | Each run holds the value it had then | The earlier copies still hold it. It is missing from later copies | | **Incremental · Append** | Lands again as a new row. The earlier version stays | Stays in the table | | **Incremental · Append + Deduped** | The table holds its latest version | Stays in the table | ## Things worth knowing - **Full refresh replaces a table in one step.** For a short time after a run that replaces a table, before sanda has attached the new copy, a question over the table returns a message that it has no rows attached right now, rather than an empty answer. It clears when sanda next checks the run. See [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting#a-table-has-no-rows-attached-right-now). - **Each successful run is metered as one run,** however many rows it moves. A full refresh does more work in your warehouse than an incremental run on the same table, and takes longer. - **Modes are per stream.** One connection can mix them: a small lookup table on the default and a large fact table on incremental. - **Connections created before all five modes were offered keep what they had.** A full refresh overwrote, and an incremental deduplicated when the table had a key. :::links - [Sync modes reference](https://docs.sanda-os.com.au/reference/sync-modes): Each mode in full, with what it needs. - [Choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams): Where you set a mode, a cursor and a primary key. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often each run happens. ::: --- # Sync schedules > How often a connection runs, how the time grid and time zones work, and how to see every scheduled sync, task and delivery on one clock. A connection's schedule says when sanda runs it without being asked. You choose it when you add the connection and can change it whenever you like. Whatever the schedule, you can always run a connection yourself with **Sync now**. ## Frequency options The **Sync frequency** control offers four choices. | Choice | What it does | |---|---| | **Manual** | Runs only when you press **Sync now**. A new connection starts here, because a schedule is a claim about a time zone and the safest default is to do nothing until a person chooses | | **Hourly** | Runs on a fixed interval. Pick **Every hour**, **Every 6 hours** or **Every 12 hours** | | **Daily** | Runs every day at a time you choose, in a time zone you choose | | **Weekly** | Runs on the days you tick (any of **Sun** to **Sat**) at a time you choose, in a time zone you choose | Under the control, a sentence states what you have picked, for example **Every Mon, Fri at 7:00 AM (Australia/Sydney)**, and a line beneath it gives the next run: **Next run: Mon 6 Oct, 7:00 AM (Australia/Sydney).** Choose at least one day for **Weekly**. The picker shows **Pick at least one day.** and will not save until you do. The hourly choices are not tied to a clock in your city. **Every hour** runs at the top of each hour, **Every 6 hours** at 00:00, 06:00, 12:00 and 18:00 UTC, and **Every 12 hours** at 00:00 and 12:00 UTC. They stay on UTC on purpose: an interval has no local time to keep, and read on a wall clock it would stretch or shrink on the two nights a year the clocks change. ## The time grid Schedules run on a 15 minute grid, so a sync can start at :00, :15, :30 or :45 and at no other minute. The **Time** list offers exactly those times. A start you might have typed as 9:07 is not offered, and a schedule set some other way is refused with a message that says so. The reason is that sanda's scheduler wakes on the quarter hour, and one rule about when things may be scheduled is easier to keep in your head than two. The same grid applies to tasks, index builds and report deliveries. ## Time zones and daylight saving For **Daily** and **Weekly** you choose a **Time** and a **Timezone**. The time zone defaults to the one your browser is set to. The list opens with a short set of common zones, such as `Australia/Sydney`, `Australia/Perth` and `Pacific/Auckland`, then `UTC`, and continues with every other zone. sanda stores a daily or weekly schedule as the wall clock you chose, with its zone beside it, and runs it in that zone. The consequence is that **7:00 AM stays 7:00 AM** when the clocks change. A Sydney schedule for 7:00 AM runs at 20:00 UTC in summer time and 21:00 UTC in winter time, and you never have to think about it. Two edge cases are handled so that a run is never missed or doubled: - **A time that does not exist.** When the clocks go forward, a time such as 2:30 AM never happens that day. The sync runs at the first valid moment after the gap, once, and the next day runs at 2:30 as usual. - **A time that happens twice.** When the clocks go back, a time such as 2:30 AM occurs twice. The sync runs once, at the first. For a zone with daylight saving, the **Next run** line under the picker adds **It stays at 7:00 AM local time when the clocks change.** ### Older schedules A daily or weekly schedule saved before sanda kept local time is stored as a fixed UTC time, so it moves by an hour against your clock when the clocks change. Its page says so: **This connection was scheduled before sanda kept local time, so it runs at a fixed UTC time and moves by an hour against Australia/Sydney when the clocks change.** Press **Keep it at 7:00 AM local time** to switch it to the wall clock. Picking a new daily or weekly time does the same. A schedule that was set some other way than this picker shows as its raw text under **This connection's current schedule is …, set outside this picker.** It keeps running exactly as it is until you change it. **Replace with a new schedule** puts you back on the picker. ## Set or change a schedule You set the schedule while [adding a connection](https://docs.sanda-os.com.au/connections/add-a-connection), at the **Configure the connection** step. To change it later: :::steps 1. **Open the connection.** Select it in **Data · Connections**. 2. **Go to Settings.** The **Schedule** panel shows the current frequency. 3. **Pick a new frequency.** Choose **Manual**, **Hourly**, **Daily** or **Weekly**, then the interval, days, time and time zone as needed. 4. **Press Save schedule.** The button stays disabled until something has changed. **Saved.** appears beside it when it has gone through. ::: ![The Schedule panel on a connection's Settings tab, set to weekly on Monday.](https://docs.sanda-os.com.au/media/schedule-settings.png "Changes apply from the next run: a sync already in flight keeps to the old schedule.") The panel notes that changes apply from the next run, so a sync already in flight keeps to the old schedule. For a connection that is still being set up, it notes that the schedule takes effect once the connection is syncing. To stop scheduled runs without deleting anything, save the frequency as **Manual**. The connection keeps its streams and its rows, and runs only when you press **Sync now**. ## When the next run is The **Status** tab of a connection shows four figures. When a schedule is set, the last one is **Next sync**, with the frequency underneath, for example **Mon 7:00 AM** over **Every day at 7:00 AM (Australia/Sydney)**. Both are written in the schedule's own time zone, the one the frequency names, whatever zone your browser is in. An hourly schedule names its zone the same way, as in **Every 6 hours (UTC)**. Manual connections show **Frequency** instead, because nothing is scheduled. ## See everything on one clock A sync rarely runs alone. Tasks rebuild your models after it, and reports go out after those. To see all of it together, press **Sync times** at the top of **Data · Connections**. It opens **Monitor · Pipeline**, whose **Account schedule** panel draws syncs, tasks, model runs, index builds and deliveries on one clock. - **Calendar day** shows one day, and **Next 24 hours** shows a rolling window. The date picker has previous and next day buttons and a **Today** button. - The display time zone is your browser's, or **UTC**. On the two days a year a day is not 24 hours, the panel says **daylight saving**. - **Action type** filters to **Syncs**, **Tasks**, **Model runs**, **Index builds** or **Deliveries**. A search box finds one by name. - **Timeline** draws a lane for each item. **In time order** lists every action in order. - On the timeline, bars are recorded runs and diamonds are future starts from the current schedule. A circle marks something that waits for a sync to finish, such as a task set to run after one. Its exact start depends on how long the sync takes, so it is not fixed. See [Pipeline](https://docs.sanda-os.com.au/modelling/pipeline) for the rest of the page, including how to set a task to run after a sync finishes. ## What a schedule costs and what can stop it - **Each successful run is metered.** A connection that runs hourly makes many more runs than one that runs daily. Pick the slowest schedule that keeps your reports fresh enough. See [Usage](https://docs.sanda-os.com.au/billing/usage). - **One run at a time.** sanda runs one sync at a time for each connection, and **Sync now** is disabled while a run is in progress. - **Suspended or at a limit.** A workspace whose warehouse is suspended for its budget, or a warehouse that has hit a storage or compute limit, does not run syncs until it is back under. See [Usage and suspension](https://docs.sanda-os.com.au/warehouse/usage-and-suspension). :::links - [Monitor syncs](https://docs.sanda-os.com.au/connections/monitor-syncs): Follow a run and read the history. - [Manage a connection](https://docs.sanda-os.com.au/connections/manage): Sync now, change the schedule, delete. - [Pipeline](https://docs.sanda-os.com.au/modelling/pipeline): Tasks, deliveries and the account schedule. ::: --- # Monitor syncs > Follow a sync from the moment it starts, read the history of past runs, and know what sanda will and will not tell you when a run fails. Three places tell you how your connections are doing: the list of connections, the run panel on a connection's **Status** tab, and its **Timeline** tab. Between them you can see whether data is fresh, how a run in progress is getting on, and what happened on every recent run. ## The list **Data · Connections** shows one row per connection with its **Last sync**, the **Rows** it has synced and its **Status**. See [Connection statuses](https://docs.sanda-os.com.au/connections#connection-statuses) for what each status means. While a connection is syncing, its row shows the run instead of the total: the number of rows this run has added, and how fast, for example **+96K** and **21.6K/s**. Before rows start to move it says what stage the run is in, such as **queued** or **launching**. The list checks a syncing connection about every ten seconds, and only while something is syncing. ## Follow a run Open a connection and press **Sync now**, and the **Status** tab opens a run panel at once. It also appears when you open a connection that is syncing, whoever started the run and however it started, and it is the first thing on the page while the run goes on. ![A finished run: six stages ticked with how long each took, and every stream with its rows and time.](https://docs.sanda-os.com.au/media/run-panel.png "The run panel after a sync has landed.") ### The six stages A run moves through six stages, drawn as a track along the top of the panel. Each finished stage shows how long it took, and the current one counts up by the second. | Stage | The panel says | What is happening | |---|---|---| | **Requested** | Starting a sync | You have pressed **Sync now** and sanda is asking for a run | | **Queued** | Sync queued | The run is accepted and waiting for room to start | | **Launching** | Launching the connector | sanda starts a fresh connector for every run, which can take a minute or two before the first rows move | | **Syncing** | Syncing | Rows are moving. The sentence says how many streams are in and which one is on | | **Finishing** | Finishing | Every stream has been read and the last rows are being written to your warehouse | | **Landed** | Synced | The run succeeded and the rows are in your warehouse | One sentence under the track says what the current stage means in plain words, for example **Reading from PostgreSQL into acme_warehouse: 4 of 11 streams synced. Now on orders.** When a run has to try again, it adds **An earlier attempt stopped, so this is attempt 2.** A stage that the page did not see begin, because you opened it partway through a run, shows no duration rather than a guess. ### The numbers Once there is something to count, a row of figures appears under the sentence. | Figure | What it is | |---|---| | **Rows read** or **Rows landed** | How many rows the run has moved so far | | **Rows per second** | How fast rows are moving right now | | **Data read** or **Data landed** | How much data the run has moved so far | | **Data per second** | How fast data is moving right now | The label tells you which count you are looking at. **Rows read** is the number read from the source, and it moves as the run goes. **Rows landed** appears when sanda can only see what the warehouse has committed. That count can stay at zero for most of a run and then jump, because a full refresh commits late. **Rows per second** is the rate over about the last minute. It is measured between the moments the count moves, so it stays steady even when the count only updates every few seconds. Until sanda has seen the count move twice, it shows the run's average so far and says so. Beside the current rate, the panel shows the average once it has one, for example **21.6K on average**. If the count stops moving for a minute and a half or longer, the rate reads zero and says **nothing new for** how long it has held. The sentence changes to **Reading from … but nothing new has arrived for a while. The source may be slow to answer, or waiting out a rate limit.** A source that is slow, or rate limiting sanda, looks exactly like this. So does a run that has hung. ### Streams in this run The table under the figures has one row per stream in the run, with its state, its rows and its time. | State | Meaning | |---|---| | **Not started** | Waiting its turn | | **Syncing** | Being read now, with how long it has been going | | **Rate limited** | The source asked sanda to wait, with the time it will retry, such as **until 3:15 PM** | | **Synced** | Done, with how long it took | | **Failed** | Stopped with an error | | **Cancelled** | Stopped before it finished | | **In this run** | The stream is part of the run, but sanda has no state for individual streams on this run | When a run has more than 15 streams, the table opens on the ones worth looking at (syncing, failed or rate limited) and a few more, with **Show all N streams** to see the rest and **Show fewer** to fold it back. ### When the run ends The panel's title changes to **Synced** or **Sync failed**, and stays until you press **Done**. A run that landed says **Synced 2,833,970 rows in 2m 11s. They are in acme_warehouse.** A run that failed says where it stopped, for example **The run stopped while syncing, after 2m 3s. The reason is above.** The reason itself appears above the panel. See [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting). ## What the connection page shows Above the run panel, four figures describe the connection itself. They stay the same while a run is going, because they are the connection's own facts and not the run's. - **Rows synced** is a running total of the rows moved by every successful run. A full refresh counts the whole table again each time, so this is a measure of work done, not the size of your tables. - **Data moved** is the same for data. - **Last sync** is when the last run finished. - **Next sync**, or **Frequency** on a manual connection. See [Sync schedules](https://docs.sanda-os.com.au/connections/schedules#when-the-next-run-is). Lower on the page, **Lands in** shows the table prefix, such as `raw.acme_xero_au__*`, with one table per stream. **Processed in** shows where the rows are loaded and processed: your workspace's [data region](https://docs.sanda-os.com.au/reference/glossary#data-region), such as **Australia (Sydney)**. Loading, staging and modelling stay in that region. Once rows have landed and nothing from the connection is on your map yet, a banner offers **Learn from my data**. Pressing it asks cherry to read the new tables and propose what they mean. See [Learn from your data](https://docs.sanda-os.com.au/semantic-fluid/learn). ## Sync history The **Timeline** tab lists the last ten runs of the connection, newest first. The tab's label carries the count. Each run shows: - **Sync** and a status: **queued**, **running**, **succeeded** or **failed**. A run in progress shows its stage instead of **running**. - Its rows and data size, how long it took, and for a run that succeeded, its rows per second. - **N attempts** when it needed more than one, in red for a run that failed. One attempt is normal and says nothing. - When it started, and its run number, such as `#105`. - The reason, in red, when it failed. A run that was cancelled before it finished is recorded as failed, with a note saying it was cancelled. A run that failed without a reason says so, and **Check status** asks again. A run that sanda lost track of, because a newer run started before it saw the first one end, is closed as failed with a note that begins **sanda lost track of this run**. If a run still reads **running** after twelve hours, the row adds **Still reported as running.** Press **Check status** to settle it. ## Keeping the page up to date While a connection is syncing, its page checks the run about every six seconds, and about every two while it is queued. You do not need to refresh. A scheduled run that nobody is watching is caught up by sanda's own check, which runs every 15 minutes. Until it does, anything that reads sanda's record rather than the run itself can show a run that finished at 2:01 as still syncing at 2:05. Opening the list, or a connection that reads **Syncing**, checks the run straight away, and so does pressing **Check status**. **Check status** re-reads the latest run from sanda's managed sync and updates the page. It says what it found: **Status checked: the page shows the latest run.**, or that nothing has run yet. It also retries making landed tables readable, which is how a connection that reads **Error** with a message beginning **Rows landed, but sanda could not attach** clears once you have fixed the cause. ## Other places to look - **Overview.** The **Rows synced** tile totals every connection. **Recent activity** records **Connection added**, **Source connected, schema read**, **Connection handed to sync**, **Stream selection changed**, **Sync schedule changed**, **CSV loaded into warehouse** and **Connection removed**. - **Monitor · Pipeline.** Every recorded sync as a bar on the account schedule, next to the tasks and deliveries that follow it. See [Sync schedules](https://docs.sanda-os.com.au/connections/schedules#see-everything-on-one-clock). - **cherry.** Ask whether your data is fresh, or why a sync failed. cherry can read your connections and their recent runs, and can start a sync for you. Depending on your cherry settings, it asks for confirmation first. See [Actions and approvals](https://docs.sanda-os.com.au/cherry/actions-and-approvals). ## Notifications sanda does not email you when a sync fails. A failed run shows as **Error** on the list and on the connection, with its reason, and the history keeps it. If you rely on a sync finishing, check **Last sync** or ask cherry. sanda does email owners and admins, plus any extra addresses you add, about events that stop syncs: - **A warehouse near a limit, and at one.** At 80% of a storage or compute limit sanda warns you. At the limit it pauses loading, which stops syncs, and tells you again. - **A budget.** Reminders as the month fills, and a notice at 100% when the warehouse is suspended and syncs wait. It also emails when a scheduled task, index build or report delivery fails. Set who is told under **Settings · Plan & budget**, in the **Budget & alerts** panel. See [Budgets and AI limits](https://docs.sanda-os.com.au/billing/budgets-and-limits). :::links - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What each error means and how to fix it. - [Manage a connection](https://docs.sanda-os.com.au/connections/manage): Sync now, Check status and the rest of a connection's controls. - [Usage and suspension](https://docs.sanda-os.com.au/warehouse/usage-and-suspension): Why a warehouse stops loading. ::: --- # Edit, pause and delete connections > Run a sync now, change what a connection syncs and when, stop it running, and delete it. Includes what the console can and cannot change after setup. Everything you do to an existing connection starts on its own page. Select a connection in **Data · Connections** to open it. The header shows the connection's name and status, and beneath it the source, the warehouse it lands in and its frequency. Two buttons sit on the right: **Check status** and **Sync now**. They appear once the connection has been handed to sanda's managed sync, which happens when you save its streams. Three tabs organise the rest: | Tab | What is on it | |---|---| | **Status** | Any error, the run panel, the four figures, the **Streams** panel and where rows land | | **Timeline** | The last ten runs. See [Monitor syncs](https://docs.sanda-os.com.au/connections/monitor-syncs) | | **Settings** | **Details**, **Schedule**, **Network** and the delete control | Owners and admins can do everything on this page. Members can open it and read it, but the controls refuse them with **Only owners and admins can manage connections.** ## Run a sync now Press **Sync now** to run a connection without waiting for its schedule. The **Status** tab opens on the run panel at once, before sanda has heard back, so you see the run start. While a run is in progress the button reads **Syncing** and is disabled. **Sync now** is refused, with the reason on the page, when the workspace is suspended. A workspace whose warehouse was suspended for its monthly budget, or whose free trial has ended or used up its credit, cannot sync. The message says when it happened and where to lift it. See [Usage and suspension](https://docs.sanda-os.com.au/warehouse/usage-and-suspension). cherry can start a sync for you as well. Depending on your cherry settings, it asks you to confirm first. See [Actions and approvals](https://docs.sanda-os.com.au/cherry/actions-and-approvals). ## Check status **Check status** re-reads the latest run and updates the page. You rarely need it while a run is going, because the page follows the run itself. It is for the times when the page may be behind, such as after a scheduled run you were not watching, or after you fixed something that was stopping tables from becoming readable. See [Monitor syncs](https://docs.sanda-os.com.au/connections/monitor-syncs#keeping-the-page-up-to-date). ## Change what syncs On the **Status** tab, press **Edit streams** in the **Streams** panel. Tick tables on or off, change a mode, and set a cursor or primary key. The change applies from the next sync. See [Choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). Turning a stream off stops it being synced. The table it landed earlier stays in your warehouse and is no longer refreshed. ## Change the schedule On the **Settings** tab, the **Schedule** panel has the same picker as setup. Choose a frequency and press **Save schedule**. The change applies from the next run. See [Sync schedules](https://docs.sanda-os.com.au/connections/schedules). ## Pause a connection There is no **Pause** button. To stop a connection running on its own, open **Settings**, choose **Manual** in the **Schedule** panel and press **Save schedule**. The connection keeps its streams, its settings and every row it has landed, and it runs only when you press **Sync now**. To start scheduled runs again, save a schedule. A connection can also read **Paused** in the list. If you did not expect that, contact sanda support. ## What you cannot change after setup The console shows a connection's **Details** on the **Settings** tab as read-only: its **Name**, **Source**, **Destination**, **Frequency**, **Landing prefix** and **Created** date. There is no control to rename a connection, move it to another warehouse, or change its source. The name also decides the landing prefix: every table the connection lands is named from it. ### Sign-in details While a connection has not connected yet, for example because you skipped the details or connecting failed, the **Status** tab offers **Finish setting up this connection** with **Enter connection details**. That form is the same as the one in setup. Once a connection has connected, the console has no field for changing its sign-in details. If a password is rotated or a key is revoked, and the connection starts failing, contact sanda support rather than deleting and recreating it. A new connection with the same name lands in the same tables as the old one. ### Starting a table again There is no reset button. A full refresh mode reads a whole table on every run, so it needs no reset. If you need an incremental table read again from the start, contact sanda support. ## Delete a connection Deleting stops the connection and removes it from sanda. Rows that already landed stay in your warehouse. :::steps 1. **Open Settings.** On the connection's page, go to the **Settings** tab. 2. **Press Delete connection.** It is in the **Danger zone** at the bottom, under the sentence **Deleting … stops its schedule and removes it from sanda. Rows already landed stay in your warehouse.** 3. **Confirm.** The button changes. Press **Yes, delete it**, or **Keep it** to back out. It is two presses on the same page, with no dialog. ::: :::danger Deleting cannot be undone. The connection's run history goes with it, and you would have to add the connection again and choose its streams again. ::: ### What happens to your data | | After you delete | |---|---| | **The landed tables** | They stay in your warehouse, as they were after the last successful run. Nothing refreshes them any more | | **Storage** | They keep counting toward your warehouse's storage. To have them removed, contact sanda support | | **Your semantic map and reports** | Untouched. Anything built on those tables keeps reading them, and shows the data as of the last sync | | **Tasks and indexes set to run after this connection syncs** | They lose that trigger and no longer run when it would have synced. Give them another schedule | | **The run history** | Deleted with the connection | | **The schedule** | Stopped | sanda also removes the connection and its stored source from its managed sync. If that cleanup cannot be completed at that moment, the deletion still goes through in sanda. The deletion is recorded in **Overview · Recent activity** as **Connection removed**. :::links - [Monitor syncs](https://docs.sanda-os.com.au/connections/monitor-syncs): Runs, history and what each status means. - [Choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams): Streams, modes, cursors and keys. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): When a run fails. ::: --- # Troubleshoot connections > What each connection error means and how to fix it, from a host sanda cannot reach to a table that has no rows attached. When something goes wrong, the reason is almost always on the page. Look in three places: - **The red note on the connection's Status tab.** It holds the last error. On a connection that is not syncing yet, it is labelled **Last attempt**. - **The Timeline tab.** Each failed run carries its reason. - **The message under the form** while you are adding a connection. **Check status** asks sanda's managed sync again and refreshes what the page shows. Failure text often starts with which side of the pipe broke, such as the source or the destination, and what kind of failure it was, so read the first words. ## When you connect Connecting happens when you press **Read schema** (or **Connect**, on a connection you are finishing). It signs in to the source and reads which tables it offers. ### Host errors before connecting For a connector with a host field, such as a database, sanda checks the host you entered before it tries to connect, and refuses the ones that can never work. The messages say **database host** wherever they name the field. The message names what you typed and says what to put where. | What the message says | What is wrong | Fix | |---|---|---| | The database host is blank | Nothing was entered | Enter the hostname or IP address the database answers on | | The database host field has more than a hostname in it | A connection string or `user@host` was pasted | Put only the host in the host field. Put the port in the port field, and the username, password and database name in their own fields | | That database host looks like host:port | `db.example.com:5432` in one field | Split it: the host in the host field, the port in the port field | | … doesn't look like a hostname or IP address | Spaces, slashes or other characters no host can carry | Enter just the host | | … means "this computer" | `localhost` or another loopback address | Use the address the database answers on from the internet | | … is a private network address | An address such as `10.x.x.x`, `172.16.x.x` to `172.31.x.x` or `192.168.x.x` | Use the database's public hostname or IP, or route it through something the internet can reach | | … has no domain, so it can only resolve inside your own network | A bare name such as `dbserver` | Use the full public hostname or IP | | Names ending in .local only resolve inside private networks | A `.local`, `.internal`, `.lan` or similar name | Use the database's public hostname or IP | | … does not resolve on the public internet | The name has a typo, or exists only inside your network | Check it for typos. Otherwise use the public hostname or IP | | … resolves to … a private network address | The name points at an internal address | Give the database an address the internet can reach | The same rules apply to an SSH tunnel host when the form offers one. See [Allowlist sanda's address](https://docs.sanda-os.com.au/connections/allowlist#hosts-sanda-can-reach). ### The connection is refused or times out **What you see:** a message that sanda reached your database's network but nothing accepted the connection, and to check the port and that sanda's sync address is allowed through the firewall. **Cause:** the host is right but the connection is being blocked, or the port is wrong. **Fix:** 1. Check the port matches the one your database listens on (5432 for PostgreSQL, 3306 for MySQL and 1433 for SQL Server, unless you changed it). 2. Allow sanda's address through every firewall in the path. See [Allowlist sanda's address](https://docs.sanda-os.com.au/connections/allowlist). 3. Check the database accepts connections from outside its own machine, and that any rule restricting logins by address includes sanda's. ### Your database refused the username and password **What you see:** **Your database refused the username and password sanda was given.** **Fix:** re-enter the details. Check the username and password, that the login is allowed to connect from sanda's address (some databases restrict each login to certain addresses), and that the login can read the schemas you want. Nothing else about the connection needs changing. If the connection failed before it ever connected, its page shows **Finish setting up this connection**. Press **Enter connection details** and try again. If you are still in the new connection flow, press **Retry connecting**. ### A hostname could not be resolved **What you see:** a message that sanda could not resolve a hostname while setting the connection up. **Cause:** the message tells you which of two situations you are in. - **It says the host may be the problem.** If the database host is misspelled, or only resolves inside your own network, that is the cause. Correct the host. See the host errors above. - **It says nothing you entered caused this.** sanda has checked your host and it resolves on the public internet, or the connector has no host field at all, so the fault is on sanda's side. Contact sanda support. ### The form says something is still needed **What you see:** **Still needed:** and a list of fields under the form, or after you press the button, a message that the connector **still needs** those fields **before it can be connected**. **Fix:** fill in the fields named. A required field hidden under **Advanced settings** opens the section by itself when it needs an answer. ### Other messages at this step | Message | What to do | |---|---| | **There is nowhere to land rows yet. Provision a warehouse under Data · Warehouse first.** | Provision one. See [Provision your warehouse](https://docs.sanda-os.com.au/warehouse/provision) | | **The warehouse this connection lands in has no credentials on file. Retry provisioning it under Data · Warehouse, or pick another destination.** | Retry provisioning from the warehouse's page, or choose another destination | | **This connector needs its connection details before sanda can read its schema.** | The form was empty. Fill it in | | **Only owners and admins can manage connections.** | Ask an owner or admin to do it | | **sanda's managed sync isn't switched on yet, so nothing can move for this connection.** | Your setup is saved and sanda will finish wiring it up and be in touch. There is nothing to retry | ## When you choose streams | Message | What to do | |---|---| | **Leave at least one stream on.** | Tick at least one table. To stop a connection, save its frequency as **Manual**, or delete it | | **1 stream needs a cursor** or **needs a primary key** | Pick a cursor or key for each stream the message counts, or choose a mode that does not need one. See [Sync modes](https://docs.sanda-os.com.au/reference/sync-modes) | | **sanda hasn't read this source's schema yet.** | The connection has not connected. Enter its details first | If the picker cannot list the streams, it shows the reason with a **Close** or **Reload** button. The list comes from a live check of your source, so the causes are the same as when connecting. A table missing from the list is usually one the login cannot read, so check its permissions. ## When a run fails A failed run shows **Sync failed** on the run panel, **Error** on the connection, and its reason in red. A run that ends without a stated reason says the last run failed and gave no reason. Press **Check status** to ask again, or open the run in the **Timeline** tab. | What you see | What it means | What to do | |---|---|---| | The source refused the login, or the connection is refused | Your credentials or firewall changed since setup | Check the source. See the sections above. A rotated password cannot be edited in the console, so contact sanda support | | **This source already carries the bookkeeping columns sanda's own loading adds** | The source is a warehouse that another loader has already filled, and its loading columns clash with sanda's | Open **Edit streams** and save the selection again. That leaves the clashing columns out, and the next sync goes through | | A failed run while the **Warehouse** page shows **At a limit: syncs are paused and queries are throttled until it comes back under, or the limit moves.** | The warehouse reached a storage or compute limit, and sanda stops loading until it is back under | Raise the limit or clear space under **Data · Warehouse**. See [Usage and suspension](https://docs.sanda-os.com.au/warehouse/usage-and-suspension) | | **This workspace's warehouse is suspended** | The month's spend reached your budget, so syncs wait | An owner or admin resumes it under **Settings · Plan & budget**, once the budget is raised or the month is back under it | | **This workspace is frozen: its free trial credit ran out** | A trial spends its credit and then waits for a paid edition | An owner or admin asks to move to an edition under **Settings · Plan & budget**. See [Free trial](https://docs.sanda-os.com.au/billing/trial) | | A note that the run was cancelled before it finished | The run was stopped | Press **Sync now** | | **sanda lost track of this run** | A newer run started before sanda saw this one finish | Look at the newer run. Nothing to fix | | **… no longer has this connection, so nothing is running** | The connection is gone from sanda's managed sync | Delete the connection and add it again, or contact sanda support first if you want the same tables | Two states are not failures: - **Rate limited** on a stream means the source asked sanda to wait. The run retries at the time shown. - A run that seems stuck may be launching. Every run starts a fresh connector, which can take a minute or two before the first rows move. If rows stop for a minute and a half or longer mid-run, the source may be slow or rate limiting, or the run may have hung. Give it time, then contact sanda support if it does not move. If a run still reads **running** after twelve hours, press **Check status** to settle it. ## Rows landed, but cannot be read Rows can arrive in your warehouse and still not be readable, because sanda makes each landed table readable after a run, and something can be in the way. ### Rows landed, but sanda could not attach **What you see:** the connection reads **Error**, with a message that begins **Rows landed, but sanda could not attach** and the number of tables, followed by why. **Cause:** sanda could not make the new copy of a table readable at its usual name, `raw.__`. Common reasons the message gives: - **Something else with that name is already there.** A view, or a table sanda did not make, already has that name in `raw`. Rename or drop it. - **A view of yours depends on a column the source no longer sends.** The message names the view and the column. sanda will not drop your view for you. Drop or redefine it. **Fix:** remove the thing in the way, then press **Check status** on the connection. sanda retries and clears the message. Otherwise the next sync attaches the tables. ### A table has no rows attached right now **What you see:** a question, report or view over a landed table fails with **… has no rows attached right now: its sync is finalising, or the last sync could not be attached. Try again in a few minutes.** **Cause:** a run that replaces a table swaps in the new copy at the end, and sanda attaches that copy when it next checks the run. Until then, sanda returns this message rather than an empty answer, so a report is never sent empty. **Fix:** wait, or press **Check status** on the connection, which attaches the copy at once. If you have the connection open while a run finishes, this happens by itself. For a scheduled run that nobody is watching, sanda's own check runs every 15 minutes. If the message persists, look for **Rows landed, but sanda could not attach** on the connection. A scheduled report that reads a table right after its sync can meet this window. Schedule the report a little later than the sync. See [Report delivery](https://docs.sanda-os.com.au/reports/delivery). ### The connection says sanda repaired something After a failed run, sanda sometimes repairs a problem in the landing tables itself and says what it did on the connection: that it moved tables out of the way, detached a table so a column's type could change, or started the sync again. If the message says to run the sync again, press **Sync now**. If it says a view of yours still reads tables that sanda could not redefine, rebuild that view under **Data · Modelling**, then run the sync again. ## When data looks wrong | You see | Likely cause | |---|---| | **Last sync** is older than you expect | The frequency is **Manual**, the connection is in error, or the workspace is suspended. Check **Status**, and the **Next sync** figure | | **Next sync** shows a different time from the one you set | It is written in the zone the frequency under it names, which may not be yours. An older schedule kept in UTC also moves by an hour when the clocks change. See [Sync schedules](https://docs.sanda-os.com.au/connections/schedules#time-zones-and-daylight-saving) | | **Rows synced** is far bigger than your tables | It totals every run, and a full refresh counts the whole table again each time | | Deleted rows are still in a table | The stream is incremental, and a cursor cannot see a deletion. See [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes#how-to-choose) | | A table has several copies of the same row | The stream is on **Full refresh · Append** or **Incremental · Append**, which keep earlier versions. Choose a deduplicated mode to keep one row per key | | A column you expected is missing | A column that the source does not send, or that sanda leaves out because it clashes with its loading columns | | A CSV replace fails, naming a view that reads a column of the table | A view of yours under **Data · Modelling** reads a column the new file drops or retypes. See [Load a CSV](https://docs.sanda-os.com.au/connections/upload-a-csv#replacing-a-table-that-is-in-use) | ## Still stuck Raise a ticket under **System · Support**. Include the connection's name, the time and the run number from the **Timeline** tab, and the message exactly as the page shows it. Failures where the message says the fault is on sanda's side, and anything about changing a connection's sign-in details, go to sanda support. :::links - [Allowlist sanda's address](https://docs.sanda-os.com.au/connections/allowlist): The usual fix for network errors. - [Monitor syncs](https://docs.sanda-os.com.au/connections/monitor-syncs): Where each message appears. - [Help and troubleshooting](https://docs.sanda-os.com.au/help/troubleshooting): Problems beyond connections. ::: --- # Modelling > Turn landed tables into cleaned, joined and rolled-up views that the semantic fluid is built on, drafted with cherry, previewed live and kept fresh by tasks. Modelling is the layer between what lands in your warehouse and what it means. Connections bring tables in exactly as the source system shaped them. Modelling is where you clean, join and roll them up into the tables the [semantic fluid](https://docs.sanda-os.com.au/semantic-fluid) should be built on, and where you keep them up to date. Everything you build here lives in the `derived` schema of one warehouse. It is yours: sanda reads `raw` for you, and writes `derived` only when you ask it to. Go to **Data · Modelling**. Only owners and admins can build; every member can open the page and read what is there, because building costs compute. ![The Modelling page on the Views tab, with the ways to start a view](https://docs.sanda-os.com.au/media/modelling-views.png "The Views tab: describe a view to cherry, draw one, or write SQL") ## The three tabs | Tab | What it holds | Read more | |---|---|---| | **Views** | Views, which run their query every time they are read, and materialized views, which store their rows and are rebuilt on refresh. | [Views and modelled tables](https://docs.sanda-os.com.au/modelling/views) | | **Procedures** | Named blocks of SQL you can run with arguments, for work that is more than one query. | [Procedures](https://docs.sanda-os.com.au/modelling/procedures) | | **Tasks** | Ordered lists of refreshes and procedure calls that run on a schedule, after a sync, or when you press **Run now**. | [Tasks](https://docs.sanda-os.com.au/modelling/tasks) | A **Warehouse** menu at the top right chooses which of your warehouses you are working in. The sanda sample dataset is listed there as read-only, and you cannot build on it. Each tab has a list on the left, a search box, and a **+** button to make something new. Select an item to see it on the right. ## Four ways to start a view 1. **Describe it to cherry.** Press **Describe the view to cherry** (or ⌘ J) and say what you want in your own words, or pick one of the examples, such as **Revenue by customer and month**. cherry opens beside the page, reads the names and types of the tables and columns in the warehouse, and drafts a view for you. 2. **Draw it.** **Visual builder** lets you start from a table, join others, drag columns into the output and wrap them in functions, without typing SQL. 3. **Write it.** **Write SQL** gives you a box for a `select` and a live preview. 4. **Save it from the workbench.** In the semantic fluid's query workbench, **save as view** keeps the selection of metrics, dimensions and filters that just ran as a view. It appears here marked **saved from a query**. The first three are on the welcome screen of the **Views** tab. All four make the same thing: a view in `derived`, with a name, a definition and a version history. ## Build with AI To draft a view, cherry is given the relations in the warehouse and the names and types of their columns. What it produces is a draft you can read. - Depending on how you ask, cherry either fills the builder with a draft for you to edit, preview and save, or saves the view itself. When it saves one, Modelling says so, for example **cherry saved daily_revenue. It is on the semantic fluid.** - A draft has to pass the same checks as one you wrote: a valid name and a single read-only `select`. If cherry cannot write one, it tells you what it needs to know. - Building with AI needs something to build on. In a warehouse with no landed tables, sanda asks you to connect a source first. - Only owners and admins can build. See [cherry actions and approvals](https://docs.sanda-os.com.au/cherry/actions-and-approvals) for when cherry asks before it acts. Ask cherry for the next change from the **Ask cherry** button in the builder, or **Edit with cherry** on an existing view. ## Live previews You see the data as you shape it. - In the builder that the **+** button opens, **Live preview** re-runs your query a moment after you stop typing and shows the first 50 rows, with a **Columns** tab for the shape. Untick it to pause, and press the play button beside it to run once. - In **Write SQL**, press **Preview 50 rows**. In the **Visual builder**, press **preview 50 rows**. - A preview reads your warehouse in a read-only transaction and stops after 20 seconds. Values are cut at 300 characters. - For a procedure, the preview shows read-only source data. The procedure has not run, and the rows are not a simulation of its output. ## Every object says what it affects Before you touch a modelled table, Modelling answers "who is standing on this?". Each object lists: - the **semantic fluid table** it is published as, and how many metrics, dimensions and filters are defined on it; - the other `derived` objects whose SQL reads it; - the tasks that rebuild or call it. **Drop** and **Redefine** are checked against the same information. ## What Modelling protects you from - **Built on a published table.** A view cannot read a `mapping` view that the fluid publishes, because the fluid replaces those whenever a table's columns change. Read the table behind it instead. The builder does not offer published views as sources. - **Dropping something in use.** Dropping a view or materialized view is refused while another object reads it, and so is a redefinition that needs it dropped and recreated. sanda names the dependents and never cascades. - **Name clashes.** A new name cannot match a landing table or a published view. The [SQL shell](https://docs.sanda-os.com.au/warehouse/sql) does not protect you in these ways, and that is why it opens on a warning. ## Cost Views cost nothing until they are read. Materialized view builds, procedure calls and task runs are billed as compute for the seconds they run. Every one is recorded with its duration and cost. See [Usage, budgets and suspension](https://docs.sanda-os.com.au/warehouse/usage-and-suspension). :::links - [Views and modelled tables](https://docs.sanda-os.com.au/modelling/views): Create, preview, refresh, redefine and drop. - [Procedures](https://docs.sanda-os.com.au/modelling/procedures): Multi-step work with arguments. - [Tasks](https://docs.sanda-os.com.au/modelling/tasks): Run refreshes and procedures on a schedule. - [Pipeline and scheduled tasks](https://docs.sanda-os.com.au/modelling/pipeline): Watch everything that runs on a clock. ::: --- # Views and modelled tables > Create a view or materialized view, preview it, put it on the semantic fluid, and refresh, redefine or drop it. A view is a saved query with a name. Once it exists in the `derived` schema, you can read it from other views, put it on the [semantic fluid](https://docs.sanda-os.com.au/semantic-fluid) as a table, and rebuild it on a schedule. This page covers views and materialized views. For work that takes several steps, use a [procedure](https://docs.sanda-os.com.au/modelling/procedures). Only owners and admins can create, change or drop them, because what they make costs compute. ## Choose a kind | | View | Materialized view | |---|---|---| | Stores its rows | No | Yes | | Runs its query | Every time it is read | When it is built or refreshed | | Freshness | Always current | As of its last build | | Cost | Nothing until it is read | Each build is billed as the seconds it runs | | Can be scheduled | No, it has nothing to refresh | Yes, in a [task](https://docs.sanda-os.com.au/modelling/tasks) | Start with a view. Move to a materialized view when the query is slow or heavy and the answer only needs to be as fresh as a schedule. In the builder the two are labelled **View · always current** and **Materialized view · scheduled refresh**. ## Names and rules - **A name** is lowercase letters, digits and underscores, starts with a letter, is at most 63 characters, and does not begin with `pg_`. The object becomes `derived.`. - **A name must be free.** It cannot match a landing table or a published view. - **A view is one `select`,** or a `with` query that ends in a `select`. No semicolons, no comments, no other kind of statement, at most 20,000 characters. The [SQL reference](https://docs.sanda-os.com.au/reference/sql) lists exactly what is refused. - **It reads what the warehouse holds,** by schema-qualified name: landing tables (`raw.__`), other `derived` views, and the tables in `mapping`. It cannot read a `mapping` view that the fluid publishes: read the table behind it. ## Write a view in SQL :::steps 1. **Open Write SQL.** In **Data · Modelling**, on the **Views** tab, press **Write SQL** under **Or start your way**. The **+** button beside **Your views** opens a different builder, with the definition beside a live preview and an **Ask cherry** button. 2. **Name it.** Type a **Name** and, if you like, a **Description** (shown in the Explorer and to agents). 3. **Choose the kind.** **View** or **Materialized view**. For a materialized view, add a **Unique key** if you can (see below). 4. **Write the query.** Type a `select` in the **SELECT** box. 5. **Preview it.** Press **Preview 50 rows**. The rows appear under the box. If the query is wrong, PostgreSQL's own message is shown as it came, because you wrote the SQL. 6. **Decide about the fluid.** **Put it on the semantic fluid, so sanda may query it** is ticked by default. In the **+** builder the same box reads **Add to semantic fluid**. 7. **Create it.** The button reads **Create view**, or **Create and build** for a materialized view. ::: ```sql title="Revenue by month and region" select date_trunc('month', s.sale_date)::date as month, s.region, sum(s.revenue) as revenue from raw.csv__sales s group by 1, 2 ``` The table and column names depend on your data. Find them in the [Explorer](https://docs.sanda-os.com.au/warehouse/explorer). Landing tables from a CSV are named `raw.csv__`, and from a connection `raw.__`. ## Draw a view **Visual builder** builds the same view without typing SQL. Its title is **Draw a view**. :::steps 1. **Bring in a table.** The list on the left is every table the warehouse holds that you may build on, in three groups: landing (`raw`), yours (`derived`) and modelled (`mapping`). Press **use** beside one to start from it. Press **use** on a second to join it. If a table is already on the semantic fluid, its relationships come along as suggested joins. A drawn view can read a table from any of the three groups. The fluid's published `mapping` views are never listed, because nothing may be built on them. 2. **Choose columns.** Click a column, or drag it, into the **columns** area. Each output column has a name you can edit. 3. **Wrap a column in a function.** Press **fx** beside a column to apply `concat`, `coalesce`, `nullif`, `lower`, `upper`, `trim`, `length`, `abs`, `round`, `date_trunc`, or an aggregate: `sum`, `count`, `avg`, `min`, `max`. Arithmetic, joining text with `||` and casts are also available, and each can be unwrapped in one press. An aggregate turns the view into a group-by over everything that is not aggregated. 4. **Filter rows.** Under **rows to keep**, type an optional SQL condition using the table aliases, for example `t0.status = 'AUTHORISED'`. 5. **Name it and choose the kind.** The same choices as above, except that a **unique key** here is one output column, chosen from a list. 6. **Preview and create.** Press **preview 50 rows**, then **create view** (or **create and build**). ::: The drawing is saved beside the SQL, so **Redefine** on a drawn view reopens the drawing. A view written as SQL reopens in the SQL box. ## Ask cherry Press **Describe the view to cherry**, or **+** for the builder with **Ask cherry** beside it. cherry drafts a name, a description and a query. Edit any of it, watch **Live preview**, and press **Save model**. See [Modelling](https://docs.sanda-os.com.au/modelling) for how drafting works. ## Materialized views A materialized view stores its rows, so it has a life a plain view does not. **The first build happens when you create it.** It runs on your compute and is billed as the seconds it takes. If it outlasts 55 seconds, sanda stops it and still creates the view, empty, with a message saying so. Schedule it and sanda builds it in the background, with up to 14 minutes. **Refresh now** rebuilds it. When it finishes, sanda says how long it took, how many rows and how much storage it holds, and how much compute it was billed as. The object shows **built** with the time and duration. **A unique key** is the columns that identify one row, for example `month, region`. With one, a refresh runs without blocking anyone who is reading the view. Without one, readers wait for the build. The first build is always the blocking kind, because PostgreSQL cannot refresh an empty view any other way. The object's **built** line says which one it uses: **refreshes concurrently on** the key columns, or **refreshes block readers (no unique key)**. **Keep it fresh** with **Schedule task** on the object, or **Save & schedule** in the builder. See [Tasks](https://docs.sanda-os.com.au/modelling/tasks). If a build fails, the object shows **last build failed** with the error, and it stays that way until a build succeeds. A build that only ran out of time is not marked as broken. ## Put it on the semantic fluid With the fluid box ticked, sanda publishes the view as a table on the fluid the moment it is saved, so cherry and reports can use it. If you left it off, or you want to add one later, press **Put on the fluid** on the object. From then on the object shows **on the fluid as** its table name, with a link. Add relationships and metrics to it on the semantic fluid page. See [Tables on the fluid](https://docs.sanda-os.com.au/semantic-fluid/tables). The fluid reads one warehouse: the newest healthy one you own. An object in another warehouse is real and readable, but it cannot be put on the fluid. ## Redefine a view Select the object and press **Redefine**. The name and the kind stay fixed; to rename, drop and recreate. A new version is recorded and the old definition stays in the history. A redefinition that only adds columns always works. One that removes or reorders columns needs the view dropped and recreated. sanda does that unless another object reads the view, in which case it stops and names the dependents. Redefine or drop those first. sanda never cascades. ## Drop a view :::steps 1. **Select the object** in the list and press **Drop**. 2. **Read the confirmation.** It says what goes, that its definitions stay in the history, whether it comes off the fluid first, and which tasks will skip it. 3. **Confirm.** ::: Dropping refuses while another `derived` object reads it, and names it. A refused drop changes nothing: the object stays, and so does its place on the fluid. If the drop goes ahead and the object is on the fluid, sanda takes it off first and retires the definitions built on that table. A task that had a step for it skips that step with a message. Steps set to run only if the step before succeeded are skipped too, and the rest carry on. ## Every change is on the record Every definition is a version. Every build is a run with its seconds and its cost. Open the object's **History** tab, here or in the [Explorer](https://docs.sanda-os.com.au/warehouse/explorer), to see them, with the statement sanda ran and who asked. An agent's change is recorded the same way, with the credential's name. ## Errors you may meet | You see | What it means | |---|---| | A landing table is already called `name`. Pick another name. | The name matches a table in `raw`. | | A published view is already called `name`. Pick another name. | The name matches a view the fluid publishes. | | `derived.name` is already a view (or materialized view or procedure). | A name has one kind. Drop it first, or pick another name. | | A view is one SELECT (or WITH … SELECT)... | The text broke the rules above. | | `mapping.name` is a view the semantic fluid publishes... | A view cannot be built on a published view. Read the table behind it. | | `derived.a` reads `derived.b`. Drop or redefine it first. | Something depends on what you are changing. | | This materialized view has not been built yet. Refresh it first. | Read it after its first build succeeds. | | Permission denied | You read something the builder role cannot. It reads `raw`, `derived` and the tables in `mapping`, and writes only `derived`. | :::links - [Procedures](https://docs.sanda-os.com.au/modelling/procedures): Multi-step work that a view cannot do. - [Tasks](https://docs.sanda-os.com.au/modelling/tasks): Rebuild materialized views on a schedule or after a sync. - [SQL reference](https://docs.sanda-os.com.au/reference/sql): Exactly what a view definition may contain. ::: --- # Procedures > Write a procedure for work that takes more than one query, run it with arguments, and add it to a task. A view is one query. A procedure is a named block of SQL that can do several things in order: create a table, clear a slice of it, refill it from your landing tables, commit between steps. You run it with arguments, by hand or from a [task](https://docs.sanda-os.com.au/modelling/tasks). Use a procedure when a view cannot express the work, for example to keep an incrementally updated reporting table, or to prepare data in steps. Only owners and admins can create, run or drop one. ## What a procedure may do A procedure runs as the warehouse's builder role. That role can: - read every table in `raw` and `derived`, and the tables in `mapping`; - create, change and drop objects in `derived`, and read and write the tables it makes there; - commit between steps, when the body is `plpgsql`. It cannot write anywhere else, and it cannot touch a published `mapping` view, sanda's bookkeeping, or another database, whatever the body says. sanda does not scan a procedure body for what it might do, because no scanner could vouch for arbitrary code. The role's permissions are the boundary. What a procedure can do is spend compute. That is why creating and running one needs an owner or admin, and why every run is recorded with its duration and cost. ## Create a procedure :::steps 1. **Open the Procedures tab.** In **Data · Modelling**, choose **Procedures**, then **Write SQL** under **Or start your way**. Or press **Describe the procedure to cherry** and let cherry draft it (examples include **Prepare a daily revenue summary**). 2. **Name it.** Lowercase letters, digits and underscores, starting with a letter, at most 63 characters. It becomes `derived.`. 3. **Declare the arguments.** In **Arguments**, write `name type, name type`, for example `since date, region text`. Leave it empty for none. Allowed types are `text`, `integer`, `bigint`, `numeric`, `boolean`, `date`, `timestamptz`, `timestamp`, `interval`, `jsonb` and `uuid`. A procedure takes at most 16 arguments. 4. **Choose the language.** `plpgsql` is a block with variables, loops and commits. `sql` is statements one after another. 5. **Write the body.** Up to 20,000 characters. The body cannot contain the text `$becca_body$`, which sanda uses to wrap it. 6. **Create it.** Press **Create procedure**. ::: ```sql title="Body of a procedure with the argument since date, language plpgsql" begin create table if not exists derived.daily_sales ( sale_date date primary key, revenue numeric not null ); delete from derived.daily_sales where sale_date >= since; insert into derived.daily_sales (sale_date, revenue) select s.sale_date, sum(s.revenue) from raw.csv__sales s where s.sale_date is not null and s.sale_date >= since group by s.sale_date; commit; end ``` This one clears everything from a date onward, then rebuilds it, so it is safe to run twice. Table and column names depend on your data. Find them in the [Explorer](https://docs.sanda-os.com.au/warehouse/explorer). In the `sql` language the body is just the statements, one after another. PostgreSQL checks a `sql` body when you create the procedure, so this version needs `derived.daily_sales` to exist already: ```sql title="The same idea in the sql language, argument since date" delete from derived.daily_sales where sale_date >= since; insert into derived.daily_sales (sale_date, revenue) select s.sale_date, sum(s.revenue) from raw.csv__sales s where s.sale_date is not null and s.sale_date >= since group by s.sale_date; ``` :::note Tables a procedure creates are ordinary tables in `derived`. sanda does not record them as modelled objects, so they have no version history, and the Explorer marks them **Made outside sanda**. To use one on the semantic fluid, add it as you would any table. See [Tables on the fluid](https://docs.sanda-os.com.au/semantic-fluid/tables). ::: ## Preview A procedure has nothing to preview, so the builder shows **Source preview**: a read-only look at the source data, when a query for it is provided, and a **Steps** tab listing the steps cherry planned when it drafted the procedure. The procedure has not run, and those rows are not a simulation of its output. ## Run a procedure Select the procedure and, if it takes arguments, type them in the box beside **Run**, separated by commas and in the order they were declared. An empty argument is passed as null. Press **Run**. When it finishes, sanda says how long it ran and what it was billed as, for example **Ran in 4.2 s, billed as 4.2 s of compute.** A run started from the page has 55 seconds. If it takes longer, it is stopped, and sanda suggests adding it to a task, which has up to 14 minutes. If the arguments do not fit, sanda says how many it takes and what they are called. ## Keep it running Press **Schedule task** on the object, or use **Save & schedule** when you save it. A task calls the procedure with the arguments you give, on a schedule or after a sync. See [Tasks](https://docs.sanda-os.com.au/modelling/tasks). ## Redefine and drop **Redefine** replaces the procedure. A name is one object, so changing the arguments replaces the procedure rather than adding a second version with a different signature. Each definition is a version, and the old ones stay in the history. **Drop** asks you to confirm. The procedure goes and its definitions stay in the history. A task that calls it skips that step. Tables the procedure built are not dropped. ## History The **History** tab shows every version with the statement sanda ran, every run with its duration, cost and who started it, and the audit records. It is the same tab as in the Explorer. :::links - [Tasks](https://docs.sanda-os.com.au/modelling/tasks): Run a procedure on a schedule. - [Views and modelled tables](https://docs.sanda-os.com.au/modelling/views): For work a single query can do. - [SQL reference](https://docs.sanda-os.com.au/reference/sql): Dialect, limits and worked examples. ::: --- # Tasks > Rebuild materialized views and run procedures in order, on a schedule, after a sync, or on demand, with a full run history. A task is an ordered list of steps in one warehouse. Each step either refreshes a [materialized view](https://docs.sanda-os.com.au/modelling/views) or calls a [procedure](https://docs.sanda-os.com.au/modelling/procedures). A task runs when its schedule comes round, when a connection you chose finishes syncing, or when you press **Run now**. Tasks keep your modelled tables current without anyone remembering to rebuild them. To watch everything that runs on a clock in one place, see [Pipeline and scheduled tasks](https://docs.sanda-os.com.au/modelling/pipeline). Only owners and admins can create, change, run or delete tasks, because every run costs compute. Other members can read them. ![The Tasks tab of Modelling, listing two tasks](https://docs.sanda-os.com.au/media/modelling-tasks.png "The Tasks tab, with each task's state, cadence and last run") ## Before you start A task needs something to run. **Regular views are always current and cannot be scheduled.** You need at least one materialized view or procedure in the warehouse. If there is none, the **Tasks** tab says so and offers **Build a model first**. ## Create a task :::steps 1. **Open the Tasks tab.** In **Data · Modelling**, choose **Tasks**, then **Create a task**. You can also press **Schedule task** on a materialized view or procedure, which starts a task with that object as its first step. 2. **Name it.** Give it a **Name**, such as `nightly rebuild`, and an optional **Description**. A name is unique within a warehouse. 3. **Add steps.** Under **Steps, in order**, choose each step from the menu. A materialized view appears as `refresh derived.` and a procedure as `call derived.`. For a procedure, type its arguments in order, separated by commas. Press **Add a step** for more, up to 20. 4. **Chain steps if they depend on each other.** On every step after the first, **only if the step before succeeded** is ticked by default. Untick it if a step should run even when the one before failed. 5. **Choose when.** Under **When**, pick a **Run frequency**, or connect it to a sync (see below), or leave both empty to run only by hand. 6. **Set the safety limits.** **Pause itself after** is how many failures in a row it takes to pause the task (default 3, and 0 never pauses it). **Maximum run time** is in minutes, from 1 to 14. 7. **Press Create task.** ::: ## Choose when it runs **Run frequency** offers **Manual**, **Hourly**, **Daily** and **Weekly**. | Choice | Options | |---|---| | **Manual** | Runs only when you press **Run now**, or a connection you linked finishes syncing. | | **Hourly** | **Every hour**, **Every 6 hours** or **Every 12 hours**. These run on the hour, in UTC. | | **Daily** | A **Time**, and a **Timezone**. | | **Weekly** | The days to run on, a **Time** and a **Timezone**. | Under the choices, sanda shows the schedule in words and the **Next run**. A daily or weekly schedule stays at the same local time when the clocks change. ### The 15 minute grid sanda's scheduler wakes every 15 minutes, on :00, :15, :30 and :45. A schedule can therefore only name one of those minutes. The **Time** list offers them and nothing else, and sanda refuses a schedule set any other way. A schedule that was stored off the grid still runs, at the next slot: a 9:07 start runs at 9:15. The same applies to **Run now**. It queues the run, and it starts at the next quarter hour, not at once. ### Also run after a sync **Also run after a sync** picks one of your connections. Each time it finishes a sync successfully, the task is queued. This is usually the better trigger: the task rebuilds when the data is new, rather than at a guessed hour. You can use it with a schedule, or on its own. ## What a run does Each run: 1. runs the steps in order; 2. records every step as its own run, with the seconds it took and the compute it was billed as; 3. gives up when the run's time is used up. A step that has no time left is skipped and says so. A step is **skipped**, with the reason recorded, when: - a step it was chained to did not succeed; - the object it points to no longer exists; - the run's maximum time was used up before it. The run then has a status: | Status | Meaning | |---|---| | `succeeded` | Every step succeeded. | | `partial` | Some steps succeeded and some did not. | | `failed` | No step succeeded. | A task never runs against itself. If a run is already queued or running, a schedule that comes round is passed over, and **Run now** tells you a run is already in progress. A run has up to 14 minutes in total, because one run has to fit inside one of sanda's scheduler ticks. Each step gets whatever time is left. ## Read a task Select a task in the list. The header shows its state, and: | Fact | Meaning | |---|---| | **runs** | Its cadence, or the connection it runs after. | | **next** | When it will run next, if it is live. | | **last** | The outcome and time of the last run. | | **steps** | How many steps it has. | | **one run may take** | Its maximum run time. | | **pauses itself** | After how many failures in a row, and how many it has had so far. | Below it are the steps, **What it affects** (each object, the fluid table it is published as, and what reads it), and **Runs**. Each run shows its status, when it started, what started it, how many steps succeeded, how long it took and the compute it was billed as. Open **each step** for the step's own seconds, rows and size, and any error. What started a run appears as the trigger: `schedule`, `console` (**Run now**), `after_sync`, or `mcp` for an agent. ### States | State | Meaning | |---|---| | **live** | It runs on its schedule and after its sync. | | **paused** | You paused it. Nothing fires it until you press **Resume**. | | **suspended** | It paused itself after too many failures in a row. | ## Run, pause, change and delete | Button | What it does | |---|---| | **Run now** | Queues a run. It starts at the next quarter hour. Disabled while the task is paused. | | **Pause** and **Resume** | Stops and restarts its schedule. **Resume** also clears its failure count and lifts a self-pause. | | **Redefine** | Opens the form. Saving replaces the steps and the schedule as a whole. | | **Delete** | Removes the task. Its runs stay in the history. Nothing else is touched. | To change only when a task runs, you can also use **Save run time** on the [Pipeline](https://docs.sanda-os.com.au/modelling/pipeline) page, without opening the form. ## When a task fails When a run does not succeed, the failure counts against the task, and the last run shows the first error, for example `refresh daily_revenue: …`, which names the step and then what went wrong. After the number of failures in a row that you set, the task pauses itself. It shows **suspended**, with **Paused itself** and how long ago, and its schedule stops. The message says: fix what the last run names, then resume it. :::steps 1. **Open the task** and read the error on the last run. Open **each step** to see which one failed. 2. **Fix the cause.** Typical causes are a step's object being dropped, a build running out of time, or a source table changing shape. Fix it in [Modelling](https://docs.sanda-os.com.au/modelling). 3. **Press Resume.** Press **Run now** to check it works before waiting for the schedule. ::: sanda emails your alert recipients when a scheduled run fails or pauses itself, at most once a day for each task. You can switch that off, or add recipients, in **Settings · Plan & budget · Budget & alerts**. ## Tasks and budgets If the workspace is [suspended for its budget](https://docs.sanda-os.com.au/warehouse/usage-and-suspension), nothing is queued. A task does not replay the runs it missed: once the warehouse is resumed it carries on from its next scheduled time. A run that was already queued ends with the suspension message and does not count as a failure of the task. :::links - [Pipeline and scheduled tasks](https://docs.sanda-os.com.au/modelling/pipeline): See every sync, task, index build and delivery on one timeline. - [Views and modelled tables](https://docs.sanda-os.com.au/modelling/views): Build the materialized views a task refreshes. - [Usage, budgets and suspension](https://docs.sanda-os.com.au/warehouse/usage-and-suspension): How task runs are billed. ::: --- # Pipeline and scheduled tasks > See every sync, task, index build and report delivery on one timeline, find what failed, and understand the schedule grid. The Pipeline page is where everything that runs on a clock is watched. It draws your syncs, the [tasks](https://docs.sanda-os.com.au/modelling/tasks) that rebuild your models after them, the search indexes that refresh, and the report deliveries that go out, on one page, so you can see what runs, what it depends on, and when it reaches your team. Go to **Monitor · Pipeline**. Every member can read it. Owners and admins can also change when things run from here. Press **Refresh** at the top right to reload it. ## The four stages The **Your pipeline** panel lays the work out in the order data moves. ![The Pipeline overview with Sync, Transform, Semantic fluid and Deliver stages](https://docs.sanda-os.com.au/media/pipeline-overview.png "The four stages of the pipeline, with each item's state and schedule") | Stage | What is in it | Where you manage it | |---|---|---| | **Sync** ("Bring your sources in") | Your connections, each with how often it syncs. | [Connections](https://docs.sanda-os.com.au/connections/schedules) | | **Transform** ("Run tasks and refresh indexes") | Your tasks, and your search indexes marked **Index**. | [Tasks](https://docs.sanda-os.com.au/modelling/tasks) and [sanda search](https://docs.sanda-os.com.au/search) | | **Semantic fluid** ("Give your data meaning") | A summary of how many published tables are refreshed by tasks. | [The semantic fluid](https://docs.sanda-os.com.au/semantic-fluid) | | **Deliver** ("Reports scheduled in Reports") | Your scheduled report deliveries. | [Delivery](https://docs.sanda-os.com.au/reports/delivery) | The panel's subtitle counts them, for example **2 sources · 2 tasks · 4 indexes · 2 deliveries**. The **Sync**, **Transform** and **Deliver** stages list six items at a time, with arrows to page through. **Sync** and **Transform** also have a **+**, which opens **Connections** or the **Tasks** tab. Each row shows a coloured dot, the name, its state and when it runs. A red dot means the last run failed, or a task is suspended. Amber means paused. Green is fine. To find something, type in **Find a connection, task or delivery…**. The status menu offers **All statuses**, **Needs attention** (failed or suspended), **Active** and **Paused**. ### Selected flow Select a connection, task or delivery and press **Selected flow** to draw just that item with its direct dependencies: the sync a task waits for, the tables it refreshes. **Back to all** returns to the overview. The button is disabled until you select one of those. ### The inspector Selecting an item opens an inspector beside the panel. | Item | What you can see and change | |---|---| | Connection | When it syncs, its next sync and its last one. Owners and admins can change the schedule (**Save sync time**) and start one (**Sync now**). It also lists the tasks that run straight after it. **Open connection** goes to its page. | | Task | Its state, cadence, next and last run. Owners and admins can change its schedule and its sync trigger (**Save run time**), press **Run now**, and **Pause** or **Resume** it. **Open task to change its steps** opens it in Modelling. | | Delivery | Read-only. Times and destinations are managed in Reports: **Manage in Reports**. | | Search index | Read-only. Manage it in sanda search: **Open index**. | | Semantic fluid | The published tables that tasks keep fresh. | Members who are not owners or admins see **Only owners and admins can change when this runs.** ## The account schedule Under the overview, **Account schedule** shows **syncs, tasks, model runs, index builds and deliveries on one clock**, one day at a time. ![The account schedule for one day, with a lane for each sync, task and delivery](https://docs.sanda-os.com.au/media/pipeline-day.png "One lane per item: bars are recorded runs, diamonds are future starts, circles wait for a sync") Read a lane like this: - **Bars** are recorded runs, coloured by outcome. A bar starts when the run started and ends when it finished. - **Diamonds** are future starts, worked out from the current schedule. They appear for today and days to come. - **Circles** are runs that wait for a sync to finish. Their exact start time is not known, because it depends on how long the sync takes. - **The dashed line** is now. Model runs are builds, procedure calls and maintenance statements that ran outside a task. Index builds are sanda search refreshes. You can move around: - **Calendar day** shows 00:00 to 24:00. **Next 24 hours** shows the day ahead from now. Use **Previous day**, **Next day**, the date box and **Today** to move. - The timezone menu chooses between your browser's timezone and **UTC**. A day on which the clocks change has 23 or 25 hours, and the header says so. - **Find an action…** and the type menu (**All actions**, **Syncs**, **Tasks**, **Model runs**, **Index builds**, **Deliveries**) filter the lanes. - **Timeline** shows the lanes. **In time order** lists the same events as an agenda, each tagged **Recorded**, **Scheduled** or **After sync**. Select a bar to open its lane in the agenda. - If more than 2,000 runs were recorded in the window, **Load more recorded activity** fetches the rest. All scheduled starts are always shown. ## The 15 minute grid sanda's scheduler wakes every 15 minutes, on :00, :15, :30 and :45, and everything scheduled is held to that grid: tasks, connection syncs, search index refreshes and report deliveries. A schedule can only name one of those minutes, and the time pickers only offer them. A start set for 9:07 would run at 9:15. The same wait applies to anything you queue by hand. **Run now** on a task queues a run, and it starts at the next quarter hour. A run also has a ceiling, because it has to fit inside one tick: no task runs for longer than 14 minutes. ## Find what failed 1. Set the status menu to **Needs attention**. It shows anything whose last run failed, and anything suspended. 2. Select the item. The inspector shows its last outcome, and for a task the state. 3. Open the item to read the error. A task's runs list the error on the run and on each step. For a connection, see [Monitor syncs](https://docs.sanda-os.com.au/connections/monitor-syncs). For a delivery, see [Delivery](https://docs.sanda-os.com.au/reports/delivery). A task that fails several times in a row pauses itself and shows **suspended**. Fix the cause, then press **Resume**. See [When a task fails](https://docs.sanda-os.com.au/modelling/tasks). Failures are also emailed to your alert recipients, once a day for each task or delivery, if you have that switched on in **Settings · Plan & budget · Budget & alerts**. When the workspace is [suspended for its budget](https://docs.sanda-os.com.au/warehouse/usage-and-suspension), scheduled work is held. Nothing is queued, and once the workspace is resumed each item carries on from its next slot, without replaying what it missed. :::links - [Tasks](https://docs.sanda-os.com.au/modelling/tasks): Create tasks and read their run history. - [Monitor syncs](https://docs.sanda-os.com.au/connections/monitor-syncs): Follow what each connection has loaded. - [Delivery](https://docs.sanda-os.com.au/reports/delivery): Schedule report deliveries. ::: --- # The semantic fluid > One vocabulary for your business, written down once and shared by queries, cherry, reports and AI assistants, so everyone gets the same number. A warehouse holds rows. It does not know that `total` on an invoice is before discounts, that a "paid invoice" is one where `status` is `'PAID'`, or that the `customer_id` on an invoice is the same customer as the `customer_id` on a support ticket. The semantic fluid holds that knowledge. It is the list of tables that matter, the numbers you report, the ways you slice them, and how the tables join, all in your business's own words. You find it in the console under **Intelligence · Semantic fluid**. ## Why it exists Two people asking "what was revenue last month?" get two different numbers unless someone has decided what revenue means. An AI assistant left alone with a table guesses that `amount` is revenue and returns a number nobody can audit. The fluid takes the guess out. You define `net revenue` once. A query, a report, cherry and an AI assistant all use that definition, so they all return the same figure. When the definition changes, every one of them changes with it. Nothing sanda proposes goes live unreviewed. Suggestions from your data wait in a queue, and only definitions you accept reach an answer. See [review what sanda proposes](https://docs.sanda-os.com.au/semantic-fluid/review). ## What it contains | Kind | What it holds | Example | |---|---|---| | Tables | The tables sanda may read, whether each is a fact or a dimension, its grain, key and event time | `invoices`: one row per invoice, keyed on `invoice_id`, timed by `issued_at` | | Metrics | A number defined once, as SQL over one table | `net revenue` is `{gross revenue} - sum(discount_amount)` for paid invoices | | Dimensions | A column you slice by, and how a date rolls up | `issued date` rolls up from day to month to quarter to year | | Filters | Named conditions that say which rows count | `paid invoices` is `status = 'PAID'` | | Relationships | Which tables join, on which columns, and which end holds one row | one `customers` row to many `invoices` | | Aliases | The other words your team uses for a table or a column | `invoices` is also called `sales ledger` | | Value labels | Report labels for stored codes | `AUTHORISED` is shown as `approved` | | Categories | One word for a way of slicing, across every column that carries it | `region`, over two source systems | | Datasets | Groups of tables, each with a colour on the map | `billing`, `support` | | Glue | Column pairs that join two datasets | a contact's email in one system, a customer's email in another | Each kind has its own page. Start with [tables and datasets](https://docs.sanda-os.com.au/semantic-fluid/tables), [metrics](https://docs.sanda-os.com.au/semantic-fluid/metrics) and [relationships](https://docs.sanda-os.com.au/semantic-fluid/relationships). ## Who uses it The same definitions answer every one of these: - **The workbench.** [Query the fluid](https://docs.sanda-os.com.au/semantic-fluid/query-the-fluid) lets you pick a metric, a dimension and a filter and see the rows, with no AI involved. - **cherry.** [Ask cherry](https://docs.sanda-os.com.au/cherry/ask) a question and it looks up your metrics and tables by name, then runs them. - **Reports.** Blocks on a report are built from the same metrics, dimensions and filters. See [reports](https://docs.sanda-os.com.au/reports). - **AI assistants.** Claude, ChatGPT and other assistants connect over [MCP](https://docs.sanda-os.com.au/mcp) and read the same vocabulary. - **Excel and Power BI.** The [OData feed](https://docs.sanda-os.com.au/odata) is built over the fluid. - **sanda search.** A [search service](https://docs.sanda-os.com.au/search) can only index a table that is on the map, and only the columns you put there. ## What "on the map" means The fluid is also a boundary. sanda answers questions only from tables you have put on the map, and only from the columns you ticked when you added each one. A table that is not on the map does not exist to cherry, to a report or to an AI assistant, and a column you left out cannot be read. This is why adding a table is a deliberate step. See [what the agent is told](https://docs.sanda-os.com.au/semantic-fluid/agent-context) for exactly what an agent can see. ## How it relates to the warehouse The warehouse holds your data. The fluid holds what the data means. The fluid stores definitions, never a copy of your numbers: a number is worked out from the warehouse each time someone asks. ```text connections and uploads ↓ land rows in your warehouse (landing tables) ↓ you put a table on the map mapping views: only the tables and columns you chose ↓ read through the semantic fluid: tables, metrics, dimensions, filters, relationships, names ↓ answers the workbench, cherry, reports, AI assistants, Excel and Power BI ``` When you add a table, sanda publishes it as a view named `mapping.
` over exactly the columns you ticked. That view is what questions read. Your landing tables stay untouched. See the [warehouse](https://docs.sanda-os.com.au/warehouse) for how data gets there. While a workspace has no warehouse of its own, the map shows sanda's [learning dataset](https://docs.sanda-os.com.au/reference/glossary#learning-dataset), a sample business you can explore. As soon as you have a healthy warehouse of your own, its map replaces the sample. ## How it stays current - **Review keeps it honest.** Run [Learn from my data](https://docs.sanda-os.com.au/semantic-fluid/learn) again after new data lands. Proposals extend what you have and never overwrite a definition you accepted. - **A synced table's view follows its source.** When a source stops sending a column, sanda rebuilds the table's `mapping` view over what is left, and the card's column list follows. A column a sync adds is not offered to agents until you tick it under **columns…** on the table's card. See [tables and datasets](https://docs.sanda-os.com.au/semantic-fluid/tables#choose-the-columns). - **A definition is a rule, not a stored number.** Change a metric and the next question, report run or agent answer uses the new definition. :::note While a table is being refreshed by a sync, a question over it can briefly answer that the table has no rows attached and its sync is finalising. Ask again in a few minutes, and check the connection's status if it keeps happening. ::: ## Where to go next :::links - [The map](https://docs.sanda-os.com.au/semantic-fluid/the-map): Cards, lines and the panels around them. - [Learn from my data](https://docs.sanda-os.com.au/semantic-fluid/learn): Have cherry read your warehouse and propose a first model. - [Naming rules](https://docs.sanda-os.com.au/semantic-fluid/naming): Why every name is lowercase, and what a name does. - [What the agent is told](https://docs.sanda-os.com.au/semantic-fluid/agent-context): Read exactly what an AI receives. ::: --- # The map > The Semantic fluid page draws your tables as cards and your relationships as lines. Arrange it, select tables and open the panels around it. The Semantic fluid page opens on the map. Each table you have put on the fluid is a card, each relationship is a line between two cards, and everything else (metrics, aliases, filters and so on) opens from a rail of panels on the left. The map is a picture of the fluid, not a separate thing: every card and line on it exists in the fluid, and every change you make on it changes the fluid. Open it from **Intelligence · Semantic fluid**, or [open Semantic fluid](https://console.sanda-os.com.au/app/semantic) directly. ![The semantic map: five table cards, three of them joined by lines, with the rail of panels on the left and the zoom controls on the right.](https://docs.sanda-os.com.au/media/the-map-overview.png "The map, with the rail on the left, the zoom controls on the right and the legend at the bottom.") The whole workspace shares one map. When someone drags a card, everyone else opens the board with that card in its new place. ## What is on the page ![The top of the Semantic fluid page: the Build a model button, the Map and List switch, the Ask cherry to learn button, and the query semantic fluid bar.](https://docs.sanda-os.com.au/media/the-map-header.png "The page header and the workbench bar.") Above the map you have: - **Build a model.** Takes you to [Data · Modelling](https://docs.sanda-os.com.au/modelling/views), where you build views that can then be put on the map. - **Map** and **List.** Two views of the same fluid. The list is for precise edits the map does not offer. See [the list view](#the-list-view). - **Ask cherry to learn.** Asks cherry to read your warehouse and propose a model. See [Learn from my data](https://docs.sanda-os.com.au/semantic-fluid/learn). - **query semantic fluid.** A bar with an **open** button that opens the workbench over the map. See [Query the fluid](https://docs.sanda-os.com.au/semantic-fluid/query-the-fluid). On the map itself you have the rail on the left, the zoom controls on the right, and a legend at the bottom right once there is a line to read. ## Reading a card A card is one table. - **The header** carries the table's name and its relation (for example `mapping.billing__invoices`). The coloured dot on the left is the table's dataset. The **fact** or **dim** badge says whether sanda treats the table as something that happened or something that is. The chevron shows or hides the columns. - **The columns** list each column's name in code style. Key columns come first, then the event time, then the rest in table order. A card shows twelve columns and a **+N more columns** link for the rest. - **The tags** beside a column say what the fluid knows about it: **key**, **time**, **values** (it has value labels), **dim** (a dimension is built on it) and `⋈ customer` (it carries a relationship key). A category shows as a chip. - **The metrics strip** at the foot lists up to four metrics measured on the table, with their units. - **modelled** appears under the name of a table that is a view you built yourself, not one a sync landed. Its link opens the view in Modelling. If the map has five tables or fewer, every card opens with its columns showing. With more, cards start collapsed, showing a one-line summary of columns, metrics and grain. ![A table card for invoices, with its columns, tags and metrics strip.](https://docs.sanda-os.com.au/media/the-map-card.png "One card: header, columns with tags, and the metrics measured on the table.") Hover over a column and a small tag icon appears. It opens a dialog where you give the column a name, other words people use for it, and any categories. See [naming rules](https://docs.sanda-os.com.au/semantic-fluid/naming#aliases) and [dimensions and filters](https://docs.sanda-os.com.au/semantic-fluid/dimensions-and-filters#categories). ## Move around - **Pan** by dragging the empty canvas. - **Zoom** with the plus and minus buttons. The map runs from 25% to 250%, and the current level shows beneath the buttons. The mouse wheel scrolls the page and does not zoom the map. - **Fit to content** (the arrows button) frames every card. ## Move a card Drag a card by its header. A dashed outline shows where it will land: cards snap to a grid, and a card dropped on top of another is moved clear of it, so no table can be hidden under another. Drag to the edge of the map and it scrolls to follow you. The new position is saved for everyone. ## Tidy the map Once the map has more than one table, the four squares button, **arrange by relationships**, lays out every card by how the tables join. Tables that join others are placed from a hub outward: the fact table with the most joins sits in the middle, its neighbours to the left and right, and lookups (usually dimensions) on the outside. Tables that join nothing sit in a grid underneath, grouped by dataset. It replaces your own arrangement, and only positions change. No table's kind, dataset or columns are touched. The same layout always comes out for the same fluid. When there are joins to show and cards that have never been arranged, a tip appears beside the button: **See how your tables join**. Press the button, or dismiss the tip with its cross. The tip comes back when a learn pass adds new tables. ## Select tables - Click a card to select it. A ring shows the selection, and a strip at the foot of the map says what is selected. - Hold Shift (or ⌘) and click to add a card to the selection or take it out. - Click empty canvas, or press Escape, to clear it. - Press Delete to take the selected tables off the map. sanda asks first, naming them, with **remove** and **cancel**. See [taking a table off the map](https://docs.sanda-os.com.au/semantic-fluid/tables#take-a-table-off-the-map). Escape undoes one layer at a time: a confirmation, then a dialog, then an open panel, then a selected line, then the card selection. Delete does nothing while you are typing in a field or while a panel is open. ## Relate two tables Drag from a column on one card onto a column on another. Columns whose types would join without a cast light up while you drag, and the others dim. Dropping opens a dialog to name the relationship and say which end holds one row. Click any line to see what it is and to unlink it. See [relationships](https://docs.sanda-os.com.au/semantic-fluid/relationships). ## The rail Each card on the rail opens a panel over the middle of the canvas, and the map behind it does not move. The rail stays above the panel, so you go from one panel to the next in one press. | Card | Opens | Read more | |---|---|---| | **suggestions** | Everything sanda has proposed and is waiting on you. Shown only when something is waiting. | [Review what sanda proposes](https://docs.sanda-os.com.au/semantic-fluid/review) | | **new table** | The picker for adding tables from your warehouse | [Tables and datasets](https://docs.sanda-os.com.au/semantic-fluid/tables) | | **datasets** | Groups of tables, and the colour each paints on the map | [Tables and datasets](https://docs.sanda-os.com.au/semantic-fluid/tables#datasets) | | **relationships** | Every join, with which end holds one row | [Relationships](https://docs.sanda-os.com.au/semantic-fluid/relationships) | | **aliases** | What your team calls each table and column | [Naming rules](https://docs.sanda-os.com.au/semantic-fluid/naming#aliases) | | **categories** | One word for a way of slicing, over many columns | [Dimensions and filters](https://docs.sanda-os.com.au/semantic-fluid/dimensions-and-filters#categories) | | **metrics** | What your numbers mean | [Metrics](https://docs.sanda-os.com.au/semantic-fluid/metrics) | | **filters** | Named conditions | [Dimensions and filters](https://docs.sanda-os.com.au/semantic-fluid/dimensions-and-filters#named-filters) | | **glue** | Joins between two source systems | [Relationships](https://docs.sanda-os.com.au/semantic-fluid/relationships#join-two-source-systems) | Each card shows a count of what it holds. The counts for aliases, metrics and filters leave out suggestions, so a card never says you have eleven aliases when you have three and eight suggestions. Every panel has a search box in the same place. Selecting a card on the map does not narrow a panel's list, so clicking a card to look at it never empties the panel you open next. Press Escape, the cross, or the shaded canvas to close a panel. ## The list view Press **List** for the same fluid as lists. There is a tab for each of **mappings**, **metrics**, **dimensions**, **categories**, **filters** and **tables**, each with an add form and a list of what exists. The pencil on a row opens the form on that row's current values, and typing a name that already exists fills the form in the same way, so saving corrects the row rather than blanking what you left empty. Use it for what the map does not offer: value labels, join keys typed by hand, dimensions and their roll-ups, and a table's grain, key and event time. In the list view, the **Add tables** button opens the same picker as the **new table** card, and suggestions wait in a panel headed **N waiting on you** rather than on the rail. ## An empty map A workspace with nothing on its map says so. If a learn pass has left suggestions, the message counts them and offers **review the suggestions**. If nothing has been proposed, it offers **add tables from my warehouse**. ## Keep going :::links - [Tables and datasets](https://docs.sanda-os.com.au/semantic-fluid/tables): Put tables on the map and group them. - [Relationships](https://docs.sanda-os.com.au/semantic-fluid/relationships): Join tables and set which end holds one row. - [Query the fluid](https://docs.sanda-os.com.au/semantic-fluid/query-the-fluid): Ask a question from the map. ::: --- # Learn from my data > Ask cherry to read your warehouse and propose tables, metrics, dimensions, relationships and names for you to review. It uses AI, which is billed. Learn from my data gets you a first model without typing one. cherry reads the shape of your warehouse, asks an AI to describe what it means, and files what comes back as suggestions. Nothing goes live until you accept it, so the worst outcome of a bad pass is a queue you reject. ## Before you start - **Rows have landed.** Sync a [connection](https://docs.sanda-os.com.au/connections/add-a-connection) or [upload a CSV](https://docs.sanda-os.com.au/connections/upload-a-csv) first. A connection that has never run has nothing to read. - **cherry has a model to run on.** If the button is greyed out and its tooltip says cherry has no model to run on yet, an owner or admin can check **Settings · cherry**. - **cherry may work with your data.** The **data** switch under **Settings · cherry** covers running a learn pass. - **You are an owner, admin or member.** cherry changes nothing for a viewer. - **Today's AI limit is not used up.** A pass is AI usage. See [cost and time](#cost-and-time). ## Run a pass :::steps 1. **Open the Semantic fluid page.** In the console, go to **Intelligence · Semantic fluid**. 2. **Press Ask cherry to learn.** cherry's pane opens on a new conversation, and cherry starts work at once. The same request sits behind **Learn from my data** on a connection's page (while none of its tables are on the map), on the finish screen of a CSV upload, and in cherry's setup checklist. Those send it in the conversation you are already in. 3. **Watch it read.** cherry shows a step for each connection, such as "reading Billing (1 of 2)". Once every connection is read, it shows "finding the relationships across your map". 4. **Read the card.** Under cherry's words, a card headed "learned from N tables" reports what was proposed. cherry adds what stands out and what it could not settle. 5. **Review the suggestions.** The suggestions panel opens on the map. See [Review what sanda proposes](https://docs.sanda-os.com.au/semantic-fluid/review). ::: You can also ask in your own words: "learn from my Billing connection" reads one connection alone. ## What a pass reads For each table, sanda reads a description of its shape: - The table's name, its columns and their types, and an approximate row count. - Up to six short sample values per column, taken from a small sample of rows. Each value is shortened. - A check of the table's key against a sample of its rows, and a measurement of likely joins. These are counts only, never values. A pass considers at most the 60 largest tables in your warehouse, and up to 120 columns of any one table. Within a wide table it keeps key columns first, then columns you have already named, event times, id-like names and numbers. The AI is told how many columns were left out. The loader's own bookkeeping columns are never read. If your warehouse holds more than 60 tables, the smallest are left out. Add those yourself from **new table** on the map. A pass that reads one connection applies the limit to that connection's own tables, so a small connection beside a large one is read in full. Columns whose names suggest a secret, such as a password, token, API key, card number or tax file number, contribute their type and nothing else. :::caution The description, sample values included, is sent to an AI model to write the proposals. Bear that in mind for tables that hold personal information. ::: ## What it proposes | Proposal | What you get | |---|---| | Tables | A table worth querying, with a name, whether it is a fact or a dimension, its grain, its key columns and its event time column | | Dimensions | A column worth slicing by. Dates get a grain and a roll-up, and category columns get sample values | | Metrics | A number defined as SQL over one table, such as `sum(gross_amount) - sum(discount_amount)` | | Names | Business names for columns, and value labels for stored codes | | Relationships | Joins between tables, each with which end holds one row | | Questions | What only you can settle: the currency, the start of your financial year, whether a status code means what its name says | Every proposal carries a confidence between 0 and 1. Near 0.9 means the data made it plain, around 0.5 means a reasonable reading of a name, and lower means it is worth a glance. Tables are also filed into their connector's [dataset](https://docs.sanda-os.com.au/semantic-fluid/tables#datasets) and given a place on the map. Relationships come from two sources. First sanda proposes the joins your schema states: a column named like another table's key, or a foreign key the source declares. Then it asks the AI about the rest, with the measured evidence in front of it. See [Relationships](https://docs.sanda-os.com.au/semantic-fluid/relationships#how-sanda-finds-them). A pass does not propose named filters, categories or glue. Add those yourself. Table aliases come from **suggest aliases** on the map, not from a pass. ## How sanda checks it before you see it - Proposals go through the same checks as a form you fill in by hand. A metric must be a single SQL fragment on one table, and a column that already has a dimension cannot get a second one. - Each metric is test-planned against its real table, which reads no rows. A metric that does not run against its table, for a column that does not exist say, is dropped with the reason. A metric that builds on another metric cannot be checked this way and is counted as unchecked. - A relationship that names a column sanda did not show the AI is dropped. - A join whose names match but whose values do not line up is not proposed. Fewer than half of the sampled values found in the other table's key counts as not lining up. - Accepted definitions are never overwritten, and a suggestion you rejected is not proposed again under the same name. The card lists what was dropped under **N dropped · show why**. ## Read the result The card reports, in this order: 1. What was proposed: tables, dimensions, metrics and mappings, and from how many tables. It notes how many were grouped into a dataset and given a place on the map. 2. Relationships: how many were drawn from matching keys and how many sanda proposed. If there were none, a **suggest relationships** button offers another try. 3. Metrics that could not be checked, and **N dropped · show why** for proposals that were dropped. 4. **sanda could not settle these**, the open questions. 5. **sanda's notes**, and any connection the pass did not run for. When something was proposed, **review the suggestions** opens the queue. ## Cost and time AI is billed. Every model call in a pass is metered as AI usage, priced by the token, and counted against your daily AI limit. The default limit is A$5 a day, and owners and admins can raise it as far as A$50. A trial's free credit pays for a pass first. See [usage](https://docs.sanda-os.com.au/billing/usage) and [budgets and limits](https://docs.sanda-os.com.au/billing/budgets-and-limits). A pass makes one AI call for each connection it reads, one after another, then one relationship search across the whole map, which asks the AI about the joins your names did not settle. A workspace with four sources pays for one relationship search, not four. cherry's own messages in the conversation are AI usage too. It can take a few minutes, and longer with many connections. The result stays in cherry's conversation, so you can reopen it later. If today's limit is used up, the pass stops and says so. The limit resets at midnight UTC, or an owner or admin can raise it. ## Run it again Run a pass again whenever new data lands. It extends what you have and skips anything already on the map. The map has smaller AI-assisted actions that use the same reading: - **suggest aliases** in the aliases panel proposes the words your team would use, and metrics your data makes measurable. - **suggest relationships** in the relationships panel searches for joins. - **describe a metric** in the metrics panel drafts a metric from a sentence, as a form you edit. - **Map with AI** in the glue dialog proposes column pairs between two datasets. Nothing is saved until you press **Create glue**. Each is billed the same way. ## If nothing was proposed - **Nothing has landed yet.** cherry says so. Sync first. - **Everything is already on the map.** cherry says nothing new was proposed. - **The daily limit is used up.** The card names the limit and when it resets. - **A connection failed.** The card names the connection and the reason, and the other connections still count. See [troubleshooting](https://docs.sanda-os.com.au/help/troubleshooting) if a pass keeps failing. :::links - [Review what sanda proposes](https://docs.sanda-os.com.au/semantic-fluid/review): Accept, correct or reject each suggestion. - [Ask cherry](https://docs.sanda-os.com.au/cherry/ask): Talk to cherry about your data. - [Models and usage](https://docs.sanda-os.com.au/cherry/models-and-usage): Which model cherry runs on and what it costs. ::: --- # Review what sanda proposes > Everything sanda proposes waits in one queue. Accept, correct or reject each suggestion, accept the lot, or ask cherry to do it. sanda never writes its own guesses into your fluid. What it proposes from your data becomes a [suggestion](https://docs.sanda-os.com.au/reference/glossary#suggestion) in a review queue, and only what you accept reaches an answer, a report or an AI assistant. That gate is what makes it safe to let sanda learn your data: the worst outcome is a queue of rejects, not a wrong number in a board pack. ## Where suggestions wait On the map, a **suggestions** chip appears at the top of the rail whenever something is waiting, with a count. Press it to open the queue, headed "N suggestions waiting on you". cherry opens the same queue when a [learn pass](https://docs.sanda-os.com.au/semantic-fluid/learn) has proposed something. ![The suggestions panel over the map, listing tables, a relationship, metrics and names, each with accept and reject buttons.](https://docs.sanda-os.com.au/media/review-panel.png "The review queue: grouped by kind, with a confidence beside each suggestion.") In the list view there is no rail. The same suggestions wait in a panel headed **N waiting on you**, with **Edit**, **Accept** and a reject button on each row. A suggestion is in one of three states: | State | What it means | Reaches answers | |---|---|---| | Waiting | sanda proposed it and you have not decided | No | | Accepted | It is live in the fluid | Yes | | Rejected | It is retired: kept, but out of the map and out of every answer | No | ## What is in the queue Suggestions are grouped by kind, in this order: tables, relationships, mappings (names and value labels), dimensions, metrics, filters and categories. Each row shows the suggestion's name, a line saying what it is (a relation, a SQL expression, a pair of columns) and a confidence from 0 to 1, which is the AI's own estimate. For a metric or a relationship it also shows the reason: the column and values that made it possible, such as "100% of 412 sampled values found there". Read the reason. A suggestion whose reason you can check in a glance is one you can accept in a glance. A relationship is one row, even though it is stored as one entry per table. Both sides are decided together, because half a relationship is a labelled column and no join. ## Decide one suggestion - **Accept.** Press **accept**. The suggestion goes live at once. An accepted table appears on the map as a card. - **Reject.** Press the cross. The suggestion is retired. sanda does not propose it again under the same name. - **Correct, then accept.** Press the row to open its form, change what is wrong, and press **accept with these changes**. This is the most common verdict: the metric you wanted, summing the wrong column. The correction and the acceptance happen in one step, so a wrong definition is never live, even briefly. A correction faces the same checks as anything you type. A metric must still be one SQL fragment, and a name that already exists is refused with "You already have a metric by that name. Rename this one, or reject it and edit the one you have." ### Correct a relationship A relationship's form has three parts: - **Shared identity (join key)**, the name of the thing both columns hold, such as `customer`. - **Shown as**, the words used to describe the join. - For each table, whether it holds **one row** or **many rows** per value of the key. At least one side has to hold one row. sanda does not join many to many, because a total across such a join is silently too large. See [Relationships](https://docs.sanda-os.com.au/semantic-fluid/relationships#cardinality). ## Work through the queue from the keyboard ↓ and ↑ (or J and K) move between suggestions. Enter accepts the highlighted one, and Delete rejects it. These stand down while you are typing in a form. A decided suggestion leaves the list at once and the request settles behind it, so the panel does not shift under your pointer. If a decision fails, the suggestion returns with the reason. Use the search box to narrow the queue by a name, a column or a reason. ## Accept them all Press **accept all N**. sanda asks once more with **yes, accept all N** and a reminder that every suggestion goes live and enters what the agent is told. You can still edit or delete them afterwards. Several accepts run at once. Tables go through one at a time, so two new cards cannot land on the same spot. If some are refused, they stay in the list and the first reason is shown. If you have typed in the search box, the button reads **accept these N** and accepts only what the search left showing. :::caution Accept all signs a model's whole output in one press. Read the tables and relationships first, then accept the rest. ::: When the queue is empty, the panel offers **tidy the map**, which [arranges the cards by how they join](https://docs.sanda-os.com.au/semantic-fluid/the-map#tidy-the-map), so you can check the relationships at a glance. After that, the natural next step is a first report. Ask cherry to [build one](https://docs.sanda-os.com.au/reports/build-with-cherry). ## Let cherry accept for you Ask cherry in plain words: "accept the suggestions", or "accept the proposed tables and the relationships between them". cherry reads the queue, then accepts or rejects each item. It accepts the tables a relationship needs before the relationship, and it can pass a correction along with an accept. What happens next depends on **Before it changes something** under **Settings · cherry**: - **Ask first** (the default). Each change cherry proposes waits in the conversation as a card with **Confirm** and **Decline**. Several of one kind arrive as one card with a row each, and **Confirm all N** and **Decline all** at its foot. - **Autonomous.** cherry accepts as soon as it decides to. The **semantics** switch under the same settings decides whether cherry may review suggestions at all. See [actions and approvals](https://docs.sanda-os.com.au/cherry/actions-and-approvals). ## What to check before you accept - **Tables.** Is it a fact or a dimension, and is the grain right? A wrong grain is how an invoice line gets counted as an invoice. - **Metrics.** Read the SQL and the reason. `sum(total)` is only revenue if `total` is what you mean by revenue. - **Relationships.** Check which end holds one row. Reversing it produces totals that are too large and look reasonable. - **The questions cherry could not settle.** The currency, the start of your financial year, and whether a status code means what its name says are things only you can answer, and they are what make a number quietly wrong. :::links - [Metrics](https://docs.sanda-os.com.au/semantic-fluid/metrics): What a good metric definition looks like. - [Relationships](https://docs.sanda-os.com.au/semantic-fluid/relationships): Shapes, keys and fixing a wrong join. - [Actions and approvals](https://docs.sanda-os.com.au/cherry/actions-and-approvals): How cherry asks before it changes something. ::: --- # Tables and datasets > Put tables from your warehouse on the map, choose their columns, set fact or dimension, group them into datasets and take them off again. A table on the map is one table from your warehouse, plus what sanda knows about it: a name, whether it is a fact or a dimension, the grain of a row, its key, its event time, its columns and the dataset it belongs to. Everything else in the fluid hangs off a table. A metric is measured on one, a dimension reads a column of one, and a relationship joins two. Two words are easy to confuse. A **table** is one table in your warehouse. A **dataset** is a group of tables that you define, such as "the Xero data". ## Add tables from your warehouse :::steps 1. **Open the picker.** On the Semantic fluid page, press **new table** on the rail. In the list view, press **Add tables**. The dialog, **Add tables from your warehouse**, lists everything that has landed, read live. Tables are grouped by the connection they came from. The filter box narrows by name or relation, and a group heading folds. Tables already on the map show **on the map** and cannot be ticked again. 2. **Tick the tables you want.** Each row shows the name sanda would give the table, a **fact** or **dim** badge, its relation, its column count and an approximate row count. **Select all** ticks a whole group. Press the badge to flip a wrong guess before the table is added. 3. **Choose columns if you need to.** Press a table's name to open its columns. See [choose the columns](#choose-the-columns). 4. **Pick a dataset.** Under **Group into**, keep "its connector's dataset", or pick another. 5. **Press Add N tables.** You can add up to 60 tables at once. ::: ![The Add tables from your warehouse dialog, with one table ticked and its columns open.](https://docs.sanda-os.com.au/media/tables-picker.png "The picker: tables grouped by connection, a badge to flip fact or dimension, and a column list with the key and event time locked.") Added tables are live at once. They are not suggestions. sanda publishes each one as a view named `mapping.
` over the columns you chose, gives it a card on the map, and files it into its dataset. Adding a table also looks for the joins its column names state, such as `customer_id` on an invoice and `customer_id` as the key of your customers. From this picker those are proposed, not created: they wait in the [review queue](https://docs.sanda-os.com.au/semantic-fluid/review). When cherry adds tables for you, it draws those joins straight away as part of the change you confirm, and lists them in its reply. Only tables that have landed can be added. If the list is empty, it says "Nothing has landed yet. Once a connection completes its first sync, or a CSV is loaded, its tables show up here." See [connections](https://docs.sanda-os.com.au/connections). If sanda cannot publish a table, the dialog says so after adding it: the table is on the map, but answers over it fail until it can be published. Open the picker and add it again to repair it. A table flagged **needs publishing** is in this state. ## What sanda guesses Every table starts with mechanical first guesses, and all of them are yours to change. | Property | The guess | |---|---| | Name | The connection's stream name, lowercased, with underscores and hyphens turned into spaces. A second `invoices` from another connection becomes `invoices (shopify)`, then `invoices 2` | | Fact or dimension | From the name: invoices, payments and orders read as facts, and customers, products and employees as dimensions. Failing that, a table with a date and a number is a fact and anything else is a dimension | | Key | A column named `id`, or after the table, such as `invoice_id`. sanda then counts distinct values in a sample of the rows. A column that repeats is not stored as a key, and a table whose ids are only unique together gets that pair as its key, which marks it as a bridge between two tables. A table with nothing unique gets no key | | Event time | The first of the usual names (`issued_at`, `occurred_at`, `invoice_date` and so on), otherwise the first date or time column | | Dataset | Its connection's group | ## Fact or dimension The kind decides what an agent may do with a table. A [fact](https://docs.sanda-os.com.au/reference/glossary#fact) records something that happened and carries numbers to add up: invoices, payments, orders. A [dimension](https://docs.sanda-os.com.au/reference/glossary#dimension) records something that is and is what you slice by: customers, products, currencies. sanda aggregates facts, labels and groups by dimensions, and never sums a dimension. That rule stops an agent adding up a price list. Change it three ways: press the **fact** or **dim** badge on the card, use **This table is a** in the card's menu (the dot in the header), or press the badge in the picker before adding. ## Grain, key and event time Three properties tell sanda what one row is. - **Grain** is a sentence: "one row per invoice line". It is what prevents double counting, because an agent reads it before it aggregates. - **Key columns** identify one row. They also decide which end of a join holds one row, and they are how sanda counts a table's rows once when a join repeats them, as when orders are counted beside their order lines. See [relationships](https://docs.sanda-os.com.au/semantic-fluid/relationships#cardinality). - **Event time** is the column that "last month" filters by. **Notes** is a separate field: a sentence about what the table holds. The agent's full text carries it as the table's description. To change them, open the list view, go to the **tables** tab and press the pencil on the table's row. The form opens with the table's current values in **Table**, **Relation**, **What kind of table**, **One row is**, **Key columns**, **Event time column**, **Dataset** and **Notes**. Change what is wrong and press **Save table**. Typing the name of a table that already exists into **Add table** does the same: the form fills in with what is stored, says the table is already stored, and saving updates it. A field you clear is cleared. What the form has no field for, such as the row count and the column list, is kept. Or ask cherry: "the grain of invoices is one row per invoice line". cherry changes only the fields you name. ## Choose the columns A ledger can arrive with well over a hundred columns, of which only a handful are ever reported on. Adding only those makes the card readable and the agent's context short, and it keeps the rest unreadable to any agent. :::steps 1. **Open the columns.** Press the dot in a card's header and choose **columns…**. The dialog, **columns on invoices**, reads the table fresh from the warehouse, so columns a later sync added are offered too. 2. **Tick what should be readable.** **all** and **none** set every box at once. The key and the event time are ticked and locked, because a table cannot state its grain or answer "last month" without them. 3. **Press save.** The button reads **save N columns**, and sanda rebuilds the table's `mapping` view over exactly what is ticked. ::: What you untick is dropped from the `mapping` view, not from your warehouse. An agent can no longer read it, and a metric that uses it can no longer run. ## Datasets A [dataset](https://docs.sanda-os.com.au/reference/glossary#dataset) is a group of tables. Every stream from one connection starts in that connection's group, and a table you have moved by hand stays where you put it, even when you add more tables later. A dataset gives its tables a colour on the map. The agent's full context lists which tables belong together, and [glue](https://docs.sanda-os.com.au/semantic-fluid/relationships#join-two-source-systems) joins two datasets. Open the **datasets** panel from the rail to manage them: - **Create one.** Type a name in **New dataset** and press **add dataset**. sanda gives it the first unused colour of eight: rose, peach, butter, sage, mint, sky, lilac and mauve. - **Recolour it.** Press one of the colour dots on the dataset's row. - **Move a table.** Use the menu on the table's row, or the **Dataset** list in the card's menu. **No dataset** takes it out of any group. - **Find your way.** Hovering over a dataset dims every card outside it on the map. - **Delete it.** The bin on its row deletes the group. Its tables stay on the map, ungrouped. Tables that are in no dataset are listed under **Ungrouped**. ## Views you build A view or materialized view you build in [Modelling](https://docs.sanda-os.com.au/modelling/views) reaches the map the same way a landing table does, and the picker lists it under **Modelled tables**. On the map it carries **modelled** under its name. sanda lists modelled tables first in what an agent is told, and tells the agent to prefer one when it can answer, because it is a shape you decided on rather than the shape a source arrived in. ## Take a table off the map Select the card, press Delete, and confirm with **remove**. sanda names the tables it is about to remove first. Removing a table retires it. Nothing is deleted, but three things happen: - The table leaves the map and everything an agent is told. - Its relationships, aliases and dimensions, and the metrics measured on it, are retired with it. A metric that is not measured on the table, but builds on a retired metric with `{name}`, is flagged as unresolved, and the agent is told not to use it. - Filters and categories written against it stop applying. Add the table again from the picker and it comes back with its old name and dataset. Its metrics and relationships stay retired. Define them again and they return under their old names. The list view's **tables** tab also has a bin on each row, and it retires the table in the same way, with its relationships, aliases, dimensions and metrics. A retired table stays in the list marked **retired**, without a bin. If you ask cherry to take a table off the map, it always asks first, even in Autonomous mode. ## Wipe the fluid At the foot of the Semantic fluid page, **Wipe semantic fluid** starts again from an empty map. Owners and admins can use it. :::danger Wiping permanently deletes every table, dataset, metric, relationship, alias and suggestion, and the map's layout. Agents and reports lose this context straight away. Your connections, warehouses and data are not touched. There is no undo. sanda asks you to type `wipe` to confirm. ::: The button is not offered while the map is showing the learning dataset. Remove that from **Data · Warehouse** instead. :::links - [Metrics](https://docs.sanda-os.com.au/semantic-fluid/metrics): Define numbers on your tables. - [Relationships](https://docs.sanda-os.com.au/semantic-fluid/relationships): Join tables together. - [Naming rules](https://docs.sanda-os.com.au/semantic-fluid/naming): How table names are formed. ::: --- # Metrics > Define a number once, as SQL over one table, and every query, report and AI assistant uses that definition. Build metrics out of other metrics, across tables too. A [metric](https://docs.sanda-os.com.au/reference/glossary#metric) is a number you have defined once. `net revenue` is gross revenue less discounts, for paid invoices, in Australian dollars. Say that here, and every query, report, cherry answer and AI assistant uses it instead of working out its own version. A metric is written the way you would write SQL for one table, with bare column names and no schema prefix. It is measured on one table, the one you pick when you define it. | Field | Example | What it is for | |---|---|---| | Name | `net revenue` | What people call the number. It is also how other metrics refer to it | | Expression | `{gross revenue} - sum(discount_amount)` | The definition: one SQL fragment over the table's columns | | Only counting rows where | `status = 'PAID'` | A condition that belongs to this number | | Table | `invoices` | The table the number is measured on | | Unit | `AUD` | Shown beside the number | | Also called | `net sales` | Other words for it | | Notes | `Paid invoices, after discounts.` | What it is for and what it excludes | Open the panel from the rail: press **metrics** on the map. The panel lists every metric with its expression and the table it is measured on. ## Define a metric There are two routes, both in the metrics panel. ### Write one :::steps 1. **Press write one.** A form opens at the top of the panel. You need at least one table on the map. 2. **Name it.** Type the name, for example `total revenue`. Capitals are lowercased when you save. See [naming rules](https://docs.sanda-os.com.au/semantic-fluid/naming). 3. **Write the expression.** For example `sum(gross_amount)`. Under the box, **Insert** chips add a reference to an existing metric in one press. Once a table is picked, the chips offer only metrics measured on that table. Before one is picked, pressing a chip also picks its table. 4. **Add a condition if the number needs one.** In the field that reads "only counting rows where", type `status = 'PAID'`. 5. **Pick the table.** The **which table is it measured on?** list is required. An expression such as `sum(amount)` means a different number on every table that has an `amount`, so sanda will not guess. 6. **Add a unit, other words and notes if you want them.** Then press **save metric**. ::: ### Describe it If you would rather say what you want, press **describe a metric**, type a sentence such as "refund rate by month" or "the share of orders that came back", and press **draft it**. If the map has categories, chips under **Group by** (up to six) add "by region" and the like to your sentence. sanda drafts the metric from your own tables and columns, in an editable form headed "sanda's draft · check it". It lists the columns the draft reads and how confident it is. Check the SQL, correct it, then choose: - **save metric** puts it live in your metrics. - **save for review** parks it in the [review queue](https://docs.sanda-os.com.au/semantic-fluid/review) instead, for a second pair of eyes. If your data cannot support what you described, sanda says exactly what is missing (a column, a table, a join) instead of inventing something. The draft is an AI call, and it is billed like any other. See [Learn from my data](https://docs.sanda-os.com.au/semantic-fluid/learn#cost-and-time). ## Write the expression - **One SQL fragment.** An aggregate, or arithmetic over aggregates. No semicolon, and no comment (`--` or `/* */`). It can be up to 2,000 characters. A fragment that cannot contain those cannot end the statement it is placed in. - **Columns of one table.** Use bare names such as `unit_price`, never `mapping.products.unit_price`. - **Constants are safe.** Text inside quotes, such as `'PAID'`, is left alone. ```sql title="Examples" sum(unit_price * quantity) count(distinct customer_id) avg(total) count(*) filter (where status = 'refunded')::numeric / nullif(count(*), 0) ``` When several tables are in one question, sanda writes each column out in full before it runs the query, so two tables that both have a `unit_price` never make it ambiguous. If a word in a definition is a column of another table in the same question, sanda stops and says which definition, which word and which two tables, and suggests the fix: write the column with its table name, or measure the metric on the other table. ## Build on other metrics Refer to another metric by putting its name in braces. `net revenue` can be `{gross revenue} - sum(discount_amount)`. Before anything is run, sanda expands the reference, so `net revenue` arrives as `(sum(total)) - sum(discount_amount)`. You write `gross revenue` once and reuse it everywhere. - Names match without regard to case, and spaces around the name are ignored: `{ Net Revenue }` and `{net revenue}` are the same reference. - A metric cannot be built out of itself, directly or through another metric. sanda detects the loop, leaves the reference unexpanded, and tells the agent not to use the metric. sanda also refuses to run a metric whose reference names something that is not defined. - A referenced metric keeps its own condition. `paid share` can be `{net revenue} / nullif({gross revenue}, 0)`: the top half counts only paid invoices, as `net revenue` does, and the bottom half counts them all. - A metric can be built from metrics on other tables, as long as a [relationship](https://docs.sanda-os.com.au/semantic-fluid/relationships) joins them. sanda works out each part on its own table and then does the arithmetic: with `revenue` summed over the order lines and `order count` counted over the orders, `average order value` is `{revenue} / nullif({order count}, 0)`, and each order is counted once however many lines it has. Without a relationship between the tables, the [workbench](https://docs.sanda-os.com.au/semantic-fluid/query-the-fluid) and `run_query` refuse it and say which join to draw. - In a metric that is worked out table by table, keep every column inside an aggregate. Arithmetic around the references, such as `nullif(..., 0)` or `::numeric`, is fine. A bare column outside `sum()` or `count()` belongs to no one table's part, so sanda refuses it and names the metric. To rename a metric, open it, press **edit**, change the name and save. sanda repoints every metric that refers to the old name, and tells you which ones before you save. Deleting a metric that others refer to leaves those metrics unresolved. ## Filters on a metric The field "only counting rows where" is that number's own condition. `net revenue` above counts only paid invoices. It belongs to that one number. When it is the only number in a question, or every number shares it, the condition becomes the query's `where`. Beside numbers that do not share it, sanda writes it onto that number alone, as `filter (where status = 'PAID')`, so `net revenue` and `invoice count` side by side give paid revenue and every invoice. Two metrics whose conditions disagree, such as paid and draft revenue, sit side by side the same way. When several metrics, or many questions, need the same condition, name it once as a [filter](https://docs.sanda-os.com.au/semantic-fluid/dimensions-and-filters#named-filters) and use it by name. ## Unit, period and display - **Unit** is text such as `AUD`. It shows beside the metric on cards and in what the agent is told. - **Also called** lists other words for the number. A word that names exactly one metric finds it. If two metrics both answer to `revenue`, that word is ambiguous and finds neither. The list view's **metrics** tab has three more fields: - **Reported per** is the natural period, such as `month`. The agent is told "per month". - **Shape** records how the number is built: sum, average, count, distinct count, minimum, maximum, ratio or something else. - **Shown as** records how it should read: plain number, currency, percent, whole number, decimal or duration. The agent's text uses the definition, the unit and the period. Shape and display are kept with the metric and included in the JSON download of what the agent is told. On a report, **Shown as** and **Unit** decide how the metric's figures read on kpi, bar and line blocks, on the canvas, a shared link, the reports portal and the email alike. See [blocks](https://docs.sanda-os.com.au/reports/blocks). ## What sanda does with a metric Ask for `net revenue` by `region` and sanda writes this, from your definitions, with no AI involved: ```sql title="What sanda composes" select (sum("mapping"."billing__invoices"."total")) - sum("mapping"."billing__invoices"."discount_amount") as "net_revenue", "mapping"."billing__customers"."region" as "region" from "mapping"."billing__invoices" left join "mapping"."billing__customers" on "mapping"."billing__customers"."customer_id" = "mapping"."billing__invoices"."customer_id" where ("mapping"."billing__invoices"."status" = 'PAID') group by 2 order by 1 desc limit 16 ``` The reference was expanded, the condition became a `where`, the join followed a relationship you signed off, and the number is grouped by region, largest first. The join matters. Reaching from many invoices to one customer is a lookup, so every invoice keeps exactly one customer and the total is the same total. It is a `left join`, so an invoice whose customer is missing is kept, in a group with no region, rather than dropped from the total. The other direction is different, because one customer's columns repeat once per invoice. When a question puts a number measured on customers beside the invoices, sanda counts each customer once by the table's key columns, in a part of the query of its own, and lines the two up by region. When a question would need two tables that each have many rows per customer joined to each other, such as invoices and payments, sanda declines and says why rather than return a number that is too large: > sanda won’t join mapping.billing__payments to mapping.billing__customers here: one mapping.billing__customers row matches many mapping.billing__payments rows, so every total on the one-row side would be counted once per match and come back too large. Build this from the mapping.billing__payments side instead, or ask for the two tables separately. A refusal is an answer about the shape of the question. See [relationships](https://docs.sanda-os.com.au/semantic-fluid/relationships#why-the-shape-matters). ## More examples | Name | Table | Expression | Condition | |---|---|---|---| | `gross revenue` | `invoices` | `sum(total)` | | | `net revenue` | `invoices` | `{gross revenue} - sum(discount_amount)` | `status = 'PAID'` | | `invoice count` | `invoices` | `count(*)` | | | `average invoice value` | `invoices` | `avg(total)` | | | `refund rate` | `orders` | `count(*) filter (where status = 'refunded')::numeric / nullif(count(*), 0)` | | | `overdue invoices` | `invoices` | `count(*)` | `status <> 'PAID' and due_at < now()` | ## Edit or delete Press a metric in the panel to open it. **edit** opens the form on its current values, and **save changes** replaces the definition: every report and chat answer picks it up on its next run. **Reported per**, **Shape** and **Shown as**, which the panel's form does not show, keep their values, and so do the metrics repointed by a rename. The bin deletes the metric. cherry can define metrics for you too: "define net revenue as gross revenue less discounts, for paid invoices". In Autonomous mode a metric cherry defines is live at once, and in Ask first mode it waits for you to confirm. See [actions and approvals](https://docs.sanda-os.com.au/cherry/actions-and-approvals). :::links - [Dimensions and filters](https://docs.sanda-os.com.au/semantic-fluid/dimensions-and-filters): The slicing and the conditions to use with a metric. - [Relationships](https://docs.sanda-os.com.au/semantic-fluid/relationships): How a metric reaches other tables' columns. - [Query the fluid](https://docs.sanda-os.com.au/semantic-fluid/query-the-fluid): Try a metric against a dimension. ::: --- # Dimensions and filters > Dimensions are the columns you slice by. Named filters say which rows count. Categories give one word to a way of slicing across source systems. A metric says what to count. Three other kinds of definition say how to slice it and which rows to count: - A **dimension** is a column you slice by, such as `customer name` or `issued date`. - A [category](https://docs.sanda-os.com.au/reference/glossary#category) is one word for a way of slicing that several columns share, such as `region`. - A named [filter](https://docs.sanda-os.com.au/reference/glossary#filter) is a condition your team says out loud, such as `paid invoices`, written down once. ## Dimensions "Revenue by region" needs a `region`. A dimension gives a column a name people use, other words for it, and, for a date, a grain and a roll-up. You do not have to make a dimension of a column to slice by it. In the [workbench](https://docs.sanda-os.com.au/semantic-fluid/query-the-fluid) you can pick any column on the map. A dimension is for the columns people actually ask about, so that a question phrased in your team's words finds the right one. ### Add a dimension Dimensions live in the list view. Press **List**, open the **dimensions** tab, and press **Add dimension**. | Field | What to enter | Example | |---|---|---| | **Dimension** | The name | `customer name` | | **Source column** | The column, in full: schema, table, column | `mapping.billing__customers.customer_name` | | **Type** | Category, Time, Place, Yes / no or Number band | Category | | **Grain** | For a time dimension, the finest unit | `day` | | **Rolls up** | For a time dimension, the steps it rolls up through, finest first, separated by commas | `day, month, quarter, year` | | **Table** | The table the column belongs to. Leave it empty for a dimension that any table carrying the column may use | `customers` | | **Also called** | Other words, separated by commas | `client, account` | | **Notes** | What it means to your team | | You can also ask cherry: "make region a dimension on the customers table". A [learn pass](https://docs.sanda-os.com.au/semantic-fluid/learn) proposes dimensions too. A column from the loader's own bookkeeping cannot be a dimension. It is not part of what sanda publishes. ### One dimension per column A column has one dimension. If a column already has one under a different name, sanda refuses the second and says which name it holds: > “customer city” already names mapping.billing__customers.customer_city. One column is one dimension: rename that one, or add “customer town” as a synonym of it. A second name for one column is a synonym, and that is where it belongs. This is what stops the workbench listing `customer city` and `customer_city` as two things when they are one. To rename a dimension, delete it in the list and add it again under the new name. Adding a dimension under its own existing name changes it in place, so to edit one, re-enter every field. ### Time dimensions Set **Type** to **Time**, give it a **Grain**, and list the steps it rolls up through under **Rolls up**, finest first: `day, month, quarter, year`. "By quarter" then becomes a lookup, not a guess about which date function to use. In the workbench, a time dimension gets a grouping control: **each row**, **day**, **week**, **month**, **quarter** or **year**. Grouping a column that is not a date is refused with a sentence, not a warehouse error. ### Synonyms **Also called** words work like the aliases described in [naming rules](https://docs.sanda-os.com.au/semantic-fluid/naming#synonyms-instead-of-second-spellings). A word finds a dimension only if it names exactly one. The workbench's search and the agent's lookups match them, and the agent's full text lists them after "Also called". ## Categories A dimension names one column. A category names the idea, so it can cover many columns. `region` in your billing system and `ship_region` in your shop would need two dimensions under two names, and nothing tells an agent they are the same question. As one category, `region` covers both, and "by region" works across the two systems. ### Create a category and tag its columns :::steps 1. **Open categories.** Press **categories** on the rail. 2. **Name it.** Type in **New category** (for example `region` or `channel`) and press **add category**. Press the new category's name to open it. 3. **Say what it means.** Under "What this way of slicing means to your team", write a note and press **save**. The agent reads it beside the column list. 4. **Tick the columns.** **Columns that carry it** is on the left, and a finder on the right lists every column on the map. Search for `region`, `zone` or `state` and tick each column that carries the idea. ::: You can also tag from a card. Hover over a column, press the tag icon, and tick categories in the dialog, or create a new one there. A column can carry more than one category, because a `region` column can also be `territory`. Hover over a category in the panel and every column it covers lights up on the map, so you can see that `region` is tagged on three tables and missed on a fourth. What the agent gets is one instruction: "group by this, and here is every column that means it". A category whose tables are all off the map is left out. ## Named filters Every other kind says what a column is. None says which rows count. "Approved", "open" and "excluding staff accounts" would otherwise be re-explained in every question, or copied into one metric's condition and left to drift. Name the condition once and use it by name. ### Name a condition :::steps 1. **Open filters.** Press **filters** on the rail, then **name a condition**. You need a table on the map. 2. **Name it.** For example `approved invoices`. 3. **Write the condition.** In the field that reads "Only rows where", enter a SQL condition over the table's columns, such as `status = 'AUTHORISED' and voided_at is null`. 4. **Pick the table.** The table is required. An unqualified condition means nothing until you know whose `status` it is. 5. **Add other words and notes if you want them.** Then press **save filter**. ::: The condition must be a single SQL fragment: no semicolon and no comment, up to 1,000 characters. To change a filter, press it in the list and press **edit**. Saving under the same name replaces it. There is no rename, because nothing builds a filter out of another filter: to rename one, save it under the new name and delete the old one. ### Using a filter - In the workbench, filters have their own folder. Press one to add it to the question. - The agent is told that a named filter is your own definition of which rows count, and to use it by name. Its full text gives the condition to put in the `where` clause exactly as written. - If the filter's table leaves the map, the filter stops applying, and its row in the panel reads "table has gone". ### Three ways to narrow rows | | Belongs to | Has a name | Use it when | |---|---|---|---| | A metric's **only counting rows where** | One metric | No | The condition is part of what that number means | | A **named filter** | The workspace | Yes | Many questions need the same condition | | A **condition** in the workbench | One question | No | You want to narrow a single question and move on | Conditions use a fixed set of operators and named date periods. See [filters](https://docs.sanda-os.com.au/reference/filters). :::links - [Metrics](https://docs.sanda-os.com.au/semantic-fluid/metrics): Numbers to slice and narrow. - [Naming rules](https://docs.sanda-os.com.au/semantic-fluid/naming): What a name is and how synonyms work. - [Query the fluid](https://docs.sanda-os.com.au/semantic-fluid/query-the-fluid): Use dimensions, filters and conditions together. ::: --- # Relationships > How tables join in the fluid. Draw a relationship, say which end holds one row, see how sanda suggests them, and fix a wrong one. A relationship tells sanda that two tables can be read together, on which columns, and which end holds a single row. `invoices.customer_id` and `customers.customer_id` hold the same customer. One `customers` row has many `invoices`. With that written down, "revenue by customer" is a question sanda can answer. A relationship needs two different tables, both on the map and accepted. ## How a relationship is stored sanda does not keep a list of joins. Both columns carry a shared key that names the identity they hold, such as `customer`. Every column carrying the same key is joined to every other. That is why you never edit a join list. State once that `customers.customer_id` is the customer, and a third table with a `customer_id` joins both of the others when you give it the same key. The key is a name for an identity, lowercase and usually one or two words: `customer`, `invoice`, `product`. ## Read a line on the map Each line runs between two columns. Its ends use the notation anyone who has drawn a schema knows: - A **bar** at an end means one row holds that value. - A **crow's foot** means many rows can. - A **dashed** line is [glue](#join-two-source-systems). The legend sits at the bottom right of the map, under **reading a line**. Click a line, or hover over a relationship in the relationships panel, and it reads like a sentence: `customer · one customers to many invoices`. When nobody has said which end holds one row, sanda works it out and adds "sanda's guess, until you say". See [cardinality](#cardinality). ## Create a relationship ### Drag one :::steps 1. **Start on a column.** Press anywhere along a column row on one card and drag. The row's dot on the card's edge starts the drag at once. 2. **Drop it on a column of another table.** Columns whose types would join without a cast light up, and the rest dim. You can still drop on a dimmed one: a text `customer_ref` can genuinely join an integer `customer_id`. 3. **Check the dialog.** **create relationship** shows the two columns and asks for: - **Shared identity (join key)**, guessed from the column names, so `contact_id` on both sides suggests `contact`. - **Shown as**, the words used to describe the join. - **How it fans out**: `one customers → many invoices`, `one invoices → many customers`, or `one to one`. 4. **Press create relationship.** ::: The fan-out is filled in from what the columns look like: a column that is its table's whole key holds one row per value, and everything else holds many. If both look like many, the dimension side is more likely the one. Change it if it is wrong. ### Relate two tables without dragging When two cards are far apart on the board, open the **relationships** panel on the rail and press **relate two tables**. Choose a table and a column on each side, check the key and the fan-out, and press **create relationship**. ### Accept a suggestion sanda proposes relationships too. They wait in the [review queue](https://docs.sanda-os.com.au/semantic-fluid/review) as one row each. ## Cardinality Every relationship is **one to many** or **one to one**. There is no many to many, and that is a correctness rule, not a limitation. A many-to-many join multiplies rows before anything adds them up, so a total across it is silently too large and looks completely reasonable. Two tables that genuinely relate many to many relate through a table that records the pairing, such as employees and territories through `employee territories`. That is one to many, twice. The pairing table has to be on the map, or the model is missing something. When nobody has said which end holds one row, sanda reads it from the keys: - A column that is its table's only key column holds one row per value. - Anything else is assumed to hold many. This is the safe assumption. Reading a one-to-one as one-to-many costs a de-duplication nobody needed. Reading a one-to-many as one-to-one costs a total that is too large. - If neither side can be the one, sanda promotes the more key-like side and marks the result as a guess. To say which end is the one, open the **relationships** panel, find the relationship and choose from **how it fans out**. Both sides update in one press, and the guess flag clears. ## Why the shape matters Say a customer has three invoices. Joining customers to invoices repeats the customer's columns three times, and any total of a customer column across that join counts the customer three times. Reaching the other way, from an invoice to its one customer, is a lookup: every invoice keeps exactly one customer, so nothing is counted twice. sanda gives every join to the agent with its direction, for example `customer · one mapping.billing__customers to many mapping.billing__invoices`, and tells it to add up the many side to its own level before joining it to the one side. When sanda composes a query itself and a number sits on the side that repeats, it counts each of that table's rows once by its [key columns](https://docs.sanda-os.com.au/semantic-fluid/tables#grain-key-and-event-time). When a question would need two many sides joined to each other, sanda declines and says why. See [metrics](https://docs.sanda-os.com.au/semantic-fluid/metrics#what-sanda-does-with-a-metric) for an example sentence. ## How sanda finds them There are three sources of evidence. 1. **What your names state.** sanda draws a join, without any AI, when a column is named exactly like another table's key (`customer_id` on invoices, and `customer_id` as the key of customers), when a `
_id` column points at a table keyed on a bare `id`, or when the source declares a foreign key. If two other tables both fit, sanda reports the ambiguity and draws nothing. 2. **What your values prove.** sanda measures what share of a column's sampled values are found in the other table's key. A name match with fewer than half found is not proposed. When it is, the reason says so, such as "100% of 91 sampled values found there". 3. **What an AI can add.** For keys spelled differently on each side, and joins the values prove that no name states, sanda asks the AI with the measured evidence in front of it: declared keys, keys checked against the rows, and the measurements. A handful of small whole numbers is found in every integer key, so a match alone never carries a proposal without a name or a type behind it. Suggestions arrive: - when you **add tables** (from names, and any declared keys), - at the end of a [learn pass](https://docs.sanda-os.com.au/semantic-fluid/learn) (all three), - when you press **suggest relationships** in the relationships panel, which needs at least two tables. The third source is an AI call, and it is billed. See [Learn from my data](https://docs.sanda-os.com.au/semantic-fluid/learn#cost-and-time). ## Fix a wrong relationship - **The shape is wrong.** Use **how it fans out**, as above. - **The join should not exist.** Click its line and press **unlink**, or press the bin on its row in the relationships panel. Unlinking removes the shared key from those two columns. Other tables that carry the key keep theirs. - **The key is wrong.** A key that names two different things joins tables nobody meant. Unlink, and create the relationship again under a key of its own. - **You might unlink more than you mean to.** When either column also joins a third table on the same key, the dialog warns you first and lists the other relationships that would go with it. To keep one of them, unlink the pair you actually want rid of, or recreate the kept one afterwards under its own key. ## One key, one identity Because the graph comes from the key's name, a key must name exactly one identity. - **Reusing a key on purpose is fine.** A third table carrying `customer` joins the first two with no further work. When you type a key that other columns already carry, sanda lists the tables the new column would also be joined to before you save, and says: "Right if they are one identity; otherwise give this relationship its own key." - **Two different joins with one key become a hub.** If orders-to-customers and orders-to-products were both given the key `id`, every column carrying `id` would join every other, and the map would draw joins nobody made. Give each identity its own key. - **Two many sides on one key join each other.** Invoices and payments both carry `customer`, so they are joined to each other too. Neither is the one, so sanda has to guess which end is, and marks the join as a guess. That is what a shared key is for, but add up each side to its own level before combining. - **A bridge takes its own key.** When a pairing table joins the same table as an ordinary one, it is given a key such as `employee via employee territories`, so sanda does not derive a join between the two many sides. ## Join two source systems [Glue](https://docs.sanda-os.com.au/reference/glossary#glue) joins two datasets when they describe the same identity under different column names: a contact's `email` in your billing system and a customer's `email_address` in your shop. Where a key derives its joins from a name, glue states them directly. :::steps 1. **Open glue.** Press **glue** on the rail, then **glue two datasets**. You need at least two [datasets](https://docs.sanda-os.com.au/semantic-fluid/tables#datasets). 2. **Choose the two datasets and name the glue.** For example "Customers across Xero and Shopify". 3. **Add column pairs.** For each pair, choose the left and right columns, a shared identity such as `customer`, and how many rows each side holds: sanda guesses, one row, or many rows. **Add pair** adds a row. 4. **Or let sanda propose pairs.** **Map with AI** matches the two datasets' columns by likeness of name, type and sample values. It is billed, and it saves nothing. Check each proposed pair, then continue. 5. **Press Create glue.** Saved pairs become joins an agent may use, and you are signing them. ::: Glue lines are dashed on the map, coloured by the first dataset. Click one to remove a pair, or press **edit glue** to change the whole set. A glue's last pair cannot be removed from the line: edit the glue, or delete it from its row in the panel. The agent's join list marks a glued join with `glue:` and the glue's name. :::links - [The map](https://docs.sanda-os.com.au/semantic-fluid/the-map): Dragging, selecting and tidying. - [Metrics](https://docs.sanda-os.com.au/semantic-fluid/metrics): What sanda does with a join when it answers. - [Review what sanda proposes](https://docs.sanda-os.com.au/semantic-fluid/review): Decide suggested relationships. ::: --- # Naming rules > Every name in the fluid is lowercase, and a name is also a key. What sanda changes when you save, and how to add another word without making a duplicate. Every name in the semantic fluid is lowercase, whoever wrote it: a table's card title, a dataset, a metric, a dimension, a filter, a category, a business term, a join key. `invoices`, never `Invoices`. You can type capitals if you like. sanda lowercases the name when it saves. The same rule is given to cherry and to any AI that proposes a name, so its output does not need correcting. ## Why a name is a key A name in the fluid is more than a label. It is how sanda finds the thing again: - A metric is referenced by name, as `{net revenue}`. - The query composer, cherry and AI assistants look up metrics, dimensions and filters by name. - Saving a definition under a name that already exists replaces it. That is how a suggestion is corrected on acceptance, and how a metric is edited in place. Every one of those ignores case. So `Revenue` and `revenue` are one row to sanda and two to your eye. If names could differ by a capital, correcting a capital by hand would not rename anything. It would create a duplicate. Lowercase everywhere means the list you read and the vocabulary sanda resolves are the same list. ## What changes when you save | You type | sanda stores | What happened | |---|---|---| | `Net Revenue` | `net revenue` | Case moved | | `customer_country` | `customer country` | Underscores became spaces | | ` invoice lines ` | `invoice lines` | Whitespace tidied | | `On-Time Delivery %` | `on-time delivery %` | Only the case moved. The hyphen and the sign are yours | An underscore is not punctuation someone types into a name. It is a column's word-break, and it reaches a name when a person, a model or an agent copies a column name into the name field. Left alone, it made two rows for one column, `customer_country` and `customer country`, which sanda treats as one name when it looks things up. Three things are never touched: - **Identifiers.** A column is `_becca_synced_at` or `mapping.billing__invoices.total`, exactly as the warehouse spells it. - **Sentences.** Notes, descriptions, grains and sample values keep their capitals: "Paid invoices, after discounts. Excludes GST." It is names that are lowercase, not the prose around them. - **Proper nouns inside a sentence.** Xero is still Xero, AUD is still AUD. A join key is tidied a little more, turning hyphens into spaces as well, because it is usually taken from a column name. Every other name keeps a hyphen you type, including a new metric's name (tidied as you leave the field) and a new category's name. ## Names sanda generates When sanda names something for you, it applies the same rule to the identifier it started from: - A table's name comes from its stream: a stream called `invoices` gives `invoices`, and a view called `daily_revenue` gives `daily revenue`. - If that name is taken, the second is qualified with its connection, `invoices (shopify)`, and then numbered, `invoices 2`. - A connection's dataset is named after the connection, lowercased: Billing becomes `billing`. - A relationship's key is lowercased and its separators turned into spaces. A name you type yourself is folded the way the table above shows. ## Lookups are looser than storage When an agent, cherry or an MCP client names a table, sanda ignores case and treats underscores, hyphens and spaces as one separator. `order_details`, `order details` and `Order-Details` are the same table. A relation works with or without its schema: `mapping.order_details` and `order_details`. That is all it does. A name is matched, not guessed at: `product` does not resolve to `product_name`. When a name matches nothing, the refusal offers the two or three nearest names. ## Aliases An [alias](https://docs.sanda-os.com.au/reference/glossary#alias) is another name for a table or a column: the words your team actually says. `amt_due` is "amount owing", also called "outstanding" and "balance". A table called `billing__invoices` is "sales ledger", also called "invoice book". Press **aliases** on the rail to open the whole vocabulary at once. It lists every column of every table on the map with two fields: - **called**, the name your team uses. - **also called**, other words, separated by commas. The panel shows how much of each table you have named ("6 of 9 named"), and **only the unnamed** narrows the list to the gaps. Each table has a first row, **the table itself**, for its own name. A **save** button appears when you change a row, and Enter saves too. If you fill in only **also called**, sanda uses the column's own name as the term. For one column or table, press the tag icon on a column in its card, or choose **aliases…** in the card's menu. Press **suggest aliases** and sanda reads the tables on the map, with sample values, and proposes the words a business would use, plus metrics your values make measurable (a `status` column that holds `refunded` makes a refund rate possible). They wait in the [review queue](https://docs.sanda-os.com.au/semantic-fluid/review), and the AI call is billed. Aliases are handed to the agent, so a question phrased in any of them finds the right column. :::note A name cannot reuse the term of a join key on the same column. Naming a column `customer` when a relationship already uses `customer` as its key would silently overwrite the relationship, so sanda refuses and says which two things collided: "“customer” is already the name of the join key on that column, and a relationship is using it." ::: ## Synonyms instead of second spellings When a thing has another name, do not add a second row. Add the other name as a synonym of the first. - A metric, dimension or filter has **Also called**. - A column or table has **also called** in the aliases panel. Synonyms are lowercased, and any underscores you type are kept, so `amt_due` stays findable by the name people see in a report. A synonym finds a definition only when it names exactly one. If two metrics both answer to `revenue`, that word finds neither, and sanda does not pick one for you. For dimensions the rule is enforced. A column can have only one dimension, and a second name for it is refused with an instruction to add it as a synonym instead. See [dimensions and filters](https://docs.sanda-os.com.au/semantic-fluid/dimensions-and-filters#one-dimension-per-column). ## Labels for stored codes A value label changes how a code is shown, not what it is stored as. `AUTHORISED` can read as `approved` in a report, while a query still filters on `AUTHORISED`. Add one in the list view. Open the **mappings** tab, press **Add mapping**, set **What kind** to "Replacement values for reports", give the mapping a name under **Human name**, name the column under **Source column**, and enter one pair per line under **Stored value = label**: ```text AUTHORISED = approved VOIDED = cancelled ``` Labels are kept exactly as you type them, capitals included. The agent is told to filter on the stored value and label the output. ## Rename something - **A metric.** Press **edit**, change the name and save. sanda repoints every metric that refers to it. See [metrics](https://docs.sanda-os.com.au/semantic-fluid/metrics#build-on-other-metrics). - **A filter.** Save it under the new name and delete the old one. - **A dimension.** Delete it and add it again under the new name. - **A table.** The list view has no rename: saving under a new name adds another table. Give the table an alias, or ask cherry to rename it. :::links - [Metrics](https://docs.sanda-os.com.au/semantic-fluid/metrics): References and renames. - [Dimensions and filters](https://docs.sanda-os.com.au/semantic-fluid/dimensions-and-filters): One dimension per column. - [What the agent is told](https://docs.sanda-os.com.au/semantic-fluid/agent-context): Where names and aliases end up. ::: --- # Query the fluid > Pick a metric, the dimensions to slice it by and the filters your team has named, and see the rows. sanda composes the SQL, with no AI involved. The workbench is the quickest way to see what your fluid does. You pick a metric, a dimension and a filter, and sanda composes the SQL from your definitions, runs it and shows the rows. No AI is asked anything, so the same picks always give the same SQL, and pressing **clear** costs nothing. Use it to check a new metric, to answer a quick question, or to see how the agent would read a definition. ## Open it Above the map is a bar that reads "query semantic fluid" with an **open** button. Press either, and the workbench opens as one card over the whole map. The map stays behind it, scaled back and greyed, and returns exactly as you left it when you close the card. Press Escape or the cross to close it. Underneath the bar, a line says how much there is to pick from: every metric, dimension, filter and column across your tables. ![The workbench with net revenue and issued date picked, grouped by month, and the result table on the right.](https://docs.sanda-os.com.au/media/query-workbench.png "The workbench: the fluid in folders on the left, and the chips, result and SQL on the right.") ## Pick what you want to see The left half is your fluid in four folders: **metrics**, **dimensions**, **filters** and **tables**, where each table lists its columns. The search box at the top is ready as soon as the card opens. It narrows every folder as you type, matching names, definitions, other words and column names, so typing `debtor` finds a metric called `amount owing` if `debtor` is one of its synonyms. A table whose name matches keeps all its columns. Press a row to put it in the query. Press it again to take it out. You can also drag it across to the right half. Each table has a button, labelled for example "Show invoices on the map", that closes the card and centres that table on the map. The right half is your question: - **Chips** show what you have picked. A cross takes one out. - **The result** appears below, and updates on every change. There is no run button. - **The sql sanda composed** is one press away at the foot. The order you pick things in does not matter. Put a dimension down and drop a number on it, and the number is worked out by that dimension. "Product" then "revenue" is the same question as "revenue" then "product". ## Summarise a column, group a date Two controls sit on a chip, because they belong to that one use of the field. - **How a raw column summarises.** A plain numeric column such as `unit_price` can be added to a query, but sanda never assumes what you meant. Choose **sum**, **avg**, **min**, **max**, **count** or **distinct count** on its chip, and the column header says which, for example `sum of unit_price`. An implicit sum is how a list price gets added up across a join and read as revenue. **sum** and **avg** appear only on numeric columns. The default, **as it is**, lists the column. - **How coarsely a date groups.** A time dimension's chip offers **each row**, **day**, **week**, **month**, **quarter** and **year**. Without a grain you get one row per timestamp. Grouping something that is not a date is refused with a sentence. ## Narrow the rows Press **add a condition** to narrow this question. Choose a field, an operator and a value. The operators offered depend on the field's type, so a date is compared with "is within", "is before" and "is on or after", a number with "is at least" and "is between", and text with "contains" and "starts with". A date field also offers periods by name, such as yesterday, last 30 days and last quarter. sanda resolves them in your own time zone to an exact window, and the chip states the window it used. The full list is in [filters](https://docs.sanda-os.com.au/reference/filters). A condition belongs to one question and goes when you clear it. A [named filter](https://docs.sanda-os.com.au/semantic-fluid/dimensions-and-filters#named-filters) is the workspace's own vocabulary. They are kept apart on screen for that reason: pick a named filter from the **filters** folder, and add a condition for anything nobody has named. **rows** sets how many rows to show. The default is 15 and the most is 200. For more than that, narrow the question or use the [OData feed](https://docs.sanda-os.com.au/odata). ## Read the result - **table** is the whole result, with every column and every row. A null is shown as `null`, not as an empty cell. - **chart** draws one measure against one label as bars, largest first. It draws the top twelve and says so when there are more. It needs a number to plot, and it says so when there is none. Rows come back in a sensible order without being asked: a number broken down by a label comes back largest first, so a page cut at the row limit is the top of the list, and a time series comes back in time order. Everything you did not aggregate is grouped by. Put `region` beside `net revenue` and the SQL groups by `region`. A row whose relationship finds no match is kept, with `null` on the other side, rather than left out. Revenue by product still adds up to total revenue when some order lines have no product: those lines are a group of their own. ## Numbers from different tables Numbers measured on different tables can sit in one result. Put `revenue`, summed over the order lines, beside `order count`, counted over the orders, and group them by customer country. Each order has many lines, so a single pass over the lines would count every order once per line. sanda works the orders out in a part of the query of their own instead, counting each order once by the table's [key columns](https://docs.sanda-os.com.au/semantic-fluid/tables#grain-key-and-event-time), and lines the two parts up by country. The SQL under the result shows both parts. - The table counted this way needs its key columns set. Without them, sanda declines and says which table needs them. - Each number keeps its own condition. A metric that counts only shipped orders beside one that counts every order gives both counts, not two counts of shipped orders. - A metric built from metrics on other tables, such as `average order value`, is worked out the same way. See [metrics](https://docs.sanda-os.com.au/semantic-fluid/metrics#build-on-other-metrics). ## When sanda declines Some questions cannot be answered honestly as one table, and sanda says so in a sentence where the rows would have been. It is drawn as prose, not as an error, because nothing failed: sanda declined to run it. You will see a refusal when: - the question needs two tables that each have many rows for the same thing, such as invoices and payments for a customer, joined to each other, and the sentence names the two tables and which one repeats, - a number is measured on a table the join repeats, and that table has no key columns to count its rows once by, - a column's name looks like it holds a secret, such as a password or a token, and sanda will not select it, - a metric's definition uses a column that two tables share, and sanda cannot tell which is meant, - a metric refers to another metric that is not defined, - a column is asked to group by a period but is not a date, or - you asked for a sum of a column that is not a number. Read the sentence. It usually says which relationship or definition to fix. See [relationships](https://docs.sanda-os.com.au/semantic-fluid/relationships#why-the-shape-matters). ## Keep a query as a view Once a selection has run, **save as view** appears beside the table and chart toggle. It keeps that SELECT as a view in your warehouse and puts it on the fluid as a table, so a rollup you assembled in business terms becomes something sanda can query like any other table. :::steps 1. **Press save as view.** A small form opens. 2. **Name it.** Enter a name such as `revenue_by_month`. It is lowercased as you type. Add a description if you want one. 3. **Choose whether to store the rows.** Tick "store the rows (materialized)" to build it now, on your compute, billed as it runs. A schedule under Modelling can rebuild it. Leave it unticked for a view that runs on read. 4. **Press create.** sanda confirms where it kept it, and says it is under Modelling. ::: Nothing else in the workbench is saved. See [views](https://docs.sanda-os.com.au/modelling/views) for what you can do with the result. :::links - [Metrics](https://docs.sanda-os.com.au/semantic-fluid/metrics): Define the numbers you pick here. - [Dimensions and filters](https://docs.sanda-os.com.au/semantic-fluid/dimensions-and-filters): Define what you slice and narrow by. - [Filters](https://docs.sanda-os.com.au/reference/filters): Every operator and named period. ::: --- # What the agent is told > Read exactly what cherry and AI assistants receive about your business, and improve their answers by improving the fluid. A customer who cannot see what an AI was told cannot trust what it said. So the Semantic fluid page shows you the real text, not a friendlier rendering of it. If a name is not in that text, the agent does not know it exists. ## See it At the foot of the Semantic fluid page is a panel headed **What the agent is told**. Its subtitle says what it is: "Exactly this text and these tools, compiled from the accepted rows above. Nothing else." :::steps 1. **Open the panel.** Scroll to the foot of the Semantic fluid page and press **Show**. sanda compiles your accepted definitions into the text. 2. **Read the text.** It scrolls inside the panel. 3. **Read the tools.** Under "and these tools, onto the same fluid", each tool the agent may call is listed with its description. ::: ![The What the agent is told panel, open, showing the start of the compiled text: the workspace, its warehouse and sources, and the tables sanda may read.](https://docs.sanda-os.com.au/media/agent-context-panel.png "The panel, open: a badge names the version of the rules, and Download and JSON sit beside Hide.") With the panel open you have four controls: - A badge, `policy` followed by a version, names the version of the rules in the text. The same version is recorded against every AI call, so an answer can be traced to the exact instructions behind it. - **Download** saves the whole fluid as one Markdown document. See [the full document](#the-full-document). - **JSON** downloads the compiled data behind the text, which is useful for comparing two compilations or feeding the fluid to something else. - **Hide** closes the panel. Only accepted definitions are compiled. A suggestion waiting for review, a rejected one and a retired one are not in it. ## Who receives it The same text goes to every AI surface in sanda: - **cherry** receives it with its own instructions and the tools it uses to change things. - **Questions in the reports portal** are answered from it. - **AI assistants connected over [MCP](https://docs.sanda-os.com.au/mcp)** read it. They are given short instructions when they connect, and can read two resources: **Workspace brief** (`becca://fluid/brief`), which is the text in this panel, and **Full compiled context** (`becca://fluid/context`), which is the download. ## What is in the text The panel shows a one-page map of your workspace: what exists, by name, and nothing an agent can look up in one call. This is an excerpt of it: ```text # Acme Services Warehouse: sanda warehouse · sanda sql (PostgreSQL-compatible). Sources landed: Billing (postgres, healthy); Support (zendesk, healthy). ## Tables sanda may read - customers (mapping.billing__customers) · dimension · 5 columns · one row per customer · ~1,840 rows - invoices (mapping.billing__invoices) · fact · 8 columns · one row per invoice · ~41,200 rows - payments (mapping.billing__payments) · fact · 5 columns · one row per payment · ~36,800 rows - support tickets (mapping.support__tickets) · fact · 5 columns · one row per ticket · ~9,600 rows ## Metrics defined (5) gross revenue, net revenue, invoice count, cash received, ticket count ## Dimensions (4) issued date, invoice status, customer name, region ## Named filters (2) paid invoices, excluding voided ## Categories to group by (1) region ``` In order, it holds: - The workspace, its warehouse and the dialect of SQL, and the sources that have landed with their status. - **Tables sanda may read.** One line per table: its name, relation, whether it is a fact or a dimension, its column count, its grain and an approximate row count. Modelled tables come first and say so. - **The names** of every metric, dimension, named filter and category. - **Text you can search**, only when you have a [sanda search](https://docs.sanda-os.com.au/search) service. - **The rules.** Just before the rules, the text tells the agent that everything else, meaning columns, types, definitions, joins and sample values, is one tool call away, and that anything not named does not exist. The text is short on purpose. If it listed every column and definition, every question would pay for every table to ask about one, and a wide warehouse would be cut off, leaving the agent unable to ask about a column it was never shown. A name it can see, it can look up. ## The tools The panel lists the tools the agent may call. They all read from the same fluid. | Tool | What it does | |---|---| | `search_fluid` | Finds metrics, dimensions, named filters and tables by name or synonym. Its description tells the agent to call it before assuming a name exists | | `describe_table` | Gives everything about one table: every column with its type, the grain, the key, the metrics and dimensions measured on it and every join it can reach | | `run_query` | Asks a question by naming metrics, dimensions, filters and conditions. sanda composes the SQL from your definitions and runs it. This is the tool for almost every question | | `run_sql` | Runs one read-only `SELECT` the agent wrote itself. It is the fallback, for something `run_query` cannot express, and it must say why, which is shown to the person | | `search_documents` | Finds rows by what their text says. It is listed only when you have a sanda search service, and it never produces a total | Not every surface gets every tool. `run_sql` needs the **sql** switch for cherry and its own permission for a connected assistant, and the reports portal gets only the first three tools. ## The rules The rules come from a versioned policy, not from anything you configure. In plain words, the agent is told to: - Use only what is in the fluid. A metric, dimension, filter, table or column it did not find is not something it may name. - Prefer `run_query` over `run_sql`, always, because `run_query` gives your number and `run_sql` gives its own. - Treat a refusal as an answer about the shape of the question, and re-ask the question the way the refusal suggests. - Treat what `search_documents` finds as evidence to query on, never as a total. - Prefer a metric over a raw column. - Say so, and name the column, whenever it leans on a raw column nobody has mapped. - Say in a sentence why `run_sql` was the only way, when it was. - Never state a number it did not get back from a tool call. - Refuse outright only when nothing in the fluid can answer, and then say what is missing. ## The full document **Download** gives you the whole fluid as Markdown. It is what an agent would have if the text carried everything. It holds: - Each table as a structured object: relation, name, kind, grain, primary key, event time, row estimate, dataset, description, aliases and every column with its type. A table wider than 120 columns lists the first 120 and says how many it left out, so "the column I need isn't in what you showed me" stays a possible answer. - Metrics, with references already expanded, their table, condition, unit and period. - Dimensions, categories and named filters. - Column names in business language, and stored value labels. - Dataset groups, and the joins the agent may use, each saying which way it fans out. ```text ## Joins you may use Every join says which way it fans out. Aggregate the "many" side to its own grain before joining it to the "one" side, or the "one" side's values repeat and any sum over them is too large. - mapping.billing__customers ↔ mapping.billing__invoices on mapping.billing__customers.customer_id = mapping.billing__invoices.customer_id (customer · one mapping.billing__customers to many mapping.billing__invoices) ``` The same fluid always compiles to the same text, in the same order, so two downloads can be compared line by line. ## Improve answers by improving the fluid Most weak answers are a gap in the fluid, and the text shows you where. 1. **Put the tables on the map, with the columns questions need.** A column that is not on the map cannot be read. See [tables and datasets](https://docs.sanda-os.com.au/semantic-fluid/tables). 2. **Define numbers as metrics.** The agent is told to name any raw column it leaned on. When an answer says so, that column is your next metric. See [metrics](https://docs.sanda-os.com.au/semantic-fluid/metrics). 3. **Say what a row is.** A grain, a key and an event time stop double counting and make "last month" mean something. 4. **Get fact and dimension right.** Agents add up facts and never add up dimensions. 5. **Relate the tables, and state which end holds one row.** See [relationships](https://docs.sanda-os.com.au/semantic-fluid/relationships). 6. **Name the conditions your team says out loud.** Use [named filters](https://docs.sanda-os.com.au/semantic-fluid/dimensions-and-filters#named-filters). 7. **Add the words your team uses.** See [aliases](https://docs.sanda-os.com.au/semantic-fluid/naming#aliases). 8. **Give one word to a way of slicing that spans systems.** Use [categories](https://docs.sanda-os.com.au/semantic-fluid/dimensions-and-filters#categories). 9. **Take stale tables off the map.** Every table on the map is a name the agent has to weigh. Build the views you actually want to ask about in [Modelling](https://docs.sanda-os.com.au/modelling/views), and sanda will tell the agent to prefer them. 10. **Read the refusals.** A refusal names the two tables and the reason. It usually tells you which relationship or definition is missing. After a change, press **Show** again and check that the name you expect is in the text. ## What is never in it - Rows or totals from your warehouse. The text describes your data. A dimension may carry a few example values. - Suggestions waiting for review, and anything rejected or retired. - Tables that are not on the map, and columns you left off. - The loader's own bookkeeping columns. While a workspace has no warehouse of its own, the text says so: it tells the agent that the map is sanda's sample dataset, not your business. :::links - [Review what sanda proposes](https://docs.sanda-os.com.au/semantic-fluid/review): Decide what reaches the text. - [MCP](https://docs.sanda-os.com.au/mcp): Connect an AI assistant to the fluid. - [Ask cherry](https://docs.sanda-os.com.au/cherry/ask): See the answers this text produces. ::: --- # sanda search > Find records by what their text says, by meaning and by words, using an index that lives inside your own warehouse. sanda search finds rows by what their text says. The note on an invoice, the body of a support ticket, the description of a product: the paragraph is often where the reason for the number is, and a metric cannot read a paragraph. It is included on Standard and Enterprise, and during a trial. You set it up and use it in the console under **Intelligence · Vector search**. ## What it is for The [semantic fluid](https://docs.sanda-os.com.au/semantic-fluid) answers questions whose answer is a number. sanda search answers questions whose answer is a row: - "Which invoices mention water damage?" - "Find the tickets that sound like this one." - "What do the notes say about the Ferndale site?" It matches meaning as well as words, so a paraphrase finds a row that does not contain your words. It also matches words, so an exact reference such as an invoice number or a surname still finds its row. Search returns rows and their keys, and never a total. To count or total what it found, take the keys and run a query with a condition on them. cherry does this for you. See [search your records](https://docs.sanda-os.com.au/search/use-search). ## How it works ```text text columns of one table on your map | cut into small pieces, turned into vectors v an index in your warehouse ^ your search: matched by meaning and by words, then ranked together ``` 1. **You create an index** over one table on your semantic map. You choose a key column that identifies each row, the text columns to search, and any columns to keep as filters. See [create a search service](https://docs.sanda-os.com.au/search/create-a-service). 2. **sanda builds it.** It cuts the text into small pieces, turns each piece into a list of numbers that stands for its meaning (a vector), and stores them. See [build and refresh an index](https://docs.sanda-os.com.au/search/build-and-refresh). 3. **You search.** Your words are turned into a vector the same way. sanda runs a search by meaning and a search by words, combines the two rankings, and shows each row once, with its best-matching piece. An index is a table in your own warehouse, named after the index and beside your data. Only sanda writes to it. It counts towards your storage. An index can only cover a table on your semantic map (one that has been accepted, not one sanda has only suggested), and only the columns you put there. That keeps an index inside what cherry may already read. ## What leaves Australia Your warehouse stays in Australia. cherry and sanda's other AI features already send small pieces of data to AI models (a question, a few sample values, the rows a query returns), as the [privacy page](https://docs.sanda-os.com.au/workspace/privacy) sets out. Making an index is different in scale: it sends the text of whole columns, in bulk, to a third-party service outside Australia. So sanda search is off until a workspace owner switches it on. - **What is sent.** The text of the columns you choose to index, in small pieces, and the text of each search you run, to a third-party service outside Australia that turns text into vectors. The service acts on sanda's instructions and does not keep the text. - **What comes back.** A list of numbers per piece. They are stored in your own warehouse in Australia, beside the rows they came from. - **What is not sent.** Your other columns, including the columns you keep as filters, your query results and your schema. Only an **owner** can switch it on, because it is a decision about where your text may be processed. An admin cannot. The banner on the page sets out the same points and links to the [privacy policy](https://docs.sanda-os.com.au/workspace/privacy), which says who runs the model and where. The owner presses **Switch sanda search on**, and sanda records who agreed, when, and which version of the wording they agreed to. To switch it off, the owner opens **Search data processing & permissions** at the foot of the page and presses **Switch it off**. Every index pauses, builds stop, and no more text is sent. Nothing already indexed is deleted, because the vectors are in your own warehouse. Switching it on again resumes where things stopped. An index's vectors are deleted when you remove the index. ## Editions and limits | Edition | sanda search | |---|---| | Basic | Not included. **Vector search** shows what the next edition adds | | Standard | 5 indexes, up to 2,000,000 rows each | | Enterprise | No cap on indexes or table size | | Trial | Included, with a small allowance of its own | If your workspace moves to an edition without sanda search, it keeps its indexes and they stop answering. They answer again the moment you move back up. See [editions](https://docs.sanda-os.com.au/billing/editions). ## What it costs sanda search spends three things you already pay for. - **AI.** Indexing and every search turn text into vectors, which is billed like any other AI call and counts towards your [daily AI limit](https://docs.sanda-os.com.au/cherry/models-and-usage). You see an estimate before the first build. After that sanda only re-embeds rows whose text has changed. - **Compute.** Building an index runs on your warehouse compute, and each build shows what it used. - **Storage.** The index counts towards your warehouse storage. ## Who does what | Action | Who | |---|---| | Switch sanda search on or off | Owners | | Create, rebuild, pause and remove an index | Owners and admins | | Search | Anyone who can ask questions: owners, admins, members and viewers | ## Where you can use it - **The console.** The **Vector search** page. - **cherry.** Ask about wording and cherry searches for you, once your workspace has an index with rows in it. - **AI assistants you connect over MCP.** They can search with the permission to ask questions, and manage indexes with a separate search permission that only an owner or admin can grant. See [MCP permissions](https://docs.sanda-os.com.au/mcp/permissions). ## Things to know - **There is no relevance cut-off.** A search always returns the nearest rows, even for a question with no answer in the data. Read what comes back, and look at how each row matched. - **More text finds more.** An index over one short column gives the search little to work with. Add the description or notes a reader would think of as part of the record. - **Filters are not searched.** A column you keep as a filter narrows results but its words are not indexed. If you want to find a row by a category name, index that column as text too. :::links - [Create a search service](https://docs.sanda-os.com.au/search/create-a-service): Choose the table, the text and the filters. - [Build and refresh an index](https://docs.sanda-os.com.au/search/build-and-refresh): Progress, freshness, cost and failures. - [Search your records](https://docs.sanda-os.com.au/search/use-search): Queries, filters, results, and search from cherry. ::: --- # Create a search service > Choose the table, the key, the text and the filter columns for a search index, review the estimated cost, and start the first build. A search service, which the console calls an index, covers one table on your semantic map. You choose the columns whose words become searchable and the columns to keep for narrowing a search, see what indexing would cost, and start the first build. ## Before you start - **Your edition includes sanda search.** Standard and Enterprise do, and so does a trial. See [sanda search](https://docs.sanda-os.com.au/search). - **An owner has switched sanda search on.** Until then the page shows **sanda search is switched off for this workspace** and no index can be created. - **The table is on your semantic map.** An index can only cover a table that is on the map. A table sanda has only suggested counts once someone accepts it (see [review what sanda proposes](https://docs.sanda-os.com.au/semantic-fluid/review)), and a table taken off the map does not count. To search a view you built, put it on the map first. See [views](https://docs.sanda-os.com.au/modelling/views). - **The table has text in it.** A name, a description, a note or a comment. Search matches words, so a table of numbers and dates has nothing to index. - **You are an owner or admin.** Members and viewers can search but cannot create indexes. ## Create an index :::steps 1. **Open Vector search.** In the console, go to **Intelligence · Vector search**. If you have more than one warehouse, choose the one to index from **Search warehouse**. 2. **Press Create an index.** The form **Create a search index** opens. 3. **Name it.** Use lowercase letters, digits and underscores, starting with a letter, up to 63 characters. The name becomes a table in your warehouse, so the field lowercases what you type. The names `estimate`, `models` and `consent` are taken. 4. **Pick the table.** **Table** lists the tables on your semantic map. Suggestions nobody has accepted yet are not listed. 5. **Pick the key column.** The key identifies a row, so it must be unique and never empty. sanda checks this when you create the index and tells you which it is if not. Every search result carries its key, and keys are how you take results to a query. 6. **Choose the text to index.** Under **Text to index**, pick between one and 16 columns. Only columns that hold text are offered. 7. **Choose the filters.** Under **Filters**, pick up to 16 columns to keep beside the text: a status, a region, a date, an amount. They narrow a search but are not searched as words. 8. **Choose the embedding model, if offered.** See below. 9. **Say what it is for.** **What it is for** is optional. cherry and connected assistants read it, so a good sentence helps them pick the right index. 10. **Estimate the cost.** Press **Estimate indexing cost**. See below. 11. **Press Create and index.** The first build starts and the index appears in the list. ::: ![The Create a search index form with a table, a key, one text column and two filter columns chosen, and an estimate shown.](https://docs.sanda-os.com.au/media/create-index-form.png "The form once the estimate has been run.") ## Choosing the text More text finds more. An index over one short column, such as a product name, gives the search little to work with. Add the description, the category name and the notes a reader would think of as part of the record. Two rules protect you: - **Secrets are refused.** A column whose name says it holds a password, token, key or similar is refused as a key, text or filter column. - **Personal details are flagged.** A column under **Text to index** whose name suggests an email address, phone number, date of birth, salary, address or passport gets a warning, because its text would be sent outside Australia to be indexed. Only text columns are sent, so a column you keep as a filter is not flagged: the warning suggests keeping the column as a filter if you did not mean to send it. Ordinary business text is what this feature is for, so the warning is not a refusal. A column that is a number, a date or a true or false flag has no words to match, so the text list leaves it out. It is still available as a filter. ## Choosing the embedding model When more than one is on offer, **Embedding model** appears, and each option shows its name and how many numbers its vectors hold. Leave it on **Default** for ordinary business text. A wider vector finds more and costs more to store: the larger option stores twice as much per row. The choice is fixed for the life of the index. Changing it later means indexing again from scratch, because a vector is only comparable to one made by the same model. ## Estimate the cost **Estimate indexing cost** appears once you have chosen a name, a table, a key column and at least one text column. It reads a small sample of your text and shows the approximate number of rows, the tokens, the cost of embedding them, and your daily AI limit. It is an estimate, not a quote. If indexing would take more than a day at your limit, it says so. The build then pauses each day at 80% of the limit, so that questions still work, and picks up where it stopped the next day. Searches work on whatever is indexed in the meantime. **Create and index** stays disabled until you have seen the estimate. Changing the table, key, text columns or model discards the estimate, so you review the cost of what you are about to create. ## Limits | | Limit | |---|---| | Indexes per workspace | 5 on Standard, no cap on Enterprise, a small allowance on a trial | | Rows in one index | 2,000,000 on Standard, no cap on Enterprise, a small allowance on a trial | | Text columns | 16 | | Filter columns | 16 | | Source | A table on your semantic map | If your table has more rows than your edition indexes, sanda refuses and suggests narrowing it with a view first. Build a view over the rows you care about, put it on the map, and index that. A view or table sanda has no row estimate for is counted before the check, and an agent that points an existing index at a different table is held to the same limit. ## Change or remove an index The console has no edit form. To change the table, key, text or model, remove the index and create it again. Remove it from **Index details**, then **Remove**. That deletes the index from your warehouse, so building it again costs a full first build. AI assistants you connect over MCP, with the search permission, can change an index in place and set how it stays fresh. See [MCP tools](https://docs.sanda-os.com.au/mcp/tools). cherry cannot create or change indexes. ## If the form will not let you | You see | What to do | |---|---| | **Still needs …** followed by a list | Fill in what it lists. The button is disabled until you do | | **Review the estimated cost before creating your index.** | Press **Estimate indexing cost** | | A message that the key is empty on some rows, or repeats | Pick a column that is unique and always set | | A message that the table is not on the map, or has been taken off it | Put it on the semantic map first | | A message that the table is only suggested for the map | Accept it on **Intelligence · Semantic fluid** first. See [review what sanda proposes](https://docs.sanda-os.com.au/semantic-fluid/review) | | A message that the workspace is at its limit of indexes | Remove one you no longer use | | A message that the warehouse is at its storage limit | Free space or raise the limit, because an index needs room to write | :::links - [Build and refresh an index](https://docs.sanda-os.com.au/search/build-and-refresh): What happens after you press Create and index. - [Search your records](https://docs.sanda-os.com.au/search/use-search): Try your index. ::: --- # Build and refresh an index > How a search index is built, how to watch its progress, how to keep it fresh, what it costs, and what to do when a build fails. Creating an index starts its first build. After that you keep it fresh by indexing what has changed. This page covers what a build does, how to watch one, what triggers a refresh, and what to do when something goes wrong. ## What a build does A build reads the table behind your index in key order and, for each row, compares its text with what is already indexed. Only rows whose text is new or has changed are sent to be embedded. A row whose filter values changed but whose text did not is updated in place, with no AI call at all. So the first build embeds everything, and later builds embed only what moved. Refreshing an index over a table where one row changed costs one embedding, plus the compute to read the table. Rows deleted from the source are removed from the index at the end of a pass that has read the whole table. ### Builds run in slices A large index does not build in one go. sanda works in slices, stops at the end of each, keeps its place, and carries on from there. That is not a failure. Rows indexed by an earlier slice are searchable straight away, so an index answers while it is still building, from what it has so far. sanda's scheduler runs on the quarter hour. A build you queue starts at the next :00, :15, :30 or :45, which is a 15 minute grid. A scheduled slice has about four minutes to work in. ## Watch a build Open the index on **Intelligence · Vector search**. - **The index list** shows a pill for each index: **indexing** with a count of rows read, **paused**, **broken**, **empty**, or the number of rows once it is indexed. - **Search records** shows a strip while a build is in flight: "Next indexing slice at 5:30 AM" or "Indexing is in progress", with the rows read so far, and **View progress**. - **Build history** shows the live build and every earlier one. ![The live build panel: part way through, not running, with rows read, rows embedded, tokens and cost.](https://docs.sanda-os.com.au/media/build-progress.png "A build between slices. Its rows so far are already searchable.") The live panel is titled **Indexing now** while a slice runs, **Part way through, not running** between slices, and **Waiting to start** before the first. It shows rows read against the table's approximate size, rows embedded, tokens and cost. The **Builds** table lists each attempt: when it ran, what started it (a sync, its schedule, this console, an agent, or sanda), rows read and embedded, tokens, cost and compute used, and the outcome: **indexed**, **queued**, **running**, **failed** or **cancelled**. Open a build to see its slices. ### Run now While a build is waiting for the scheduler, **Run now** appears on **Index details**. It indexes for one request instead of waiting. A press has under a minute to work in, so a big index takes several presses, or you can leave it to the scheduler. Use it to see your first results sooner. ## Keep it fresh An index has one of three triggers for refreshing, shown as a pill beside its status: | Pill | What it means | |---|---| | **manual** | It is refreshed when you ask | | **after** a connection's name | It is refreshed each time that connection lands rows | | A schedule | It is refreshed on the schedule the pill shows, on the quarter-hour grid | An index you create in the console is **manual**. The form does not set a trigger. To set one, use an AI assistant connected over MCP with the search permission: it can set an index to refresh after a connection's sync, or on a schedule. After a sync is usually the right trigger. See [MCP tools](https://docs.sanda-os.com.au/mcp/tools). To refresh by hand, open **Index details** and press: - **Index what changed.** Queues an incremental build: only changed text is embedded. This is the button to use day to day. - **Rebuild everything.** Replaces the whole index from scratch. Use it only when you need to, because it costs what the first build cost. Both also clear a suspension, described below. ## What it costs Each build shows its own cost. Indexing spends three things: - **AI.** Embedding tokens are billed like any other AI call and count towards your [daily AI limit](https://docs.sanda-os.com.au/cherry/models-and-usage). Indexing uses only 80% of a day's limit, so that questions still work later in the day. When it reaches that, the build pauses until midnight UTC and picks up where it stopped. Syncs and questions are not affected. - **Compute.** Reading and writing runs on your warehouse compute. The **Billed** column shows the compute each build used. - **Storage.** **Index size** counts towards your warehouse storage. ## Pause, resume and remove - **Pause indexing** cancels any queued or running build and stops automatic refreshes. Searches keep working on what is already indexed. **Resume indexing** turns refreshing back on. - **Remove** deletes the index from your warehouse, after you confirm. Building it again costs a full first build. If an owner switches sanda search off, every index is **paused** and searching stops until it is switched back on. See [sanda search](https://docs.sanda-os.com.au/search). ## When a build fails A build fails when it hits something that will not fix itself. The index shows **broken** with the reason under its name. A broken index keeps answering from what it had. Common causes, and what to do: | Reason | What to do | |---|---| | The table is no longer on the semantic map | Put it back on the map, or remove the index | | The key column is empty on some rows, or repeats | Fix the data, or remove the index and create it again with a different key | | The embedding option is no longer offered | Remove the index and create it again | | The warehouse is not ready | Check it on the **Warehouse** page | Some things only pause a build. If the warehouse is at its storage limit, the build waits until there is room. If the warehouse is suspended for its budget, sanda tries again an hour later. Neither counts as a failure. By default, after three failures in a row, sanda stops trying automatically. If alerts are on under **Settings · Plan & budget**, it emails the alert recipients, at most once a day for each index, with the setting **When a scheduled run fails, or pauses itself after failing repeatedly**. When you have fixed the cause, press **Index what changed** to try again. :::tip Read the error first The message on a broken index is the reason from the last attempt. It usually names the table or column, so start there. ::: :::links - [Search your records](https://docs.sanda-os.com.au/search/use-search): Try what you have built. - [Models and AI usage](https://docs.sanda-os.com.au/cherry/models-and-usage): The daily limit that indexing shares. ::: --- # Search your records > Search an index by meaning and by words, narrow it with filters, read why each record matched, and search from cherry or a connected assistant. Anyone who can ask questions in your workspace can search an index: owners, admins, members and viewers. You do it on **Intelligence · Vector search**, by asking cherry, or from an AI assistant you have connected. ## Search in the console :::steps 1. **Open Vector search.** In the console, go to **Intelligence · Vector search** and choose an index from the list. On a phone the list is a menu called **Search index**. 2. **Describe what you want.** Type in the box under **What are you looking for?**. A phrase or a sentence works better than a single word, because sanda search matches meaning as well as words. An exact reference such as an invoice number works too. 3. **Set how many results.** **Results** offers 5, 10, 25 or 50. 4. **Narrow it, if you want.** Press **Filters**. See below. 5. **Press Search.** Results appear under the box. ::: Search is unavailable while an index has nothing in it yet ("Your first records are being prepared") or while sanda search is switched off and the index is paused. A query can be up to 1,000 characters. ## Narrow with filters **Filters** opens **Narrow the search**. A condition is a filter column, a comparison and a value. **All conditions must match**, and you can add up to eight with **+ Add condition**. **Clear filters** removes them all. ![The search box with the Filters panel open, showing one condition on the customer column.](https://docs.sanda-os.com.au/media/use-search-filters.png "One filter condition. Every condition must match.") The filter columns are the ones you chose when you created the index. If it has none, the panel says so. What you can compare depends on the column's type: | Column holds | Comparisons | |---|---| | Text | **is**, **is not**, **contains** | | A number, or a date and time | **is**, **is not**, **greater than**, **at least**, **less than**, **at most** | | True or false | **is**, **is not**, with **True** or **False** to choose | For text, **is** matches the whole value exactly, including its capitals, and **contains** matches part of it whatever the capitals. Dates and times are entered in your own time zone. Every condition needs a value before you can search. See [filters and date ranges](https://docs.sanda-os.com.au/reference/filters) for how filters and conditions work across sanda. ## Read the results ![Two search results, each with the record key, how it matched, an excerpt with the matching words highlighted, and its filter values.](https://docs.sanda-os.com.au/media/use-search-results.png "Results for a search by meaning and words.") Each result shows: - **The key.** The value of the key column, which identifies the row. - **How it matched.** **meaning + words**, **meaning**, **words**, or **substring** for the rare search that is only stop words or punctuation. - **An excerpt** of the matching text, with your words highlighted. Highlights show words only, so a row that matched by meaning may have none. - **Filter values**, up to four, and the source table. The list is ranked by meaning and word matching combined. sanda does not show a score, because the order tells you which row ranked higher and not how confident the search is. The label on each row is the honest signal. A row that matched on **meaning** only, for a question whose words are in your data, is a hint that the words matched nothing. The heading says **Hybrid search** when both searches ran, and **Words only** when the search by meaning could not, for example because the daily AI limit has been reached. It then tells you why. If no row contained your words, sanda says so: "No records matched your exact words. These are the nearest results by meaning; they may not all be relevant." A search always returns the nearest rows, even where nothing is a good match, so read them before you act on them. Press a result to open **Record** with the full indexed text, its fields, and **Copy record ID**. If a long text was split into pieces, the result shows the piece that matched best, and the detail says which one. **Recent in this session** lists your last five searches on the empty page so you can run one again. ## Search from cherry Ask about wording and cherry searches for you: - "Which invoices mention water damage?" - "Find the tickets that sound like this one." - "What do the notes say about the Ferndale site?" cherry has the search when your workspace has an index with rows in it and your edition includes sanda search. It can use an index's filter columns to narrow a search, and it reads back the key, the filter values and the start of each row's text. Search finds rows and never produces a number. When you ask "how many" or "how much", cherry takes the keys from the hits and runs a query with a condition on those keys, so the total comes from your definitions. See [ask questions](https://docs.sanda-os.com.au/cherry/ask). cherry cannot create or change an index. That is done in the console. ## Search from a connected assistant An AI assistant connected over MCP can search with the permission to ask questions. Its search takes a query, an optional index name (needed when you have more than one), how many rows to return (10 by default, up to 50) and filters. Its filters can also use `in`, a list of up to 50 values. See [MCP tools](https://docs.sanda-os.com.au/mcp/tools) and [MCP permissions](https://docs.sanda-os.com.au/mcp/permissions). ## What a search costs Each search turns your words into a vector, so it costs a fraction of your daily AI limit, and your search text is sent to the same outside service as the index text. See [sanda search](https://docs.sanda-os.com.au/search#what-leaves-australia). If the daily limit is used up, search still answers from matching words alone. If an owner has switched sanda search off, every index is paused and searching stops until it is switched back on. :::links - [Filters and date ranges](https://docs.sanda-os.com.au/reference/filters): Conditions, operators and dates across sanda. - [Build and refresh an index](https://docs.sanda-os.com.au/search/build-and-refresh): Keep what you search up to date. - [Ask questions](https://docs.sanda-os.com.au/cherry/ask): Let cherry combine search and queries. ::: --- # Meet cherry > cherry is sanda's data agent. Ask it about your numbers, and it builds metrics, views and reports when you say so. cherry is the one conversation in sanda. You ask a question of your data and cherry answers it from your semantic fluid, with the queries behind the answer one click away. You ask it to build something (a metric, a view, a report) and it does the work in the same conversation, holding each change for your confirmation unless your workspace has chosen otherwise. cherry is included on every edition. What it can change for you depends on your role and on what your workspace has switched on. ## Where cherry lives There is one conversation, and you can reach it from four places. A thread you start in one is the same thread in the others. - **The pane.** On every page except the two below, cherry sits in a column at the right of the window. Open and close it with the **cherry** button at the right of the top bar, or with ⌘J (CtrlJ on Windows). Drag its left edge to make it wider. It stays open as you move between pages, so you can ask about a report while you look at it. A dot on the button tells you cherry has replied while the pane was closed. - **cherry chat.** The **cherry chat** entry in the left navigation opens the conversation full screen, with your earlier threads down the left. The full screen button in the pane's header takes you there. - **The overview.** The overview opens on a greeting and a box to ask in. Asking there takes you to cherry chat with your question already sent. - **A phone.** cherry is a sheet over the page. It opens only when you ask it to, and only on the page you asked from. ![The cherry pane on a new conversation, with four suggested first questions and the box to ask in.](https://docs.sanda-os.com.au/media/cherry-pane-empty.png "A new conversation in the pane. The suggestions change with how far your workspace is set up.") On cherry chat and on the overview, ⌘J puts the cursor in the box instead, because those pages are cherry already. See [keyboard shortcuts](https://docs.sanda-os.com.au/reference/keyboard-shortcuts) for the rest. cherry knows which page you are on. On a report it knows which report is open, so "put a revenue tile on this" means that report. On the modelling page it knows which warehouse you are looking at. ## What cherry does - **Answers questions** about your numbers: compare two periods, break a figure down, find the biggest movements. See [ask questions](https://docs.sanda-os.com.au/cherry/ask). - **Builds in the semantic fluid.** It puts tables on the map, defines metrics, dimensions, filters and relationships, and reviews what learn from my data proposed. - **Builds in the warehouse.** It writes views and procedures and schedules them. - **Builds reports.** It creates a report, places tiles, charts, tables and written analysis, and changes a report that already exists. See [build reports with cherry](https://docs.sanda-os.com.au/reports/build-with-cherry). - **Runs the moving parts.** It starts a sync, runs learn from my data, and schedules a report to be emailed. - **Sets up a new workspace**, one step at a time, with a checklist you press through. See [set up with cherry](https://docs.sanda-os.com.au/get-started/setup-with-cherry). - **Answers how-to questions** about sanda from these docs, with a link to the page. - **Takes you to the right page** when you ask, and the conversation goes with you. The full list, and which parts depend on your role, is in [what cherry can do](https://docs.sanda-os.com.au/cherry/capabilities). ## Grounded in your semantic fluid cherry does not guess at your business. It is given the names of every metric, dimension, named filter and table on your [semantic fluid](https://docs.sanda-os.com.au/semantic-fluid), and it answers by asking for those things by name. sanda then composes the query from the definitions your team agreed. The number cherry quotes is your definition of revenue, not its own. When the fluid cannot express a question, cherry can write read-only SQL of its own. It says in a sentence why it needed to, and the statement is shown to you. If it leans on a column nobody has defined, it names the column, so you know which term to define next. cherry is instructed to quote only numbers that came back from a query in the conversation, and to pass on a refusal in plain words rather than work around it. You do not have to take that on trust: every answer can show its work. ## It shows its work Under each reply you can see: - **The steps it took**, as one quiet line between its sentences: what it looked up, how many steps, how long, and whether any failed. - **SQL and rows**, the statement behind the answer and the rows it returned. - **What it changed or proposed**, as cards with the exact detail. - **What the reply cost**, as a small dollar figure under the reply. ## What cherry sees To answer, the model behind cherry needs enough to be useful. A request can carry: - your question and the recent messages in the conversation; - the names of your tables and columns, and the definitions on your semantic fluid; - a small sample of real values from your columns, up to six per column, so it can tell what a column holds; - the rows a query returns, up to 50 at a time; - your name, your role and your workspace's name. Columns whose names suggest a secret, such as passwords and tokens, are never sampled or returned. Other columns are not filtered by content. Requests go only to model endpoints that do not keep what they receive and do not train on it. Your [privacy page](https://docs.sanda-os.com.au/workspace/privacy) sets this out in full. ## What cherry does not do - It does not act on its own initiative in setup. It does not provision a warehouse, connect a source, sync, learn or build a report unless you ask. - It never takes a credential in chat. You type a password or token into the connection wizard, never into cherry. - It does not change your plan, billing, team or settings. - It does not create or manage search services. You do that in the console. It can search an index once one exists. See [use search](https://docs.sanda-os.com.au/search/use-search). - It does not change your data with the SQL it writes to answer a question. That SQL is read-only. - It does not email a report without asking you first, whatever mode your workspace is in. ## Who can use it Everyone who can open the console can ask cherry questions. What it can change depends on your role: a viewer's cherry answers questions and changes nothing, and some actions are for owners and admins only. See [what cherry can do](https://docs.sanda-os.com.au/cherry/capabilities). People who read reports on the reports portal get a smaller cherry that only answers questions about what has been published to them. It cannot build, change or send anything. ## Cost Every AI call sanda makes for your workspace is billed, cherry's included, and counts towards your daily AI limit. The meter in the top bar shows where you are. See [models and AI usage](https://docs.sanda-os.com.au/cherry/models-and-usage). :::links - [Ask questions](https://docs.sanda-os.com.au/cherry/ask): How to ask well, and what a reply contains. - [What cherry can do](https://docs.sanda-os.com.au/cherry/capabilities): The full list, by role. - [Actions and approvals](https://docs.sanda-os.com.au/cherry/actions-and-approvals): Ask first, Autonomous, and what always asks. - [Memory and conversations](https://docs.sanda-os.com.au/cherry/memory-and-history): Threads, history and what cherry remembers. - [Models and AI usage](https://docs.sanda-os.com.au/cherry/models-and-usage): Choosing a model and the daily AI limit. ::: --- # Ask questions > How to ask cherry well, what a reply contains, how to follow up, and how to have cherry take you to a page. You ask cherry in plain words, in the box at the foot of the pane, the cherry chat page or the overview. Press Enter to send and ShiftEnter for a new line. A message can be up to 8,000 characters. What you have typed and not sent is kept for that conversation in your browser, so a half-written question survives a look at another page. While cherry works you can press the stop button. It keeps what it had written so far, and the reply is marked **stopped**. ## Ask well cherry answers from the metrics and dimensions on your [semantic fluid](https://docs.sanda-os.com.au/semantic-fluid), so the closer your question is to how your team names things, the fewer steps it needs. It also matches synonyms and descriptions, so "debtors" will find a metric filed as "receivables" if that word is on it. - **Name the measure and the period.** "Revenue for last quarter" beats "how are we doing". - **Say what to compare against.** "Against the same months last year" gives cherry a second query to run. - **Say what you want back.** A single figure, a table, a chart on a report, or a paragraph you can paste. - **Point at the thing.** On a report, "this report" means the one open in front of you. Otherwise give its name. - **Ask it to build in the same breath.** cherry acts on the first ask. It does not stop to ask whether to go ahead, and it does every step the ask needs, holding any change for your confirmation. For a report it needs a name and what the report is for, and asks for whichever is missing in one short question. | You want | You could ask | |---|---| | A comparison | What changed in the numbers this month compared with last month? Lead with the biggest movements. | | A breakdown | Show revenue by region for the last 6 months, as a table. | | A finding in text | Which invoices mention water damage, and what do they add up to? | | A definition | Define a metric called net revenue as the invoice total less discounts. | | A report | Build a monthly management pack: headline tiles, the trends and a short analysis. | | A change to a report | Put the weekly sales report on the last 7 days. | | How sanda works | How do I invite a member? What does Standard include? | | A page | Take me to the review queue. | | A delivery | Email the June board pack to finance@example.com every Monday at 8am Sydney time. | Some of these ask you to confirm before anything changes. See [actions and approvals](https://docs.sanda-os.com.au/cherry/actions-and-approvals). ## What a reply contains A reply is everything cherry said, in the order it said it, with the work it did between the sentences. ![A reply in the pane: a line of steps between two paragraphs, then two suggested next questions.](https://docs.sanda-os.com.au/media/cherry-reply.png "A finished reply. The line reading 'running a query' is a folded group of steps.") - **Words and steps.** Each group of steps is one line: what cherry did, how many steps, how long, and a count of any that failed. Open the line to see each step. - **SQL and rows.** Under the reply, **SQL and rows** opens the statement behind the answer and the rows it returned. If there were more rows than are shown, it says how many it is showing out of how many. A query cherry runs for you returns at most 50 rows, so for a longer list ask for a top ten, or ask for a report. - **Cards** for what it made or proposed: a report ("created the report") with **open the report**, a view with **open in modelling**, what learn from my data found with **review the suggestions**, and any change waiting for your confirmation. - **A "took you to" link** when cherry moved you to a page. - **Buttons.** When your ask could mean two things, cherry offers the options as buttons and your choice is sent as your next message. At the end of a reply it may offer two to four suggested next questions. - **A foot line** with copy, ask again, and the cost of the reply. The figure under a reply is what the reply cost as billed, in Australian dollars. It counts towards the daily AI limit. See [models and AI usage](https://docs.sanda-os.com.au/cherry/models-and-usage). ## Follow up cherry reads the recent part of the conversation (the last 24 messages), so you can say "the same for June", "now by product" or "why is that down". Pressing one of the suggested questions asks it for you. You can change your mind about the last thing you sent: - **Edit and send again.** Press the pencil beside your last message, change it, and press **Send again**. - **Ask again.** Press the circular arrow under cherry's last reply to have it answer the same question afresh. Both cut the conversation at that message. Everything after it is removed, including any change still waiting for your confirmation on a removed reply. ## Ask it to take you somewhere Say where you want to go: "take me to the review queue", "open the June board pack", "where do I add a connection". cherry opens the page and the conversation goes with you. On a desktop the pane opens beside the page and stays open. On a phone the page is shown and the cherry button carries a dot when the reply is in. cherry can take you to these pages: Overview, Warehouse, Connections (including the new connection wizard and the CSV upload), Explorer, Modelling, Semantic fluid, Vector search, Agents, Reports, cherry chat, Pipeline, Settings and Support. It does not take you anywhere you did not ask to go. ## How sanda works Ask "how do I connect Xero", "what is a sync mode" or "what does my plan include" and cherry searches these docs, answers from the page it finds and gives you the link. Its steps line reads "reading the docs on" and your words. It does not use the docs for questions about your own numbers, and it adds no AI cost of its own beyond the reply. ## Searching text If your workspace has a [search index](https://docs.sanda-os.com.au/search), ask about wording: "which tickets sound like this one", "what do the notes say about the Ferndale site". cherry searches the index and gets back rows, never a total. To count or total them it takes the keys from the hits and runs a query on those. See [search your records](https://docs.sanda-os.com.au/search/use-search). ## Dates When you name a period such as "last month", cherry works it out on sanda's servers in UTC, and the query result carries the dates it used. If the exact days matter, give the dates. See [filters and date ranges](https://docs.sanda-os.com.au/reference/filters). ## When cherry stops early A reply has room for up to 16 rounds of work and about three minutes, a little longer while learn from my data runs. If it runs out, cherry says so and you ask for the rest. It also stops, and says why, when the workspace has reached its daily AI limit or a trial's free credit has run out. ## If an answer looks wrong 1. Open **SQL and rows** and read the statement. 2. Check the metric it named. If it used a column instead of a metric, the answer rests on a column name and not on an agreed definition. [Define the metric](https://docs.sanda-os.com.au/semantic-fluid/metrics) and ask again. 3. Ask cherry why. "Which metric did you use for that, and what is its definition?" is a fair question. 4. Press **Ask again**, or edit your question to be more specific. :::links - [What cherry can do](https://docs.sanda-os.com.au/cherry/capabilities): What it can read, build and change, by role. - [Actions and approvals](https://docs.sanda-os.com.au/cherry/actions-and-approvals): When cherry asks before it acts. - [Memory and conversations](https://docs.sanda-os.com.au/cherry/memory-and-history): Keeping and finding your threads. ::: --- # What cherry can do > Everything cherry can read, build and change, grouped by what it does, and which parts depend on your role, your workspace's switches and your edition. cherry's abilities come in families. Three things decide which of them you get in a given conversation: your role, the switches your workspace has set under **Settings · cherry**, and your edition. ## The short version - **Everyone who can open the console** can ask cherry questions, have it search text, have it answer how-to questions from these docs, have it take them to a page, and have it remember things. - **Owners, admins and members** can also have cherry change the semantic fluid, create and change reports, and run learn from my data. - **Owners and admins** can also have cherry build in the warehouse, start syncs, load rows and schedule report emails. - **Viewers** get answers and nothing else. cherry changes nothing for a viewer whatever the switches say. ## Reading and answering These are what a chat is, so they are always on. | What cherry does | Who | |---|---| | Looks things up on your semantic fluid by name: metrics, dimensions, named filters, tables | Everyone | | Describes a table: its columns, key, and the joins it can reach | Everyone | | Runs a query composed from your definitions and returns rows (at most 50 at a time) | Everyone | | Searches the text in your records, when the workspace has an index | Everyone, on Standard, Enterprise and trials | | Searches sanda's own documentation for how something works, and links the page | Everyone, on every edition | | Runs read-only SQL of its own when the fluid cannot express the question | Owners, admins and members, if the **sql** switch is on | | Looks at your connections, their last sync and your warehouses' capacity | Everyone, if the **data** switch is on | The documentation search has no switch. It is how cherry answers "how do I…", "what does this feature do" and "what does my plan include" from these pages rather than from memory, and it is not used for questions about your own data. It makes no AI call of its own, so it adds nothing to your daily AI limit beyond the reply that quotes it. cherry prefers a query composed from your definitions over SQL it writes itself. It uses SQL of its own only for things like window functions and cohorts, says why, and shows you the statement. ## Defining things on the semantic fluid Switch: **semantics**. Owners, admins and members. - Put landed tables on the map, and mark a table as a fact or a dimension. - Define a metric, dimension, filter, relationship, alias, category or dataset. - Accept or reject what learn from my data proposed, singly or in bulk. - Take tables off the map, and delete a definition. These always ask first. See [semantic fluid](https://docs.sanda-os.com.au/semantic-fluid) for what each of these is. ## Building in the warehouse Switch: **modelling**. Owners and admins. - Write a view or procedure, replace one, rebuild a materialized view, or drop one (drops always ask first). - Schedule a task, run it now, pause or resume it, and read its history. - Read tuning advice for a warehouse and apply the findings you choose. Every build runs on your compute and is billed to your workspace. See [modelling](https://docs.sanda-os.com.au/modelling). ## Reports Switch: **reports**. | What cherry does | Who | |---|---| | Creates a report, places tiles, charts, tables and written analysis, and changes a report's filters and blocks | Owners, admins and members, on reports they can edit | | Reads a report, and lists the reports you can open | Everyone | | Schedules a report to be emailed, changes or pauses a delivery, sends one now, removes one | Owners and admins | A report shared with you to read cannot be changed by cherry for you. Every change cherry makes to a report is kept as a version you can restore from the report's history. Anything that emails a report always asks first. See [build reports with cherry](https://docs.sanda-os.com.au/reports/build-with-cherry). ## Data Switch: **data**. | What cherry does | Who | |---|---| | Starts a sync on a connection | Owners and admins | | Runs learn from my data, which proposes tables, metrics, dimensions and relationships | Owners, admins and members | | Loads a small table of rows you give it into the warehouse | Owners and admins | Learn from my data files proposals, and nothing goes live until you accept them. It uses AI, so it counts towards your daily AI limit. cherry runs it only when you ask. See [learn from your data](https://docs.sanda-os.com.au/semantic-fluid/learn). ## Setting up a workspace cherry reads how far your workspace is set up and draws a checklist under its reply: a warehouse, a source, learn from my data, the review of what it proposed, and a first report. Every step is a button you press, and cherry takes no step you did not ask for. Where a step needs an owner or admin, the checklist says so. See [set up with cherry](https://docs.sanda-os.com.au/get-started/setup-with-cherry). ## Moving you around cherry can open any console page you ask for, and the conversation goes with you. It can offer next steps as buttons, and hand an unclear ask back to you as a choice. ## Remembering cherry can keep a fact for later conversations, and search your earlier conversations when you refer back to them. See [memory and conversations](https://docs.sanda-os.com.au/cherry/memory-and-history). ## What decides what you get **Your role.** cherry acts with your own permissions. It cannot do for you what you could not do yourself in the console. **The switches.** Owners and admins can turn each of five families off for the whole workspace under **Settings · cherry**: **semantics**, **modelling**, **reports**, **data** and **sql**. A family that is off is not offered to cherry at all. All five are on by default. **Your edition.** cherry is on every edition. The one part that depends on edition is text search, which is on Standard, Enterprise and trials. On Basic, cherry has no text search to offer. **Your workspace's state.** In a closed workspace, cherry answers questions and changes nothing. ## What cherry cannot do cherry has no way to change your plan, billing, team, security settings or integrations, to create or manage search services, to add a connection (credentials go in the wizard), or to run SQL that changes your data. Some of that is available to connected AI assistants under separate permissions. See [MCP permissions](https://docs.sanda-os.com.au/mcp/permissions). :::links - [Actions and approvals](https://docs.sanda-os.com.au/cherry/actions-and-approvals): Which of these ask first. - [Workspace roles](https://docs.sanda-os.com.au/workspace/members-and-roles): What owners, admins, members and viewers can do. ::: --- # Actions and approvals > How cherry asks before it changes anything, what Ask first and Autonomous mean, and the few actions that always need your confirmation. Answering a question changes nothing. Building does: a metric goes live, a view uses your compute, a sync starts. So cherry has two modes for changes, and your workspace chooses one. - **Ask first** is the default. Every change cherry proposes waits as a card in the conversation with **Confirm** and **Decline**. Nothing runs until you press one. - **Autonomous** runs changes as soon as cherry decides to make them. The choice is per workspace, not per person, and only owners and admins can change it. A few actions ask first in both modes. See [what always asks](#what-always-asks). ## Ask first When you ask cherry to change something, it does all the work of proposing it and then stops. What you see is a card that says, in one sentence, what would happen: "create view derived.revenue_by_month", "put invoices on the map", "email “monthly operations” to finance@example.com, every Mon at 08:00 Australia/Sydney". ![A reply with a proposed delivery. A card reads 'waiting for you' and offers Confirm and Decline.](https://docs.sanda-os.com.au/media/actions-confirm-card.png "A held change. Nothing has run yet.") Open **show the detail** on a card to see exactly what will run: the SQL of a view, or the full input of any other change. A view's card also has **preview 50 rows**, which runs the query against your data before you commit to building it. :::steps 1. **Read the card.** Check the sentence and, if it matters, the detail. 2. **Press Confirm or Decline.** Confirm runs the stored change once, as you, with your own role's permissions, so it is held to the same rules as the console's buttons. Decline discards it. 3. **Read the result.** After Confirm, a line appears in the conversation, such as "confirmed · create view derived.revenue_by_month", and cherry says in a sentence what happened. After Decline, the line reads "declined" and cherry does not reply. ::: A card is in one of six states: **waiting for you**, **running**, **ran**, **failed**, **declined** or **expired**. A change nobody decides on expires after 7 days, and you ask cherry again. Only the person whose conversation it is can confirm its changes. ### Several changes at once When cherry proposes several changes of one kind, such as six relationships, they come as one card with a row each. Each row has its own confirm and decline buttons, and the card has **Confirm all** and **Decline all**. When a reply is waiting on more than one kind of change, one bar under them all reads "N changes waiting for you" and confirms or declines every one. Confirmed changes run one after another in the order cherry proposed them, which is the order they depend on each other: a table is accepted before the relationship that joins it. If one fails, the rest still run and each card shows its own result. cherry then answers once, about all of them. ## Autonomous In Autonomous mode cherry defines things on the semantic fluid, builds views, schedules tasks, starts syncs and loads rows as soon as it decides to. Settings says what that means: a metric it defines is live at once, a view it builds runs on your compute and is billed to your workspace, and a sync it starts runs now. Nobody is asked first. Choose it when you trust the people using cherry and want fewer interruptions. You can switch back to Ask first at any time. ## What always asks These ask for your confirmation in both modes, because they cannot be taken back or cannot be recalled. | Action | Why it asks | |---|---| | Take tables off the map | Every definition on the table goes with it | | Delete a definition | It is gone for good | | Drop a view or procedure | Its rows are gone | | Remove a report delivery | The schedule is gone | | Schedule, change or send an emailed report | A report in an inbox cannot be recalled | ## What runs without asking - Answering questions, and reading anything you can read. - Creating a report, placing blocks on it and changing its filters and blocks. Every change is kept as a version you can restore from the report's history. - Learn from my data, when you ask for it. It only files proposals, and nothing goes live until you accept them. It does use AI, so it counts towards your daily AI limit. - Remembering a fact, searching your earlier conversations, and taking you to a page. ## Everything else, in Ask first Held in Ask first and run at once in Autonomous: - putting tables on the map (which also draws the joins their column names state), and marking one as a fact or a dimension; - defining a metric, dimension, filter, relationship, alias, category or dataset; - accepting or rejecting a proposal; - creating, replacing or rebuilding a view or procedure, and running a procedure; - creating, running, pausing or resuming a scheduled task; - applying warehouse tuning findings; - starting a sync, and loading rows into the warehouse; - pausing or resuming a report delivery. ## Change the mode Owners and admins set it under **Settings · cherry**, in the panel **cherry · permissions**, under **Before it changes something**. Members and viewers see the setting but cannot change it. ![The two choices, Ask first and Autonomous, with a sentence on what each means.](https://docs.sanda-os.com.au/media/settings-ask-first.png "Ask first is selected by default.") Below it, under **What cherry may do**, five switches turn whole families of change on or off for everyone in the workspace: **semantics**, **modelling**, **reports**, **data** and **sql**. See [what cherry can do](https://docs.sanda-os.com.au/cherry/capabilities). ## What is recorded - **The conversation** keeps every card in the state it reached, and a line for each confirmation, decline or failure. Reopen a thread tomorrow and it reads as it did today. - **The audit trail** records changes to your workspace, including a change to these settings. When cherry acts for you it acts as you, with your role's permissions. - **Reports** keep a version for each change cherry makes. ## Connected AI assistants This page is about cherry, inside the console. AI assistants you connect over MCP are a separate case, with their own approval queue on the **Agents** page. See [MCP permissions](https://docs.sanda-os.com.au/mcp/permissions). :::links - [What cherry can do](https://docs.sanda-os.com.au/cherry/capabilities): The families of change, by role. - [Models and AI usage](https://docs.sanda-os.com.au/cherry/models-and-usage): What cherry costs, and the daily limit. ::: --- # Memory and conversations > How cherry keeps your conversations, how to find and manage them, and how to have it remember facts about your business across all of them. cherry keeps two different things. **Conversations** are the threads you have had, kept so you can go back to them. **Memory** is a short list of facts cherry reads at the start of every conversation, so you do not have to explain your business each time. ## Conversations Every conversation is a thread. A new thread starts when you press **new chat** at the top of the pane, when you press the plus in the thread list, or when you ask a question from the overview. cherry names a thread after its first exchange, in a few lowercase words, and you can rename it. Your threads are yours. Nobody else in your workspace can open them in the console, admins and owners included. cherry itself only searches your own. ### Find your threads Open the thread list with the history button in the pane's header, or use the list down the left of **cherry chat**. - **Find a thread** searches titles and the latest message of each thread. - Pinned threads sit at the top, then the rest, most recent first. - The **…** menu on a thread (**Thread options**) offers **rename**, **pin** or **unpin**, **archive** or **restore**, and **delete**. - **show archived** lists threads you have put away. Archiving hides a thread from the list. It does not delete it. When you reload the console or open it on another page, it reopens the thread you were last in on that browser. ### Delete a conversation **delete** removes the thread and every message in it for good. cherry asks you to confirm first. Confirmed changes that cherry made are not undone by deleting the thread, because the change is in your workspace, not in the conversation. ### How long conversations are kept Your chat history, including your questions, the answers and the rows behind each answer, is kept until you delete the conversation or close your workspace. It belongs to your workspace and is never used for training. See [privacy](https://docs.sanda-os.com.au/workspace/privacy). ### What cherry reads from a thread cherry reads the most recent 24 messages of the thread when it answers, not the whole of a long one. When you change topic, start a new chat. If you need something from an old thread, tell cherry what it was: it can search your earlier conversations by what was said ("the view we built last week", "that debtors question") and bring back the matching messages. ## Memory cherry keeps a fact for later conversations when you tell it something durable about your business or how you like to work: "our financial year starts in July", "revenue means net of GST", "show me tables, not charts". You will see a step called **remembering** when it does. You can also say "remember that…" yourself, or add a line by hand. Three things cherry is told not to keep: a secret, a number it fetched from your data, and a guess. Never type a password or token into a conversation. ### Two scopes - **The workspace.** Shared with everyone in the workspace, and read into everyone's conversations. - **Just me.** Read only into your own conversations, and visible only to you. Only owners and admins can add to or remove the workspace's memory, because it is read into everyone's conversations, theirs included. Everyone else keeps personal notes. When cherry is asked to remember something for the workspace by someone else, it keeps it as their own note. ### See, add and remove memory Under **Settings · cherry**, the panel **cherry · memory** lists what cherry remembers. Each line shows whether it is **yours** or the **workspace**'s, who added it and when. - To add one, type it under **Remember that**, choose **the workspace** or **just me** under **For**, and press **Remember**. - To remove one, press the bin beside it (**Forget**). A memory is one sentence of up to 500 characters, and a workspace can hold up to 200 in all, personal ones included. Every memory is read into every conversation, so a wrong or out-of-date line affects every answer until you remove it. Keep the list short and correct. :::tip Check memory when an answer surprises you If cherry keeps applying a rule you did not expect, look in **cherry · memory** first. A line such as "always exclude internal accounts" will shape every answer. ::: :::links - [Ask questions](https://docs.sanda-os.com.au/cherry/ask): Follow-ups, editing your last message and asking again. - [Models and AI usage](https://docs.sanda-os.com.au/cherry/models-and-usage): Choosing the model a thread runs on. ::: --- # Models and AI usage > Choose the model cherry answers with, see what AI costs in the top bar, and know what happens when the daily AI limit is reached. cherry runs on an AI model, and every call to one is billed to your workspace. You choose which model answers, owners and admins choose which models are on offer, and a daily limit stops spend running away. ## Choose a model At the foot of the box you type in, the model button shows the model cherry will use for this conversation. Press it to open the list. Each option shows a **tier**, which says what the model is for, and a **cost hint**: | Tier | What it suits | |---|---| | best | The deepest reasoning: wide schemas, long setups, questions that need several joins | | balanced | Everyday questions and building | | fast | Direct lookups, at the lowest cost. Thinner on multi-step reasoning | The cost hint runs from lowest to highest, and it is relative: a rough guide to which options spend more of your daily limit for the same conversation. Your choice applies to the conversation you are in, and is remembered in your browser for the next new one. If you have not chosen, a new conversation starts on your workspace's default. You can switch models part way through a conversation, and the next reply uses the new one. Some AI work is not run by the model in the picker. The passes that learn your data, for example, run on a model sanda chooses. They are still billed and still count towards your daily limit. ## Which models are on offer Owners and admins choose the models under **Settings · cherry**, in the panel **cherry · models**. - Switch on up to five models. They are what appears in the picker. At least one must stay on. - Press **make default** on one of them to set the model new conversations start on. - Each model shows its tier and its price per million tokens in and out, before sanda's AI markup, so you can compare them. Where the price is not available, it shows the cost hint instead. - A model marked **not listed by the provider** is switched on but may no longer exist. Switch it off. Members and viewers can use the picker but cannot change what is on it. ## The daily AI limit Every workspace starts with a daily AI limit of A$5. Owners and admins can set their own, up to A$50 a day. If you need more than that, contact sanda support. To change it, go to **Settings · Plan & budget**, open **Budget & alerts**, and set **Daily AI limit (AUD)**. Leave the field empty to use the default. A limit of 0 switches AI off for the workspace. The limit counts every AI call sanda makes for your workspace: cherry, the passes that learn your data, suggestions, drafts, and search indexing and searches. Syncs and scheduled reports are not counted. It resets at midnight UTC, which is 10am in Sydney during standard time and 11am during daylight saving. ## Watch the meter The ring in the top bar shows how much of today's limit you have used, and on a wide screen the figure beside it shows today's spend. Press it to open **AI today**: - today's spend against the limit, in a bar that turns amber as you near the limit and red when you reach it; - **This month**, the AI you will see on your invoice, which is billed with storage and compute; - **Today by model**, what each model has cost so far today; - your limit, and whether it is your own or the default; - when it **Resets**; - links to **Change the limit** and **Change model**. The meter updates as cherry works, so you can watch a long reply spend. The figure under each reply is what that reply cost, in the same billed dollars, so the two agree. ## Every AI call is billed Each call is charged at cost plus sanda's AI markup, in Australian dollars, and appears on your invoice with storage and compute. Nothing is free, though a trial's free credit pays for its first AI use. See [usage and billing](https://docs.sanda-os.com.au/billing/usage) and [budgets and limits](https://docs.sanda-os.com.au/billing/budgets-and-limits). Even the small call that names a new conversation is billed. Ways to spend less: - Start a new chat when you change topic. A long thread sends more of its history with every message. - Use a **fast** model for lookups and a **best** one for hard modelling. - Ask for a top ten, or for a report, instead of a long list. ## What happens at the limit When the workspace reaches its limit: - cherry stops, in the middle of a reply if need be, and says so: the workspace has reached today's AI limit, it resets at midnight UTC, and an owner or admin can raise it under **Settings · Plan & budget**. Your next message gets the same answer until then. - Other AI work waits, including learn from my data, suggestions and drafts. - Search index builds pause and pick up where they stopped. Search itself still answers, from matching words only. - **Syncs, reports and your data are not affected.** If alerts are on under **Settings · Plan & budget · Budget & alerts**, sanda emails the alert recipients once a day when the limit is reached. The setting is **When the day's AI limit is reached**. On a trial, AI is paid for from the trial's free credit first. Once the A$10 is spent, the workspace is frozen until an owner or admin moves it to a paid edition. Your data is kept. :::links - [Actions and approvals](https://docs.sanda-os.com.au/cherry/actions-and-approvals): How to keep changes under control. - [Budgets and limits](https://docs.sanda-os.com.au/billing/budgets-and-limits): The monthly budget beside the daily limit. - [Usage and billing](https://docs.sanda-os.com.au/billing/usage): Where AI appears on your invoice. ::: --- # Reports > A report is a canvas of blocks built on your semantic fluid. Build one by hand or with cherry, then share it, email it or publish it to readers. A report in sanda is a **canvas of blocks**: headline numbers, charts, tables and written analysis, laid out on a page and built on your [semantic fluid](https://docs.sanda-os.com.au/semantic-fluid). You draw a box and pick a metric, or you ask [cherry](https://docs.sanda-os.com.au/cherry) to build the whole pack, and the report keeps itself current because it never stores the numbers. ## What a report is made of Every block on a report stores a **question**, not an answer: which metric, broken down by which dimension, under which conditions. When someone opens the report, sanda asks each question again through the same [definitions](https://docs.sanda-os.com.au/semantic-fluid/metrics) that answer a question in cherry chat or the SQL workbench. The figure in a report, the figure in a chat answer and the figure in an emailed copy agree because they come from one place. That has three consequences worth knowing early: - **A report is never stale.** Numbers are read from your warehouse each time the report is opened, printed, shared or emailed. Only the written analysis can go out of date, and on the canvas its block says when it was written. - **A definition change reaches every report.** Fix `net revenue` once in the semantic fluid and every block that uses it changes with it. - **Nothing on the canvas can be typed over.** A cell holds what the warehouse said. A column of your own is a [formula](https://docs.sanda-os.com.au/reports/sheets), never a number you overwrote. ```text connections → sanda warehouse → semantic fluid → blocks on a canvas │ ┌───────────────┬───────────────────────────┼──────────────────────┐ the console a shared link an email on a schedule the reports portal (your team) (anyone you send it to) (PDF and PNG attached) (readers you invite) ``` ## Who reads a report A report can reach five kinds of reader, and each is a different promise about who sees what. | Reader | How they get it | Signs in? | What they see | |---|---|---|---| | Your team | **Activate · Reports** in the console | Yes | The canvas. Anyone who may edit can change it. | | Colleagues you choose | [Share a report](https://docs.sanda-os.com.au/reports/sharing) with named members | Yes | The canvas, read-only or editable. | | Anyone you send a link to | A [public link](https://docs.sanda-os.com.au/reports/sharing#public-links) | No | A read-only page with live numbers. | | Email recipients | A [scheduled delivery](https://docs.sanda-os.com.au/reports/delivery) | No | The report as an email, with a PDF and a PNG attached. | | Readers | The [reports portal](https://docs.sanda-os.com.au/reports/portal) | Yes | Published reports only, limited to their own rows if you set rules. | The console is for the people who build reports. Link visitors and email recipients need no account at all, and readers have a seat on the portal and nothing else. ## The Reports page Open the console and go to **Activate · Reports**. The page has two tabs, **Reports** and **Portal**, and a **Report schedules** button at the top right. The **Reports** tab is your list: - **New canvas** opens a short form. Give the report a **Name** and, under **What should it contain?**, describe it in plain language. Press **Create and let cherry build it** to have cherry lay out the whole pack from that description, or **Create blank canvas** to start empty. Either way the description stays with the report as its brief. - **Your reports** lists every report you can open, with its description, how many blocks it has, how many live [schedules](https://docs.sanda-os.com.au/reports/delivery) it has, and when it was created. Small tags mark a report that is narrower than the workspace (**private**, **shared with you**, or a count such as **2 shared**) and any report that a public link can open (**1 link**). - The copy icon duplicates a report and the bin icon deletes it. Deleting removes the report's blocks and its history, and cannot be undone. A read-only member does not see the copy icon, because a copy is a new report. - **Templates** appears once you have saved one. A template is a canvas saved to be copied, and **new report from this** (not shown to a read-only member) makes a report from it. See [the report canvas](https://docs.sanda-os.com.au/reports/canvas#templates-and-copies). Report names are unique in a workspace. Creating or renaming a report to a name that exists is refused. The **Portal** tab is where an owner or admin looks after the reports portal. See [The reports portal](https://docs.sanda-os.com.au/reports/portal). ## Who can do what | Action | Who | |---|---| | Open a report | Everyone in the workspace, unless the report is private (then its author, the people it is shared with, and owners and admins) | | Create, duplicate and edit reports | Anyone except a read-only member or a reader | | Share a report, make it private, create public links, delete it | The report's author, and owners and admins | | Publish to the portal, schedule a delivery | Owners and admins | A member with the read-only role can open reports and ask questions, and changes nothing. A reader has no console at all: they use the [reports portal](https://docs.sanda-os.com.au/reports/portal). ## Where to go next :::links - [The report canvas](https://docs.sanda-os.com.au/reports/canvas): Draw blocks, arrange them, filter the whole page and restore an earlier version. - [Blocks](https://docs.sanda-os.com.au/reports/blocks): The six kinds of block and what each one can show. - [Sheets and formulas](https://docs.sanda-os.com.au/reports/sheets): Sort, total, format and add your own columns to a table, then take it to Excel. - [Build reports with cherry](https://docs.sanda-os.com.au/reports/build-with-cherry): Ask for a whole pack, or change one that exists. - [Share a report](https://docs.sanda-os.com.au/reports/sharing): Colleagues, and public links for people outside. - [Schedules and delivery](https://docs.sanda-os.com.au/reports/delivery): Email a report on a schedule, or post it to Slack. - [The reports portal](https://docs.sanda-os.com.au/reports/portal): A place for the people you build reports for. ::: --- # The report canvas > Create a report, draw blocks on the page, arrange them, filter the whole report at once, and restore an earlier version. The canvas is where you build and edit a report. It works like a design tool and prints like a page: a ruled sheet on a darker desk, blocks that snap to the sheet's grid, and cherry docked at the side when you want help. ![The canvas toolbar: back arrow, report name, block count, undo and redo, then block, header, filters, share and publish, re-run, report colours, history, more, zoom and the cherry button.](https://docs.sanda-os.com.au/media/canvas-toolbar.png "The toolbar of an empty canvas") ## Create a report :::steps 1. **Open Reports.** In the console, go to **Activate · Reports**. 2. **Open the form.** Press **New canvas**. 3. **Name it and describe it.** Enter a **Name** and, under **What should it contain?**, write the brief in plain language, for example "P&L vs budget, cash position, top debtors by ageing, margin by product line". The description can be up to 1,000 characters. 4. **Choose how to start.** Press **Create and let cherry build it** to have cherry lay out the whole pack from your description (this one needs both the name and the description), or **Create blank canvas** to build it yourself. ::: A new report opens straight away. If a blank report has a description, its empty canvas offers **let cherry build it from the description**. ## The page The sheet is 24 cells wide. Each cell is a square that blocks snap to, so two blocks placed by different people, or by cherry, line up without nudging. The sheet grows downwards as the report does and always keeps a clear band at the foot for the next block. Because the width is fixed, a report is the same report on every screen and prints the same way. The canvas opens zoomed to fit the page across your window, up to 150%, and a page narrower than the window sits centred on the desk. Blocks cannot leave the sheet's width. Drawing, dragging and resizing all stop at its left and right edges, because a block half off the page is a block half out of the report. ## Add a block Every block starts as a box you draw. You can draw as many boxes as you like before filling any of them. :::steps 1. **Draw a box.** Press and drag on the empty page. The box snaps to the grid. The **block** button in the toolbar drops a box in the middle of your view if you would rather not draw. 2. **Choose a door.** The box asks **what goes here?** and offers two answers: **pick from the fluid** or **ask cherry**. 3. **Fill it.** The next two sections describe each door. ::: ### Pick from the fluid This door builds a block from your semantic fluid with no AI involved. It opens a pane on the right, titled **build a block from the fluid**, and the box on the page becomes a live preview of the block at its real size. The preview re-runs every time you change a pick. 1. Press a **metric** in the catalogue for the number, then a **dimension** to break it down. You can also press a raw column, and the menu on its chip chooses how it is summarised. A date dimension arrives grouped by month, and the menu on its chip changes the grouping. 2. Add **conditions** to narrow the rows, using the same condition builder as the workbench. See [filters and conditions](https://docs.sanda-os.com.au/semantic-fluid/dimensions-and-filters). 3. Leave **show it as** alone, or choose **kpi**, **bar**, **line** or **table**. Until you choose, sanda suggests a mark from what you picked and says so: a measure alone is a kpi, a measure over dates is a line, a measure over anything else is a bar, and a selection with no measure is a table. 4. Edit the **title** if you want to. It starts as your selection said in words, such as "net revenue by month". 5. Set **rows** for a bar, line or table, up to 200. A kpi has no rows setting. 6. For a kpi, tick **compare with the previous period** to show the change against the period before. For a kpi or a line, enter a **target** and, if you like, what it is **called**. See [Blocks](https://docs.sanda-os.com.au/reports/blocks) for both. 7. Press **place block**. To change a block that is already on the page, select it and press **edit** in its toolbar. The same pane opens seeded with what the block asks today, and the button reads **apply**. A block cherry placed is a starting point, not something to delete and re-ask for. ### Ask cherry This door takes a sentence. Pick what cherry may place with the chips, then describe it. | Chip | cherry may place | |---|---| | **cherry decides** | A visual, written analysis, or both (at most three blocks) | | **a visual** | One kpi, chart or table | | **written analysis** | One text block, with the numbers it rests on | Type your request (for example "Net revenue by month, last 12 months") and press Enter or **build**. Blocks land on the page while cherry is still working. If your request is ambiguous, cherry asks **which do you mean?** and offers a few buttons. If cherry places nothing, it tells you why under **cherry says**, and **ask differently** opens the box again. For building a whole pack in one go, use cherry's own pane instead. See [Build reports with cherry](https://docs.sanda-os.com.au/reports/build-with-cherry). ## Arrange blocks Select a block by clicking it. Its border turns to the accent colour. - **Move** it by dragging its header. A dashed outline shows where it will land. - **Resize** it by dragging the corner handle at its bottom right. A block is never smaller than 3 cells wide and 2 high. - **Rename** it by double-clicking its title. Titles are lowercase, like every other name in the semantic fluid. - **Switch the mark** with the four icons in the small toolbar above a selected chart or table (kpi, bar, line and table). The question stays the same and only the drawing changes. - **Change the time grouping** with the **by day**, **by week**, **by month**, **by quarter** or **by year** menu, which appears when the block breaks a date down. - **Remove** it with the bin icon, or press Delete. Click a bar in a bar block to filter the whole canvas to that category (this works when the breakdown is not a date). See [Filter the whole report](#filter-the-whole-report). The numbers are read when the report opens. Press **re-run every block** in the toolbar to read them all again, or **run again** in one block's header. A **header** block is the report's masthead: its logo, name and subtitle. Press **header** in the toolbar to add one. A report has at most one, and it goes at the top, with everything already on the page moving down to make room. ## Filter the whole report Press **filters** in the toolbar. The pane titled **filters · every block** holds conditions that apply to every block on the canvas, so "the June pack" can be the same blocks with a different window. You add a filter in two ways: - **Say it.** Type "last 12 months", "only paid invoices" or "region is apac" into the **say it** box and send it. sanda turns the sentence into one condition and checks it against your fluid before adding it. If it cannot, it tells you what was missing. - **Build it.** Use the condition builder under the box to pick a field, an operator and values, with the same date presets the workbench has. A report filter on a field **replaces** any block's own condition on that field. A canvas filter of "last 12 months" therefore widens a block that said "last month". A filter on a named period such as "last 12 months" keeps moving with the calendar. A report takes up to 20 filters, and a filter's field has to reach every block's table, or the blocks it cannot reach refuse to run. The **filters** button shows how many are active. Clicking a bar adds an **is** filter for that category and opens the pane, so you can see and remove it. ## Comments Each block has a speech bubble in its header. Open it to leave a note for whoever reviews the report, such as a question for the board. Comments carry your name and time, stay with the block, and go when the block goes. Only the person who wrote a comment can remove it. Comments are working notes. They are not included in a [shared link](https://docs.sanda-os.com.au/reports/sharing), on the [portal](https://docs.sanda-os.com.au/reports/portal) or in copies of the report. ## Zoom, pan and fit | To | Do this | |---|---| | Pan | Scroll, or hold Space and drag, or drag with the middle button | | Zoom about the pointer | ⌘ or Ctrl and scroll | | Zoom in or out | The plus and minus buttons in the toolbar | | Go back to 100% | Press the percentage between them | | Fit all the blocks in view | The expand icon at the right of the zoom group | Zoom runs from 25% to 200%. The page can never leave the window. ## Save, undo and history The canvas saves as you work. The block count in the toolbar reads **saving…** while a change is on its way and **not saved** if one failed. Closing the tab with an unsaved change asks first. - **Undo and redo** with ⌘Z and ⌘⇧Z (or Ctrl on Windows), or the arrows beside the report's name. Undo goes back up to 60 changes in the current visit. - **History** (the clock icon) lists saved versions of the report, newest first, each with when it was saved, how many blocks and filters it had, and who saved it. Press **restore** on an older one to go back to it. Restoring is a save of its own, so the version you left is kept too. A version includes the report's colours. A save within two minutes of the newest version updates it instead of adding another, and the last fifty versions are kept. Renaming a report does not make a version. Changes cherry makes are saved like your own edits, so you can **restore** an earlier version. ## Templates and copies Open the **more** menu (three dots) for the report-level verbs. - **duplicate report** asks for a name (it suggests "name copy") and opens the copy. A copy keeps the blocks, filters and colours, and leaves behind the comments, cherry's conversation about the report and the history. Anyone who can create reports can duplicate one, including a report shared with them to read. A read-only member does not see it. - **save as template** asks for a name and makes a template from the canvas as it is now. Your report stays as it is. The template appears under **Templates** on the Reports page, and its own canvas carries a **template** tag. A template is never delivered by email. On the Reports page, press **new report from this** to make a real report from it: it keeps the blocks, filters and colours, and the numbers are read fresh. - **delete report** removes the report, its blocks and its history. It is offered to the report's author and to owners and admins, and asks you to confirm. ## Print or export to PDF Choose **export pdf** in the **more** menu, or press ⌘P. The canvas drops its toolbar and lays the page flat at a scale that fits a landscape sheet, then opens your browser's print dialog. Use its option to save as a PDF. This is the console's own print. A report that arrives by email has its own PDF and PNG attachments, made when the email is sent. See [Schedules and delivery](https://docs.sanda-os.com.au/reports/delivery#what-arrives). ## Reports that are read-only If a report was shared with you to read, or you are a read-only member, the report opens with a **read only** tag and the toolbar loses its editing verbs. A read-only member also loses **duplicate report**, since they cannot create reports. You can still pan, zoom, and sort and export any table, because those are ways of reading. See [Share a report](https://docs.sanda-os.com.au/reports/sharing). ## Keyboard shortcuts | Shortcut | Does | |---|---| | ⌘Z | Undo | | ⌘⇧Z | Redo | | ⌘P | Export to PDF | | Delete or Backspace | Remove the selected block | | Space and drag | Pan | | Esc | Deselect, and close an open menu or the builder | --- # Blocks > The six kinds of block on a report canvas (kpi, bar, line, table, text and header), what each one shows, and the settings each one takes. A block is one panel on a [report canvas](https://docs.sanda-os.com.au/reports/canvas): a hairline border, a header you drag by, a lowercase title and a resize handle in the corner. There are six kinds. Four draw data, one holds written analysis, and one is the report's masthead. ![A published report as a reader sees it: a header block, four kpi blocks, a line block and a bar block.](https://docs.sanda-os.com.au/media/portal-report.png "A header, four kpis, a line and a bar") | Kind | Shows | Built from | Starts at (cells) | |---|---|---|---| | **kpi** | One headline number | One measure, no breakdown | 6 wide, 4 high | | **bar** | A number by category, largest first | A measure and a breakdown | 12 wide, 8 high | | **line** | A number over time | A measure and dates | 12 wide, 8 high | | **table** | The rows themselves, as a [sheet](https://docs.sanda-os.com.au/reports/sheets) | Anything | 12 wide, 8 high | | **text** | Written analysis | Words | 12 wide, 8 high | | **header** | The report's logo, name and subtitle | The **header** button | 24 wide, 3 high | The page is 24 cells wide, so every starting width is a whole fraction of it. Four kpis make a row and two of anything else do. When cherry builds a pack it lays the blocks out in bands that way: headline kpis across the top, then trends, then breakdowns, then analysis beside the table it is about. ## What every data block stores A kpi, bar, line or table stores a **selection**: the metrics, dimensions, columns, named filters and conditions it asks the [semantic fluid](https://docs.sanda-os.com.au/semantic-fluid), and a row limit. It never stores rows. Each time the report is opened, printed, shared or emailed, sanda asks the question again, with the report's [filters](https://docs.sanda-os.com.au/reports/canvas#filter-the-whole-report) folded in. A report with many blocks asks a few of them at a time, so it fills in from the top rather than all at once at the end. A block that asks exactly what another block asked a few seconds before can be answered from that answer, without asking your warehouse again. A block returns at most 200 rows. In the builder the row limit starts at 15 and you can raise it to 200. If the fluid cannot answer a block, for example because a report filter's field does not reach the block's table, the block shows sanda's sentence saying why instead of a chart. A block that runs and finds nothing says **That ran and matched no rows.** ## How a block decides what to draw The kind you choose is what you asked for, and the rows that come back decide what can honestly be drawn. When the two disagree, sanda draws the nearest true mark and says so in a caption at the foot of the block, such as "drawn as a bar: a line needs dates along the bottom". | You chose | The rows are | Drawn as | |---|---|---| | Anything | No measure to plot | A table | | **kpi** | One number | A kpi | | **kpi** | A number with a breakdown | A line if the breakdown is dates, otherwise a bar | | **line** | A number by dates | A line | | **line** | A number by anything else | A bar | | **line** | One number | A kpi | | **bar** | A number by category | A bar | | **bar** | One number | A kpi | | **table** | Anything | A table | A breakdown counts as dates when there is exactly one breakdown column and its first value starts with a year and month, such as `2026-09` or `2026-09-30`. ## kpi A kpi is one large number with the measure's name under it. Numbers under 10,000 show in full, with up to two decimals. Larger ones are shortened: 12,345 reads as 12.3K, 482,000 as 482K, 1,250,000 as 1.3M. A missing value shows as a lone dash. When the metric has a **Shown as** or a **Unit** in the [semantic fluid](https://docs.sanda-os.com.au/semantic-fluid/metrics#unit-period-and-display), the number takes it: a percent metric reads 72% rather than 0.72, a currency metric in AUD reads AUD 184K, and a metric with a unit of kg reads 81.25 kg. A whole number shows no decimals. A target's figure and the gap to it are written the same way. The change on the previous period is always a percentage change, whatever the metric is. The same applies to the values a bar and a line print. A kpi has two optional extras. **Compare with the previous period.** Tick this in the builder and the kpi shows the change against the period before, for example **+4.1% vs previous period**. sanda finds the block's one date-range condition, slides it back by its own length, asks the same question again and works out the change. The change is shown to one decimal place when it is under 10% and as a whole number above that. An increase uses your report's **up** colour and a decrease its **down** colour (see [Report colours and logo](https://docs.sanda-os.com.au/reports/themes)). The comparison needs the block to have exactly one date-range condition, including any a [report filter](https://docs.sanda-os.com.au/reports/canvas#filter-the-whole-report) supplies. With none, or with two, nothing is compared. If the earlier period has no value, the kpi says **no previous period to compare**. If its value is zero, no change is shown, because a change from zero has no size. **A target.** Enter a figure and, if you like, a name for it (up to 40 characters, "target" by default). The kpi then says how far it is from the figure, for example "25 above target (80)", "3.1 below goal (4)" or "on budget (80)". The gap is stated in words and never coloured, because whether above is good is for the reader to decide. ## bar A bar block draws a number by category, largest first, in a single colour with each value at the tip of its bar. It shows as many bars as the block's height allows, up to twelve, and the caption says how many smaller ones are not shown. Negative values draw to the left of a zero line. If the selection has more than one measure, a chip for each appears above the bars and you switch between them. Click a bar to filter the whole canvas to that category. sanda adds the filter to the report's filters and opens the **filters** pane so you can see it and remove it. This works when the first breakdown is a dimension without a time grouping, or a raw column with no summary. ## line A line block draws one measure over dates. It labels the first and last points, shows each point's value when you hover, and marks every point when there are 24 or fewer. The points are sorted by date. Dates are labelled by month and year when every point falls on the first of a month, and by day otherwise. A **target** on a line is drawn as a dashed rule labelled with its name and figure. It is a reference, not a second series. ## table A table block shows the rows the selection returned. It is the one block that grows an instrument: a header you can sort by, a footer with totals and a selection summary, formats, hidden columns, columns of your own written as formulas, and an export to Excel. That has its own page: [Sheets and formulas](https://docs.sanda-os.com.au/reports/sheets). ## text A text block holds written analysis, and cherry is what writes it. To add one, draw a box, choose **ask cherry** and pick **written analysis**, or ask cherry in its pane. The canvas has no editor for the words themselves. To change what a text block says, press the sparkle icon in its header (**rewrite against today's numbers**), which asks cherry to rewrite it against the current figures, or ask cherry to change it. The analysis is plain paragraphs with two bits of markup: - A line that starts with `- ` is a bullet. - Text between double asterisks, like `**this**`, is bold. cherry is told to use only figures that came back from a query it ran in that conversation. A sentence about a number goes stale in a way a chart does not, so on the canvas the block's footer records when it was written, for example "written 30 Sep · numbers as they were then". ## header A header block is the report's masthead: your logo (or a coloured bar when there is none), the report's name in a display size, and a subtitle, which is the report's description (on the canvas, today's date when there is none). Add it with the **header** button in the toolbar. A report has at most one, always at the top. cherry never places one. ## Where each feature shows Not every feature travels to every place a report goes. | Feature | Canvas | Shared link and portal | Email | |---|---|---|---| | Numbers | Live | Live | As at the send time | | kpi comparison with the previous period | Yes | Yes | Yes | | kpi and line targets | Yes | Yes | Yes | | A metric's **Shown as** and **Unit** on kpi, bar and line figures | Yes | Yes | Yes | | Click a bar to filter | Yes | No | No | | Sort, select and export a table | Yes | Yes | Not applicable | | Comments | Yes | No | No | In an email, a table shows its first 10 rows, and a line is drawn as filled columns that trace its shape, because inboxes do not draw charts. --- # Sheets and formulas > Sort, total and format a table block, add columns of your own written as Excel-style formulas, and download the result as an Excel workbook. A table block is a **sheet**. Someone looking at rows wants to do four things: sort them, total them, format a column, and add a column of their own. Most people already do all four in Excel after exporting, which is where a report stops being sanda's numbers and becomes a copy in a downloads folder. The sheet lets you do them on the report, over live rows, and the export is there for the cases that genuinely belong elsewhere. Two rules keep a report a report rather than a spreadsheet: - **Nothing is editable.** A cell holds what the warehouse said. A column of your own is a formula, never a number typed over one. - **A sheet stores instructions, never rows.** Your sort, formats, totals, hidden columns and formulas are saved on the block and applied to whatever the block returns next time. A margin column you define in June is still a margin column in July, over July's rows. ## Sort, select and read - **Sort** by clicking a column header. Click again to reverse, and a third time to clear. Empty cells sort last in either direction. - **Select** a cell by clicking it. Drag, or shift-click, to select a range. The footer then shows how many cells are selected and, for the numeric ones, their sum and average, where Excel puts them. - The footer also says how many rows and columns the table has. When the block's row limit cut the result short it reads, for example, **15 rows of more**. People who can only read a report, whether through a shared link, the portal or a read-only share, can sort and select too. Their changes last for the visit and are not saved, and they cannot format, total or add columns. ## Format a column Hover a column header and press the three dots to open its menu. **format** offers: | Format | Shows | |---|---| | **automatic** | Numbers to two decimals at most, with thousands separators. Text is left alone. | | **whole number** | 1,235 | | **number** | 1,234.57 | | **percent** | 25.6%. The value is multiplied by 100, so use it on a ratio such as 0.256, not on 25.6. | | **currency** | $1,234.50 | | **compact (1.2M)** | 12.5K, 1.3M. Under 10,000 the number shows in full. | | **text** | The value exactly as it is | A value that starts with a zero and is five or more digits long, such as a code `00123`, is never treated as a number under any format, so its zeros survive. The console does not currently let you choose the number of decimals or a currency code. **currency** shows a dollar sign and two decimals. ## Total a column The **total** menu on a column adds a totals row to the foot of the table. Choose **sum**, **average**, **minimum**, **maximum** or **count** (which counts the cells that are not empty), or **none** to remove it. The row is labelled **total** in the first column when that column has no total of its own. Totals cover the rows the block returned, the same as everything else in the sheet. If the footer says **of more**, raise the block's row limit before trusting a total. ## Hide a column The three-dot menu has **hide**, and **show all** once anything is hidden. A hidden column is not drawn, and it is left out of the csv download. A formula written before you hid a column keeps reading it on the report. The formula editor only accepts columns that are showing, though. In the Excel download a hidden column is still written, hidden the way Excel hides one, so a formula that reads it keeps calculating there too. Unhide it in Excel to see it. ## Add a column of your own :::steps 1. **Open the editor.** Press **formula**, the button with a plus icon in the table's footer. 2. **Name the column.** Type a **column name** such as `margin %`. Names are lowercase. 3. **Write the formula.** Type it in the formula box, using column names in square brackets. For example: ```text IF([net revenue] = 0, 0, ([net revenue] - [cost]) / [net revenue]) ``` The formula checks itself as you type. If it cannot be read, or it is longer than 500 characters, the reason appears under the box and the save button stays off. A column name stops at 60 characters. 4. **Save it.** Press the tick or Enter. The new column appears at the right of the table, marked with a function icon in its header. 5. **Format it.** Open the new column's menu and choose **percent**. ::: A column in brackets means **this row's value**. Inside `SUM`, `AVERAGE`, `MIN`, `MAX`, `COUNT` or `COUNTA` the same reference means **the whole column**, so `[net revenue] / SUM([net revenue])` is each row's share of the total in one line. A formula can read the columns before it, including formula columns you added earlier. You can add up to 12 formula columns to one table, and hidden formula columns count toward that. When a table has 12, the **formula** button turns off and its tooltip says why. Remove one to add another. To change a formula, open its column's menu and press **edit formula**. The editor's bin icon removes the column. When a formula cannot be worked out for a row, the cell shows an error word such as `#DIV/0!` and the rest of the table carries on. A formula that cannot be read at all fills its column with `#NAME?`, marks the header with a red `!` and says why under the table. The full list is in the [formula reference](https://docs.sanda-os.com.au/reference/sheet-formulas#errors). The language is Excel's, on purpose: the same operators, the same function names, the same meaning. Everything you can write is in the [formula reference](https://docs.sanda-os.com.au/reference/sheet-formulas). ## Take it to Excel The footer has two download buttons. Both export the table **as you see it**: the sort applied and formula results included. The csv leaves hidden columns out. The workbook keeps them as hidden columns, so every formula still has the cells it reads. | Button | You get | |---|---| | **xlsx** | An Excel workbook with one sheet. Formula columns are **live formulas**, not pasted numbers, so they keep calculating in Excel. | | **csv** | A plain text file of the values. | The workbook has a bold header row that stays put as you scroll, a filter on each column, and your formats carried across as Excel number formats. A totals row, if you set one, is written as Excel formulas over the column. The sheet is named after the block's title, cut to Excel's 31-character limit. Numbers the warehouse sent as text, such as `2,000`, arrive as numbers. The file is named after the block's title. The compact format arrives in Excel as a whole number with thousands separators. A CSV holds the raw values rather than the formatted ones, with a **total** row at the end if you set totals. Any text cell that begins with `=`, `+`, `-` or `@` is written with a leading apostrophe, so a spreadsheet reads it as text and never runs it as a formula. Anyone who can see a table can download it, including people holding a shared link and portal readers, who get the rows their own data rules allow. Bear that in mind before you share a report whose tables hold detail. :::tip Live rather than a download To keep Excel connected to the semantic fluid so it refreshes on demand, use the [OData feed](https://docs.sanda-os.com.au/odata/excel) instead of exporting a table. ::: ## What the sheet does not do There are no cell references such as `A1`, no lookups such as `VLOOKUP`, no conditional sums such as `SUMIF`, no date functions and no running totals. A formula sees the rows of its own table and nothing else. If you need a shaped number more than once, define it as a metric in the [semantic fluid](https://docs.sanda-os.com.au/semantic-fluid/metrics), so every report gets it. --- # Report colours and logo > Set your workspace's report colours and logo once under Settings, and override them on a single report when it needs its own look. Reports draw in your colours. You choose them once for the workspace, every report follows, and a single report can pick its own when it needs to (a board pack in the board's colours, say). Nothing else about a report's design changes: colours are the accent that charts and kpis are drawn in, the two colours a kpi's change uses, and your logo. ## What the colours do A report theme has three colours and a logo. | Setting | Where it shows | |---|---| | **accent · charts and kpis** | Bars, lines, the header block's bar, a selected block's outline on the canvas, and the tints derived from it | | **up · a good change** | A kpi's change when it is an increase, such as **+4.1% vs previous period** | | **down · a bad change** | A kpi's change when it is a decrease | | Logo | The [header block](https://docs.sanda-os.com.au/reports/blocks#header), on the canvas, on a shared link, on the [portal](https://docs.sanda-os.com.au/reports/portal) and on the PDF you print from the canvas | The up and down colours follow the direction of the number, not whether the change is good news. A fall in overdue debtors is a decrease and is drawn in the **down** colour. Choose colours you are comfortable reading that way. The theme applies to the report itself. The console around it keeps its own colours. ## Set the workspace colours Only an owner or admin can change these. :::steps 1. **Open the panel.** In the console, go to **Settings · Workspace**. The panel is called **Report colours**, under the workspace name. 2. **Pick a starting point.** Choose one of the presets: **salmon** (sanda's own and the default), **cherry**, **navy**, **ocean**, **forest**, **plum**, **amber**, **slate** or **ink**. 3. **Adjust if you need to.** Click any of the three colour swatches to set an exact colour. The preset changes to a custom one. 4. **Add your logo.** Press **upload** and choose a PNG, JPEG, WebP or SVG. Keep it under 146 KB. If it is larger, sanda says how big it is and asks for it smaller: export it smaller or as an SVG. Press **replace** to swap it and **remove** to take it off. 5. **Check the preview.** A small kpi and a bar chart are drawn with the colours you have chosen, using the same drawing the report uses. 6. **Save.** Press **Save colours**. Every report that has not picked its own follows at once. ::: :::tip A preset is a starting point for the colours Picking a preset sets the accent and puts **up** and **down** back to their defaults. Your logo stays. Choose the preset first, then adjust any single colour. ::: Until you save something, the panel says **using sanda's own colours**. ## Give one report its own colours Anyone who can edit a report can override the colours on it. :::steps 1. **Open the report.** Go to **Activate · Reports** and open it. 2. **Open the colours.** Press the palette button in the toolbar. Its label is **report colours**, and it shows a dot in the report's current accent. 3. **Choose.** Pick a preset, adjust the swatches or upload a logo, exactly as in the workspace panel. The change saves as you make it. 4. **Go back if you change your mind.** Press **workspace colours** to drop the override and follow the workspace again. ::: A per-report theme is part of the report's [history](https://docs.sanda-os.com.au/reports/canvas#save-undo-and-history), so restoring an older version restores its colours too. A copy of a report, and a template made from it, keep the colours. ## Where your colours travel | Place | Colours | Logo | |---|---|---| | The canvas | Yes | Yes, in the header block | | A [shared link](https://docs.sanda-os.com.au/reports/sharing) | Yes | Yes | | The [reports portal](https://docs.sanda-os.com.au/reports/portal) | Yes. The portal itself draws in the workspace colours. A published report keeps the colours it had of its own when it was published, and follows the workspace's if it had none. | Yes, in the portal's top bar and the report's header block | | An [emailed report](https://docs.sanda-os.com.au/reports/delivery) | Yes. Its charts and kpis use the report's colours. | No | | A printed PDF from the canvas | Yes | Yes | An emailed report and its PDF and PNG attachments are drawn without your logo, because inboxes do not reliably show embedded images. --- # Build reports with cherry > Ask cherry to build a whole report from a sentence, or to change one that exists by setting its window, retitling a block, redefining it or taking it off. cherry can build a report for you and change one you already have. It works from your [semantic fluid](https://docs.sanda-os.com.au/semantic-fluid), so the numbers in a report cherry builds are the same numbers your team agreed on, and it says so plainly when the fluid cannot show what you asked for. ## Ways to start | From | What you do | |---|---| | **Activate · Reports** | Press **New canvas**, describe the report under **What should it contain?**, then **Create and let cherry build it**. | | An empty Reports page | Press **Build one with cherry**. cherry asks what to call the report and what it is for before it builds anything. | | An empty canvas that has a description | Press **let cherry build it from the description**. | | A box you drew on a canvas | Choose **ask cherry**, pick what it may place and describe one block. See [the report canvas](https://docs.sanda-os.com.au/reports/canvas#ask-cherry). | | cherry's pane, on any page | Ask in your own words. Open the pane from the cherry button at the top of the console. On a canvas whose pane is closed, the chat icon at the right of the toolbar opens it. | cherry needs two things to build a report: a **name** and **what it is for**. If you gave only one, it asks for the other in a single short question and stops. Once it has both, it builds the whole report in one reply. ## What cherry builds A full report from cherry follows the order a management pack reads in: 1. Four to six headline **kpis**. 2. Two or three **trends** over time. 3. The **breakdowns** that matter. 4. A short paragraph of written **analysis**. Then it opens the report for you. Blocks appear on the canvas one at a time as cherry places them, and when the last one lands the pack settles into rows: four kpis to a row and two of anything else, every block in a row the same height. See [Blocks](https://docs.sanda-os.com.au/reports/blocks) for what each kind shows. cherry chooses the mark from the shape of the question: one number is a kpi, a number over time is a line, a number by category is a bar, several columns to read are a table and a paragraph of judgement is text. A kpi over a date window can carry a comparison with the previous period. Titles are short and lowercase. cherry works to a few rules that keep the analysis honest: - **Every number in a written block comes from a query** it ran in that conversation. It is told not to estimate. - **A target only appears when you gave one.** It is told never to invent a goal to measure against. - **If the fluid cannot show what you asked for**, it names what is missing (a metric, a dimension, a relationship, a source) and does not place something that answers a different question. These are instructions to cherry, not checks the canvas makes, so read a written block before you send it on. ## Ask for a change You do not have to rebuild a report to change it. Ask cherry, and it reads the report first, then changes it. ```text Put the weekly sales dashboard on the last 7 days. Rename "net revenue by week" to "revenue by week" and draw it as a bar. Take the debtors table off the board pack. Add a target of 80 to the on-time delivery kpi. ``` cherry can do these things to a report that exists: | It can | Notes | |---|---| | Set the report's filters | A window for the whole report is **one** report filter on its date dimension, not a condition on each block, and it holds for blocks added later. cherry can also clear them. | | Redefine a block | The metric, breakdown, conditions, mark, row limit, target, comparison, title or written words. The block keeps its position, size, comments and sheet. | | Take blocks off | Any blocks, by name. | | Place new blocks | On the open report, or on another report you name. | | Find a report | It can list the reports you can open and read one before changing it. | Every change is checked before it is made. cherry runs each block again with the report's filters folded in, and if any block could not run, **nothing changes** and it tells you which blocks and why. That way a report is never left half edited, and a filter on a field that a block's table cannot reach is caught up front rather than every time someone opens the report. If the report is open, the changes land as a single edit, so one **undo** takes them all back. Every change is also saved as a version in the report's [history](https://docs.sanda-os.com.au/reports/canvas#save-undo-and-history). ### What cherry does not do to a report cherry does not move or resize blocks, and does not add the header block. It does not edit a table's [sheet](https://docs.sanda-os.com.au/reports/sheets) (formulas, formats, totals). It does not set colours, share or publish a report. Those are yours to do on the canvas, and a block cherry redefines keeps whatever sheet you built on it. ## Schedule and send with cherry cherry can also schedule a report: tell it the report, the email addresses and when, and it sets up the [delivery](https://docs.sanda-os.com.au/reports/delivery). It can list what is scheduled, change a delivery, send one now, pause or resume it, and remove it. ```text Email the board pack to board@acme.co and cfo@acme.co every Monday at 7am Sydney time. ``` **An email always asks first.** However your workspace's cherry is set up, scheduling, changing and sending a delivery wait for you to confirm on a card, because a report in an inbox is a copy of your numbers that cannot be recalled. Removing a delivery asks too. Pausing asks unless cherry is set to run changes without asking. Scheduling is for owners and admins, as it is on the Report schedules page. ## What you control An owner or admin decides what cherry may do under **Settings · cherry**. The **reports** group covers creating reports, placing and changing blocks, and scheduling deliveries. If it is off, cherry can still answer questions about your reports and can no longer change them. Creating and changing a report is not held for confirmation in the way a change to your data model is, because every change is a version you can restore. A member with the read-only role can ask cherry about reports and it changes nothing. cherry's work counts against your workspace's AI usage. See [models and usage](https://docs.sanda-os.com.au/cherry/models-and-usage) and [actions and approvals](https://docs.sanda-os.com.au/cherry/actions-and-approvals). ## Getting good results - **Name the numbers the way your team does.** cherry matches the metrics and dimensions in your fluid, so "net revenue by region" lands more exactly than "how are we doing". - **Give the window.** "Last 12 months" or "this quarter" saves a follow-up. For a whole report, say so: "make the whole report last 7 days". - **Say what the report is for.** The description becomes the brief every later request reads, so "for the Monday leadership meeting" shapes what it includes. - **Start from the fluid you have.** If a report needs a number that is not defined yet, [define the metric](https://docs.sanda-os.com.au/semantic-fluid/metrics) first, or ask cherry to. --- # Share a report > Choose who in your workspace can open a report, and send a public link to people outside it, such as a board or a client, that you can revoke at any time. A report is open to everyone in your workspace until you narrow it. You can share it with specific colleagues, and you can give a link to people who will never have a sanda account. These are different promises, and the **share** pane keeps them apart. :::note The **share** button in a report's toolbar is offered to the report's author and to owners and admins. Someone a report was shared with cannot share it onwards, even with permission to edit, because widening an audience belongs to whoever narrowed it. ::: ## Four ways to put a report in front of people | | Share | Public link | Publish to the portal | Scheduled email | |---|---|---|---|---| | **For** | Colleagues in the workspace | Anyone who holds the link | Members and readers you choose | Any email address you list | | **Sign-in** | Yes | No | Yes | No | | **They see** | The working report, and can edit if you allow | A read-only page with live numbers | A frozen copy of the layout with live numbers, limited by each reader's data rules | The numbers as at each send | | **You stop it by** | Removing the person, or making the report private | Revoking the link | Unpublishing | Pausing or removing the delivery. What was already sent cannot be recalled. | The last two are covered in [The reports portal](https://docs.sanda-os.com.au/reports/portal) and [Schedules and delivery](https://docs.sanda-os.com.au/reports/delivery). ## Who in your workspace can open it Open the report and press **share**. Under **who in this workspace can open it** choose: - **everyone here**: every member of the workspace can open it. This is the default. - **only chosen people**: only you, the people you list, and the workspace's owners and admins. Owners and admins can open every report in the workspace, private ones included. They can read the audit trail and the warehouse too, so sanda does not pretend otherwise, and the pane says so on a private report. A private report stays private when it is copied. **Duplicate** and **save as template** on a private report make a private copy that belongs to whoever made it, and the people the original was shared with are not added to the copy. Its schedules are listed on the Report schedules page only for people who can open it. A private report carries a **private** tag on its canvas. In the Reports list it reads **private**, or a count such as **2 shared** once people are added, and the people it is shared with see **shared with you**. ### Share with a person :::steps 1. **Open the pane.** Press **share** in the toolbar. 2. **Pick the person.** Under **people**, use **Share with…** to choose a member of the workspace. 3. **Decide whether they can change it.** Tick **can edit** if they should be able to edit the report. Left unticked, they read it. The tag **read only** shows on their canvas. 4. **Press Share.** ::: Sharing with someone narrows the report to private in the same press. "Share with Dana" and "stop showing it to the other thirty people" are one intention, so the pane does not ask for it twice. You can only share with a member of the workspace. To share with someone who is not yet in it, invite them under **Settings · Team** first, or use a [public link](#public-links). Change a person's edit permission with the **can edit** box beside their name, and remove them with the bin icon. Someone with edit permission can change the report's blocks, filters and colours. They cannot delete it, share it or publish it. A member with the read-only role cannot edit a report even if **can edit** is ticked. ## Public links A public link is for the people a report is actually for: a board, a bank, a client. Whoever holds the link can read the report without signing in, and the link is the whole credential. :::caution Anyone with the link sees every row the report's blocks show, live, and it does not matter who forwarded it. A link cannot be limited to a person or a set of rows. If different people should see different rows, publish to the [reports portal](https://docs.sanda-os.com.au/reports/portal) and give readers data rules instead. ::: ### Create a link :::steps 1. **Open the pane.** Press **share** and scroll to **public links**. 2. **Say what it is for.** In the label box (for example "June board") type a name so you can tell links apart later. 3. **Choose an expiry.** **No expiry**, **7 days**, **30 days** or **90 days**. 4. **Press Create link.** The link appears once, in a highlighted box. 5. **Copy it now.** Press **copy**. sanda keeps only the link's last six characters, so a link you did not copy cannot be recovered. Make a new one instead. ::: A report can have up to 20 links that have not been revoked, expired ones included. Revoke one to make room. ### What the person with the link sees They open a page headed **Shared by** your workspace's name, with the report's name and description and a **Print** button. The report is drawn as a page that scales to their window and never bigger than life size. Every block shows its numbers as they are when the page opens, and a note at the foot says so and that the link can be turned off. A kpi that compares with the previous period shows its change, as it does on the canvas. If the link expires, the note gives the date. Because the page is not a copy, **anything you change on the report shows for people holding the link** the next time they open it. Tables work as they do for any reader: the person can sort a table, select a range of cells to see their sum and average, and download the table as **xlsx** or **csv**. They cannot change the report. Comments, the questions blocks were built from, and the SQL behind a figure are never sent to them. Each block's numbers are read with the report's own filters, from the block's saved question. The visitor sends nothing but which block, so a link cannot be used to ask your warehouse anything else. ### Manage and revoke links Each link in the pane shows its label (or "link ending" and its last six characters), a status tag, how many times it has been opened and when it was last opened, and its expiry. | Tag | Meaning | |---|---| | **live** | It opens the report | | **expired** | Its expiry date has passed | | **revoked** | You turned it off | Press the bin icon beside a live link to **revoke** it. It stops working at once, and it stays in the list marked **revoked** so you can see it existed. Someone opening a dead link is told so: "That link has been turned off by the workspace that shared it", or "That link has expired. Ask whoever shared it for a new one." Each open is counted once per visit, not once per block. In the Reports list, a **1 link** tag marks any report that a live public link can open, so you can see at a glance which reports have left the building. Creating, sharing and revoking are recorded in your workspace's audit trail. --- # Schedules and delivery > Email a report on a schedule with a PDF and a PNG attached, or post it to Slack or a webhook. Set recipients, send now, pause, and see how each run went. A delivery sends a report on a clock. At each run, sanda asks every block on the report again, so the numbers are current, and sends the answers as an email, a Slack message or a webhook. Nobody has to open sanda, and the recipients need no account. Owners and admins schedule, change, send, pause and remove deliveries. Other members can see the list and read each delivery's status. ## Schedule a report ![The Schedule a report panel: a report chooser, the Hourly, Daily and Weekly buttons, the days of the week, a time and timezone, the destination, recipients and a closing note.](https://docs.sanda-os.com.au/media/schedule-new.png "The panel for a new delivery") :::steps 1. **Open Report schedules.** Go to **Activate · Reports** and press **Report schedules**. To schedule one report, use the **Schedule** link on its row in the Reports list. 2. **Start a delivery.** Press **Schedule a report**. 3. **Choose the report.** A template cannot be delivered, so only real reports are listed. 4. **Choose when.** Under **Deliver** pick **Hourly**, **Daily** or **Weekly**. For **Hourly** choose **Every hour**, **Every 6 hours** or **Every 12 hours**. For **Weekly** tick the days. For **Daily** or **Weekly** set a **Time** and a **Timezone**. sanda shows the schedule in words and the **Next run** underneath. 5. **Choose where it goes.** Under **Send to** pick **Email**, **Slack** or **Webhook**. For email, type the addresses in **Recipients**, separated by commas or spaces. 6. **Add a closing note if you like.** The **Closing note** is the sentence the email ends on, up to 500 characters, sent exactly as you wrote it. 7. **Press Schedule delivery.** ::: ### The 15 minute grid Schedules run on a 15 minute grid, so a delivery can only start at :00, :15, :30 or :45. The time list offers only those, so a 9:07 start is not something you can pick. The reason is cost: sanda's scheduler wakes every 15 minutes rather than every minute, so it can rest in between. A daily or weekly delivery keeps the **local** time you chose. A 7:00 AM delivery in Australia/Sydney stays at 7:00 AM when the clocks change. The hourly options have no clock time to keep and run on the hour, UTC. ### Recipients - Up to 25 addresses per delivery. Duplicates are removed and addresses are stored in lower case. - An address that is not valid is dropped, and a delivery with none left is refused. - If your workspace limits who can receive reports, every address must be at an allowed domain. See [limit who can receive reports](#limit-who-can-receive-reports). Recipients do not need a sanda account, and they see the same numbers whoever they are. A delivery is not limited by any reader's [data rules](https://docs.sanda-os.com.au/reports/portal#limit-what-a-reader-sees): everyone on the list sees every row the report shows. An email cannot be recalled once it is sent, so check the list before you press **Schedule delivery**. ## What arrives An emailed report is the canvas, published. Blocks sit where you put them and are drawn as the canvas draws them, in the report's colours. - **Subject**: the report's name and the date, for example `weekly trading · 30 September 2026`. - **Header**: the report's name, your workspace, when the figures were read, and one line saying which [report filters](https://docs.sanda-os.com.au/reports/canvas#filter-the-whole-report) apply. - **Blocks**: kpis with their comparison and target, bars, lines (drawn as filled columns, because inboxes do not draw charts) and tables in the sheet's own formats. A table shows its first 10 rows. Written analysis appears as saved. - **Closing note**: yours, verbatim. - **A link** to the live report in the console, which opens for recipients who have a sanda account, and a line saying how often it is sent. - **A plain-text version** that says each block in words, for mail clients that prefer it. No AI writes the email. A scheduled report is a statement of what the data says, so a sentence a model composed about a figure would be a second thing to check. The only words that are not from your data or your team are the ones in the template. If one block cannot run, for example because a report filter's field does not reach its table, the delivery still goes and that block's section says it did not run. ### The PDF and the PNG An email also carries the report as two attachments: a **PDF** and a **PNG**, named after the report and the date. The PDF is a single page exactly as tall as the report, so nothing is cut through the middle of a chart, and the PNG is the same page as an image. They exist so a report can be forwarded, printed or dropped into a board pack exactly as it looked. The files are a courtesy on top of the email, never the delivery itself. If they cannot be produced, or would make the message too large, the email goes without them, and the PNG is left off before the PDF. ## Send now, pause, change and remove Select a delivery in the list to open its panel. It shows when it **runs**, its **next** run, who it goes **to**, and how the **last** run went. | Button | Does | |---|---| | **Save delivery** | Saves changes to the time, the recipients (or the Slack or webhook URL) and the closing note | | **Send now** | Sends the report to the delivery's recipients right now, outside its schedule. It goes out within a minute or two. | | **Pause** and **Resume** | Stops the schedule, or restarts it. A resumed delivery next goes at its next slot counted from now. A paused delivery can still be sent by hand. | | **Remove** | Deletes the delivery. The report stays, and the record of its past runs stays on the timeline. | The channel of a delivery cannot be changed. If a report should go somewhere else, make a second delivery. The list can be searched by report name and filtered to **Active** or **Paused** deliveries. ## Slack and webhooks Email is not the only place a report can go. Under **Send to**, choose **Slack** or **Webhook** and paste a URL in place of recipients. Because the URL is a credential, sanda stores it encrypted, never shows it again, and displays only the host and the last six characters so you can tell deliveries apart. To change it, paste a new one in the box marked **Replace the …**. | | Slack | Webhook | |---|---|---| | **URL** | A Slack incoming webhook, starting `https://hooks.slack.com/` | An `https://` address on a public hostname | | **What is posted** | A short summary: the report's name, when it is as at, any filters, each chart's or table's headline (with the top three of a ranking), your closing note and a link to the report. Written analysis is left out, and a long report is cut with a note of how many sections remain | The digest as JSON, written analysis included | A webhook URL must use the standard HTTPS port, must not carry a user name or password, and must be a public hostname rather than an IP address, `localhost` or a name that only resolves inside a private network. A delivery URL that breaks these rules is refused when you save it. The webhook receives one `POST` per run with a JSON body, and the run's id in an `x-becca-delivery` header so you can spot and drop a retry you already took: ```json title="Webhook body" { "type": "report.delivered", "version": 1, "report": { "id": "…", "name": "weekly trading", "description": "…", "url": "https://console.sanda-os.com.au/app/reports/…" }, "workspace": { "name": "Acme Services", "slug": "acme-services" }, "run": { "id": "…", "trigger": "schedule", "scheduled_for": "2026-09-28T21:00:00.000Z" }, "generated_at": "2026-09-28T21:00:04.000Z", "timezone": "Australia/Sydney", "cadence": "Every Mon at 07:00 Australia/Sydney", "filters": ["issued date in the last 7 days"], "note": "That is the week. Nothing here needs a reply.", "sections": [ { "id": "…", "kind": "kpi", "shape": "figure", "title": "net revenue", "lead": ["482,000 net revenue"], "rows": [] } ] } ``` Each section is one block in canvas order, already formatted in the metric's own units. `shape` says what the rows turned out to be: `figure`, `series`, `ranking`, `table`, `text` or `note`. There are no raw result sets in the payload. A receiver that wants the rows asks the semantic fluid for them, where the reader's own access applies. Any `2xx` answer counts as delivered. A redirect, or a `4xx` other than 408 and 429, is treated as a final refusal: sanda does not retry it and tells you to paste a new URL. A `5xx`, a 408, a 429, a timeout or a dropped connection is retried. sanda waits ten seconds for an answer. Slack and webhook deliveries are not covered by the recipient-domain limit, since they have no address to check. ## Limit who can receive reports An owner or admin can restrict which addresses a delivery may email. Go to **Settings · Security**, find **Report delivery** and enter **Recipient domains**, separated by commas or spaces, such as `acme.com.au`. Subdomains count, so `finance.acme.com.au` is inside `acme.com.au`. Leave it empty to allow any address. The limit is checked when a delivery is created or changed and again at each send, so a list set after a delivery was scheduled still holds: recipients outside it are left out of that run. A run with nobody left is refused. ## See how each run went - On the **Report schedules** page, each delivery shows its status: **live**, **paused**, or **failing** when its last run failed, with the reason. (The list beside it says **active**, **paused** or **failed**.) - **Monitor · Pipeline** draws every delivery as a bar on the report's row of the timeline, next to your syncs and tasks, coloured by outcome. Hover a bar to see whether it was sent, what triggered it (the schedule, or someone pressing **Send now**), when, how many recipients, the subject and any error. The delivery's panel there has **Manage in Reports**. ### When a delivery fails A failed run is recorded with its reason, and the delivery keeps its schedule, so the next slot still goes. Owners and admins are emailed about it once a day for each delivery, however many runs fail, with the reason and a link to the Pipeline page. Typical reasons are a report that was deleted, no recipient inside the allowed domains, a suspended workspace, or an email that could not be sent. That email follows the alert switch under **Settings · Plan & budget**, in **Budget & alerts**: **When a scheduled run fails, or pauses itself after failing repeatedly**. It is on by default. You can add extra addresses to **Also send to** there. ## Schedule with cherry cherry can schedule, change, send, pause and remove deliveries when you ask in plain words. It always asks you to confirm before it schedules, changes, sends or removes a delivery. See [Build reports with cherry](https://docs.sanda-os.com.au/reports/build-with-cherry#schedule-and-send-with-cherry). An AI assistant connected over [MCP](https://docs.sanda-os.com.au/mcp/tools) with the report-delivery permission can do the same. Only an owner or admin can grant that permission, because a delivery puts a copy of your numbers in someone's inbox. --- # The reports portal > A place for the people you build reports for. Publish to readers who sign in, limit each to their own rows, and let them ask cherry about the numbers. The console is where your team builds reports. Most of the people a report is for never build anything. A regional manager wants this month's numbers for their region, and a director wants the pack. Giving them the console would hand them a rail of pages they will never use and, worse, the whole warehouse behind the one report they came for. The **reports portal** is the other half. It is a separate site at `reports.sanda-os.com.au` that shows only the reports you publish to someone, lets them ask cherry about the numbers, and has nothing on it that can change anything. ![The portal's home page for a reader named Dana: a greeting, a box to ask cherry, three suggested questions and a starred report.](https://docs.sanda-os.com.au/media/portal-home.png "The portal home for a reader") ## Who can sign in | Role | What they do on the portal | |---|---| | **Reader** (Reader · reports portal only) | Signs in to the portal and nowhere else. Can be limited to their own rows with [data rules](#limit-what-a-reader-sees). | | Read-only, member, admin, owner | Can open the portal as well as the console. Data rules do not apply to them. Owners and admins see every published report. | A reader who opens the console is sent to the portal. Readers have a seat in your workspace, so invite them as you invite anyone: **Settings · Team**, then **Invite someone**, with the role **Reader · reports portal only**. See [members and roles](https://docs.sanda-os.com.au/workspace/members-and-roles). ## Set up the portal :::steps 1. **Invite your readers.** Do this under **Settings · Team**, as above. You can also change an existing member's role there. 2. **Publish a report.** Open a report, press **publish** in the toolbar and fill in the pane. See [Publish a report](#publish-a-report). 3. **Give readers data rules.** If readers should see only their own rows, set rules under **Activate · Reports**, on the **Portal** tab. See [Limit what a reader sees](#limit-what-a-reader-sees). 4. **Check it as they will.** In the portal, use the **Viewing as me** menu to see it as a reader does. See [Check a reader's view](#check-a-readers-view). ::: ## Look after the portal Owners and admins run the portal from **Activate · Reports**, on the **Portal** tab. **Open the portal** at the top right takes you to it. ![The Portal tab of the Reports page: The portal settings, Published reports and Readers and their data.](https://docs.sanda-os.com.au/media/portal-admin.png "The Portal tab") **The portal** panel sets how it greets people: | Setting | What it does | |---|---| | **Name** | What the portal calls itself. Empty uses your workspace's name. | | **A reader with no data rules sees** | **Every row the reports show**, or **Nothing, until their rules are set**. Choose nothing when every reader is meant to be limited, so nobody sees everything by accident before their rules exist. | | **Welcome** | A few lines under the greeting on the home page, up to 600 characters. | | **The portal is open** | Switched off, readers are told the portal is closed. Owners and admins can still open it to get it ready. | | **Readers can ask cherry** | Switched off, cherry disappears from the portal and the reports are all that is left. | Press **Save**. **Published reports** lists what is on the portal, with each report's group, audience, when it was published and how many people have read it. Use the arrows to put them in order. Featured reports come first, then this order. **View** opens a report on the portal. ## Publish a report Publishing puts a **copy** of a report on the portal, and it is a decision: the readers see the version you published, not the report you keep working on. A board pack can be reworked for a week without the board watching it happen. :::steps 1. **Open the report.** Go to the report's canvas. Only owners and admins see the **publish** button. 2. **Press publish.** The pane opens on the right. 3. **Fill it in.** Set the **Name on the portal**, a one-line **Description**, and a **Group** such as finance, operations or board. The portal's home groups reports under these. 4. **Choose who can open it.** **Everyone in the workspace** reaches every member and reader, each seeing only their own rows where they have data rules. **Only the people I choose** shows a list of members to tick. Owners and admins can always open it. 5. **Feature it if it matters.** **Feature it** puts the report first on the portal's home. 6. **Press Publish.** ::: A report with no blocks cannot be published. What is frozen at the moment you publish is the report's **layout**: its blocks, filters and colours. The numbers are not frozen. Readers see the current figures each time they open a report, read from your warehouse. Once a report is published the pane says **on the portal**, when it was published and how many people have read it. If you have changed the report since, it says so: **This report has changed since it was published. Readers still see the published version until you update it.** **Update to this version** sends the new layout. **Save and update** appears when the report has not changed, and saves changes to the portal details, such as its group or audience. **Unpublish** takes the report off, and nobody there can open it any more. **view on the portal** opens it. ## What a reader sees **The home page** opens with a greeting, your workspace's name, the reader's role and the date, and your welcome if you wrote one. When cherry is on, a large box for it sits in the middle with three suggested questions. Below it is **Your reports** as tiles, with a search box, **Find a report**. Each tile draws a miniature of the report's layout, with its name, description, group and when it was last opened. Starred reports come first, then everything, with a chip for each group when there is more than one. Featured reports lead. A reader with nothing published to them sees **Nothing has been published to you yet.** **A report** is the same page a [shared link](https://docs.sanda-os.com.au/reports/sharing#what-the-person-with-the-link-sees) shows, laid on paper that scales to the window. On the portal it fills the width, up to 175%, because a reader on a wide screen came to read the numbers. Beside the title are the reader's only actions: - **Star** a report to keep it near the top. - **Refresh** reads every figure again. - **Print** prints the page at the width. - **Ask cherry** opens a pane about this report, when cherry is on. A zoom bar at the foot of the window zooms out and in, shows the current zoom (press it to return to the width), and fits the whole report on screen. Tables can be sorted, selected and downloaded, and a reader gets only the rows their rules allow. The foot of the page says when the figures were read and when the report was published. ## Limit what a reader sees A **data rule** names a field and the values a reader may see. It is applied to every query the portal runs for that reader, whether it is a block on a report or an answer from cherry. :::steps 1. **Open the rules.** On the **Portal** tab, under **Readers and their data**, press **Data rules** beside the reader. 2. **Add a rule.** Press **Add a rule**. In **Field**, choose a dimension from your semantic fluid (the list offers them) or write a column in full as `schema.table.column`. 3. **List the values.** In **May see**, type the values separated by commas, such as `north, south`. 4. **Save.** Press **Save rules**. ::: A few facts about how rules work: - A reader must match **all** of their rules, and within one rule any of its values will do. A field can have only one rule, so put all its values in it. A reader can have up to 20 rules. - Rules are applied **after** the report's own filters, so nothing on the report can replace one. - If a block's table cannot reach a rule's field, the block is **not shown** to that reader. They see a message such as "This block cannot be shown with the data you have access to." rather than an unfiltered figure. - The reader is told what applies, once, above the report: for example **Showing your part of the data: region is north**. - **Rules are for readers only.** Anyone with the console has the SQL shell, the explorer and search, none of which can apply a per-person filter, so a rule on them would be a promise with a hole in it. The **Readers and their data** panel says so, and refuses a rule for anyone but a reader. Make someone a reader to limit what they see. :::caution Written analysis is text saved on the report. Every reader sees a text block as it was written, whatever their rules. If a report goes to readers who are limited to their own rows, keep whole-business figures out of its text blocks. ::: With **A reader with no data rules sees** set to **Nothing, until their rules are set**, a reader without rules sees the reports without their figures, and a message saying their access has not been set up. ### Check a reader's view When your workspace has more than one member, owners and admins get a **Viewing as me** menu in the portal's top bar. Choose **As** a person, and the portal draws exactly as that person sees it: their reports and their data rules. A note at the top says so, with **Back to my view**. Nothing you open while previewing is counted as theirs. Use it to check a rule before a reader finds it wrong. ## cherry on the portal If **Readers can ask cherry** is on, a reader can ask about the numbers in their own words, from the home page's box or from a report. The home page sends the question to cherry's own page, **Conversations**, which lists their earlier conversations on the left. Every conversation is kept and can be deleted by the person who had it. On a report, **Ask cherry** opens a pane that knows which report is open, with suggestions such as "Summarise this report in three lines" and "What changed since last month?". cherry here is a smaller cherry than the console's. It looks things up and runs queries against your semantic fluid, using the definitions your team agreed, and it cannot change anything. It cannot write its own SQL or search documents, and it does not show the SQL it ran, because for a reader that would carry their own rule. A reply streams in, with the rows behind it folded underneath. For a reader with rules, its answers use only the part of the data they may see. Questions asked on the portal count against your workspace's daily AI limit. See [models and usage](https://docs.sanda-os.com.au/cherry/models-and-usage). ## Sign in and the handoff The portal has no sign-in form of its own. **Sign in** on the portal goes to the console's sign-in, the same one your team uses, and brings the reader straight back to the page they wanted. You open the portal from the console with **Open the portal** on the **Portal** tab, and anyone with console access has **Open sanda** in the portal's account menu to go back. The two sites share a sign-in but not a session, so sanda passes you across with a code that works once and lasts two minutes. The code only works when you come from the console: opened from a link on another website, it signs nobody in and goes to the portal's home. If a sign-in takes too long the portal says so and asks the reader to sign in again. A person who has not been invited is asked to contact whoever sends them their reports. --- # Push > Write sanda's agreed numbers into the fields of your Salesforce records. Each run sends only the records whose values changed. Push writes sanda's numbers into fields on your Salesforce records: revenue over the last twelve months on each Account, the overdue balance beside it, the date of the last order. The numbers come from your [semantic fluid](https://docs.sanda-os.com.au/semantic-fluid), so they mean what your team agreed they mean. They land in ordinary Salesforce fields, so Salesforce reports, list views, flows and page layouts read them like any other field. Push is included on Standard and Enterprise, and during a trial. You find it in the console under **Activate · Push**, once it is switched on for your console. Until then the console doesn't list it: it isn't in the navigation, and **Settings · Plan & budget** doesn't name it among your edition's features. ## How it works ```text your semantic fluid: a number per record, such as revenue or overdue balance | read on each run, one row per Salesforce record v compared with what Salesforce last accepted from sanda | only the records whose values changed v the fields on those records in your Salesforce org ``` 1. **You connect a Salesforce org** by signing in to Salesforce as the user sanda writes as. See [connect Salesforce](https://docs.sanda-os.com.au/push/connect-salesforce). 2. **You define a push**: the Salesforce object, the field that finds each record, and the sanda value written into each field. You preview what it would send before anything goes. See [create a push](https://docs.sanda-os.com.au/push/create-a-push). 3. **It runs** after a connection syncs, on a schedule, or when you press **Run now**. Each run reads the values, formats them the way Salesforce stores them, and compares them with what Salesforce last accepted. Only the records whose values changed are sent, and a run where nothing changed makes no call to Salesforce at all. See [runs and refusals](https://docs.sanda-os.com.au/push/runs). This is not the Salesforce connection under **Data · Connections**, which reads your Salesforce data into sanda (see [Salesforce](https://docs.sanda-os.com.au/connectors/salesforce)). Push goes the other way, and it is set up separately. The two work well together: the Salesforce sync lands each record's Id in your warehouse, and a push uses it to find the record again. ## What counts as a change A record has changed when what sanda would send for it has changed: the exact values, formatted for their fields. A sync running, or a warehouse row being touched, is not a change on its own. - A number is rounded to the field's decimal places before it is compared, so 100.004 and 100.0049 sent to a two-decimal currency field are the same value. - An empty value clears the field. A metric with nothing to measure doesn't leave last month's number standing. - A Salesforce Id is compared in its 18-character form, so a record that arrives once with a 15-character Id and once with an 18-character Id is one record, sent once. sanda records a value as sent only when Salesforce says it accepted it. A run that stops part way leaves nothing half recorded: the next run sends whatever Salesforce did not confirm. ## Editions and limits | Edition | Pushes per workspace | |---|---| | Basic | Not included. **Push** shows what the next edition adds | | Standard | 1 | | Enterprise | no limit | | Trial | 1, as on Standard | One push reads at most 50,000 records and writes at most 100 fields. A push that reads more refuses to run and says so: narrow it with a condition, or split it into pushes by region or segment. If your workspace moves to an edition with fewer pushes, it keeps every push. Its oldest pushes keep running, up to the new edition's count, and the rest keep their definitions and wait. On Basic none of them runs. Moving back up is immediate. See [editions](https://docs.sanda-os.com.au/billing/editions). ## What Push never does - Delete a Salesforce record. - Create a record when it finds records by their Id. - Clear a field because a record has left the push. A record that no longer matches a push's conditions stops being sent to, and keeps the value it has. - Send a value Salesforce already has. - Spend the last 10% of your org's daily API allowance. See [your API allowance](https://docs.sanda-os.com.au/push/runs#your-salesforce-api-allowance). - Record a value as sent that Salesforce did not confirm. ## Who can do what | | Owners and admins | Members and read-only members | |---|---|---| | See pushes, their runs and what each run couldn't deliver | Yes | Yes | | Preview what a push would send | Yes | Yes | | Connect or disconnect a Salesforce org | Yes | No | | Create, change, pause, run or delete a push | Yes | No | A push writes into Salesforce on the whole workspace's behalf and spends your org's API allowance, so only owners and admins change one. A preview reads your warehouse and sends nothing, so anyone who can open the console can ask for one. :::links - [Connect Salesforce](https://docs.sanda-os.com.au/push/connect-salesforce): Sign in as the user sanda writes as. - [Create a push](https://docs.sanda-os.com.au/push/create-a-push): Choose the object, how records are found and what goes in each field. - [Runs and refusals](https://docs.sanda-os.com.au/push/runs): What each run did, and why a record was refused or held. ::: --- # Connect Salesforce > Connect the Salesforce org a push writes to, by signing in to Salesforce as the user sanda writes as. A push writes into a Salesforce org you connect once. You connect by signing in to Salesforce and allowing sanda. There is no app to create in your own org and nothing to copy. ## Before you start - **Choose who sanda writes as.** sanda can change exactly what the Salesforce user you sign in as can change, field by field. Use a Salesforce integration user (the API-only licence) rather than a person's own login, and give it edit access to only the fields your pushes write. Then a push never depends on one person staying in the job, and sanda's access is as narrow as you choose. - **Let that user allow sanda.** Your org hasn't installed sanda's app, and Salesforce only lets a user allow an app the org hasn't installed if the user holds the **Approve Uninstalled Connected Apps** permission. A Salesforce admin can give it to the integration user. - **Be an owner or admin in sanda.** Only owners and admins connect an org. ## Connect an org :::steps 1. **Open Push.** In the console, go to **Activate · Push**. 2. **Start connecting.** Press **Connect Salesforce**. For a Salesforce sandbox, press **Connect a sandbox** instead. 3. **Sign in to Salesforce.** Salesforce asks which user to sign in as even when this browser is already signed in to Salesforce, because the org open in another tab is often not the one you mean. Sign in as the user sanda should write as. 4. **Allow sanda.** Salesforce shows what sanda asks for: to manage data through the API, and to do so at any time. Press **Allow**. 5. **Check the org.** You land back on **Push** with a note naming the org and the user. The org is listed under **Salesforce orgs**, marked **connected**. ::: Connect each org once, however many pushes write to it. Connecting the same org again keeps its pushes and replaces the old connection. ## If connecting doesn't finish You land back on **Push** with a sentence saying what happened, and nothing is connected. | What happened | What to do | |---|---| | You pressed **Deny** on Salesforce's screen | Press **Connect Salesforce** again and choose **Allow**. | | sanda couldn't match Salesforce's answer to the tab | Connecting started in another tab or another workspace, or was left too long before it finished. Press **Connect Salesforce** again from this tab. | | Salesforce refused, or didn't finish signing sanda in | Try again. If it happens again, sanda support can see why. | | Only owners and admins can connect | Ask an owner or admin of the workspace. | ## What sanda keeps - **One token per org**, which lets sanda sign in to Salesforce for a run. It is stored encrypted, used only to start a run or read your objects and fields, and never shown in the console. - **The org's name and address, and the user you signed in as**, shown under **Salesforce orgs**. - **The org's API usage at its last reading**, shown beside the org, so you can see how much of the day's allowance is left. See [your API allowance](https://docs.sanda-os.com.au/push/runs#your-salesforce-api-allowance). ## Connect again Salesforce can stop accepting sanda's connection: an admin revoked it, a policy expired it, or the user sanda signs in as was deactivated. The org is then marked **connect again**, and its pushes are skipped, not failed, until you connect it again. The first run after catches up. The run that found it fails and says so, and owners and admins get the failed-run email if it is turned on (see [when runs fail](https://docs.sanda-os.com.au/push/runs#when-runs-fail)). It doesn't count toward a push pausing itself, because the cause is the org's connection rather than the push. To carry on, press **Connect again** beside the org and sign in as before. ## Disconnect :::steps 1. **Find the org.** Go to **Activate · Push**. The org is under **Salesforce orgs**. 2. **Disconnect.** Press **Disconnect** beside it, then **Disconnect** again to confirm. ::: sanda revokes its token at Salesforce and forgets it. The pushes that write to the org stay, marked **waiting**, and run again once you connect it. Nothing in Salesforce changes: the values pushes already wrote stay where they are. --- # Create a push > Choose the Salesforce object, how each record is found and the sanda value for every field, then preview what the first run would send. A push is four choices: the object it writes to, how it finds each record, which sanda value goes in each field, and when it runs. Saving sends nothing. The push opens with a preview of what its first run would write. ## Before you start - **A connected Salesforce org.** See [connect Salesforce](https://docs.sanda-os.com.au/push/connect-salesforce). - **The values on your semantic fluid.** A push sends metrics, dimensions and columns of the tables on your [semantic map](https://docs.sanda-os.com.au/semantic-fluid/the-map). - **A way to match rows to records.** Usually that is the Salesforce Id, which a Salesforce sync lands in your warehouse beside every record. See [Salesforce](https://docs.sanda-os.com.au/connectors/salesforce). ## Create the push :::steps 1. **Open the editor.** Go to **Activate · Push** and press **New push**. 2. **Name it.** Type a **Name**, such as "account revenue". The **Description** is optional. 3. **Choose where it writes.** Pick the **Salesforce org** and the **Object**. The list holds the objects the Salesforce user can edit, the most used first. 4. **Choose how each record is found.** Under **How each record is found**, pick the **Salesforce field**: the record Id, or a field marked External ID in Salesforce. Then pick the **sanda value that holds it**, a column or a dimension. When you match on the Id and your Salesforce connection landed an Id column for the object, sanda fills it in and marks it as a suggestion. 5. **Map the fields.** Under **What it writes**, pick a Salesforce field on the left and the sanda value for it on the right. Press **Add a field** for each one more. 6. **Narrow the rows, if you need to.** Under **Which rows**, press **add a condition**. Leave it empty to send a value for every record. 7. **Choose when it runs.** Under **When it runs**, pick a connection in **Run after a sync**, a schedule under **On a schedule**, or both. See [when it runs](#when-it-runs). 8. **Save.** Press **Save push**. Until everything the push needs is chosen, the button is unavailable and the line beside it says what is missing. The push opens with **Preview the next run** already read, and nothing has been sent. ::: Read the preview, then press **Run now** to send the first run. See [preview and run](#preview-and-run). ## Matching on the Id or an External ID | Match on | What a run does | Use it when | |---|---|---| | The record Id | Updates records that already exist, and never creates one. A row whose value is not an Id of that object is refused before anything is sent. | The numbers are about records that came from Salesforce, which is the usual case. | | An External ID field | Updates the record with that value, and creates a record when none has it. | The records belong to another system, and Salesforce keeps that system's key in an External ID field. | A push that can create records needs every field Salesforce requires to create one. The editor names any it is missing under **What it writes**. If every record already exists, you can leave them out, but a row with no matching record is then refused. The sanda value that finds a record is a column or a dimension, never a metric: a metric is worked out over many rows and can't say which one record a row is about. If two rows of a push match one record, neither is sent, because sending both would let whichever came last win. That happens when a column you send has more than one value for a record, such as an invoice date on an Account. Send a metric for it instead, such as the latest invoice date, so each record has one row. ## Which fields it can write The **Salesforce field** lists hold the fields the Salesforce user can edit. sanda writes text and picklist fields, checkboxes, numbers, currency and percent fields, dates, dates with times, times, and lookups (as an Id). Each value is formatted for its field before it is sent: - A number is rounded to the field's decimal places. The hint under the field says how many. - Text longer than the field holds is refused for that record, and the run says which. - A checkbox takes true or false. Yes and no, and 1 and 0, also work. - A date is sent as the day, and a date and time as an exact moment. - An empty value clears the field in Salesforce. Under **What it writes**, the fields sanda can't write are listed with the reason: formula fields, auto-numbered fields, fields the Salesforce user can't edit, address and location fields (map their parts instead, such as the street and the city), and fields that hold a file. One push writes at most 100 fields and reads at most 50,000 records. :::caution Check a percent field in the preview Salesforce stores 12.5 in a percent field to mean 12.5 per cent. A sanda metric that holds the fraction (0.125) is sent as it stands and shows as 0.125 per cent. Look at a percent field in the preview before the first run. ::: ## Which rows **Which rows** narrows the records a push sends values for, with the same conditions you use elsewhere in sanda. A named period such as `last_30_days` stays a period, so the window moves with each run. sanda works it out in UTC each time the push runs. See [filters and date ranges](https://docs.sanda-os.com.au/reference/filters). A record that stops matching a push's conditions keeps the value the push last wrote. The push stops sending to it, and does not clear the field. ## When it runs - **Run after a sync.** The push runs whenever the connection you pick lands new rows, which is when sanda's numbers can have changed. This is usually all a push needs. - **On a schedule.** **Hourly**, **Daily** or **Weekly**, on sanda's 15 minute grid. A daily or weekly time stays at the same local time when the clocks change. - **By hand only.** Choose **Manual** and leave **Run after a sync** empty. The push runs when someone presses **Run now**. - **Pause itself after.** How many failed runs in a row before the push pauses itself. 0 never pauses. See [when runs fail](https://docs.sanda-os.com.au/push/runs#when-runs-fail). ## Preview and run Open a push and press **Preview** under **Preview the next run**. sanda reads the push's rows from your warehouse, formats them for their fields and compares them with what Salesforce last accepted, without sending anything. You see: - how many rows were read, how many records a run would send, and how many are unchanged; - the first twenty records it would send, each marked **first send** or **changed**; - the rows that can't be sent as they stand, with the reason. Anyone who can open the console can preview, read-only members included. When it looks right, press **Run now**. ## Change a push Open the push and press **Edit**, change what you need, and press **Save changes**. - Renaming a push, or changing its conditions or when it runs, asks nothing of Salesforce. - Changing the org, the object or the field that finds a record starts the push over: the next run sends every record, because what the push remembers belongs to its old target. The editor says so before you save. - Changing the fields it writes means the next run sends the records whose values changed, which after a new field is usually every one. --- # Runs and refusals > What each run of a push did, why a record was refused or held, how Push treats your Salesforce API allowance, and what happens when runs fail. Open a push under **Activate · Push** to see its runs. **Runs** lists the last thirty, newest first. Each says in a sentence what it did, such as "Sent 31 records. Salesforce accepted every one.", with how it started, when, and how long it took. Open a run to see how many Salesforce API calls it used and the records it couldn't deliver. ## What a run's status means | Status | What it means | |---|---| | **succeeded** | Everything that changed arrived. A run that found nothing changed succeeds without calling Salesforce. | | **partial** | It sent, but Salesforce refused some records, or some rows couldn't be sent as they stood. | | **skipped** | It sent nothing, for a reason that is nobody's fault: the workspace is paused for its budget, the org needs connecting again, the push was paused after the run was queued, or your org's API allowance was too low. It doesn't count as a failure. | | **failed** | It couldn't finish. The reason is one sentence, and it counts toward the push pausing itself. | | **cancelled** | It was cut off before it finished. What Salesforce didn't confirm is sent on the next run. | | **queued**, **running**, **waiting** | Under way. **waiting** is a bulk job Salesforce is still working through. The page updates itself until the run ends. | ## How changes are sent Up to 2,000 changed records go in requests of up to 200, and each record's answer is recorded as it comes back. More than that go as one bulk job, which Salesforce works through in its own time while the run shows **waiting**. Either way, a value counts as sent only once Salesforce accepts it. ## Records it couldn't deliver There are two kinds, and an open run lists both with the record each was for. - **Rows sanda couldn't send as they stand.** A value that doesn't fit its field (text longer than the field holds, a word in a checkbox, a day that doesn't exist), a value that isn't an Id of the object, or several rows that match one record. These never reach Salesforce. - **Records Salesforce refused.** Salesforce's own message comes first, because it names your validation rule or field. sanda adds a hint for the common refusals, such as a field the Salesforce user can't edit or a record that has been deleted. A run keeps up to 200 of them, which is enough to see the pattern. Its counts say how many there were in all. The preview lists the first kind before anything is sent. See [preview and run](https://docs.sanda-os.com.au/push/create-a-push#preview-and-run). ## Held records A record Salesforce refuses 3 runs in a row, for the same value, is held: sanda stops sending it until its value changes. A validation rule that refuses a number refuses it on every run, and sending it again spends your API allowance to learn nothing new. A changed value is a new question, so it is sent. A refusal about the moment rather than the value, such as Salesforce saving the same record for someone else at that instant, doesn't count toward holding a record. It is sent again on the next run. ## Send everything again **Send everything again** sends every record the push reads, including the ones Salesforce already has, and releases held records. Use it when values in Salesforce were changed by hand and you want sanda's back, or once you have fixed whatever made Salesforce refuse a record. It costs about one API call for every 200 records. ## Your Salesforce API allowance Your org's daily API allowance is shared with every other integration and with your own users, so Push is careful with it. - **A run with nothing to send makes no call at all.** Not even to sign in. - **A run with something to send reads the allowance first.** If sending would leave less than 10% of it, and never less than 500 calls, the run sends nothing, says how many calls were left, and is skipped. The next run carries on. - **Most runs are cheap.** One call to read the allowance, then one for every 200 changed records. A bulk job costs a handful of calls whatever its size. Each org under **Salesforce orgs** on the Push page shows how much of the allowance was used at the last reading. ## When runs fail A failed run emails owners and admins, at most once a day for each push, when **When a scheduled run fails, or pauses itself after failing repeatedly** is ticked under **Settings · Plan & budget · Budget & alerts**. See [budgets and alerts](https://docs.sanda-os.com.au/billing/budgets-and-limits). After as many failed runs in a row as its **Pause itself after** says, a push pauses itself and is marked **suspended**. Fix the cause, then press **Resume**. A skipped run never counts, and neither does a failure caused by the org needing to be connected again. ## Pause, resume and delete - **Pause** stops the push's schedule and its runs after a sync. **Run now** still works. - **Resume** turns it back on, starts its schedule from now rather than catching up on what it missed, and forgets the failures that paused it. - **Delete push**, at the foot of the page, removes it. Nothing changes in Salesforce: the values it wrote stay where they are. ## If your edition changes A workspace keeps every push when it moves to an edition with fewer. The oldest keep running, up to the new edition's count. The others keep their definitions, show **not running**, and say why. On Basic, none runs and the page shows what the next edition adds. Moving back up is immediate. See [editions](https://docs.sanda-os.com.au/billing/editions). --- # Workspace settings > What each tab of Settings controls, who can change it, and where the detail lives. Settings is where you manage the workspace itself: its name, plan, people, sign-in rules and what happens when it closes. Open it from **System · Settings** in the console, or go straight to [Settings](https://console.sanda-os.com.au/app/settings). ![The row of tabs across the top of Settings.](https://docs.sanda-os.com.au/media/settings-tabs.png "The six tabs of Settings.") Settings is laid out as six tabs across the top of the page. The tab you are on is part of the address, so a copied link or a reload opens the same tab. Everyone who can open the console can open Settings. Owners and admins change most of what is on it, a few decisions belong to the owner alone, and a read-only person can change nothing in the workspace (they can still manage their own sign-in). The [permissions matrix](https://docs.sanda-os.com.au/workspace/members-and-roles) has the full list. ## The tabs | Tab | What it controls | Who can change it | |---|---|---| | **Workspace** | The workspace name and the default report colours. Its slug, plan and data region are shown here but not changed. Closing or deleting the workspace is at the foot of this tab | Owners and admins. Owners only, to close or delete | | **Plan & budget** | Your edition, what you have used, the monthly budget and the daily AI limit | Owners and admins | | **Team** | The people in the workspace and their roles | Owners and admins | | **cherry** | Which models cherry answers on, what it may change, and what it remembers | Owners and admins | | **Security** | Your own sign-in, your sessions, whether everyone needs two-step, and who scheduled reports may be emailed to | You, for your own sign-in. Owners, to require two-step. Owners and admins, for report delivery | | **Integrations** | Tokens and connected apps that reach the workspace from outside, and the tables an assistant may add rows to | Owners and admins | ## Workspace **Name** is what your team sees in the console and what appears on emails from sanda. **Slug** is the workspace's short name, shown for reference and never editable. You type it to confirm when you close or delete the workspace. **Plan** shows your edition, or **trial · every feature** while you are on the free trial. **Data region** is where your workspace's data is held: its warehouse, and the loading and modelling of what you sync. Under it, sanda says what runs in that region and what is sent outside it. The region was chosen when the workspace was created and the workspace stays in it, so there is nothing to change here. See [data region](https://docs.sanda-os.com.au/reference/glossary#data-region). Press **Save** after you rename the workspace. **Report colours**, under the workspace name, sets the default colours that every report canvas draws its charts and KPIs in. Change them here and every report follows. A single report can still override them on its own canvas. Leave them unset and reports use sanda's own colours. Press **Save colours** to apply a change. Some workspaces also show a read-only panel, **What you told us at signup**, with the company and role details entered when the workspace was created. ### Close or delete At the foot of the **Workspace** tab, owners can close the workspace, which makes it read-only, or delete it straight away. After a workspace closes, the tab reads **Workspace · closed** and this section offers **Reopen this workspace**. See [Close or delete a workspace](https://docs.sanda-os.com.au/workspace/close-or-delete). ## Plan & budget This tab has two panels. - **Plan** shows your edition, the platform fee, the storage and compute rates, a run rate at today's usage, and the limits that apply. On a trial it also shows your free credit as a meter. Between Basic and Standard, each edition has a **Move to** button that makes the change at once. Any other change has an **Ask to move to** button that sends sanda a request. See [Editions](https://docs.sanda-os.com.au/billing/editions) and [Free trial](https://docs.sanda-os.com.au/billing/trial). - **Budget & alerts** shows this month's spend so far, today's AI use against the daily limit, and the controls for a monthly budget, budget reminders and the daily AI limit. If a warehouse has been suspended for spend, **Resume warehouse** is here. See [Budgets and AI limits](https://docs.sanda-os.com.au/billing/budgets-and-limits). ## Team **Team** lists everyone in the workspace and any invitations still waiting, and is where owners and admins invite people, change roles and remove people. See [Members and roles](https://docs.sanda-os.com.au/workspace/members-and-roles). ## cherry Three panels: **cherry · models** (which models are offered in the composer and which is the default), **cherry · permissions** (what cherry may change, and whether it asks first) and **cherry · memory** (what cherry keeps between conversations). Owners and admins change models and permissions. See [Models and usage](https://docs.sanda-os.com.au/cherry/models-and-usage) and [Actions and approvals](https://docs.sanda-os.com.au/cherry/actions-and-approvals). ## Security - **Security** lists your connected sign-ins (Google or Apple, where you have linked one) and your two-step sign-in. See [Sign in](https://docs.sanda-os.com.au/workspace/sign-in) and [Two-step verification](https://docs.sanda-os.com.au/workspace/two-step). - **Your sessions** lists every browser you are signed in on, with **Sign out** for each and **Sign out everywhere else**. - **Report delivery** holds the list of domains that scheduled reports may be emailed to. Leave it empty and any address can be a recipient. Subdomains count, so `finance.acme.com.au` is inside `acme.com.au`. Slack and webhook deliveries have no address, so this list does not apply to them. See [Reports](https://docs.sanda-os.com.au/reports). ## Integrations The **Integrations** panel holds the MCP endpoint and the Excel feed address, and the tokens and connected apps that use them. Owners and admins create tokens. Any member can disconnect an app they connected themselves. The **Intake tables** panel that follows lists the tables an assistant is allowed to add rows to. See [Agent tokens](https://docs.sanda-os.com.au/mcp/agent-tokens) and [OData](https://docs.sanda-os.com.au/odata). :::links - [Members and roles](https://docs.sanda-os.com.au/workspace/members-and-roles): Invite people and decide what each can do. - [Security](https://docs.sanda-os.com.au/workspace/security): How sanda keeps workspaces apart and what staff can see. - [How billing works](https://docs.sanda-os.com.au/billing): What the Plan & budget tab is measuring. ::: --- # Members and roles > Invite people to your workspace, choose what each person can do, change a role, remove someone, or hand the workspace to another owner. Everyone in a workspace has one role. The role decides what they can open, what they can change and whether they get the console at all. Nobody is priced per seat, so adding people never changes your bill. ## Roles | Role | Who it suits | In short | |---|---|---| | **owner** | The person who created the workspace, or who was handed it | Everything, including closing or deleting the workspace | | **admin** | People who run the workspace day to day | Everything except the owner-only decisions below | | **read-only** | People who need answers but should change nothing | Opens every page and asks questions. Changes nothing | | **reader** | People who only read reports | Reads the reports you publish to the [reports portal](https://docs.sanda-os.com.au/reports/portal). No console | When you invite someone you choose **Admin**, **Read-only** or **Reader · reports portal only**. Owner is not offered on the invitation. Ownership moves by [handing the workspace over](#hand-the-workspace-to-an-admin). The account block at the foot of the left rail shows the signed-in person's role in the same words as the invitation, for example **Read-only**. ## What each role can do | | owner | admin | read-only | reader | |---|---|---|---|---| | Open every console page and see who is in the workspace | Yes | Yes | Yes | No | | Ask cherry, run questions and search | Yes | Yes | Yes | Portal only | | Open the reports portal and read the reports published to you | Yes | Yes | Yes | Yes | | Change things: connections, the semantic fluid, modelling, reports, schedules | Yes | Yes | No | No | | Provision or delete a warehouse, change warehouse limits, use the SQL shell | Yes | Yes | No | No | | Define, rebuild or remove a sanda search service | Yes | Yes | No | No | | Create integration tokens, and approve or decline what an agent asks to do | Yes | Yes | No | No | | Change the workspace name, report colours and report delivery domains | Yes | Yes | No | No | | Change the monthly budget or daily AI limit, resume a suspended warehouse, move between Basic and Standard or ask for another edition | Yes | Yes | No | No | | Change how cherry works | Yes | Yes | No | No | | Invite people, change roles, remove people other than an owner | Yes | Yes | No | No | | Switch sanda search on or off for the workspace | Yes | No | No | No | | Require two-step sign-in for everyone | Yes | No | No | No | | Hand the workspace to an admin | Yes | No | No | No | | Close, reopen or delete the workspace | Yes | No | No | No | | Set up two-step sign-in, sign out other sessions, accept the privacy policy, send a support message | Yes | Yes | Yes | Yes | Two things are worth knowing about the reader and read-only rows. - A **read-only** person asks questions through cherry, the results pane, search and MCP, and cannot commit changes through any of them. They can still disconnect an app they connected themselves. - A **reader** has no console. When they sign in they go straight to the reports portal, where they can ask cherry about the reports published to them unless you switch that off. Any data rules you set for them apply to everything they ask. An admin can do almost everything, but some decisions stay with the owner: closing, reopening, deleting or handing over the workspace, changing or removing another owner, switching sanda search on or off (it decides whether your text may leave Australia), and requiring two-step sign-in. The **Role** hint on the invitation lists the same things when you choose **Admin**. See [Privacy and your data](https://docs.sanda-os.com.au/workspace/privacy). ## Invite someone :::steps 1. **Open the Team panel.** Go to **Settings · Team** and press **Invite someone**. 2. **Fill in the invitation.** Enter their **Work email**. **Name** is optional, and they confirm it when they join. Choose a **Role**. The hint under the field says what that role means. 3. **Send it.** Press **Send invitation**. sanda confirms with "Invitation sent to" their address. The link works once and expires in 7 days. ::: ![The Team panel in Settings, with the Invite someone button above the list of people and their roles.](https://docs.sanda-os.com.au/media/settings-team.png "The Team panel, before anyone else has joined.") The invitation appears under **Invited, not yet joined**, with the days left. From there you can: - **Resend** to send a fresh link. The old link stops working and the 7 days start again. - **Revoke** to withdraw it. The link stops working at once. If the email could not be delivered, sanda tells you, and you can press **Resend** again in a moment. ### What the person sees The link opens a page headed "Join Acme Services on sanda." (with your workspace name). It says who invited them and as which role, and shows their email address, which they cannot change. They enter **Your name** and press the button named for the workspace, for example **Join Acme Services**. That signs them in. Readers land in the reports portal. Everyone else lands in the console. The first time they open the console they read and accept sanda's privacy policy, as every person does. See [Privacy and your data](https://docs.sanda-os.com.au/workspace/privacy). Next time they sign in with their email address and a one-time link. See [Sign in](https://docs.sanda-os.com.au/workspace/sign-in). ### Rules that always apply - **One address belongs to one workspace.** If the address already belongs to another sanda workspace, the person is told to ask for an invitation to a different address. - **You cannot invite an address that is already in your workspace,** or your own. - **A link works once.** After it is used, withdrawn or expired, the page says which, and the person asks you for a new one. - **A workspace that has been closed does not take new members.** ## Change a role Use the role menu on the person's row in **Settings · Team** and pick **Admin**, **Read-only** or **Reader · reports portal only**. The change applies from their next request. If you move someone out of admin, sanda also withdraws any invitations they sent that nobody has accepted yet. Integration tokens and connected apps they made keep working, but they lose the permissions only an owner or admin can hold, such as running or committing SQL. ## Remove someone Press the bin icon on their row, then **Yes, remove**. Removing someone: - ends their membership and signs them out of this workspace immediately; - withdraws invitations they sent that were not yet accepted; - stops any integration token or connected app they made from opening anything; - keeps what they built. Connections, reports and the record of what they did stay in the workspace. They can be invited again later, or to another workspace. Nobody can change their own role or remove themselves. An admin cannot change or remove an owner. The owner cannot be removed at all while they are the only owner. To step down, hand the workspace over first. ## Hand the workspace to an admin Only the owner can do this, and only to an admin. If the person you want is not an admin yet, change their role first. :::steps 1. **Find the admin's row.** In **Settings · Team**, press **Make owner** on their row. 2. **Confirm.** sanda asks, "Hand this workspace to" that person, and tells you that they become the owner and you become an admin. Press **Yes, make them owner**. Press **Keep it** to cancel. ::: It is a swap in one step. Both of you get an email saying so. Only the new owner can undo it. :::links - [Sign in](https://docs.sanda-os.com.au/workspace/sign-in): How people get in, and how sessions work. - [Two-step verification](https://docs.sanda-os.com.au/workspace/two-step): Ask everyone for a code from an authenticator app. - [Reports portal](https://docs.sanda-os.com.au/reports/portal): What a reader sees. ::: --- # Sign in > How you sign in to sanda with a one-time email link or Google or Apple, how long a session lasts, and how to sign out of every browser. There is no password to set or lose. You sign in with a one-time link sent to your work email, or with Google or Apple where the sign-in page offers them. If you have turned on [two-step verification](https://docs.sanda-os.com.au/workspace/two-step), sanda then asks for a code from your authenticator app. ## Sign in with an email link :::steps 1. **Ask for a link.** Go to the [sign-in page](https://console.sanda-os.com.au/signin), enter your **Work email** and press **Email me a sign-in link**. 2. **Open the email.** The link works once and expires in 30 minutes. 3. **Press the button.** The link opens a page headed "Finish signing in". Press **Continue to sanda** to go to your workspace. ::: You must press the button because the link on its own does not sign anyone in. Some email systems open every link in a message to scan it for threats. If opening the link signed you in, a scanner could use up your link before you did, or someone could send you a link that signs you in to their workspace. Nothing happens until a person presses the button. ### If the email does not arrive - Check your spam folder, and give it a minute. - Ask for another link. Each new link cancels every earlier link you have not used, so only the newest works. - Use the address your workspace knows you by. If that address has no sanda workspace, the page still says a link is on its way and none is sent. This is deliberate: it stops the form being used to find out who is a customer. - Do not ask for many links in a row. sanda limits how many it sends to one address in an hour, and the page looks the same when it holds one back. Wait and try once. - If a link has expired or been used, the sign-in page says so and does not send a new one by itself. If you asked for the link in the same browser, your address is already in the field, so pressing **Email me a sign-in link** sends a new one. Otherwise enter your address first. If your address has no workspace at all, [start a free trial](https://docs.sanda-os.com.au/get-started) instead. An invited teammate signs in for the first time from their invitation. See [Members and roles](https://docs.sanda-os.com.au/workspace/members-and-roles). ## Sign in with Google or Apple When your sign-in page offers **Continue with Google** or **Continue with Apple**, it shows them above the email field. If you do not see them, use an email link. - **The address must match.** The first time you use one, sanda links it to your existing account by matching the email address, and only if Google or Apple has confirmed that address is yours. From then on, both the provider and email links work. - **It does not create a workspace.** If the address has no sanda workspace, you are sent to sign-up with your email filled in. - **Two-step still applies.** A provider proves your email address. It does not prove your phone, so sanda still asks for your authenticator code if you have one. - **See what is linked.** **Settings · Security** shows the provider linked to your account under **Connected sign-ins**. If you see "That account is already connected to a different sanda sign-in", that Google or Apple account belongs to another sanda account. Use your email link instead, or write to sanda support. ## Sessions Signing in starts a session in your browser. A session ends after 14 days of not being used, or 30 days after you signed in, whichever comes first. Using sanda does not extend the 30 days. The session lives in a cookie that the browser sends only to the console, only over a secure connection, and that scripts on the page cannot read. sanda stores only a scrambled form of it. ### See where you are signed in **Settings · Security** has a **Your sessions** panel. It lists every browser you are signed in on, across every workspace you belong to, newest activity first. Each row shows the browser and platform, the first two parts of the network address (the only part sanda stores against a session), when you signed in and when the session was last active. Your current browser is marked **this device**, and sessions in another workspace are marked **another workspace**. - Press **Sign out** on a row to end that session. - Press **Sign out everywhere else** to end every session except the one you are using. It also cancels any two-step sign-in that is waiting for a code. Use these if you left a laptop signed in somewhere. An owner or admin who removes you from a workspace also ends your sessions in it straight away. ### Sign out Press the **Sign out** icon next to your name at the foot of the left rail. It ends the session in this browser and cancels any two-step sign-in that browser had open. ## If you cannot get in - Lost your phone or authenticator? See [Two-step verification](https://docs.sanda-os.com.au/workspace/two-step). - No email, or an address that no longer works? Ask your owner or admin to invite your new address, or write to sanda support at hello@sanda-os.com.au. :::links - [Two-step verification](https://docs.sanda-os.com.au/workspace/two-step): Add a code from an authenticator app. - [Security](https://docs.sanda-os.com.au/workspace/security): How sanda protects sign-in and sessions. - [Privacy and your data](https://docs.sanda-os.com.au/workspace/privacy): The policy you accept the first time you sign in. ::: --- # Two-step verification > Add a six-digit code from an authenticator app to your sign-in, keep recovery codes, and see how an owner can require it for everyone. Two-step verification asks for a six-digit code from an authenticator app after your email link or your Google or Apple sign-in. Someone who gets into your inbox still cannot get into your workspace. It is off until you turn it on, and an owner can [require it of everyone](#require-it-for-the-whole-workspace). Any app that reads a QR code and makes time-based codes works, including Google Authenticator, Microsoft Authenticator and 1Password. ## Turn it on :::steps 1. **Start the setup.** Go to **Settings · Security**, find **Two-step sign-in**, press **Set up**, then **Set up an authenticator app**. 2. **Add sanda to your app.** Scan the QR code. If you cannot scan, choose "enter a setup key" in your app and type the key shown, as a time-based code. A password manager can take the setup link instead. 3. **Enter the code.** Type the six-digit code your app now shows for sanda and press **Turn on two-step sign-in**. 4. **Keep your recovery codes.** sanda shows 10 recovery codes, once. Press **Copy codes** or **Download as text**, store them somewhere other than your phone, tick **I have saved my recovery codes**, and press **Done**. ::: Nothing switches on until the code in step 3 matches, so a setup you abandon halfway changes nothing. If the code does not match, check that the app is showing sanda and that your phone's clock is set automatically. ## Signing in with it After you use your email link or press **Continue with Google** or **Continue with Apple**, sanda shows "Enter the code from your app." Type the six digits and press **Continue to sanda**. - A code works once. Wait for the next one if you have just used it. - A code is accepted for up to thirty seconds either side of the moment it was made, so a phone whose clock is slightly out still works. - You have 10 minutes from the first step to finish, and five tries. After five wrong codes that sign-in is stopped and you start again from the beginning. sanda also limits how many codes one person can try in an hour, whichever sign-in they are on. ## Recovery codes If you lose your phone, press **Lost your phone? Use a recovery code** on the code page and enter one of your 10 codes. Each works once, in place of an app code. After you sign in this way sanda takes you to **Settings · Security**, where the **Two-step sign-in** row shows how many recovery codes you have left. sanda keeps only a scrambled copy of each code and cannot show it to you again. When you are nearly out, the panel tells you. To get a fresh set of 10, turn two-step off and set it up again. ## Change phones or turn it off Turn it off first, then set it up on the new phone. **Turn off** asks for a current code from your app or one unused recovery code. A session left open on a shared computer is not enough. Codes tried here count toward the same hourly limit as codes tried at sign-in. After too many, sanda says "Too many codes tried. Wait an hour, then try again." While two-step is on, you cannot start a second setup, so nobody can swap your authenticator without a code. :::steps 1. **Turn it off.** In **Settings · Security**, press **Turn off** on the **Two-step sign-in** row. 2. **Enter a code.** Use your app's current code or an unused recovery code, then press **Turn off two-step sign-in**. 3. **Set it up again.** Press **Set up** and follow the steps above with your new phone. ::: ## If you lose the phone and the recovery codes You cannot reset it yourself, because a reset would let anyone with your inbox switch it off. Write to sanda support at hello@sanda-os.com.au. sanda will need to confirm it is really you before it restores your access. ## Require it for the whole workspace An owner can require two-step sign-in for every person in the workspace. Admins cannot. :::steps 1. **Turn it on for yourself first.** The switch is not available until the owner has two-step on. 2. **Open the row.** In **Settings · Security**, find **Require two-step sign-in for everyone** and press **Require**. ::: The row then reads **required**. Press **Make optional** to lift the rule. What happens to people who have not set it up yet: - They can still sign in with their email link or Google or Apple. They are not locked out, because that is how they reach the setup. - The console then shows "Set up two-step sign-in." in place of every page, and refuses everything else until they finish. They can still send a support message and answer the privacy policy. - The same rule holds for the reports portal, for the MCP endpoint and for the screen where someone approves a connected app. Someone without two-step cannot use a side door around it. - Integration tokens and connected apps that already exist are machine credentials, so sanda does not ask them for a code. If a person turns two-step off in a workspace that requires it, they are asked to set it up again straight away. sanda records in your workspace's audit trail when two-step is turned on or off, when a recovery code is used, and when the requirement changes. :::links - [Sign in](https://docs.sanda-os.com.au/workspace/sign-in): The email link and Google or Apple. - [Members and roles](https://docs.sanda-os.com.au/workspace/members-and-roles): Who can require it. - [Security](https://docs.sanda-os.com.au/workspace/security): How sanda protects sign-in. ::: --- # Security > How sanda keeps workspaces apart, where data is held, who can reach it, how agents are controlled, and how to report a vulnerability. This page describes how sanda protects a workspace, in the same terms as sanda's [terms of service](https://sanda-os.com.au/terms) and [privacy policy](https://sanda-os.com.au/privacy). Where the two documents say something, they are the authority and this page follows them. ## Keeping workspaces apart Each workspace gets its own warehouse database and its own credentials, rather than a shared table with a tenant column beside every row. Every query sanda runs on your behalf is checked against the workspace it came from before it is allowed to run. Before sanda serves any workspace data, it checks that this separation is in force. If it cannot prove that, it refuses to serve the data at all, rather than risk answering with someone else's. Your warehouse is a standard PostgreSQL database, so what is inside it is yours to read, query and take with you. See [Close or delete a workspace](https://docs.sanda-os.com.au/workspace/close-or-delete) for how you get a full copy. ## Where your data is held Your warehouse, the data you sync into it, and sanda's own records about your workspace are held in Sydney, Australia. Some work happens outside Australia, and sanda says so plainly: - **AI features** send the part of your data a question needs to an AI model. Those models run outside Australia, mostly in the United States. Every model is called on a zero-data-retention basis: what is sent is used to answer the request and then discarded, and it is not used to train anything. - **sanda search** sends the text of the columns you choose, and the searches you run, to an embedding model in the United States. It is off until a workspace owner switches it on. - **Running the service.** Requests to the sanda website and console may be handled at a network location outside Australia. [Privacy and your data](https://docs.sanda-os.com.au/workspace/privacy) sets out exactly what is sent and when. ## Encryption and stored secrets - Data is encrypted in transit and at rest. - Sign-in links and session tokens are stored only in scrambled form. So are your two-step recovery codes, which sanda cannot show again. - The credentials you give sanda to connect your systems are held only by sanda's sync engine, never in sanda's own database. They are never shown back to anyone, including sanda staff. ## Signing in Sign-in is by a one-time email link, or Google or Apple where offered, with an optional second step. A link works once and expires in 30 minutes. Sessions end after 14 days unused or 30 days from sign-in, and you can see and end them yourself. An owner can require two-step verification of everyone. Each of these has its own page: - [Sign in](https://docs.sanda-os.com.au/workspace/sign-in) - [Two-step verification](https://docs.sanda-os.com.au/workspace/two-step) ## Roles Every person has one role: owner, admin, read-only or reader. The role decides what they can open and change. A read-only person changes nothing whatever else is switched on. A reader has no console at all. The matrix is on [Members and roles](https://docs.sanda-os.com.au/workspace/members-and-roles). ## Agent access controls An agent is anything that reaches your workspace from outside: an AI assistant connected over MCP, a script with a token, or Excel reading the feed. You decide what each one can do, and you can take it away at any time. See [Agent tokens](https://docs.sanda-os.com.au/mcp/agent-tokens) and [Permissions](https://docs.sanda-os.com.au/mcp/permissions) for the detail. - **Questions only by default.** A new integration token can ask questions of your semantic fluid and nothing more. Every further ability is a separate switch that starts off: running its own SQL, committing SQL, writing the fluid, seeing connections, starting a sync, loading rows, defining views, adding rows to intake tables, managing search and emailing reports. - **Only owners and admins can grant the sensitive ones.** Those abilities also stop working if the person who granted them stops being an owner or admin. - **Set an expiry.** When you create a token you can make it expire. - **Seen once.** A new token is shown a single time. sanda stores only a hash of it and cannot show it again. - **Revoke to stop.** Owners and admins see every token and connected app in **Settings · Integrations**, with when each was made and last used. Revoke one and its next call fails. Any member can disconnect an app they connected themselves. - **Leaving ends it.** A token or connected app stops working when the person who made it leaves the workspace. - **Ask before an agent acts.** On **Intelligence · Agents**, **Ask before an agent acts** makes risky actions wait for an owner or admin: committing SQL, dropping a derived view or removing a search service. The owners and admins are emailed, and an action nobody decides within 24 hours expires without running. It is off until you switch it on. See [Actions and approvals](https://docs.sanda-os.com.au/cherry/actions-and-approvals). - **Intake tables are opt-in.** An agent can add rows only to tables an owner or admin has opened for that purpose, listed under **Intake tables** in Settings. - **Report emails are restricted.** **Report delivery** in **Settings · Security** limits the domains scheduled reports can be emailed to. An agent's emailed report to an address outside your members' domains waits for an owner's approval when approvals are on. - **A closed workspace goes read-only for agents too.** Anything an agent holds narrows to questions and reading. - **Plan gates apply everywhere.** sanda search is refused on a workspace whose edition does not include it, over MCP and in cherry as well as in the console. See [Editions](https://docs.sanda-os.com.au/billing/editions). cherry, sanda's own agent, has its own switches under **Settings · cherry · permissions**: what it may change, and whether it asks first. ## What sanda staff can see The privacy policy says what sanda staff can and cannot reach: - Only the people who operate sanda's production systems can reach them, they sign in separately, and what they do is logged. - sanda's admin tools sit behind a separate sign-in. They cannot read your chat history or query your warehouse. - There is no feature that lets sanda staff sign in as you. - Connector credentials are never shown to anyone, staff included. What sanda does hold, so it can run and bill the service: sign-in events, sync outcomes, error logs, which features your workspace used, and a record that an AI request ran with which model and what it cost, but not what was asked. When you send a message under **System · Support**, it goes to sanda with your workspace's own record attached so support already has the context. sanda keeps an audit record of security-relevant actions (sign-ins, membership changes, SQL run in the shell). It is kept for the life of the workspace and deleted with it. ## Availability sanda does not offer an uptime guarantee unless one is written into a separate agreement with you. Maintenance that needs downtime is announced in advance where sanda can foresee it. ## If something goes wrong No system is perfectly secure, and sanda does not claim otherwise. If a breach is likely to cause you serious harm, sanda will notify you and the Office of the Australian Information Commissioner as the Notifiable Data Breaches scheme requires: promptly, and in any case as soon as practicable after assessing it. sanda will tell you what it knows. ## Report a vulnerability Email **security@sanda-os.com.au**. sanda also lists hello@sanda-os.com.au as a contact, and both are published at [sanda-os.com.au/.well-known/security.txt](https://sanda-os.com.au/.well-known/security.txt). - **Ask before you test.** The terms ask you not to probe the service for vulnerabilities without asking first. Write with what you plan to do and sanda will agree a scope. sanda always says yes to a genuine, coordinated test. - **Stay in your own workspace.** Do not try to reach another workspace's data or degrade the service for others. - **Good faith is safe.** Reporting something in good faith is never treated as a breach of the terms. :::links - [Privacy and your data](https://docs.sanda-os.com.au/workspace/privacy): What is collected, what reaches an AI model, and how long it is kept. - [Members and roles](https://docs.sanda-os.com.au/workspace/members-and-roles): Who can do what. - [Agent tokens](https://docs.sanda-os.com.au/mcp/agent-tokens): Create, limit and revoke a token. ::: --- # Privacy and your data > Which consent prompts you will meet, what sanda collects, what reaches an AI model, how long data is kept, and how to ask for your information. This page explains in plain terms what sanda's [privacy policy](https://sanda-os.com.au/privacy) says, and where in the product you meet it. The policy is the authority. If this page and the policy ever differ, the policy wins. sanda is run by Sanda Technologies Pty Ltd (ABN 63 702 995 015, ACN 702 995 015). The company is responsible for the personal information the policy describes, and it is the other party to the terms. Two facts sit under everything else. sanda does not sell your data and does not use it to train anything. And it is your data: what you connect, upload or generate stays yours, and sanda takes no licence to it beyond running the service for you. ## Consent prompts you will meet **At sign-up, you accept the terms and the privacy policy.** The sign-up form has an unticked box naming both, each linked. sanda will not create a workspace without it. sanda records which dated version of each you were shown, when you accepted, and from where. You can ask sanda for that record at any time. **Every person accepts the privacy policy in the console.** The first time anyone signs in, including a teammate who joined by invitation, the console shows the whole policy in place of every page, with its effective date and version. To go on, they tick **I have read and agree to sanda's privacy policy** and press **Agree and continue**. The acceptance is recorded with the version, the moment and the address it came from, and cannot be edited afterwards. When the policy changes, every person is asked again. If someone does not agree, they can sign out. sanda cannot be used without accepting. They can still send a support message. Integration tokens and connected apps are not asked. The person who made one meets the prompt the next time they sign in. **An owner decides about sanda search.** sanda search sends text from your tables to an embedding model outside Australia, so it is off for every new workspace and only a workspace owner can switch it on. The owner presses **Switch sanda search on** under **Intelligence · Vector search**, after reading what is sent. **Switch it off** later pauses every service and sends no further text. An admin cannot make this choice. **On the sanda website, you choose about analytics.** The website sets no advertising or tracking cookies. A banner asks you to **Allow** or **Decline** cookieless page-view counting, and nothing loads until you answer. If your browser already sends a Global Privacy Control or Do Not Track signal, sanda treats that as a decline and does not ask. You can change your answer at any time from the privacy policy page. ## What sanda collects The policy describes three kinds of information, kept apart. **What you tell sanda.** When you sign up: your name, email, company and, if you choose to give it, your role. sanda sends email only about your account and the product. **Your business data.** Whatever your connected sources contain, synced into a warehouse that belongs to your workspace and no other. sanda does not choose what is in it, does not mine it and does not use it to train anything. That includes your conversations with cherry. sanda keeps your chat history, with the questions, the answers and the rows of data behind each answer, so you can come back to it. It belongs to your workspace and is deleted when you delete the conversation or close the workspace. If your sources hold personal information about your own customers or staff, sanda handles it on your instructions. You stay responsible for it, and for having the right to connect it. **What using sanda produces.** Sign-in events, sync outcomes, error logs, and a record of which pages and features your workspace used, by whom, and whether each request worked. That includes the network address and browser details of each sign-in. For AI features, sanda records that a request ran, which model answered and what it cost, not what was asked. This is what lets sanda tell you why a sync failed, keep your account secure and bill by usage. ## AI and your data sanda uses AI models to answer questions and to help you build your model. To be useful, a model has to see enough. Depending on the feature, a request can contain: - your question and the recent messages in the conversation; - the names of your tables and columns, and the definitions your workspace has agreed; - a small sample of real values from your columns, so the model can tell what a column holds; - the rows a query returns; - your name, your role and your workspace's name, so the assistant knows who it is talking to. Columns whose names suggest secrets, such as passwords, tokens, tax file numbers and card numbers, are never sampled or returned. Other columns are not filtered by their content. If a column holds personal information, such as email addresses, a sample of it can be sent. Requests go through a routing service in the United States to the company that runs the model. sanda only sends requests to endpoints that do not keep what they receive and do not train on it. If no such endpoint exists for a model, the request is not sent. Models run outside Australia, mostly in the United States. sanda does not use personal information to make decisions about individuals. Its models write queries and draft reporting, and people decide what to do with the results. Automatic limits, such as pausing a workspace at a spending limit, apply to the whole workspace and not to any person. ### sanda search If an owner switches on sanda search, the text of the columns you choose to index is sent to an embedding model in the United States, along with the text of each search. Columns used only to filter stay in your warehouse. The model returns a list of numbers for each piece of text, and sanda stores those in your own warehouse in Sydney beside the rows they came from. A column whose name says it holds a secret is refused outright, and a column that looks like a personal detail is flagged to whoever sets the service up. Leaving sanda search off means the text in your tables is never indexed. It does not stop the other AI features from working as described above. ### Limiting AI use Every AI call sanda makes for your workspace counts against a daily limit that your owners and admins control. See [Budgets and AI limits](https://docs.sanda-os.com.au/billing/budgets-and-limits). ## How long sanda keeps it | What | How long | |---|---| | Your warehouse and everything in it | Until you delete it or close your workspace. A closed workspace stays readable for 90 days so you can export what you need, then it is permanently deleted | | Search indexes | With the search service that made them. Removing a service deletes its index | | Chat history | Until you delete the conversation or close your workspace | | Account details | While your account is open, then deleted with your workspace | | Activity and error logs | 30 days | | Security audit records | For the life of the workspace, then deleted with it | | Backups | 30 days, then overwritten | | Invoices, and the record that you accepted the terms and policy | Kept after your account closes because the law requires it. Invoices are kept for at least 5 years | | Enquiries and support messages | As long as needed to deal with what you asked. Ask and sanda will delete them | What closing does, step by step, is in [Close or delete a workspace](https://docs.sanda-os.com.au/workspace/close-or-delete). ## Your rights You can ask sanda for a copy of the personal information it holds about you, ask it to correct or delete that information, or export your workspace and take it elsewhere. Your warehouse is standard PostgreSQL, so an export is a database rather than a proprietary file. Write to **privacy@sanda-os.com.au**. sanda aims to reply within 5 working days, and always within 30 days. It will confirm it is you first, and there is no charge. If it cannot do what you ask, for example because the law requires a record to be kept, it tells you why in writing. Deleting your information can mean closing your account. If you are not satisfied, you can complain to the Office of the Australian Information Commissioner at [oaic.gov.au](https://www.oaic.gov.au). :::links - [Security](https://docs.sanda-os.com.au/workspace/security): Isolation, encryption and what staff can see. - [Close or delete a workspace](https://docs.sanda-os.com.au/workspace/close-or-delete): What happens to your data, and when. - [sanda search](https://docs.sanda-os.com.au/search): What indexing does and how to switch it on. ::: --- # Close or delete a workspace > Close a workspace to make it read-only for 90 days, reopen it, or delete it straight away. What happens to your data, and how to take a copy first. You have two ways to leave sanda, and only an owner can use either. **Close** makes the workspace read-only for 90 days, so you can take a copy of your data and change your mind. **Delete** destroys it straight away. Both live at the foot of **Settings · Workspace**. Everyone in the workspace can read that section, so nobody has to ask what happens to their data when the business leaves. Only the owner gets the buttons. Anyone else sees "Only an owner can close or delete the workspace." ## Take a copy first Your warehouse is a standard PostgreSQL database, so a copy is a database you can open anywhere. Ask before the 90 days are up and sanda will give you a PostgreSQL dump of the whole workspace. This is not a courtesy sanda can withdraw. It is written into the terms. Email [hello@sanda-os.com.au](mailto:hello@sanda-os.com.au) to ask. That section repeats the address once a workspace is closed. To see what you can export yourself, read [Export your data](https://docs.sanda-os.com.au/warehouse/export). :::caution If you delete the workspace, nothing can be exported afterwards. Take your copy first. ::: ## Close the workspace :::steps 1. **Open the section.** Go to **Settings · Workspace** and scroll to the foot of the tab. The heading reads **Close this workspace**. 2. **Say why, if you like.** **Why are you leaving?** is optional. A person at sanda reads it. 3. **Type the short name.** The field is labelled with your slug, for example "Type acme-services to confirm". Type that slug exactly, without worrying about capitals. It is also shown in **Settings · Workspace**. 4. **Press Close workspace.** The button stays off until the slug matches. ::: From that moment the workspace is read-only for everyone, the owner included. - Every page still opens, and every question can still be asked. - An export can still be taken. - Nothing can be changed. Integration tokens and connected apps narrow to reading too. - People can still send a support message. - A banner over every page says "This workspace is closed and read-only", with the date it is due to be destroyed. sanda is told when a workspace closes, with the reason you gave. ### What happens at the end When the 90 days are up, sanda destroys the warehouse and its contents. Backups that contain them are overwritten within a further 30 days. The section shows the exact date: "Everything can still be read and exported until" that day. Some records outlive the workspace, because the law or sanda's legal obligations require it: invoices, and the record that you accepted the terms and the privacy policy. They contain no business data. [Privacy and your data](https://docs.sanda-os.com.au/workspace/privacy) lists how long each kind of information is kept. ### Change your mind While the workspace is closed, the owner sees **This workspace is closed** and a **Reopen this workspace** button. Press it and everything comes back exactly as it was. You can do this at any point before the 90 days run out. ## Delete the workspace now Deleting skips the 90 days. It closes and destroys the workspace in one step. :::danger Deleting cannot be undone, and nothing can be exported afterwards. sanda destroys the warehouse and everything in it, the connections, the semantic fluid and the reports. ::: :::steps 1. **Open the section.** At the foot of **Settings · Workspace**, find **Or delete it now**. On a closed workspace it reads **Delete it now**. 2. **Type the short name.** This field is labelled "Type" and your slug, then "to delete". It is separate from the close form, so typing in one never arms the other. 3. **Press Delete workspace now.** ::: What happens: - Every member loses access. - Anyone whose only workspace this was loses their sanda account too, so their email address is free to sign up again. - You are billed for anything used since your last invoice. Nothing is billed on a trial. - The console then takes you to sign-up if your address has no workspace left, or to sign-in if it belongs to another. If a deletion cannot finish, sanda stops before destroying anything and says so. If part of it did complete, the workspace is left closed and read-only and you are asked to email sanda so it can finish the job. ## Start again The console has no reset. To start with a clean workspace, delete this one and sign up again with the same address. ## Cancelling and billing The terms let you cancel at any time from the console. You are billed for what you used up to the day you leave, and nothing after it. See [How billing works](https://docs.sanda-os.com.au/billing). :::links - [Privacy and your data](https://docs.sanda-os.com.au/workspace/privacy): How long each kind of information is kept. - [Export your data](https://docs.sanda-os.com.au/warehouse/export): Take your warehouse with you. - [Invoices](https://docs.sanda-os.com.au/billing/invoices): What your final invoice contains. ::: --- # How billing works > A flat monthly platform fee plus what you use, in Australian dollars, billed monthly in arrears. What is measured, what is free, and how a part month is handled. You pay a flat platform fee for each workspace every month, plus what you use: storage, compute, managed sync runs and AI. Everything is in AUD, billed monthly in arrears and sent to you by email. There is no card to enter and no charge per person. ## What makes up a bill | Charge | What it covers | How it is measured | |---|---|---| | **Platform fee** | The product: connections, the semantic fluid, reports, cherry and the console | A flat amount per workspace per month, set by your edition: A$20 on Basic, A$50 on Standard and A$200 on Enterprise | | **Storage** | Data held in your warehouses | Gigabyte-months held across the month, at A$1.06 per GB-month | | **Compute** | The time your warehouse is awake and the work it does | Compute hours, at A$0.483 per compute hour | | **Managed sync runs** | Each successful run of a managed sync | Per run | | **AI** | Every AI call sanda makes for your workspace | By the token, inside a daily limit you control | Basic and Standard are metered at the same storage and compute rates, so moving between them changes the fee, the room and the features, never the price of a gigabyte or an hour. See [Usage and metering](https://docs.sanda-os.com.au/billing/usage) for how each meter works and [Editions](https://docs.sanda-os.com.au/billing/editions) for what each fee buys. ## What you do not pay for - **People.** Every connector, report, schedule and seat is included in the platform fee. Nothing is priced per user, so adding a teammate never changes your bill. - **Faults and billing questions.** sanda answers these free on every edition. How-to help with your own setup is included on Enterprise. On Basic and Standard it is billed by the hour at A$150. - **The sample dataset.** sanda's learning dataset is free and is not metered. - **A trial.** A trial pays nothing. Its usage draws down free credit instead. See [Free trial](https://docs.sanda-os.com.au/billing/trial). ## Limits are not prices Your edition sets the most a warehouse can reach, and you can set each warehouse lower to cap a month. At a limit sanda pauses loading or slows queries. It never bills past one, and reads and reports keep working. Limits are described in [Limits](https://docs.sanda-os.com.au/reference/limits). A monthly budget is a different thing. It is a ceiling you set on your own spend, and reaching it suspends the warehouse until an owner or admin resumes it. See [Budgets and AI limits](https://docs.sanda-os.com.au/billing/budgets-and-limits). ## When and how you are billed sanda bills monthly in arrears, so an invoice normally covers the calendar month that has just ended. Invoices arrive by email to your workspace's owners and admins, and you pay by bank transfer. sanda does not take card payments. See [Invoices](https://docs.sanda-os.com.au/billing/invoices) for the lines on an invoice and how GST appears. - **A part month is pro-rated by days.** When an invoice covers less than a calendar month, you pay the platform fee only for the days it covers, and the invoice line says how many. - **A whole month bills the whole fee.** A calendar month bills the fee whatever its length, so February and March cost the same. - **An agreed fee stands.** If sanda has agreed a different fee with you, **Settings · Plan & budget** shows it beside the words "as agreed with you", or says the platform fee is waived. - **Prices can change.** sanda can change prices with 30 days' notice. The [pricing page](https://sanda-os.com.au/pricing) always shows the current rates. - **You can cancel at any time.** You are billed for what you used up to the day you leave, and nothing after it. See [Close or delete a workspace](https://docs.sanda-os.com.au/workspace/close-or-delete). ## Watch it as it happens You do not have to wait for an invoice to see a number. The console shows every charge as it builds up: - the AI ring in the top bar shows today's AI spend against your daily limit; - **Overview** and **Data · Warehouse** show storage and compute, and each warehouse shows a run rate at today's usage; - **Settings · Plan & budget** shows this month's usage so far, priced exactly as the invoice will price it. ## The trial Every workspace starts on a free trial of 7 days with A$10 of free credit and every feature. No card is needed. The workspace freezes when the days are over or the credit runs out, whichever comes first. See [Free trial](https://docs.sanda-os.com.au/billing/trial) for what stops and how to carry on. :::links - [Editions](https://docs.sanda-os.com.au/billing/editions): Basic, Standard and Enterprise side by side. - [Usage and metering](https://docs.sanda-os.com.au/billing/usage): The rate card, and how each meter is measured. - [Budgets and AI limits](https://docs.sanda-os.com.au/billing/budgets-and-limits): Cap your spend and your daily AI use. - [Invoices](https://docs.sanda-os.com.au/billing/invoices): What you receive each month. ::: --- # Editions > Basic, Standard and Enterprise compared, what each adds, how a feature your edition lacks is refused, and how to move between Basic and Standard or ask for another edition. There are three editions. Basic and Standard run on the same platform and meter storage and compute at the same rates. Enterprise runs on a plane of its own, with its own rates. What changes as you move up is the platform fee, how much room you have, and which features are included. | | Basic | Standard | Enterprise | | --- | --- | --- | --- | | Platform fee | A$20 a month | A$50 a month | A$200 a month | | Warehouses | 1 | Up to 3 | Up to 50 | | Storage per warehouse | Up to 10 GB | Up to 100 GB | Up to 16 TB | | Storage | A$1.06 per GB-month | A$1.06 per GB-month | A$1.06 per GB-month | | Compute | A$0.483 per compute hour | A$0.483 per compute hour | A$0.483 per compute hour | | Concurrent connections per warehouse | 60 | 60 | 300 | | Longest query | 60 seconds | 60 seconds | 5 minutes | | MCP and agents | Included | Included | Included | | sanda search | Not included | Up to 5 services, 2,000,000 rows per table | Included, no limit on services or table size | | Push to Salesforce | Not included | One push | Included, no limit on pushes | | How-to support | A$150 an hour | A$150 an hour | Included | | Faults and billing questions | Free | Free | Free | Prices are in Australian dollars. The [pricing page](https://sanda-os.com.au/pricing) always shows the current rates. ## What every edition includes The core product is the same on all three, and a trial includes it too: - the warehouse and its connections, with every connector; - the explorer, modelling and its schedules; - the semantic fluid; - reports and their deliveries; - cherry, sanda's own agent; - MCP and agents: the MCP endpoint, the assistants you connect to it, and the **Agents** page that shows what they do, with every action recorded and approvable (see [MCP](https://docs.sanda-os.com.au/mcp)); - the OData feed, which Excel, Power BI and Power Query read; - compute that scales to zero when idle; - no charge per seat, report, schedule or connector; - free answers to faults and billing questions. ## What the higher editions add - **sanda search (Standard and Enterprise).** Vector and full-text search over the text in your own tables. Standard holds a set number of services and a set table size, shown above. Enterprise has no limit on either. See [sanda search](https://docs.sanda-os.com.au/search). - **Push to Salesforce (Standard and Enterprise).** sanda's numbers written into fields on your Salesforce records, sending only what changed. Standard holds 1, and Enterprise has no limit. The console lists Push, in the navigation and under **Settings · Plan & budget**, once it is switched on for your console. See [Push](https://docs.sanda-os.com.au/push). - **Faster analytics and high availability (Enterprise).** Enterprise warehouses run analytical queries up to 10 times faster, using an in-memory columnar engine over the tables you already have, with no change to your SQL or your reports. They carry a 99.99% uptime SLA that includes maintenance windows, read replicas that keep reports and dashboards fast while data loads, and continuous backup with point-in-time recovery. - **Support included (Enterprise).** How-to questions and hands-on help with your own setup, at no extra charge. On Basic and Standard, that help is billed by the hour at A$150. Faults and billing questions are free on every edition. - **More room.** More warehouses and more storage in each as you go up, and on Enterprise more concurrent queries and longer queries. The table shows each figure and [Limits](https://docs.sanda-os.com.au/reference/limits) explains what happens at one. ## When your edition lacks a feature sanda refuses the request where the work happens, not only where the button is. A feature your edition does not include is unavailable however you reach it: - **In the console,** **Vector search** and **Push** stay in the left navigation, tagged with the edition that includes them. Their pages read "This isn't included in your edition", say which editions have the feature, and offer **Choose your edition** and **Compare editions**. The controls are not drawn, because none of them would work. - **Over MCP,** the search tools are refused on an edition without sanda search, with the same sentence. - **In cherry,** the search tools are not offered, and cherry's context carries no search services. - **In the background,** sanda does not queue or run search builds, and does not run pushes. The refusal names the editions that include the feature and tells you where an owner or admin changes edition: **Settings · Plan & budget**. If you move to a lower edition, you keep what you built. Search services stay where they are and stop answering, so moving back up is immediate. Pushes stay too: the oldest keep running, up to the new edition's count, and the rest wait until you move back or remove one. On Basic none runs. See [Push](https://docs.sanda-os.com.au/push#editions-and-limits). The new edition's limits apply to every warehouse as soon as the move is made, and any limit you set on a warehouse is capped at the new edition's numbers. ## Move between Basic and Standard Basic and Standard share a platform and every metered rate, so an owner or admin moves between them straight from the console. :::steps 1. **Open the Plan panel.** Go to **Settings · Plan & budget** and find **Editions**. Your current edition is marked **your edition**. 2. **Choose the move.** Press **Move to Standard** or **Move to Basic**. Only owners and admins can. Anyone else sees who to ask. 3. **Check what changes.** sanda lists the new platform fee and when it is billed, the feature you gain or stop, and the room the new edition gives. 4. **Confirm.** Press **Move to Standard** or **Move to Basic** again. The change is made at once, and the console updates without a reload. ::: How the month is billed: - **Moving up** to Standard bills Standard's fee, A$50, for the whole month you moved in. - **Moving down** to Basic bills Basic's fee, A$20, from the next month. The month you moved in is still billed as Standard. A move to Basic is refused while the workspace holds more than Basic does: more than 1 warehouse, or a warehouse holding more than 10 GB. The message names what to delete or trim first. Search services and pushes do not block the move. Search services keep their indexes and stop answering, and pushes keep their definitions and stop running, until you move back. ## Ask for another edition Some changes are an arrangement with sanda rather than a setting, so a person at sanda makes them and the console is how you ask: - leaving a trial for a paid edition; - moving to or from Enterprise; - any move when your workspace's platform fee was agreed with sanda, so the agreement can move with it. :::steps 1. **Open the Plan panel.** Go to **Settings · Plan & budget** and find **Editions**. On a trial no edition is marked **your edition**. 2. **Ask for the move.** Press **Ask to move to** the edition you want, for example **Ask to move to Enterprise**. Only owners and admins can. Anyone else sees who to ask. 3. **Follow it.** sanda answers "Asked", and the request appears under **System · Support** as a message titled "Plan change: move to Enterprise" (or whichever edition you asked for), with its status. The change shows in Settings as soon as it is made. ::: On a trial, the request moves you to a paid edition and lifts a credit freeze. See [Free trial](https://docs.sanda-os.com.au/billing/trial). To see what a month could cost before you ask, use the estimator on the [pricing page](https://sanda-os.com.au/pricing). It chooses the cheapest edition that can hold your data. :::links - [Usage and metering](https://docs.sanda-os.com.au/billing/usage): The metered rates every edition shares. - [Limits](https://docs.sanda-os.com.au/reference/limits): Every limit, per edition, and what happens at each. - [Members and roles](https://docs.sanda-os.com.au/workspace/members-and-roles): Who can change edition, or ask to. ::: --- # Usage and metering > The rate card, how sanda measures storage, compute, sync runs and AI, where to watch each in the console, and how to estimate a month. sanda bills on readings it takes itself, not on what you say you will use. A quiet month costs less than a busy one, and you can watch every meter build up in the console before an invoice exists. ## The rate card | Meter | Measured | Price | | --- | --- | --- | | Platform fee | A month, per workspace | Basic A$20 · Standard A$50 · Enterprise A$200 | | Storage | Per GB-month held, measured across the month | A$1.06 | | Compute | Per compute hour your warehouse is awake | A$0.483 | | Managed sync | Per sync run | Metered per run | | AI | By the token, inside your daily AI limit | Metered by the token | Prices are in Australian dollars. The [pricing page](https://sanda-os.com.au/pricing) always shows the current rates. Basic and Standard meter storage and compute at the same rates. Enterprise runs on a plane of its own and has its own storage and compute rates, which the pricing page shows. See [Editions](https://docs.sanda-os.com.au/billing/editions). ## Storage Storage is billed in **GB-months**: how much your warehouses hold, added up across the month. sanda does not bill a month's storage at a single reading. Each reading counts for the time until the next one, so holding more data for part of a month counts for that part of the month and no more. - **What counts.** Everything your warehouse holds: the tables you sync or upload, the tables and views you build, and sanda search indexes. An index is storage like any other. - **How it is measured.** sanda reads each warehouse's size regularly in the background, and again whenever anyone opens the **Overview** or **Data · Warehouse** pages. An owner or admin can also press **Measure now** on a warehouse. - **What does not count.** The sample learning dataset is free and is not metered. At a warehouse's storage limit, loading pauses. Nothing is billed past the limit. See [Limits](https://docs.sanda-os.com.au/reference/limits). ## Compute Compute is billed in **compute hours**. One compute hour is one vCPU running for an hour. A warehouse scales to zero when nothing is using it, so you pay for the time it is awake and the work it does, and nothing while it sleeps. - **A question wakes the warehouse for a minute.** Five questions inside that minute count as one minute. A busy workspace asking small questions all day pays for the minutes the warehouse stayed awake, not for the milliseconds each query ran. - **An awake minute counts at the smallest size,** half a vCPU. The work your queries do above that is added on top. - **Everything that uses the warehouse counts.** That covers questions from people and from cherry, report blocks, refreshes of the Excel or Power BI feed, scheduled runs, rebuilds of views and procedures, and sanda search indexing. Each warehouse has a compute limit in hours a day. You are billed the hours used. At the limit, sanda narrows queries rather than stopping them, and pauses loading until use is back under. The hours that still run are billed, so the limit slows spend rather than capping it. You can set a warehouse's limit lower than your edition allows to hold a month down. See [Limits](https://docs.sanda-os.com.au/reference/limits). ## Managed sync runs Each successful run of a managed sync is one run on your invoice. A run that fails is not counted. The **Managed sync runs** line on an invoice shows how many ran and the price per run. ## AI AI is billed by the token, at the rate for the model that answered. Your invoice has one line for each model used, with the number of millions of tokens and the price per million. Which model answers is chosen in cherry. See [Models and usage](https://docs.sanda-os.com.au/cherry/models-and-usage). Every AI call sanda makes for your workspace counts and is billed, whoever asked for it: - questions from your team, in cherry and elsewhere; - sanda's own work on your data, such as learning your schema and suggesting definitions; - drafts, and sanda search indexing. Syncs and scheduled reports are not AI and are not counted here. A daily limit caps AI spend, and your owners and admins set it. See [Budgets and AI limits](https://docs.sanda-os.com.au/billing/budgets-and-limits). ## Where to watch usage | Where | What you see | |---|---| | The top bar | The AI ring shows today's AI spend against your daily limit. Open it for the month so far | | **Overview** | **Account usage and spend**: thirty days of storage, compute and estimated cost across every warehouse | | **Data · Warehouse** | Each warehouse's storage and compute against its limits, thirty days of daily usage, and a run rate at today's usage | | **Settings · Plan & budget** | The run rate on the **Plan** panel. **This month so far** and **AI today** on **Budget & alerts**, priced exactly as the invoice will price them | **This month so far** is the same arithmetic the invoice uses for usage, so the usage you watch during the month is the usage you are billed for at the end of it. It leaves out the flat platform fee. On a trial, the **Free trial credit** meter shows the credit drawn down. See [Free trial](https://docs.sanda-os.com.au/billing/trial). ## Estimate a month The **run rate** is today's readings carried across a month. It covers storage and compute only, so add the platform fee. It is a guide, not a forecast and not a bill. Before you have a warehouse, use the estimator on the [pricing page](https://sanda-os.com.au/pricing). Enter how much data you have, or how many rows, and how many people will use it. It shows the platform fee, storage and compute for one warehouse. Team size affects only how long the warehouse stays awake, since seats are never a line on the bill. Managed sync, AI and sanda search are extra, and depend on what you do with them. To work it out yourself, add up: 1. the platform fee for your edition; 2. GB-months held, times the storage rate; 3. compute hours, times the compute rate; 4. managed sync runs; 5. AI, which depends on the models you use. :::links - [How billing works](https://docs.sanda-os.com.au/billing): The whole picture. - [Budgets and AI limits](https://docs.sanda-os.com.au/billing/budgets-and-limits): Put a ceiling on spend. - [Invoices](https://docs.sanda-os.com.au/billing/invoices): How these meters appear as lines. ::: --- # Free trial > A free trial comes with credit and every feature and needs no card. What freezes when the credit runs out, and how to carry on. Every new workspace starts on a free trial. It runs for 7 days, comes with A$10 of free credit, includes every feature and needs no card. Nothing renews into a paid subscription on its own, and there is one trial per organisation. ## What a trial includes - **Every feature.** MCP and agents and sanda search are on, as well as the core product, so you can try all of it. Limits are Standard's, except that search allows fewer services and smaller tables than Standard does. The console names the limit when you reach it. - **The same daily AI limit** as any workspace. - **No fees.** A trial pays no platform fee, and nothing it uses is billed. ## How the credit works The credit is what your trial would have cost. Four things draw it down, priced at the published rates: - compute, by the hour; - storage, by the GB-month held; - AI, by the token; - managed sync runs, by the run. Nothing is billed. The credit is a running total of the same arithmetic an invoice would use. ### Watch it - The **top bar** shows a pill that starts with "Trial" and gives the credit left and the days left. It changes colour when the credit is nearly gone or the days are few, and again when the credit is used up ("Trial · credit used up") or the trial has ended ("Trial · ended"). Press it to go to **Settings · Plan & budget**. - The **Plan** panel there shows the **Free trial credit** meter, the date it has been counting from, and how much each of Compute, Storage, AI and Sync runs has used. ### The reminder at 80% When the trial has used 80% of its credit, sanda emails your owners and admins, and anyone you have added under **Also send to** in **Budget & alerts**. Nothing changes yet. The email shows what the credit has gone on and how to choose an edition. It is sent once for each credit figure. If you have switched off **Send alert emails for this workspace**, the reminder is not sent, but the meter and the pill still show the balance. ## When the credit runs out or the trial ends The workspace is **frozen** when either of two things happens first: - **The credit is used up.** sanda checks every trial about every quarter of an hour, and checks again before each AI call, so a trial can run a little past its credit before the freeze lands. - **The trial ends.** At the end of its 7 days, the workspace freezes the same way, with credit left or not. AI stops at the end date itself, and the rest stops at the next check, a quarter of an hour later at most. It is the same freeze either way. Only the words say which it was. ### What stops - **Nothing can use the warehouse.** Queries, reports, syncs and scheduled runs wait. That includes questions in cherry that need the warehouse, the SQL shell, CSV uploads, the Excel and Power BI feed, agents over MCP, view rebuilds and search indexing. - **sanda makes no AI calls** for the workspace at all. ### What is kept Your data, your connections, your semantic fluid and your reports are all kept. Nothing is deleted because a trial froze. You can still sign in, open Settings and ask for an edition. The console tells you at every step: - a banner over every page: "Your free trial credit is used up, so this workspace is frozen", or "Your free trial ended on" and the date, followed by the same words; - a matching note on each warehouse card; - **Frozen since** and a **Choose an edition** button in **Budget & alerts**. Owners and admins also get an email when the freeze happens, unless alert emails are switched off. It says whether the credit ran out or the trial ended. ## How the freeze lifts - **Move to an edition.** An owner or admin presses **Ask to move to** an edition in **Settings · Plan & budget**, and a person at sanda makes the change. This lifts the freeze whatever froze it. See [Editions](https://docs.sanda-os.com.au/billing/editions). - **More credit or more time.** sanda support can give a trial more credit, or move its end date later. The freeze lifts only when both hold: more credit than has been used, and an end date still ahead. More credit alone does not lift a trial that has ended. The workspace then carries on where it stopped. **Resume does not lift it.** The **Resume warehouse** button in **Budget & alerts** is for the monthly budget. It refuses a frozen trial, because the next check would freeze the workspace again a few minutes later. If you had set a monthly budget and this month's spend is already over it when the freeze lifts, the workspace stays suspended under the budget instead. Raise or remove the budget, then press **Resume warehouse**. See [Budgets and AI limits](https://docs.sanda-os.com.au/billing/budgets-and-limits). ## The days The console counts the days down beside the credit. When the count reaches zero the trial has ended and the workspace freezes, even with credit left. If you plan to carry on, ask for an edition before then. No reminder email is sent before the end date, so watch the pill. :::links - [How billing works](https://docs.sanda-os.com.au/billing): The fee and meters you move to after a trial. - [Editions](https://docs.sanda-os.com.au/billing/editions): What to choose, and how to ask. - [Get started](https://docs.sanda-os.com.au/get-started): Start a trial. ::: --- # Budgets and AI limits > Set a daily AI limit and a monthly budget, choose when sanda emails you, and see what a budget suspension stops and how to resume. You have three ways to keep spend in check, and they work differently. | | What it caps | What happens at the limit | |---|---|---| | **Daily AI limit** | AI spend in a day | AI waits until the limit resets. Everything else keeps running | | **Monthly budget** | Metered spend in a month | Every warehouse in the workspace is suspended until an owner or admin resumes it | | **Warehouse limits** | One warehouse's storage and compute | Loading pauses and queries are narrowed. Nothing is suspended | The first two are set in **Settings · Plan & budget**, in the **Budget & alerts** panel. Owners and admins change them. Everyone else can see the meters. Warehouse limits are set per warehouse and covered in [Limits](https://docs.sanda-os.com.au/reference/limits). ## The daily AI limit Every AI call sanda makes for your workspace counts against the limit: cherry, the passes that learn your data, suggestions, drafts and sanda search indexing. Syncs and scheduled reports do not. The limit is measured in the same dollars as your invoice, so the meter matches the bill. - **The default is A$5 a day.** A new workspace starts there. - **Owners and admins can set their own, up to A$50 a day.** Every dollar under the limit is billed to you, so capping it is your call. - **For more, ask sanda support.** The ceiling above is the most a workspace can set for itself. sanda support can set a higher limit on your account. - **Lowering is always allowed.** A limit sanda support set above the ceiling can come down, and it survives saving other settings. ### Change it :::steps 1. **Open the panel.** Go to **Settings · Plan & budget** and find **Budget & alerts**. 2. **Enter a limit.** In **Daily AI limit (AUD)**, type an amount. Leave it empty to use the default. 3. **Save.** Press **Save budget & alerts**. ::: ### At the limit When today's limit is used up, sanda makes no more AI calls until the limit resets at midnight UTC, which is mid-morning in Sydney. Syncs, reports and your data are unaffected. The message says so, and it says an owner or admin can raise the limit. The AI ring in the top bar and the **AI today** meter show how much of the day is used. If you set the limit to zero, sanda makes no AI calls for the workspace at all. By default sanda emails your owners and admins the first time the limit is reached on a given day. Tick or clear **When the day's AI limit is reached** to change that. ## The monthly budget A monthly budget is a ceiling you set on your own metered spend: storage, compute, sync runs and AI, priced as the invoice will price them. It does not include the platform fee. Type an amount in **Monthly budget (AUD)**. Leave it empty for no budget: the meter then only reports, and nothing suspends. **This month so far** shows the running total against your budget as a percentage. ### Reminders Below 100%, a budget only sends emails. Under **Remind me at**, pick 50%, 80% or 90%. The default is 80%. sanda sends one email per threshold per month. If you cross more than one between checks, you get a single email for the highest. Owners and admins always receive alerts. **Also send to** adds up to ten more addresses, one per line, such as a finance inbox. Three more switches decide what else sanda emails about: - when a warehouse passes 80% of a limit, and when it reaches one and loading pauses; - when the day's AI limit is reached; - when a scheduled run fails, or pauses itself after failing repeatedly. **Send alert emails for this workspace** turns all of these off at once, and with them the email at 100% that says the warehouse was suspended and the email saying an agent's action is waiting for approval. The suspension itself still happens, and the banner over every page still says so. **Recent alerts** at the foot of the panel lists what has been sent, and any that could not be delivered, with the reason. ## When the budget is reached At 100% of the budget, sanda suspends every warehouse in the workspace. sanda checks the month's spend about every quarter of an hour, so a workspace can pass its budget a little before the suspension lands. **What stops.** Nothing can be queried, loaded, rebuilt or indexed, by anybody or anything: queries, reports, chat, syncs and scheduled runs, and also the feed, agents over MCP, the SQL shell and CSV uploads. **What stays.** Your data is kept. Its storage keeps being metered, because it is still held. **What you see.** A banner over every page reads "Your warehouse is suspended: this month's metered spend reached the budget", with the date. Each warehouse card says so, and **Budget & alerts** shows **Warehouse suspended since** that date. Your owners and admins get an email at 100%, unless alert emails are switched off. **It never lifts on its own.** Not at midnight, not at the start of a new month, and not when you raise the budget. A person has to resume it. ### Resume a suspended warehouse :::steps 1. **Make room under the budget.** Either raise or remove the budget, or wait until a new month has begun so the month's spend is back under it. 2. **Open the panel.** Go to **Settings · Plan & budget** and find **Budget & alerts**. 3. **Press Resume warehouse.** Only an owner or admin can. sanda confirms with "Resumed. Queries, syncs and scheduled runs are back." ::: If the month is still at or over the budget, the button is off and the panel says why: raise or remove the budget first, or the warehouse would be suspended again at the next check. :::caution A trial that is frozen because its free credit ran out is a different case. **Resume warehouse** does not lift it. Moving to an edition does. See [Free trial](https://docs.sanda-os.com.au/billing/trial). ::: ## Warehouse limits are separate A warehouse's own storage and compute limits are set under **Data · Warehouse**, with **Limits**. They throttle one warehouse and pause its loading, and never suspend it. Setting them lower than your edition allows is another way to cap a month. See [Limits](https://docs.sanda-os.com.au/reference/limits). :::links - [Usage and metering](https://docs.sanda-os.com.au/billing/usage): What the month's total is made of. - [Limits](https://docs.sanda-os.com.au/reference/limits): Every limit, and what happens at it. - [Models and usage](https://docs.sanda-os.com.au/cherry/models-and-usage): How cherry spends the daily AI limit. ::: --- # Invoices > When sanda invoices you, what each line means, how GST appears on a tax invoice, where to find your invoices, and how to pay. sanda invoices monthly in arrears, in AUD. Each invoice covers a period that has just ended, normally a calendar month, and lists the platform fee and each meter as its own line. ## Where to find your invoices sanda emails each invoice to your workspace's owners and admins, and the email is the invoice. It carries every line and the total, so keep it for your records. The console does not list invoices. If a bookkeeper or a finance inbox should receive them too, ask sanda support to add the address. Invoices are paid by bank transfer. sanda does not take card payments, so it never holds card details. The bank details and payment terms are printed at the foot of each invoice, and the invoice has a **Reference** to quote when you pay. sanda keeps invoices for at least five years, as Australian tax law requires. A trial pays nothing, so there is nothing to pay. See [Free trial](https://docs.sanda-os.com.au/billing/trial). ## What is on an invoice An invoice shows who it is from, with sanda's ABN, and who it is for. It shows the period, the date it was issued and the reference, then the lines, then the total. | Line | What it is | Unit | |---|---|---| | **sanda platform** · your edition | The flat platform fee. A part month says how many days it covers | Month, or day | | **Warehouse storage** | The data your warehouses held, integrated over the period | GB-month | | **Warehouse compute** · your edition | Compute, metered by the hour | Compute hour | | **Managed sync runs** | Each successful run | Run | | **AI** | One line for each model used, with its name | Million tokens | Lines you may also see: - **Lines sanda adds by hand,** such as how-to support hours on Basic and Standard, billed at A$150 an hour, or a discount. - **An "included" AI line.** On invoices for periods before sanda began billing its own work on your data, that work appears at no charge, marked "included". - **A line at zero.** A meter with nothing on it in the period can still appear, at zero. The platform fee comes first, so you meet the flat part before the metered part. For how each meter is measured, see [Usage and metering](https://docs.sanda-os.com.au/billing/usage). The usage lines are priced with the same arithmetic as **This month so far** under **Settings · Plan & budget**, so nothing on an invoice should be a surprise. For the platform fee and part months, see [How billing works](https://docs.sanda-os.com.au/billing). ## Tax invoices and GST An invoice from sanda is one of two documents, and its heading says which. - **Tax invoice.** When sanda is registered for GST at the time it issues the invoice, the document says **Tax invoice**, shows sanda's ABN, shows the subtotal, and shows GST as its own line with its rate. The total is labelled as including GST. The GST line is printed even when it is nothing. - **Invoice.** When sanda is not registered for GST, the document says **Invoice**, has no GST line, and says in words that no GST has been charged and that sanda is not registered. It still carries sanda's ABN. The kind is fixed when the invoice is issued. A document keeps the heading it was sent with, whatever sanda does afterwards. Read the heading on your own invoice to see which applies. ## If an invoice looks wrong Ask before you pay. Questions about billing are free on every edition. Write to sanda support at hello@sanda-os.com.au with the invoice reference. The terms say charges more than 14 days overdue suspend a workspace, and that suspension is not deletion. If you dispute a charge, say so early. :::links - [How billing works](https://docs.sanda-os.com.au/billing): The fee, the meters and the rules for a part month. - [Usage and metering](https://docs.sanda-os.com.au/billing/usage): How each line is measured. - [Budgets and AI limits](https://docs.sanda-os.com.au/billing/budgets-and-limits): Cap spend before it reaches an invoice. ::: --- # Getting help > How to reach sanda's team from the console or by email, what support covers on each edition, and how to report a security issue. Most questions have a faster answer here than in a ticket. Try [troubleshooting](https://docs.sanda-os.com.au/help/troubleshooting) for a message or a symptom, and the [FAQ](https://docs.sanda-os.com.au/help/faq) for a question about data, privacy, the trial or closing a workspace. When you need a person, this page says how to reach one. ## Send a message from the console Go to **System · Support**, fill in a **Subject** and **What’s going on**, and press **Send**. The message reaches the people who answer them, along with your workspace's own record, so they already have the context. There is nothing to choose: the message is about the workspace you are signed in to. sanda usually replies within a business day. Your message appears under **Your tickets** with a status of open, in progress or closed. Everyone in the workspace can see the tickets sent from it. Every role can send a message, including read-only members, and a closed workspace can too. The edition you are on decides what a reply costs, never whether you get one. A useful message saves a round trip. Include: - What you were trying to do, and what happened instead. - The exact wording of any message on screen. - Where you were in the console, for example **Data · Connections**, and the name of the connection, report or table involved. - When it happened. ## What support covers | Kind of question | Basic and Standard | Enterprise | |---|---|---| | **Faults**: something is wrong with sanda rather than with how it is being used | Free | Free | | **Billing questions** | Free | Free | | **How-to questions and hands-on help** with your own setup | Billed at A$150 an hour | Included | A trial has every feature, support included, for as long as it lasts. The Support page states which applies to your workspace before you write. ## Contact by email Use email when you cannot sign in, or for anything that is not about a workspace. | Address | Use it for | |---|---| | **hello@sanda-os.com.au** | General questions, a sign-in problem, or a request that needs a person, such as a full copy of your data. | | **privacy@sanda-os.com.au** | Privacy requests: a copy of the personal information sanda holds about you, a correction, or deletion. | | **security@sanda-os.com.au** | Reporting a security issue. | For a privacy request, sanda aims to reply within 5 working days and always within 30 days, which is the limit the law sets. It will need to confirm it is you before it acts, and there is no charge. See the [privacy policy](https://sanda-os.com.au/privacy). ## Report a security issue Write to **security@sanda-os.com.au**. That address is separate from the general one. You can also write to hello@sanda-os.com.au, and the same details are published at [sanda-os.com.au/.well-known/security.txt](https://sanda-os.com.au/.well-known/security.txt). The terms of service ask you not to probe sanda for vulnerabilities without asking first. So tell sanda what you want to test, and sanda will agree a scope. sanda always says yes to a genuine, coordinated test, and a report made in good faith is never treated as a breach of the terms. ## Related pages :::links - [Troubleshooting](https://docs.sanda-os.com.au/help/troubleshooting): Fixes for the common problems, each linked to the detailed page. - [FAQ](https://docs.sanda-os.com.au/help/faq): Where your data is held, what happens when the trial ends, how to close a workspace. - [Editions](https://docs.sanda-os.com.au/billing/editions): What each edition includes. ::: --- # Troubleshooting > Fixes for the most common problems, including sign-in emails, failed syncs, a suspended or frozen workspace, AI limits, MCP and report deliveries. Each entry starts with what you see, then the cause, then the fix, and links to the page that covers the topic in full. If you are looking at an exact message in the console, search this page for its words. ## The sign-in email does not arrive The sign-in page says "Check your inbox" whether or not the address has a workspace. That is deliberate, so nobody can use the form to find out who is a customer. It also means the page cannot tell you that no email was sent. Check these in order: 1. **Look in spam or junk.** The email comes from `noreply@sanda-os.com.au` and its subject is "Your sanda sign-in link". If your company filters mail, ask your IT team to allow that address. 2. **Check the address.** Only an address that belongs to a workspace receives a link, and an address belongs to one workspace. Use the one you signed up with, or the one your owner invited. 3. **Wait a minute, then ask for another.** A new link cancels the earlier ones, so use the newest email. 4. **Count your requests.** sanda sends at most five links per address in an hour and withholds any more without saying so. Wait for the hour to pass. 5. **Try Google.** If the sign-in page offers **Continue with Google** and your work address is a Google account, use it. If none of that works, email [hello@sanda-os.com.au](mailto:hello@sanda-os.com.au). See [sign-in](https://docs.sanda-os.com.au/workspace/sign-in). ## The sign-in link has expired or does not work You see "That link has expired or was already used." A sign-in link works once and expires after 30 minutes. Only the newest link works, because each new request cancels the ones before it. - **Ask for a new link** from the sign-in page and open the email that arrives last. The page shows the message above the email field. If you asked for the old link in the same browser, your address is already filled in: press **Email me a sign-in link**. - **Press Continue to sanda.** Opening the link only takes you to a page with that button. Pressing it signs you in, which is why an email scanner that visits the link does not use it up. - **"That link was incomplete."** The link is missing part of its address. Copy the whole address, or press the button in the email instead. If you use two-step sign-in, you may see "That sign-in waited too long for its code" or "Too many wrong codes, so that sign-in was stopped". Start again from the sign-in page. If you no longer have your phone, choose **Lost your phone? Use a recovery code**. Each of the 10 recovery codes you saved works once. See [two-step sign-in](https://docs.sanda-os.com.au/workspace/two-step). ## A sync fails A connection shows an error badge, or its first sync never lands rows. 1. Open the connection from **Data · Connections**. The **Status** tab shows the reason under the badge. 2. If there is no reason, press **Check status** to ask again, then open the **Timeline** tab to read the run. 3. Fix the cause, then press **Sync now**. The usual causes are credentials that no longer work, a source behind a firewall that has not allowed sanda's sync address, a connection that is paused, and a warehouse that has reached a storage or compute limit. At a limit, loading pauses until the warehouse is back under it or the limit is raised. A suspended or frozen workspace pauses syncs too. See [connection troubleshooting](https://docs.sanda-os.com.au/connections/troubleshooting), [monitor syncs](https://docs.sanda-os.com.au/connections/monitor-syncs) and [the allowlist](https://docs.sanda-os.com.au/connections/allowlist). ## The warehouse is suspended for budget A banner over every page reads "Your warehouse is suspended" and says this month's metered spend reached the budget. Queries, reports, chat, syncs and scheduled runs are paused. Your data is kept. An owner or admin set a monthly budget under **Settings · Plan & budget**, in **Budget & alerts**, and this month's spend has reached it. An owner or admin can fix it: 1. Open **Settings · Plan & budget** and raise or remove the budget, or wait for the month to turn. 2. Press **Resume warehouse**. The button stays disabled while the month's spend is at or over the budget, because the warehouse would be suspended again at the next check. A warehouse's own storage and compute limits are different: they slow queries or pause loading, and never suspend anything. See [usage and suspension](https://docs.sanda-os.com.au/warehouse/usage-and-suspension) and [budgets and limits](https://docs.sanda-os.com.au/billing/budgets-and-limits). ## The daily AI limit is reached cherry says today's AI limit is used up and that it resets at midnight UTC, or that an owner or admin can raise it. The ring in the top bar is full. Syncs, reports and your data are unaffected. Every AI call sanda makes for your workspace counts against the limit: cherry's answers, learning passes, suggestions, drafts and search indexing. The default is A$5 a day. - **Wait for the reset.** The day ends at midnight UTC, which is 10:00 am in Sydney during standard time and 11:00 am during daylight saving. The top bar's usage card shows the reset in your local time. - **Raise the limit.** An owner or admin can set it under **Settings · Plan & budget**, in **Budget & alerts**, up to A$50. If the limit is set to zero, sanda makes no AI calls at all until it is raised. Owners and admins can also be emailed when the limit is reached, using the **When the day’s AI limit is reached** option in the same panel. See [models and usage](https://docs.sanda-os.com.au/cherry/models-and-usage) and [budgets and limits](https://docs.sanda-os.com.au/billing/budgets-and-limits). ## The trial is frozen A banner reads "Your free trial credit is used up, so this workspace is frozen", or "Your free trial ended on" a date, "so this workspace is frozen". The top bar's trial pill reads "credit used up" or "ended". Queries, reports, AI, syncs and scheduled runs are paused. Your data is kept. Either compute, storage, AI and sync runs have drawn the credit down to nothing, or the trial's 7 days are over. **Resume warehouse** cannot lift this freeze, because it is not a budget suspension. An owner or admin can lift it by moving the workspace to an edition: 1. Open **Settings · Plan & budget** and compare the editions. 2. Press **Ask to move to** the edition you want, for example **Ask to move to Standard**. Your request goes to sanda's team as a ticket under **System · Support**. When the change is made, the workspace carries on where it stopped. Owners and admins are emailed when 80% of the credit has been used, and again when the workspace freezes for either reason, unless alert emails are switched off. See [the trial](https://docs.sanda-os.com.au/billing/trial) and [editions](https://docs.sanda-os.com.au/billing/editions). ## An AI assistant cannot connect over MCP Work through these, most likely first: 1. **The token.** "That token is not valid. It may have been revoked, or expired." An owner or admin creates a new one under **Settings · Integrations**. sanda shows a token once, so copy it in full at that moment. A token also stops working when the person who created it leaves the workspace. 2. **The endpoint and header.** Copy the MCP endpoint from **Settings · Integrations** rather than typing it. It takes POST requests only, and the token goes in an `Authorization: Bearer` header. 3. **Permissions.** A tool call that fails with "not available to this token" needs a permission that is switched off. Optional permissions start off. An owner or admin turns them on for a token in **Settings · Integrations**. For a connected app such as Claude, disconnect it and connect again, then tick the permissions on the consent screen. Some permissions can only be granted by an owner or admin. 4. **The workspace.** A suspended or frozen workspace pauses queries, so assistants are refused too. See the entries above. 5. **An approval is waiting.** If an owner or admin has turned on **Ask before an agent acts**, risky actions wait on the **Agents** page until someone approves them, and expire after 24 hours. See [connect Claude](https://docs.sanda-os.com.au/mcp/connect-claude), [agent tokens](https://docs.sanda-os.com.au/mcp/agent-tokens) and [permissions](https://docs.sanda-os.com.au/mcp/permissions). ## A report delivery does not arrive Open **Activate · Reports**, press **Report schedules**, and choose the delivery. Then check: 1. **Its status.** The pill reads **live**, **failing** or **paused**, and the **last** line says when it was last sent or what went wrong. Press **Resume** if it is paused. A suspended or frozen workspace pauses deliveries. 2. **Send it now.** Press **Send now** to test it without waiting for the schedule. 3. **The recipients.** A delivery with nobody to go to cannot send. If an owner or admin has limited recipients to certain domains, an address outside them is left out, and the message says "This workspace only delivers reports to addresses at" those domains. Change the list under **Settings · Security**, in **Report delivery**, or empty it to allow any address. The list does not apply to Slack or webhook deliveries. 4. **Spam.** Reports come from `noreply@sanda-os.com.au`. Ask your IT team to allow it. 5. **The time.** Check the **next** line. Times fall on a 15 minute grid in the timezone you chose. When a delivery fails, owners and admins are emailed at most once a day for that delivery, unless you have switched off the scheduled run alert in **Budget & alerts**. See [report delivery](https://docs.sanda-os.com.au/reports/delivery). ## A page says it is not included in your edition You see "This isn't included in your edition" on **Vector search**. sanda search is included from Standard. An owner or admin can press **Choose your edition** on that page, then **Ask to move to Standard**. See [editions](https://docs.sanda-os.com.au/billing/editions). ## cherry proposed a change but nothing happened By default cherry asks first. A change it proposes waits as a card in the conversation, and nothing runs until you press **Confirm**. Press **Decline** to drop it. Even in **Autonomous** mode, cherry still asks before emailing a report, dropping a view, retiring a table, or deleting a definition or a delivery. Read-only members cannot make changes at all. You can change the mode under **Settings · cherry**. See [actions and approvals](https://docs.sanda-os.com.au/cherry/actions-and-approvals). ## The console says the workspace is closed and read-only An owner closed the workspace, so every page opens but nothing can be changed. An owner can reopen it, until the date the banner shows, by pressing **Reopen this workspace**. It is at the foot of **Settings**, in the section that now reads **Closed**. See [close or delete a workspace](https://docs.sanda-os.com.au/workspace/close-or-delete). ## The console asks me to accept the policy or set up two-step sign-in - **"The privacy policy has changed."** Every person reads and accepts sanda's privacy policy once, and again whenever it changes. Read it and accept to continue. - **"Set up two-step sign-in."** Your workspace asks everyone to sign in with a code from an authenticator app. Follow the setup, and keep your recovery codes somewhere safe. See [two-step sign-in](https://docs.sanda-os.com.au/workspace/two-step). ## Still stuck :::links - [Getting help](https://docs.sanda-os.com.au/help): Send a message to sanda's team from **System · Support**, or email. - [FAQ](https://docs.sanda-os.com.au/help/faq): Where your data is held, what happens when the trial ends, and how to close a workspace. ::: --- # FAQ > Answers to common questions about where your data is held, who can see it, AI and privacy, the trial, billing, and closing a workspace. Where an answer depends on a policy, this page says what sanda's [privacy policy](https://sanda-os.com.au/privacy) and [terms of service](https://sanda-os.com.au/terms) say, and links to them. If the two ever differ from this page, the policy and the terms are the ones that apply. ## Your data ### Where is my data held? Your warehouse and the data you sync or upload into it are held in your workspace's [data region](https://docs.sanda-os.com.au/reference/glossary#data-region), which you choose when you sign up and which does not change afterwards. Workspaces can be created in Australia (Sydney). sanda's own database (your definitions, reports, schedules, settings and chat history) is held in Sydney, Australia. Some work happens outside Australia: AI features send the part of your data a question needs to an AI model, and sanda search, if you switch it on, sends indexed text to an embedding model in the United States. [How sanda works](https://docs.sanda-os.com.au/get-started/how-becca-works) lists exactly what leaves and when. ### What kind of database is my warehouse? A dedicated PostgreSQL database that belongs to your workspace alone, provisioned, tuned and run for you. PostgreSQL is an open standard, so nothing in your warehouse is proprietary. See [warehouse](https://docs.sanda-os.com.au/warehouse). ### Can I take my data with me? Yes. Because your warehouse is standard PostgreSQL, a full copy is a database rather than a proprietary file. Email [hello@sanda-os.com.au](mailto:hello@sanda-os.com.au) and sanda will hand you a PostgreSQL dump of the whole workspace. Ask before a closed workspace's read-only period ends, because the data is destroyed after it. You can also read your data in Excel or Power BI through the feed under **Settings · Integrations**, and export a report as a PDF from its **more** menu. See [export](https://docs.sanda-os.com.au/warehouse/export). ### Who can see my data? - **People in your workspace**, according to their role. Owners and admins can do everything the console offers. Read-only members can open every page and ask every question but change nothing. Readers see only the reports published to them on the reports portal, and can be limited to their own rows. - **Anyone you share a link with.** A public report link is the whole credential, so anyone holding it can read that report. You can set an expiry and revoke it. - **Other workspaces cannot.** Each workspace has its own database and its own credentials. - **sanda's team, within limits.** The privacy policy says access to production is limited to the people who operate it, and is logged. It also says sanda's admin tools sit behind a separate sign-in, cannot read your chat history or query your warehouse, and that there is no feature that lets staff sign in as you. ### Does sanda sell or share my data? No. Nobody buys it and nobody is sold it. Your data reaches a third party only through the service providers that run parts of sanda, who do not use it for anything of their own, and in a few limited cases: professional advisers under a duty of confidence, a sale of the business, or a valid legal demand. See the [privacy policy](https://sanda-os.com.au/privacy). ## AI and privacy ### Does sanda use my data to train AI? No. The privacy policy says sanda does not mine your data or use it to train anything. It also says every AI model sanda uses is called on a zero-data-retention basis: what is sent is used to answer the request and then discarded, and it is not stored by the provider or used to train anything. Your chat history is never mined or used for training either. ### What does an AI model see? Depending on the feature, a request can carry your question and recent messages, the names of your tables and columns, your definitions, a small sample of real values from your columns, the rows a query returns, and your name, role and workspace name. Columns whose names suggest secrets, such as passwords, tokens, tax file numbers and card numbers, are never sampled or returned. Other columns are not filtered by content, so if a column holds personal information such as email addresses, a sample of it can be sent. ### What does sanda keep from my conversations with cherry? sanda keeps your chat history, including your questions, cherry's answers and the rows behind each answer, in its own database so you can come back to it. It belongs to your workspace, and it is deleted when you delete the conversation or close the workspace. ### Can I stop sanda using AI? Yes. An owner or admin can set the daily AI limit to 0 under **Settings · Plan & budget**, in **Budget & alerts**. sanda then makes no AI calls for the workspace. Syncs, reports and your data keep working. Raise the limit again to switch AI back on. ## The trial and billing ### What happens when the trial ends? A trial runs for 7 days and comes with A$10 of free credit, and the top bar shows both. Nothing is billed during a trial, and nothing renews into a paid subscription on its own. When the days are over, or sooner if the credit runs out first, the workspace freezes. Queries, reports, AI, syncs and scheduled runs wait, and everything in the workspace is kept. To carry on, an owner or admin presses **Ask to move to** an edition under **Settings · Plan & budget**, and the workspace continues where it stopped. If you plan to carry on, ask before the day count reaches zero. See [the trial](https://docs.sanda-os.com.au/billing/trial). ### How much does sanda cost? Each edition has a flat monthly platform fee: A$20, A$50 and A$200 a month for Basic, Standard and Enterprise. On top of that you pay for what you use: storage by the gigabyte-month, compute by the hour, AI by the token, and managed sync by the run. Nothing is priced per seat, report, schedule or connector. Prices are in Australian dollars. See [editions](https://docs.sanda-os.com.au/billing/editions). ### How do I change edition? An owner or admin opens **Settings · Plan & budget**. Between Basic and Standard, they press **Move to Basic** or **Move to Standard**, check what changes and confirm, and the move is made at once. A move up bills the higher fee for that whole month, and a move down bills the lower fee from the next month. For anything else (leaving a trial, Enterprise, or a fee agreed with sanda), they press **Ask to move to** the edition they want. That sends a request to sanda's team, who make the change, and it appears under **System · Support** with its status. See [editions](https://docs.sanda-os.com.au/billing/editions). ### How do I pay? sanda invoices monthly in arrears, by email, and you pay by direct bank transfer. It does not take card payments, so it never holds card details. See [invoices](https://docs.sanda-os.com.au/billing/invoices). ### Is there an uptime guarantee? Not unless one is written into a separate agreement with you. The terms say sanda is not offered with one by default, and that maintenance needing downtime is announced in advance where sanda can foresee it. ## Your workspace ### How do I close a workspace? Only an owner can. You can close it, which keeps it readable for a while, or delete it at once. :::steps 1. **Open the section.** Go to **Settings · Workspace** and scroll to the foot of the tab. The heading reads **Close this workspace**. 2. **Close it.** Optionally say why you are leaving, type the workspace's short name to confirm, and press **Close workspace**. The workspace becomes read-only for 90 days. Every page still opens, every question can still be asked, and you can take an export. After that, the warehouse and everything in it are destroyed, and backups are overwritten within a further 30 days. An owner can reopen it at any point before then, and everything comes back exactly as it was. 3. **Or delete it now.** In the same section, type the short name again and press **Delete workspace now**. This skips the 90 days: the warehouse, connections, semantic fluid and reports are destroyed at once, every member loses access, and nothing can be exported afterwards. Anyone whose only workspace it was loses their sanda account too, so their email can sign up again. Anything used since your last invoice is still billed, and nothing is billed on a trial. ::: Invoices and the record that you accepted the terms are kept after closing, because the law requires it. They hold no business data. See [close or delete a workspace](https://docs.sanda-os.com.au/workspace/close-or-delete). ### Can I have more than one workspace? An email address belongs to one workspace. A second trial for the same address is not created, and an address that already has a workspace cannot be invited into another. ### How do I add my team? Go to **Settings · Team** and press **Invite someone**. You choose a role for each person: admin, read-only, or reader for the reports portal only. See [members and roles](https://docs.sanda-os.com.au/workspace/members-and-roles). ### Why am I asked to accept the privacy policy again? Every person reads and accepts the privacy policy once, and again whenever it changes. sanda tells you in the console and asks you to accept the new version before you carry on. ## Using sanda ### Do I need to write SQL? No. cherry composes and runs the queries, and shows you the query behind each answer. Owners and admins can also run their own SQL in the SQL shell if they want to. See [ask cherry](https://docs.sanda-os.com.au/cherry/ask). ### How do I know an answer is right? Check the query cherry shows, and compare one or two answers with a figure you already trust. The terms say it plainly: sanda is a very good analyst and it is not a person. The numbers come from your data, but a figure that will be relied on for a decision should be one somebody has looked at. ### Which systems can I connect? See the [connector catalogue](https://docs.sanda-os.com.au/connectors). You can also load a CSV file directly, which is covered in [upload a CSV](https://docs.sanda-os.com.au/connections/upload-a-csv). ### Can my own AI assistant use sanda? Yes. MCP lets assistants such as Claude read your semantic fluid with the permissions you grant, and it is included on every edition. The Excel and Power BI feed works on every edition. See [MCP](https://docs.sanda-os.com.au/mcp) and [OData](https://docs.sanda-os.com.au/odata). ## Related pages :::links - [Getting help](https://docs.sanda-os.com.au/help): Support, contact addresses and reporting a security issue. - [Troubleshooting](https://docs.sanda-os.com.au/help/troubleshooting): Fixes for common problems. ::: --- # Connector catalogue > Every system sanda can bring into your warehouse, how a connector works, and what to do when yours is not listed. A connector is sanda's way of reading one kind of system. It knows how to sign in, which tables or objects the system offers (sanda calls each one a stream), and how to read them, so nobody writes extraction code. You choose a connector when you [add a connection](https://docs.sanda-os.com.au/connections/add-a-connection). The connection is the saved link between that system and your warehouse. Each page in this catalogue covers one connector: what to prepare in that system, and every field its setup form asks for. The field lists are generated from the connectors themselves, so they match what you see in the console. The popular connectors ([PostgreSQL](https://docs.sanda-os.com.au/connectors/postgres), [MySQL](https://docs.sanda-os.com.au/connectors/mysql), [CSV / files](https://docs.sanda-os.com.au/connectors/csv), [Google Sheets](https://docs.sanda-os.com.au/connectors/google_sheets), [Stripe](https://docs.sanda-os.com.au/connectors/stripe), [Xero](https://docs.sanda-os.com.au/connectors/xero), [QuickBooks](https://docs.sanda-os.com.au/connectors/quickbooks), [Shopify](https://docs.sanda-os.com.au/connectors/shopify), [Salesforce](https://docs.sanda-os.com.au/connectors/salesforce) and [HubSpot](https://docs.sanda-os.com.au/connectors/hubspot)) also carry notes on where to find each credential. ## Find your system Filter by name, or narrow the list to one family. ### Databases - [PostgreSQL](https://docs.sanda-os.com.au/connectors/postgres) - [MySQL](https://docs.sanda-os.com.au/connectors/mysql) - [SQL Server](https://docs.sanda-os.com.au/connectors/mssql) - [MongoDB](https://docs.sanda-os.com.au/connectors/mongodb) - [Oracle](https://docs.sanda-os.com.au/connectors/oracle) - [Snowflake](https://docs.sanda-os.com.au/connectors/snowflake) - [BigQuery](https://docs.sanda-os.com.au/connectors/bigquery) - [Redshift](https://docs.sanda-os.com.au/connectors/redshift) - [ClickHouse](https://docs.sanda-os.com.au/connectors/clickhouse) - [DynamoDB](https://docs.sanda-os.com.au/connectors/dynamodb) - [Elasticsearch](https://docs.sanda-os.com.au/connectors/elasticsearch) - [CockroachDB](https://docs.sanda-os.com.au/connectors/cockroachdb) ### Files and storage - [CSV / files](https://docs.sanda-os.com.au/connectors/csv) - [S3 / object storage](https://docs.sanda-os.com.au/connectors/s3) - [Google Sheets](https://docs.sanda-os.com.au/connectors/google_sheets) - [Google Drive](https://docs.sanda-os.com.au/connectors/google_drive) - [Azure Blob Storage](https://docs.sanda-os.com.au/connectors/azure_blob_storage) - [SFTP](https://docs.sanda-os.com.au/connectors/sftp) - [Google Cloud Storage](https://docs.sanda-os.com.au/connectors/gcs) ### Finance and payments - [Stripe](https://docs.sanda-os.com.au/connectors/stripe) - [Xero](https://docs.sanda-os.com.au/connectors/xero) - [QuickBooks](https://docs.sanda-os.com.au/connectors/quickbooks) - [NetSuite](https://docs.sanda-os.com.au/connectors/netsuite) - [PayPal](https://docs.sanda-os.com.au/connectors/paypal) - [Square](https://docs.sanda-os.com.au/connectors/square) - [Chargebee](https://docs.sanda-os.com.au/connectors/chargebee) - [Recurly](https://docs.sanda-os.com.au/connectors/recurly) - [Braintree](https://docs.sanda-os.com.au/connectors/braintree) - [Zuora](https://docs.sanda-os.com.au/connectors/zuora) ### Commerce - [Shopify](https://docs.sanda-os.com.au/connectors/shopify) - [WooCommerce](https://docs.sanda-os.com.au/connectors/woocommerce) - [Amazon Seller Partner](https://docs.sanda-os.com.au/connectors/amazon_seller) - [Amazon Ads](https://docs.sanda-os.com.au/connectors/amazon_ads) - [BigCommerce](https://docs.sanda-os.com.au/connectors/bigcommerce) - [Recharge](https://docs.sanda-os.com.au/connectors/recharge) ### CRM and support - [Salesforce](https://docs.sanda-os.com.au/connectors/salesforce) - [HubSpot](https://docs.sanda-os.com.au/connectors/hubspot) - [Pipedrive](https://docs.sanda-os.com.au/connectors/pipedrive) - [Zendesk](https://docs.sanda-os.com.au/connectors/zendesk) - [Intercom](https://docs.sanda-os.com.au/connectors/intercom) - [Freshdesk](https://docs.sanda-os.com.au/connectors/freshdesk) - [Zoho CRM](https://docs.sanda-os.com.au/connectors/zoho_crm) - [Front](https://docs.sanda-os.com.au/connectors/front) - [Close](https://docs.sanda-os.com.au/connectors/close_com) ### Marketing and analytics - [Google Ads](https://docs.sanda-os.com.au/connectors/google_ads) - [Google Analytics 4](https://docs.sanda-os.com.au/connectors/google_analytics) - [Google Search Console](https://docs.sanda-os.com.au/connectors/google_search_console) - [Facebook Marketing](https://docs.sanda-os.com.au/connectors/facebook_marketing) - [Instagram](https://docs.sanda-os.com.au/connectors/instagram) - [TikTok Marketing](https://docs.sanda-os.com.au/connectors/tiktok) - [LinkedIn Ads](https://docs.sanda-os.com.au/connectors/linkedin_ads) - [Klaviyo](https://docs.sanda-os.com.au/connectors/klaviyo) - [Mailchimp](https://docs.sanda-os.com.au/connectors/mailchimp) - [Mixpanel](https://docs.sanda-os.com.au/connectors/mixpanel) - [Amplitude](https://docs.sanda-os.com.au/connectors/amplitude) - [Typeform](https://docs.sanda-os.com.au/connectors/typeform) - [Microsoft Advertising](https://docs.sanda-os.com.au/connectors/bing_ads) - [Pinterest](https://docs.sanda-os.com.au/connectors/pinterest) - [Snapchat Ads](https://docs.sanda-os.com.au/connectors/snapchat_marketing) - [Iterable](https://docs.sanda-os.com.au/connectors/iterable) - [SendGrid](https://docs.sanda-os.com.au/connectors/sendgrid) - [Braze](https://docs.sanda-os.com.au/connectors/braze) - [PostHog](https://docs.sanda-os.com.au/connectors/posthog) - [SurveyMonkey](https://docs.sanda-os.com.au/connectors/surveymonkey) ### Product and operations - [GitHub](https://docs.sanda-os.com.au/connectors/github) - [Jira](https://docs.sanda-os.com.au/connectors/jira) - [Notion](https://docs.sanda-os.com.au/connectors/notion) - [Airtable](https://docs.sanda-os.com.au/connectors/airtable) - [Slack](https://docs.sanda-os.com.au/connectors/slack) - [Asana](https://docs.sanda-os.com.au/connectors/asana) - [GitLab](https://docs.sanda-os.com.au/connectors/gitlab) - [Monday](https://docs.sanda-os.com.au/connectors/monday) - [ClickUp](https://docs.sanda-os.com.au/connectors/clickup) - [Zoom](https://docs.sanda-os.com.au/connectors/zoom) - [Twilio](https://docs.sanda-os.com.au/connectors/twilio) - [Harvest](https://docs.sanda-os.com.au/connectors/harvest) ### People - [BambooHR](https://docs.sanda-os.com.au/connectors/bamboo_hr) - [Greenhouse](https://docs.sanda-os.com.au/connectors/greenhouse) ### By arrangement - [MYOB](https://docs.sanda-os.com.au/connectors/myob) - [Something else](https://docs.sanda-os.com.au/connectors/other) ## How a connector works The steps are the same for every source, whatever it holds. 1. **You give sanda the details for the system.** A host and a login for a database, an API key or app credentials for a SaaS product. The details go straight to sanda's managed sync over TLS, and sanda's own database never stores them. 2. **sanda reads what the system offers.** Pressing **Read schema** signs in and lists the streams available. This doubles as the test: a wrong password or a closed firewall fails here, with a reason. 3. **You choose what to sync.** Every table starts on **Full refresh · Overwrite**, which reads the whole table on every run and replaces what landed last time. For a large table, switch to an incremental mode with a cursor column so each run reads only the rows that have changed. See [sync modes](https://docs.sanda-os.com.au/connections/sync-modes) and [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 4. **Each stream lands as a table in your warehouse.** Rows go to the `raw` schema, one table per stream, named `raw.__`. 5. **Runs repeat on a schedule.** You set the frequency, or run a sync by hand with **Sync now**. See [sync schedules](https://docs.sanda-os.com.au/connections/schedules). ## Connect a system :::steps 1. **Open Connections.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose the source.** Search by name, or pick from **Popular** or **All sources**. 3. **Fill in the form.** The setup guide sits beside it, and the page for your connector here says where to find each credential. 4. **Read the schema and choose streams.** Nothing moves until you save your choice of streams. 5. **Run the first sync.** Press **Sync now**, or let the schedule start it. ::: Owners and admins add connections. The full walkthrough is in [Add a connection](https://docs.sanda-os.com.au/connections/add-a-connection). If the system sits behind a firewall, [allow sanda's address](https://docs.sanda-os.com.au/connections/allowlist) first. ## Connectors set up by arrangement A few connectors have no setup form. [MYOB](https://docs.sanda-os.com.au/connectors/myob) is wired by hand, and so is [Something else](https://docs.sanda-os.com.au/connectors/other), which covers any system that is not in the catalogue. [Zuora](https://docs.sanda-os.com.au/connectors/zuora) and [BigCommerce](https://docs.sanda-os.com.au/connectors/bigcommerce) are set up the same way, because the ready-made connectors for them are no longer maintained. In the console they carry a **Concierge** badge. There is nothing to fill in. You create the connection with a name and a frequency, and a sanda engineer gets in touch to wire it up and confirm the first load with you. Until then the connection shows **Setup incomplete** in **Data · Connections**. A connector that normally has a form can land here too. If sanda cannot read a connector's setup form, the console says so and offers the same route, and the connector's page in this catalogue says the same. ## Ask for a connector If the system you use is not in the catalogue, pick **Something else** when you add a connection. Give it a name and a frequency, and a sanda engineer gets in touch to find out what the system is and the best way to bring its data in. If what you have is a file rather than a system, [load a CSV](https://docs.sanda-os.com.au/connections/upload-a-csv) straight into your warehouse. For anything else, see [getting help](https://docs.sanda-os.com.au/help). --- # PostgreSQL > Bring tables and views from a PostgreSQL database into your warehouse, on a schedule, with optional incremental reads. ## Before you start You need three things on the PostgreSQL side: a server sanda can reach, a login that can only read, and sanda's address allowed through your firewall. - **A reachable server.** PostgreSQL 11 or newer. sanda connects from the internet, so the host name must resolve on the public internet. A private address, or a name that only works inside your network, is refused before sanda tries. For a database on a private network, see the SSH tunnel tip below. - **The connection details.** The host name, the port (5432 unless you changed it) and the name of the database. - **A read-only role.** sanda only ever reads. Give it a role of its own, so its access is easy to see and easy to revoke. Run this once, as a role that can create roles: ```sql title="A read-only role for sanda" create role becca_reader login password 'use-a-long-random-password'; grant connect on database acme to becca_reader; grant usage on schema public to becca_reader; grant select on all tables in schema public to becca_reader; alter default privileges in schema public grant select on tables to becca_reader; ``` Replace `acme` with your database name. Repeat the last three statements for every other schema you want in sanda. The last statement covers tables created later, but only those created by the role that runs it. Run it as the role that creates your tables, or add `for role` and that role's name after `alter default privileges`. - **sanda's address allowed.** Add sanda's fixed address to the firewall or security group that protects the database, and to `pg_hba.conf` if you restrict logins by address. See [Allowlist sanda's address](https://docs.sanda-os.com.au/connections/allowlist). ## Connect PostgreSQL :::steps 1. **Choose PostgreSQL.** Go to **Data · Connections**, press **New connection** and pick **PostgreSQL**. 2. **Say where the database is.** Enter the host name alone in **Host**, with no port and no `postgres://` prefix, and the name of the database in **Database Name**. **Port** sits under **Advanced settings** and starts at 5432. 3. **Enter the login.** Put the role's name in **Username** and its password in **Password**. 4. **Check the encryption.** Under **Advanced settings**, **SSL Mode** opens on **require**, which always encrypts the connection. Choose **verify-ca** or **verify-full** if sanda should also check the server's certificate. Both then ask for the **CA certificate**. 5. **Continue.** Press **Continue**, name the connection, and press **Read schema**. sanda signs in and lists the tables the role can read. If it fails, the message says why. See [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting). ::: The rest of the flow, naming the connection, setting a frequency and choosing streams, is the same for every source. See [Add a connection](https://docs.sanda-os.com.au/connections/add-a-connection). ## What syncs sanda reads the tables and views in the schemas you allow. **Schemas** sits under **Advanced settings**. Leave it empty to read every schema the role can see, or list the ones you want. The names are case sensitive. Each table you choose becomes a stream, and lands in your warehouse as its own table. **Check Table and Column Access Privileges** is on by default. During schema discovery sanda tests each table individually and leaves out any the role cannot read. A table missing from the stream list usually means the role lacks `select` on it. ### How changes are found **Update Method**, under **Advanced settings**, decides how sanda can tell what has changed since the last run. It opens on **Scan Changes with User Defined Cursor**, the one that needs nothing set up on your database. | Update Method | How it finds changes | Sees deletes | What your database needs | |---|---|---|---| | **Scan Changes with User Defined Cursor** | Reads the rows whose cursor column, such as `updated_at`, has moved since the last run | No | `select` only. You pick the cursor for each table in the stream list | | **Detect Changes with Xmin System Column** | Uses PostgreSQL's built-in `xmin` system column, so no cursor column is needed | No | `select` only. Suited to databases without heavy write loads. It does not read views | | **Read Changes using Change Data Capture (CDC)** | Reads PostgreSQL's write-ahead log through logical replication | Yes | `wal_level = logical`, a replication slot, a publication and a role with the `replication` attribute | A table left on a full refresh mode is read whole on every run and replaced, so it does reflect deletes at the source, at the cost of reading everything each time. See [sync modes](https://docs.sanda-os.com.au/connections/sync-modes). ## Tips - **Start with the schemas that matter.** You can widen the selection later without redoing anything. - **Read the big tables incrementally.** The first run reads every table you choose in full. For a large table, switch to an incremental mode and choose a cursor column that only moves forward, such as `updated_at`. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). - **Prefer a read replica.** If you have one, point sanda at it so syncs never compete with your application. A replica that lags hands sanda older data. Change data capture from a replica needs PostgreSQL 16.1 or later and extra configuration on the server, so it normally runs against the primary. - **Reach a private database through a tunnel.** **SSH Tunnel Method** sits under **Advanced settings**, with **SSH Key Authentication** and **Password Authentication** options. The jump server has to be reachable from the internet, and it needs sanda's address allowed, exactly as a database would. See [Allowlist sanda's address](https://docs.sanda-os.com.au/connections/allowlist). - **Azure Entra sign-in.** If your server signs in with a Microsoft Entra service principal, turn on **Azure Entra Service Principal Authentication** under **Advanced settings**. **Password** then holds the service principal's client secret, and you also fill in **Azure Entra Tenant Id** and **Azure Entra Client Id**. ### Set up change data capture Only do this if you need deletes captured and can change server settings. It needs `wal_level = logical` in the server configuration, which takes a restart. On a managed service that is usually a setting in the provider's console instead. Then, as a privileged role: ```sql title="Prepare PostgreSQL for change data capture" alter role becca_reader replication; select pg_create_logical_replication_slot('becca_slot', 'pgoutput'); create publication becca_pub for table public.orders, public.customers; ``` Choose **Read Changes using Change Data Capture (CDC)** under **Update Method** in **Advanced settings**, and enter `becca_slot` in **Replication Slot** and `becca_pub` in **Publication**. A table without a primary key needs `alter table your_table replica identity full`, or PostgreSQL refuses updates and deletes on it once it is in a publication. :::caution A replication slot that nothing reads makes the server keep its write-ahead log, and the disk can fill. If you delete the connection or stop syncing for good, drop the slot with `select pg_drop_replication_slot('becca_slot');`. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Host** | string | Yes | Hostname of the database. | | **Port** | integer | Yes | Port of the database. Defaults to 5432. Default `5432`. | | **Database Name** | string | Yes | The name of the database to connect to. | | **Username** | string | Yes | The username which is used to access the database. | | **Password** | string, secret | No | The password associated with the username. | | **Azure Entra Service Principal Authentication** | boolean | No | Interpret password as a client secret for a Microsoft Entra service principal. Default `false`. | | **Azure Entra Tenant Id** | string | No | If using Entra service principal, the ID of the tenant. | | **Azure Entra Client Id** | string | No | If using Entra service principal, the application ID of the service principal. | | **SSL Mode** | one of 6 options | No | The encryption method which is used when communicating with the database. | | **Schemas** | array | No | The list of schemas to sync from. Case sensitive. Empty means all schemas. | | **JDBC URL Parameters (Advanced)** | string | No | Additional properties to pass to the JDBC URL string when connecting to the database formatted as 'key=value' pairs separated by the symbol '&'. (example: key1=value1&key2=value2&key3=value3). | | **SSH Tunnel Method** | one of 3 options | Yes | Whether to initiate an SSH tunnel before connecting to the database, and if so, which kind of authentication to use. | | **Update Method** | one of 3 options | Yes | Configures how data is extracted from the database. | | **Check Table and Column Access Privileges** | boolean | No | When this feature is enabled, during schema discovery the connector will query each table or view individually to check access privileges and inaccessible tables, views, or columns therein will be removed. In large schemas, this might cause schema discovery to take too long, in which case it might be advisable to disable this feature. Default `true`. | | **Checkpoint Target Time Interval** | integer | No | How often (in seconds) a stream should checkpoint, when possible. Default `300`. | | **Max Concurrent Queries to Database** | integer | No | Maximum number of concurrent queries to the database. Leave empty to let becca optimize performance. | ### SSL Mode The encryption method which is used when communicating with the database. Choose one of the following. Each asks for its own fields. **disable** No further fields. **allow** No further fields. **prefer** No further fields. **require** No further fields. **verify-ca** | Field | Type | Required | Description | | --- | --- | --- | --- | | **CA certificate** | string, secret | Yes | | | **Client certificate File** | string, secret | No | Client certificate (this is not a required field, but if you want to use it, you will need to add the Client key as well). | | **Client Key** | string, secret | No | Client key (this is not a required field, but if you want to use it, you will need to add the Client certificate as well). | | **Client key password** | string, secret | No | Password for keystorage. This field is optional. If you do not add it, the password will be generated automatically. | **verify-full** | Field | Type | Required | Description | | --- | --- | --- | --- | | **CA certificate** | string, secret | Yes | | | **Client certificate File** | string, secret | No | Client certificate (this is not a required field, but if you want to use it, you will need to add the Client key as well). | | **Client Key** | string, secret | No | Client key (this is not a required field, but if you want to use it, you will need to add the Client certificate as well). | | **Client key password** | string, secret | No | Password for keystorage. This field is optional. If you do not add it, the password will be generated automatically. | ### SSH Tunnel Method Whether to initiate an SSH tunnel before connecting to the database, and if so, which kind of authentication to use. Choose one of the following. Each asks for its own fields. **No Tunnel** No further fields. **SSH Key Authentication** | Field | Type | Required | Description | | --- | --- | --- | --- | | **SSH Tunnel Jump Server Host** | string | Yes | Hostname of the jump server host that allows inbound ssh tunnel. | | **SSH Connection Port** | integer | Yes | Port on the proxy/jump server that accepts inbound ssh connections. Default `22`. | | **SSH Login Username** | string | Yes | OS-level username for logging into the jump server host. | | **SSH Private Key** | string, secret | Yes | OS-level user account ssh key credentials in RSA PEM format ( created with ssh-keygen -t rsa -m PEM -f myuser_rsa ). | **Password Authentication** | Field | Type | Required | Description | | --- | --- | --- | --- | | **SSH Tunnel Jump Server Host** | string | Yes | Hostname of the jump server host that allows inbound ssh tunnel. | | **SSH Connection Port** | integer | Yes | Port on the proxy/jump server that accepts inbound ssh connections. Default `22`. | | **SSH Login Username** | string | Yes | OS-level username for logging into the jump server host. | | **Password** | string, secret | Yes | OS-level password for logging into the jump server host. | ### Update Method Configures how data is extracted from the database. Choose one of the following. Each asks for its own fields. **Read Changes using Change Data Capture (CDC)** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Replication Slot** | string | Yes | A plugin logical replication slot. | | **Publication** | string | Yes | A Postgres publication used for consuming changes. | | **Initial Waiting Time in Seconds (Advanced)** | integer | No | The amount of time the connector will wait when it launches to determine if there is new data to sync or not. Defaults to 1200 seconds. Valid range: 120 seconds to 2400 seconds. Default `1200`. | | **LSN commit behavior** | string | No | Determines when becca should flush the LSN of processed WAL logs in the source database. `After loading Data in the destination` is default. If `While reading Data` is selected, in case of a downstream failure (while loading data into the destination), next sync would result in a full sync. One of `While reading Data`, `After loading Data in the destination`. Default `After loading Data in the destination`. | | **Debezium heartbeat query (Advanced)** | string | No | Specifies a query that the connector executes on the source database when the connector sends a heartbeat message. | | **Initial Load Timeout in Hours (Advanced)** | integer | No | The amount of time an initial load is allowed to continue for before catching up on CDC events. Default `8`. | | **Debezium Engine Shutdown Timeout in Seconds (Advanced)** | integer | No | The amount of time to allow the Debezium Engine to shut down, in seconds. Default `60`. | **Detect Changes with Xmin System Column** No further fields. **Scan Changes with User Defined Cursor** No further fields. ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # MySQL > Bring tables from a MySQL or MariaDB database into your warehouse, on a schedule, with optional incremental reads. ## Before you start You need three things on the MySQL side: a server sanda can reach, a user that can only read, and sanda's address allowed through your firewall. - **A reachable server.** MySQL 8.0 or newer, or MariaDB 10.5 or newer. sanda connects from the internet, so the host name must resolve on the public internet. A private address, or a name that only works inside your network, is refused before sanda tries. - **The connection details.** The host name, the port (3306 unless you changed it) and the name of the database. - **A read-only user.** sanda only ever reads. Give it a user of its own, so its access is easy to see and easy to revoke: ```sql title="A read-only user for sanda" create user 'becca_reader'@'%' identified by 'use-a-long-random-password'; grant select, show view on acme.* to 'becca_reader'@'%'; ``` Replace `acme` with your database name. The `%` lets the user sign in from any address. To accept logins only from sanda, replace it with sanda's fixed address from the allowlist page. - **sanda's address allowed.** Add sanda's address to your provider's network rules or security group. See [Allowlist sanda's address](https://docs.sanda-os.com.au/connections/allowlist). ## Connect MySQL :::steps 1. **Choose MySQL.** Go to **Data · Connections**, press **New connection** and pick **MySQL**. 2. **Say where the database is.** Enter the host name alone in **Host**, with no port, and the name of the database in **Database**. **Port** sits under **Advanced settings** and starts at 3306. 3. **Enter the login.** Put the user's name in **User** and its password in **Password**. 4. **Check the encryption.** Under **Advanced settings**, **Encryption** opens on **required**, which always encrypts the connection and fails if the server cannot. **preferred** allows an unencrypted connection when the server does not support encryption. **verify_ca** and **verify_identity** also check the server's certificate, and ask for the **CA certificate**. 5. **Continue.** Press **Continue**, name the connection, and press **Read schema**. sanda signs in and lists the tables the user can read. If it fails, the message says why. See [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting). ::: The rest of the flow, naming the connection, setting a frequency and choosing streams, is the same for every source. See [Add a connection](https://docs.sanda-os.com.au/connections/add-a-connection). ## What syncs sanda reads the tables in the database you name. Each table you choose becomes a stream, and lands in your warehouse as its own table. To limit which tables sanda can see, add a filter under **Table Filters** in **Advanced settings**. The field takes JSON: the database name, which must match **Database**, and a list of SQL `like` patterns for the table names to include. ```json title="Only the orders tables and customers" [{"database_name": "acme", "table_name_patterns": ["orders%", "customers"]}] ``` **Check Table and Column Access Privileges** is on by default. During schema discovery sanda tests each table individually and leaves out any the user cannot read. A table missing from the stream list usually means the user lacks `select` on it. ### How changes are found **Update Method**, under **Advanced settings**, decides how sanda can tell what has changed since the last run. | Update Method | How it finds changes | Sees deletes | What your database needs | |---|---|---|---| | **Scan Changes with User Defined Cursor** | Reads the rows whose cursor column, such as `updated_at`, has moved since the last run | No | `select` only. You pick the cursor for each table in the stream list | | **Read Changes using Change Data Capture (CDC)** | Reads MySQL's binary log | Yes | Binary logging in row format, and a user with replication privileges | The form opens on the cursor option. A table left on a full refresh mode is read whole on every run and replaced, so it does reflect deletes at the source, at the cost of reading everything each time. See [sync modes](https://docs.sanda-os.com.au/connections/sync-modes). ## Tips - **Read the big tables incrementally.** The first run reads every table you choose in full. For a large table, switch to an incremental mode and choose a cursor column that only moves forward, such as `updated_at`. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). - **Watch TINYINT(1) columns.** They arrive as booleans. If you use them for small counts, turn on **Treat TINYINT(1) Columns as Integers** under **Advanced settings**. - **Prefer a read replica.** If you have one, point sanda at it so syncs never compete with your application. A replica that lags hands sanda older data. - **Reach a private database through a tunnel.** **SSH Tunnel Method** sits under **Advanced settings**, with **SSH Key Authentication** and **Password Authentication** options. The jump server has to be reachable from the internet, and it needs sanda's address allowed, exactly as a database would. See [Allowlist sanda's address](https://docs.sanda-os.com.au/connections/allowlist). ### Set up change data capture Only do this if you need deletes captured and can change server settings. The server must write its binary log in row format, with full row images (`binlog_format = ROW` and `binlog_row_image = FULL`). The user needs `reload`, `show databases` and the replication privileges on top of `select`. These are global privileges, so they are granted on `*.*`: ```sql title="Privileges for the sanda user" grant reload, show databases, replication slave, replication client on *.* to 'becca_reader'@'%'; ``` Choose **Read Changes using Change Data Capture (CDC)** under **Update Method**. Its own options sit under it: - **Configured server timezone for the MySQL source (Advanced).** Set this only if the server's timezone is not a standard one. - **Invalid CDC Position Behavior (Advanced).** MySQL removes old binary logs on its own schedule. If sanda's saved position has been removed by the time it next runs, **Fail sync** stops the sync until the connection is reset, which sanda support does for you, and **Re-sync data** starts a fresh full load automatically, which costs more and can lose data. It opens on **Fail sync**. Keep binary log retention longer than the longest gap between syncs, so sanda's position is still there when the next run starts. ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Host** | string | Yes | Hostname of the database. | | **Port** | integer | Yes | Port of the database. Default `3306`. | | **User** | string | Yes | The username which is used to access the database. | | **Password** | string, secret | No | The password associated with the username. | | **Database** | string | Yes | The database name. | | **Table Filters** | array | No | Optional filters to include only specific tables from the specified database. | | **JDBC URL Params** | string | No | Additional properties to pass to the JDBC URL string when connecting to the database formatted as 'key=value' pairs separated by the symbol '&'. (example: key1=value1&key2=value2&key3=value3). | | **Encryption** | one of 4 options | No | The encryption method which is used when communicating with the database. | | **SSH Tunnel Method** | one of 3 options | No | Whether to initiate an SSH tunnel before connecting to the database, and if so, which kind of authentication to use. | | **Update Method** | one of 2 options | Yes | Configures how data is extracted from the database. | | **Checkpoint Target Time Interval** | integer | No | How often (in seconds) a stream should checkpoint, when possible. Default `300`. | | **Check Table and Column Access Privileges** | boolean | No | When this feature is enabled, during schema discovery the connector will query each table or view individually to check access privileges and inaccessible tables, views, or columns therein will be removed. In large schemas, this might cause schema discovery to take too long, in which case it might be advisable to disable this feature. Default `true`. | | **Max Concurrent Queries to Database** | integer | No | Maximum number of concurrent queries to the database. Leave empty to let becca optimize performance. | | **Treat TINYINT(1) Columns as Integers** | boolean | No | When enabled, TINYINT(1) columns are emitted as integers instead of booleans. Default `false`. | ### Encryption The encryption method which is used when communicating with the database. Choose one of the following. Each asks for its own fields. **preferred** No further fields. **required** No further fields. **verify_ca** | Field | Type | Required | Description | | --- | --- | --- | --- | | **CA certificate** | string, secret | Yes | | | **Client certificate File** | string, secret | No | Client certificate (this is not a required field, but if you want to use it, you will need to add the Client key as well). | | **Client Key** | string, secret | No | Client key (this is not a required field, but if you want to use it, you will need to add the Client certificate as well). | | **Client key password** | string, secret | No | Password for keystorage. This field is optional. If you do not add it, the password will be generated automatically. | **verify_identity** | Field | Type | Required | Description | | --- | --- | --- | --- | | **CA certificate** | string, secret | Yes | | | **Client certificate File** | string, secret | No | Client certificate (this is not a required field, but if you want to use it, you will need to add the Client key as well). | | **Client Key** | string, secret | No | Client key (this is not a required field, but if you want to use it, you will need to add the Client certificate as well). | | **Client key password** | string, secret | No | Password for keystorage. This field is optional. If you do not add it, the password will be generated automatically. | ### SSH Tunnel Method Whether to initiate an SSH tunnel before connecting to the database, and if so, which kind of authentication to use. Choose one of the following. Each asks for its own fields. **No Tunnel** No further fields. **SSH Key Authentication** | Field | Type | Required | Description | | --- | --- | --- | --- | | **SSH Tunnel Jump Server Host** | string | Yes | Hostname of the jump server host that allows inbound ssh tunnel. | | **SSH Connection Port** | integer | Yes | Port on the proxy/jump server that accepts inbound ssh connections. Default `22`. | | **SSH Login Username** | string | Yes | OS-level username for logging into the jump server host. | | **SSH Private Key** | string, secret | Yes | OS-level user account ssh key credentials in RSA PEM format ( created with ssh-keygen -t rsa -m PEM -f myuser_rsa ). | **Password Authentication** | Field | Type | Required | Description | | --- | --- | --- | --- | | **SSH Tunnel Jump Server Host** | string | Yes | Hostname of the jump server host that allows inbound ssh tunnel. | | **SSH Connection Port** | integer | Yes | Port on the proxy/jump server that accepts inbound ssh connections. Default `22`. | | **SSH Login Username** | string | Yes | OS-level username for logging into the jump server host. | | **Password** | string, secret | Yes | OS-level password for logging into the jump server host. | ### Update Method Configures how data is extracted from the database. Choose one of the following. Each asks for its own fields. **Scan Changes with User Defined Cursor** No further fields. **Read Changes using Change Data Capture (CDC)** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Configured server timezone for the MySQL source (Advanced)** | string | No | Enter the configured MySQL server timezone. This should only be done if the configured timezone in your MySQL instance does not conform to IANNA standard. | | **Invalid CDC Position Behavior (Advanced)** | string | No | Determines whether becca should fail or re-sync data in case of an stale/invalid cursor value in the mined logs. If 'Fail sync' is chosen, syncs fail until becca support resets the connection. If 'Re-sync data' is chosen, becca will automatically trigger a refresh but could lead to higher costs and data loss. One of `Fail sync`, `Re-sync data`. Default `Fail sync`. | | **Initial Load Timeout in Hours (Advanced)** | integer | No | The amount of time an initial load is allowed to continue for before catching up on CDC logs. Default `8`. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # SQL Server > Connect SQL Server to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect SQL Server :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose SQL Server.** It is listed under databases; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Host** | string | Yes | The hostname of the database. | | **Port** | integer | Yes | The port of the database. Default `1433`. | | **Database** | string | Yes | The name of the database. | | **Schemas** | array | No | The list of schemas to sync from. If not specified, all schemas will be discovered. Case sensitive. | | **Username** | string | No | The username which is used to access the database. Not required if Microsoft Entra ID authentication is configured below. | | **Password** | string, secret | No | The password associated with the username. Not required if Microsoft Entra ID authentication is configured below. | | **JDBC URL Params** | string | No | Additional properties to pass to the JDBC URL string when connecting to the database formatted as 'key=value' pairs separated by the symbol '&'. (example: key1=value1&key2=value2&key3=value3). | | **Entra ID Client ID** | string | No | Application (client) ID of a Microsoft Entra ID service principal. When provided together with Client Secret, Entra ID authentication is used instead of username and password. | | **Entra ID Client Secret** | string, secret | No | Client secret for the Microsoft Entra ID service principal. When provided together with Client ID, Entra ID authentication is used instead of username and password. | | **Encryption** | one of 3 options | No | The encryption method which is used when communicating with the database. | | **SSH Tunnel Method** | one of 3 options | No | Whether to initiate an SSH tunnel before connecting to the database, and if so, which kind of authentication to use. | | **Update Method** | one of 2 options | Yes | Configures how data is extracted from the database. | | **Checkpoint Target Time Interval** | integer | No | How often (in seconds) a stream should checkpoint, when possible. Default `300`. | | **Concurrency** | integer | No | Maximum number of concurrent queries to the database. | | **Check Table and Column Access Privileges** | boolean | No | When this feature is enabled, during schema discovery the connector will query each table or view individually to check access privileges and inaccessible tables, views, or columns therein will be removed. In large schemas, this might cause schema discovery to take too long, in which case it might be advisable to disable this feature. Default `true`. | | **AdditionalProperties** | object | No | | ### Encryption The encryption method which is used when communicating with the database. Choose one of the following. Each asks for its own fields. **Unencrypted** No further fields. **Encrypted (trust server certificate)** No further fields. **Encrypted (verify certificate)** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Host Name In Certificate** | string | No | Specifies the host name of the server. The value of this property must match the subject property of the certificate. | | **Certificate** | string, secret | No | Certificate of the server, or of the CA that signed the server certificate. | ### SSH Tunnel Method Whether to initiate an SSH tunnel before connecting to the database, and if so, which kind of authentication to use. Choose one of the following. Each asks for its own fields. **No Tunnel** No further fields. **SSH Key Authentication** | Field | Type | Required | Description | | --- | --- | --- | --- | | **SSH Tunnel Jump Server Host** | string | Yes | Hostname of the jump server host that allows inbound ssh tunnel. | | **SSH Connection Port** | integer | Yes | Port on the proxy/jump server that accepts inbound ssh connections. Default `22`. | | **SSH Login Username** | string | Yes | OS-level username for logging into the jump server host. | | **SSH Private Key** | string, secret | Yes | OS-level user account ssh key credentials in RSA PEM format ( created with ssh-keygen -t rsa -m PEM -f myuser_rsa ). | **Password Authentication** | Field | Type | Required | Description | | --- | --- | --- | --- | | **SSH Tunnel Jump Server Host** | string | Yes | Hostname of the jump server host that allows inbound ssh tunnel. | | **SSH Connection Port** | integer | Yes | Port on the proxy/jump server that accepts inbound ssh connections. Default `22`. | | **SSH Login Username** | string | Yes | OS-level username for logging into the jump server host. | | **Password** | string, secret | Yes | OS-level password for logging into the jump server host. | ### Update Method Configures how data is extracted from the database. Choose one of the following. Each asks for its own fields. **Scan Changes with User Defined Cursor** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Exclude Today's Data** | boolean | No | When enabled incremental syncs using a cursor of a temporal type (date or datetime) will include cursor values only up until the previous midnight UTC. Default `false`. | **Read Changes using Change Data Capture (CDC)** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Initial Waiting Time in Seconds (Advanced)** | integer | No | The amount of time the connector will wait when it launches to determine if there is new data to sync or not. Defaults to 300 seconds. Valid range: 120 seconds to 3600 seconds. | | **Invalid CDC Position Behavior (Advanced)** | string | No | Determines whether becca should fail or re-sync data in case of an stale/invalid cursor value in the mined logs. If 'Fail sync' is chosen, syncs fail until becca support resets the connection. If 'Re-sync data' is chosen, becca will automatically trigger a refresh but could lead to higher costs and data loss. One of `Fail sync`, `Re-sync data`. Default `Fail sync`. | | **Initial Load Timeout in Hours (Advanced)** | integer | No | The amount of time an initial load is allowed to continue for before catching up on CDC logs. Default `8`. | | **Poll Interval in Milliseconds (Advanced)** | integer | No | How often (in milliseconds) Debezium should poll for new data. Must be smaller than heartbeat interval (15000ms). Lower values provide more responsive data capture but may increase database load. Default `500`. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # MongoDB > Connect MongoDB to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect MongoDB :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose MongoDB.** It is listed under databases; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Cluster Type** | one of 2 options | Yes | Configures the MongoDB cluster type. | | **Initial Waiting Time in Seconds (Advanced)** | integer | No | The amount of time the connector will wait when it launches to determine if there is new data to sync or not. Defaults to 300 seconds. Valid range: 120 seconds to 1200 seconds. Default `300`. | | **Size of the queue (Advanced)** | integer | No | The size of the internal queue. This may interfere with memory consumption and efficiency of the connector, please be careful. Default `10000`. | | **Document discovery sample size (Advanced)** | integer | No | The maximum number of documents to sample when attempting to discover the unique fields for a collection. Default `10000`. | | **Document discovery timeout in seconds (Advanced)** | integer | No | The amount of time the connector will wait when it discovers a document. Defaults to 600 seconds. Valid range: 5 seconds to 1200 seconds. Default `600`. | | **Invalid CDC position behavior (Advanced)** | string | No | Determines whether becca should fail or re-sync data in case of an stale/invalid cursor value into the WAL. If 'Fail sync' is chosen, syncs fail until becca support resets the connection. If 'Re-sync data' is chosen, becca will automatically trigger a refresh but could lead to higher costs and data loss. One of `Fail sync`, `Re-sync data`. Default `Fail sync`. | | **Capture mode (Advanced)** | string | No | Determines how becca looks up the value of an updated document. If 'Lookup' is chosen, the current value of the document will be read. If 'Post Image' is chosen, then the version of the document immediately after an update will be read. Warning: Severe data loss will occur if this option is chosen and the appropriate settings are not set on your Mongo instance: https://www.mongodb.com/docs/manual/changeStreams/#change-streams-with-document-pre-and-post-images. One of `Lookup`, `Post Image`. Default `Lookup`. | | **Initial Load Timeout in Hours (Advanced)** | integer | No | The amount of time an initial load is allowed to continue for before catching up on CDC logs. Default `8`. | ### Cluster Type Configures the MongoDB cluster type. Choose one of the following. Each asks for its own fields. **MongoDB Atlas Replica Set** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Connection String** | string | Yes | The connection string of the cluster that you want to replicate. | | **Database Names** | array | Yes | The names of the MongoDB databases that contain the collection(s) to replicate. | | **Username** | string | Yes | The username which is used to access the database. | | **Password** | string, secret | Yes | The password associated with this username. | | **Authentication Source** | string | Yes | The authentication source where the user information is stored. Default `admin`. | | **Schema Enforced** | boolean | No | When enabled, syncs will validate and structure records against the stream's schema. Default `true`. | **Self-Managed Replica Set** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Connection String** | string | Yes | The connection string of the cluster that you want to replicate. | | **Database Names** | array | Yes | The names of the MongoDB databases that contain the collection(s) to replicate. | | **Username** | string | No | The username which is used to access the database. | | **Password** | string, secret | No | The password associated with this username. | | **Authentication Source** | string | No | The authentication source where the user information is stored. Default `admin`. | | **Schema Enforced** | boolean | No | When enabled, syncs will validate and structure records against the stream's schema. Default `true`. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # Oracle > Connect Oracle to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect Oracle :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose Oracle.** It is listed under databases; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Host** | string | Yes | Hostname of the database. | | **Port** | integer | Yes | Port of the database. Oracle recommends 1521, the default listening port for client connections to the listener, or 2484, the registered listening port for client connections using TCP/IP with SSL. Default `1521`. | | **Connect by** | one of 2 options | No | Connect data that will be used for DB connection. | | **User** | string | Yes | The username which is used to access the database. | | **Password** | string, secret | No | The password associated with the username. | | **Schemas** | array | No | The list of schemas to sync from. Defaults to user. Case sensitive. | | **JDBC URL Params** | string | No | Additional properties to pass to the JDBC URL string when connecting to the database formatted as 'key=value' pairs separated by the symbol '&'. (example: key1=value1&key2=value2&key3=value3). | | **Encryption** | one of 3 options | No | The encryption method with is used when communicating with the database. | | **SSH Tunnel Method** | one of 3 options | No | Whether to initiate an SSH tunnel before connecting to the database, and if so, which kind of authentication to use. | ### Connect by Connect data that will be used for DB connection. Choose one of the following. Each asks for its own fields. **Service name** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Service name** | string | Yes | | **System ID (SID)** | Field | Type | Required | Description | | --- | --- | --- | --- | | **System ID (SID)** | string | Yes | | ### Encryption The encryption method with is used when communicating with the database. Choose one of the following. Each asks for its own fields. **Unencrypted** No further fields. **Native Network Encryption (NNE)** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Encryption Algorithm** | string | No | This parameter defines what encryption algorithm is used. One of `AES256`, `RC4_56`, `3DES168`. Default `AES256`. | **TLS Encrypted (verify certificate)** | Field | Type | Required | Description | | --- | --- | --- | --- | | **SSL PEM File** | string, secret | Yes | Privacy Enhanced Mail (PEM) files are concatenated certificate containers frequently used in certificate installations. | ### SSH Tunnel Method Whether to initiate an SSH tunnel before connecting to the database, and if so, which kind of authentication to use. Choose one of the following. Each asks for its own fields. **No Tunnel** No further fields. **SSH Key Authentication** | Field | Type | Required | Description | | --- | --- | --- | --- | | **SSH Tunnel Jump Server Host** | string | Yes | Hostname of the jump server host that allows inbound ssh tunnel. | | **SSH Connection Port** | integer | Yes | Port on the proxy/jump server that accepts inbound ssh connections. Default `22`. | | **SSH Login Username** | string | Yes | OS-level username for logging into the jump server host. | | **SSH Private Key** | string, secret | Yes | OS-level user account ssh key credentials in RSA PEM format ( created with ssh-keygen -t rsa -m PEM -f myuser_rsa ). | **Password Authentication** | Field | Type | Required | Description | | --- | --- | --- | --- | | **SSH Tunnel Jump Server Host** | string | Yes | Hostname of the jump server host that allows inbound ssh tunnel. | | **SSH Connection Port** | integer | Yes | Port on the proxy/jump server that accepts inbound ssh connections. Default `22`. | | **SSH Login Username** | string | Yes | OS-level username for logging into the jump server host. | | **Password** | string, secret | Yes | OS-level password for logging into the jump server host. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # Snowflake > Connect Snowflake to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect Snowflake :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose Snowflake.** It is listed under databases; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Authorization Method** | one of 3 options | No | | | **Server URL** | string | Yes | The host domain of the snowflake instance (must include the account, region, cloud environment, and end with snowflakecomputing.com). | | **Role** | string | Yes | The role you created for becca to access Snowflake. | | **Warehouse** | string | Yes | The warehouse you created for becca to access data. | | **Database** | string | Yes | The database you created for becca to access data. | | **Schema** | string | No | The source Snowflake schema tables. Leave empty to access tables from multiple schemas. | | **JDBC URL Params** | string | No | Additional properties to pass to the JDBC URL string when connecting to the database formatted as 'key=value' pairs separated by the symbol '&'. (example: key1=value1&key2=value2&key3=value3). | | **Update Method** | one of 1 options | No | Configures how data is extracted from the database. | | **Checkpoint Target Time Interval** | integer | No | How often (in seconds) a stream should checkpoint, when possible. Default `300`. | | **Concurrency** | integer | No | Maximum number of concurrent queries to the database. Default `1`. | | **Check Table and Column Access Privileges** | boolean | No | When this feature is enabled, during schema discovery the connector will query each table or view individually to check access privileges and inaccessible tables, views, or columns therein will be removed. In large schemas, this might cause schema discovery to take too long, in which case it might be advisable to disable this feature. Default `true`. | ### Authorization Method Choose one of the following. Each asks for its own fields. **Key Pair Authentication** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Username** | string | Yes | The username you created to allow becca to access the database. | | **Private Key** | string, secret | Yes | RSA Private key to use for Snowflake connection. | | **Passphrase** | string, secret | No | Passphrase for private key. | **Username and Password (Deprecated)** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Username** | string | Yes | The username you created to allow becca to access the database. | | **Password** | string, secret | Yes | Deprecated. The password associated with the username. | **Programmatic Access Token** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Programmatic Access Token** | string, secret | Yes | The programmatic access token used to authenticate to Snowflake. | ### Update Method Configures how data is extracted from the database. Choose one of the following. Each asks for its own fields. **Scan Changes with User Defined Cursor** No further fields. ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # BigQuery > Connect BigQuery to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect BigQuery :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose BigQuery.** It is listed under databases; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Credentials JSON** | string, secret | Yes | The contents of your Service Account Key JSON file. | | **Default Dataset ID** | string | No | The dataset ID to search for tables and views. If you are only loading data from one dataset, setting this option could result in much faster schema discovery. | | **Project ID** | string | Yes | The GCP project ID for the project containing the target BigQuery dataset. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # Redshift > Connect Redshift to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect Redshift :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose Redshift.** It is listed under databases; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Host** | string | Yes | Host Endpoint of the Redshift Cluster (must include the cluster-id, region and end with .redshift.amazonaws.com). | | **Port** | integer | Yes | Port of the database. Default `5439`. | | **Database** | string | Yes | Name of the database. | | **Schemas** | array | No | The list of schemas to sync from. Specify one or more explicitly or keep empty to process all schemas. Schema names are case sensitive. | | **Username** | string | Yes | Username to use to access the database. | | **Password** | string, secret | Yes | Password associated with the username. | | **JDBC URL Params** | string | No | Additional properties to pass to the JDBC URL string when connecting to the database formatted as 'key=value' pairs separated by the symbol '&'. (example: key1=value1&key2=value2&key3=value3). | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # ClickHouse > Connect ClickHouse to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect ClickHouse :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose ClickHouse.** It is listed under databases; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Host** | string | Yes | The host endpoint of the Clickhouse cluster. | | **Port** | integer | Yes | The port of the database. Default `8123`. | | **Database** | string | Yes | The name of the database. | | **Username** | string | Yes | The username which is used to access the database. | | **Password** | string, secret | No | The password associated with this username. | | **JDBC URL Parameters (Advanced)** | string | No | Additional properties to pass to the JDBC URL string when connecting to the database formatted as 'key=value' pairs separated by the symbol '&'. (Eg. key1=value1&key2=value2&key3=value3). | | **SSL Connection** | boolean | No | Encrypt data using SSL. Default `true`. | | **SSH Tunnel Method** | one of 3 options | No | Whether to initiate an SSH tunnel before connecting to the database, and if so, which kind of authentication to use. | ### SSH Tunnel Method Whether to initiate an SSH tunnel before connecting to the database, and if so, which kind of authentication to use. Choose one of the following. Each asks for its own fields. **No Tunnel** No further fields. **SSH Key Authentication** | Field | Type | Required | Description | | --- | --- | --- | --- | | **SSH Tunnel Jump Server Host** | string | Yes | Hostname of the jump server host that allows inbound ssh tunnel. | | **SSH Connection Port** | integer | Yes | Port on the proxy/jump server that accepts inbound ssh connections. Default `22`. | | **SSH Login Username** | string | Yes | OS-level username for logging into the jump server host. | | **SSH Private Key** | string, secret | Yes | OS-level user account ssh key credentials in RSA PEM format ( created with ssh-keygen -t rsa -m PEM -f myuser_rsa ). | **Password Authentication** | Field | Type | Required | Description | | --- | --- | --- | --- | | **SSH Tunnel Jump Server Host** | string | Yes | Hostname of the jump server host that allows inbound ssh tunnel. | | **SSH Connection Port** | integer | Yes | Port on the proxy/jump server that accepts inbound ssh connections. Default `22`. | | **SSH Login Username** | string | Yes | OS-level username for logging into the jump server host. | | **Password** | string, secret | Yes | OS-level password for logging into the jump server host. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # DynamoDB > Connect DynamoDB to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect DynamoDB :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose DynamoDB.** It is listed under databases; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Credentials** | one of 2 options | No | Credentials for the service. | | **Dynamodb Endpoint** | string | No | The URL of the Dynamodb database. | | **Ignore missing read permissions tables** | boolean | No | Ignore tables with missing scan/read permissions. Default `false`. | | **Dynamodb Region** | string | No | The region of the Dynamodb database. One of ``, `af-south-1`, `ap-east-1`, `ap-northeast-1`, `ap-northeast-2`, `ap-northeast-3`, `ap-south-1`, `ap-south-2`, `ap-southeast-1`, `ap-southeast-2`, `ap-southeast-3`, `ap-southeast-4`, `ca-central-1`, `ca-west-1`, `cn-north-1`, `cn-northwest-1`, `eu-central-1`, `eu-central-2`, `eu-north-1`, `eu-south-1`, `eu-south-2`, `eu-west-1`, `eu-west-2`, `eu-west-3`, `il-central-1`, `me-central-1`, `me-south-1`, `sa-east-1`, `us-east-1`, `us-east-2`, `us-gov-east-1`, `us-gov-west-1`, `us-west-1`, `us-west-2`. | | **Reserved attribute names** | string, secret | No | Comma separated reserved attribute names present in your tables. | ### Credentials Credentials for the service. Choose one of the following. Each asks for its own fields. **Authenticate via Access Keys** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Dynamodb Key Id** | string, secret | Yes | The access key id to access Dynamodb. becca requires read permissions to the database. | | **Dynamodb Access Key** | string, secret | Yes | The corresponding secret to the access key id. | **Role Based Authentication** No further fields. ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # Elasticsearch > Connect Elasticsearch to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect Elasticsearch :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose Elasticsearch.** It is listed under databases; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Authentication Method** | one of 3 options | No | The type of authentication to be used. | | **Server Endpoint** | string | Yes | The full url of the Elasticsearch server. | ### Authentication Method The type of authentication to be used. Choose one of the following. Each asks for its own fields. **None** No further fields. **Api Key/Secret** | Field | Type | Required | Description | | --- | --- | --- | --- | | **API Key ID** | string | Yes | The Key ID to used when accessing an enterprise Elasticsearch instance. | | **API Key Secret** | string, secret | Yes | The secret associated with the API Key ID. | **Username/Password** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Password** | string, secret | Yes | Basic auth password to access a secure Elasticsearch server. | | **Username** | string | Yes | Basic auth username to access a secure Elasticsearch server. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # CockroachDB > Connect CockroachDB to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect CockroachDB :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose CockroachDB.** It is listed under databases; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Host** | string | Yes | Hostname of the database. | | **Port** | integer | Yes | Port of the database. Default `5432`. | | **DB Name** | string | Yes | Name of the database. | | **User** | string | Yes | Username to use to access the database. | | **Password** | string, secret | No | Password associated with the username. | | **JDBC URL Parameters (Advanced)** | string | No | Additional properties to pass to the JDBC URL string when connecting to the database formatted as 'key=value' pairs separated by the symbol '&'. (Eg. key1=value1&key2=value2&key3=value3). | | **Connect using SSL** | boolean | No | Encrypt client/server communications for increased security. Default `false`. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # CSV / files > Read a file from a web address or cloud storage into one table in your warehouse, and read it again on every sync. This connector reads one file from a location that stays the same, and reads it again on every sync. If you have a file that will not change, or that you would rather drop in by hand, use [Load a CSV](https://docs.sanda-os.com.au/connections/upload-a-csv) instead. It puts the file straight into your warehouse with no connector and no schedule. ## Before you start - **A stable location.** sanda reads the same address on every sync, so a fixed link to a file that gets replaced beats an email attachment. Each run reads the whole file, so the file should always hold the current data. - **Access that matches where it lives.** A file on the public web needs nothing but its address, and it must open without signing in. A private file needs one of the other **Storage Provider** options and the credentials that go with it: | Storage Provider | What it asks for | |---|---| | **HTTPS: Public Web** | Nothing. The address must open without signing in | | **GCS: Google Cloud Storage** | **Service Account JSON**, the whole key file pasted in | | **S3: Amazon** | **Access Key ID** and **Secret Access Key** | | **AzBlob: Azure Blob Storage** | **Storage Account**, plus a **SAS Token** or a **Shared Key** | | **SSH: Secure Shell**, **SCP: Secure copy protocol**, **SFTP: Secure File Transfer Protocol** | **Host**, **User**, **Password** and **Port** (22 unless you changed it) | - **Read-only credentials.** For a bucket, create credentials that can read only that bucket. sanda only ever reads. - **sanda's address allowed**, for a file server or a bucket that restricts access by address. See [Allowlist sanda's address](https://docs.sanda-os.com.au/connections/allowlist). ## Connect CSV / files :::steps 1. **Choose CSV / files.** Go to **Data · Connections**, press **New connection** and pick **CSV / files**. 2. **Name the table.** **Dataset Name** becomes the name of the table the file lands in. Use letters, numbers, dashes and underscores only. 3. **Give the address.** Put the full address in **URL**, including its scheme. `https://example.com/data.csv`, `gs://my-bucket/data.csv` and `s3://my-bucket/data.csv` are the shapes the form expects. 4. **Set the format and location if they are not the defaults.** Under **Advanced settings**, **File Format** opens on `csv` and **Storage Provider** opens on **HTTPS: Public Web**. Change them to match your file, and fill in the credentials the provider asks for. 5. **Continue.** Press **Continue**, name the connection, and press **Read schema**. sanda opens the file and reads its columns. If it fails, the message says why. See [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting). ::: ## What syncs One file becomes one stream, and lands in your warehouse as one table named after the connection and **Dataset Name**. **File Format** accepts `csv`, `json`, `jsonl`, `excel`, `excel_binary`, `fwf`, `feather`, `parquet` and `yaml`. The form warns that some formats may be experimental. A file carries no record of what changed, so each run reads it in full, and the stream uses a full refresh mode. The default, **Full refresh · Overwrite**, replaces the table on every run. See [sync modes](https://docs.sanda-os.com.au/connections/sync-modes). ## Tips - **Use Reader Options for an unusual layout.** **Reader Options** takes a JSON object of settings for the chosen format. For a semicolon separated file, enter `{"sep": ";"}`. - **Header row.** By default the first row is read as the column names. If your file has none, set **Reader Options** to `{"header": null}`, or the first record is lost. - **Many files, or a folder?** Use a connector that reads by pattern: [S3 / object storage](https://docs.sanda-os.com.au/connectors/s3), [Google Cloud Storage](https://docs.sanda-os.com.au/connectors/gcs), [Azure Blob Storage](https://docs.sanda-os.com.au/connectors/azure_blob_storage) or [SFTP](https://docs.sanda-os.com.au/connectors/sftp). - **A spreadsheet rather than a file?** [Google Sheets](https://docs.sanda-os.com.au/connectors/google_sheets) reads each tab as a table. ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Dataset Name** | string | Yes | The Name of the final table to replicate this file into (should include letters, numbers dash and underscores only). | | **File Format** | string | Yes | The Format of the file which should be replicated (Warning: some formats may be experimental). One of `csv`, `json`, `jsonl`, `excel`, `excel_binary`, `fwf`, `feather`, `parquet`, `yaml`. Default `csv`. | | **Storage Provider** | one of 8 options | Yes | The storage Provider or Location of the file(s) which should be replicated. | | **Reader Options** | string | No | This should be a string in JSON format. It depends on the chosen file format to provide additional options and tune its behavior. | | **URL** | string | Yes | The URL path to access the file which should be replicated. | ### Storage Provider The storage Provider or Location of the file(s) which should be replicated. Choose one of the following. Each asks for its own fields. **HTTPS: Public Web** | Field | Type | Required | Description | | --- | --- | --- | --- | | **User-Agent** | boolean | No | Add User-Agent to request. Default `false`. | **GCS: Google Cloud Storage** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Service Account JSON** | string, secret | No | In order to access private Buckets stored on Google Cloud, this connector would need a service account json credentials with the proper permissions. Please generate the credentials.json file and copy/paste its content to this field (expecting JSON formats). If accessing publicly available data, this field is not necessary. | **S3: Amazon** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Access Key ID** | string | No | In order to access private Buckets stored on Amazon S3, this connector would need credentials with the proper permissions. If accessing publicly available data, this field is not necessary. | | **Secret Access Key** | string, secret | No | In order to access private Buckets stored on Amazon S3, this connector would need credentials with the proper permissions. If accessing publicly available data, this field is not necessary. | **AzBlob: Azure Blob Storage** | Field | Type | Required | Description | | --- | --- | --- | --- | | **SAS Token** | string, secret | No | To access Azure Blob Storage, this connector would need credentials with the proper permissions. One option is a SAS (Shared Access Signature) token. If accessing publicly available data, this field is not necessary. | | **Shared Key** | string, secret | No | To access Azure Blob Storage, this connector would need credentials with the proper permissions. One option is a storage account shared key (aka account key or access key). If accessing publicly available data, this field is not necessary. | | **Storage Account** | string | Yes | The globally unique name of the storage account that the desired blob sits within. | **SSH: Secure Shell** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Host** | string | Yes | | | **Password** | string, secret | No | | | **Port** | string | No | Default `22`. | | **User** | string | Yes | | **SCP: Secure copy protocol** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Host** | string | Yes | | | **Password** | string, secret | No | | | **Port** | string | No | Default `22`. | | **User** | string | Yes | | **SFTP: Secure File Transfer Protocol** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Host** | string | Yes | | | **Password** | string, secret | No | | | **Port** | string | No | Default `22`. | | **User** | string | Yes | | **Local Filesystem (limited)** No further fields. ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # S3 / object storage > Connect S3 / object storage to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect S3 / object storage :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose S3 / object storage.** It is listed under files and storage; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Bucket** | string | Yes | Name of the S3 bucket where the file(s) exist. | | **Start Date** | string | No | UTC date and time in the format 2017-01-25T00:00:00.000000Z. Any file modified before this date will not be replicated. | | **Access Key ID** | string, secret | No | In order to access private Buckets stored on Amazon S3, this connector requires credentials with the proper permissions. If accessing publicly available data, this field is not necessary. | | **Secret Access Key** | string, secret | No | In order to access private Buckets stored on Amazon S3, this connector requires credentials with the proper permissions. If accessing publicly available data, this field is not necessary. | | **Endpoint** | string | No | Endpoint to an S3 compatible service. Leave empty to use Amazon S3. | | **Region** | string | No | Region where the S3 bucket is located. If not provided, the region will be determined automatically. | | **Delivery Method** | one of 2 options | No | | | **Role ARN** | string | No | Specifies the Amazon Resource Name (ARN) of an IAM role that you want to use to perform operations requested using this profile. | | **The list of streams to sync** | array | Yes | Each item defines a stream: which files belong in it, their format, and how they are parsed and validated. Each stream lands in your warehouse as its own table. | ### Delivery Method Choose one of the following. Each asks for its own fields. **Replicate Records** No further fields. **Copy Raw Files** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Preserve Sub-Directories in File Paths** | boolean | No | If enabled, sends subdirectory folder structure along with source file names to the destination. Otherwise, files will be synced by their names only. This option is ignored when file-based replication is not enabled. Default `true`. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # Google Sheets > Bring the tabs of a Google spreadsheet into your warehouse, one table per tab, read again on every sync. ## Before you start You need the spreadsheet's link, and a way for sanda to sign in to Google as something that can see it. There are two, and the **Authentication** field asks you to pick one. It opens on **Service Account Key Authentication**. A service account is the easier of the two to keep running. It is a Google identity for software, and it can only see the spreadsheets that someone has shared with it. To set one up: :::steps 1. **Prepare a Google Cloud project.** In the Google Cloud console, create a project or choose an existing one, and enable the Google Sheets API for it. 2. **Create the service account.** Create a service account in that project, add a JSON key to it, and download the key file. 3. **Share the spreadsheet with it.** The key file holds an email address for the service account, in its `client_email` entry. Share the spreadsheet with that address, with **Viewer** access. ::: :::note Some organisations stop people creating service account keys, by policy. If Google Cloud refuses to create the key, ask whoever administers your Google Cloud organisation, or use the OAuth option below. ::: The other option, **Authenticate via Google (OAuth)**, needs your own Google OAuth application with the read-only Sheets and Drive scopes (`spreadsheets.readonly` and `drive.readonly`). It gives you a **Client ID** and **Client Secret**, and you obtain a **Refresh Token** by completing Google's consent screen once. Choose it only if you already have those, or your organisation does not allow service account keys. ## Connect Google Sheets :::steps 1. **Choose Google Sheets.** Go to **Data · Connections**, press **New connection** and pick **Google Sheets**. 2. **Paste the spreadsheet link.** In Google Sheets, press **Share** at the top right of the spreadsheet, then **Copy link**, and paste it into **Spreadsheet Link**. 3. **Sign in.** Under **Authentication**, choose **Service Account Key Authentication**. For the OAuth option, fill in **Client ID**, **Client Secret** and **Refresh Token** instead. With a service account, paste the whole JSON key file into **Service Account Information.** 4. **Continue.** Press **Continue**, name the connection, and press **Read schema**. sanda signs in and lists the tabs it can see. If it fails, the message says why. See [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting). ::: The rest of the flow, naming the connection, setting a frequency and choosing streams, is the same for every source. See [Add a connection](https://docs.sanda-os.com.au/connections/add-a-connection). ## What syncs Each tab of the spreadsheet becomes its own stream, and lands in your warehouse as its own table. The first row of a tab is read as its column names, and the rows below it are the data. Google Sheets does not tell sanda which rows changed, so sanda reads each tab in full on every run, and its streams use a full refresh mode. See [sync modes](https://docs.sanda-os.com.au/connections/sync-modes). ## Tips - **Keep a header row, with no gaps.** Column names become field names in your warehouse. By default sanda stops reading columns at the first empty header cell. Turn on **Read Empty Header Columns** under **Advanced settings** if you have blank headers with data beneath them. - **Tidy the column names.** **Convert Column Names to SQL-Compliant Format**, under **Advanced settings**, turns names such as `Order Total` into `order_total`. It is off by default. The options beneath it refine how it treats numbers, letters and special characters. - **Rename tabs on the way in.** **Stream Name Overrides** takes a JSON list. Each item has a `source_stream_name`, the exact name of the tab, and a `custom_stream_name`, the name you want. - **Many tabs take longer.** Google limits how fast its Sheets API can be read. More **Number of Concurrent Threads** speeds up a spreadsheet with many tabs, but can hit that limit. - **One spreadsheet per connection.** To bring in several spreadsheets, add a connection for each. ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Spreadsheet Link** | string | Yes | Enter the link to the Google spreadsheet you want to sync. To copy the link, click the 'Share' button in the top-right corner of the spreadsheet, then click 'Copy link'. | | **Row Batch Size** | integer | No | Default value is 1000000. An integer representing row batch size for each sent request to Google Sheets API. Row batch size means how many rows are processed from the google sheet, for example default value 1000000 would process rows 2-1000002, then 1000003-2000003 and so on. Based on Google Sheets API limits documentation, it is possible to send up to 300 requests per minute, but each individual request has to be processed under 180 seconds, otherwise the request returns a timeout error. In regards to this information, consider network speed and number of columns of the google sheet when deciding a batch_size value. Default `1000000`. | | **Convert Column Names to SQL-Compliant Format** | boolean | No | Converts column names to a SQL-compliant format (snake_case, lowercase, etc). If enabled, you can further customize the sanitization using the options below. Default `false`. | | **Remove Leading and Trailing Underscores** | boolean | No | Removes leading and trailing underscores from column names. Does not remove leading underscores from column names that start with a number. Example: "50th Percentile? "→ "_50_th_percentile" This option will only work if "Convert Column Names to SQL-Compliant Format (names_conversion)" is enabled. Default `false`. | | **Combine Number-Word Pairs** | boolean | No | Combines adjacent numbers and words. Example: "50th Percentile?" → "_50th_percentile_" This option will only work if "Convert Column Names to SQL-Compliant Format (names_conversion)" is enabled. Default `false`. | | **Remove All Special Characters** | boolean | No | Removes all special characters from column names. Example: "Example ID*" → "example_id" This option will only work if "Convert Column Names to SQL-Compliant Format (names_conversion)" is enabled. Default `false`. | | **Combine Letter-Number Pairs** | boolean | No | Combines adjacent letters and numbers. Example: "Q3 2023" → "q3_2023" This option will only work if "Convert Column Names to SQL-Compliant Format (names_conversion)" is enabled. Default `false`. | | **Allow Leading Numbers** | boolean | No | Allows column names to start with numbers. Example: "50th Percentile" → "50_th_percentile" This option will only work if "Convert Column Names to SQL-Compliant Format (names_conversion)" is enabled. Default `false`. | | **Read Empty Header Columns** | boolean | No | When enabled, the connector will continue reading columns after empty header cells and will include data from those columns using generated column names (e.g., "column_C"). By default, the connector stops reading columns when it encounters an empty header cell. Default `false`. | | **Number of Concurrent Threads** | integer | No | Number of concurrent threads for syncing. Higher values can speed up syncs for spreadsheets with multiple sheets, but may hit rate limits. Google Sheets API limits to 300 read requests per minute (~5 req/sec). Default `2`. | | **Authentication** | one of 2 options | Yes | Credentials for connecting to the Google Sheets API. | | **Stream Name Overrides** | array | No | Renames streams (Google Sheets tab names) as they appear in becca. Each item is an object with a `source_stream_name` (the exact name of the tab in your spreadsheet) and a `custom_stream_name` (the name you want it to have). A `source_stream_name` that is not found in your spreadsheet is ignored and the default name is used. This only renames streams, not fields or columns. Leave it blank to keep every tab name. | ### Authentication Credentials for connecting to the Google Sheets API. Choose one of the following. Each asks for its own fields. **Authenticate via Google (OAuth)** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Client ID** | string, secret | Yes | Enter your Google application's Client ID. | | **Client Secret** | string, secret | Yes | Enter your Google application's Client Secret. | | **Refresh Token** | string, secret | Yes | Enter your Google application's refresh token. | **Service Account Key Authentication** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Service Account Information.** | string, secret | Yes | The JSON key of the service account to use for authorization. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # Google Drive > Connect Google Drive to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect Google Drive :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose Google Drive.** It is listed under files and storage; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Folder Url** | string | Yes | URL for the folder you want to sync. Using individual streams and glob patterns, it's possible to only sync a subset of all files located in the folder. | | **Delivery Method** | one of 3 options | No | | | **Start Date** | string | No | UTC date and time in the format 2017-01-25T00:00:00.000000Z. Any file modified before this date will not be replicated. | | **The list of streams to sync** | array | Yes | Each item defines a stream: which files belong in it, their format, and how they are parsed and validated. Each stream lands in your warehouse as its own table. | | **Authentication** | one of 2 options | Yes | Credentials for connecting to the Google Drive API. | ### Delivery Method Choose one of the following. Each asks for its own fields. **Replicate Records** No further fields. **Copy Raw Files** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Preserve Sub-Directories in File Paths** | boolean | No | If enabled, sends subdirectory folder structure along with source file names to the destination. Otherwise, files will be synced by their names only. This option is ignored when file-based replication is not enabled. Default `true`. | **Replicate Permissions ACL** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Domain** | string | No | The Google domain of the identities. | | **Include Identity Stream** | boolean | No | This data can be used in downstream systems to recreate permission restrictions mirroring the original source. Default `true`. | ### Authentication Credentials for connecting to the Google Drive API. Choose one of the following. Each asks for its own fields. **Authenticate via Google (OAuth)** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Client ID** | string, secret | Yes | Client ID for the Google Drive API. | | **Client Secret** | string, secret | Yes | Client Secret for the Google Drive API. | | **Refresh Token** | string, secret | Yes | Refresh Token for the Google Drive API. | **Service Account Key Authentication** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Service Account Information** | string, secret | Yes | The JSON key of the service account to use for authorization. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # Azure Blob Storage > Connect Azure Blob Storage to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect Azure Blob Storage :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose Azure Blob Storage.** It is listed under files and storage; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Start Date** | string | No | UTC date and time in the format 2017-01-25T00:00:00.000000Z. Any file modified before this date will not be replicated. | | **Authentication** | one of 3 options | Yes | Credentials for connecting to the Azure Blob Storage. | | **Azure Blob Storage account name** | string | Yes | The account's name of the Azure Blob Storage. | | **Azure blob storage container (Bucket) Name** | string | Yes | The name of the Azure blob storage container. | | **Delivery Method** | one of 2 options | No | | | **The list of streams to sync** | array | Yes | Each item defines a stream: which files belong in it, their format, and how they are parsed and validated. Each stream lands in your warehouse as its own table. | | **Endpoint Domain Name** | string | No | This is Azure Blob Storage endpoint domain name. Leave default value (or leave it empty if run container from command line) to use Microsoft native from example. | ### Authentication Credentials for connecting to the Azure Blob Storage. Choose one of the following. Each asks for its own fields. **Authenticate via Oauth2** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Client ID** | string, secret | Yes | Client ID of your Microsoft developer application. | | **Client Secret** | string, secret | Yes | Client Secret of your Microsoft developer application. | | **Refresh Token** | string, secret | Yes | Refresh Token of your Microsoft developer application. | | **Tenant ID** | string, secret | Yes | Tenant ID of the Microsoft Azure Application user. | **Authenticate via Client Credentials** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Client ID** | string, secret | Yes | Client ID of your Microsoft developer application. | | **Client Secret** | string, secret | Yes | Client Secret of your Microsoft developer application. | | **Tenant ID** | string, secret | Yes | Tenant ID of the Microsoft Azure Application. | **Authenticate via Storage Account Key** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Azure Blob Storage account key** | string, secret | Yes | The Azure blob storage account key. | ### Delivery Method Choose one of the following. Each asks for its own fields. **Replicate Records** No further fields. **Copy Raw Files** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Preserve Sub-Directories in File Paths** | boolean | No | If enabled, sends subdirectory folder structure along with source file names to the destination. Otherwise, files will be synced by their names only. This option is ignored when file-based replication is not enabled. Default `true`. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # SFTP > Connect SFTP to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect SFTP :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose SFTP.** It is listed under files and storage; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Start Date** | string | No | UTC date and time in the format 2017-01-25T00:00:00.000000Z. Any file modified before this date will not be replicated. | | **Host Address** | string | Yes | The server host address. | | **User Name** | string | Yes | The server user. | | **Authentication** | one of 2 options | Yes | Credentials for connecting to the SFTP Server. | | **Host Address** | integer | No | The server port. Default `22`. | | **Folder Path** | string | No | The directory to search files for sync. Default `/`. | | **Delivery Method** | one of 2 options | No | | | **The list of streams to sync** | array | Yes | Each item defines a stream: which files belong in it, their format, and how they are parsed and validated. Each stream lands in your warehouse as its own table. | ### Authentication Credentials for connecting to the SFTP Server. Choose one of the following. Each asks for its own fields. **Authenticate via Password** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Password** | string, secret | Yes | | **Authenticate via Private Key** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Private key** | string, secret | Yes | The Private key. | ### Delivery Method Choose one of the following. Each asks for its own fields. **Replicate Records** No further fields. **Copy Raw Files** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Preserve Sub-Directories in File Paths** | boolean | No | If enabled, sends subdirectory folder structure along with source file names to the destination. Otherwise, files will be synced by their names only. This option is ignored when file-based replication is not enabled. Default `true`. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # Google Cloud Storage > Connect Google Cloud Storage to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect Google Cloud Storage :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose Google Cloud Storage.** It is listed under files and storage; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Authentication** | one of 2 options | Yes | Credentials for connecting to the Google Cloud Storage API. | | **Start Date** | string | No | UTC date and time in the format 2017-01-25T00:00:00.000000Z. Any file modified before this date will not be replicated. | | **Bucket** | string | Yes | Name of the GCS bucket where the file(s) exist. | | **Sanitize File URLs** | boolean | No | Deprecated: this option has no effect. Default `false`. | | **The list of streams to sync** | array | Yes | Each item defines a stream: which files belong in it, their format, and how they are parsed and validated. Each stream lands in your warehouse as its own table. | ### Authentication Credentials for connecting to the Google Cloud Storage API. Choose one of the following. Each asks for its own fields. **Authenticate via Google (OAuth)** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Access Token** | string, secret | Yes | | | **Client ID** | string, secret | Yes | | | **Client Secret** | string, secret | Yes | | | **Access Token** | string, secret | Yes | | **Service Account Authentication.** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Service Account Information.** | string, secret | Yes | Enter your Google Cloud service account key in JSON format. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # Stripe > Bring customers, invoices, charges, subscriptions and the rest of your Stripe account into your warehouse, with history. ## Before you start You need two things from Stripe: your account ID, and an API key that can read your data. - **A restricted key.** sanda only ever reads, so create a restricted key rather than using your full secret key. In the Stripe Dashboard, open **Developers**, then **API keys**, and create a restricted key. Give it **Read** on every available permission and nothing more, and choose what to sync in sanda instead. Copy the key when Stripe shows it, because Stripe does not show it again. A restricted key starts with `rk_live_`. - **Your account ID.** It starts with `acct_` and is shown in your Stripe account settings. - **Permission to create keys.** Ask the owner of the Stripe account if you cannot see the **API keys** page. A key from test mode reads your test data, not your live data. Use a live key for the real account. ## Connect Stripe :::steps 1. **Choose Stripe.** Go to **Data · Connections**, press **New connection** and pick **Stripe**. 2. **Enter the account.** Put your account ID, the one starting `acct_`, in **Account ID**. 3. **Enter the key.** Paste the restricted key into **Secret Key**. The form's hint mentions `sk_live_`, the start of a full secret key, but a restricted key works in the same field. 4. **Choose how far back to load.** Under **Advanced settings**, **Replication start date** opens on 2017-01-25T00:00:00Z, and only data generated after that date is loaded. Enter a later date, in UTC, to load less history and finish the first run sooner. 5. **Continue.** Press **Continue**, name the connection, and press **Read schema**. sanda signs in and lists the Stripe objects it can read. If it fails, the message says why. See [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting). ::: The rest of the flow, naming the connection, setting a frequency and choosing streams, is the same for every source. See [Add a connection](https://docs.sanda-os.com.au/connections/add-a-connection). ## What syncs Stripe's objects each become a stream, and land in your warehouse as their own tables. The usual starting points are customers, invoices, charges, subscriptions and refunds. The stream list shows what the connection offers, and you choose which to sync. ## Tips - **Amounts are in the smallest currency unit.** For most currencies, including Australian dollars, an amount of 1050 means 10.50 in that currency. Divide by 100 when you model revenue. - **Catch late restatements with a lookback.** **Lookback Window in days** re-reads the last N days on every run, for streams that cannot read Stripe's event log: `Events`, `SetupAttempts`, `ShippingRates`, `BalanceTransactions`, `Files`, `FileLinks` and `Refunds`. A few days is a sensible start if your reports rely on figures Stripe restates after the fact. - **A long pause can lose changes.** Some incremental reads follow Stripe's event log, and Stripe keeps events for 30 days. If a connection stays paused for longer, the streams you list under **Streams with API Data Retention Validation** do a full refresh on their next run, in place of reading a log that no longer reaches back far enough. - **Stripe's rate limits apply.** **Max number of API calls per second** caps sanda's requests. When you leave it empty, sanda uses 25 calls a second for test keys and 100 for live keys. ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Account ID** | string | Yes | Your Stripe account ID (starts with 'acct_'). | | **Secret Key** | string, secret | Yes | Stripe API key (usually starts with 'sk_live_'). | | **Replication start date** | string | No | UTC date and time in the format 2017-01-25T00:00:00Z. Only data generated after this date will be replicated. Default `2017-01-25T00:00:00Z`. | | **Lookback Window in days** | integer | No | When set, the connector will always re-export data from the past N days, where N is the value set here. This is useful if your data is frequently updated after creation. The Lookback Window only applies to streams that do not support event-based incremental syncs: Events, SetupAttempts, ShippingRates, BalanceTransactions, Files, FileLinks, Refunds. Default `0`. | | **Data request time increment in days** | integer | No | The time increment used by the connector when requesting data from the Stripe API. The bigger the value is, the less requests will be made and faster the sync will be. On the other hand, the more seldom the state is persisted. Default `365`. | | **Number of concurrent threads** | integer | No | The number of worker thread to use for the sync. The performance upper boundary depends on call_rate_limit setting and type of account. Default `10`. | | **Streams with API Data Retention Validation** | array | No | Select streams where cursor age is validated against the Stripe API 30-day event retention period. When a selected stream's cursor is older than 30 days, the connector performs a full refresh to avoid missing data. Streams not selected here will always use incremental sync regardless of cursor age. | | **Max number of API calls per second** | integer | No | The number of API calls per second that you allow connector to make. This value can not be bigger than real API call rate limit (https://stripe.com/docs/rate-limits). If not specified the default maximum is 25 calls per second for test/sandbox tokens and 100 for production tokens. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # Xero > Bring invoices, contacts, payments and the rest of your Xero organisation's accounting data into your warehouse. ## Before you start Xero calls an organisation a tenant. To connect one, you create a custom connection in Xero's developer portal, which gives sanda a client ID and a client secret for that single organisation, and you look up the organisation's tenant ID. - **A role that can authorise it.** You need to be a Xero adviser or admin for the organisation, because the organisation has to consent to the connection. - **A custom connection.** In Xero's developer portal, create a new app and choose the **Custom connection** type. Grant it read-only accounting scopes for the areas you want in sanda, such as invoices, contacts, payments and settings, then authorise it against your organisation. Xero treats custom connections as a premium option and may charge for them, so check its current terms before you create one. - **A client ID and secret.** Once the connection is authorised, Xero shows the app's client ID and lets you generate a client secret. Xero shows the secret only once, so copy it straight away. - **The tenant ID.** See the steps below. ### Find the tenant ID The tenant ID is the organisation's identifier in Xero's API. With a custom connection you can read it from Xero's connections endpoint. First ask for an access token, using the client ID and secret: ```bash title="Get an access token" curl -s -u "CLIENT_ID:CLIENT_SECRET" \ -d "grant_type=client_credentials" \ -d "scope=YOUR_SCOPES" \ https://identity.xero.com/connect/token ``` Replace `YOUR_SCOPES` with the scopes you granted the app, separated by spaces. The reply holds an `access_token`. Then list the connected organisations with it: ```bash title="List the connected organisation" curl -s https://api.xero.com/connections \ -H "Authorization: Bearer ACCESS_TOKEN" ``` The `tenantId` in the reply is the value the form asks for. Access tokens last 30 minutes, so this token is only for looking up the tenant ID. sanda gets its own from the client ID and secret. ## Connect Xero :::steps 1. **Choose Xero.** Go to **Data · Connections**, press **New connection** and pick **Xero**. 2. **Enter the tenant ID.** Paste it into **Tenant ID**. The field is masked, like a password. 3. **Choose how far back to load.** Enter a UTC date and time in **Start Date**, in the form `2022-03-01T00:00:00Z`. Xero data created before it is not synced. The start of your financial year, or the day you began using Xero, are common choices. 4. **Choose the authentication method.** **Authentication Method** opens on **Bearer Access Token**. Change it to **OAuth Custom Connection** and enter **Client ID** and **Client Secret**. See the tip below. 5. **Continue.** Press **Continue**, name the connection, and press **Read schema**. sanda signs in and lists the areas of Xero it can read. If it fails, the message says why. See [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting). ::: The rest of the flow, naming the connection, setting a frequency and choosing streams, is the same for every source. See [Add a connection](https://docs.sanda-os.com.au/connections/add-a-connection). ## What syncs Each area of Xero lands as its own table, so reports can join them cleanly. That includes invoices, contacts, payments and bank transactions. The stream list shows everything the connection can read, and you choose which to sync. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). ## Tips - **Use OAuth Custom Connection for a connection that keeps running.** **Bearer Access Token** takes a single access token, and Xero access tokens expire after 30 minutes, so syncs would stop working almost at once. It suits a quick test and nothing longer. With **OAuth Custom Connection**, sanda uses the client ID and secret to get fresh tokens itself. - **One connection per organisation.** A tenant ID belongs to one Xero organisation. If you have several, add a connection for each and give each a distinct name, for example `Acme · Xero (AU)`. The name decides the tables the rows land in. - **Expect a long first load for a large ledger.** Xero limits how many API calls an organisation can make each minute and each day, so years of history can take a while. A later **Start Date** shortens the first run. - **Keep the scopes read-only.** sanda only ever reads. If a stream fails with a permission error, the app is missing the scope for that area. Add it in the developer portal and authorise the connection again. ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Tenant ID** | string, secret | Yes | Enter your Xero organization's Tenant ID. | | **Start Date** | string | Yes | UTC date and time in the format YYYY-MM-DDTHH:mm:ssZ. Any data with created_at before this date will not be synced. | | **Authentication Method** | one of 2 options | Yes | | ### Authentication Method Choose one of the following. Each asks for its own fields. **OAuth Custom Connection** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Client ID** | string | Yes | Your Xero application's Client ID. | | **Client Secret** | string, secret | Yes | Your Xero application's Client Secret. | **Bearer Access Token** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Access Token** | string, secret | Yes | The access token used to call the Xero API. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # QuickBooks > Bring invoices, customers, items, payments, accounts and journal entries from QuickBooks Online into your warehouse. ## Before you start sanda connects to QuickBooks Online, not QuickBooks Desktop. The form asks for a full set of OAuth credentials: an app's client ID and secret, plus tokens for your company. You get all of them from Intuit's developer portal. - **An Intuit developer account.** Create an app in the developer portal, with the accounting scope (`com.intuit.quickbooks.accounting`). - **Keys for the right environment.** Each app has development keys and production keys. Use production keys for a live company, and development keys with **Sandbox** turned on for a sandbox company. Intuit may ask you to complete a short questionnaire about the app before it issues production keys. - **Tokens for your company.** In the developer portal, use the OAuth 2.0 Playground. Choose your app, select the accounting scope, and authorise it against your company. The Playground then shows an access token and a refresh token, and its **Make API Calls** panel shows the realm ID, which QuickBooks also labels the Company ID. Use tokens generated only for sanda. Intuit can issue a new refresh token each time one is used, so two tools sharing one set of tokens can lock each other out. ## Connect QuickBooks :::steps 1. **Choose QuickBooks.** Go to **Data · Connections**, press **New connection** and pick **QuickBooks**. 2. **Identify the company.** Put the realm ID in **Realm ID**. The field is masked, like a password. 3. **Enter the app's keys.** Put the app's client ID in **Client ID** and its client secret in **Client Secret**. 4. **Enter the tokens.** Paste the access token into **Access Token** and the refresh token into **Refresh Token**. Intuit access tokens last one hour, so enter that expiry in **Token Expiry Date**, as a UTC date and time such as `2026-09-30T05:00:00Z`. 5. **Choose how far back to load.** Enter a UTC date and time in **Start Date**, in the form `2021-03-20T00:00:00Z`. Data before it is not replicated. 6. **Check the environment.** **Sandbox** sits under **Advanced settings** and opens off, which is right for a live company. Turn it on for a sandbox company. 7. **Continue.** Press **Continue**, name the connection, and press **Read schema**. sanda signs in and lists the QuickBooks objects it can read. If it fails, the message says why. See [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting). ::: The rest of the flow, naming the connection, setting a frequency and choosing streams, is the same for every source. See [Add a connection](https://docs.sanda-os.com.au/connections/add-a-connection). ## What syncs Each QuickBooks object becomes a stream, and lands in your warehouse as its own table. The usual starting points are invoices, customers, items, payments, accounts and journal entries. The stream list shows everything the connection can read, and you choose which to sync. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). ## Tips - **Refresh tokens expire.** Intuit's refresh tokens expire after 100 days without use. A connection that has been paused for months may need new tokens from the Playground. - **One connection per company.** A realm ID belongs to one QuickBooks company. For several companies, add a connection for each and give each a distinct name, for example `Acme · QuickBooks (AU)`. The name decides the tables the rows land in. - **Live and sandbox do not mix.** Production keys only work against live companies, and development keys against sandbox ones. A mismatch fails at **Read schema**. - **A later Start Date shortens the first load.** The first run reads everything from **Start Date** onward. ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Realm ID** | string, secret | Yes | Labeled Company ID. The Make API Calls panel is populated with the realm id and the current access token. | | **Client ID** | string | Yes | Identifies which app is making the request. Obtain this value from the Keys tab on the app profile via My Apps on the developer site. There are two versions of this key: development and production. | | **Access Token** | string, secret | Yes | Access token for making authenticated requests. | | **Client Secret** | string, secret | Yes | Obtain this value from the Keys tab on the app profile via My Apps on the developer site. There are two versions of this key: development and production. | | **Refresh Token** | string, secret | Yes | A token used when refreshing the access token. | | **Token Expiry Date** | string | Yes | The date-time when the access token should be refreshed. | | **Start Date** | string | Yes | The default value to use if no bookmark exists for an endpoint (rfc3339 date string). E.g, 2021-03-20T00:00:00Z. Any data before this date will not be replicated. | | **Sandbox** | boolean | Yes | Determines whether to use the sandbox or production environment. Default `false`. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # NetSuite > Connect NetSuite to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect NetSuite :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose NetSuite.** It is listed under finance and payments; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Realm (Account Id)** | string, secret | Yes | Netsuite realm e.g. 2344535, as for `production` or 2344535_SB1, as for the `sandbox`. | | **Consumer Key** | string, secret | Yes | Consumer key associated with your integration. | | **Consumer Secret** | string, secret | Yes | Consumer secret associated with your integration. | | **Token Key (Token Id)** | string, secret | Yes | Access token key. | | **Token Secret** | string, secret | Yes | Access token secret. | | **Object Types** | array | No | The API names of the Netsuite objects you want to sync. Setting this speeds up the connection setup process by limiting the number of schemas that need to be retrieved from Netsuite. | | **Start Date** | string | Yes | Starting point for your data replication, in format of "YYYY-MM-DDTHH:mm:ssZ" | | **Window in Days** | integer | No | The amount of days used to query the data with date chunks. Set smaller value, if you have lots of data. Default `30`. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # PayPal > Connect PayPal to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect PayPal :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose PayPal.** It is listed under finance and payments; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Client ID** | string, secret | Yes | The Client ID of your Paypal developer application. | | **Client secret** | string, secret | Yes | The Client Secret of your Paypal developer application. | | **Start Date** | string | Yes | Start Date for data extraction in ISO format. Date must be in range from 3 years till 12 hrs before present time. | | **Dispute Start Date Range** | string | No | Start Date parameter for the list dispute endpoint in ISO format. This Start Date must be in range within 180 days before present time, and requires ONLY 3 miliseconds(mandatory). If you don't use this option, it defaults to a start date set 180 days in the past. | | **End Date** | string | No | End Date for data extraction in ISO format. This can be help you select specific range of time, mainly for test purposes or data integrity tests. When this is not used, now_utc is used by the streams. This does not apply to Disputes and Product streams. | | **Sandbox** | boolean | Yes | Determines whether to use the sandbox or production environment. Default `false`. | | **Refresh token** | string, secret | No | The key to refresh the expired access token. | | **Number of days per request** | integer | No | The number of days per request. Must be a number between 1 and 31. Default `7`. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # Square > Connect Square to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect Square :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose Square.** It is listed under finance and payments; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Authentication** | one of 2 options | No | Choose how to authenticate to Square. | | **Sandbox** | boolean | Yes | Determines whether to use the sandbox or production environment. Default `false`. | | **Start Date** | string | No | UTC date in the format YYYY-MM-DD. Any data before this date will not be replicated. If not set, all data will be replicated. Default `2021-01-01`. | | **Include Deleted Objects** | boolean | No | In some streams there is an option to include deleted objects (Items, Categories, Discounts, Taxes). Default `false`. | ### Authentication Choose how to authenticate to Square. Choose one of the following. Each asks for its own fields. **Oauth authentication** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Client ID** | string, secret | Yes | The Square-issued ID of your application. | | **Client Secret** | string, secret | Yes | The Square-issued application secret for your application. | | **Refresh Token** | string, secret | Yes | A refresh token generated using the above client ID and secret. | **API key** | Field | Type | Required | Description | | --- | --- | --- | --- | | **API key token** | string, secret | Yes | The API key for a Square application. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # Chargebee > Connect Chargebee to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect Chargebee :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose Chargebee.** It is listed under finance and payments; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **API Key** | string, secret | Yes | Chargebee API Key. | | **Site** | string | Yes | The site prefix for your Chargebee instance. | | **Start Date** | string | Yes | UTC date and time in the format 2017-01-25T00:00:00.000Z. Any data before this date will not be replicated. | | **Product Catalog** | string | No | Product Catalog version of your Chargebee site, 1.0 or 2.0. If left blank, the product catalog version is set to 2.0. One of `1.0`, `2.0`. Default `2.0`. | | **Number of concurrent threads** | integer | No | The number of worker threads to use for the sync. The performance upper boundary is based on the limit of your Chargebee plan. Default `3`. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # Recurly > Connect Recurly to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect Recurly :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose Recurly.** It is listed under finance and payments; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **API Key** | string, secret | Yes | Recurly API Key. | | **Begin time** | string | No | ISO8601 timestamp from which the replication from Recurly API will start from. | | **End time** | string | No | ISO8601 timestamp to which the replication from Recurly API will stop. Records after that date won't be imported. | | **Number of concurrent threads** | integer | No | The number of worker threads to use for the sync. Default `10`. | | **Days in length for each API call to get data** | integer | No | Days in length for each API call to get data from the accounts stream. Smaller values will result in more API calls but better concurrency. Default `30`. | | **Use Sandbox Environment** | boolean | No | Set to true for sandbox accounts (400 requests/min, all types). Defaults to false for production accounts (1,000 GET requests/min). Default `false`. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # Braintree > Connect Braintree to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect Braintree :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose Braintree.** It is listed under finance and payments; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Environment** | string | Yes | Environment specifies where the data will come from. One of `Development`, `Sandbox`, `Qa`, `Production`. | | **Merchant ID** | string | Yes | The unique identifier for your entire gateway account. | | **Private Key** | string, secret | Yes | Braintree Private Key. | | **Public Key** | string | Yes | Braintree Public Key. | | **Start Date** | string | No | UTC date and time in the format 2017-01-25T00:00:00Z. Any data before this date will not be replicated. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # Zuora > Bring Zuora data into sanda. It has no self-serve form, so the sanda team sets the connection up with you. ## Connect Zuora Zuora has no self-serve form. Choose it when you add a connection and sanda opens a conversation with the team, who set the connection up with you and wire it by hand. :::links - [Add a connection](https://docs.sanda-os.com.au/connections/add-a-connection): The steps in the console. - [Getting help](https://docs.sanda-os.com.au/help): How to reach the sanda team. ::: --- # Shopify > Bring orders, products, customers, inventory and the rest of your Shopify store into your warehouse. ## Before you start sanda reads your store through an app that you create in Shopify and install on the store. The app's Admin API access token is what you give sanda. - **Admin access to the store.** You need to be able to create and install an app on the store. - **An app with read access.** In your Shopify admin, open **Settings**, then **Apps and sales channels**, then **Develop apps**, and create an app. Grant it read scopes for the data you want in sanda, such as orders, products, customers and inventory, then install it. Shopify changes where apps are created from time to time. If you cannot find this screen, follow Shopify's current instructions for creating an app with Admin API access to your own store. - **The Admin API access token.** Shopify reveals it when you install the app, and shows it once, so copy it straight away. It starts with `shpat_`. Shopify shows an app only the last 60 days of orders unless the app also has the `read_all_orders` scope. Add that scope before you install if you want older order history. ## Connect Shopify :::steps 1. **Choose Shopify.** Go to **Data · Connections**, press **New connection** and pick **Shopify**. 2. **Name the store.** Enter the part of your store's address before `.myshopify.com` in **Shopify Store**. If your address is `my-store.myshopify.com`, enter `my-store`. You can also paste the full address. 3. **Choose API Password.** **Shopify Authorization Method** sits under **Advanced settings** and opens on **OAuth2.0**. None of its fields is required, so the form does not stop you if you leave them empty. Change it to **API Password** and paste the Admin API access token into **API Password**. 4. **Choose how far back to load.** **Replication Start Date** opens on 2020-01-01. Enter a date as `YYYY-MM-DD`. Data before it is not replicated. 5. **Continue.** Press **Continue**, name the connection, and press **Read schema**. sanda signs in and lists the Shopify objects it can read. If it fails, the message says why. See [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting). ::: The rest of the flow, naming the connection, setting a frequency and choosing streams, is the same for every source. See [Add a connection](https://docs.sanda-os.com.au/connections/add-a-connection). The **OAuth2.0** option asks for a **Client ID**, a **Client Secret** and an **Access Token**. Use it only if you connect through a Shopify app that issues those. ## What syncs Shopify's objects each become a stream, and land in your warehouse as their own tables: orders, products, customers, inventory levels, locations, fulfilments, discount codes and abandoned checkouts among them. The stream list shows everything the app can read, so a scope you did not grant means a stream you will not see. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). ## Tips - **Add scopes before you need them.** Scopes are fixed when the app is installed. Adding one later means updating the app in Shopify and, if Shopify asks, installing it again. - **Read the big streams incrementally.** Orders and customers grow fast. After the first run, switch them to an incremental mode. See [sync modes](https://docs.sanda-os.com.au/connections/sync-modes). - **Catch late changes.** **Lookback Window (in Days)** re-reads records from the past N days on each incremental run, to pick up records that arrived late. It opens on 0, which means no lookback. - **One connection per store.** For several stores, add a connection for each and give each a distinct name. The name decides the tables the rows land in. ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Shopify Store** | string | Yes | The name of your Shopify store found in the URL. For example, if your URL is https://NAME.myshopify.com, then the name is 'NAME'. You may also paste the full myshopify URL (e.g. https://NAME.myshopify.com) and it will be normalized automatically. | | **Shopify Authorization Method** | one of 2 options | No | The authorization method to use to retrieve data from Shopify. | | **Replication Start Date** | string | No | The date you would like to replicate data from. Format: YYYY-MM-DD. Any data before this date will not be replicated. Default `2020-01-01`. | | **GraphQL BULK Date Range in Days** | integer | No | Defines what would be a date range per single BULK Job. Default `30`. | | **Add `user_id` to Transactions (slower)** | boolean | No | Defines which API type (REST/BULK) to use to fetch `Transactions` data. If you are a `Shopify Plus` user, leave the default value to speed up the fetch. Default `false`. | | **Include Closed Fulfillment Orders** | boolean | No | If enabled, the `Fulfillment Orders` stream includes closed fulfillment orders. Shopify excludes closed orders by default. Default `false`. | | **BULK Job checkpoint (rows collected)** | integer | No | The threshold, after which the single BULK Job should be checkpointed (min: 15k, max: 1M). Default `100000`. | | **Add `Presentment prices` to Product Variants** | boolean | No | If enabled, the `Product Variants` stream attempts to include `Presentment prices` field (may affect the performance). Default `true`. | | **BULK Job termination threshold** | integer | No | The max time in seconds, after which the single BULK Job should be `CANCELED` and retried. The bigger the value the longer the BULK Job is allowed to run. Default `7200`. | | **Lookback Window (in Days)** | integer | No | If set to a positive number, during each incremental sync the connector will re-fetch records from the past N days before the saved state. This helps capture records that may have been missed due to race conditions or late-arriving data. The default value of 0 means no lookback and preserves existing behavior. Default `0`. | | **Populate top-level customer fields in Orders** | boolean | No | Populate the top-level customer_* fields in Orders from the customer object. When disabled, these fields contain null. The original customer object is retained in both modes. Default `false`. | ### Shopify Authorization Method The authorization method to use to retrieve data from Shopify. Choose one of the following. Each asks for its own fields. **OAuth2.0** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Client ID** | string, secret | No | The Client ID of the Shopify developer application. | | **Client Secret** | string, secret | No | The Client Secret of the Shopify developer application. | | **Access Token** | string, secret | No | The Access Token for making authenticated requests. | **API Password** | Field | Type | Required | Description | | --- | --- | --- | --- | | **API Password** | string, secret | Yes | The API Password for your private application in the `Shopify` store. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # WooCommerce > Connect WooCommerce to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect WooCommerce :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose WooCommerce.** It is listed under commerce; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Customer Key** | string, secret | Yes | Customer Key for API in WooCommerce shop. | | **Customer Secret** | string, secret | Yes | Customer Secret for API in WooCommerce shop. | | **Shop Name** | string | Yes | The name of the store. For https://EXAMPLE.com, the shop name is 'EXAMPLE.com'. | | **Start Date** | string | Yes | The date you would like to replicate data from. Format: YYYY-MM-DD. | | **Number of Concurrent Threads** | integer | No | The number of worker threads to use for the sync. Higher values can speed up syncs but may hit rate limits. WooCommerce API rate limits depend on the hosting provider. Default `5`. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # Amazon Seller Partner > Connect Amazon Seller Partner to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect Amazon Seller Partner :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose Amazon Seller Partner.** It is listed under commerce; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Environment** | string | Yes | Select the Environment. One of `PRODUCTION`, `SANDBOX`. Default `PRODUCTION`. | | **Region** | string | Yes | Select the Region. One of `AE`, `AU`, `BE`, `BR`, `CA`, `DE`, `EG`, `ES`, `FR`, `GB`, `IE`, `IN`, `IT`, `JP`, `MX`, `NL`, `PL`, `SA`, `SE`, `SG`, `TR`, `UK`, `US`. Default `US`. | | **Seller Partner Account Type** | string | Yes | The type of Amazon Seller Partner account to authorise. One of `Seller`, `Vendor`. Default `Seller`. | | **Application ID** | string, secret | No | Your Amazon Application ID. | | **LWA Client Id** | string, secret | Yes | Your Login with Amazon Client ID. | | **LWA Client Secret** | string, secret | Yes | Your Login with Amazon Client Secret. | | **Refresh Token** | string, secret | Yes | The Refresh Token obtained via OAuth flow authorization. | | **Start Date** | string | No | UTC date and time in the format 2017-01-25T00:00:00Z. Any data before this date will not be replicated. If start date is not provided or older than 2 years ago from today, the date 2 years ago from today will be used. | | **End Date** | string | No | UTC date and time in the format 2017-01-25T00:00:00Z. Any data after this date will not be replicated. | | **Period In Days** | integer | No | For syncs spanning a large date range, this option is used to request data in a smaller fixed window to improve sync reliability. This time window can be configured granularly by day. Default `90`. | | **Sales and Traffic Report ASIN Granularity** | string | No | The level of ASIN granularity for the Sales and Traffic report streams. PARENT returns data aggregated at the parent ASIN level. CHILD returns data at the child ASIN level with populated childAsin values. SKU returns data at the individual SKU level with populated childAsin and sku values. One of `PARENT`, `CHILD`, `SKU`. Default `PARENT`. | | **Report Options** | array | No | Additional information passed to reports. This varies by report type. | | **Financial Events Step Size** | string | No | The time window size for fetching financial events data in chunks for the ListFinancialEvents and ListFinancialEventGroups streams. Options include hourly (1H, 6H, 12H) and daily (1D, 7D, 14D, 30D, 60D, 90D, 180D) granularity. Hourly step sizes (e.g., 1H, 6H) are recommended for very high data volumes where daily windows cause pagination token expiration (TTL errors). They fetch smaller chunks per request, reducing the risk of timeouts. Daily step sizes (e.g., 1D, 7D) are better for moderate data volumes. They balance sync speed with API efficiency. Larger step sizes (e.g., 30D, 180D) are better for smaller data volumes. They fetch more data per request, speeding up syncing and reducing the number of API calls. Select a step size that matches your data volume to optimize syncing speed and API performance. One of `1H`, `2H`, `4H`, `6H`, `8H`, `12H`, `1D`, `7D`, `14D`, `30D`, `60D`, `90D`, `180D`. Default `180D`. | | **Max Age of Completed Reports to Reuse (Hours)** | integer | No | When the connector finds an existing completed (DONE) report matching the same date range and marketplace, it can reuse that report instead of creating a new one. This setting controls how old (in hours) a completed report can be and still be reused. Set to 0 to always create new reports. Maximum is 72 hours. Default `0`. | | **Number of Workers** | integer | No | The number of workers to use for the connector when syncing concurrently. Default `2`. | | **Maximum Concurrent Async Job Count** | integer | No | The maximum number of concurrent asynchronous job requests that can be active at a time. Default `2`. | | **Include PII (Personally Identifiable Information)** | boolean | No | When enabled, the connector requests a Restricted Data Token (RDT) to access PII fields such as BuyerInfo and ShippingAddress in the Orders and OrderItems streams. Your Amazon SP-API developer profile must have an approved Restricted Role (Direct-to-Consumer Shipping or Tax Invoicing). If the RDT request is denied (HTTP 403), the connector falls back to the standard token automatically and PII fields remain empty. Default `false`. | | **Financial Events Max Results Per Page** | integer | No | The maximum number of results to return per page for the ListFinancialEvents stream. If the response exceeds the maximum number of transactions or 10 MB, the API returns an InvalidInput error. Lower this value if you encounter InvalidInput errors during sync. Valid range is 1-100. Default `100`. | | **Report Stream Lookback Window (Hours)** | integer | No | Number of hours to re-fetch for incremental report streams on each sync. Increase this value if Amazon continues updating report data after the previous sync has completed. Duplicates from the lookback window are handled by destination deduplication. Set to 0 to disable. This does not apply to GET_SALES_AND_TRAFFIC_REPORT_BY_MONTH or GET_VENDOR_SALES_REPORT, which use monthly or date-only report boundaries. Default `0`. | | **Failed Report Retry Wait Time (Seconds)** | integer | No | Time in seconds to wait before retrying a report that returned FATAL status. Amazon enforces per-report-type cooldowns after generating a report. Near-real-time FBA reports have an approximately 30-minute cooldown, daily FBA reports approximately 4 hours. The default of 1800 seconds covers the most common cooldown. Increase this value if you see repeated FATAL errors on daily FBA reports. Default `1800`. | | **Report Creation 429 Max Retries** | integer | No | Maximum number of retry attempts when the report creation API returns HTTP 429 (Too Many Requests). Each retry uses exponential backoff based on the x-amzn-RateLimit-Limit header. Reduce this value to avoid exhausting rate limits on retrying requests. Default `5`. | | **Stop Sync When Report Streams Are Rate Limited** | boolean | No | Only applies to report streams. When enabled, the source stops retrying immediately once the rate limit retry budget is exhausted and fails with an actionable configuration error. This is useful when persistent rate limiting indicates the connector configuration needs adjustment (such as reducing the number of report streams or increasing the sync interval). When disabled (default), the source applies its backoff and retry strategy; if all retry attempts are exhausted, a transient error is thrown and the sync may be rescheduled automatically. Default `false`. | | **Inbound Replication Mode** | string | No | Settings only for Fulfillment Inbound streams. Use fixed datetimes or rolling window (last N days). When inbound_replication_mode=fixed, inbound_start_datetime is required. When inbound_replication_mode=rolling_days, inbound_rolling_days is required. One of `fixed`, `rolling_days`. Default `rolling_days`. | | **Inbound Start Datetime** | string | No | Only used when inbound_replication_mode=fixed. Required when inbound_replication_mode=fixed. UTC datetime in format 2026-01-25T00:00:00Z. | | **Inbound End Datetime** | string | No | Only used when inbound_replication_mode=fixed. Optional when inbound_replication_mode=fixed. If not set, the connector uses the current UTC time. UTC datetime in format 2026-01-25T00:00:00Z. | | **Inbound Rolling Days** | integer | No | Only used when inbound_replication_mode=rolling_days. Required when inbound_replication_mode=rolling_days. Fetch data including today, for the last N days. Default `30`. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # Amazon Ads > Connect Amazon Ads to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect Amazon Ads :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose Amazon Ads.** It is listed under commerce; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Client ID** | string, secret | Yes | The client ID of your Amazon Ads developer application. | | **Client Secret** | string, secret | Yes | The client secret of your Amazon Ads developer application. | | **Refresh Token** | string, secret | Yes | Amazon Ads refresh token. | | **Region** | string | No | Region to pull data from (EU/NA/FE). One of `NA`, `EU`, `FE`. Default `NA`. | | **Start Date** | string | No | The Start date for collecting reports, should not be more than 60 days in the past. In YYYY-MM-DD format. | | **Profile IDs** | array | No | Profile IDs you want to fetch data for. The Amazon Ads source connector supports only profiles with seller and vendor type, profiles with agency type will be ignored. Note: If Marketplace IDs are also selected, profiles will be selected if they match the Profile ID OR the Marketplace ID. | | **Marketplace IDs** | array | No | Marketplace IDs you want to fetch data for. Note: If Profile IDs are also selected, profiles will be selected if they match the Profile ID OR the Marketplace ID. | | **Look Back Window** | integer | No | The amount of days to go back in time to get the updated data from Amazon Ads. Default `3`. | | **Number of concurrent threads** | integer | No | The number of worker threads to use for the sync. Default `14`. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # BigCommerce > Bring BigCommerce data into sanda. It has no self-serve form, so the sanda team sets the connection up with you. ## Connect BigCommerce BigCommerce has no self-serve form. Choose it when you add a connection and sanda opens a conversation with the team, who set the connection up with you and wire it by hand. :::links - [Add a connection](https://docs.sanda-os.com.au/connections/add-a-connection): The steps in the console. - [Getting help](https://docs.sanda-os.com.au/help): How to reach the sanda team. ::: --- # Recharge > Connect Recharge to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect Recharge :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose Recharge.** It is listed under commerce; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Access Token** | string, secret | Yes | The value of the Access Token generated. | | **Lookback Window (in days)** | integer | No | Specifies how many days of historical data should be reloaded each time the recharge connector runs. Default `0`. | | **Start Date** | string | Yes | The date from which you'd like to replicate data for Recharge API, in the format YYYY-MM-DDT00:00:00Z. Any data before this date will not be replicated. | | **Use `Orders` Deprecated API** | boolean | No | Define whether or not the `Orders` stream should use the deprecated `2021-01` API version, or use `2021-11`, otherwise. Default `true`. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # Salesforce > Bring accounts, contacts, leads, opportunities and other Salesforce objects, custom ones included, into your warehouse. ## Before you start Salesforce signs sanda in through OAuth. That means an app registered in Salesforce, and a refresh token that lets sanda keep signing in without you. The form asks for the app's client ID and secret, and the refresh token. - **A Salesforce edition with API access.** Some editions include it only as an add-on, so ask your Salesforce admin if you are unsure. - **A user for sanda.** Use a dedicated integration user rather than a person's login, with the **API Enabled** permission and read access to the objects you want in sanda. sanda only ever reads. Whatever this user cannot see, sanda cannot sync. - **An app that allows OAuth.** In Salesforce Setup, create the app that sanda will sign in as. Salesforce calls it a connected app, or an external client app in newer orgs. Turn on OAuth for it, with a callback URL you control and the scopes **Manage user data via APIs (api)** and **Perform requests at any time (refresh_token, offline_access)**. - **The client ID and secret.** Salesforce calls them the consumer key and the consumer secret. You find both in the app's details in Setup. - **A refresh token.** Authorise the app once as the integration user, using Salesforce's OAuth authorisation code flow. Salesforce sends an authorisation code to your callback URL. Exchange that code for tokens, and keep the `refresh_token` from the reply. If the app requires PKCE, add a code challenge to the authorisation request and the matching code verifier to the token request. If your Salesforce is a sandbox, do the authorisation against the sandbox login address, and turn on **Sandbox** in the form. ## Connect Salesforce :::steps 1. **Choose Salesforce.** Go to **Data · Connections**, press **New connection** and pick **Salesforce**. 2. **Enter the app's keys.** Put the consumer key in **Client ID** and the consumer secret in **Client Secret**. 3. **Enter the refresh token.** Paste it into **Refresh Token**. 4. **Say if it is a sandbox.** **Sandbox** sits under **Advanced settings** and opens off. Turn it on for a sandbox org. 5. **Choose how far back to load.** **Start Date** is under **Advanced settings**. Enter a date as `YYYY-MM-DD`, or a date and time as `YYYY-MM-DDTHH:mm:ssZ`. When it is empty, sanda loads the last two years. 6. **Continue.** Press **Continue**, name the connection, and press **Read schema**. sanda signs in and lists the objects the integration user can read. If it fails, the message says why. See [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting). ::: The rest of the flow, naming the connection, setting a frequency and choosing streams, is the same for every source. See [Add a connection](https://docs.sanda-os.com.au/connections/add-a-connection). ## What syncs Each Salesforce object becomes a stream, and lands in your warehouse as its own table. The usual starting points are `Account`, `Contact`, `Lead`, `Opportunity`, `Task` and `User`. The list also holds the other objects the integration user can read, custom objects included. An org with a very large number of objects makes a long list. **Filter Salesforce Objects** under **Advanced settings** narrows it by object name. ## Tips - **Choose a start date.** Leaving **Start Date** empty loads the last two years, but a fixed date makes the first load predictable. **End Date** stops loading at a chosen point, and is rarely needed. - **Read the large objects incrementally.** The first run reads every object you choose in full. Switch big ones such as `Opportunity` and `Task` to an incremental mode afterwards. See [sync modes](https://docs.sanda-os.com.au/connections/sync-modes). - **Leave Force to use BULK API off.** It is under **Advanced settings**. Turning it on can leave some fields empty on some streams. - **Watch for NA turning into null.** Strings such as `NA`, `N/A`, `NULL`, `None` and `NaN` returned by the Salesforce Bulk API are treated as missing and arrive as null. Turn on **Preserve "NA" and similar string values** if `NA` is real data, for example North America. - **Mind your API allowance.** Salesforce caps how many API requests an org can make in a day, and syncs use some of them. If your org is close to its limit, sync less often. See [sync schedules](https://docs.sanda-os.com.au/connections/schedules). ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Sandbox** | boolean | No | Toggle if you're using a Salesforce Sandbox. Default `false`. | | **Client ID** | string | Yes | Enter your Salesforce developer application's Client ID. | | **Client Secret** | string, secret | Yes | Enter your Salesforce developer application's Client secret. | | **Refresh Token** | string, secret | Yes | Enter your application's Salesforce Refresh Token used for becca to access your Salesforce account. | | **Start Date** | string | No | Enter the date (or date-time) in the YYYY-MM-DD or YYYY-MM-DDTHH:mm:ssZ format. becca will replicate the data updated on and after this date. If this field is blank, becca will replicate the data for last two years. | | **End Date** | string | No | Enter the date (or date-time) in the YYYY-MM-DD or YYYY-MM-DDTHH:mm:ssZ format. For streams synced incrementally, becca will replicate data updated before this date; the bound is exclusive, so a date-only value means midnight UTC and the named day itself is not included. Streams synced in full refresh mode ignore this field and replicate all data. If this field is blank, becca will replicate data up to the current time. | | **Force to use BULK API** | boolean | No | Toggle to use Bulk API (this might cause empty fields for some streams). Default `false`. | | **Stream Slice Step for Incremental sync** | string | No | The size of the time window (ISO8601 duration) to slice requests. Default `P30D`. | | **Lookback Window for Incremental sync** | string | No | The duration (ISO8601 duration) to re-read data from the source when running incremental syncs. This compensates for records that may not be immediately available when querying the Salesforce API due to eventual consistency delays. The connector will re-query this amount of time before the last cursor position on each sync. Increase this value if you observe missing records in your destination. Default `PT10M`. | | **Filter Salesforce Objects** | array | No | Add filters to select only required stream based on `SObject` name. Use this field to filter which tables are displayed by this connector. This is useful if your Salesforce account has a large number of tables (>1000), in which case you may find it easier to choose tables and speed up the connector's performance if you restrict the tables displayed by this connector. | | **Preserve "NA" and similar string values** | boolean | No | By default, string values such as "NA", "N/A", "NULL", "None" and "NaN" returned by the Salesforce Bulk API are treated as missing and synced as null. Enable this option to keep these values as literal strings (for example, "NA" meaning "North America"). Empty fields are still synced as null. Leaving this disabled preserves the connector's historical behavior. Default `false`. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # HubSpot > Bring contacts, companies, deals, tickets and engagement activity from HubSpot into your warehouse. ## Before you start The simplest way to connect HubSpot is a private app. It is an access token, scoped to the data you choose, that belongs to your HubSpot account. The form's **Authentication** field opens on **Private App**. - **Permission to create private apps.** This is usually a super admin in your HubSpot account. - **A private app with read scopes.** In your HubSpot account settings, under **Integrations**, open **Private Apps** and create one. If HubSpot has moved this screen, search its settings for private apps. Give the app read scopes for the data you want in sanda, for example `crm.objects.contacts.read`, `crm.objects.companies.read` and `crm.objects.deals.read`. sanda only ever reads. - **The access token.** Once the app is created, open its **Auth** tab and reveal the token. HubSpot issues one token per private app, and that token is the whole credential. If you already run a HubSpot developer application, the **OAuth** option takes its **Client ID** and **Client Secret**, plus a **Refresh Token** you have obtained through HubSpot's authorisation flow. ## Connect HubSpot :::steps 1. **Choose HubSpot.** Go to **Data · Connections**, press **New connection** and pick **HubSpot**. 2. **Choose how to sign in.** Leave **Authentication** on **Private App** and paste the token into **Access token**. For the other option, choose **OAuth** and fill in **Client ID**, **Client Secret** and **Refresh Token**. 3. **Choose how far back to load.** **Start date** is under **Advanced settings**. Enter a UTC date and time, in the form `2017-01-25T00:00:00Z`. When it is empty, sanda starts from 2006-06-01, HubSpot's creation date, so choose a date that fits your data. 4. **Continue.** Press **Continue**, name the connection, and press **Read schema**. sanda signs in and lists the HubSpot objects the token can read. If it fails, the message says why. See [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting). ::: The rest of the flow, naming the connection, setting a frequency and choosing streams, is the same for every source. See [Add a connection](https://docs.sanda-os.com.au/connections/add-a-connection). ## What syncs Each HubSpot object becomes a stream, and lands in your warehouse as its own table. The usual starting points are contacts, companies and deals. The stream list shows everything the token can read, so a scope you did not grant means a stream you will not be able to sync. Add the scope to the private app when you want more. **Enable experimental streams**, under **Advanced settings**, makes HubSpot's experimental streams available for sync. It opens off. ## Tips - **Choose a real start date.** Starting from 2006 makes the first load as long as it can be. A date near when you began using HubSpot is usually enough. - **Recover records that arrive late.** **CRM Search Lookback Window (minutes)** re-fetches records from the last N minutes on each incremental run. Use it if you notice missing records in contacts, companies, deals or tickets. **Property History Lookback Window (minutes)** does the same for property history streams. - **Type mismatches.** If HubSpot returns values that do not match a property's declared type, and the load rejects the records, turn on **Treat dynamic number and boolean properties as strings** under **Advanced settings**. After changing it, refresh the source schema and the affected stream data. - **Keep the token safe.** Anyone with the token can read what its scopes allow. If it leaks, rotate it in the private app, then update the connection. ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Association Between Objects Streams** | array | No | | | **Authentication** | one of 2 options | Yes | Choose how to authenticate to HubSpot. | | **Custom Object Association Streams** | array | No | | | **Enable experimental streams** | boolean | No | If enabled then experimental streams become available for sync. Default `false`. | | **CRM Search Lookback Window (minutes)** | integer | No | How far back (in minutes) to re-fetch records during incremental syncs for CRM Search streams (e.g. contacts, companies, deals, tickets). Set this if you notice missing records in CRM Search streams to recover data that may be delayed by the HubSpot API. Does not affect property history streams. Default `0`. | | **Number of concurrent threads** | integer | No | The number of worker threads to use for the sync. Default `10`. | | **Property History Lookback Window (minutes)** | integer | No | How far back (in minutes) to re-fetch records during incremental syncs for property history streams (deals, contacts, companies property history). Set this if you notice missing records in property history streams caused by cursor drift from HubSpot calculated properties. Default `0`. | | **Start date** | string | No | UTC date and time in the format 2017-01-25T00:00:00Z. Any data before this date will not be replicated. If not set, "2006-06-01T00:00:00Z" (Hubspot creation date) will be used as start date. It's recommended to provide relevant to your data start date value to optimize synchronization. | | **Treat dynamic number and boolean properties as strings** | boolean | No | If enabled, HubSpot dynamic `number` and `boolean` properties are exposed as `string`. Useful when HubSpot returns values that do not match the declared type and the destination rejects the records. Default `false`. | ### Authentication Choose how to authenticate to HubSpot. Choose one of the following. Each asks for its own fields. **OAuth** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Client ID** | string | Yes | The Client ID of your HubSpot developer application. | | **Client Secret** | string, secret | Yes | The client secret for your HubSpot developer application. | | **Refresh Token** | string, secret | Yes | Refresh token to renew an expired access token. | **Private App** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Access token** | string, secret | Yes | HubSpot Access token. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # Pipedrive > Connect Pipedrive to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect Pipedrive :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose Pipedrive.** It is listed under crm and support; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **API Token** | string, secret | Yes | Your personal Pipedrive API token. In Pipedrive, click your name (top right) · Company settings · Personal preferences · API and copy the token. If the API tab is missing, ask your Pipedrive admin to enable API access for your user. | | **Start Date** | string | No | Only records created or updated on or after this UTC date and time are replicated. Format YYYY-MM-DDTHH:MM:SSZ, for example 2017-01-25T00:00:00Z. Defaults to 2010-01-01T00:00:00Z when left empty. Default `2010-01-01T00:00:00Z`. | | **Number of concurrent workers** | integer | No | Number of streams synced in parallel (1-10). All requests share one client-side request budget sized to Pipedrive's lowest-plan burst limit, so higher values rarely make a sync faster. Default `3`. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # Zendesk > Connect Zendesk to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect Zendesk :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose Zendesk.** It is listed under crm and support; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Subdomain** | string | Yes | This is your unique Zendesk subdomain that can be found in your account URL. For example, in https://MY_SUBDOMAIN.zendesk.com/, MY_SUBDOMAIN is the value of your subdomain. | | **Authentication** | one of 3 options | No | Zendesk allows three authentication methods. | | **Start Date** | string | No | The UTC date and time from which you'd like to replicate data, in the format YYYY-MM-DDT00:00:00Z. All data generated after this date will be replicated. | | **Number of concurrent threads** | integer | No | The number of worker threads to use for the sync. Higher values can improve sync throughput on large workspaces; lower values reduce load on the source. The minimum is 2 so that another stream keeps emitting while the `tickets` stream reads. Default `4`. | | **Page Size (ticket_comments)** | integer | No | The number of records per page for the ticket_comments stream API requests. Lower values may help prevent timeouts on large datasets. The maximum value is 1000. Default `100`. | | **Tickets Search Lookback Window (days)** | integer | No | Only applies to the optional `tickets_search` stream. Number of days to re-scan before the last saved cursor on each incremental sync. This helps catch late or out-of-order `updated_at` changes. Note: it does NOT recover automation/macro/system updates (those never change `updated_at`). Use the default `tickets` stream if you need complete change tracking. Default `0`. | ### Authentication Zendesk allows three authentication methods. Choose one of the following. Each asks for its own fields. **OAuth2.0 with Refresh Token** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Access Token** | string, secret | No | Access Token for making authenticated requests. | | **Client ID** | string, secret | Yes | The OAuth client's ID. | | **Client Secret** | string, secret | Yes | The OAuth client secret. | | **Refresh Token** | string, secret | Yes | The refresh token used to obtain new access tokens. Note that Zendesk uses rotating refresh tokens. Each refresh will return a new refresh token and invalidate the previous one. | | **Token Expiry Date** | string | No | The date-time when the access token should be refreshed. | **API Token** | Field | Type | Required | Description | | --- | --- | --- | --- | | **API Token** | string, secret | Yes | The value of the API token generated. | | **Email** | string | Yes | The user email for your Zendesk account. | **OAuth2.0 (Legacy)** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Access Token** | string, secret | Yes | The OAuth access token. | | **Client ID** | string, secret | No | The OAuth client's ID. | | **Client Secret** | string, secret | No | The OAuth client secret. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # Intercom > Connect Intercom to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect Intercom :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose Intercom.** It is listed under crm and support; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Access token** | string, secret | Yes | Access token for making authenticated requests. | | **Client Id** | string, secret | No | Client Id for your Intercom application. | | **Client Secret** | string, secret | No | Client Secret for your Intercom application. | | **Activity logs stream slice step size (in days)** | integer | No | Set lower value in case of failing long running sync of Activity Logs stream. Default `30`. | | **Lookback window** | integer | No | The number of days to shift the state value backward for record sync. Default `0`. | | **Start date** | string | Yes | UTC date and time in the format 2017-01-25T00:00:00Z. Any data before this date will not be replicated. | | **Num Workers** | integer | No | The number of worker threads to use for concurrent stream processing. Increase this to speed up syncs for workspaces with large volumes of conversations. The default should work for most use cases. Default `10`. | | **API Rate Limit (requests per minute)** | integer | No | The effective API request budget per minute. The default of 9500 is 95% of the standard Intercom rate limit (10,000/min), leaving headroom for occasional bursts. If your workspace has a higher rate limit (e.g. 150,000/min), set this to ~95% of that value (e.g. 142500) to use your full API throughput. A per-10-second window is derived automatically as api_rate_limit / 6. Default `9500`. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # Freshdesk > Connect Freshdesk to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect Freshdesk :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose Freshdesk.** It is listed under crm and support; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **API Key** | string, secret | Yes | Freshdesk API Key. | | **Domain** | string | Yes | Freshdesk domain | | **Requests per minute** | integer | No | The number of requests per minute that this source allowed to use. There is a rate limit of 50 requests per minute per app per account. | | **Start Date** | string | No | UTC date and time. Any data created after this date will be replicated. If this parameter is not set, all data will be replicated. | | **Lookback Window** | integer | No | Number of days for lookback window for the stream Satisfaction Ratings. Default `14`. | | **Subscription Plan/Tier** | string | No | Your API subscription tier (affects rate limits). One of `growth`, `pro`, `enterprise`. Default `growth`. | | **Number of Concurrent Threads** | integer | No | Number of concurrent threads for syncing. Higher values can speed up syncs but may increase API rate limit usage. Adjust based on your Freshdesk API plan. Default `5`. | | **Rate Limit Plan** | one of 5 options | No | Rate Limit Plan for API Budget. | ### Rate Limit Plan Rate Limit Plan for API Budget. Choose one of the following. Each asks for its own fields. **Free Plan** No further fields. **Growth Plan** No further fields. **Pro Plan** No further fields. **Enterprise Plan** No further fields. **Custom Plan** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Contacts Rate** | integer | No | Maximum Rate in Limit/minute for contacts list endpoint in Custom Plan. | | **General Rate** | integer | No | General Maximum Rate in Limit/minute for other endpoints in Custom Plan. | | **Tickets Rate** | integer | No | Maximum Rate in Limit/minute for tickets list endpoint in Custom Plan. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # Zoho CRM > Connect Zoho CRM to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect Zoho CRM :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose Zoho CRM.** It is listed under crm and support; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Max Concurrent Requests** | integer | No | Maximum number of concurrent requests to the Zoho CRM API. When left empty, the connector detects your Zoho CRM edition via the organization API and uses that edition's limit (Free: 5, Standard: 10, Professional: 15, Enterprise: 20, Ultimate: 25), defaulting to 5 if detection fails. Set this to raise the limit without re-authenticating, or to throttle the connector when sharing API credits with other integrations. | | **Client ID** | string, secret | Yes | OAuth2.0 Client ID. | | **Client Secret** | string, secret | Yes | OAuth2.0 Client Secret. | | **Data Center Location** | string | Yes | Please choose the region of your Data Center location. One of `US`, `AU`, `EU`, `IN`, `CN`, `JP`. | | **Environment** | string | Yes | Please choose the environment. One of `Production`, `Developer`, `Sandbox`. | | **Refresh Token** | string, secret | Yes | OAuth2.0 Refresh Token. | | **Start Date** | string | No | ISO 8601, for instance: `YYYY-MM-DD`, `YYYY-MM-DD HH:MM:SS+HH:MM` | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # Front > Connect Front to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect Front :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose Front.** It is listed under crm and support; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **API Key** | string, secret | Yes | | | **Start date** | string | Yes | | | **Page limit** | string | No | Page limit for the responses. Default `50`. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # Close > Connect Close to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect Close :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose Close.** It is listed under crm and support; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **API Key** | string, secret | Yes | Close.com API key (usually starts with 'api_'). | | **Replication Start Date** | string | No | The start date to sync data; all data after this date will be replicated. Leave blank to retrieve all the data available in the account. Format: YYYY-MM-DD. Default `2021-01-01`. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # Google Ads > Connect Google Ads to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect Google Ads :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose Google Ads.** It is listed under marketing and analytics; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Google Credentials** | object | Yes | | | **Customer ID(s)** | string | No | Comma-separated list of (client) customer IDs. Each customer ID must be specified as a 10-digit number without dashes. | | **Customer Statuses Filter** | array | No | A list of customer statuses to filter on. | | **Start Date** | string | No | UTC date in the format YYYY-MM-DD. Any data before this date will not be replicated. (Default value of two years ago is used if not set). | | **End Date** | string | No | UTC date in the format YYYY-MM-DD. Any data after this date will not be replicated. (Default value of today is used if not set). | | **Custom GAQL Queries** | array | No | | | **Conversion Window** | integer | No | A conversion window is the number of days after an ad interaction (such as an ad click or video view) during which a conversion, such as a purchase, is recorded in Google Ads. Default `14`. | | **Number of Concurrent Threads** | integer | No | The number of concurrent threads to use for syncing. Increasing this value may speed up syncs for accounts with many customers or streams. Adjust based on your API usage and rate limits. Default `3`. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # Google Analytics 4 > Connect Google Analytics 4 to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect Google Analytics 4 :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose Google Analytics 4.** It is listed under marketing and analytics; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Credentials** | one of 2 options | No | Credentials for the service. | | **Property IDs** | array | Yes | A list of your Property IDs. The Property ID is a unique number assigned to each property in Google Analytics, found in your GA4 property URL. This ID allows the connector to track the specific events associated with your property. | | **One Stream per Report** | boolean | No | When enabled, each report produces one stream containing data from all configured properties, distinguished by the property_id field. These streams take a Consolidated suffix (for example, devicesConsolidated) and replace the per-property streams. Default `false`. | | **Start Date** | string | No | The start date from which to replicate report data in the format YYYY-MM-DD. Data generated before this date will not be included in the report. Not applied to custom Cohort reports. | | **End Date** | string | No | The end date from which to replicate report data in the format YYYY-MM-DD. Data generated after this date will not be included in the report. Not applied to custom Cohort reports. When no date is provided or the date is in the future, the date from today is used. | | **Custom Reports** | array | No | You can add your Custom Analytics report by creating one. | | **Data Request Interval (Days)** | integer | No | The interval in days for each data request made to the Google Analytics API. A larger value speeds up data sync, but increases the chance of data sampling, which may result in inaccuracies. We recommend a value of 1 to minimize sampling, unless speed is an absolute priority over accuracy. Acceptable values range from 1 to 364. Does not apply to custom Cohort reports. Default `1`. | | **Lookback window (Days)** | integer | No | Since attribution changes after the event date, and Google Analytics has a data processing latency, we should specify how many days in the past we should refresh the data in every run. So if you set it at 5 days, in every sync it will fetch the last bookmark date minus 5 days. Default `2`. | | **Keep Empty Rows** | boolean | No | If false, each row with all metrics equal to 0 will not be returned. If true, these rows will be returned if they are not separately removed by a filter. Default `false`. | | **Convert `conversions:*` Metrics to Float** | boolean | No | Enables conversion of `conversions:*` event metrics from integers to floats. This is beneficial for preventing data rounding when the API returns float values for any `conversions:*` fields. Default `false`. | | **Subscription Plan/Tier** | string | No | Quota tier of the Google Analytics 4 properties being queried. Determines the per-property rate-limit policy applied locally once the tier-aware rate-limit budget is activated. Select "Analytics 360 Property" only if all configured property IDs belong to an Analytics 360 subscription. See https://developers.google.com/analytics/devguides/reporting/data/v1/quotas. One of `Standard Property`, `Analytics 360 Property`. Default `Standard Property`. | ### Credentials Credentials for the service. Choose one of the following. Each asks for its own fields. **Authenticate via Google (Oauth)** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Client ID** | string | Yes | The Client ID of your Google Analytics developer application. | | **Client Secret** | string, secret | Yes | The Client Secret of your Google Analytics developer application. | | **Refresh Token** | string, secret | Yes | The token for obtaining a new access token. | | **Access Token** | string, secret | No | Access Token for making authenticated requests. | **Service Account Key Authentication** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Service Account JSON Key** | string, secret | Yes | The JSON key linked to the service account used for authorization. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # Google Search Console > Connect Google Search Console to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect Google Search Console :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose Google Search Console.** It is listed under marketing and analytics; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Website URL Property** | array | Yes | The URLs of the website property attached to your GSC account. | | **Start Date** | string | No | UTC date in the format YYYY-MM-DD. Any data before this date will not be replicated. Default `2021-01-01`. | | **End Date** | string | No | UTC date in the format YYYY-MM-DD. Any data created after this date will not be replicated. Must be greater or equal to the start date field. Leaving this field blank will replicate all data from the start date onward. | | **Authentication Type** | one of 2 options | Yes | | | **Custom Reports** | array | No | You can add your Custom Analytics report by creating one. | | **Data Freshness** | string | No | If set to 'final', the returned data will include only finalized, stable data. If set to 'all', fresh data will be included. When using Incremental sync mode, we do not recommend setting this parameter to 'all' as it may cause data loss. One of `final`, `all`. Default `final`. | | **Number of concurrent threads** | integer | No | The number of worker threads to use for the sync. Default `40`. | | **Always Use Aggregation Type Auto** | boolean | No | Some search analytics streams fail with a 400 error if the specified `aggregationType` is not supported. This is customer implementation dependent and if this error is encountered, enable this setting which will override the existing `aggregationType` to use `auto` which should resolve the stream errors. Default `false`. | | **Search Analytics API Requests Per Minute** | integer | No | The maximum number of requests per minute for Search Analytics API calls. The default (1200) matches Google's documented maximum quota. If you are experiencing rate limit errors, you may need to lower this value. Most new Google Cloud projects start with a quota of 60 requests per minute. Check your Google Cloud Console quotas to see your actual limit. Default `1200`. | ### Authentication Type Choose one of the following. Each asks for its own fields. **OAuth** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Access Token** | string, secret | No | Access token for making authenticated requests. | | **Client ID** | string, secret | Yes | The client ID of your Google Search Console developer application. | | **Client Secret** | string, secret | Yes | The client secret of your Google Search Console developer application. | | **Refresh Token** | string, secret | Yes | The token for obtaining a new access token. | **Service Account Key Authentication** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Admin Email** | string | Yes | The email of the user which has permissions to access the Google Workspace Admin APIs. | | **Service Account JSON Key** | string, secret | Yes | The JSON key of the service account to use for authorization. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # Facebook Marketing > Connect Facebook Marketing to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect Facebook Marketing :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose Facebook Marketing.** It is listed under marketing and analytics; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Ad Account ID(s)** | array | Yes | The Facebook Ad account ID(s) to pull data from. The Ad account ID number is in the account dropdown menu or in your browser's address bar of your Meta Ads Manager. | | **Start Date** | string | No | The date from which you'd like to replicate data for all incremental streams, in the format YYYY-MM-DDT00:00:00Z. If not set then all data will be replicated for usual streams and only last 2 years for insight streams. | | **End Date** | string | No | The date until which you'd like to replicate data for all incremental streams, in the format YYYY-MM-DDT00:00:00Z. All data generated between the start date and this end date will be replicated. Not setting this option will result in always syncing the latest data. | | **Campaign Statuses** | array | No | Select the statuses you want to be loaded in the stream. If no specific statuses are selected, the API's default behavior applies, and some statuses may be filtered out. | | **AdSet Statuses** | array | No | Select the statuses you want to be loaded in the stream. If no specific statuses are selected, the API's default behavior applies, and some statuses may be filtered out. | | **Ad Statuses** | array | No | Select the statuses you want to be loaded in the stream. If no specific statuses are selected, the API's default behavior applies, and some statuses may be filtered out. | | **Fetch Thumbnail Images from Ad Creative** | boolean | No | Set to active if you want to fetch the thumbnail_url and store the result in thumbnail_data_url for each Ad Creative. Default `false`. | | **Action breakdowns for the Built-in Ads Insight stream** | array | No | Action breakdowns for the Built-in Ads Insights stream that will be used in the request. You can override default values or remove them to make it empty if needed. Default `action_type,action_target_id,action_destination`. | | **Custom Insights** | array | No | A list which contains ad statistics entries, each entry must have a name and can contains fields, breakdowns or action_breakdowns. | | **Page Size of Requests** | integer | No | Page size used when sending requests to Facebook API to specify number of records per page when response has pagination. Most users do not need to set this field unless they specifically need to tune the connector to address specific issues or use cases. Default `100`. | | **Insights Lookback Window** | integer | No | The attribution window. Facebook freezes insight data 28 days after it was generated, which means that all data from the past 28 days may have changed since we last emitted it, so you can retrieve refreshed insights from the past by setting this parameter. If you set a custom lookback window value in Facebook account, please provide the same value here. Default `28`. | | **Insights Job Timeout** | integer | No | Insights Job Timeout establishes the maximum amount of time (in minutes) of waiting for the report job to complete. When timeout is reached the job is considered failed and we are trying to request smaller amount of data by breaking the job to few smaller ones. If you definitely know that 60 minutes is not enough for your report to be processed then you can decrease the timeout value, so we start breaking job to smaller parts faster. Default `60`. | | **Include Incrementality** | boolean | No | If enabled, the incrementality attribution window will be included in the action attribution windows for all built-in insight streams. This allows you to retrieve incrementality data for action metrics. Default `false`. | | **Include Engaged View** | boolean | No | If enabled, the engaged view (ev) attribution window will be included in the action attribution windows for all built-in insight streams. This surfaces attribution data for users who had an engaged view of a video ad (watched 10+ seconds or 97%+ of a short video). Default `false`. | | **Authentication** | one of 2 options | Yes | Credentials for connecting to the Facebook Marketing API. | ### Authentication Credentials for connecting to the Facebook Marketing API. Choose one of the following. Each asks for its own fields. **Authenticate via Facebook Marketing (Oauth)** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Access Token** | string, secret | No | The value of the generated access token. From your App’s Dashboard, click on "Marketing API" then "Tools". Select permissions ads_management, ads_read, read_insights, business_management. Then click on "Get token". | | **Client ID** | string, secret | Yes | Client ID for the Facebook Marketing API. | | **Client Secret** | string, secret | Yes | Client Secret for the Facebook Marketing API. | **Service Account Key Authentication** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Access Token** | string, secret | Yes | The value of the generated access token. From your App’s Dashboard, click on "Marketing API" then "Tools". Select permissions ads_management, ads_read, read_insights, business_management. Then click on "Get token". | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # Instagram > Connect Instagram to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect Instagram :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose Instagram.** It is listed under marketing and analytics; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Access Token** | string, secret | Yes | The value of the access token generated with instagram_basic, instagram_manage_insights, pages_show_list, pages_read_engagement, Instagram Public Content Access permissions. | | **Client Id** | string, secret | No | The Client ID for your Oauth application. | | **Client Secret** | string, secret | No | The Client Secret for your Oauth application. | | **Number of concurrent threads** | integer | No | The number of worker threads to use for the sync. Default `20`. | | **Start Date** | string | No | The date from which you'd like to replicate data for User Insights, in the format YYYY-MM-DDT00:00:00Z. All data generated after this date will be replicated. If left blank, the start date will be set to 2 years before the present date. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # TikTok Marketing > Connect TikTok Marketing to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect TikTok Marketing :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose TikTok Marketing.** It is listed under marketing and analytics; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Authentication Method** | one of 2 options | No | | | **Replication Start Date** | string | No | The Start Date in format: YYYY-MM-DD. Any data before this date will not be replicated. If this parameter is not set, all data will be replicated. Default `2016-09-01`. | | **End Date** | string | No | The date until which you'd like to replicate data for all incremental streams, in the format YYYY-MM-DD. All data generated between start_date and this date will be replicated. Not setting this option will result in always syncing the data till the current date. | | **Attribution Window** | integer | No | The attribution window in days. Default `3`. | | **Daily Reports Date Step** | integer | No | The number of days per API request for daily report streams. Use the default 30 for most accounts. If syncs fail with TikTok API error 40067 ("query too large"), reduce this value (e.g., 7 or 1). Smaller values make more API requests but avoid query size limits for accounts with many ads. Default `30`. | | **Include Deleted Data in Reports and Ads, Ad Groups and Campaign streams.** | boolean | No | Set to active if you want to include deleted data in report based streams and Ads, Ad Groups and Campaign streams. Default `false`. | ### Authentication Method Choose one of the following. Each asks for its own fields. **OAuth2.0** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Access Token** | string, secret | Yes | Long-term Authorized Access Token. | | **Advertiser ID** | string | No | The Advertiser ID to filter reports and streams. Let this empty to retrieve all. | | **App ID** | string, secret | Yes | The Developer Application App ID. | | **Secret** | string, secret | Yes | The Developer Application Secret. | **Sandbox Access Token** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Access Token** | string, secret | Yes | The long-term authorized access token. | | **Advertiser ID** | string | Yes | The Advertiser ID which generated for the developer's Sandbox application. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # LinkedIn Ads > Connect LinkedIn Ads to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect LinkedIn Ads :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose LinkedIn Ads.** It is listed under marketing and analytics; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Account IDs** | array | No | Specify the account IDs to pull data from, separated by a space. Leave this field empty if you want to pull the data from all accounts accessible by the authenticated user. | | **Custom Ad Analytics Reports** | array | No | | | **Custom Ad Statistics Reports** | array | No | | | **Authentication** | one of 2 options | No | | | **Lookback Window** | integer | No | How far into the past to look for records. (in days). Default `0`. | | **Number of Workers** | integer | No | The number of workers to use for the connector. This is used to limit the number of concurrent requests to the LinkedIn Ads API. If not set, the default is 3 workers. Default `3`. | | **Start Date** | string | Yes | UTC date in the format YYYY-MM-DD. Any data before this date will not be replicated. | ### Authentication Choose one of the following. Each asks for its own fields. **OAuth2.0** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Client ID** | string, secret | Yes | The client ID of your developer application. | | **Client Secret** | string, secret | Yes | The client secret of your developer application. | | **Refresh Token** | string, secret | Yes | The key to refresh the expired access token. | **Access Token** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Access Token** | string, secret | Yes | The access token generated for your developer application. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # Klaviyo > Connect Klaviyo to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect Klaviyo :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose Klaviyo.** It is listed under marketing and analytics; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Api Key** | string, secret | Yes | Klaviyo API Key. | | **Start Date** | string | No | UTC date and time in the format 2017-01-25T00:00:00Z. Any data before this date will not be replicated. This field is optional. If not provided, becca will replicate the data for the last year. The metrics stream is not affected by this field and always syncs all metric definitions. | | **Disable Fetching Predictive Analytics** | boolean | No | Certain streams like the profiles stream can retrieve predictive analytics data from Klaviyo's API. However, at high volume, this can lead to service availability issues on the API which can be improved by not fetching this field. Warning: Enabling this setting will stop the "predictive_analytics" column from being populated in your downstream destination. | | **Number of concurrent threads** | integer | No | The number of worker threads to use for the sync. The performance upper boundary is based on the limit of your Klaviyo plan. Default `10`. | | **Lookback Window (Days)** | integer | No | The number of days to look back when syncing data in incremental mode. This helps capture any late-arriving data. Only applies to the events_detailed stream. For the flow_series_reports stream, use Reporting Lookback Window instead. Default `0`. | | **Reporting Lookback Window (Days)** | integer | No | Re-syncs the last N days of the flow_series_reports stream on every incremental run. Klaviyo keeps updating conversion attribution after a send (by default for up to 5 days, and up to 90 days if you raise the attribution window in your account settings), so the numbers first reported for a day keep changing for a while. Set this to at least your attribution window to pick those revisions up. Because flow_series_reports rows are keyed by the calendar day they report on, a destination that deduplicates on the primary key replaces the row previously written for a re-synced day, while in append mode each re-synced day adds an extra row per sync. This does not apply to campaign_values_reports: that endpoint returns a single aggregate for the whole requested period instead of a per-day breakdown, so a re-read cannot replace anything and would only add double-counted rows. Defaults to 0 (no re-sync). Default `0`. | | **Report Stream Conversion Metric IDs** | string | No | Comma-separated list of specific metric IDs to use for flow_series_reports and campaign_values_reports streams. If left empty, the connector will automatically fetch reports for ALL available metrics in your account. Due to Klaviyo's strict API rate limits, syncing all metrics can be extremely slow and may take hours to complete. Recommended: Specify only the conversion metrics you need (e.g., "RESQ6t" for Placed Order) to avoid slow syncs. Find metric IDs in your Klaviyo account under Analytics · Metrics, or use the metrics stream to list all available metrics and their IDs. | | **Event Stream Metric IDs** | string | No | Comma-separated list of Klaviyo metric IDs to filter the events and events_detailed streams. If left empty, events for ALL metrics will be synced. Specify only the metrics you need to significantly reduce sync volume and duration. Note: Klaviyo does not support filtering by custom metrics. Only built-in metrics can be filtered. Find metric IDs in your Klaviyo account under Analytics · Metrics, or use the metrics stream to list all available metrics and their IDs. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # Mailchimp > Connect Mailchimp to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect Mailchimp :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose Mailchimp.** It is listed under marketing and analytics; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Number of Concurrent Threads** | integer | No | The number of worker threads to use for the sync. Higher values can speed up syncs but may hit rate limits. Mailchimp allows up to 10 simultaneous connections. Default `2`. | | **Authentication** | one of 2 options | No | | | **Incremental Sync Start Date** | string | No | The date from which you want to start syncing data for Incremental streams. Only records that have been created or modified since this date will be synced. If left blank, all data will by synced. | ### Authentication Choose one of the following. Each asks for its own fields. **OAuth2.0** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Access Token** | string, secret | Yes | An access token generated using the above client ID and secret. | | **Client ID** | string, secret | No | The Client ID of your OAuth application. | | **Client Secret** | string, secret | No | The Client Secret of your OAuth application. | **API Key** | Field | Type | Required | Description | | --- | --- | --- | --- | | **API Key** | string, secret | Yes | Mailchimp API Key. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # Mixpanel > Connect Mixpanel to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect Mixpanel :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose Mixpanel.** It is listed under marketing and analytics; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Authentication** | one of 2 options | Yes | Choose how to authenticate to Mixpanel. | | **Attribution Window** | integer | No | A period of time for attributing results to ads and the lookback period after those actions occur during which ad results are counted. Default attribution window is 5 days. (This value should be non-negative integer). Default `5`. | | **Project Timezone** | string | No | Time zone in which integer date times are stored. The project timezone may be found in the project settings in the Mixpanel console. Default `US/Pacific`. | | **Select Properties By Default** | boolean | No | Setting this config parameter to TRUE ensures that new properties on events and engage records are captured. Otherwise new properties will be ignored. Default `true`. | | **Start Date** | string | No | The date in the format YYYY-MM-DD. Any data before this date will not be replicated. If this option is not set, the connector will replicate data from up to one year ago by default. | | **End Date** | string | No | The date in the format YYYY-MM-DD. Any data after this date will not be replicated. Left empty to always sync to most recent date. | | **Region** | string | No | The region of mixpanel domain instance either US or EU. One of `US`, `EU`. Default `US`. | | **Date slicing window** | integer | No | Defines window size in days, that used to slice through data. You can reduce it, if amount of data in each window is too big for your environment. (This value should be positive integer). Default `30`. | | **Page Size** | integer | No | The number of records to fetch per request for the engage stream. Default is 1000. If you are experiencing long sync times with this stream, try increasing this value. Default `1000`. | | **Export Lookback Window** | integer | No | The number of seconds to look back from the last synced timestamp during incremental syncs of the Export stream. This ensures no data is missed due to delays in event recording. Default is 0 seconds. Must be a non-negative integer. Default `0`. | | **Number of concurrent threads** | integer | No | The number of worker threads to use for the sync. The performance upper boundary is based on the limit of your Mixpanel pricing plan. Default `3`. | ### Authentication Choose how to authenticate to Mixpanel. Choose one of the following. Each asks for its own fields. **Service Account** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Username** | string | Yes | Mixpanel Service Account Username. | | **Secret** | string, secret | Yes | Mixpanel Service Account Secret. | | **Project ID** | integer | Yes | Your project ID number. | **Project Secret** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Project Secret** | string, secret | Yes | Mixpanel project secret. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # Amplitude > Connect Amplitude to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect Amplitude :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose Amplitude.** It is listed under marketing and analytics; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Active Users Group by Country** | boolean | No | According to Amplitude documentation, grouping by `Country` is optional. If you face issues fetching the stream or checking the connection please set this field to `False`. Default `true`. | | **API Key** | string, secret | Yes | Amplitude API Key. | | **Data region** | string | No | Amplitude data region server. One of `Standard Server`, `EU Residency Server`. Default `Standard Server`. | | **Request time range** | integer | No | A time range that is too large in a single request can cause a timeout error. In that case, provide a shorter time interval in hours. Default `24`. | | **Secret Key** | string, secret | Yes | Amplitude Secret Key. | | **Replication Start Date** | string | Yes | UTC date and time in the format 2021-01-25T00:00:00Z. Any data before this date will not be replicated. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # Typeform > Connect Typeform to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect Typeform :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose Typeform.** It is listed under marketing and analytics; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Authorization Method** | one of 2 options | Yes | | | **Start Date** | string | No | The date from which you'd like to replicate data for Typeform API, in the format YYYY-MM-DDT00:00:00Z. All data generated after this date will be replicated. | | **Form IDs to replicate** | array | No | When this parameter is set, the connector will replicate data only from the input forms. Otherwise, all forms in your Typeform account will be replicated. You can find form IDs in your form URLs. For example, in the URL "https://mysite.typeform.com/to/u6nXL7" the form_id is u6nXL7. You can find form URLs on Share panel. | ### Authorization Method Choose one of the following. Each asks for its own fields. **OAuth2.0** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Access token** | string, secret | Yes | Access Token for making authenticated requests. | | **Client id** | string, secret | Yes | The Client ID of the Typeform developer application. | | **Client secret** | string, secret | Yes | The Client Secret the Typeform developer application. | | **Refresh token** | string, secret | Yes | The key to refresh the expired access_token. | | **Token expiry date** | string | Yes | The date-time when the access token should be refreshed. | **Private Token** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Private Token** | string, secret | Yes | Log into your Typeform account and then generate a personal Access Token. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # Microsoft Advertising > Connect Microsoft Advertising to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect Microsoft Advertising :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose Microsoft Advertising.** It is listed under marketing and analytics; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Tenant ID** | string, secret | No | The Tenant ID of your Microsoft Advertising developer application. Set this to "common" unless you know you need a different value. | | **Client ID** | string, secret | Yes | The Client ID of your Microsoft Advertising developer application. | | **Client Secret** | string, secret | No | The Client Secret of your Microsoft Advertising developer application. | | **Refresh Token** | string, secret | Yes | Refresh Token to renew the expired Access Token. | | **Developer Token** | string, secret | Yes | Developer token associated with user. | | **Account Names Predicates** | array | No | Predicates that will be used to sync data by specific accounts. | | **Reports replication start date** | string | No | The start date from which to begin replicating report data. Any data generated before this date will not be replicated in reports. This is a UTC date in YYYY-MM-DD format. If not set, data from previous and current calendar year will be replicated. | | **Lookback window** | integer | No | Also known as attribution or conversion window. How far into the past to look for records (in days). If your conversion window has an hours/minutes granularity, round it up to the number of days exceeding. Used only for performance report streams in incremental mode without specified Reports Start Date. Default `0`. | | **Number of concurrent workers** | integer | No | The number of worker threads to use for the sync. Increase this to speed up syncs for accounts with many reports. The default should work for most use cases. Default `10`. | | **Custom Reports** | array | No | You can add your Custom Bing Ads report by creating one. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # Pinterest > Connect Pinterest to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect Pinterest :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose Pinterest.** It is listed under marketing and analytics; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Number of concurrent workers** | integer | No | The number of worker threads to use for the sync. Higher values can speed up syncs but may increase rate-limit pressure against Pinterest. Default `2`. | | **Account ID** | string | No | The Pinterest account ID you want to fetch data for. This ID must be provided to filter the data for a specific account. | | **OAuth2.0** | object | No | | | **Custom Reports** | array | No | A list which contains ad statistics entries, each entry must have a name and can contains fields, breakdowns or action_breakdowns. | | **Start Date** | string | No | A date in the format YYYY-MM-DD. If you have not set a date, it would be defaulted to latest allowed date by api (89 days from today). | | **Status** | array | No | For the ads, ad_groups, and campaigns streams, specifying a status will filter out records that do not match the specified ones. If a status is not specified, the source will default to records with a status of either ACTIVE or PAUSED. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # Snapchat Ads > Connect Snapchat Ads to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect Snapchat Ads :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose Snapchat Ads.** It is listed under marketing and analytics; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Client ID** | string, secret | Yes | The Client ID of your Snapchat developer application. | | **Client Secret** | string, secret | Yes | The Client Secret of your Snapchat developer application. | | **Refresh Token** | string, secret | Yes | Refresh Token to renew the expired Access Token. | | **Start Date** | string | No | Date in the format 2022-01-01. Any data before this date will not be replicated. Default `2022-01-01`. | | **End Date** | string | No | Date in the format 2017-01-25. Any data after this date will not be replicated. | | **Action Report Time** | string | No | Specifies the principle for conversion reporting. One of `conversion`, `impression`. Default `conversion`. | | **Swipe Up Attribution Window** | string | No | Attribution window for swipe ups. One of `1_DAY`, `7_DAY`, `28_DAY`. Default `28_DAY`. | | **View Attribution Window** | string | No | Attribution window for views. One of `none`, `1_HOUR`, `3_HOUR`, `6_HOUR`, `1_DAY`, `7_DAY`. Default `1_DAY`. | | **Organization IDs** | array | No | The IDs of the organizations to retrieve. | | **Ad Account IDs** | array | No | Ad Account IDs of the ad accounts to retrieve. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # Iterable > Connect Iterable to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect Iterable :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose Iterable.** It is listed under marketing and analytics; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **API Key** | string, secret | Yes | Iterable API Key. | | **Start Date** | string | Yes | The date from which you'd like to replicate data for Iterable, in the format YYYY-MM-DDT00:00:00Z. All data generated after this date will be replicated. | | **Region** | string | No | The region where your Iterable account is hosted. Select 'EU' if your account is on the European data center. One of `US`, `EU`. Default `US`. | | **Lookback Window (Minutes)** | integer | No | Number of minutes to re-read from the current time when determining the end of each sync window for export-based streams. This accounts for eventual consistency delays in Iterable's Export API, preventing silent data loss for events not yet indexed. Increase this value if you observe missing events near the end of sync windows. Default `5`. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # SendGrid > Connect SendGrid to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect SendGrid :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose SendGrid.** It is listed under marketing and analytics; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Start date** | string | Yes | UTC date and time in the format 2017-01-25T00:00:00Z. Any data before this date will not be replicated. | | **API Key** | string, secret | Yes | Sendgrid API Key, use admin to generate this key. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # Braze > Connect Braze to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect Braze :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose Braze.** It is listed under marketing and analytics; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **URL** | string | Yes | Braze REST API endpoint. | | **Rest API Key** | string, secret | Yes | Braze REST API key. | | **Start date** | string | Yes | Rows after this date will be synced. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # PostHog > Connect PostHog to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect PostHog :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose PostHog.** It is listed under marketing and analytics; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Events stream slice step size (in days)** | integer | No | Set lower value in case of failing long running sync of events stream. Default `30`. | | **API Key** | string, secret | Yes | | | **Base URL** | string | No | Base PostHog url. Defaults to PostHog Cloud (https://app.posthog.com). Default `https://app.posthog.com`. | | **Start Date** | string | Yes | The date from which you'd like to replicate the data. Any data before this date will not be replicated. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # SurveyMonkey > Connect SurveyMonkey to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect SurveyMonkey :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose SurveyMonkey.** It is listed under marketing and analytics; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Origin datacenter of the SurveyMonkey account** | string | No | Depending on the originating datacenter of the SurveyMonkey account, the API access URL may be different. One of `USA`, `Europe`, `Canada`. Default `USA`. | | **SurveyMonkey Authorization Method** | object | Yes | The authorization method to use to retrieve data from SurveyMonkey. | | **Start Date** | string | Yes | UTC date and time in the format 2017-01-25T00:00:00Z. Any data before this date will not be replicated. | | **Survey Monkey survey IDs** | array | No | IDs of the surveys from which you'd like to replicate data. If left empty, data from all boards to which you have access will be replicated. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # GitHub > Connect GitHub to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect GitHub :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose GitHub.** It is listed under product and operations; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Authentication** | one of 2 options | Yes | Choose how to authenticate to GitHub. | | **GitHub Repositories** | array | Yes | List of GitHub organizations/repositories, e.g. `your-org/your-repo` for single repository, `your-org/*` for get all repositories from organization and `your-org/a*` for matching multiple repositories by pattern. | | **Start date** | string | No | The date from which you'd like to replicate data from GitHub in the format YYYY-MM-DDT00:00:00Z. If the date is not set, all data will be replicated. For the streams which support this configuration, only data generated on or after the start date will be replicated. | | **API URL** | string | No | Please enter your basic URL from self-hosted GitHub instance or leave it empty to use GitHub. Default `https://api.github.com/`. | | **Branches** | array | No | List of GitHub repository branches to pull commits for, e.g. `your-org/your-repo/master`. If no branches are specified for a repository, the default branch will be pulled. | | **Max Waiting Time (in minutes)** | integer | No | Max time (in minutes) the connector will wait when all API tokens are rate-limited before failing. GitHub rate limits reset every 60 minutes, so values above 60 allow the connector to wait for a full reset cycle. Default `120`. | | **Number of Concurrent Threads** | integer | No | Number of concurrent threads for syncing. Higher values can speed up syncs but increase the risk of hitting GitHub's secondary rate limits. Default `4`. | ### Authentication Choose how to authenticate to GitHub. Choose one of the following. Each asks for its own fields. **OAuth** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Access Token** | string, secret | Yes | OAuth access token. | | **Client Id** | string, secret | No | OAuth Client Id. | | **Client secret** | string, secret | No | OAuth Client secret. | **Personal Access Token** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Personal Access Tokens** | string, secret | Yes | Log into GitHub and then generate a personal access token. To load balance your API quota consumption across multiple API tokens, input multiple tokens separated with ",". | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # Jira > Connect Jira to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect Jira :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose Jira.** It is listed under product and operations; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Authentication** | one of 3 options | Yes | Choose how to authenticate to Jira. | | **Domain** | string | Yes | Your Jira host (full domain: do not include 'https://' or paths). Examples: your-domain.atlassian.net, your-domain.jira.com, jira.your-domain.com. | | **Projects** | array | No | List of Jira project keys to replicate data for, or leave it empty if you want to replicate data for all projects. | | **Start Date** | string | No | The date from which you want to replicate data from Jira, use the format YYYY-MM-DDT00:00:00Z. Note that this field only applies to certain streams, and only data generated on or after the start date will be replicated. Or leave it empty if you want to replicate all data. | | **Lookback window** | integer | No | When set to N, the connector will always refresh resources created within the past N minutes. By default, updated objects that are not newly created are not incrementally synced. Default `0`. | | **Number of concurrent threads** | integer | No | The number of worker threads to use for the sync. Default `5`. | ### Authentication Choose how to authenticate to Jira. Choose one of the following. Each asks for its own fields. **API Token** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Email** | string | Yes | The user email for your Jira account which you used to generate the API token. This field is used for Authorization to your account by BasicAuth. | | **API Token** | string, secret | Yes | Jira API Token. API Token is used for Authorization to your account by BasicAuth. | **Service Account** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Service Account Token** | string, secret | Yes | Atlassian Service Account API token. Service account API tokens authenticate with Bearer authentication and use Atlassian's Platform API Gateway. | **OAuth2.0** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Client ID** | string, secret | Yes | The Client ID of your Atlassian OAuth application. | | **Client Secret** | string, secret | Yes | The Client Secret of your Atlassian OAuth application. | | **Refresh Token** | string, secret | Yes | The Refresh Token obtained after completing the OAuth flow. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # Notion > Connect Notion to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect Notion :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose Notion.** It is listed under product and operations; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Authentication Method** | one of 2 options | No | Choose either OAuth or Access Token. | | **Number of concurrent workers** | integer | No | Number of worker threads to use for the sync. Higher values can speed up large syncs but may increase rate-limit pressure against Notion's limit of approximately three requests per second per integration. Default `3`. | | **Start Date** | string | No | UTC date and time in the format YYYY-MM-DDTHH:MM:SS.000Z. During incremental sync, any data generated before this date will not be replicated. If left blank, the start date will be set to 2 years before the present date. | ### Authentication Method Choose either OAuth or Access Token. Choose one of the following. Each asks for its own fields. **OAuth2.0** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Access Token** | string, secret | Yes | The Access Token received by completing the OAuth flow for your Notion integration. | | **Client ID** | string, secret | Yes | The Client ID of your Notion integration. | | **Client Secret** | string, secret | Yes | The Client Secret of your Notion integration. | **Access Token** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Access Token** | string, secret | Yes | The Access Token for your private Notion integration. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # Airtable > Connect Airtable to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect Airtable :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose Airtable.** It is listed under product and operations; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Authentication** | one of 2 options | No | | | **Add Base ID to Stream Name** | boolean | No | When enabled, includes the base ID in stream names to ensure uniqueness. Use this if you have cloned Airtable bases with duplicate table names. Note that enabling this will change stream names and require a full refresh. Default `false`. | | **Number of Concurrent Threads** | integer | No | Number of concurrent threads for syncing. Higher values can speed up syncs but may hit rate limits. Airtable limits to 5 requests per second per base. Default `5`. | ### Authentication Choose one of the following. Each asks for its own fields. **OAuth2.0** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Access token** | string, secret | No | Access Token for making authenticated requests. | | **Client ID** | string, secret | Yes | The client ID of the Airtable developer application. | | **Client Secret** | string, secret | Yes | The client secret of the Airtable developer application. | | **Refresh token** | string, secret | Yes | The key to refresh the expired access token. | | **Token expiry date** | string | No | The date-time when the access token should be refreshed. | **Personal Access Token** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Personal Access Token** | string, secret | Yes | The Personal Access Token for the Airtable account. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # Slack > Connect Slack to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect Slack :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose Slack.** It is listed under product and operations; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Number of concurrent threads** | integer | No | The number of worker threads to use for the sync. Default `2`. | | **Channel messages date window size (in days)** | integer | No | The size (in days) of the date window that will be used while syncing data from the channel messages stream. A smaller window will allow for greater parallelization when syncing records, but can lead to rate limiting errors. Default `100`. | | **Channel name filter** | array | No | A channel name list (without leading '#' char) which limit the channels from which you'd like to sync. Empty list means no filter. | | **Authentication mechanism** | one of 2 options | No | Choose how to authenticate into Slack. | | **Include archived channels** | boolean | No | Whether to include archived channels in the sync. When disabled (default), archived channels are excluded from the Slack API response, reducing the number of API calls for downstream streams such as channel_messages, threads, and channel_members. Enable this option if you need to sync data from archived channels. Default `false`. | | **Include private channels** | boolean | No | Whether to read information from private channels that the bot is already in. If false, only public channels will be read. If true, the bot must be manually added to private channels. Default `false`. | | **Join all channels** | boolean | Yes | Whether to join all channels or to sync data only from channels the bot is already in. If false, you''ll need to manually add the bot to all the channels from which you''d like to sync messages. Default `true`. | | **Threads Lookback window (Days)** | integer | Yes | How far into the past to look for messages in threads, default is 0 days. Default `0`. | | **Start Date** | string | Yes | UTC date and time in the format 2017-01-25T00:00:00Z. Any data before this date will not be replicated. | | **Ignore messages with no replies in threads stream** | boolean | No | When enabled, the threads stream will skip messages that have no replies (reply_count is 0, null, or absent), reducing the number of API calls. Disabled by default to make Threads stream contain unthreaded messages in its records. Default `false`. | ### Authentication mechanism Choose how to authenticate into Slack. Choose one of the following. Each asks for its own fields. **Sign in via Slack (OAuth)** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Access token** | string, secret | Yes | Slack access_token. | | **Client ID** | string | Yes | Slack client_id. | | **Client Secret** | string, secret | Yes | Slack client_secret. | **Bot Token** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Bot Token** | string, secret | Yes | A Slack bot token (xoxb-). | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # Asana > Connect Asana to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect Asana :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose Asana.** It is listed under product and operations; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Authentication mechanism** | one of 2 options | No | Choose how to authenticate to Github. | | **Number of concurrent threads** | integer | No | The number of worker threads to use for the sync. The performance upper boundary is based on the limit of your Asana pricing plan. Default `10`. | | **Organization Export IDs** | array | No | Globally unique identifiers for the organization exports. | ### Authentication mechanism Choose how to authenticate to Github. Choose one of the following. Each asks for its own fields. **Authenticate via Asana (Oauth)** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Client id** | string, secret | Yes | | | **Client secret** | string, secret | Yes | | | **Refresh token** | string, secret | Yes | | **Authenticate with Personal Access Token** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Personal Access Token** | string, secret | Yes | Asana Personal Access Token. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # GitLab > Connect GitLab to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect GitLab :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose GitLab.** It is listed under product and operations; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Authorization Method** | one of 2 options | Yes | | | **Start Date** | string | No | The date from which you'd like to replicate data for GitLab API, in the format YYYY-MM-DDT00:00:00Z. Optional. If not set, all data will be replicated. All data generated after this date will be replicated. | | **API URL** | string | No | Please enter your basic URL from GitLab instance. Default `gitlab.com`. | | **Groups** | array | No | List of groups. e.g. your-group. | | **Projects** | array | No | Space-delimited list of projects. e.g. your-group/your-project your-group/other-project. | | **Number of Concurrent Threads** | integer | No | Number of concurrent threads for syncing. Higher values can speed up syncs but may hit rate limits. Adjust based on your GitLab instance rate limits. Default `4`. | ### Authorization Method Choose one of the following. Each asks for its own fields. **OAuth2.0** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Access token** | string, secret | Yes | Access Token for making authenticated requests. | | **Client id** | string, secret | Yes | The API ID of the Gitlab developer application. | | **Client secret** | string, secret | Yes | The API Secret the Gitlab developer application. | | **Refresh token** | string, secret | Yes | The key to refresh the expired access_token. | | **Token expiry date** | string | Yes | The date-time when the access token should be refreshed. | **Private Token** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Private Token** | string, secret | Yes | Log into your Gitlab account and then generate a personal Access Token. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # Monday > Connect Monday to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect Monday :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose Monday.** It is listed under product and operations; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Boards to sync** | array | No | The IDs of the boards that the Items and Boards streams will extract records from. When left empty, streams will extract records from all boards that exist within the account. | | **Authorization Method** | one of 2 options | No | | | **Number of concurrent threads** | integer | No | The number of worker threads to use for the sync. Default `4`. | ### Authorization Method Choose one of the following. Each asks for its own fields. **OAuth2.0** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Subdomain/Slug** | string | No | Slug/subdomain of the account, or the first part of the URL that comes before .monday.com. | | **Access Token** | string, secret | Yes | Access Token for making authenticated requests. | | **Client ID** | string, secret | Yes | The Client ID of your OAuth application. | | **Client Secret** | string, secret | Yes | The Client Secret of your OAuth application. | **API Token** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Personal API Token** | string, secret | Yes | API Token for making authenticated requests. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # ClickUp > Connect ClickUp to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect ClickUp :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose ClickUp.** It is listed under product and operations; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Api token** | string, secret | Yes | Every ClickUp API call required authentication. This field is your personal API token. | | **Include Closed Tasks** | boolean | No | Include or exclude closed tasks. By default, they are excluded. Default `false`. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # Zoom > Connect Zoom to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect Zoom :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose Zoom.** It is listed under product and operations; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Account id** | string | Yes | The account ID for your Zoom account. You can find this in the Zoom Marketplace under the "Manage" tab for your app. | | **Client id** | string | Yes | The client ID for your Zoom app. You can find this in the Zoom Marketplace under the "Manage" tab for your app. | | **Client secret** | string, secret | Yes | The client secret for your Zoom app. You can find this in the Zoom Marketplace under the "Manage" tab for your app. | | **Authorization endpoint** | string | Yes | Default `https://zoom.us/oauth/token`. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # Twilio > Connect Twilio to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect Twilio :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose Twilio.** It is listed under product and operations; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Account ID** | string, secret | Yes | Twilio account SID. | | **Auth Token** | string, secret | Yes | Twilio Auth Token. | | **Replication Start Date** | string | Yes | UTC date and time in the format 2020-10-01T00:00:00Z. Any data before this date will not be replicated. | | **Lookback window** | integer | No | How far into the past to look for records. (in minutes). Default `0`. | | **Number of concurrent threads** | integer | No | The number of worker threads to use for the sync. Default `3`. | | **Slice Step Duration** | string | No | The time window size for each data slice when syncing incremental streams. Smaller windows may help avoid timeouts for accounts with large data volumes. One of `P1D`, `P1W`, `P1M`, `P1Y`. Default `P1M`. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # Harvest > Connect Harvest to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect Harvest :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose Harvest.** It is listed under product and operations; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Authentication mechanism** | one of 2 options | No | Choose how to authenticate to Harvest. | | **Account ID** | string, secret | Yes | Harvest account ID. Required for all Harvest requests in pair with Personal Access Token. | | **Start Date** | string | Yes | UTC date and time in the format 2017-01-25T00:00:00Z. Any data before this date will not be replicated. | | **Number of Concurrent Threads** | integer | No | Number of concurrent threads for syncing. Higher values can speed up syncs but may increase API rate limit usage. Adjust based on your Harvest API plan. Default `4`. | ### Authentication mechanism Choose how to authenticate to Harvest. Choose one of the following. Each asks for its own fields. **Authenticate via Harvest (OAuth)** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Client ID** | string | Yes | The Client ID of your Harvest developer application. | | **Client Secret** | string, secret | Yes | The Client Secret of your Harvest developer application. | | **Refresh Token** | string, secret | Yes | Refresh Token to renew the expired Access Token. | **Authenticate with Personal Access Token** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Personal Access Token** | string, secret | Yes | Log into Harvest and then create new personal access token. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # BambooHR > Connect BambooHR to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect BambooHR :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose BambooHR.** It is listed under people; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **api_key** | string, secret | Yes | Api key of bamboo hr. | | **subdomain** | string | Yes | Sub Domain of bamboo hr. | | **custom_reports_fields** | string | No | Comma-separated list of fields to include in custom reports. | | **custom_reports_include_default_fields** | boolean | No | If true, the custom reports endpoint will include the default fields defined here: https://documentation.bamboohr.com/docs/list-of-field-names. Default `true`. | | **employee_fields** | string | No | Comma-separated list of fields to include for employees. Default `firstName,lastName`. | | **Start date** | string | No | | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # Greenhouse > Connect Greenhouse to sanda: what to prepare, the fields the connection form asks for, and how its data lands in your warehouse. ## Connect Greenhouse :::steps 1. **Open the catalogue.** In the console, go to **Data · Connections** and press **New connection**. 2. **Choose Greenhouse.** It is listed under people; you can also search for it by name. 3. **Fill in the form.** Enter the fields below. Credentials go straight to sanda’s sync engine and are never stored in sanda’s database. 4. **Choose what to sync.** Pick the streams you want, and a sync mode for each. See [choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams). 5. **Run the first sync.** sanda tests the connection, then lands each stream as a table in your warehouse. ::: ## Configuration fields What the connection form asks for. Required fields are marked; the rest are optional. | Field | Type | Required | Description | | --- | --- | --- | --- | | **Authentication** | one of 2 options | Yes | | | **Start date** | string | No | UTC date and time in the format YYYY-MM-DDTHH:MM:SSZ. Records updated before this date will not be replicated. If omitted, the connector replicates all history. | | **Number of concurrent threads** | integer | No | The number of worker threads to use for syncing. Greenhouse publishes no Harvest v3 rate-limit ceiling, so the default is conservative and all threads share a single budget. Increase this only if your Greenhouse account can support higher API usage, or lower it if you see rate-limit errors. Default `2`. | ### Authentication Choose one of the following. Each asks for its own fields. **OAuth** | Field | Type | Required | Description | | --- | --- | --- | --- | | **OAuth client ID** | string, secret | Yes | | | **OAuth client secret** | string, secret | Yes | | | **Refresh token** | string, secret | Yes | The refresh token obtained from the Greenhouse authorization code exchange. Populated automatically when you authorize above. | | **Access token** | string, secret | No | | | **Token expiry date** | string | No | | **Client Credentials** | Field | Type | Required | Description | | --- | --- | --- | --- | | **Client ID** | string, secret | Yes | The client ID of your Greenhouse custom integration credential. | | **Client secret** | string, secret | Yes | The client secret of your Greenhouse custom integration credential. | | **Site Admin user ID** | integer | No | Optional. Leave blank to authorize as the credential's integration service user, which can read every Harvest v3 list endpoint. If you set it, it must be the Greenhouse user ID of a Site Admin; any other user is denied access to the list endpoints the connector reads. | ## Related :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How sanda reads each stream on every run. - [Sync schedules](https://docs.sanda-os.com.au/connections/schedules): How often a connection runs. - [Allowlist sanda’s address](https://docs.sanda-os.com.au/connections/allowlist): For a system behind a firewall. - [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting): What an error means and how to fix it. ::: --- # MYOB > Bring MYOB data into sanda. It has no self-serve form, so the sanda team sets the connection up with you. ## Connect MYOB MYOB has no self-serve form. Choose it when you add a connection and sanda opens a conversation with the team, who set the connection up with you and wire it by hand. :::links - [Add a connection](https://docs.sanda-os.com.au/connections/add-a-connection): The steps in the console. - [Getting help](https://docs.sanda-os.com.au/help): How to reach the sanda team. ::: --- # Something else > Bring Something else data into sanda. It has no self-serve form, so the sanda team sets the connection up with you. ## Connect Something else If the system you use is not in the catalogue, sanda can still bring its data in. Choose **Something else** when you add a connection, tell us what the system is, and the sanda team will work out the best route with you. :::links - [Add a connection](https://docs.sanda-os.com.au/connections/add-a-connection): The steps in the console. - [Getting help](https://docs.sanda-os.com.au/help): How to reach the sanda team. ::: --- # MCP and agents > Connect Claude, ChatGPT or another MCP client to your semantic fluid, with scoped permissions, approvals and a record of every call. The Model Context Protocol (MCP) is an open standard that lets an AI assistant call tools on another system. Every sanda workspace is an MCP server. Point Claude, ChatGPT or an agent of your own at it, and the assistant can find your agreed metrics, look closely at a table, and get rows back for a question, without a database connection and without guessing what "revenue" means. The server is one address for every workspace. Who is asking decides which workspace answers. ```text https://console.sanda-os.com.au/api/mcp ``` ## What an assistant gets An assistant reads through [the semantic fluid](https://docs.sanda-os.com.au/semantic-fluid), not through your warehouse directly. It names what it wants by reference (a metric, a dimension, a filter) and sanda composes the SQL, resolves the joins and runs it. The number that comes back is your workspace's own definition of that number, the same one sanda's chat and your reports use. Asking questions is the baseline. Anything more is a permission that someone grants on purpose: | Permission | What it lets an assistant do | |---|---| | Ask questions (on every integration token, and ticked by default when you connect an app) | Find metrics, dimensions and tables, describe a table, run composed queries, search the text inside rows, look up how sanda works in these docs | | Run its own SQL | Send a read-only `SELECT` when the fluid cannot express a question | | See connections and warehouses | Check whether data is fresh and how full a warehouse is | | Start a sync | Run a connection now | | Write the fluid | Put tables on the map and define metrics, dimensions and filters, live at once | | Load and add rows | Land tables of its own rows, or append to tables you have opened for intake | | Define views and procedures | Build and schedule derived views on your compute | | Run and commit SQL | One statement at a time, committed, with no undo | | Manage search | Create and rebuild sanda search services | | Email reports | Schedule report deliveries and send them | An assistant can also look up how sanda works. The `search_docs` tool searches these docs, the same ones you are reading, and returns the matching sections with their links, so an assistant can answer "how do I connect a source?" from the page rather than from memory. It reads no data of yours, needs only `fluid:read` and costs nothing on the AI limit. The full list is in [scopes](https://docs.sanda-os.com.au/reference/scopes), and every tool, with its parameters, is in the [MCP tool reference](https://docs.sanda-os.com.au/mcp/tools). ## What an assistant cannot do Nothing here widens what sanda can see. It changes who is holding the map. - **It reads only what you have modelled and accepted.** Proposals waiting for review are not visible, and neither is a table that is not on the map. - **Questions go through a read-only role.** Raw landed data, other workspaces and anything outside the accepted fluid are out of reach, whatever SQL an assistant writes. The permissions that write are the exception, and each says so in plain words on the consent screen. - **Answers are capped.** A question returns at most 50 rows, and each statement has a timeout. - **A tool it may not use is not offered.** It is absent from the tool list rather than present and refusing, so an assistant never tries what it cannot do. - **It never sees a credential.** Connection lists and warehouse lists never include a password or connection string. - **Writes exist only behind a permission.** A connection that was only granted questions cannot change anything. ## Two ways to connect | | Sign in with OAuth | Hold an integration token | |---|---|---| | For | A person connecting an assistant such as Claude or ChatGPT to their own workspace | A machine: Claude Code, the Claude API, an agent harness of your own | | Set up by | Pressing **Allow** on sanda's consent screen | An owner or admin creating a token and pasting it into the client | | Acts for | The person who approved it | The owner or admin who created it | | Ends when | You disconnect it, or you leave the workspace | The token is revoked or expires, or its creator leaves the workspace | If a client offers both, use OAuth: there is no secret to store. Step by step: :::links - [Connect Claude](https://docs.sanda-os.com.au/mcp/connect-claude): Add sanda to Claude as a connector, or to Claude Code and the Claude API with a token. - [Connect other MCP clients](https://docs.sanda-os.com.au/mcp/connect-other-clients): ChatGPT, and any client that speaks MCP, including the sign-in protocol and a curl example. - [Agent tokens](https://docs.sanda-os.com.au/mcp/agent-tokens): Create, scope, rotate and revoke integration tokens. ::: ## Editions MCP and agents are included on every edition, and on a trial. The search tools need sanda search, which is included from Standard. The Excel and Power BI feed and its tokens work on every edition. See [editions](https://docs.sanda-os.com.au/billing/editions). sanda search tools follow sanda search, which is also on Standard and Enterprise. ## Watch what agents do Open **Intelligence · Agents** in the console. Every member can see it. It answers the question the credential screen cannot: is it working? - **Harness** lists four things that must be true before an agent can do anything: a healthy warehouse, definitions on the map, a live credential, and (optionally) text an agent can search. Each one that is not true links to the page that fixes it. - **Agents** has one row per integration token or connected app, with what each may do in plain sentences, the permissions that go beyond asking questions marked, and when it was last used. - **Activity** has four tabs. **Sessions** groups one credential's calls with no gap of 15 minutes or more, because MCP holds no session of its own. **Calls** is the live feed. **Refused** lists what sanda declined to run at all, such as an unknown tool or a missing permission. **By tool** shows which tools are leaned on and which keep answering no. - **Approvals**, for owners and admins, holds the risky actions an agent asked for until a person decides. A tool that answers no and a request sanda refuses are different things. An answer of no ran and explained itself in a sentence the assistant can act on ("those two tables fan out"). A refusal means sanda declined to run the tool: an unknown tool, a permission the credential does not hold, or a protocol mistake by the client. **Refused** is the tab to read when an assistant says it cannot see anything. The activity feed shows that a call happened and how it went. It does not keep the rows that came back. ## Every action is recorded and approvable Every tool call is recorded with the credential that made it, the tool, how long it took and whether it answered no. Changes an agent makes go through the same handlers as the console's own buttons, with the same validation and the same audit record, so a metric an assistant defines is checked exactly as one you define yourself. For the few actions that cannot be undone (committing SQL, dropping a view or a search service, emailing a report to an address outside your members' email domains), an owner or admin can require a person to approve first. The action waits, the owners and admins are emailed, and nothing runs until someone presses **Approve and run**. Approval is off by default. See [permissions and approvals](https://docs.sanda-os.com.au/mcp/permissions). ## cherry is not MCP cherry, sanda's own agent, lives in the console and is on every edition. It uses many of the same tools under your own role, with its own approval rules. See [cherry](https://docs.sanda-os.com.au/cherry). --- # Connect Claude > Add sanda to Claude as a custom connector, sign in, choose what Claude may do, and check that it works. Claude Code and the Claude API use a token. Claude on the web and Claude Desktop connect to sanda as a custom connector. You paste one address, sign in to sanda, and choose what Claude may do. There is no key to copy and nothing to store. Claude Code and the Claude API are set up once by a person and then used by a machine, so they hold an [integration token](https://docs.sanda-os.com.au/mcp/agent-tokens) instead. Both are covered below. ## Before you start - **Your workspace is on Standard or Enterprise, or on a trial.** On Basic, sanda refuses the connection. See [editions](https://docs.sanda-os.com.au/billing/editions). - **You are a member of the workspace.** Any role except reader can connect Claude to ask questions. Only an owner or admin can grant the permissions that write, run SQL or send email. A person with the reader role cannot connect anything. - **Something is on the map.** Claude answers from [the semantic fluid](https://docs.sanda-os.com.au/semantic-fluid), so it needs at least one table there, and a metric to be useful. **Intelligence · Agents** shows a **Harness** panel that tells you which of these is missing. - **Your Claude plan allows custom connectors.** If your Claude organisation is managed by an administrator, they may need to add the connector for the organisation first. ## Connect Claude on the web or in Claude Desktop Menu names in Claude change from time to time, so look for the connectors section of Claude's settings. What you need is a name for the connector and sanda's address. :::steps 1. **Copy sanda's address.** In the console, open **Settings · Integrations** and press the copy button beside **MCP endpoint**. It is the same for every workspace: ```text https://console.sanda-os.com.au/api/mcp ``` 2. **Add a custom connector in Claude.** Open Claude's settings, go to its connectors, and choose to add a custom connector. 3. **Name it and paste the address.** Call it `sanda` and paste the address. You do not need a client ID or a secret. If the form offers advanced settings for them, leave them empty. 4. **Connect.** Claude opens sanda in a browser tab. If you are not signed in to sanda, sign in first. You come back to the consent screen on your own afterwards. 5. **Read the consent screen** and choose what Claude may do (below). 6. **Press Allow.** Claude takes you back and the connector shows as connected. ::: ### The consent screen The screen has one job, which is to ask you a clear question. It is deliberately bare, with no console navigation around it. | Part | What it tells you | |---|---| | The heading, which is the app's name followed by "wants to use your sanda data" | Which app is asking, and the account you are signed in as. Only you can approve it, and you can disconnect it later under **Settings · Integrations** | | **It will be sent back to** | The web address sanda will return your browser to when you finish. Check it belongs to the app you started from | | **Which workspace** | The one workspace this grant reaches. If you belong to several, pick one | | **What it will be able to do** | One tick box per permission, each written as a sentence | The reading permissions are ticked: asking questions and, if the app asked for it, seeing connections and warehouses. Every permission that writes, runs SQL of its own or starts something starts **unticked**, and you tick only what the job needs. If you are not an owner or admin, the permissions that only an owner or admin can grant are greyed out with the note "Only a workspace owner or admin can grant this. It will be left out." The note under the tick boxes follows what is ticked. While everything ticked only reads, it says the app can write nothing. Tick a permission that changes something (committing SQL, changing what is modelled, loading or adding rows, defining views, managing search, emailing reports or starting syncs) and the note says so instead, naming each one, with a warning sign beside it. Press **Allow** followed by the app's name to approve, or **Refuse** to stop. **Allow** stays disabled until a workspace is chosen and at least one thing is ticked. :::note If the consent screen warns that "This client runs on your own machine", the app asking is running on your computer and sanda cannot verify which application is listening. Approve it only if you just started the connection yourself from software you installed. ::: Start with the defaults. You can connect again later with different permissions: approving the same app again replaces the earlier grant. To widen what Claude may do, disconnect the connector in Claude, add it again, and tick more on the consent screen. ## Check that it works :::steps 1. **Start a new conversation in Claude** and make sure the sanda connector is switched on for it. 2. **Ask a question of your data.** For example: "What metrics does sanda have?" Claude should call `search_fluid` and list what your workspace has modelled. 3. **Open Intelligence · Agents in the console.** The connector appears under **Agents** as a connected app, and the call appears under **Activity** within about ten seconds. The **Sessions** tab refreshes itself every 10 seconds while you watch it. ::: If Claude says it cannot see any metrics, read the **Harness** panel first. The commonest cause is that definitions are waiting for review: an assistant sees only what has been accepted. ## Connect Claude Code Claude Code holds an integration token. An owner or admin [creates one](https://docs.sanda-os.com.au/mcp/agent-tokens#create-a-token), then you add sanda as a remote server: ```bash title="Add sanda to Claude Code" claude mcp add --transport http sanda https://console.sanda-os.com.au/api/mcp \ --header "Authorization: Bearer bat_your_token_here" ``` The console shows this command with your new token already filled in, once, when you create the token. ## Connect the Claude API To give a Claude API request access to sanda, add sanda to the request's `mcp_servers` and add a matching toolset: ```json title="A Messages API request, abridged" { "mcp_servers": [ { "type": "url", "url": "https://console.sanda-os.com.au/api/mcp", "name": "sanda", "authorization_token": "bat_your_token_here" } ], "tools": [{ "type": "mcp_toolset", "mcp_server_name": "sanda" }] } ``` The MCP connector is a beta feature of the Claude API, so the request also needs its beta flag. Check Claude's documentation for the current name. ## Disconnect Claude To end access, open **Settings · Integrations**. A connected app is listed with the label "connected app", who connected it and when it was last used. Press **Disconnect**. The next call Claude makes fails. Removing the connector inside Claude does not by itself tell sanda, so disconnect it here too. Owners and admins see every member's connected apps and can disconnect any of them. Everyone else sees their own. A connection also stops when the person who approved it leaves the workspace. If they stay but are no longer an owner or admin, the permissions that need one are ignored and the row is marked "permissions inactive". Everything else it holds still works. ## If it does not connect | You see | Why | What to do | |---|---|---| | "This workspace requires two-step sign-in" on the consent screen | The workspace you chose asks every member to set up two-step sign-in, and you have not. This applies to the workspace you connect, even if you are signed in to a different one | Set up an authenticator app in the console, then connect again | | "Your access to this workspace is to its reports portal, so it cannot be connected to an assistant" | Your role is reader | Ask an owner or admin to connect it, or to change your role. See [members and roles](https://docs.sanda-os.com.au/workspace/members-and-roles) | | "That redirect address is not one this client has registered" | The app asked sanda to send you somewhere it never registered, so sanda refused | Close the tab and start again from the app. Nothing was granted | | Claude connects but a tool is missing | The tool needs a permission you did not tick | Disconnect, connect again and tick it. Permissions that write need an owner or admin | | Claude connects but finds no metrics | Nothing on the map, or definitions still waiting for review | Check **Harness** on **Intelligence · Agents**, then review proposals in [the semantic fluid](https://docs.sanda-os.com.au/semantic-fluid) | More help is in [troubleshooting](https://docs.sanda-os.com.au/help/troubleshooting). ## Next steps :::links - [Permissions and approvals](https://docs.sanda-os.com.au/mcp/permissions): What each permission allows, and how to make risky actions wait for a person. - [Connect other MCP clients](https://docs.sanda-os.com.au/mcp/connect-other-clients): ChatGPT and any client that speaks MCP. - [MCP tool reference](https://docs.sanda-os.com.au/mcp/tools): Every tool Claude can be given. ::: --- # Connect other MCP clients > Connect ChatGPT or any MCP client to sanda with OAuth or an integration token, including discovery, registration, the token endpoint and a curl example. sanda is a remote MCP server with its own OAuth 2.1 authorization server, so any client that supports remote MCP servers can connect. A client authenticates in one of two ways: it signs a person in with OAuth, or it sends an [integration token](https://docs.sanda-os.com.au/mcp/agent-tokens) as a bearer credential. For Claude, see [Connect Claude](https://docs.sanda-os.com.au/mcp/connect-claude). This page covers everything else. ## The endpoint ```text POST https://console.sanda-os.com.au/api/mcp Authorization: Bearer Content-Type: application/json ``` - **One address, POST only.** A GET answers 405 with `Allow: POST`. - **Plain JSON-RPC 2.0.** Every message is one request and every answer is one JSON object. There is no streaming, no server-sent events and no session id, so a client needs nothing beyond an HTTP POST. A batch (a JSON array) is refused with `-32600`. - **Request bodies up to 1,000,000 bytes.** That is enough for about ten thousand short rows in `load_rows`. A larger body is refused with 400 and `-32700`. - **No browser origins.** A request that carries an `Origin` header other than sanda's own is refused with 403, so a web page on another site cannot call the endpoint. - **A notification** (a request with no `id`) is answered with 202 and no body. ### Protocol versions sanda speaks both eras of MCP on the same address, and a client using either works unchanged. | Era | Revisions | How a client starts | |---|---|---| | Handshake | `2024-11-05`, `2025-03-26`, `2025-06-18`, `2025-11-25` | `initialize`. sanda answers with the client's version when it supports it, and with `2025-11-25` otherwise | | Stateless | `2026-07-28` | No handshake. Every request carries its protocol version and client capabilities in `params._meta`. `server/discover` replaces `initialize` | For a `2026-07-28` request, the `MCP-Protocol-Version`, `Mcp-Method` and `Mcp-Name` headers are optional, but if sent they must agree with the body, or sanda answers 400 with `-32020`. ### Methods `initialize`, `server/discover`, `ping`, `tools/list`, `tools/call`, `resources/list` and `resources/read`. sanda offers tools and two resources, `becca://fluid/brief` (the one-page map of the workspace) and `becca://fluid/context` (everything, in full, and large). It does not offer prompts or resource subscriptions. Prefer the brief plus tool calls to the full context. Both resources need `fluid:read`: without it, `resources/list` returns an empty list and `resources/read` is refused like a tool call, with a 403 and `insufficient_scope` for an OAuth client. ## Choose how to authenticate | | OAuth | Integration token | |---|---|---| | Use it when | A person connects their own assistant, and the client can open a browser | A machine runs unattended, or the client has a header field and nothing else | | The credential | An access token that lasts one hour, refreshed by the client | A `bat_` token that lasts until it expires or is revoked | | Permissions | Chosen by the person on the consent screen | Chosen by an owner or admin when the token is created | | Revoke it | **Settings · Integrations**, or `POST /api/oauth/revoke` | **Settings · Integrations** | If both are on offer, use OAuth: there is no secret to store. ## Connect ChatGPT ChatGPT can connect to a remote MCP server as a custom connector, on the plans that offer them. Add a connector, give it sanda's address, and choose OAuth as its authentication: ```text https://console.sanda-os.com.au/api/mcp ``` If the form asks for a client ID or secret, leave them empty. sanda does not issue client secrets: every MCP client is a public client and proves itself with PKCE. When you connect, ChatGPT sends you to the same consent screen described in [Connect Claude](https://docs.sanda-os.com.au/mcp/connect-claude#the-consent-screen). Menu names in ChatGPT differ between plans and change over time, so check ChatGPT's own documentation for where the connector form is and whether your plan offers it. ## Sign in with OAuth from your own client sanda implements the MCP authorization specification as both the resource server and the authorization server, on one address. All of it is public and needs no key. | What | Address | |---|---| | Protected resource metadata | `https://console.sanda-os.com.au/.well-known/oauth-protected-resource` | | Authorization server metadata | `https://console.sanda-os.com.au/.well-known/oauth-authorization-server` | | Authorization endpoint | `https://console.sanda-os.com.au/api/oauth/authorize` | | Token endpoint | `https://console.sanda-os.com.au/api/oauth/token` | | Dynamic client registration | `https://console.sanda-os.com.au/api/oauth/register` | | Revocation endpoint | `https://console.sanda-os.com.au/api/oauth/revoke` | The discovery documents are served with `Access-Control-Allow-Origin: *` and cached for 60 seconds. The protected resource document is also served at `/.well-known/oauth-protected-resource/api/mcp`. ### Let the 401 point you at discovery A request with no credential, or an invalid one, is refused with 401. Its `WWW-Authenticate` header names the discovery document and every scope sanda has: ```http HTTP/1.1 401 Unauthorized WWW-Authenticate: Bearer realm="sanda", resource_metadata="https://console.sanda-os.com.au/.well-known/oauth-protected-resource", scope="fluid:read query:run sql:run fluid:write workspace:read workspace:manage warehouse:write warehouse:model search:manage sql:write warehouse:append reports:deliver" ``` When a credential was presented and refused, `error="invalid_token"` follows the realm. ### Read the metadata ```bash title="Discovery" curl -s https://console.sanda-os.com.au/.well-known/oauth-protected-resource curl -s https://console.sanda-os.com.au/.well-known/oauth-authorization-server ``` The first document is abridged here: ```json { "resource": "https://console.sanda-os.com.au/api/mcp", "authorization_servers": ["https://console.sanda-os.com.au"], "scopes_supported": ["fluid:read", "query:run", "sql:run", "fluid:write", "workspace:read", "workspace:manage", "warehouse:write", "warehouse:model", "search:manage", "sql:write", "warehouse:append", "reports:deliver"], "bearer_methods_supported": ["header"] } ``` The authorization server document tells a client what to do: - `code_challenge_methods_supported` is `["S256"]`. PKCE with S256 is required, and `plain` is refused. - `grant_types_supported` is `authorization_code` and `refresh_token`. There is no client credentials grant, because a person has to consent. - `token_endpoint_auth_methods_supported` is `["none"]`. Every client is public. - `client_id_metadata_document_supported` is `true`, and a `registration_endpoint` is also offered. - `authorization_response_iss_parameter_supported` is `true`. sanda returns `iss` on every authorization response, success or error. Validate it. ### Identify your client A client introduces itself in one of two ways. Use the first if you can. **Client ID Metadata Document.** Host a JSON document at an HTTPS address that has a path, and use that address as your `client_id`. There is no registration step. ```json title="https://client.example.com/oauth/client.json" { "client_id": "https://client.example.com/oauth/client.json", "client_name": "Example agent", "redirect_uris": ["https://client.example.com/oauth/callback"], "grant_types": ["authorization_code", "refresh_token"], "response_types": ["code"], "token_endpoint_auth_method": "none" } ``` sanda fetches the document and holds it in a cache. The rules: - `client_id` inside the document must equal the address it was fetched from, exactly. - `client_name` is required, and so are one to 20 `redirect_uris`. Each must be `https`, or `http` on `localhost`, `127.0.0.1` or `[::1]`, and none may carry a fragment. - The address must be a named host with a path. An IP address, `localhost` and internal suffixes such as `.local` or `.internal` are refused, as is a client ID with a user name or password in it. - sanda does not follow redirects, waits five seconds, and reads at most 64 KB. - The cache lasts as long as the response's `Cache-Control: max-age` says, held between five minutes and a day. **Dynamic client registration.** Post your redirect addresses and take the client ID you are given: ```bash title="Register a client" curl -s https://console.sanda-os.com.au/api/oauth/register \ -H 'content-type: application/json' \ -d '{"client_name":"Example agent","redirect_uris":["https://client.example.com/oauth/callback"]}' ``` The answer is 201 with a `client_id`, your `redirect_uris`, and `token_endpoint_auth_method` of `none`. Registration is open, because it grants no access on its own: a person still has to approve the client on the consent screen. Send one to 20 redirect addresses, each `https` or loopback `http`. Redirect addresses are compared by **exact string**. A trailing slash, an added query parameter or a different port is a different address, and sanda stops before any redirect happens. ### Send the person to the consent screen ```text https://console.sanda-os.com.au/api/oauth/authorize ?response_type=code &client_id=https%3A%2F%2Fclient.example.com%2Foauth%2Fclient.json &redirect_uri=https%3A%2F%2Fclient.example.com%2Foauth%2Fcallback &code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM &code_challenge_method=S256 &scope=fluid%3Aread%20query%3Arun &resource=https%3A%2F%2Fconsole.sanda-os.com.au%2Fapi%2Fmcp &state=abc123 ``` | Parameter | Rule | |---|---| | `response_type` | Must be `code` | | `code_challenge` | A PKCE S256 challenge of at least 43 characters. `code_challenge_method`, if sent, must be `S256` | | `scope` | Space separated. sanda drops any scope it does not have. If none remain, the request is for `fluid:read` | | `resource` | Optional, but if sent it must be `https://console.sanda-os.com.au/api/mcp`, or the request fails with `invalid_target`. Send it on the token request too | | `state` | Returned to you unchanged | An unknown client or an unregistered redirect address ends on an error page in the browser and never redirects. Any other error is sent to your redirect address as `error`, `error_description`, `state` and `iss`. sanda sends the browser to its consent screen, where the person signs in if they must, picks a workspace, and chooses which of the scopes you asked for to grant. The screen is described in [Connect Claude](https://docs.sanda-os.com.au/mcp/connect-claude#the-consent-screen). A person can never grant a scope you did not ask for, and they can grant fewer. If they approve, sanda redirects to your `redirect_uri` with `code`, `state` and `iss`. If they refuse, it redirects with `error=access_denied`. The consent request lasts 10 minutes, and the code lasts two minutes and works once. ### Exchange the code ```bash title="Token request" curl -s https://console.sanda-os.com.au/api/oauth/token \ -d grant_type=authorization_code \ -d code=AUTHORIZATION_CODE \ -d client_id=https://client.example.com/oauth/client.json \ -d redirect_uri=https://client.example.com/oauth/callback \ -d code_verifier=YOUR_CODE_VERIFIER \ -d resource=https://console.sanda-os.com.au/api/mcp ``` The request is form encoded, and `code_verifier` is 43 to 128 characters. A code that is wrong, expired, already used or presented by a different client, and a verifier that does not match, all answer `invalid_grant` with no detail about which check failed. A code is spent the moment it is presented, so a mistake needs a new authorization. ```json { "access_token": "...", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "...", "scope": "fluid:read query:run" } ``` The `scope` is what the person granted, which may be less than you asked for. ### Refresh and revoke An access token lasts one hour. Refresh it with the refresh token: ```bash title="Refresh" curl -s https://console.sanda-os.com.au/api/oauth/token \ -d grant_type=refresh_token \ -d refresh_token=YOUR_REFRESH_TOKEN ``` Refresh tokens **rotate on every use**, and each one lasts 30 days from when it was issued. Store the new refresh token each time. A token that has already been used is refused with `invalid_grant`. An access token is bound to sanda's MCP address and is checked against it on every call. To clean up when a user disconnects, post the token to the revocation endpoint (RFC 7009). It answers 200 whether or not the token existed: ```bash title="Revoke" curl -s https://console.sanda-os.com.au/api/oauth/revoke -d token=YOUR_TOKEN ``` ## Use an integration token An owner or admin [creates a token](https://docs.sanda-os.com.au/mcp/agent-tokens#create-a-token) and gives it to the client. The client sends it on every request as a bearer credential. ```bash title="Initialize" curl -s https://console.sanda-os.com.au/api/mcp \ -H "Authorization: Bearer bat_your_token_here" \ -H "content-type: application/json" \ -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}' ``` ```json title="Response, abridged" { "jsonrpc": "2.0", "id": 1, "result": { "protocolVersion": "2025-11-25", "capabilities": { "tools": { "listChanged": false }, "resources": { "listChanged": false, "subscribe": false } }, "serverInfo": { "name": "becca-semantic-fluid", "title": "sanda semantic fluid", "version": "1.6.0" }, "instructions": "...", "resultType": "complete" } } ``` `instructions` is a paragraph written for the model: which tool to call first and which to prefer. Now list the tools this token may use: ```bash title="List tools" curl -s https://console.sanda-os.com.au/api/mcp \ -H "Authorization: Bearer bat_your_token_here" \ -H "content-type: application/json" \ -d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' ``` A token created with no extra switches is offered five tools: `search_fluid`, `describe_table`, `run_query`, `search_documents` and `search_docs`. A tool the token may not use is absent from the list, and calling it anyway is refused. Each tool comes back with a `name`, a `title`, a `description` written for the model, an `inputSchema` and `annotations` that mark it read-only or destructive. Call a tool with `tools/call`: ```bash title="Call a tool" curl -s https://console.sanda-os.com.au/api/mcp \ -H "Authorization: Bearer bat_your_token_here" \ -H "content-type: application/json" \ -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"run_query","arguments":{"metrics":["net revenue"],"dimensions":["region"],"limit":5}}}' ``` ```json title="Response, abridged" { "jsonrpc": "2.0", "id": 3, "result": { "content": [{ "type": "text", "text": "..." }], "isError": false, "resultType": "complete" } } ``` The result is text: for `run_query`, a table of rows followed by the SQL sanda composed. Use the names of metrics and dimensions that `search_fluid` returns for your workspace. The tools and their parameters are in the [MCP tool reference](https://docs.sanda-os.com.au/mcp/tools). ## Errors A tool that could not answer returns a sentence and `isError: true`, for example that two tables would fan out. Refusals before a tool runs come back as HTTP status codes and JSON-RPC errors: | Status | Code | Meaning | |---|---|---| | 401 | | No credential, or one that is invalid, revoked or expired. `WWW-Authenticate` points at discovery | | 402 | | The workspace is suspended | | 403 | | The request carried a foreign `Origin` | | 403 | `-32602` | An OAuth client called a tool or read a resource it was not granted. `WWW-Authenticate` carries `error="insufficient_scope"` and the scope it needs, and the body names it as `data.required_scope`, so a client can ask the person to approve again | | 405 | | Not a POST | | 200 | `-32602` | An unknown tool or resource, or a tool or resource an integration token was not granted, which cannot step up | | 200 | `-32601` | No such method. A `2026-07-28` request gets 404 | | 400 | `-32600` | Not a JSON-RPC request, or a batch | | 400 | `-32700` | The body was not valid JSON | | 400 | `-32022` | Unsupported `2026-07-28` protocol version. `data.supported` lists what sanda speaks | | 400 | `-32020` | A mirror header disagrees with the body | A call that is waiting for a person to approve it is not an error. See [permissions and approvals](https://docs.sanda-os.com.au/mcp/permissions#approvals). ## A configuration file Many clients read a small configuration file with the server's address and a header. The key names differ between clients, so check the client's own documentation, but the values are always these: ```json { "mcpServers": { "sanda": { "url": "https://console.sanda-os.com.au/api/mcp", "headers": { "Authorization": "Bearer bat_your_token_here" } } } } ``` ## Next steps :::links - [Agent tokens](https://docs.sanda-os.com.au/mcp/agent-tokens): Create, scope, rotate and revoke the tokens a client holds. - [Permissions and approvals](https://docs.sanda-os.com.au/mcp/permissions): What each scope allows, and how actions wait for a person. - [Scopes](https://docs.sanda-os.com.au/reference/scopes): The full list, with what each one allows. ::: --- # Agent tokens > Create, scope, rotate and revoke the integration tokens that Claude Code, agents and Excel use to reach your workspace, and keep them safe. An agent token is a secret string that lets something outside sanda reach one workspace. The console calls it an **integration token**, and it always begins with `bat_`. You paste it into a client once, and the client sends it with every request. Use a token when a person is not there to sign in: Claude Code, the Claude API, an agent harness of your own, or an Excel workbook that refreshes on a schedule. Where a client can sign a person in with OAuth, prefer that, because there is no secret to store. See [connect other MCP clients](https://docs.sanda-os.com.au/mcp/connect-other-clients). ## Where a token works | Where | How it is sent | Editions | |---|---|---| | The MCP address, `https://console.sanda-os.com.au/api/mcp` | `Authorization: Bearer bat_...` | Every edition | | The Excel and Power BI feed, `https://console.sanda-os.com.au/api/odata/` | As the password of a Basic credential, or as a bearer header | Every edition | A token cannot sign in to the console. It can do what the MCP tools and the feed do, and nothing else. You can create and revoke tokens on any edition. The sanda search tools answer only on an edition that includes sanda search. ## Create a token Only an owner or admin can create tokens. :::steps 1. **Open Settings · Integrations.** In the console, go to **Settings** and choose **Integrations**. 2. **Open the New token form.** Press **New token**. 3. **Name the token.** Under **Name**, say what will hold it, for example "Finance agent" or "Board pack workbook". A name is up to 80 characters. The name is how you will tell your tokens apart later. 4. **Tick only what it needs.** Every token can ask questions. Each switch beyond that is off until you turn it on. See the table below. 5. **Set an expiry.** Under **Expires**, choose **Never**, **30 days**, **90 days** or **One year**. 6. **Press Create token.** sanda shows the token once, with the feed address for Excel and Power BI and ready-made entries for Claude Code, the Claude API and curl. 7. **Copy it now.** sanda keeps only a hash and cannot show it again. If you lose it, create another. ::: A workspace can hold 25 live tokens at once. Revoke one before creating another. ### The switches Every token can ask questions of the fluid (`query:run`). The switches add to that. The scope is the permission's name in the tool reference and on the consent screen. | Switch | Scope | What it allows | |---|---|---| | **May run its own SQL** | `sql:run` | A read-only `SELECT` of its own, through the same read-only role, when the fluid cannot express a question | | **May run and commit its own SQL** | `sql:write` | One statement at a time as the warehouse's builder role, committed at once. Reads every landing table and writes only the derived schema. No undo | | **May write the fluid** | `fluid:write` | Put tables on the map, define metrics, dimensions, filters and relationships, and accept or remove proposals. Live at once | | **May see connections and warehouses** | `workspace:read` | What is connected, when it last synced, how full each warehouse is. Never a credential | | **May start a sync** | `workspace:manage` | Run a connection now instead of on its schedule | | **May load rows into the warehouse** | `warehouse:write` | Land tables of its own rows as `raw.csv__...`. It cannot touch a table a sync landed | | **May define views and procedures** | `warehouse:model` | Build views, materialized views and procedures in the derived schema, run them and put views on the map, on your compute | | **May add rows to intake tables** | `warehouse:append` | Append rows to tables an owner or admin has opened for intake, and nothing else | | **May manage search services** | `search:manage` | Create, rebuild and remove sanda search services | | **May email reports** | `reports:deliver` | Schedule report deliveries, change or pause them and send one now | The full effect of each, including which tools it allows, is in [scopes](https://docs.sanda-os.com.au/reference/scopes). A switch that reaches beyond asking questions is a decision worth making on purpose. Leave every switch off unless the client's job needs it. ## What decides what a token can do The switches are the ceiling. Four other things can lower it: - **Who made it.** The privileged scopes (everything above except **May see connections and warehouses** and **May add rows to intake tables**) last only as long as the person who made the token is still an owner or admin. If they are demoted, sanda stops honouring those switches and the token's row is marked "permissions inactive". Everything else it holds still works. - **Whether that person is still here.** A token stops working when the person who created it leaves the workspace. Its row is marked "maker left" until someone clears it away. - **The workspace's state.** When a workspace is closed it becomes read-only, and every token is limited to asking questions and seeing connections. - **The edition.** The search tools need Standard or Enterprise. ## See your tokens **Settings · Integrations** lists every live token, and the apps members have connected by approving sanda's consent screen, in one list. Any member can see every token. Owners and admins also see every member's connected apps, and everyone else sees their own. Each row shows: - the token's name and its last six characters, which is enough to match it against the string in a client's configuration and not enough to use it; - who made it, when, when it was last used ("never used" if it has not been), and when it expires; - one label for each thing it may do beyond questions. | Label | Scope | |---|---| | fluid only | Nothing beyond asking questions | | may run SQL | `sql:run` | | commits SQL | `sql:write` | | writes the fluid | `fluid:write` | | sees connections | `workspace:read` | | starts syncs | `workspace:manage` | | loads rows | `warehouse:write` | | appends rows | `warehouse:append` | | defines views | `warehouse:model` | | manages search | `search:manage` | | emails reports | `reports:deliver` | To see what a token has been doing, open **Intelligence · Agents**. It shows each credential's permissions in full sentences, and the calls it has made. See [MCP and agents](https://docs.sanda-os.com.au/mcp). ## Rotate a token A token cannot be changed in place, and sanda cannot show you an old one. To rotate: :::steps 1. **Create a new token** with the same name (add a date, such as "Finance agent, October") and the same switches. 2. **Put it in every place that used the old one.** For an Excel workbook, follow [change or clear the saved token](https://docs.sanda-os.com.au/odata/excel#change-or-clear-the-saved-token). 3. **Check that the new token is in use.** On **Intelligence · Agents**, or in the token list, the new one shows a "last used" time once a client has called with it. 4. **Revoke the old token.** ::: Setting an expiry when you create a token turns rotation into a habit. A token that expires in 90 days needs replacing every 90 days, and a token nobody remembers stops working on its own. ## Revoke a token Press **Revoke** on the token's row. Owners and admins can revoke any token. Anyone can revoke a token they made themselves. Revoking takes effect at once: the next call the token makes fails with a 401. The token disappears from the list, and sanda keeps a record of its creation and revocation in the audit trail. There is no undo, and no way to bring the same token back. Create a new one instead. A connected app, such as Claude, is disconnected from the same list with **Disconnect**. ## Keep tokens safe - **Treat a token like a password.** Anyone who holds it can do whatever its switches allow, as your workspace, until it is revoked. - **One token for each client.** When something goes wrong you can revoke that one and leave the others working. Name it after what holds it. - **Grant the least.** A workbook needs no switches at all. A reporting agent rarely needs to write the fluid. - **Set an expiry.** Choose 30 or 90 days for anything you would have to remember to review. - **Keep it out of chat, tickets and source control.** The `bat_` prefix is there so that secret scanners recognise a leaked token, and it is worth switching scanning on for your repositories. - **Keep it out of URLs.** Send it in a header, or as a Basic password in Excel. A URL is copied into logs and browser history. - **If one leaks, revoke it first.** Then open **Intelligence · Agents** and read the **Calls** tab for what it did, and create a replacement. - **Ask for approval on the risky actions.** An owner or admin can make committed SQL, dropped views, dropped search services and emails to outside addresses wait for a person, even for a token that holds the permission. See [permissions and approvals](https://docs.sanda-os.com.au/mcp/permissions#approvals). ## Next steps :::links - [Permissions and approvals](https://docs.sanda-os.com.au/mcp/permissions): Decide what an agent may do, and who has to agree first. - [Connect Excel](https://docs.sanda-os.com.au/odata/excel): Use a token as the password of a Basic credential. - [Scopes](https://docs.sanda-os.com.au/reference/scopes): What every switch allows and which tools it gives. ::: --- # Permissions and approvals > What each MCP permission allows, who can grant it, how to make risky agent actions wait for a person, and where every action is recorded. An assistant connected to sanda can do only what its credential's permissions allow. Permissions are called **scopes**. sanda checks them on every call, a person grants them on purpose, and the few actions that cannot be undone can be made to wait for an owner or admin before they run. ## Scopes A credential is an [integration token](https://docs.sanda-os.com.au/mcp/agent-tokens) or a connected app such as Claude. Each holds a list of scopes. Asking questions of the semantic fluid is the baseline, and every other scope adds one kind of action. A tool the credential may not use is **absent from the tool list**, not present and refusing, so an assistant never tries what it cannot do. There are twelve scopes, in three kinds: | Kind | Scopes | Who can grant | |---|---|---| | Reading | `fluid:read`, `query:run`, `workspace:read` | Any member | | Adding rows | `warehouse:append` | Any member | | Privileged | `sql:run`, `sql:write`, `fluid:write`, `workspace:manage`, `warehouse:write`, `warehouse:model`, `search:manage`, `reports:deliver` | An owner or admin only | `warehouse:append` is the one write any member can grant, and it is safe for a reason: it reaches only the tables an owner or admin has opened for intake, and it can only add rows to them. It cannot create, replace or delete anything. A wider scope includes a narrower one. `sql:run` includes `query:run`, which includes `fluid:read`. `sql:write` includes `sql:run` and `workspace:read`. `fluid:write` includes `fluid:read`. `workspace:manage`, `warehouse:write`, `warehouse:model` and `search:manage` each include `workspace:read`. `warehouse:append` includes `fluid:read`. `reports:deliver` includes nothing else, because sending a report is not asking its questions. The full list, with every tool each one allows, is in [scopes](https://docs.sanda-os.com.au/reference/scopes). ## Who can grant what | Where the permission is granted | Who | The rule | |---|---|---| | The consent screen, for a connected app | Any member of the workspace except a reader | A person can grant only the scopes the app asked for, and fewer if they untick some. A privileged scope is greyed out unless they are an owner or admin | | **Settings · Integrations**, for an integration token | An owner or admin | They can turn on any switch | The privileged scopes and `warehouse:append` start **unticked** on the consent screen. A client that asks for everything is shown everything, and the person opts in to each one that writes. sanda enforces the rule on its own side as well as on the screen: a scope a person's role may not grant is dropped from the grant even if the request names it. A person with the reader role cannot connect an assistant at all. ### Privileged scopes follow the person A privileged scope lasts only as long as the person who granted it is still an owner or admin. sanda reads their current role on every call. If they are moved to member or viewer, the privileged scopes on the tokens and connected apps they made stop working, the rest keep working, and **Settings · Integrations** marks the row "permissions inactive". A credential also stops working entirely when the person behind it leaves the workspace. A closed workspace is read-only for everyone. Every credential keeps only the reading it already had: asking questions and seeing connections if it could do those before. A credential that could only add rows to intake tables or send reports can no longer do either, and closing a workspace never lets a credential read more than it did. ## Approvals A workspace can ask for a person to approve the few things an agent can do that sanda cannot undo. It is **off by default**, so a client that worked before keeps working exactly as it did until an owner or admin turns approvals on. ### Turn approvals on :::steps 1. **Open Intelligence · Agents.** Only an owner or admin sees the **Approvals** panel. 2. **Press Ask before an agent acts.** The panel now shows how many actions are waiting. 3. **To stop asking, press Stop asking.** Anything already waiting stays in place, to be approved or declined. New calls run straight through. ::: The switch applies to every integration token and every connected app in the workspace. It does not apply to people using the console: a person who drops a view in the explorer is never asked to approve themselves. ### What waits | Call | Held when | |---|---| | `execute_sql` | It runs in `write` mode, which is the default. `read` mode is never held, because looking before deciding is what it is for | | `drop_view` | Always, because it drops a view, materialized view or procedure and its rows go with it | | `drop_search_service` | Always, because it deletes the index and rebuilding costs a full first build | | `create_delivery` and `update_delivery` | A recipient's email domain is not one your members use. A report emailed to someone cannot be recalled | Everything else an agent can do runs as normal. Approval is a brake for the handful of acts that cannot be undone, not a second consent screen for every write. If you want a person in front of every definition, leave `fluid:write` off, and the tools that write the fluid are not offered at all. ### What the agent sees The tool answers with an ordinary result, not an error, that says it has **not run**, why it is waiting, and an id. The agent is told to call `action_status` with that id, and never to report the action as done until `action_status` says it ran. A result rather than an error is deliberate: a client that reads an error as "try another way" is exactly the one that must not go looking for another way. - A call sanda could not have run anyway (a missing `why`, a second statement, an unknown warehouse) is refused in the tool's own words and never queued. - Sending the identical call again while it waits returns the same id. It does not queue a second one or email anyone again. - A workspace holds at most 50 waiting actions. Beyond that, calls are refused until some are decided. `action_status` is available to a credential that holds a scope a held tool needs (`sql:write`, `warehouse:model`, `search:manage` or `reports:deliver`). An agent sees its own credential's actions and nobody else's. Called with no id, it lists the ten most recent. ### Decide sanda emails the workspace's owners and admins when an action is held, since either can decide it. The email names the credential, the tool, why it is held and what would run. It is not sent when **Send alert emails for this workspace** is switched off in **Settings · Plan & budget**, and the action still waits on the Agents page. The extra addresses under **Also send to** are not emailed, because they cannot approve anything. Owners and admins can decide. On **Intelligence · Agents**, each waiting action shows: - the tool, whether the credential is an integration token or a connected app, its name, and who it acts for; - when it was asked and when it expires; - why it is held, and the agent's own stated reason where the tool takes one; - everything that would run: the whole statement for SQL, the object's name and warehouse for a drop, or the full input otherwise. Read all of it, then press **Approve and run** or **Decline**. A member does not see the queue. ### What happens when you approve - The action is claimed in a single step, so it runs **at most once** however many people press the button at the same moment. - sanda checks the credential again. If it has been revoked, has expired, acts for someone who has left the workspace or is no longer an owner or admin, or no longer holds the scope the tool needs, or if the workspace is suspended or closed, the action does **not** run, and the reason is recorded. - Otherwise the stored input runs through the same handler the original call would have reached, **as that credential**. The audit record names the credential and the person it acts for, exactly as it would have without approval. - What the tool answered is recorded, and it is what `action_status` returns. The panel shows each outcome as a label: | Label | Meaning | |---|---| | ran | It was approved and the tool answered | | ran · answered no | It was approved and ran, and the tool answered no, for example because a view did not exist | | not run · credential changed | It was approved, but the credential could no longer act | | declined | An owner or admin declined it, and it did not run | | expired | No one decided within 24 hours. It never runs | | running | It was approved and is running now | | no outcome recorded | It started and sanda never recorded how it ended, so check the warehouse before asking again | Decided actions stay in **Decided in the last 7 days**, with the outcome. Anything not decided within 24 hours expires and never runs. If it is still needed, the agent asks again and it waits again. ### cherry has its own rules cherry, sanda's agent in the console, is not MCP and does not use this queue. It asks before actions of its own, on its own terms. See [cherry actions and approvals](https://docs.sanda-os.com.au/cherry/actions-and-approvals). ## What is recorded - **Every tool call and resource read** is recorded with the credential, the tool, when it happened, how long it took and whether it answered no. **Intelligence · Agents** shows them on the **Calls** tab, and groups them into sessions. The record is of the call and how it went, not the rows that came back. - **Every refusal** is recorded separately. **Refused** lists calls sanda declined to run at all: an unknown tool, a scope the credential does not hold, a protocol the client got wrong. - **Every change** an agent makes goes through the same handler the console's button runs, with the same validation, so it lands in the audit trail as a console change does. Statements run with `execute_sql` are kept with the statement and the reason the agent gave. - **Every step of an approval** is recorded: held, approved, declined, run, refused. - **Creating and revoking** a token, and **granting** an app, are recorded with who did them. ## Choosing permissions Start with nothing beyond asking questions and add only what a job needs. | The job | Grant | Notes | |---|---|---| | An assistant that answers questions | Nothing beyond the defaults | Sees the fluid, runs composed queries and searches text | | Also says how fresh the data is | `workspace:read` | Lists connections and warehouses. Never a credential | | Also refreshes data before it asks | `workspace:manage` | Starts a sync, which costs compute like any other sync | | An agent that models the fluid | `fluid:write` | Definitions go live at once, and approvals do not hold them. Keep `fluid:write` off if you want a person to make every change | | A person logging their day by talking to an assistant | `warehouse:append` only | On a table an owner or admin has opened for intake. Nothing else is reachable | | A weekly board pack by email | `reports:deliver` | Turn approvals on, so a recipient outside the email domains your members use waits for an owner | | An agent that builds and schedules views | `warehouse:model` | Every build runs on your compute and shows on your bill. See [usage](https://docs.sanda-os.com.au/billing/usage) | | An agent that can run its own SQL | `sql:run` first | Read-only. Move to `sql:write` only with approvals on, because it commits with no undo | ## Next steps :::links - [Scopes](https://docs.sanda-os.com.au/reference/scopes): Every scope, what it includes and which tools it allows. - [Agent tokens](https://docs.sanda-os.com.au/mcp/agent-tokens): Create and revoke the credentials these permissions belong to. - [MCP tool reference](https://docs.sanda-os.com.au/mcp/tools): The tools, their parameters and which scope each needs. ::: --- # MCP tool reference > Every tool sanda's MCP server offers, with the scope it needs, its parameters and what it does. Generated from the tool definitions the server runs. This is every tool sanda's MCP server can offer an assistant. The names, titles, scopes and parameters below are read from the same tool definitions the server runs, so they cannot drift from what a client sees. ## How a credential is offered tools `tools/list` returns the tools **this credential** may use, not every tool. A tool is left off the list, and refused if called anyway, when any of these is true: - **The credential does not hold the scope.** Each tool needs one scope. A wider scope includes a narrower one, so a credential holding `sql:write` also gets everything `sql:run` and `query:run` give. See [scopes](https://docs.sanda-os.com.au/reference/scopes). - **The edition does not include it.** The sanda search tools (`search_documents` and every tool needing `search:manage`) are on Standard and Enterprise. - **The person behind it is no longer allowed.** A privileged scope is honoured only while the person who granted it is an owner or admin. - **The workspace is closed.** Then only the tools that read remain: asking questions, looking up the docs and seeing connections. `action_status` is offered only to a credential that holds a scope one of the held tools needs. See [permissions and approvals](https://docs.sanda-os.com.au/mcp/permissions#approvals). A token created with no extra switches is offered `search_fluid`, `describe_table`, `run_query`, `search_documents` and `search_docs`. ## Reading an entry Each entry starts with the tool's name (the string a client sends), then a row of markers: the tool's title, what kind of tool it is, the scope it needs and, where it applies, the editions that include it. The kind comes from the tool's annotations, which a client can use to skip a confirmation prompt or add one: - **Read only** means the tool changes nothing, so calling it again is safe. - **Writes** means the tool changes something. - **Destructive** means the tool can remove or overwrite something, such as taking tables off the map, dropping a view or committing SQL. The parameter table lists every top-level parameter with its type, whether it is required, and what it means. Where a parameter is an object or a list, its fields are described in the parameter's own text or in the entry's notes. ## Limits every tool shares | Limit | Value | |---|---| | Rows or hits returned by `run_query`, `run_sql` and `search_documents` | At most 50 | | Rows returned by `execute_sql` | 50 by default, at most 500 | | Rows loaded or added in one `load_rows` or `append_rows` call | 10,000 | | Request body | 1,000,000 bytes | | Time a tool holds a call open when it builds or runs something | 55 seconds. A longer job belongs on a schedule | | Time a `run_query` or `run_sql` statement may run | 20 seconds | | Held actions a workspace may have waiting | 50 | A tool that cannot answer returns a sentence and `isError: true` rather than a failure, for example that two tables would fan out or that a column does not exist. An assistant is expected to read it and try again differently. ## Resources sanda also serves two resources for clients that read them. Both need `fluid:read`, like `search_fluid`. A credential without it is listed no resources, and reading one is refused the way a tool is, with a step-up challenge for a connected app: | Resource | What it holds | |---|---| | `becca://fluid/brief` | The one-page map of the workspace: its tables and the names of every metric, dimension, filter and category. Read this first | | `becca://fluid/context` | Everything in full: every column, expression, sample value and join. Large, so prefer the brief plus tool calls | ## cherry and MCP cherry, the console's own agent, uses most of these tools too, under the signed-in person's role and the capabilities switched on under **Settings · cherry**. It is not offered `execute_sql`, `set_intake`, `append_rows` or the tools that manage sanda search, and `action_status` is for MCP credentials alone. cherry also has tools of its own for building reports, which are never served over MCP. See [cherry](https://docs.sanda-os.com.au/cherry/capabilities). ## Read the fluid and your data Find what the workspace has modelled, look closely at a table, ask a question and search the text inside rows. ### search_fluid

Search the fluidRead onlyfluid:read

Finds what the workspace has modelled: metrics, dimensions, named filters, tables and columns. An assistant calls it before using any name it has not already seen in the workspace brief or an earlier answer, because a metric found here is the definition the business agreed on. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `query` | string | No | A word or fragment to match against names, synonyms, descriptions, definitions and column names. Leave it empty to list everything. | | `kind` | string, one of `all`, `metrics`, `dimensions`, `filters`, `tables`, `columns` | No | Narrows the search to one kind of thing: `all` (the default), `metrics`, `dimensions`, `filters`, `tables` or `columns`. | The answer is text, one line per match. A search that finds nothing says so, which is an answer: try a synonym before concluding the question cannot be asked. ### describe_table

Describe a tableRead onlyfluid:read

Describes one table in full: every column with its type, the grain, the primary key, the time column, the metrics and dimensions measured on it and every join it can follow. An assistant calls it before it names a column. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `table` | string | Yes | The table's business name, its schema-qualified relation or its identifier, for example `order details`, `mapping.order_details` or `order_details`. Case and separators do not matter. | A table that is not on the map is not found, and the answer names the closest tables that are. ### run_query

Run a composed queryRead onlyquery:run

Answers a question with rows. The assistant names what it wants by reference (metrics, dimensions, filters, conditions) and sanda composes the SQL, resolves the joins and runs it, so a number means what the workspace says it means. This is the tool for almost every question. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `metrics` | array of strings | No | Metric names from `search_fluid`. These are the numbers the business has defined. | | `dimensions` | array of strings | No | Dimension names to break the numbers down by. | | `granularity` | string, one of `day`, `week`, `month`, `quarter`, `year` | No | How coarsely to group a time dimension: `day`, `week`, `month`, `quarter` or `year`. Empty means one row per underlying value. | | `columns` | array of objects | No | Raw columns for questions no metric covers, each as a fully-qualified `schema.table.column` and an `aggregate` (`sum`, `avg`, `min`, `max`, `count`, `count_distinct`, or empty to list the value). A well-behaved assistant says when it has leaned on one, because the answer rests on a column name rather than an agreed definition. | | `filters` | array of strings | No | Named filter names from `search_fluid`: conditions the workspace has already agreed on. | | `conditions` | array of objects | No | Extra narrowing for this question only. Each condition has a `field` (a dimension name or a fully-qualified column), an `op`, a list of `values` and an optional named period in `relative`. Operators are the same ones the console's filters use. | | `order_by` | array of objects | No | How to sort, first entry first, as `by` (a metric, dimension or column already in the query) and `direction` (`asc` or `desc`). Left out, a number broken down by a label comes back largest first and a time series in time order. | | `limit` | integer | No | How many rows to return. The default and the maximum are both 50. | Operators and named periods are listed in [filters](https://docs.sanda-os.com.au/reference/filters). The schema marks every key of a condition as required, so send all four: use an empty list for `values` and an empty string for `relative` when they do not apply. `relative` is only for `in_range` and names a period such as `last_month`; sanda resolves it in UTC and states the window it used. ```json { "metrics": ["net revenue"], "dimensions": ["region", "issue date"], "granularity": "month", "conditions": [ { "field": "issue date", "op": "in_range", "values": [], "relative": "last_6_months" } ], "limit": 10 } ``` The answer is text: a table of rows, the window any named period resolved to, and the SQL sanda composed. Metrics measured on different tables can be asked for together, and each is counted on its own table, so revenue beside order count counts every order once. Each metric keeps its own condition. Two tables that would fan out if joined come back as a sentence explaining the refusal, not as a wrong number. A column whose name looks like it holds a secret is never returned. ### search_documents

Search the text inside the rowsRead onlyquery:runStandard · Enterprise

Finds rows by what their text says, for questions about wording rather than about a number, such as which invoices mention water damage. It searches meaning and words together and answers with rows and never a total. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `query` | string | Yes | What to look for, in words. A phrase or a sentence works better than a keyword, because meaning is half of how this searches. | | `service` | string | No | Which search service to search, by name. Leave it out when the workspace has only one. | | `k` | integer | No | How many rows to return. The default is 10 and the maximum is 50. | | `filters` | array of objects | No | Narrows the search by a service's attribute columns, each as `column`, `op` (`eq`, `neq`, `in`, `contains`, `gt`, `gte`, `lt`, `lte`) and `value`. Only columns the service declares as attributes can be filtered. | Every hit carries the value of its table's key column. To turn hits into a number, take those keys and call `run_query` with a condition on them. This is a question of the data, so it needs only `query:run`, and it is offered to any client holding that scope on an edition with sanda search (Standard and Enterprise). If the workspace has no search service yet, a call answers that there is nothing to search. Managing services is a separate, privileged scope (`search:manage`). ### run_sql

Run SQL you wroteRead onlysql:run

Runs one read-only SELECT the assistant wrote itself. It is the fallback for a question `run_query` cannot express, such as a window function or a cohort. It bypasses the workspace's own definitions, so the number is the assistant's rather than the business's. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `sql` | string | Yes | One PostgreSQL SELECT (a leading WITH is fine). No semicolon, no comments, no DDL or DML. Every relation must be one `describe_table` listed. | | `why` | string | Yes | One sentence naming what `run_query` could not express. It is required, and a client is expected to show it to the person beside the answer. | Runs through the reader role inside a read-only transaction, returns at most 50 rows and stops after 20 seconds. A column whose name looks like a credential or an identity number is refused, whatever the statement calls it. A default integration token does not see this tool at all: it is listed only for credentials granted `sql:run`. cherry is offered it only when its sql capability is switched on under **Settings · cherry**. ### search_docs

Search the sanda docsRead onlyfluid:read

Searches sanda's own documentation at docs.sanda-os.com.au for how the product works and how to do something in it. An assistant calls it for a question about sanda, never for a question about your data, and gives you the link it used. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `query` | string | Yes | What to look for, in a few words, for example `connect xero`, `incremental sync` or `invite a member`. | | `limit` | integer | No | How many sections to return, from 1 to 5. The default is 3. | The answer is text: for each match, the page and section title, the address of the section, and the section's own words. A search that finds nothing says so and points at the docs site. This reads public pages and nothing of the workspace's data, so it needs only `fluid:read`, and a call is never held for approval. It makes no model call, so it costs nothing on the AI limit. cherry uses it too, to answer how-to questions with a link. ## See and refresh the workspace Check which connections and warehouses exist, how fresh and how full they are, and start a sync. ### list_connections

List connectionsRead onlyworkspace:read

Lists every data connection in the workspace: the system it pulls from, its health, when it last synced, how many rows it has landed, its schedule and where it lands. An assistant uses it to answer whether the data is fresh. Takes no parameters. Never returns a credential. ### connection_status

One connection, in detailRead onlyworkspace:read

Shows one connection's last ten runs with status, rows, duration and any error text, and the dataset its rows became. While a sync is running it also reports the stage, rows read so far, the average rate and each stream's state. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `connection` | string | Yes | The connection's name or id, from `list_connections`. | ### list_warehouses

List warehousesRead onlyworkspace:read

Lists each warehouse with its storage against its limit, compute, table and row counts, and whether ingest is paused. An assistant uses it before a large load to check how much room is left. Takes no parameters. Never returns a connection string. ### sync_now

Start a syncWritesworkspace:manage

Starts one connection's sync now instead of waiting for its schedule. The rows land in the background, and `connection_status` shows how far the run has got. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `connection` | string | Yes | The connection's name or id, from `list_connections`. | A sync reads from the source system and lands rows in the warehouse, so it costs compute like any other sync. This is the only tool that drives a connection. ## Write the semantic fluid Put landed tables on the map, define metrics, dimensions, filters and relationships, and review what sanda proposed. Definitions go live at once. ### list_warehouse_tables

List tables in the warehouseRead onlyfluid:write

Lists every table that has landed in the warehouse, with its columns, a row estimate and whether it is already on the semantic map. This is the raw layer `describe_table` cannot see, and the first step before `import_tables`. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `only` | string | No | One schema-qualified relation to describe on its own, for example `raw.xero__invoices`. Leave it empty to list them all. | ### import_tables

Put warehouse tables on the mapWritesfluid:write

Puts landed tables on the semantic map so the fluid can query them. sanda reads each table's shape, publishes a view over the columns chosen and files it under its connector's dataset, then reports the kind, key and time column it guessed and the joins it drew. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `relations` | array of strings | Yes | Schema-qualified relations to add, for example `raw.xero__invoices`, from `list_warehouse_tables`. Up to 60 at a time. | | `columns` | object | No | Optional. For each relation, the column names to keep. The key and time columns are always kept. Leave a relation out to take every column. | | `kinds` | object | No | Optional. For each relation, `fact` or `dimension`, when the caller already knows. A fact records things that happened (orders, invoices) and a dimension records things that are (products, customers). This overrides sanda's guess. | | `relationships` | string, one of `infer`, `none` | No | `infer` (the default) draws joins that the column names assert, such as a column named exactly like another table's key. `none` draws none. Ambiguous matches are reported and never drawn. | Importing a table that is already on the map refreshes it and does not duplicate it. Nothing here changes the rows in the warehouse. ### set_table_kind

Mark a table as a fact or a dimensionWritesfluid:write

Says whether a table on the map is a fact or a dimension. Getting this right is what stops a query summing a price list. An assistant calls it after `import_tables` when sanda's guess was wrong. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `table` | string | Yes | The table as the fluid names it, its relation or its identifier. Case and separators do not matter. | | `kind` | string, one of `fact`, `dimension` | Yes | `fact` or `dimension`. | ### retire_tables

Take tables off the mapDestructivefluid:write

Takes tables off the semantic map. The rows in the warehouse are untouched, but every metric, dimension, filter and relationship written against those tables goes with them. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `tables` | array of strings | Yes | Business names, relations or identifiers, as `import_tables` named them. | Destructive to the model, not to the data. A well-behaved assistant reads the table names back to the person before calling it. cherry always asks first, whatever its mode. ### define

Define something in the fluidWritesfluid:write

Creates or replaces one definition in the fluid, live at once: a metric, dimension, filter, relationship, alias, category or dataset, or a statement of what a table already on the map means. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `kind` | string, one of `metric`, `dimension`, `filter`, `relationship`, `alias`, `category`, `dataset`, `table` | Yes | What to define: `metric`, `dimension`, `filter`, `relationship`, `alias`, `category`, `dataset` or `table`. | | `definition` | object | Yes | The fields for that kind, as an object. Each kind's fields are listed in the notes below. | A definition with the same name replaces the old one, and names are matched without regard to case. The reply names the table the definition landed on and says if an expression uses a column that table does not have. | Kind | Fields | |---|---| | `metric` | `name`, `table`, `expression` (one SQL fragment over that table's own columns, for example `sum(total_ex_gst)`), and optionally `aggregation` (`sum`, `avg`, `count`, `count_distinct`, `min`, `max`, `ratio`, `custom`), `filters`, `unit`, `format` (`currency`, `percent`, `integer`, `decimal`, `duration`), `description`, `synonyms` | | `dimension` | `name`, `column` (as `schema.table.column`), and optionally `kind` (`categorical`, `time`, `geo`, `boolean`, `numeric`), `description`, `synonyms` | | `filter` | `name`, `table`, `expression` (a named WHERE condition), optionally `description` | | `relationship` | `left_column`, `right_column`, `key` (the business name of the shared identity), optionally `left_cardinality`, `right_cardinality` (`one` or `many`), `description` | | `alias` | `column`, `term`, optionally `description` | | `category` | `name`, optionally `description` | | `dataset` | `name`, optionally `description` | | `table` | `table`, and any of `description`, `grain`, `primary_key`, `time_column`, `kind`, `name`. Only the fields given change | ```json { "kind": "metric", "definition": { "name": "net revenue", "table": "invoices", "expression": "sum(total_ex_gst)", "format": "currency", "synonyms": ["turnover"] } } ``` ### list_proposals

List proposals awaiting reviewRead onlyfluid:write

Lists everything sanda's own learning pass has proposed and nobody has signed off: tables, metrics, dimensions, filters and relationships. Proposals are invisible to queries until they are accepted. Takes no parameters. ### review_proposal

Accept or reject a proposalWritesfluid:write

Accepts or rejects one proposal. An accept may carry corrections, so a nearly right proposal is one call. A relationship is decided on both sides together. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `kind` | string, one of `metric`, `dimension`, `filter`, `mapping`, `table`, `category`, `dataset`, `glue` | Yes | What is being reviewed: `metric`, `dimension`, `filter`, `mapping`, `table`, `category`, `dataset` or `glue`. | | `id` | string | Yes | The proposal's id, from `list_proposals`. | | `action` | string, one of `accept`, `reject` | Yes | `accept` or `reject`. | | `edits` | object | No | Optional corrections applied when accepting, in the same shape `define` takes for that kind. | Accept a relationship's tables before the relationship. ### delete_definition

Delete a definitionDestructivefluid:write

Removes one metric, dimension, filter, mapping, category, dataset or glue definition from the fluid for good. Redefining with `define` is better than deleting and recreating, because a delete loses the definition's history. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `kind` | string, one of `metric`, `dimension`, `filter`, `mapping`, `category`, `dataset`, `glue` | Yes | What to remove: `metric`, `dimension`, `filter`, `mapping`, `category`, `dataset` or `glue`. Use `retire_tables` for a table. | | `id` | string | Yes | The definition's id. | cherry always asks first, whatever its mode. ## Load and change warehouse data Land tables of an agent's own rows, add rows to tables opened for intake, and run one committed SQL statement of its own. ### load_rows

Load rows into the warehouseWriteswarehouse:write

Lands a table of an agent's own rows in the warehouse as `raw.csv__
`, through the same loader and storage limit a sync uses. Typical uses are a forecast it computed or a lookup it was given. It cannot touch a table a sync landed. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `table` | string | Yes | A short name. It becomes `raw.csv__`, lower-cased with underscores. | | `columns` | array of objects | Yes | The columns in order, each with a `name` and an optional `type` (`text`, `bigint`, `integer`, `numeric`, `boolean`, `date`, `timestamptz` or `jsonb`). Leave the type empty to infer it from the values. Up to 250 columns. | | `rows` | array of anys | Yes | The rows, each either a list of values in column order or an object keyed by column name. Empty cells are null. Up to 10,000 rows per call. | | `mode` | string, one of `replace`, `append` | No | `replace` (the default) swaps the table for the new rows, in their new shape, and keeps the views built on it. It is refused, naming the view, when one of your own views reads a column the new rows drop or retype. `append` adds to it and must match its columns. | | `warehouse` | string | No | Which warehouse, by name or id, when the workspace has more than one. | | `import` | boolean | No | When true, also puts the table on the semantic map so `run_query` can reach it. | | `intake` | boolean | No | When true, also opens the table for intake, so credentials holding `warehouse:append` may add rows to it. | Send `rows: []` with declared types to create an empty table. For more than 10,000 rows, append in batches. ```json { "table": "sales forecast", "columns": [{ "name": "month", "type": "date" }, { "name": "forecast", "type": "numeric" }], "rows": [["2026-10-01", 125000], ["2026-11-01", 131500]], "import": true } ``` ### set_intake

Open or close a table for intakeWriteswarehouse:write

Opens a hand-loaded table for intake, or closes it. While a table is open, credentials holding `warehouse:append` may add rows to it and do nothing else. Closing leaves the rows and the map as they are. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `table` | string | Yes | The table as `load_rows` named it. `dad habits`, `csv__dad_habits` and `raw.csv__dad_habits` are one table. | | `open` | boolean | Yes | `true` opens the table, `false` closes it. | | `note` | string | No | Optional. What the table is for, shown beside it in the **Intake tables** panel of **Settings · Integrations**. | | `warehouse` | string | No | Which warehouse, by name or id, when the workspace has more than one. | The shape for a log a person keeps by talking to an assistant: land the table once with `load_rows`, open it here, then give the person a credential holding only `warehouse:append`. Not offered to cherry. ### append_rows

Add rows to an intake tableWriteswarehouse:append

Adds rows to a table an owner or admin has opened for intake. It only ever appends: it cannot create, replace or delete a table, and a table that is not open refuses. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `table` | string | Yes | The table, as it was opened for intake. | | `columns` | array of strings | No | Only when rows are lists: the column names in the order the values come. | | `rows` | array of anys | Yes | The rows, each an object keyed by column name or a list in `columns` order. Send an empty list to be told the table's columns and types. Up to 10,000 rows per call. | | `warehouse` | string | No | Which warehouse, by name or id, when the workspace has more than one. | Send dates as `YYYY-MM-DD`, numbers as numbers and booleans as `true` or `false`, and leave a field null when the value is not known: the warehouse refuses a value a column cannot hold. If the same day is told twice, append a second row and let the workspace's model decide which counts. This is the one write any member may grant, because its reach is only the tables an owner or admin has opened. Not offered to cherry. ```json { "table": "daily log", "rows": [{ "day": "2026-09-30", "steps": 8200, "note": "Long walk" }] } ``` ### execute_sql

Run and commit SQL you wroteDestructivesql:write

Runs one SQL statement the agent wrote on a warehouse, as the warehouse's builder role, and commits it. The builder reads every landing table and writes only in the derived schema. This is the SQL shell held by a machine, and there is no undo. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `sql` | string | Yes | Exactly one PostgreSQL statement. Semicolons inside a string are fine and a second statement is refused. Qualify every relation with its schema (`raw.`, `derived.` or `mapping.`). | | `why` | string | Yes | One sentence on what the statement is for. It is required and is kept in the audit trail beside the statement. | | `mode` | string, one of `write`, `read` | No | `write` (the default) runs and commits the statement. `read` runs it inside a read-only transaction the database enforces, for looking before deciding. | | `warehouse` | string | No | Which warehouse, by name or id, when the workspace has more than one. | | `rows` | integer | No | How many rows to return from a statement that returns rows. The default is 50 and the maximum is 500. | A sync's landing tables, a published `mapping` view, the search indexes and sanda's own bookkeeping are out of reach whatever the statement says. A statement that changes rows answers with its command tag and the number of rows touched. When the workspace asks for approval before an agent acts, a write-mode call is held for an owner or admin and does not run until one approves it. A `read` call is never held. See [permissions and approvals](https://docs.sanda-os.com.au/mcp/permissions#approvals). Not offered to cherry. ## Views and procedures Define, build and tune views, materialized views and procedures in the derived schema of a warehouse. ### list_sources

List what a view may be built onRead onlywarehouse:model

Lists every relation in a warehouse that a view may be built on, with columns and types: landing tables, derived objects already defined, and the `mapping` tables. It is read as the role that would own the view, so it lists exactly what a `CREATE` will accept. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `warehouse` | string | No | Which warehouse, by name or id, when the workspace has more than one. | A published `mapping.*` view is not listed and cannot be built on. Build on the table behind it. ### list_views

List derived views and proceduresRead onlywarehouse:model

Lists every view, materialized view and procedure the workspace has defined in a warehouse's derived schema: its definition, its version, when a materialized view was last built and whether it is on the semantic map. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `warehouse` | string | No | Which warehouse, by name or id, when the workspace has more than one. | ### create_view

Define a view or materialized viewWriteswarehouse:model

Defines a view in the derived schema from one SELECT over anything `list_sources` names. A materialized view stores its result and is built now, on the workspace's compute. The view goes on the semantic map in the same call unless told not to. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | Yes | A lowercase identifier. It becomes `derived.`. | | `sql` | string | Yes | One SELECT (or WITH ... SELECT) over the relations `list_sources` names, by schema-qualified name. One statement, no semicolons, no comments. | | `materialized` | boolean | No | When true, stores the rows and rebuilds them on refresh, instead of computing on every read. | | `unique_key` | array of strings | No | Materialized views only: the columns that identify one row, so a refresh can run without blocking readers. | | `description` | string | No | What the view is for, in a sentence. | | `replace` | boolean | No | When true, redefines `derived.` if it already exists. | | `publish` | boolean | No | Whether to put the view on the semantic map. The default is true. | | `warehouse` | string | No | Which warehouse, by name or id, when the workspace has more than one. | A materialized view's build runs on the workspace's compute and is billed to it, which is why the scope that allows this is privileged. See [usage](https://docs.sanda-os.com.au/billing/usage). ### refresh_view

Rebuild a materialized viewWriteswarehouse:model

Rebuilds one materialized view now and reports how long it ran and what it cost. An assistant calls it after the tables behind the view have synced. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | Yes | The materialized view's name, from `list_views`. | | `warehouse` | string | No | Which warehouse, by name or id, when the workspace has more than one. | Runs for up to 55 seconds inside the call. A build that needs longer, up to 14 minutes, belongs on a schedule. ### drop_view

Drop a derived objectDestructivewarehouse:model

Drops one view, materialized view or procedure from the derived schema, taking it off the semantic map first. It is refused while another derived view reads it, because sanda never cascades. The definition stays in the history and the rows do not. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | Yes | The object's name, from `list_views`. | | `warehouse` | string | No | Which warehouse, by name or id, when the workspace has more than one. | When the workspace asks for approval before an agent acts, this call is held for an owner or admin. See [permissions and approvals](https://docs.sanda-os.com.au/mcp/permissions#approvals). cherry always asks first, whatever its mode. ### create_procedure

Define a procedureWriteswarehouse:model

Defines a procedure in the derived schema: a multi-step transform in plpgsql or SQL that builds, merges or reshapes derived tables, with `COMMIT` allowed between steps. It runs as the warehouse's builder role. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | Yes | A lowercase identifier. It becomes `derived.`. | | `body` | string | Yes | The procedure body: the plpgsql block, or the SQL statements. | | `args` | string | No | The arguments as `name type, name type`, for example `since date, region text`. Empty for none. Types are `text`, `integer`, `bigint`, `numeric`, `boolean`, `date`, `timestamptz`, `timestamp`, `interval`, `jsonb` and `uuid`. | | `language` | string, one of `plpgsql`, `sql` | No | `plpgsql` or `sql`. | | `description` | string | No | What the procedure does, in a sentence. | | `replace` | boolean | No | When true, redefines `derived.` if it already exists. | | `warehouse` | string | No | Which warehouse, by name or id, when the workspace has more than one. | The builder role may read landing tables, `derived` objects and the `mapping` tables, and may create and write only in `derived`. It cannot write a row anywhere else, whatever the body says. Run it with `call_procedure`. ### call_procedure

Run a procedureWriteswarehouse:model

Runs one procedure now, with its arguments in declared order, and reports how long it ran and what it cost. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | Yes | The procedure's name, from `list_views`. | | `args` | array of anys | No | Argument values in declared order. Leave it out for a procedure with none. | | `warehouse` | string | No | Which warehouse, by name or id, when the workspace has more than one. | Runs for up to 55 seconds inside the call. A run that needs longer belongs on a schedule. ### warehouse_advice

What is slowing this warehouse downRead onlywarehouse:model

Reads a warehouse's own statistics and says what is slowing it down or costing it storage: a missing index, a table autovacuum has fallen behind on, stale statistics after a sync, an unused index, storage a rewrite would give back, or a table large enough to partition. It writes nothing. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `warehouse` | string | No | Which warehouse, by name or id, when the workspace has more than one. | Each finding carries an id, the figures behind it and the statement sanda would run. A finding with no statement is one sanda will not act on, and it says why. Pass the ids you want acted on to `optimise_warehouse`. ### optimise_warehouse

Apply what the advice foundWriteswarehouse:model

Runs the findings you name by id from `warehouse_advice`: a vacuum, an analyse, an index created or dropped. sanda re-reads the warehouse first and runs only findings that are still current. It never runs SQL you send it. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `ids` | array of strings | Yes | Finding ids from `warehouse_advice`, for example `vacuum:raw.xero__invoices`. Up to eight at a time. | | `warehouse` | string | No | Which warehouse, by name or id, when the workspace has more than one. | Each statement runs on the workspace's compute, is recorded on the run ledger and is billed as the seconds it takes. A finding that would lock a table against readers says so in the advice, so read it before sending its id. ## Schedules Run views and procedures on a clock or after a sync, and read what each run did and cost. ### list_schedules

List schedulesRead onlywarehouse:model

Lists every task graph in a warehouse: its steps, its cadence, whether it is paused or has suspended itself after failures, when it next runs and how its last run went. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `warehouse` | string | No | Which warehouse, by name or id, when the workspace has more than one. | ### create_schedule

Schedule a task graphWriteswarehouse:model

Defines a task graph: an ordered list of steps on the warehouse, each a materialized view to refresh or a procedure to call, and when it runs. Steps run in order, and a step is skipped when the one before it failed. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | Yes | A name for the graph, for example `nightly rebuild`. | | `tasks` | array of objects | Yes | The steps in order, from one to 20. Each is `{ "refresh": "view_name" }` or `{ "call": "procedure_name", "args": [ ... ] }`, with an optional step `name`. | | `cron` | string | No | A five-field cron expression evaluated in UTC. sanda's scheduler runs on a 15 minute grid, so the minute field must be 0, 15, 30 or 45. Leave it out for a graph that runs only after a sync or by hand. | | `timezone` | string | No | The zone the cron was written for, kept for display. Evaluation is always UTC. | | `after_sync` | string | No | A connection, by name or id. The graph runs each time that connection lands rows, which is usually the better trigger. It may be combined with `cron`. | | `suspend_after_failures` | integer | No | Pause the graph after this many failures in a row, from 1 to 100. The default is 3 and 0 never pauses. | | `description` | string | No | What the graph is for, in a sentence. | | `warehouse` | string | No | Which warehouse, by name or id, when the workspace has more than one. | One run has at most 14 minutes, and every run is billed as the seconds its statements executed. ```json { "name": "nightly rebuild", "tasks": [{ "refresh": "revenue_by_month" }, { "call": "close_month", "args": ["2026-09-01"] }], "cron": "0 18 * * 1-5" } ``` That cron is weekdays at 18:00 UTC. ### run_now

Run a task graph nowWriteswarehouse:model

Queues one task graph to run now rather than waiting for its schedule. Because the scheduler runs on a 15 minute grid, it starts at the next quarter hour. A graph already queued or running is not queued again. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | Yes | The graph's name, from `list_schedules`. | | `warehouse` | string | No | Which warehouse, by name or id, when the workspace has more than one. | ### pause_schedule

Pause or resume a task graphWriteswarehouse:model

Pauses a task graph so nothing fires it (not its schedule, not a sync landing, not `run_now`), or resumes one that is paused or has suspended itself. Resuming resets its failure count. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | Yes | The graph's name, from `list_schedules`. | | `resume` | boolean | No | `true` to resume. Leave it out or set it to `false` to pause. | | `warehouse` | string | No | Which warehouse, by name or id, when the workspace has more than one. | ### run_history

Run historyRead onlywarehouse:model

Shows what ran and what it cost: the recent runs of one task graph with each step's outcome and seconds, or the versions, builds and calls of one derived view or procedure. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `schedule` | string | No | A task graph, by name, to see its runs. | | `view` | string | No | A derived object, by name, to see its versions and runs. Give one of `schedule` or `view`. | | `warehouse` | string | No | Which warehouse, by name or id, when the workspace has more than one. | ## Manage sanda search Create, change, build and remove search services over tables on the map, and estimate what indexing costs first. ### list_search_services

List search servicesRead onlysearch:manageStandard · Enterprise

Lists every sanda search service in a warehouse: the table and columns it indexes, how many rows and how large the index is, when it last built and what that cost. It also says whether sanda search is switched on for the workspace at all. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `warehouse` | string | No | Which warehouse, by name or id, when the workspace has more than one. | The search tools are available on Standard and Enterprise. Not offered to cherry. ### describe_search_service

Describe a search serviceRead onlysearch:manageStandard · Enterprise

Describes one search service in full: its definition and state, every build with the rows it embedded and what it cost, and every version its definition has had. An assistant uses it to find out why a service is broken. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | Yes | The service, by name. | | `warehouse` | string | No | Which warehouse, by name or id, when the workspace has more than one. | ### estimate_search_build

Estimate what indexing would costRead onlysearch:manageStandard · Enterprise

Estimates what indexing a table would cost before anything is created: rows, tokens, dollars and how that compares with the workspace's daily AI limit. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `source` | string | Yes | The table on the map, as `mapping.
`. | | `text_columns` | array of strings | Yes | The columns whose text would be indexed. | | `key_column` | string | No | The column that identifies a row. | | `model` | string | No | The embedding model to use. Leave it out for sanda's default. | | `chunk_chars` | integer | No | How much text one indexed piece holds. | | `warehouse` | string | No | Which warehouse, by name or id, when the workspace has more than one. | `create_search_service` refuses without `confirm: true` when a build would use more than half a day's AI limit. This is how to know that in advance. ### create_search_service

Create a search serviceWritessearch:manageStandard · Enterprise

Indexes the text of a table so it can be searched by meaning as well as by words. The first build sends the text of the columns named to an embedding model outside Australia and is charged against the workspace's daily AI limit. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | Yes | A lowercase name for the service. It becomes a table in the warehouse. | | `source` | string | Yes | A table on the semantic map, as `mapping.
`. A view in the derived layer must be published to the map first. | | `key_column` | string | No | The column that identifies a row. It must be unique and never empty, because a hit is only useful if it can be joined back to a metric. | | `text_columns` | array of strings | Yes | The columns whose text is indexed. At least one. | | `attribute_columns` | array of strings | No | Columns kept beside the text as filters, such as status, region or a date. They are not indexed as text. | | `model` | string | No | The embedding model to use. Leave it out for sanda's default. | | `chunk_chars` | integer | No | How much text one indexed piece holds. The default is 1200 characters. | | `fts_config` | string, one of `simple`, `english` | No | Word matching: `simple`, or `english`, which stems words and drops common stop words. | | `after_sync` | string | No | A connection, by name or id. The service rebuilds each time it lands rows, which is usually the right trigger. | | `cron` | string | No | A five-field UTC cron on the quarter hour (minute 0, 15, 30 or 45), for a service rebuilt on a clock instead. With neither trigger the service is rebuilt when asked. | | `timezone` | string | No | The zone the cron was written for, kept for display. Evaluation is always UTC. | | `description` | string | No | What the service is for, in a sentence. | | `confirm` | boolean | No | Set to true to go ahead when the estimated cost is a large share of the day's AI limit. | | `replace` | boolean | No | Set to true to replace a service of the same name, rebuilding it from scratch. | | `warehouse` | string | No | Which warehouse, by name or id, when the workspace has more than one. | sanda search must be switched on by a workspace owner in the console before this works. An agent holding the scope cannot switch it on, because it is a decision about where a business's data may be processed. A column whose name says it holds a secret is refused outright. Not offered to cherry. ### update_search_service

Change a search serviceWritessearch:manageStandard · Enterprise

Redefines a search service or changes when it rebuilds. Changing the model, text columns, chunk size, word matching, key or source rebuilds the whole index at the cost of a first build. Changing only the attribute columns, description or cadence costs nothing. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | Yes | The service to change, by name. | | `source` | string | No | A new source table on the map. | | `key_column` | string | No | A new key column. | | `text_columns` | array of strings | No | The new list of columns to index. | | `attribute_columns` | array of strings | No | The new list of filter columns. | | `model` | string | No | A different embedding model. | | `chunk_chars` | integer | No | A new chunk size. | | `fts_config` | string, one of `simple`, `english` | No | `simple` or `english`. | | `after_sync` | string | No | A connection, by name or id. Null clears it. | | `cron` | string | No | A five-field UTC cron on the quarter hour. Null clears the schedule. | | `timezone` | string | No | The zone the cron was written for, kept for display. | | `description` | string | No | A new description. | | `enabled` | boolean | No | `false` pauses rebuilding. Searches keep working on what is already indexed. | | `suspend_after_failures` | integer | No | Pause rebuilding after this many failures in a row. | | `warehouse` | string | No | Which warehouse, by name or id, when the workspace has more than one. | A well-behaved assistant tells the person before it rebuilds a large table. Not offered to cherry. ### build_search_service

Index a search service nowWritessearch:manageStandard · Enterprise

Queues an index build now instead of waiting for the service's own trigger. Only rows whose text changed since the last build are sent to the model, so it is cheap on an unchanged table. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | Yes | The service, by name. | | `full` | boolean | No | When true, discards the index and rebuilds every row. It costs what the first build cost. | | `warehouse` | string | No | Which warehouse, by name or id, when the workspace has more than one. | This only queues the build. sanda's scheduler runs on a 15 minute grid, and indexing then runs in slices across as many ticks as it takes. Use `run_search_build` to spend the call indexing now. Not offered to cherry. ### run_search_build

Index a search service now, inside this callWritessearch:manageStandard · Enterprise

Queues a build and then spends up to 55 seconds of the call indexing it, so a small index finishes inside the call. It reports what it read, embedded and cost. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | Yes | The service, by name. | | `full` | boolean | No | When true, rebuilds every row and not only what changed. | | `warehouse` | string | No | Which warehouse, by name or id, when the workspace has more than one. | An index too large for one call keeps its place and says there is more to do: call again to carry it on, or leave it to the scheduler. This builds an index and does not query one. Use `search_documents` for that. Not offered to cherry. ### drop_search_service

Remove a search serviceDestructivesearch:manageStandard · Enterprise

Removes a search service and deletes its index from the warehouse. Searches against it stop at once, and rebuilding it later costs a full first build. The definition and every build it ran stay on record. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | Yes | The service to remove, by name. | | `warehouse` | string | No | Which warehouse, by name or id, when the workspace has more than one. | When the workspace asks for approval before an agent acts, this call is held for an owner or admin. See [permissions and approvals](https://docs.sanda-os.com.au/mcp/permissions#approvals). Not offered to cherry. ## Email reports Schedule, change, pause, send and remove report deliveries. ### list_deliveries

List report deliveriesRead onlyreports:deliver

Lists every report delivery: the report it sends, its cadence, who it goes to, whether it is paused, when it next goes and how its last run went, with each delivery's id. It also names the reports that have no delivery. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `report` | string | No | Only this report's deliveries, by name or id. | Reading deliveries needs the same scope as creating them, because the list is who receives what. ### create_delivery

Schedule a report deliveryWritesreports:deliver

Emails a report on a schedule. At each firing sanda asks every block on the report again and sends the answers to the recipients. Email only: Slack and webhook deliveries are set up on the Report schedules page, because their address is a secret. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `report` | string | Yes | The report to send, by name or id. | | `recipients` | array of strings | Yes | Email addresses, up to 25. | | `cron` | string | Yes | A five-field cron on the quarter hour (minute 0, 15, 30 or 45), for example `0 8 * * 1` for Mondays at 8. | | `timezone` | string | No | An IANA zone the cron is local to, for example `Australia/Sydney`. It stays on local time across daylight saving. Leave it out and the cron is UTC. | | `note` | string | No | A closing note for the email, up to 500 characters. | A workspace may limit which email domains a delivery can go to, and the answer says so when a recipient is outside them. To send a report once, create the delivery, send it with `send_delivery`, then pause it with `pause_delivery`. When the workspace asks for approval before an agent acts, a call naming a recipient outside the domains its members use is held for an owner or admin, because a report emailed to someone cannot be recalled. See [permissions and approvals](https://docs.sanda-os.com.au/mcp/permissions#approvals). cherry always asks before an email, whatever its mode. ```json { "report": "Board pack", "recipients": ["cfo@example.com.au"], "cron": "0 8 * * 1", "timezone": "Australia/Sydney" } ``` ### update_delivery

Change a report deliveryWritesreports:deliver

Changes who a delivery goes to, its closing note or when it goes. `recipients` is the whole new list, so to add someone pass the current recipients with them, and to remove someone leave them out. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `report` | string | No | The report the delivery sends, by name or id. | | `delivery` | string | No | The delivery's id, from `list_deliveries`, when the report has more than one. | | `recipients` | array of strings | No | The whole new list of email addresses. | | `cron` | string | No | A new five-field cron on the quarter hour. It stays on the delivery's own clock unless `timezone` names another. | | `timezone` | string | No | The IANA zone a new cron is local to. Leave it out to keep the delivery's own. | | `note` | string | No | A new closing note. An empty string removes it. | Held for approval on the same terms as `create_delivery` when a recipient is outside the domains the workspace's members use. ### send_delivery

Send a report delivery nowWritesreports:deliver

Sends a delivery now, to its recipients, instead of waiting for its schedule. It goes out within a minute or two. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `report` | string | No | The report the delivery sends, by name or id. | | `delivery` | string | No | The delivery's id, when the report has more than one. | A paused delivery can still be sent by hand. cherry always asks first, whatever its mode. ### pause_delivery

Pause or resume a report deliveryWritesreports:deliver

Pauses a delivery so its schedule stops sending it, or resumes a paused one. A resumed delivery next goes at its next firing from now. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `report` | string | No | The report the delivery sends, by name or id. | | `delivery` | string | No | The delivery's id, when the report has more than one. | | `resume` | boolean | No | `true` to resume. Leave it out or set it to `false` to pause. | ### delete_delivery

Remove a report deliveryDestructivereports:deliver

Removes a delivery. The report stays, and the record of every run it made stays on the timeline. Only the schedule and its recipients go. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `report` | string | No | The report the delivery sends, by name or id. | | `delivery` | string | No | The delivery's id, when the report has more than one. | `pause_delivery` is the way to stop one for a while. cherry always asks first, whatever its mode. ## Approvals Find out what happened to an action the workspace held for a person to approve. ### action_status

Check an action waiting for approvalRead onlyany held scope

Reports what happened to an action the workspace held for approval: still waiting, declined, expired, or approved and what the tool answered when it ran. With no id it lists this credential's ten most recent actions. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | No | The id the held call answered with. Leave it out to list recent actions. | A credential sees only its own actions. The tool is listed only for credentials that hold a scope a held tool needs (`sql:write`, `warehouse:model`, `search:manage` or `reports:deliver`), because a credential that can only ask questions never has anything waiting. It is read-only, and it is never held itself. A well-behaved assistant never tells a person a held action is done until this says it ran. See [permissions and approvals](https://docs.sanda-os.com.au/mcp/permissions#approvals). --- # Excel and Power BI (OData) > Open your semantic fluid in Excel or Power BI as live, refreshable tables. Nothing to install: paste one address and a token. Your semantic fluid is also a live data feed that Excel, Power BI and Power Query read without an add-in, a driver or a database connection. You paste an address and a token, choose the tables you want, and they arrive as refreshable tables in the words your business agreed on, not in the warehouse's column names. The feed uses OData, an open standard that Excel and Power BI already understand. It is on every edition. ```text https://console.sanda-os.com.au/api/odata/ ``` The trailing slash matters to some clients. ## How it works :::steps 1. **Create an integration token.** An owner or admin creates one under **Settings · Integrations**. The token needs no switches: asking questions is all a feed does. See [agent tokens](https://docs.sanda-os.com.au/mcp/agent-tokens). 2. **Paste the address into Excel or Power BI.** Both have an OData feed connector. See [Connect Excel](https://docs.sanda-os.com.au/odata/excel) and [Connect Power BI](https://docs.sanda-os.com.au/odata/power-bi). 3. **Sign in with the token.** Excel takes it as the password of a Basic credential. 4. **Choose tables.** Load them, and refresh whenever you like. ::: Nothing is installed, and the feed stores no copy of your data. Each refresh reads your warehouse live. ## What the feed offers For each table on your semantic map, the feed offers up to two tables: | Table | What it holds | When it exists | |---|---|---| | `invoices` | The rows. Every column the fluid can see, named the way your business named it | Every table on the map | | `invoices_summary` | That table's metrics, grouped by its dimensions | Only where the table has a metric | For example, a workspace with an `invoices` table, a `region` dimension, an `issue date` dimension and a `net revenue` metric offers `invoices` with the columns `issue_date`, `region` and the rest, and `invoices_summary` with `region`, `issue_date` and `net_revenue`. The names here are examples. Yours come from your own fluid. **Use the summary tables for numbers.** `net revenue` in a summary is the definition your business signed off, composed by the same engine that answers it in sanda's own workbench. It is not a `SUM` somebody wrote in a cell and nobody reviewed. Use the rows table when you need the detail. A few things to know about the tables: - **Names are folded into identifiers a URL can carry.** `order details` is `order_details`, and `on-time delivery %` is `on_time_delivery`. Two names that fold to the same identifier both stay reachable, and the second gets a numeric suffix. - **A column with a dimension takes the dimension's name.** Your header row reads `region` and `issue date`, not `cust_ctry_cd` and `issued_at`. - **Only what is on the map.** A table that is not on the semantic map is not in the feed. A view you build under **Data · Modelling** and put on the map appears as a table like any other. See [views](https://docs.sanda-os.com.au/modelling/views). - **Every row has a `_row` column.** It is the row's position in that read, and OData needs every row to have a key. It is not a business key and it does not survive a refresh. Ignore it, or remove the column in Power Query. If your map is empty, the feed has no tables. Import a table under **Intelligence · Semantic fluid** and it appears. ## Signing in An integration token is the only credential a feed needs, and it needs no special switches. - **Excel:** choose **Basic**, and paste the token as the password. The user name is ignored. - **Power BI:** the same Basic credential. - **A script:** send `Authorization: Bearer bat_...`. The token identifies the workspace, not the person holding it. An owner or admin creates it and can revoke it at any time under **Settings · Integrations**. Details are in the [feed reference](https://docs.sanda-os.com.au/odata/reference#authentication). ## Refreshing and what it costs Every refresh is a live read of your warehouse, through the same read-only role that answers questions. It is billed to your workspace like any other query, and it is subject to the same compute budget. A workbook set to refresh every five minutes is a query every five minutes, indefinitely, so choose a schedule somebody will actually look at. See [usage](https://docs.sanda-os.com.au/billing/usage). A page is 1,000 rows. When a table has more, sanda hands the client a link to the next page, and Excel and Power BI follow it without being asked, so a 40,000-row table simply takes a moment. ## What the feed does not do The feed is read only and deliberately small. - **It cannot write.** Anything other than a read is refused. - **`or` and `not` in a filter are refused.** sanda combines conditions with `and` only, and says so when a filter asks for more. - **`$count`, `$expand`, `$apply` and `$search` are refused by name.** A filter that was silently ignored would hand you a bigger number than you asked for, so nothing is dropped quietly. - **A metric cannot be filtered on.** It is worked out from the rows a query reads. Filter on a dimension. - **It pages up to 1,000,000 rows into a table and no further.** After that, narrow the read with a filter, or build a view. The [feed reference](https://docs.sanda-os.com.au/odata/reference) has the details. ## Next steps :::links - [Connect Excel](https://docs.sanda-os.com.au/odata/excel): Data, Get Data, From OData Feed, step by step. - [Connect Power BI](https://docs.sanda-os.com.au/odata/power-bi): Power BI Desktop, a Power Query snippet and scheduled refresh in the service. - [OData feed reference](https://docs.sanda-os.com.au/odata/reference): Addresses, tables, query options, paging, limits and errors. ::: --- # Connect Excel > Load your semantic fluid into Excel as live tables with Data, Get Data, From OData Feed. Use a Basic sign-in with an integration token, then refresh. Excel reads sanda's feed with its built-in OData connector. You paste one address and a token, tick the tables you want and load them. Refreshing re-reads your warehouse. The menu names below are from Excel for Microsoft 365 on Windows and may differ in other versions. The address and the token are the same everywhere. ## Before you start - **An integration token.** An owner or admin creates one under **Settings · Integrations**. A feed needs none of the optional switches. Copy the token when it is shown, because sanda cannot show it again. If you are not an owner or admin, ask one to create it for you. See [agent tokens](https://docs.sanda-os.com.au/mcp/agent-tokens#create-a-token). - **Something on the map.** The feed offers the tables on your [semantic fluid](https://docs.sanda-os.com.au/semantic-fluid). If the map is empty, there is nothing to load. ## Connect :::steps 1. **Open the OData feed connector.** In Excel, go to **Data · Get Data · From Other Sources · From OData Feed**. 2. **Paste sanda's address.** Paste this exactly, including the trailing slash, and press **OK**: ```text https://console.sanda-os.com.au/api/odata/ ``` 3. **Choose Basic.** Excel asks how to sign in. Choose **Basic**. 4. **Enter the token.** Paste the token into **Password**. sanda ignores the user name, so leave it empty. If your version of Excel insists on a value, type any text. If Excel asks which level to apply the sign-in to, leave it on the sanda address. Press **Connect**. 5. **Choose tables.** The Navigator lists everything on your map. Each table appears twice where it has metrics: its rows (`invoices`) and its summary (`invoices_summary`). Tick one and press **Load**, or press **Transform Data** to shape it first. ::: The table lands on a worksheet as an Excel table. Numbers arrive as numbers and dates as dates, because Excel reads the column types from the feed. ### Which table to pick - **The summary** (`..._summary`) holds your metrics grouped by your dimensions. It is the one to use for numbers: `net revenue` there is the definition your business agreed on, and it is small and quick to refresh. - **The rows** hold every column of the table. Use them for detail, and narrow them first (below), because a large table takes a while. Ignore the `_row` column. It is sanda's row number for that read, and it is not stable from one refresh to the next. ## Narrow what you load Choosing fewer columns and fewer rows makes a refresh cheaper as well as smaller, because sanda does the narrowing in the warehouse and not after the rows arrive. In the Power Query editor (**Transform Data**), remove the columns you do not need and filter the rows. Power Query sends what it can to sanda as `$select` and `$filter`. A few rules follow from what sanda accepts: - **Filters combine with "and".** A filter that needs "or" or "not" comes back as an error. Split it into separate steps, or filter after loading. - **Filter dates with "on or after" and "before".** sanda compares dates as a window that includes its first day and excludes its end, so a month filter never loses its last day. A comparison that means "after" or "on or before" is refused with a message saying which to use. - **Filter on a dimension, not a metric.** A metric is worked out from the rows a query reads, so it cannot decide which rows are read. The complete list of what sanda accepts is in the [feed reference](https://docs.sanda-os.com.au/odata/reference#query-options). ## Refresh - **Once:** choose **Data · Refresh All**. - **On a schedule:** choose **Data · Queries & Connections**, right-click the query, choose **Properties**, and set **Refresh every** and **Refresh data when opening the file**. Every refresh is a live read of your warehouse. It is billed to your workspace like any other query, so a workbook that refreshes every five minutes is a query every five minutes, indefinitely. Pick a schedule somebody will actually look at. See [usage](https://docs.sanda-os.com.au/billing/usage). When a table has more than 1,000 rows, Excel follows sanda's links to the next pages on its own. A 40,000-row table takes a little longer, and nothing more is needed. sanda pages up to 1,000,000 rows into a table. Past that, filter the table or build a view under **Data · Modelling**. See [views](https://docs.sanda-os.com.au/modelling/views). ## Change or clear the saved token Excel keeps the credential in its data source settings on your computer. To swap in a new token after you [rotate one](https://docs.sanda-os.com.au/mcp/agent-tokens#rotate-a-token), or to be asked again: :::steps 1. **Open the settings.** Go to **Data · Get Data · Data Source Settings**. 2. **Choose the sanda address** in the list. 3. **Edit or clear.** Press **Edit Permissions** and then **Edit** beside the credentials to enter a new token, or press **Clear Permissions** and Excel will ask for a credential again on the next refresh. ::: When a token is revoked or expires, the next refresh fails with a sign-in error. sanda answers a missing or wrong token with a 401. ## If something goes wrong Excel shows sanda's own message when it can. These are the common ones. | What Excel shows | What it means | What to do | |---|---|---| | A sign-in dialog or a credentials error | The token is wrong, revoked or expired | Create a new token and [update the saved one](https://docs.sanda-os.com.au/odata/excel#change-or-clear-the-saved-token) | | "That token cannot run queries, so it cannot read a feed" | The token lacks the permission to ask questions, which every token normally has | Create a new token | | "This workspace has nothing on its semantic map yet" | No tables are on the map | Import a table under **Intelligence · Semantic fluid** | | "This feed has no table called ..." | The name is not one sanda offers | Open the Navigator and choose from the list. The message names the tables it has | | "sanda can only combine filters with “and”" | A filter used “or” | Rewrite it without “or” | | "... looks like it holds a secret, so sanda won't select it" | The table has a column whose name looks like a password or key, and sanda never reads those | Rename the column if the name is wrong, or build a view that leaves it out | | "Reading ... took longer than sanda will wait" | The read ran past 30 seconds | Filter it, or remove columns | | "sanda could not read ... just now" | A temporary fault reading the warehouse. Nothing was lost | Refresh again | | "This workspace is suspended" | The workspace is suspended, for example because a trial ended | An owner can check **Settings · Plan & budget**, or ask sanda support | More help is in [troubleshooting](https://docs.sanda-os.com.au/help/troubleshooting). ## Next steps :::links - [Connect Power BI](https://docs.sanda-os.com.au/odata/power-bi): The same feed in Power BI Desktop and the Power BI service. - [OData feed reference](https://docs.sanda-os.com.au/odata/reference): Every table, option, limit and error. - [Agent tokens](https://docs.sanda-os.com.au/mcp/agent-tokens): Create, rotate and revoke the token your workbook holds. ::: --- # Connect Power BI > Read your semantic fluid in Power BI Desktop with the OData feed connector, shape it in Power Query, and keep it fresh with scheduled refresh in the service. Power BI reads sanda's feed with its built-in OData connector, the same one Excel uses. You connect once in Power BI Desktop with a token, and after you publish, the Power BI service refreshes the dataset on a schedule. ## Before you start - **An integration token.** An owner or admin creates one under **Settings · Integrations**. A feed needs none of the optional switches. Copy it when it is shown, because sanda cannot show it again. See [agent tokens](https://docs.sanda-os.com.au/mcp/agent-tokens#create-a-token). - **Something on the map.** The feed offers the tables on your [semantic fluid](https://docs.sanda-os.com.au/semantic-fluid). The feed is on every edition. ## Connect in Power BI Desktop :::steps 1. **Open the OData feed connector.** On the **Home** ribbon, choose **Get data**, then **OData feed**. If it is not in the short list, choose **More**, search for OData, and select **OData feed**. 2. **Paste sanda's address.** Paste it exactly, including the trailing slash, and press **OK**: ```text https://console.sanda-os.com.au/api/odata/ ``` 3. **Choose Basic.** When Power BI asks how to sign in, choose **Basic**. Paste the token into **Password**. sanda ignores the user name, so leave it empty, or type any text if Power BI insists on a value. 4. **Apply it to the sanda address.** If Power BI asks which level to apply the sign-in to, leave it on the address you typed. Press **Connect**. 5. **Choose tables.** The Navigator lists everything on your map. A table with metrics appears twice: its rows (`invoices`) and its summary (`invoices_summary`). Tick what you need, then choose **Load**, or **Transform Data** to shape it first. ::: Use the summary for numbers. `net revenue` in a summary is the definition your business signed off, composed by the same engine that answers it in sanda. Use the rows table for detail, and ignore its `_row` column: it is sanda's row number for that read and it is not stable from one refresh to the next. A table's description in the fluid is sent as its OData description, for clients that show one. ## Shape the data in Power Query Choosing **Transform Data** opens Power Query. What you do there is sent to sanda where it can be: removing columns becomes `$select`, and filtering rows becomes `$filter`. Narrowing at the source makes a refresh cheaper as well as smaller. A query that loads one table is a few lines of Power Query M: ```powerquery title="Load a summary table" let Source = OData.Feed("https://console.sanda-os.com.au/api/odata/"), invoices_summary = Source{[Name = "invoices_summary", Signature = "table"]}[Data] in invoices_summary ``` The credential is not part of the query. It is stored in Power BI's data source settings, so the token never appears in your file. To keep this year's rows and only the columns you need: ```powerquery title="Filter and choose columns" let Source = OData.Feed("https://console.sanda-os.com.au/api/odata/"), invoices_summary = Source{[Name = "invoices_summary", Signature = "table"]}[Data], ThisYear = Table.SelectRows(invoices_summary, each [issue_date] >= #datetimezone(2026, 1, 1, 0, 0, 0, 0, 0)), Columns = Table.SelectColumns(ThisYear, {"region", "issue_date", "net_revenue"}) in Columns ``` The table and column names in these snippets are examples. Yours come from your own fluid, so copy them from the Navigator. A few rules follow from what sanda accepts: - **Filters combine with "and".** A filter that needs "or" or "not" is refused with a message saying so, and Power BI shows it in the error pane. Filter in separate steps, or after loading. - **Filter dates with "on or after" and "before".** sanda compares dates as a window that includes its first day and excludes its end, so a month filter never loses its last day. A comparison that means "after" or "on or before" is refused. - **Filter on a dimension, not a metric.** A metric is worked out from the rows a query reads. The complete list is in the [feed reference](https://docs.sanda-os.com.au/odata/reference#query-options). ## Refresh In Power BI Desktop, choose **Refresh** on the **Home** ribbon. Every refresh is a live read of your warehouse through the read-only role, billed to your workspace like any other query. sanda sends tables in pages of 1,000 rows, and Power BI follows the links to the next pages on its own. ## Publish and schedule a refresh in the service A dataset in the Power BI service refreshes without your desktop, so it needs the token entered again there. sanda's feed is on the public internet, so the service reads it directly and no on-premises data gateway is involved. :::steps 1. **Publish the report** from Power BI Desktop to a workspace in the Power BI service. 2. **Open the dataset's settings.** In the service, find the dataset in its workspace and open its **Settings**. 3. **Edit the credentials.** Under **Data source credentials**, choose **Edit credentials** for the sanda data source. 4. **Choose Basic.** Set the authentication method to **Basic**, leave the user name empty or type any text, and paste the token into the password box. Choose a privacy level, then **Sign in**. 5. **Turn on scheduled refresh.** Under **Scheduled refresh**, switch it on, choose how often it runs and save. ::: How often the service lets you refresh depends on your Power BI licence. Whatever the schedule, each run is a query against your warehouse, so choose a frequency the report actually needs. When you [rotate the token](https://docs.sanda-os.com.au/mcp/agent-tokens#rotate-a-token), edit the credentials here as well as in Power BI Desktop. A dataset whose token was revoked or has expired fails its next refresh with a sign-in error. ### Power Query Online and dataflows A dataflow uses Power Query Online, which has an OData connector. Give it the same address and the same Basic credential, with the token as the password. ## If something goes wrong Power BI shows sanda's own message when it can. | What Power BI shows | What it means | What to do | |---|---|---| | A sign-in prompt, or a credentials error on refresh | The token is wrong, revoked or expired | Create a new token and update the credentials, in Desktop and in the service | | "This workspace has nothing on its semantic map yet" | No tables are on the map | Import a table under **Intelligence · Semantic fluid** | | "sanda can only combine filters with “and”" | A filter used “or” or “not” | Rewrite it without them | | "... is a metric, worked out from the rows sanda reads" | A filter named a metric | Filter on a dimension instead | | "... looks like it holds a secret, so sanda won't select it" | A column whose name looks like a password or key is in the table, and sanda never reads those | Rename the column if the name is wrong, or build a view that leaves it out. See [views](https://docs.sanda-os.com.au/modelling/views) | | "Reading ... took longer than sanda will wait" | The read ran past 30 seconds | Filter the table, or remove columns | More help is in [troubleshooting](https://docs.sanda-os.com.au/help/troubleshooting). ## Next steps :::links - [OData feed reference](https://docs.sanda-os.com.au/odata/reference): Every table, option, limit and error. - [Connect Excel](https://docs.sanda-os.com.au/odata/excel): The same feed in a workbook. - [Agent tokens](https://docs.sanda-os.com.au/mcp/agent-tokens): Create, rotate and revoke the token your dataset holds. ::: --- # OData feed reference > The sanda OData feed in full: addresses, tables, data types, query options, the filter grammar, paging, limits and every error it returns. sanda serves your semantic fluid as an OData v4 service. This page is the exact contract: what to send, what comes back, and what sanda refuses. For a walk through, see [Connect Excel](https://docs.sanda-os.com.au/odata/excel) or [Connect Power BI](https://docs.sanda-os.com.au/odata/power-bi). The feed is read only and is on every edition. ## Addresses The service root is: ```text https://console.sanda-os.com.au/api/odata/ ``` Keep the trailing slash. Some clients resolve their own links against this string, and without it the service document is the last path segment rather than the root. | Address | Returns | |---|---| | `/api/odata/` | The **service document**: every table this workspace offers | | `/api/odata/$metadata` | The **CSDL document**: every table's columns and their types, as XML | | `/api/odata/
` | The rows of one table | `$metadata` is also accepted percent-encoded as `%24metadata`. Only `GET` and `HEAD` are accepted. Anything else is refused with 405 and `Allow: GET, HEAD`. Every response carries `OData-Version: 4.0` and `Cache-Control: no-store`. Rows come back as `application/json;odata.metadata=minimal;charset=utf-8`, and `$metadata` as `application/xml;charset=utf-8`. A browser request that carries an `Origin` other than sanda's own is refused with 403, so a web page on another site cannot read the feed. ## Authentication A feed takes a credential that was typed in. It never uses a signed-in browser session. | Credential | How to send it | Use it for | |---|---|---| | Integration token as a Basic credential | `Authorization: Basic `, with the token as the password | Excel, Power BI, and anything that cannot set a bearer header | | Integration token as a bearer | `Authorization: Bearer bat_...` | Scripts and clients that can set a header | | OAuth access token as a bearer | `Authorization: Bearer ` | A client that already signed a person in with OAuth | Notes: - **Either half of a Basic credential is read.** A token begins `bat_`, and sanda picks it out of the pair, so a token pasted into the user name box works too. - **An OAuth access token is refused as a Basic credential.** It expires in an hour and is refreshed by a client that knows how, which a workbook does not. - **The token needs `query:run`.** Every integration token has it. Nothing else is asked for, and the feed cannot write, define or run SQL of its own. - **A missing or wrong credential is a 401** with `WWW-Authenticate: Basic realm="sanda", charset="UTF-8", Bearer realm="sanda"`. That header tells a client to ask for a credential rather than treat the response as a failure. Get a token under **Settings · Integrations**. See [agent tokens](https://docs.sanda-os.com.au/mcp/agent-tokens). ## Tables For each table on the semantic map, the feed offers a **rows** table named for the table, and, when the table has at least one metric, a **summary** table. | Table | Name | Properties | |---|---|---| | Rows | `
` | Every column of the table the fluid can see, up to 200. A column that has a dimension takes the dimension's name | | Summary | `
_summary` | The table's dimensions, then its metrics. One row for each combination of dimension values | Names in the fluid are lowercase prose. The feed folds each into an identifier: lowercase, every run of characters other than letters and digits becomes one underscore, underscores at either end are dropped, and a name that starts with a digit gets an `n` in front. `order details` is `order_details`, and `on-time delivery %` is `on_time_delivery`. If two names fold to the same identifier, the second gets `_2`, then `_3`, so both stay reachable. Every table also has a `_row` property, typed `Edm.Int64`. It is the row's position in that read (a `$skip` of 1,000 makes the first row of the next page `1000`). OData needs every row to have a key, and sanda has no honest business key to offer, because a declared primary key is missing on plenty of tables and a key that is not unique makes a client silently drop rows. `_row` is not a business key and it does not survive a refresh. You cannot filter or sort by it, and it is ignored in `$select`. Summary tables are grouped by every dimension they carry, so `$select` on a summary is also a coarser grouping. Select only `region` and `net_revenue`, and you get one row for each region. A table is only in the feed when it is on the map. If the map is empty, the service document lists no tables and reading any table returns 404. ### Service document ```json { "@odata.context": "https://console.sanda-os.com.au/api/odata/$metadata", "value": [ { "name": "invoices", "kind": "EntitySet", "url": "invoices" }, { "name": "invoices_summary", "kind": "EntitySet", "url": "invoices_summary" } ] } ``` ### Metadata `$metadata` is a CSDL 4.0 document in the namespace `sanda`, with the container `fluid`. Each table is an entity set, and its entity type is named `
_row`. ```text title="Abridged" ``` - **Every property is nullable** except `_row`, because a column you left out with `$select`, a join that matched nothing and an empty cell look the same to a client. - **Decimals carry `Scale="variable"`.** CSDL defaults an unstated scale to zero, which would have a client round every price and total. - **A table's description** rides as the annotation `Org.OData.Core.V1.Description`, for clients that show a description beside a table. A summary table's description is generated: "invoices: its metrics, grouped by its dimensions." ## Data types Types are declared in `$metadata` and values are typed to match, so a number arrives as a number and Excel can add it up. | Warehouse type | OData type | In JSON | |---|---|---| | `boolean` | `Edm.Boolean` | `true` or `false` | | `smallint` | `Edm.Int16` | A number | | `integer` | `Edm.Int32` | A number | | `bigint` | `Edm.Int64` | A number, or a string when it has more than 15 significant digits | | `numeric`, `decimal` | `Edm.Decimal` | A number, or a string when it has more than 15 significant digits | | `money` | `Edm.String` | The amount as the warehouse formats it, such as `$1,234.50`. Cast it to `numeric` in the model for a number | | `real`, `double precision` | `Edm.Double` | A number | | `date` | `Edm.Date` | `2026-06-30` | | `timestamp`, `timestamp with time zone` | `Edm.DateTimeOffset` | `2026-06-30T04:05:06.000Z`, always UTC | | `time`, `time with time zone` | `Edm.TimeOfDay` | `14:30:00`. A time zone offset is dropped, because OData has no time of day with an offset | | `interval` | `Edm.String` | An ISO 8601 duration, such as `P0Y1M3DT4H0M0S`. OData's duration type cannot hold months or years, so an interval is sent as text | | `uuid` | `Edm.Guid` | A string | | `text`, `character varying`, `jsonb`, arrays, ranges and enums | `Edm.String` | A string. JSON and arrays are sent as JSON text. Nothing is truncated | | A metric in a summary | `Edm.Decimal` | A number | Two things follow. A value too large to be exact as a JSON number arrives as a string rather than as a number that is close. And a client that would rather have every 64-bit integer and decimal as a string can say so by sending `IEEE754Compatible=true` in its `Accept` header: ```text Accept: application/json;IEEE754Compatible=true ``` ## Query options Options are applied in the warehouse and not after the rows arrive, so a `$filter` makes a refresh cheaper as well as smaller. | Option | Supported | Behaviour | |---|---|---| | `$select` | Yes | A comma-separated list of property names, case insensitive. Fewer columns is a cheaper query. `_row` and `*` are ignored, and a `$select` that names nothing else is refused | | `$filter` | Yes | See [the filter grammar](https://docs.sanda-os.com.au/odata/reference#the-filter-grammar) | | `$orderby` | Yes | Comma-separated `name`, `name asc` or `name desc`. You can order only by properties you also select. `_row` is refused | | `$top` | Yes | A whole number of rows, 0 or more. It bounds the whole read, not one page. `$top=0` returns an empty page | | `$skip` | Yes | A whole number of rows to skip, from 0 to 1,000,000 | | `$format` | `json` only | `json` or `application/json`. Any other value is refused | | `$count` | `$count=false` only | `$count=false` is accepted, and any other value is refused | | `$expand`, `$apply`, `$search` | No | Refused by name, with a message saying what to do instead | sanda does not implement any other system query option and does not read one. The four it refuses by name are refused because ignoring them would give you a bigger answer than you asked for, and nothing is dropped quietly: - `$count` would need a second scan of the table, billed to the workspace, to fill in a number nothing on the sheet needs. Follow the pages instead. - `$expand`: the tables in the feed have no links between them. sanda joins on the map instead, so ask for a summary, or model a view under **Data · Modelling**. - `$apply`: sanda does its grouping in the fluid. Every table with metrics already has a summary that is grouped. - `$search`: use `$filter` with `contains`, or sanda search in the console. ### The filter grammar A `$filter` is one or more conditions joined by `and`. Parentheses are accepted and change nothing, because every condition is combined with `and`. ```text region eq 'AU' and total ge 100 and contains(notes,'urgent') ``` **Comparisons** have the form `property operator value`: | Operator | Means | Applies to | |---|---|---| | `eq` | Equals | Text, number, boolean, date | | `ne` | Does not equal | Text, number, boolean, date | | `gt` | Greater than | Number | | `ge` | Greater than or equal, or on or after | Number, date | | `lt` | Less than, or before | Number, date | | `le` | Less than or equal | Number | **Text functions** take a text property and a quoted value: | Function | Example | |---|---| | `contains(property,'value')` | `contains(notes,'urgent')` | | `startswith(property,'value')` | `startswith(region,'New')` | | `endswith(property,'value')` | `endswith(region,'Wales')` | **Null tests** are `property eq null` (the value is empty) and `property ne null` (it has a value). Only `eq` and `ne` may be used with `null`. **Values:** - Text is in single quotes, and a quote inside text is doubled: `'O''Brien'`. - Numbers are bare: `100`, `-2.5`. - Dates and instants are bare, in ISO 8601: `2026-01-01` or `2026-01-01T00:00:00Z`. - Booleans are bare: `true` or `false`. Property names are case insensitive and are the names in `$metadata`. What kind of property it is decides which operators it accepts: a **date** is a property whose dimension is a time dimension or whose column is a date or timestamp, a **number** is a numeric column, a **boolean** is a boolean column, and everything else is **text**. What sanda refuses, always with a sentence that says what to write instead: - **`or` and `not`.** A query carries conditions that are all combined with `and`, so a disjunction has nowhere to go, and turning one into an `and` would answer a narrower question than the one asked. Ask two questions, or use `ne`. - **`gt` and `le` on a date.** sanda compares dates as a window that includes its first day and excludes its end, which is why a month filter never loses its last day. Use `ge` for on or after and `lt` for before. - **A metric.** A metric in a summary is worked out from the rows a query reads, so it cannot decide which rows are read. Filter on a dimension. - **A text function on a property that is not text.** - **More than 20 conditions.** - **A property that is not in the table.** The message suggests the nearest names. A month, filtered correctly: ```text $filter=issue_date ge 2026-06-01 and issue_date lt 2026-07-01 ``` ## Paging A page is at most **1,000 rows**. When more rows remain, the response carries `@odata.nextLink`, an address that keeps every option you sent and moves `$skip` on. Its query string is percent-encoded (`%24skip`), which every client decodes. Excel and Power BI follow it without being asked, so a large table just takes a little longer. ```json { "@odata.context": "https://console.sanda-os.com.au/api/odata/$metadata#invoices_summary(region,net_revenue)", "value": [ { "_row": 0, "region": "AU", "net_revenue": 128450.5 }, { "_row": 1, "region": "NZ", "net_revenue": 41210 } ], "@odata.nextLink": "https://console.sanda-os.com.au/api/odata/invoices_summary?%24select=region%2Cnet_revenue&%24skip=1000" } ``` The rows and numbers above are an illustration. `@odata.context` names the columns you selected in brackets, and leaves them out when you selected all of them. - **Every page is ordered.** An `OFFSET` over an unordered result is a different arbitrary slice on every page, and the symptom is a workbook missing rows. If you send no `$orderby`, sanda sorts ascending by the first four dimension or column properties it returns. Two rows that tie on every one of them may swap places, which a client cannot tell apart anyway. - **`$top` bounds the whole read.** A `$top` of 50 returns 50 rows and no `nextLink`, however large the table is. A `$top` of 2,500 returns three pages, and the `nextLink` carries the rows still to come. - **`$skip` goes up to 1,000,000.** Past that, every page costs the warehouse the whole scan again, so sanda refuses with `becca.too_deep`. Narrow the feed with `$filter`, read a summary instead, or build a view under **Data · Modelling**. ## Limits | Limit | Value | |---|---| | Rows in one page | 1,000 | | Deepest `$skip` | 1,000,000 | | Time one read may take | 30 seconds | | Columns in a rows table | 200 | | Conditions in one `$filter` | 20 | A read runs through the warehouse's read-only role and is billed to the workspace like any other query. See [usage](https://docs.sanda-os.com.au/billing/usage). The feed composes no SQL of its own. Every read goes through the composer that answers questions in sanda's workbench, so anything that surface refuses, the feed refuses for the same reason: a column whose name looks like it holds a secret, two tables that would fan out if joined, a table a join repeats that has no key columns. Reading a table that has a column named like a secret is refused whole. Leave the column out with `$select`, rename it if the name is wrong, or build a view without it. ## Errors A read sanda will not compose is a 4xx that carries sanda's own sentence. A 200 with no rows would look to Excel like an empty table, and nothing would say why, so a refusal is never a 200. ```json { "error": { "code": "becca.refused", "message": "sanda can only combine filters with “and”. Ask the two questions separately, or narrow with eq on a list." } } ``` `code` is a stable word a script can branch on, and `message` is the sentence a person sees in Excel's error pane. | Status | `code` | When | |---|---|---| | 400 | `becca.refused` | A `$select`, `$filter`, `$orderby`, `$top` or `$skip` sanda cannot use, or a question the composer will not answer | | 400 | `becca.unsupported` | `$count`, `$expand`, `$apply` or `$search`, or a `$format` that is not JSON | | 400 | `becca.too_deep` | A `$skip` beyond 1,000,000 | | 401 | `becca.unauthorized` | No credential, or one sanda does not recognise. Carries `WWW-Authenticate` | | 402 | `becca.suspended` | The workspace is suspended | | 402 | `becca.budget_suspended` | The warehouse is suspended for its budget | | 403 | `becca.scope` | The token cannot ask questions | | 403 | `becca.origin` | A browser sent an `Origin` sanda does not answer | | 404 | `becca.no_such_set` | No table by that name. The message lists the tables there are | | 405 | `becca.method` | Not a `GET` or `HEAD` | | 502 | `becca.warehouse` | The read timed out, or the warehouse failed. A timeout says to narrow it with `$filter` or `$select` | | 503 | `becca.no_warehouse` | The workspace has no warehouse to read yet | ## Try it from a terminal `-u ":$TOKEN"` is the Basic form with an empty user name. ```bash TOKEN=bat_your_token_here # every table this workspace offers curl -s https://console.sanda-os.com.au/api/odata/ -u ":$TOKEN" # the columns and their types curl -s 'https://console.sanda-os.com.au/api/odata/$metadata' -u ":$TOKEN" # ten rows curl -s 'https://console.sanda-os.com.au/api/odata/invoices?$top=10' -u ":$TOKEN" # this year, by region curl -s 'https://console.sanda-os.com.au/api/odata/invoices_summary?$select=region,net_revenue&$filter=issue_date%20ge%202026-01-01' \ -u ":$TOKEN" # the same with a bearer header curl -s https://console.sanda-os.com.au/api/odata/ -H "Authorization: Bearer $TOKEN" ``` Replace `invoices`, `invoices_summary` and the column names with your own, which the service document and `$metadata` list. Put a space in a URL as `%20`. ## Next steps :::links - [Connect Excel](https://docs.sanda-os.com.au/odata/excel): Data, Get Data, From OData Feed. - [Connect Power BI](https://docs.sanda-os.com.au/odata/power-bi): Desktop, Power Query and scheduled refresh. - [Filters](https://docs.sanda-os.com.au/reference/filters): The operators and named periods sanda uses in its own filters. ::: --- # Reference > Exact answers, generated from the product wherever possible: limits, scopes, sync modes, filters, formulas, SQL and every sanda term. The pages in this tab are for looking things up. Where a figure or a list is something sanda enforces (a plan limit, a scope, a sync mode, a filter operator, a formula function, an MCP tool), the table is generated from the same definitions the product runs on, so it matches what the console and the server actually do. ## Look it up :::links - [Limits](https://docs.sanda-os.com.au/reference/limits): Every limit sanda enforces, per edition, and what happens when you reach one. - [Scopes](https://docs.sanda-os.com.au/reference/scopes): The permissions an agent token or a connected app can hold, and the tools each one allows. - [MCP tool reference](https://docs.sanda-os.com.au/mcp/tools): Every tool sanda's MCP server offers, with its parameters, scope and edition. - [Sync modes](https://docs.sanda-os.com.au/reference/sync-modes): The five ways sanda reads a source and writes it to your warehouse. - [Filters and date ranges](https://docs.sanda-os.com.au/reference/filters): The operators and named periods behind every filter. - [Sheet formulas](https://docs.sanda-os.com.au/reference/sheet-formulas): The operators, functions, errors and formats a report sheet understands. - [SQL reference](https://docs.sanda-os.com.au/reference/sql): The dialect, schemas and naming, and what sanda sql allows. - [Glossary](https://docs.sanda-os.com.au/reference/glossary): Every sanda term, alphabetically. - [Keyboard shortcuts](https://docs.sanda-os.com.au/reference/keyboard-shortcuts): Every shortcut in the console. ::: ## For machines Every page on this site is also published as Markdown at the same address with `.md` on the end, for example [this page as Markdown](https://docs.sanda-os.com.au/reference.md). An index of every page, for AI assistants and other tools, is at [llms.txt](https://docs.sanda-os.com.au/llms.txt), and the whole site as one file is at [llms-full.txt](https://docs.sanda-os.com.au/llms-full.txt). --- # Glossary > Every sanda term in alphabetical order, each with a short definition and a link to the page that covers it. Terms are listed alphabetically. Names inside the semantic fluid, such as a table, metric or dimension, are always lowercase. For a guided introduction to the main ideas, read [core concepts](https://docs.sanda-os.com.au/get-started/concepts). ### Admin A member role that can do everything an owner can except the owner's decisions: closing, reopening, deleting or handing over the workspace, changing or removing an owner, switching sanda search on or off, and requiring two-step sign-in. Admins provision warehouses, connect systems, manage the team, set budgets, and create agent tokens. See [members and roles](https://docs.sanda-os.com.au/workspace/members-and-roles). ### Agent token A secret that lets a program read your workspace without a person signing in, such as Claude Code, an agent of your own, Excel or Power BI. Owners and admins create one under **Settings · Integrations**, where it is called an integration token. sanda shows it once, its optional permissions start off, and it can expire or be revoked. See [agent tokens](https://docs.sanda-os.com.au/mcp/agent-tokens). ### Alias What your team calls a table or column. An alias maps a hard-to-read column name to a business term, and can carry synonyms and value labels, such as a stored value `AUTHORISED` shown as `Approved`. See [naming](https://docs.sanda-os.com.au/semantic-fluid/naming). ### Approval On the **Agents** page, an owner or admin can turn on **Ask before an agent acts**. An agent's risky actions, such as committing SQL, dropping a derived view or removing a search service, then wait for approval instead of running. Anything not decided within 24 hours expires without running. See [MCP permissions](https://docs.sanda-os.com.au/mcp/permissions). ### Ask first The default cherry permission mode. Every change cherry proposes waits as a card with **Confirm** and **Decline**, and nothing runs until you press one. Compare [autonomous](https://docs.sanda-os.com.au/reference/glossary#autonomous) and [held action](https://docs.sanda-os.com.au/reference/glossary#held-action). See [actions and approvals](https://docs.sanda-os.com.au/cherry/actions-and-approvals). ### Autonomous A cherry permission mode. cherry defines semantics, builds views, schedules tasks, starts syncs and loads rows as soon as it decides to. It still asks before emailing a report, dropping a view, retiring a table, or deleting a definition or a delivery. Set it under **Settings · cherry**. See [actions and approvals](https://docs.sanda-os.com.au/cherry/actions-and-approvals). ### sanda search Search over the text inside your own tables, by meaning as well as by words. You define a [search service](https://docs.sanda-os.com.au/reference/glossary#search-service), sanda builds an index in your warehouse, and a query returns the records that match. An owner must switch it on first, and it is included from Standard. The console calls its page **Vector search**. See [search](https://docs.sanda-os.com.au/search). ### sanda sql The name for SQL that runs against your warehouse, whether from the [SQL shell](https://docs.sanda-os.com.au/reference/glossary#sql-shell) or written by cherry. It is PostgreSQL-compatible. See [SQL reference](https://docs.sanda-os.com.au/reference/sql). ### Block One piece of a report: a figure (kpi), a bar chart, a line chart, a table, a written analysis, or a header. A block stores the question it asks, not the rows, and asks again each time the report opens. See [blocks](https://docs.sanda-os.com.au/reports/blocks). ### Budget A monthly ceiling on metered spend that an owner or admin sets under **Settings · Plan & budget**, in **Budget & alerts**. Reminder emails go out as the month fills, and at 100% the warehouse is [suspended](https://docs.sanda-os.com.au/reference/glossary#suspended) until an owner or admin resumes it. A trial can set one too, and until it does, its only ceiling is its free credit. See [budgets and limits](https://docs.sanda-os.com.au/billing/budgets-and-limits). ### Category One word for a way of slicing, across every column that carries it, such as `region` or `product line`. See [dimensions and filters](https://docs.sanda-os.com.au/semantic-fluid/dimensions-and-filters). ### cherry sanda's AI data agent. It answers questions, defines metrics and relationships, builds views and reports, starts syncs, and walks a new workspace through setup. It lives in a pane beside your work, and on its own page. See [cherry](https://docs.sanda-os.com.au/cherry). ### cherry chat The full-screen page for cherry, in the left navigation. It is the same conversation as the cherry pane. See [a tour of the console](https://docs.sanda-os.com.au/get-started/console-tour). ### Closed workspace A workspace an owner has closed. It is read-only for 90 days, so every page still opens and an export can still be taken. After that, the warehouse and its contents are destroyed. An owner can reopen it at any point before then. See [close or delete](https://docs.sanda-os.com.au/workspace/close-or-delete). ### Compute The processing your warehouse does when queries run. It scales automatically, drops to zero when idle, and is billed by the hour it works. A question wakes it for a minute, so several questions inside that minute count once. See [usage](https://docs.sanda-os.com.au/billing/usage). ### Connected app An AI assistant, most often Claude, that a person has connected by approving sanda's consent screen. It acts as that person, with the permissions they ticked. Owners and admins see every connected app under **Settings · Integrations**, beside the agent tokens, and can disconnect any of them. See [connect Claude](https://docs.sanda-os.com.au/mcp/connect-claude). ### Connection A link between one source system and one warehouse. It records the connector, the streams to sync and the schedule. You add one from **Data · Connections**. See [connections](https://docs.sanda-os.com.au/connections). ### Connector A supported kind of source system, such as Xero, Shopify or PostgreSQL. The connector catalogue lists every one. See [connectors](https://docs.sanda-os.com.au/connectors). ### Console The signed-in web app where you work in sanda, at console.sanda-os.com.au. See [a tour of the console](https://docs.sanda-os.com.au/get-started/console-tour). ### Cursor The column an incremental stream uses to tell which rows are new or changed since the last run, such as an updated-at timestamp. See [sync modes](https://docs.sanda-os.com.au/reference/sync-modes). ### Daily AI limit The most AI a workspace can use in a day. It starts at A$5, and owners and admins can raise it to A$50 under **Budget & alerts**. Every AI call counts, including sanda's own work such as learning and search indexing. At the limit, AI waits until midnight UTC and nothing else stops. See [models and usage](https://docs.sanda-os.com.au/cherry/models-and-usage). ### Data region Where a workspace's data is held: its warehouses, and the loading, staging and modelling of what you sync. You choose it when you sign up, and the workspace stays in it, so it cannot be changed later. Workspaces can be created in Australia (Sydney). AI features, and sanda search if an owner switches it on, send some data outside the region, as [how sanda works](https://docs.sanda-os.com.au/get-started/how-becca-works#where-your-data-lives) explains. See [workspace settings](https://docs.sanda-os.com.au/workspace#workspace). ### Data rules Rules that limit which rows a reader sees on the reports portal, such as only their own region. They apply to readers, whose only door to the data is the portal. See [the reports portal](https://docs.sanda-os.com.au/reports/portal). ### Dataset A group of tables in the semantic fluid. By default it is every stream from one connector, and it tints those tables on the map. See [tables](https://docs.sanda-os.com.au/semantic-fluid/tables). ### Delivery A schedule that sends a report by email, or posts a summary to Slack, or posts each run as JSON to a webhook. You manage them on the **Report schedules** page. See [report delivery](https://docs.sanda-os.com.au/reports/delivery). ### Dimension In the semantic fluid, a way to slice a number, such as `customer`, `region` or `month`. A time dimension has a grain such as day and rolls up to month, quarter and year. See [dimensions and filters](https://docs.sanda-os.com.au/semantic-fluid/dimensions-and-filters). ### Edition Basic, Standard or Enterprise. An edition sets the platform fee, how many warehouses you can hold, how large each can grow, and whether sanda search and included support are on. A trial has every feature. See [editions](https://docs.sanda-os.com.au/billing/editions). ### Explorer A read-only tree of every schema, table, view and procedure in a warehouse, with columns and the first rows of each. You reach it from **Data · Explorer**. See [the explorer](https://docs.sanda-os.com.au/warehouse/explorer). ### Fact A kind of table. A fact table records something that happened and carries measures, such as invoices, payments or orders. The other kind is a dimension table, which records something that exists, such as customers or products. See [tables](https://docs.sanda-os.com.au/semantic-fluid/tables). ### Filter A named condition that says which rows count, such as `approved invoices`. You write it once and reuse it. See [dimensions and filters](https://docs.sanda-os.com.au/semantic-fluid/dimensions-and-filters). ### Frozen workspace A trial whose free credit is used up, or whose 7 days are over. Queries, reports, AI, syncs and scheduled runs wait, and your data is kept. Moving to an edition lifts the freeze, and the workspace carries on where it stopped. **Resume warehouse** does not lift it. See [the trial](https://docs.sanda-os.com.au/billing/trial). ### Full refresh A sync mode family that reads the whole table on every run. It comes as **Overwrite**, which replaces what landed last time, **Overwrite + Deduped**, which also keeps one row per primary key, and **Append**, which keeps a snapshot of each run. It needs no cursor. See [sync modes](https://docs.sanda-os.com.au/reference/sync-modes). ### Glue How two source systems join to each other, for example matching a store code in one system to a site id in another. It is one of the definitions on the map's rail. See [relationships](https://docs.sanda-os.com.au/semantic-fluid/relationships). ### Healing When a source changes, such as a renamed column, sanda notices the drift, remaps the definitions that used it, and logs it. The Semantic fluid page lists these under "The fluid at work", each marked healed or open. See [semantic fluid](https://docs.sanda-os.com.au/semantic-fluid). ### Held action A change cherry has proposed that is waiting for you. In [ask first](https://docs.sanda-os.com.au/reference/glossary#ask-first) mode it shows as a card with **Confirm** and **Decline**. When several are waiting, **Confirm all** runs them in the order cherry proposed them. See [actions and approvals](https://docs.sanda-os.com.au/cherry/actions-and-approvals). ### Incremental A sync mode family that reads only the rows whose cursor has moved since the last run. It comes as **Append**, which adds them, and **Append + Deduped**, which keeps the latest version of each primary key. See [sync modes](https://docs.sanda-os.com.au/reference/sync-modes). ### Intake table A table opened so that a connected assistant holding the intake permission can add rows to it, matching its columns, and do nothing else to it. It suits a log somebody keeps by talking. You open one when you upload a CSV, and close it in the **Intake tables** panel under **Settings · Integrations**. See [upload a CSV](https://docs.sanda-os.com.au/connections/upload-a-csv). ### Integration token The name **Settings · Integrations** uses for an [agent token](https://docs.sanda-os.com.au/reference/glossary#agent-token). ### Learn from my data Asking cherry to read your warehouse and propose the tables, relationships, metrics and dimensions it holds. It ends by telling you what stood out and what it could not settle, and opens the suggestions for you to review. See [learn from my data](https://docs.sanda-os.com.au/semantic-fluid/learn). ### Learning dataset A sample dataset you can add from the Warehouse page: a specialty-foods importer with ten years of trading, already modelled. It is free and read-only, and it lets you ask questions before your own data lands. See [warehouse](https://docs.sanda-os.com.au/warehouse). ### Map The canvas view of the semantic fluid. Tables are cards, relationships are lines, and a rail down the left opens each kind of definition: new table, datasets, relationships, aliases, categories, metrics, filters and glue. See [the map](https://docs.sanda-os.com.au/semantic-fluid/the-map). ### Materialized view A view whose rows are stored and refreshed on demand or on a schedule, so queries read the stored result. You build one in **Modelling**, and can give it a unique key so a refresh does not block readers. See [views](https://docs.sanda-os.com.au/modelling/views). ### MCP The Model Context Protocol, an open standard for letting AI assistants use tools. sanda is an MCP server, so an assistant such as Claude can search your definitions and query your data through the semantic fluid, with only the permissions you grant. It is included from Standard. See [MCP](https://docs.sanda-os.com.au/mcp). ### Member A person with a seat in a workspace. Seats are not priced. A member's [role](https://docs.sanda-os.com.au/reference/glossary#role) decides what they can do. See [members and roles](https://docs.sanda-os.com.au/workspace/members-and-roles). ### Memory What cherry keeps across conversations: facts about your business that you have told it, and how you like to work. Every line is read into every conversation, so remove a wrong one. You manage it under **Settings · cherry**. See [memory and history](https://docs.sanda-os.com.au/cherry/memory-and-history). ### Metering How sanda measures what you use: storage in gigabyte-months held, compute in hours, AI by the token, and managed sync by the run. Each edition adds a flat platform fee. The console shows usage as it accrues. See [usage](https://docs.sanda-os.com.au/billing/usage). ### Metric A number defined once, with a name, a definition and optional conditions, such as `net revenue`. Every answer that uses it uses the same definition, and one metric can build on another. See [metrics](https://docs.sanda-os.com.au/semantic-fluid/metrics). ### Modelling The area of the console where you build views, materialized views and procedures in your warehouse, and the tasks that keep them fresh. What you build lives in the `derived` schema. See [modelling](https://docs.sanda-os.com.au/modelling). ### OData feed A read-only feed of your semantic fluid that Excel, Power BI and Power Query read with nothing installed. It uses an agent token, and it works on every edition. See [OData](https://docs.sanda-os.com.au/odata). ### Optimise A page reached from a warehouse's card that finds ways to speed up queries and reclaim storage. Review the evidence and the SQL before applying a change, because each statement uses billed compute. See [warehouse](https://docs.sanda-os.com.au/warehouse). ### Owner The role you have when you sign up. An owner can do everything, including closing or deleting the workspace and handing ownership to an admin. See [members and roles](https://docs.sanda-os.com.au/workspace/members-and-roles). ### Pipeline The console page that shows one timeline of what runs and when: syncs, task runs and report deliveries. You reach it from **Monitor · Pipeline**. See [pipeline](https://docs.sanda-os.com.au/modelling/pipeline). ### Platform fee The flat monthly fee for a workspace, set by its edition. A trial pays none. It is separate from what you use. See [editions](https://docs.sanda-os.com.au/billing/editions). ### Primary key The column, or columns together, that identify one row of a table. The sync modes that deduplicate need one. When you add a table to the map, sanda guesses its key and checks the guess against a sample of the rows. See [sync modes](https://docs.sanda-os.com.au/reference/sync-modes). ### Procedure A stored routine, written in SQL or PL/pgSQL, that you build in Modelling and can run on demand or as a step in a task. See [modelling](https://docs.sanda-os.com.au/modelling). ### Provisioning Creating your warehouse. sanda does it in under a minute, from **Data · Warehouse**. See [provision a warehouse](https://docs.sanda-os.com.au/warehouse/provision). ### Public link A link that lets anyone who holds it read one report, with no account. The link is the whole credential, so treat it like a password. You can set an expiry and revoke it. See [sharing](https://docs.sanda-os.com.au/reports/sharing). ### Reader A member role for people you build reports for. A reader signs in to the reports portal, reads what you publish, and can ask cherry about it. They have no console, and data rules can limit their rows. See [the reports portal](https://docs.sanda-os.com.au/reports/portal). ### Read-only A member role that can open every page and ask every question but changes nothing. See [members and roles](https://docs.sanda-os.com.au/workspace/members-and-roles). ### Relationship How two tables join. You draw it between two columns on the map, and it records which side holds a single row. Any other table that carries the same key becomes joinable too. See [relationships](https://docs.sanda-os.com.au/semantic-fluid/relationships). ### Report A canvas of blocks that you arrange on a page. A report stores the questions its blocks ask, so it shows current numbers each time it opens. See [reports](https://docs.sanda-os.com.au/reports). ### Reports portal A place for the people you build reports for. They sign in, read the reports you publish to them, and can ask cherry about the numbers. They never see the console. See [the reports portal](https://docs.sanda-os.com.au/reports/portal). ### Role What a member can do in a workspace: [owner](https://docs.sanda-os.com.au/reference/glossary#owner), [admin](https://docs.sanda-os.com.au/reference/glossary#admin), [read-only](https://docs.sanda-os.com.au/reference/glossary#read-only) or [reader](https://docs.sanda-os.com.au/reference/glossary#reader). See [members and roles](https://docs.sanda-os.com.au/workspace/members-and-roles). ### Schedule When something runs: a connection's sync, a task in Modelling, or the delivery of a report. You pick a time of day in your own timezone, and runs fall on a 15 minute grid. See [schedules](https://docs.sanda-os.com.au/connections/schedules). ### Schemas The groups of tables inside your warehouse. `raw` holds data as it landed. `derived` holds the views and procedures you build in Modelling. `mapping` holds what the semantic fluid publishes for reading, one view per table on the map. `search` holds sanda search indexes. See [how sanda works](https://docs.sanda-os.com.au/get-started/how-becca-works). ### Scope A permission an agent token or connected app holds, such as reading the fluid, running queries or starting a sync. Every token can ask questions, and every other scope is off until someone switches it on. See [scopes](https://docs.sanda-os.com.au/reference/scopes). ### Search service The definition behind sanda search. It names one table on your map, a key column that identifies each row, and the text columns to search. sanda builds the index in your warehouse and can refresh it after the connection that feeds it lands rows. See [create a search service](https://docs.sanda-os.com.au/search/create-a-service). ### Semantic fluid The shared business language on top of your warehouse: the tables you allow, how they join, and what your numbers mean. cherry, sanda search, MCP, reports and the Excel feed all read through it, so they agree. See [semantic fluid](https://docs.sanda-os.com.au/semantic-fluid). ### Sheet The instructions that shape a table block's rows once they arrive: formula columns, formats, totals, a sort, and hidden columns. A sheet stores instructions, never rows. See [sheets](https://docs.sanda-os.com.au/reports/sheets). ### Short name The workspace's short name (its slug), shown in **Settings · Workspace** and typed to confirm closing or deleting the workspace. See [close or delete](https://docs.sanda-os.com.au/workspace/close-or-delete). ### Sign-in link The single-use link sanda emails you to sign in. It works once and expires after 30 minutes, and asking for a new one cancels the earlier ones. There are no passwords. See [sign-in](https://docs.sanda-os.com.au/workspace/sign-in). ### SQL shell A page for running one SQL statement at a time directly on a warehouse. Only owners and admins can use it. It is read-only unless you switch to read-write, results are capped and timed, every statement is recorded, and there is no undo. See [SQL](https://docs.sanda-os.com.au/warehouse/sql). ### Stream One table or object that a connection syncs from its source, such as invoices or contacts. See [choose streams](https://docs.sanda-os.com.au/connections/choose-streams). ### Suggestion What learning proposes and cherry offers for you to accept: a table, relationship, metric or dimension. Nothing reaches an answer until you accept it. You can accept it as it is, correct it first, or reject it. Also called a proposal. See [review suggestions](https://docs.sanda-os.com.au/semantic-fluid/review). ### Support ticket A message you send from **System · Support**. It reaches sanda's team with your workspace's record attached, and appears under **Your tickets** with a status. See [getting help](https://docs.sanda-os.com.au/help). ### Suspended The state of a warehouse whose month's metered spend has reached your budget. Queries, reports, chat, syncs and scheduled runs wait, and your data is kept. An owner or admin resumes it with **Resume warehouse** once the budget is raised or a new month begins. A trial that runs out of credit or reaches its end date is [frozen](https://docs.sanda-os.com.au/reference/glossary#frozen-workspace) instead. See [usage and suspension](https://docs.sanda-os.com.au/warehouse/usage-and-suspension). ### Sync One run of a connection: it reads the source and lands the rows in your warehouse. It runs on the connection's schedule, or when you press **Sync now**. See [monitor syncs](https://docs.sanda-os.com.au/connections/monitor-syncs). ### Sync mode How a stream is read and written on each run. There are five: three full refresh modes and two incremental modes. Each stream has its own. See [sync modes](https://docs.sanda-os.com.au/reference/sync-modes). ### Table In the semantic fluid, a table or view from your warehouse that you have put on the map. It is either a [fact](https://docs.sanda-os.com.au/reference/glossary#fact) or a dimension. Adding a table publishes it as a view in the `mapping` schema. See [tables](https://docs.sanda-os.com.au/semantic-fluid/tables). ### Task An ordered list of steps on one warehouse, such as refreshing a materialized view or calling a procedure. It runs on a schedule, after a named connection lands rows, or by hand, and it pauses itself after repeated failures. You manage tasks on the **Tasks** tab in Modelling. See [modelling](https://docs.sanda-os.com.au/modelling). ### Thread One conversation with cherry. Threads are yours, and you can rename, archive and search them. See [memory and history](https://docs.sanda-os.com.au/cherry/memory-and-history). ### Trial A trial has every feature and lasts 7 days, after which the workspace is [frozen](https://docs.sanda-os.com.au/reference/glossary#frozen-workspace) until it moves to an edition. It comes with A$10 of [free credit](https://docs.sanda-os.com.au/reference/glossary#trial-credit) and needs no card. See [the trial](https://docs.sanda-os.com.au/billing/trial). ### Trial credit The free credit a trial comes with, A$10. Compute, storage, AI and sync runs draw it down at the published rates, and nothing is billed. When it is used up, the workspace is [frozen](https://docs.sanda-os.com.au/reference/glossary#frozen-workspace). The top bar shows the balance. See [the trial](https://docs.sanda-os.com.au/billing/trial). ### Two-step sign-in A code from an authenticator app, asked for after your email link or your Google or Apple sign-in. Anyone can turn it on for themselves, and an owner can require it of everyone. 10 recovery codes cover a lost phone. See [two-step sign-in](https://docs.sanda-os.com.au/workspace/two-step). ### Vector search The label for sanda search in the console's left navigation, under **Intelligence**. See [sanda search](https://docs.sanda-os.com.au/reference/glossary#sanda-search). ### View A saved query in your warehouse, built in Modelling. A view can be put on the map, and cherry and reports then read it like any other table. See [views](https://docs.sanda-os.com.au/modelling/views). ### Warehouse Your sanda warehouse: a dedicated PostgreSQL database that belongs to your workspace, held in its [data region](https://docs.sanda-os.com.au/reference/glossary#data-region). Every connection and CSV lands in it. See [warehouse](https://docs.sanda-os.com.au/warehouse). ### Workspace Your business's home in sanda. It holds your warehouse, connections, semantic fluid, reports, cherry conversations, settings and team, and it is what you are billed for. An email address belongs to one workspace. See [workspace](https://docs.sanda-os.com.au/workspace). --- # Limits > Every limit sanda enforces, per edition, what happens when you reach one, and which you can raise. A limit is the most of something your workspace can have. It is not a price. At a limit sanda pauses loading or slows queries, and it never bills past one. Prices are on [How billing works](https://docs.sanda-os.com.au/billing). | Limit | Basic | Standard | Enterprise | | --- | --- | --- | --- | | Warehouses per workspace | 1 | 3 | 50 | | Storage per warehouse | 10 GB | 100 GB | 16 TB | | Concurrent connections per warehouse | 60 | 60 | 300 | | Longest single query | 60 seconds | 60 seconds | 5 minutes | | Search services per workspace | Not included | 5 | No limit | | Rows per indexed table | Not included | 2,000,000 | No limit | | Pushes per workspace | Not included | 1 | No limit | | Compute per warehouse | Up to 4 compute units | Up to 8 compute units | Up to 16 compute units | | Daily AI limit | A$5 to start, up to A$50 | A$5 to start, up to A$50 | A$5 to start, up to A$50 | | Schedule grid | Every 15 minutes | Every 15 minutes | Every 15 minutes | A trial has Standard's limits, with a smaller allowance for sanda search. See [Free trial](https://docs.sanda-os.com.au/billing/trial). ## How limits work **Edition limits are ceilings.** They are the most a warehouse can be set to. You can set any warehouse's limits lower, which is a way to cap a month, and never higher than your edition allows. If you move to a lower edition, sanda caps what you had set at the new numbers. **sanda measures and enforces them itself.** It reads each warehouse's size and the work it does regularly, warns at 80% of a limit, and enforces at the limit. Reads, reports and chat keep working throughout. **You are told.** When a warehouse passes 80% of a limit, and when it reaches one, sanda emails your owners and admins, unless you have turned those alerts off. The warehouse card also says where it stands. See [Budgets and AI limits](https://docs.sanda-os.com.au/billing/budgets-and-limits). To see or lower a warehouse's limits, go to **Data · Warehouse** and press **Limits** on its card. The form has **Storage limit (GB)**, **Compute limit (hours/day)**, **Concurrent queries** and **Statement ceiling (ms)**. Press **Apply limits** to save. Only owners and admins can. ## Warehouses per workspace How many warehouses the workspace can hold. The sample learning dataset does not count. - **At the limit,** sanda will not provision another. The message names your edition and how many it holds, and says to delete one you no longer need or to move up an edition. - **To raise it,** change edition. Beyond the top edition's figure, talk to sanda support. ## Storage per warehouse The most data one warehouse can hold, measured as the size of the warehouse's database. It includes tables you sync or upload, tables and views you build, and sanda search indexes. - **At 80%,** the warehouse card reads "Past 80% of a limit". - **At the limit,** loading pauses: syncs and CSV uploads stop landing rows. sanda will not start a new search service, because an index needs room to write. Queries are narrowed. Reads, reports and chat keep working. The card reads "At a limit", and loading resumes when the warehouse is back under the limit or the limit moves. - **To raise it,** change edition. To get back under it, delete data you no longer need. ## Compute per warehouse Compute is the same on every edition. The table shows the most any warehouse can use at once. It is billed on what runs, not on this ceiling. You can also set a **Compute limit (hours/day)** on each warehouse, below the platform's ceiling. You are billed the hours used, including any that run once the warehouse is at its limit. - **At the limit,** queries are narrowed, with fewer at once and less time for each, and loading pauses until use is back under. Reads, reports and chat keep working. - **It does not change with edition.** Setting a lower limit is how you hold compute spend down. ## Concurrent connections per warehouse How many queries can run against a warehouse at the same moment. It is the **Concurrent queries** field in the Limits form. - **At the limit,** more than that cannot run at the same moment. - **To raise it,** change edition. Enterprise allows more, as the table shows. You can set it lower. ## Longest single query The longest a single query can run before sanda stops it. It is the **Statement ceiling (ms)** field in the Limits form, and it is in milliseconds. Rebuilds of views and scheduled runs have their own separate allowance. - **At the limit,** the query is cancelled and returns a timeout error. Narrow it with a filter or a derived view. - **To raise it,** change edition. Enterprise allows longer queries. You can set it lower. ## Search services per workspace How many sanda search services the workspace can hold. Basic does not include sanda search, so it has none. Enterprise has no limit. - **At the limit,** sanda will not create another. The message says how many the edition allows. - **To raise it,** remove a service you no longer need, or change edition. ## Rows per indexed table The largest table a single search service can index. sanda estimates the table's size when you create the service. - **Over the limit,** sanda refuses to create the service and suggests narrowing the source with a derived view, or talking to sanda about a larger allowance. - **To raise it,** change edition. Enterprise has no limit. ## Daily AI limit The most AI spend a workspace can run up in a day. The table shows where it starts and the most owners and admins can set it to. The limit resets at midnight UTC. - **At the limit,** AI waits for the reset. Syncs, reports and your data are unaffected. - **To raise it,** an owner or admin can set their own limit up to the ceiling, in **Budget & alerts** under **Settings · Plan & budget**. For more, ask sanda support. See [Budgets and AI limits](https://docs.sanda-os.com.au/billing/budgets-and-limits). ## Schedule grid Schedules start on a fixed grid of 15 minutes. It is the same on every edition and cannot be raised. ## What can be raised | Limit | How to raise it | |---|---| | Warehouses per workspace | Change edition. Talk to sanda support beyond the top edition | | Storage per warehouse | Change edition | | Concurrent connections, longest single query | Change edition. Enterprise allows more of both | | Search services, rows per indexed table | Change edition. Enterprise has no limit | | Compute per warehouse | Not raised by edition. Every edition has the platform's ceiling | | Daily AI limit | Owners and admins, up to the ceiling. Ask sanda support for more | | Schedule grid | Fixed | To change edition, see [Editions](https://docs.sanda-os.com.au/billing/editions). :::links - [Editions](https://docs.sanda-os.com.au/billing/editions): What each edition includes and how to ask to move. - [Budgets and AI limits](https://docs.sanda-os.com.au/billing/budgets-and-limits): Your own ceilings on spend. - [Usage and metering](https://docs.sanda-os.com.au/billing/usage): How storage and compute are measured. ::: --- # Scopes > Every permission an integration token or connected app can hold, what it includes, which are privileged, and which MCP tools each one allows. A scope is one permission on an integration token or a connected app. sanda checks the scopes on every call to the MCP server, and a tool that a credential's scopes do not reach is left off its tool list. There are twelve scopes. The list below gives each one's consent-screen wording, what it includes and the tools it allows. After it comes what to know before you grant one. Who can grant each scope, and how risky actions can wait for a person, is in [permissions and approvals](https://docs.sanda-os.com.au/mcp/permissions). Every tool is in the [MCP tool reference](https://docs.sanda-os.com.au/mcp/tools). ## Every scope ### fluid:read

Any member may grant it

Read what your workspace has modelled: its metrics, dimensions, named filters and the tables sanda may query. Tools: [`search_fluid`](https://docs.sanda-os.com.au/mcp/tools#search-fluid), [`describe_table`](https://docs.sanda-os.com.au/mcp/tools#describe-table), [`search_docs`](https://docs.sanda-os.com.au/mcp/tools#search-docs). ### query:run

Any member may grant it

Ask questions of your data, composed from the definitions your workspace agreed on. Includes `fluid:read`. Tools: [`run_query`](https://docs.sanda-os.com.au/mcp/tools#run-query), [`search_documents`](https://docs.sanda-os.com.au/mcp/tools#search-documents). ### sql:run

Privileged: owners and admins grant it

Write and run its own read-only SQL against the tables sanda may query. Includes `fluid:read`, `query:run`. Tools: [`run_sql`](https://docs.sanda-os.com.au/mcp/tools#run-sql). ### fluid:write

Privileged: owners and admins grant it

Change what your workspace has modelled: put warehouse tables on the map, define metrics, dimensions, filters and relationships, and accept or remove proposals. Definitions it writes go live immediately. Includes `fluid:read`. Tools: [`list_warehouse_tables`](https://docs.sanda-os.com.au/mcp/tools#list-warehouse-tables), [`import_tables`](https://docs.sanda-os.com.au/mcp/tools#import-tables), [`set_table_kind`](https://docs.sanda-os.com.au/mcp/tools#set-table-kind), [`retire_tables`](https://docs.sanda-os.com.au/mcp/tools#retire-tables), [`define`](https://docs.sanda-os.com.au/mcp/tools#define), [`list_proposals`](https://docs.sanda-os.com.au/mcp/tools#list-proposals), [`review_proposal`](https://docs.sanda-os.com.au/mcp/tools#review-proposal), [`delete_definition`](https://docs.sanda-os.com.au/mcp/tools#delete-definition). ### workspace:read

Any member may grant it

See your connections and warehouses: what is connected, when it last synced, how full the warehouse is. Never a credential. Tools: [`list_connections`](https://docs.sanda-os.com.au/mcp/tools#list-connections), [`connection_status`](https://docs.sanda-os.com.au/mcp/tools#connection-status), [`list_warehouses`](https://docs.sanda-os.com.au/mcp/tools#list-warehouses). ### workspace:manage

Privileged: owners and admins grant it

Start a sync on a connection. Includes `workspace:read`. Tools: [`sync_now`](https://docs.sanda-os.com.au/mcp/tools#sync-now). ### warehouse:write

Privileged: owners and admins grant it

Load tables of its own rows into your warehouse (as raw.csv__…), through the same loader and storage ceiling a sync uses. It cannot touch tables a sync landed. Includes `workspace:read`. Tools: [`load_rows`](https://docs.sanda-os.com.au/mcp/tools#load-rows), [`set_intake`](https://docs.sanda-os.com.au/mcp/tools#set-intake). ### warehouse:model

Privileged: owners and admins grant it

Define views, materialized views and procedures in your warehouse (as derived.…), rebuild and run them, and put a view on the semantic map. Every build runs on your compute and is billed to you. Includes `workspace:read`. Tools: [`list_sources`](https://docs.sanda-os.com.au/mcp/tools#list-sources), [`list_views`](https://docs.sanda-os.com.au/mcp/tools#list-views), [`create_view`](https://docs.sanda-os.com.au/mcp/tools#create-view), [`refresh_view`](https://docs.sanda-os.com.au/mcp/tools#refresh-view), [`drop_view`](https://docs.sanda-os.com.au/mcp/tools#drop-view), [`create_procedure`](https://docs.sanda-os.com.au/mcp/tools#create-procedure), [`call_procedure`](https://docs.sanda-os.com.au/mcp/tools#call-procedure), [`warehouse_advice`](https://docs.sanda-os.com.au/mcp/tools#warehouse-advice), [`optimise_warehouse`](https://docs.sanda-os.com.au/mcp/tools#optimise-warehouse), [`list_schedules`](https://docs.sanda-os.com.au/mcp/tools#list-schedules), [`create_schedule`](https://docs.sanda-os.com.au/mcp/tools#create-schedule), [`run_now`](https://docs.sanda-os.com.au/mcp/tools#run-now), [`pause_schedule`](https://docs.sanda-os.com.au/mcp/tools#pause-schedule), [`run_history`](https://docs.sanda-os.com.au/mcp/tools#run-history). ### search:manage

Privileged: owners and admins grant it

Define, rebuild and remove search services over the tables on your map. The text of the columns a service names is sent to an embedding model outside Australia to be indexed, and keeping one fresh costs tokens, storage and compute, billed to you. Includes `workspace:read`. Tools: [`list_search_services`](https://docs.sanda-os.com.au/mcp/tools#list-search-services), [`describe_search_service`](https://docs.sanda-os.com.au/mcp/tools#describe-search-service), [`estimate_search_build`](https://docs.sanda-os.com.au/mcp/tools#estimate-search-build), [`create_search_service`](https://docs.sanda-os.com.au/mcp/tools#create-search-service), [`update_search_service`](https://docs.sanda-os.com.au/mcp/tools#update-search-service), [`build_search_service`](https://docs.sanda-os.com.au/mcp/tools#build-search-service), [`run_search_build`](https://docs.sanda-os.com.au/mcp/tools#run-search-build), [`drop_search_service`](https://docs.sanda-os.com.au/mcp/tools#drop-search-service). ### sql:write

Privileged: owners and admins grant it

Write and run its own SQL on your warehouse and commit it: insert, update and delete rows and create tables in the derived schema, reading every landing table on the way. It runs as the same hand the SQL shell lends an owner, there is no undo, and every statement is recorded in your audit trail. Includes `fluid:read`, `query:run`, `sql:run`, `workspace:read`. Tools: [`execute_sql`](https://docs.sanda-os.com.au/mcp/tools#execute-sql). ### warehouse:append

Any member may grant it

Add rows to the tables an owner or admin has opened for intake, matching their columns, and nothing else: it cannot create, replace or delete a table, and it cannot change what is modelled. Any member may grant this, because which tables are open is decided by the people who run the workspace. Includes `fluid:read`. Tools: [`append_rows`](https://docs.sanda-os.com.au/mcp/tools#append-rows). ### reports:deliver

Privileged: owners and admins grant it

Email your reports on a schedule to the addresses it names, change or pause those deliveries, and send one now. Each delivery puts a copy of the report's numbers in someone's inbox, which cannot be recalled. Tools: [`list_deliveries`](https://docs.sanda-os.com.au/mcp/tools#list-deliveries), [`create_delivery`](https://docs.sanda-os.com.au/mcp/tools#create-delivery), [`update_delivery`](https://docs.sanda-os.com.au/mcp/tools#update-delivery), [`send_delivery`](https://docs.sanda-os.com.au/mcp/tools#send-delivery), [`pause_delivery`](https://docs.sanda-os.com.au/mcp/tools#pause-delivery), [`delete_delivery`](https://docs.sanda-os.com.au/mcp/tools#delete-delivery). ## How scopes combine A wider scope includes the narrower ones it depends on, so a credential never needs to hold both. | Scope | Also includes | |---|---| | `fluid:read` | Nothing else | | `query:run` | `fluid:read` | | `sql:run` | `query:run`, `fluid:read` | | `sql:write` | `sql:run`, `query:run`, `fluid:read`, `workspace:read` | | `fluid:write` | `fluid:read` | | `workspace:read` | Nothing else | | `workspace:manage` | `workspace:read` | | `warehouse:write` | `workspace:read` | | `warehouse:model` | `workspace:read` | | `search:manage` | `workspace:read` | | `warehouse:append` | `fluid:read` | | `reports:deliver` | Nothing else | Nothing else nests. In particular: - `sql:write` is never implied by `sql:run`, `fluid:write`, `warehouse:write` or `warehouse:model`. Reading through the read-only role and committing through the builder role are different kinds of trust. - `warehouse:append` never implies `warehouse:write`, and it does not imply `query:run`. - `reports:deliver` does not include `query:run`, because sending a report is not the same as asking its questions. ## Privileged scopes Eight scopes change something or reach beyond the fluid, and only an **owner or admin** can grant them: `sql:run`, `sql:write`, `fluid:write`, `workspace:manage`, `warehouse:write`, `warehouse:model`, `search:manage` and `reports:deliver`. The other four (`fluid:read`, `query:run`, `workspace:read` and `warehouse:append`) can be granted by any member. On the consent screen, a member sees the privileged scopes greyed out, and they are left out of the grant. Only an owner or admin can create an integration token at all. A privileged scope also lasts only as long as the person who granted it is still an owner or admin. ## What to know before you grant one ### Asking questions and running SQL - **`fluid:read`** is what sanda offers a client that asks for no scope. It is ticked by default on the consent screen. - **`query:run`** is held by every integration token, and the Excel and Power BI feed needs it. It includes `search_documents` on Standard and Enterprise, so searching an existing sanda search service needs nothing more. An MCP call returns at most 50 rows. - **`sql:run`** allows one read-only `SELECT`, through the same read-only role as every question, returning at most 50 rows and stopping after 20 seconds. The assistant must say why it could not use `run_query`. A column whose name looks like a password or an identity number is refused. - **`sql:write`** allows one statement per call, run as the warehouse's builder role. The builder reads landing tables, the derived schema and the `mapping` tables, and writes only in the derived schema. A sync's landing tables, a published `mapping` view, the search indexes and sanda's own bookkeeping are out of reach whatever the statement says. Every statement is recorded with its reason, and a write-mode statement can be held for an owner or admin to approve. It includes `workspace:read` because a statement has to name a warehouse. ### Seeing and driving the workspace - **`workspace:read`** never returns a credential or a connection string. Five other scopes include it. - **`workspace:manage`** starts a sync. A sync reads from the source system and costs compute like any other. ### Changing the semantic fluid - **`fluid:write`** makes definitions **live at once**, not proposals, which is why only an owner or admin can grant it. Every call runs the same handler as the console's own button, with the same validation and audit record. Approvals do not hold these tools. If you want a person to make every change, leave this scope off. ### Loading and shaping warehouse data - **`warehouse:write`** lands tables as `raw.csv__`, up to 10,000 rows and 250 columns per call, and it cannot touch a table a sync landed. It also opens a table for intake. - **`warehouse:append`** is the one write any member may grant. It starts unticked on the consent screen. It allows appending up to 10,000 rows per call to a table that is open, and nothing else. An owner or admin opens a table for intake when they upload a CSV, or with `load_rows` or `set_intake`, and closes it in the **Intake tables** panel of **Settings · Integrations**. It includes `fluid:read` so that the assistant can describe the table it adds to. - **`warehouse:model`** builds in the derived schema, which the builder role can write and nothing else. Every build runs on your compute and is billed to you, and `drop_view` can be held for approval. ### Search and reports - **`search:manage`** is part of sanda search, so it applies on Standard and Enterprise. A workspace owner must switch sanda search on in the console first, and an agent holding this scope cannot do that. `drop_search_service` can be held for approval. - **`reports:deliver`** is email only, to at most 25 recipients within the recipient domains your workspace allows, on a schedule that falls on the quarter hour. `create_delivery` and `update_delivery` can be held for approval when a recipient is outside the email domains your members use. `action_status` is offered to a credential holding `sql:write`, `warehouse:model`, `search:manage` or `reports:deliver`, because those are the scopes whose tools can be held. ## Where scopes are set | Where | How | |---|---| | A connected app | The person ticks scopes on the consent screen. See [connect Claude](https://docs.sanda-os.com.au/mcp/connect-claude#the-consent-screen) | | An integration token | An owner or admin turns on switches when creating it. **May run its own SQL** is `sql:run`, and so on. Every token also holds `query:run`. See [agent tokens](https://docs.sanda-os.com.au/mcp/agent-tokens#the-switches) | | A client that asks | A client names scopes in the `scope` parameter of its authorization request. Scopes sanda does not have are dropped. See [connect other MCP clients](https://docs.sanda-os.com.au/mcp/connect-other-clients#send-the-person-to-the-consent-screen) | An OAuth client that gets a tool refused with `insufficient_scope` has learned which scope it lacks, and can ask the person to approve it. An integration token cannot ask: an owner or admin turns the switch on, or creates a new token. :::note A workspace that has been closed is read-only. Every credential is limited to `query:run` and `workspace:read`, whatever else it was granted. ::: --- # Sync modes > The five ways sanda can read a source and write it to your warehouse, with what each needs and how each treats edits, deletes and history. A sync mode is set per stream. It says how sanda reads the table at the source and how it writes the table in your warehouse. For advice on choosing, see [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes) in the connections guide. ## The modes at a glance | Mode | Needs a cursor | Needs a primary key | What it does | | --- | --- | --- | --- | | **Full refresh · Overwrite** (default) | No | No | Reads the whole table on every run and replaces what landed last time. | | **Full refresh · Overwrite + Deduped** | No | Yes | Reads the whole table on every run, replaces what landed and keeps one row per primary key. | | **Full refresh · Append** | No | No | Reads the whole table on every run and adds it beside the earlier copies: a history of snapshots. | | **Incremental · Append** | Yes | No | Reads only the rows whose cursor has moved since the last run, and adds them. | | **Incremental · Append + Deduped** | Yes | Yes | Reads only the rows whose cursor has moved, and keeps the latest version of each primary key. | A **cursor** is a column that moves forward whenever a row is created or changed, such as `updated_at`. A **primary key** is the column, or columns, that identify one row. Both are chosen in the [stream picker](https://docs.sanda-os.com.au/connections/choose-streams) unless the source has already decided them. ## Full refresh · Overwrite **Shown in stream lists as** `full refresh`. **Needs** nothing. **This is the default.** Reads the whole table on every run and replaces what landed last time. - **Edits at the source:** the table shows the new value after the next run. - **Deletes at the source:** the row is gone after the next run, because the whole table is replaced. - **History:** none. Only the result of the latest run exists. It is the default for any stream you have not chosen a mode for, because every table supports it and it needs nothing answered. Where a table does not offer it, the picker opens on the first mode the table does offer. ## Full refresh · Overwrite + Deduped **Shown in stream lists as** `full refresh · dedup`. **Needs** a primary key. Reads the whole table on every run, replaces what landed, and keeps one row per primary key. - **Edits at the source:** the table shows the new value after the next run. - **Deletes at the source:** the row is gone after the next run. - **History:** none. Use it when a source can return the same key more than once and you want a single row for each. ## Full refresh · Append **Shown in stream lists as** `full refresh · append`. **Needs** nothing. Reads the whole table on every run and adds it beside the earlier copies: a history of snapshots. - **Edits at the source:** each run's copy holds the value the row had at that run. - **Deletes at the source:** the earlier copies still hold the row. It is missing from the copies that follow. - **History:** one full copy per run. The table grows by the size of the source on every run, and the storage counts toward your warehouse limit. Each landed row carries a loading column, whose name starts with an underscore, that records when it was extracted. It is what tells one run's copy from the next. ## Incremental · Append **Shown in stream lists as** `incremental`. **Needs** a cursor. Reads only the rows whose cursor has moved since the last run, and adds them. The first run has nothing to compare with, so it reads the whole table. - **Edits at the source:** the changed row lands again as a new row, and the earlier version stays. - **Deletes at the source:** a cursor cannot see a row that is no longer there, so the row stays in the table. The exception is a source read by its database's change log, which reports a deletion as a flag on the row. - **History:** every version of every row that a run has seen. ## Incremental · Append + Deduped **Shown in stream lists as** `incremental · dedup`. **Needs** a cursor and a primary key. Reads only the rows whose cursor has moved, and keeps the latest version of each primary key. - **Edits at the source:** the table holds the latest version. - **Deletes at the source:** the row stays, for the same reason as **Incremental · Append**. - **History:** the table holds one row per key, not every version. ## What a mode needs | Need | Modes that ask for it | Where it comes from | |---|---|---| | A cursor | **Incremental · Append**, **Incremental · Append + Deduped** | You pick a column. The picker marks the source's suggestion. A source can also decide the cursor itself, and then the cell reads **source-defined** | | A primary key | **Full refresh · Overwrite + Deduped**, **Incremental · Append + Deduped** | You pick a column. A source can declare the key itself, and then the cell shows it | The picker refuses to save while a stream that is on lacks what its mode needs, and says how many streams are missing a cursor or a key. ## Modes a source offers Each table offers only the modes the source supports for it. A table that supports only one shows it as fixed text. When a source reports nothing about a table, all five are offered. A mode applies from the next sync after you save it. Changing a mode does not run anything by itself. ## Older connections Before sanda offered all five modes, a stream was either "full refresh" or "incremental", and sanda chose how to write it. A stream saved that way keeps doing what it did: a full refresh overwrote, and an incremental deduplicated when the table had a key (its own or the source's) and appended otherwise. Open **Edit streams** to move it to one of the five. ## What is the same in every mode - The table lands as `raw.__`, whichever mode reads it. - Every landed table carries a few loading columns whose names start with an underscore. They stay out of the semantic fluid. - A run that replaces a table is followed by a short window, until sanda has attached the new copy, in which a question over the table returns a message that it has no rows attached right now. See [Troubleshoot connections](https://docs.sanda-os.com.au/connections/troubleshooting#a-table-has-no-rows-attached-right-now). - Each successful run is metered as one run, however many rows it moves. :::links - [Sync modes](https://docs.sanda-os.com.au/connections/sync-modes): How to choose a mode for each table. - [Choose what to sync](https://docs.sanda-os.com.au/connections/choose-streams): The picker, cursors and primary keys. ::: --- # Filters and date ranges > The operators and named date periods behind every filter in sanda, how a condition is read, how date windows begin and end, and which time zone they use. Wherever you narrow a question in sanda, you are writing a condition: a field, a comparison and one or more values. This page is the reference for how those conditions read, the operators available, the named date periods, and how a date window is worked out. It ends with the filters on a search index, which use a smaller vocabulary of their own. ## Where filters appear | Where | What it is | |---|---| | **query semantic fluid**, on the semantic fluid page | The **only rows where** strip under your selection, with **add a condition** | | A report block | The conditions in the block's builder | | A whole report | The **filters** button on the report canvas, which opens **filters · every block** | | A push | **Which rows** in the push editor, which narrows the records a push sends values for. See [create a push](https://docs.sanda-os.com.au/push/create-a-push#which-rows) | | cherry and connected assistants | The `conditions` of a query, with a named period in `relative` | | A search index | Filters on the columns you kept beside the text. See [search filters](#search-filters) | A **condition** narrows one question and is thrown away with it. A **named filter** on your [semantic fluid](https://docs.sanda-os.com.au/semantic-fluid) is different: a condition your team wrote down and signed off, so that "approved" means one thing in every question. You use a named filter by name. A condition is something you are doing right now. ## How a condition works A condition has three parts. - **The field.** A dimension by its name, or any column of a table on your semantic map. The picker searches both, so you do not have to turn a column into a dimension first. - **The operator.** What to compare. The operators offered depend on the field's type. - **The values.** How many depends on the operator. sanda reads a field's type from the dimension's kind or the column's data type: **text**, **number**, **true or false**, or **date and time**. A date field is offered the date operators, so you are never asked to match a timestamp as if it were text. Every condition on a question must hold at once. There is no "or" between conditions. To match either of two values, give one condition both values. A condition takes up to 20 values, each up to 200 characters. A question takes up to 20 conditions, and a report holds up to 20 filters. ## Operators | Operator | Reads as | Field types | Values | | --- | --- | --- | --- | | `equals` | is | text, number, true or false, date and time | One or more | | `not_equals` | is not | text, number, true or false, date and time | One or more | | `contains` | contains | text | One or more | | `not_contains` | does not contain | text | One or more | | `starts_with` | starts with | text | One or more | | `ends_with` | ends with | text | One or more | | `gt` | is more than | number | One | | `gte` | is at least | number | One | | `lt` | is less than | number | One | | `lte` | is at most | number | One | | `between` | is between | number | Two (from and to) | | `in_range` | is within | date and time | Two (from and to) | | `before` | is before | date and time | One | | `on_or_after` | is on or after | date and time | One | | `set` | has a value | text, number, true or false, date and time | None | | `not_set` | is empty | text, number, true or false, date and time | None | A few behaviours worth knowing: - **is** with one value matches it. With several values it means "is one of them". In the console, separate several with commas. - **is not** and **does not contain** keep rows where the field is empty. A row whose status was never set is not a row whose status is "paid", so excluding "paid" does not silently drop the blanks. - **contains**, **starts with** and **ends with** ignore capitals. With several values they match any one of them. A `%` or `_` in your value is matched as itself. - **is between** includes both ends. - **is** on a date matches one exact moment. To match a whole day, use **is within** with that day as both ends. - **has a value** and **is empty** take no value. ## Date ranges ### Both ends, and which one is excluded A date window is **half-open**: it includes its start and excludes its end. July is from 1 July up to, but not including, 1 August. That means the whole of 31 July is in and the first moment of 1 August is out, whether the column holds a date, a timestamp or a timestamp with a time zone. The alternative, an inclusive pair of dates, is a common date bug in reporting. A window ending "31 July" against a timestamp means "up to midnight at the start of the 31st", and a day of trading disappears without a word. sanda avoids it by design. You do not need to think about this when you pick dates yourself. The **to** date you enter is the last day that is in the window, and the label beside the boxes says "both days included". sanda stores the next midnight behind the scenes. ### Named periods Pick a period by name instead of typing dates. They appear as buttons under **is within**. | Period | Reads as | Kind | | --- | --- | --- | | `today` | today | A single day | | `yesterday` | yesterday | A single day | | `tomorrow` | tomorrow | A single day | | `last_7_days` | last 7 days | Rolling, to the last full day or month | | `last_30_days` | last 30 days | Rolling, to the last full day or month | | `last_90_days` | last 90 days | Rolling, to the last full day or month | | `last_3_months` | last 3 months | Rolling, to the last full day or month | | `last_6_months` | last 6 months | Rolling, to the last full day or month | | `last_12_months` | last 12 months | Rolling, to the last full day or month | | `this_week` | this week | The current period | | `this_month` | this month | The current period | | `this_quarter` | this quarter | The current period | | `this_year` | this year | The current period | | `last_week` | last week | The last full period | | `last_month` | last month | The last full period | | `last_quarter` | last quarter | The last full period | | `last_year` | last year | The last full period | | `next_7_days` | next 7 days | Ahead of today | | `next_30_days` | next 30 days | Ahead of today | The **Kind** column groups the periods: a single day, rolling, the current period, the last full period, and ahead of today. The conventions below say exactly where each period starts and ends: - **`today`, `yesterday` and `tomorrow` are single days.** - **The day-based rolling periods count whole days back from the start of today.** `last_7_days`, `last_30_days` and `last_90_days` end yesterday. `last_7_days` is the seven complete days ending yesterday. Today is a part-day, and including it would make a daily total look as if it fell off a cliff every morning. - **The months periods are whole calendar months, ending with last month.** `last_6_months` asked in September is March to August: six months, each of them finished. `last_90_days` is the rolling alternative when you want a quarter's trading counted back from yesterday. - **`this_month` is the whole calendar month**, not month to date. For history the two are the same. For anything dated forward, such as a due date, month to date would hide the rows you opened the question to find. - **Weeks start on Monday.** - **Quarters and years are calendar quarters and years**, January to March and so on. There is no financial year period. For 1 July to 30 June, press **pick the dates myself** and enter them. - **`next_7_days` and `next_30_days` start tomorrow.** Here is what each period means on Wednesday 30 September 2026: | Period | Covers | |---|---| | `today` | 30 September | | `yesterday` | 29 September | | `tomorrow` | 1 October | | `last_7_days` | 23 to 29 September | | `last_30_days` | 31 August to 29 September | | `last_90_days` | 2 July to 29 September | | `this_week` | Monday 28 September to Sunday 4 October | | `last_week` | 21 to 27 September | | `this_month` | 1 to 30 September | | `last_month` | 1 to 31 August | | `last_3_months` | 1 June to 31 August | | `last_6_months` | 1 March to 31 August | | `last_12_months` | 1 September 2025 to 31 August 2026 | | `this_quarter` | 1 July to 30 September | | `last_quarter` | 1 April to 30 June | | `this_year` | 1 January to 31 December 2026 | | `last_year` | 1 January to 31 December 2025 | | `next_7_days` | 1 to 7 October | | `next_30_days` | 1 to 30 October | The console prints the resolved days under the period buttons, so you can check a window before it goes into a board pack. ### Time zones Which time zone a named period uses depends on where you set it. - **In the console.** When you press a period in the semantic fluid workbench or a block's conditions, sanda works out the dates at that moment, in your browser's time zone, at your local midnight. The dates it worked out are what is sent. - **In a report's filters.** A period set in **filters · every block** is kept as a period, not as dates, so the window moves with the calendar. sanda works it out on its own servers, in UTC, each time a block runs. - **In a push.** A period under **Which rows** is kept as a period too, so a push that sends "last 30 days" every day sends a window that moves with it. sanda works it out in UTC each time the push runs. - **Through cherry and connected assistants.** There is no browser, so a named period is worked out in UTC, and the result carries the dates it used. Ask cherry which dates it used if you need to see them. UTC midnight is 10am in Sydney during standard time and 11am during daylight saving. So a window worked out in UTC starts and ends at that hour, and before it each morning, UTC is still on yesterday's date. "Yesterday", asked at 8am in Sydney, is the day before the Sydney yesterday. When the exact days matter, such as a month-end close, give explicit dates. :::caution A block's own period is fixed at the moment you pick it A period chosen in a single block's own conditions keeps the dates it meant when you picked it. A period in **filters · every block** stays a period and moves with the calendar. To have a whole report follow the calendar, set the window in its filters. ::: ### In reports A condition on the **filters · every block** pane applies to every block. It replaces a block's own condition on the same field, so "last 12 months" there widens a block that said "last month". You can also type a sentence into **say it**, such as "only paid invoices" or "last 12 months", and sanda turns it into one condition for you. That uses AI, so it counts towards your [daily AI limit](https://docs.sanda-os.com.au/cherry/models-and-usage). ## Search filters A [search index](https://docs.sanda-os.com.au/search) has filters of its own, on the columns you kept beside the text when you created it. They are a smaller vocabulary than conditions, and every filter must match. | Column holds | Comparisons in the console | Name used by assistants | |---|---|---| | Text | **is**, **is not**, **contains** | `eq`, `neq`, `contains` | | A number, or a date and time | **is**, **is not**, **greater than**, **at least**, **less than**, **at most** | `eq`, `neq`, `gt`, `gte`, `lt`, `lte` | | True or false | **is**, **is not** | `eq`, `neq` | - A search takes up to eight filters, each value up to 200 characters. - On text, **is** matches the whole value exactly, capitals included, and **contains** ignores capitals. A text column cannot be compared with greater than or less than. - Dates and times in the console are entered in your own time zone. - Connected assistants can also use `in`, a list of up to 50 values. See [search your records](https://docs.sanda-os.com.au/search/use-search). :::links - [Search your records](https://docs.sanda-os.com.au/search/use-search): Filters on a search index. - [Build reports with cherry](https://docs.sanda-os.com.au/reports/build-with-cherry): Setting a window on a whole report. - [Ask questions](https://docs.sanda-os.com.au/cherry/ask): Naming a period in a question to cherry. ::: --- # Sheet formulas > Every operator, function, error and number format a report sheet formula understands, with worked examples on a small table. Formulas add a column of your own to a [table block](https://docs.sanda-os.com.au/reports/sheets) on a report. The language is Excel's, without the parts that need a grid: the operators are Excel's, the function names are Excel's and each means what it means in Excel. That is what lets an [exported workbook](https://docs.sanda-os.com.au/reports/sheets#take-it-to-excel) carry the same formula and keep calculating. ## The sample table The examples on this page run against this table, which is what a table block might return. The `net revenue` column is a sum, and `ikura` has no revenue. | product | net revenue | cost | |---|---|---| | chai | 1,200 | 700 | | aniseed syrup | 800 | 900 | | chang | 2,000 | 500 | | ikura | (empty) | 0 | Where an example gives one result, it is the result on the `chai` row unless the text says otherwise. ## Syntax A formula is an expression. A leading `=` is allowed and ignored, and spaces are ignored everywhere except inside text. | Piece | Written as | Notes | |---|---|---| | Column | `[net revenue]` | The column's name in square brackets, spaces and all | | Number | `12`, `3.5`, `.5`, `1e3` | No thousands separators. Write a negative with a minus sign: `-4`. | | Text | `"chai"` | In double quotes. Write a double quote inside text by doubling it: `"say ""hi"""`. | | Truth value | `TRUE`, `FALSE` | Shown as TRUE and FALSE | | Function | `ROUND([cost], 1)` | A name, then arguments in brackets separated by commas | | Grouping | `([a] + [b]) * 2` | Brackets change the order of work | Function names are not case-sensitive: `sum`, `Sum` and `SUM` are the same function. A formula can be up to 500 characters. The editor says so under the box and will not save a longer one. ## References A **column reference** in square brackets is the column's name as its header shows it. It is matched without regard to case, so `[Net Revenue]` finds `net revenue`. A reference means one of two things depending on where it sits: - **On its own or inside an operator or ordinary function**, it is **this row's value**. `[net revenue] - [cost]` is 500 on the `chai` row. - **As a bare argument of `SUM`, `AVERAGE`, `MIN`, `MAX`, `COUNT` or `COUNTA`**, it is **the whole column**. `[net revenue] / SUM([net revenue])` is each row's share of the total: 0.3 on the `chai` row. Only a *bare* `[column]` inside an aggregate reads the whole column. Any other argument is worked out for the current row. `SUM([net revenue] * 2)` is therefore not the total of a doubled column; on the `chai` row it is 2,400, the doubled value of that one row. To total a computed value, give it a column of its own and then sum that: add `revenue x2` as a formula, then use `SUM([revenue x2])`. Some more rules: - A formula can read the columns before it, including formula columns you added earlier. It cannot read itself or a column added after it. - Whole-column functions cover the rows the block returned, no more. If the table says **of more**, the block's row limit cut the result short and a total covers only the rows you can see. - A formula keeps reading a column you hide after writing it. The formula editor only accepts columns that are showing. - A reference to a column that does not exist makes the whole formula unreadable: its column shows `#NAME?`, and the sentence under the table says which column is missing. ### Empty cells and text that looks like numbers Formulas treat the data the way Excel does: - An **empty cell is 0 in arithmetic**, so `[net revenue] - [cost]` on the `ikura` row is 0, not an error. An empty cell compared with text is the empty text. - **Text that reads as a number is a number.** The warehouse often sends numbers as text (`2,000` or `0.00`). `"12" + 1` is 13, and `[net revenue] = 0` is true for a value that arrives as `"0.00"`. Two texts still compare as text, so the codes `"007"` and `"7"` are different. - **Other text in arithmetic is an error.** `[product] * 2` is `#VALUE!`. - **TRUE is 1 and FALSE is 0** in arithmetic. ## Operators From the one that binds tightest to the one that binds loosest. Operators at the same level work from left to right, and brackets override the order. | Operator | What it does | Example | Result | |---|---|---|---| | `%` after a value | Divides by 100 | `50%` | 0.5 | | `-` or `+` before a value | Sign | `-[cost]` | -700 | | `^` | Power | `2^3` | 8 | | `*` and `/` | Multiply and divide | `[cost] * 10%` | 70 | | `+` and `-` | Add and subtract | `[net revenue] - [cost]` | 500 | | `&` | Joins text | `[product] & " (" & [cost] & ")"` | chai (700) | | `=` `<>` `<` `>` `<=` `>=` | Compare, giving TRUE or FALSE | `[net revenue] > 1000` | TRUE | As in Excel, a sign binds tighter than a power, so `-2^2` is 4 and not -4. ### Comparing - Two numbers compare as numbers, even when one or both arrived as text. - A number and an empty cell compare as if the empty cell were 0, so `IF([net revenue] = 0, ...)` is true on a row with no revenue. That is the guard people write against dividing by zero, and it works. - Two texts compare **without regard to case**: `[product] = "CHAI"` is TRUE on the `chai` row, and `<` and `>` compare alphabetically. ## Functions | Function | Arguments | Reads a whole column | | --- | --- | --- | | `SUM` | One or more | Yes | | `AVERAGE` | One or more | Yes | | `MIN` | One or more | Yes | | `MAX` | One or more | Yes | | `COUNT` | One or more | Yes | | `COUNTA` | One or more | Yes | | `ROUND` | 1 to 2 | No | | `ROUNDUP` | 1 to 2 | No | | `ROUNDDOWN` | 1 to 2 | No | | `ABS` | 1 | No | | `INT` | 1 | No | | `MOD` | 2 | No | | `POWER` | 2 | No | | `SQRT` | 1 | No | | `IF` | 2 to 3 | No | | `IFERROR` | 2 | No | | `AND` | One or more | No | | `OR` | One or more | No | | `NOT` | 1 | No | | `ISBLANK` | 1 | No | | `ISNUMBER` | 1 | No | | `LEN` | 1 | No | | `UPPER` | 1 | No | | `LOWER` | 1 | No | | `TRIM` | 1 | No | | `CONCAT` | One or more | No | | `TEXT` | 1 to 2 | No | | `VALUE` | 1 | No | | `LEFT` | 1 to 2 | No | | `RIGHT` | 1 to 2 | No | Column summaries: `sum`, `avg`, `min`, `max`, `count`. Formats: `auto`, `integer`, `number`, `percent`, `currency`, `compact`, `text`. The functions marked as reading a whole column are the six aggregates. Functions that take any number of arguments (`SUM`, `AVERAGE`, `MIN`, `MAX`, `COUNT`, `COUNTA`, `AND`, `OR` and `CONCAT`) take at least one. The sections below say what each does. ### Whole-column functions | Function | Returns | Example | Result | |---|---|---|---| | `SUM(x, ...)` | The total of the numbers | `SUM([net revenue])` | 4,000 | | `AVERAGE(x, ...)` | The mean of the numbers. `#DIV/0!` when there are none. | `AVERAGE([net revenue])` | 1,333.33 | | `MIN(x, ...)` | The smallest number, or empty when there are none | `MIN([net revenue])` | 800 | | `MAX(x, ...)` | The largest number, or empty when there are none | `MAX([net revenue])` | 2,000 | | `COUNT(x, ...)` | How many numbers | `COUNT([product])` | 0 | | `COUNTA(x, ...)` | How many values are not empty | `COUNTA([product])` | 4 | These read numbers only and skip anything else, so `AVERAGE([net revenue])` divides 4,000 by 3: the empty `ikura` cell is left out, not counted as 0. Give an aggregate two columns and it reads both in full: `SUM([net revenue], [cost])` is 6,100. A value that is not a bare column, such as `SUM([cost], 10)`, is worked out for the row and counted once. ### Rounding and arithmetic | Function | Returns | Example | Result | |---|---|---|---| | `ROUND(x, [places])` | `x` rounded, halves away from zero. `places` defaults to 0 and may be negative. | `ROUND(1.005, 2)` | 1.01 | | `ROUNDUP(x, [places])` | `x` rounded away from zero | `ROUNDUP([net revenue] / 7, 1)` | 171.5 | | `ROUNDDOWN(x, [places])` | `x` rounded towards zero | `ROUNDDOWN(-2.349, 2)` | -2.34 | | `ABS(x)` | The size of `x`, without its sign | `ABS([cost] - [net revenue])` | 500 | | `INT(x)` | `x` rounded down to a whole number | `INT(-1.5)` | -2 | | `MOD(x, d)` | The remainder of `x` divided by `d`, with the sign of `d`. `#DIV/0!` when `d` is 0. | `MOD(-7, 3)` | 2 | | `POWER(x, y)` | `x` to the power `y` | `POWER(2, 10)` | 1,024 | | `SQRT(x)` | The square root. `#NUM!` for a negative. | `SQRT(16)` | 4 | `ROUND` works on the number as written, so it agrees with Excel where plain arithmetic would not: `ROUND(1.005, 2)` is 1.01, `ROUND(-2.5, 0)` is -3 and `ROUND(1234.5, -2)` is 1,200. `ROUND(x)` with no second argument rounds to a whole number. ### Logic | Function | Returns | Example | Result | |---|---|---|---| | `IF(test, then, [else])` | `then` when `test` is true, otherwise `else`, or FALSE when `else` is left out | `IF([net revenue] > [cost], "profit", "loss")` | profit | | `IFERROR(x, fallback)` | `x`, unless working it out gives a formula error, then `fallback` | `IFERROR([cost] / [net revenue], "n/a")` | 0.5833 (and n/a on `ikura`) | | `AND(x, ...)` | TRUE when every argument is true | `AND([net revenue] > 1000, [cost] < 800)` | TRUE | | `OR(x, ...)` | TRUE when any argument is true | `OR([net revenue] > 1500, [cost] > 800)` | FALSE | | `NOT(x)` | TRUE when `x` is false | `NOT([net revenue] > 1000)` | FALSE | | `ISBLANK(x)` | TRUE when `x` is empty | `ISBLANK([net revenue])` | TRUE on `ikura` | | `ISNUMBER(x)` | TRUE when `x` is a number, or text that reads as one | `ISNUMBER("12")` | TRUE | A test counts as true when it is TRUE, a number other than 0, or text that is not the number 0. It is false when it is FALSE, 0 or empty. `IF` only works out the branch it returns, so `IF([net revenue] = 0, 0, [cost] / [net revenue])` never divides by zero. `IFERROR` rescues `#DIV/0!`, `#VALUE!` and `#NUM!`. It cannot rescue `#NAME?`, because a formula that cannot be read is never run. ### Text | Function | Returns | Example | Result | |---|---|---|---| | `LEN(x)` | The number of characters | `LEN([product])` | 4 | | `UPPER(x)` | Upper case | `UPPER([product])` | CHAI | | `LOWER(x)` | Lower case | `LOWER("Chai")` | chai | | `TRIM(x)` | Removes leading and trailing spaces and squeezes runs of spaces to one | `TRIM(" a b ")` | a b | | `CONCAT(x, ...)` | The arguments joined as text | `CONCAT([product], ": ", [cost])` | chai: 700 | | `LEFT(x, [n])` | The first `n` characters. `n` defaults to 1. | `LEFT([product], 3)` | cha | | `RIGHT(x, [n])` | The last `n` characters. `n` defaults to 1. | `RIGHT([product], 2)` | ai | | `TEXT(x, format)` | A number written as text with thousands separators | `TEXT([net revenue], "0.00")` | 1,200.00 | | `VALUE(x)` | Text read as a number. `#VALUE!` when it is not one. | `VALUE("1,200")` | 1,200 | `TEXT` reads only how many zeros follow the decimal point in its format. `"0.00"` means two decimals and `"0"` means none. It always adds thousands separators, and it does not understand percent, currency or date formats. Text that is not a number comes back unchanged. To show a value as a percent in a column, set the column's [format](#number-formats) instead. ## Errors A formula that cannot be worked out on a row shows an error word in that cell, and the rest of the table carries on. Errors are Excel's words. | Cell shows | Meaning | Typical causes | |---|---|---| | `#DIV/0!` | Divided by zero | `[cost] / [net revenue]` on a row with no revenue. `MOD(x, 0)`. `AVERAGE` of no numbers. | | `#VALUE!` | A text value where a number was needed | `[product] * 2`. `VALUE("abc")`. | | `#NUM!` | Not a real number | `SQRT` of a negative. A result too large to hold. | | `#NAME?` | The formula cannot be read at all | An unknown column or function, a missing closing bracket or quote, or the wrong number of arguments | A `#NAME?` fills the whole column, marks the column's header with a red `!`, and the first problem is written under the table after the column's name. Some of the sentences you may see: | Under the table | What to fix | |---|---| | there is no column called “units” | Spell the column as its header shows it | | FOO is not a function this sheet knows | Check the function's name against the list above | | SUM is missing its closing ) | Close the bracket | | a column reference needs a closing ] | Close the square bracket | | a text value needs a closing quote | Close the double quote | | the formula ends too soon | Something is missing after an operator | `IF` and `IFERROR` are how you handle the first three. A `#NAME?` is always a mistake in the formula itself. ## Number formats A format changes how a value is **shown** and never the value itself, so a formula that reads a formatted column sees the underlying number. Use `ROUND` to change the value. A column's format is set from its menu on the table. | Format | Value | Shown as | |---|---|---| | **automatic** | 1234.567 | 1,234.57 | | **whole number** | 1234.567 | 1,235 | | **number** | 1234.567 | 1,234.57 | | **percent** | 0.256 | 25.6% | | **currency** | 1234.5 | $1,234.50 | | **compact (1.2M)** | 12500 | 12.5K | | **compact (1.2M)** | 1250000 | 1.3M | | **text** | 1234.567 | 1234.567 | Detail worth knowing: - **percent** multiplies by 100, so it expects a ratio. 0.256 shows as 25.6%, and 25.6 shows as 2,560.0%. - **compact** leaves numbers under 10,000 in full (9,500 stays 9,500) and shortens larger ones to K, M or B. - **automatic** leaves text alone and keeps a value such as `00123` as text, since a code with leading zeros is not a number. - Numeric formats right-align the column. - TRUE and FALSE always show as TRUE and FALSE. ## In the exported workbook When you [download a table as xlsx](https://docs.sanda-os.com.au/reports/sheets#take-it-to-excel), each formula column is written as a formula in Excel's own grid. `[net revenue] - [cost]` on the first data row of the sample table becomes `=(B2-C2)`, and `[net revenue] / SUM([net revenue])` becomes `=(B2/SUM(B$2:B$5))`, with the whole-column reference turned into a fixed range over every data row. Excel recalculates it, so a formula column keeps working after you change a number in the file. A column you hid on the report is written into the workbook as a hidden column, so a formula that reads it keeps its cell reference and keeps calculating. ## Limits | Limit | Value | |---|---| | Formula columns on one table block, hidden ones included | 12 | | Length of one formula | 500 characters | | Length of a column's name | 60 characters | | Rows a formula can see | The rows the block returned, at most 200 | What a formula cannot do: refer to cells such as `A1`, look things up (`VLOOKUP`), add conditionally (`SUMIF`), work with dates, or keep a running total. A formula sees only the rows of its own table. If you need a shaped number in more than one report, define it as a [metric](https://docs.sanda-os.com.au/semantic-fluid/metrics) so every report gets it. ## Worked examples **Margin, and margin as a percent of revenue.** Add two columns. The second reads the first. ```text margin [net revenue] - [cost] margin % IF([net revenue] = 0, 0, [margin] / [net revenue]) ``` Set `margin %` to the **percent** format. On the `chai` row it shows 41.7%, and on `ikura`, which has no revenue, 0.0%. **Each row's share of the total.** ```text share [net revenue] / SUM([net revenue]) ``` With the **percent** format the sample table reads 30.0%, 20.0%, 50.0% and 0.0%. **Flag the rows that need a look.** ```text status IF([cost] > [net revenue], "loss", "ok") ``` Text results are left-aligned like any other text column. **A tidy label.** ```text label UPPER(LEFT([product], 3)) & " · " & TEXT([cost], "0") ``` On the `chai` row this reads `CHA · 700`. **A ratio that never shows an error.** ```text cost ratio IFERROR([cost] / [net revenue], 0) ``` On `ikura` this is 0 instead of `#DIV/0!`. --- # SQL reference > The SQL dialect, schemas and naming, the columns sanda adds, what sanda sql allows and refuses, limits, and worked example queries. Your warehouse speaks PostgreSQL, so everything here is ordinary PostgreSQL. This page is the lookup for the parts that are sanda's: which schemas exist and what they are called, the columns sanda adds, exactly what each place you can type SQL accepts and refuses, the limits, and examples that run against a sanda warehouse as it is laid out. For a walk-through of the shell itself, see [sanda sql](https://docs.sanda-os.com.au/warehouse/sql). ## Dialect The dialect is PostgreSQL. Common table expressions, window functions, `filter (where ...)`, `distinct on`, lateral joins, arrays, JSON operators and the built-in date and text functions all work as in PostgreSQL, subject to what the role you run as may do. Identifiers fold to lower case unless you double-quote them. A landed column with capital letters has to be quoted, for example `"CustomerId"`. Names sanda creates are all lower case. ## Where SQL runs There are five places to run or store SQL, and they are not the same. What differs is who may use them, which login they run as, and how strictly the text is checked. | Where | Who | Runs as | What it accepts | |---|---|---|---| | [sanda sql](https://docs.sanda-os.com.au/warehouse/sql) | Owners and admins | The builder role | One statement of any kind the role may run. Read-only unless you switch to read-write. | | A view or materialized view definition in [Modelling](https://docs.sanda-os.com.au/modelling/views), and its preview | Owners and admins | The builder role | One `select` under the strict rules below. | | A procedure body in [Modelling](https://docs.sanda-os.com.au/modelling/procedures) | Owners and admins | The builder role | SQL or `plpgsql`, not scanned. The role's permissions are the boundary. | | cherry and connected assistants, when they write SQL with `run_sql` | Anyone using cherry, and credentials with the `sql:run` scope | A login that can read only the tables the semantic fluid publishes in `mapping` | One read-only `select`, under the strict rules below and a few more. They reach for it only when a question cannot be composed from the fluid's metrics and dimensions, and they must give a reason. | | An assistant over MCP using `execute_sql` | A credential with the `sql:write` scope | The builder role | The same as sanda sql, with a reason recorded for every statement. | Reports, the semantic workbench and the OData feed do not run SQL that you write. sanda composes their queries from the definitions on the fluid. ## Schemas and naming ### Schemas | Schema | What it holds | You can | |---|---|---| | `raw` | Landing tables: every stream a connection syncs, and every CSV upload. | Read, in sanda sql and in view definitions. | | `derived` | Your views, materialized views and procedures, and tables your own SQL creates. | Read and, in read-write mode, write. | | `mapping` | Views the semantic fluid publishes for cherry and reports, plus any tables built there (for example the sanda sample dataset copied into a warehouse). | Read the tables. The published views are not readable by the builder role. | | `search` | sanda search's indexes, one table per service. | Not from the shell. | | `meta` | sanda's own bookkeeping. | Not from the shell. | The managed sync also keeps a working area in the same database. It is not listed in the Explorer and you cannot read it. ### Names | What | Name | Example | |---|---|---| | A table landed by a connection | `raw.__` | `raw.xero__invoices` | | A table from a CSV upload | `raw.csv__
` | `raw.csv__sales` | | Your view, materialized view or procedure | `derived.` | `derived.monthly_revenue` | | A table published on the fluid | `mapping.` | `mapping.invoices` | **``** is the connection's name in lower case, with every run of characters other than `a` to `z` and `0` to `9` replaced by one underscore, leading and trailing underscores removed, and cut to 40 characters. If nothing is left, sanda uses the connector's type. Two underscores separate it from the stream. **`
` for a CSV** is the name you give the upload, in lower case, without a `.csv`, `.tsv` or `.txt` ending, with the same replacement rule, cut to 40 characters, and prefixed `t_` if it starts with a digit. The CSV's columns become lower snake case with no leading or trailing underscore, at most 58 characters, with `c_` added in front of a name that starts with a digit, `_2`, `_3` added to repeats, and `column_` for an empty heading. Types are worked out from the whole file: `bigint` for whole numbers, `numeric` for decimals, `boolean` for `true` and `false`, `date` for `2026-09-30`, `timestamptz` for ISO timestamps, and `text` for anything else. Empty cells are nulls. **`` in `derived`** is lower case letters, digits and underscores, starts with a letter, is at most 63 characters, does not start with `pg_`, and cannot match a table in `raw` or a view in `mapping`. Always write the schema, as in `raw.csv__sales`. ## Columns sanda adds Some columns in your tables are not from your source. Their names start with an underscore, and sanda treats every one as bookkeeping. | Column | Where | What it holds | |---|---|---| | `_becca_loaded_at` | Every `raw.csv__*` table | `timestamptz`, defaulting to the moment the row was loaded. | | Underscore-prefixed sync columns | Every table a connection lands | Loading details such as a record identifier, the time the row was extracted and a metadata record. Their names start with an underscore and one of two reserved prefixes. | A CSV heading never keeps a leading underscore, so a heading called `_becca_loaded_at` lands as `becca_loaded_at` and cannot collide. Because they are bookkeeping, these columns are left out wherever a person or cherry would see business data: - the [Explorer](https://docs.sanda-os.com.au/warehouse/explorer) counts them but does not list or preview them; - the views the fluid publishes in `mapping` do not name them, so cherry and reports cannot read them; - the fluid's map and the context cherry is given do not include them. They are real columns, so `select *` in sanda sql returns them, and you can use them. The prefixes are reserved and are matched as prefixes, so a column of your own called `_notes` is untouched. ## What sanda sql allows and refuses ### The builder role Every statement in sanda sql runs as the warehouse's builder role. The role, not a list of banned words, is what limits a statement. | The role can | The role cannot | |---|---| | Read `raw.*`, `derived.*` and the tables in `mapping.*` | Read a view the semantic fluid publishes in `mapping` | | Read the database catalogue: `information_schema` and `pg_catalog` | Read `search.*` or `meta.*`, or the managed sync's working area | | Create, change and drop objects in `derived` (read-write mode) | Write anywhere else, or reach another database or role | Anything outside those permissions fails with a permission error that says what the role may do. ### Statements - **One statement per press.** sanda reads the text the way PostgreSQL does. A semicolon inside a string, a double-quoted name, dollar-quoted text or a comment is text, and a semicolon between two statements is a boundary. A single trailing semicolon, with comments after it, is fine. - **Comments and dollar quoting are allowed.** `--` and `/* ... */` comments (including nested block comments), `E'...'` strings and `$tag$...$tag$` quoting all work in sanda sql. They are refused in view definitions. - **At most 20,000 characters.** - **Read-only or read-write.** In read-only mode the statement runs in a read-only transaction and PostgreSQL refuses anything that would change data, including a writable `with` and a function with side effects. In read-write mode it runs as written and commits at once. - **Statements that start with `select`, `with`, `values` or `table`** are read through a cursor, so the row limit is enforced at the source. Anything else runs directly. If it returns rows, as `explain` does, they are shown under the same cap. Otherwise the result is its command tag and the number of rows it touched. ### Rows, values and secrets - At most **500** rows come back. The **rows** menu chooses 50, 200 or 500. - A value is cut at **300** characters, with an ellipsis. Dates arrive as ISO strings and JSON as JSON text. - A result column whose **name** looks like a secret is returned with empty values, and the result lists it as withheld. The test reads the column's name in the result as words, split at underscores, hyphens, digits and changes of case (so `userPassword2` is `user`, `password` and `2`), and ignores case. These words count wherever they appear, even run together with others: `password`, `passwd`, `passphrase`, `passcode`, `passkey`, `token`, `credential`, `apikey`, `privatekey`, `secretkey`, `socialsecurity`, `taxfile` and `cardnumber`. `secret` counts as a word or at the end of one, so `clientsecret` is withheld and `secretary` is not. These count only as a whole word: `pass`, `ssn`, `tfn`, `iban`, `swift`, `cvv`, `pan` and `routing`. Pairs of words count too: `api_key`, `private_key`, `secret_key`, `social_security`, `tax_file` and `card_number`, however they are separated. So `pass_hash` and `auth_token` are withheld, and `passenger_count` and `compass_heading` are not. Giving a column a different name in your `select` shows it, so use that only where the match is a false one. - The Explorer's preview applies the same test to column names. ### Time The shell uses your warehouse's statement ceiling, which is set under **Limits** and is at most 60 seconds on Basic, 60 seconds on Standard and 5 minutes on Enterprise. It never waits longer than 55 seconds, so a longer ceiling is lowered to 55 seconds. A statement that runs past it is stopped. ## Rules for view definitions and assistant queries A view or materialized view definition, its preview, and every query cherry or an assistant writes with `run_sql` must pass one strict check first. sanda does this as a courtesy, so you get a sentence instead of a database error, and the login's permissions still stand behind it. | Rule | Detail | |---|---| | One statement | Begins with `select` or `with`. A trailing semicolon is removed. A semicolon anywhere else is refused, even inside a string. | | No comments | `--`, `/*` and `*/` are refused anywhere in the text, even inside a string. | | Ordinary quoting only | Dollar-quoted text, `E'...'` strings and `U&'...'` or `U&"..."` are refused. Write an ordinary string and double the quote to include one. | | At most 20,000 characters | Longer text is refused. | | Reads named tables | Use schema-qualified names of `raw`, `derived` or tables in `mapping`. A definition that reads a view the fluid publishes is rejected after it is created, and rolled back. | | Forbidden words | See below. | The check reads the statement as tokens. A forbidden word inside a string literal or inside a double-quoted name is fine: `where status = 'CREATED'` and `select "comment"` both pass. **Refused as bare words:** - statements and clauses: `insert`, `update`, `delete`, `drop`, `alter`, `create`, `grant`, `revoke`, `truncate`, `copy`, `merge`, `call`, `do`, `vacuum`, `analyze`, `reindex`, `cluster`, `lock`, `listen`, `notify`, `prepare`, `execute`, `comment`, `refresh`, `set`, `reset`, `begin`, `commit`, `rollback`, `savepoint`, `security` and `into`; - the catalogue: `pg_catalog`, `information_schema`, `pg_class`, `pg_attribute`, `pg_namespace`, `pg_tables`, `pg_views`, `pg_roles`, `pg_user`, `pg_shadow`, `pg_authid`, `pg_settings`, `pg_database`, `pg_proc`, `pg_stat_activity` and `pg_stat_statements`. These are refused even inside double quotes; - families of server, file, lock and replication functions, matched by prefix: names starting `pg_sleep`, `pg_terminate_backend`, `pg_cancel_backend`, `set_config`, `dblink`, `pg_notify`, `pg_reload_conf`, `pg_rotate_logfile`, `pg_switch_wal`, `pg_backend_pid`, `pg_export_snapshot`, `pg_stat_file`, `pg_advisory_`, `pg_try_advisory_`, `pg_read_`, `pg_ls_`, `lo_`, `pg_logical_`, `pg_replication_`, `pg_create_`, `pg_drop_`, `txid_` and `pg_current_`, and the XML export functions `query_to_xml`, `table_to_xml`, `cursor_to_xml`, `schema_to_xml` and `database_to_xml`. Because the word list matches whole words, a column named `comment` has to be written `"comment"`, and a bare column called `lo_score` is refused until it is quoted. Queries written by cherry and assistants face a few more rules. A statement is refused if any column or alias in it looks like a secret, if it uses a whole row as a value, or if it renames columns by position. Those queries read only the fluid's published tables. ## Limits Plan limits are on the [Limits](https://docs.sanda-os.com.au/reference/limits) page. These are the limits of the SQL itself. | Limit | Value | |---|---| | Statement in sanda sql, or in a view definition, or a procedure body | 20,000 characters | | Rows returned by sanda sql | 500 (menu: 50, 200 or 500) | | Longest value shown in a result | 300 characters | | Longest statement in sanda sql | The warehouse's ceiling, or 55 seconds, whichever is less | | Rows in a view preview, and in the Explorer's data preview | 50 | | Columns in the Explorer's data preview | 60 | | A view's preview | Stops after 20 seconds | | Build or procedure call started from the page | 55 seconds | | Build or procedure call in a task | Up to 14 minutes for the whole run | | Object names | 63 characters | | Columns in a drawn view | 200, with at most 8 joins | | Key columns on a materialized view | 8 | | Procedure arguments | 16, of the types `text`, `integer`, `bigint`, `numeric`, `boolean`, `date`, `timestamptz`, `timestamp`, `interval`, `jsonb` and `uuid` | | Steps in a task | 20 | | Concurrent queries in a warehouse | 60 on Basic, 60 on Standard, 300 on Enterprise, at most | | Warehouse statement ceiling | 60 seconds on Basic, 60 seconds on Standard, 5 minutes on Enterprise, at most | ## Error messages What you may read in sanda sql, and what to do. | Message | What it means | What to do | |---|---|---| | Type a statement first. | The editor is empty. | Type one. | | That statement is longer than 20,000 characters. | Over the length limit. | Split the work, or move it to a [procedure](https://docs.sanda-os.com.au/modelling/procedures). | | One statement at a time... | The text has a second statement after a semicolon. | Run them one after another. | | The shell is in read-only mode, and that statement would change something. | A change was attempted in read-only mode. | Switch to **Read-write** if you mean it. | | Permission denied... The builder role reads `raw.*`, `derived.*` and mapping's tables, and writes only `derived.*`. | The statement touched something the role may not. | Read from a table the role can reach, or write in `derived`. | | Something depends on it... | A `drop` or change would break another object. | Drop or redefine the dependents first. | | That ran past its time ceiling and was stopped. | The statement outlasted the ceiling. | Narrow it, add a filter, or materialize the result in a view. | | PostgreSQL's own message, ending "(at character N)" | A syntax or name error. `N` is the position in the text. | Fix the text at that position. | | This workspace's warehouse is suspended... | The workspace's budget was reached. | See [Usage, budgets and suspension](https://docs.sanda-os.com.au/warehouse/usage-and-suspension). | ## Examples These examples use a CSV upload called `sales`, with the headings `Sale date`, `Region`, `Product`, `Units` and `Revenue`, and dates written `2026-09-30`. sanda lands it as `raw.csv__sales` with the columns `sale_date` (a `date`), `region`, `product`, `units` and `revenue`. Replace the names with your own from the [Explorer](https://docs.sanda-os.com.au/warehouse/explorer). ### Find your way around ```sql title="Every table and view the builder role can read" select table_schema, table_name, table_type from information_schema.tables where table_schema in ('raw', 'derived', 'mapping') order by table_schema, table_name; ``` ```sql title="The columns of one table" select ordinal_position, column_name, data_type, is_nullable from information_schema.columns where table_schema = 'raw' and table_name = 'csv__sales' order by ordinal_position; ``` ```sql title="The bookkeeping columns on a landed table" select column_name, data_type from information_schema.columns where table_schema = 'raw' and table_name = 'xero__invoices' and left(column_name, 1) = '_' order by ordinal_position; ``` ```sql title="How big are the objects you have built" select c.relname as name, c.relkind, pg_size_pretty(pg_total_relation_size(c.oid)) as size from pg_class c where c.relnamespace = 'derived'::regnamespace and c.relkind in ('r', 'm') order by pg_total_relation_size(c.oid) desc; ``` ### Look at the data ```sql title="A quick look" select * from raw.csv__sales limit 20; ``` ```sql title="An exact row count" select count(*) as row_count from raw.csv__sales; ``` ```sql title="When a CSV table was last loaded" select max(_becca_loaded_at) as last_loaded from raw.csv__sales; ``` ```sql title="Duplicates on what should be a unique key" select sale_date, region, product, count(*) as copies from raw.csv__sales group by 1, 2, 3 having count(*) > 1 order by copies desc; ``` ### Answer a question ```sql title="Revenue by month" select date_trunc('month', sale_date)::date as month, sum(revenue) as revenue from raw.csv__sales group by 1 order by 1; ``` ```sql title="Month-on-month change" with monthly as ( select date_trunc('month', sale_date)::date as month, sum(revenue) as revenue from raw.csv__sales group by 1 ) select month, revenue, revenue - lag(revenue) over (order by month) as change, round( 100.0 * (revenue - lag(revenue) over (order by month)) / nullif(lag(revenue) over (order by month), 0), 1 ) as change_pct from monthly order by month; ``` ```sql title="Top ten products in the last 90 days" select product, sum(units) as units, sum(revenue) as revenue from raw.csv__sales where sale_date >= current_date - interval '90 days' group by product order by revenue desc limit 10; ``` ```sql title="See the plan before you run something heavy" explain select region, sum(revenue) from raw.csv__sales group by region; ``` ### Write in derived (read-write mode) ```sql title="Keep a rollup as a table" create table derived.monthly_revenue as select date_trunc('month', sale_date)::date as month, region, sum(revenue) as revenue from raw.csv__sales group by 1, 2; ``` ```sql title="Remove it again" drop table derived.monthly_revenue; ``` A table made this way has no version history and no schedule. For something sanda versions and rebuilds, define a view in [Modelling](https://docs.sanda-os.com.au/modelling/views). Its definition must satisfy the strict rules above: one `select`, no semicolon, no comments. ```sql title="A definition that passes the strict rules" select date_trunc('month', s.sale_date)::date as month, s.region, sum(s.revenue) as revenue from raw.csv__sales s group by 1, 2 ``` :::links - [sanda sql](https://docs.sanda-os.com.au/warehouse/sql): The shell, step by step. - [Views and modelled tables](https://docs.sanda-os.com.au/modelling/views): Turn a query into a view sanda keeps. - [Procedures](https://docs.sanda-os.com.au/modelling/procedures): Multi-step SQL with arguments. - [Limits](https://docs.sanda-os.com.au/reference/limits): Every plan limit by edition. ::: --- # Keyboard shortcuts > Every keyboard shortcut in the console, covering the cherry pane, message boxes, the suggestion queue, the map, the report canvas and the SQL shell. The console has a small set of shortcuts, and this page lists all of them. Use ⌘ on a Mac and Ctrl on Windows and Linux. Wherever a table says ⌘, Ctrl works the same way, except for clicking on the semantic map. ## Everywhere | Keys | What it does | |---|---| | ⌘ J | Opens and closes the cherry pane, from any page. On **Overview** and **cherry chat**, where cherry is the page, it moves the cursor to the message box instead. | | Esc | Closes the window, menu or panel you have open: dialogs, the AI usage card in the top bar, the cherry model menu, and the navigation drawer on a phone. | | Tab and Shift Tab | Move between controls. In an open dialog or the phone navigation drawer, focus stays inside until you close it. | | ← → | Move between tabs when a tab is focused. Wraps from the last tab to the first. | | Home and End | Jump to the first or last tab when a tab is focused. | ## cherry These apply to the message box in the cherry pane, on **cherry chat**, on the **Overview** page, and to the box beside a report on the reports portal. | Keys | What it does | |---|---| | Enter | Sends your message. | | Shift Enter | Starts a new line without sending. | | Esc | Closes the cherry pane beside a report on the reports portal. | ## The suggestion queue Learning proposes tables, relationships, metrics and dimensions, and they wait in the queue headed "N suggestions waiting on you" on the semantic map. These keys work while the queue is open and your cursor is not in a text field. See [review suggestions](https://docs.sanda-os.com.au/semantic-fluid/review). | Keys | What it does | |---|---| | ↓ or J | Moves to the next suggestion. | | ↑ or K | Moves to the previous suggestion. | | Enter | Accepts the highlighted suggestion. | | Delete or Backspace | Rejects the highlighted suggestion. | ## The semantic map These work on the map when your cursor is not in a text field. See [the map](https://docs.sanda-os.com.au/semantic-fluid/the-map). | Keys | What it does | |---|---| | Click a table card | Selects it. | | Shift-click a table card, or ⌘-click on a Mac | Adds the table to the selection, or takes it out. Ctrl-click does not do this. | | Delete or Backspace | With tables selected, asks whether to remove them from the map. Nothing is deleted: they drop off the map, and their relationships and metrics are retired. Press **remove** to confirm. | | Esc | Steps back one layer at a time: the confirmation, then an open dialog or panel, then a selected relationship, then the table selection. | ## The report canvas These work on a report when your cursor is not in a text field. See [the canvas](https://docs.sanda-os.com.au/reports/canvas). | Keys | What it does | |---|---| | ⌘ Z | Undoes your last change. It goes back within the current session only. | | ⌘ Shift Z | Redoes it. | | ⌘ P | Exports the report as a PDF, by opening the print dialog with the page laid out flat. | | Hold Space and drag | Pans the canvas. | | Scroll | Pans the canvas. | | ⌘ and scroll | Zooms the canvas. | | Delete or Backspace | Deletes the selected block. ⌘ Z brings it back. | | Esc | Deselects the block and closes any open panel or menu: colours, history, the more menu, and the block builder. | Inside a report's text fields, these keys apply: | Where | Keys | What it does | |---|---|---| | The report's name | Enter | Saves the name. | | A block's title | Enter or Esc | Saves the title, or cancels the edit. | | A new block you drew on the page | Enter | Sends what you typed to cherry to build the block. | | A new block you drew on the page | Shift Enter | Starts a new line. | | A new block you drew on the page | Esc | Closes the prompt, or cancels the new block. | | A block's comment box | Enter | Adds the comment. | | A block's comment box | Shift Enter | Starts a new line. | | A sheet's formula column | Enter | Saves the formula, once it is valid. | | A sheet's open menu or editor | Esc | Closes it. | ## The SQL shell | Keys | What it does | |---|---| | ⌘ Enter | Runs the statement in the editor. The shell runs one statement at a time. | ## Related pages :::links - [A tour of the console](https://docs.sanda-os.com.au/get-started/console-tour): The cherry pane and the rest of the console. - [Sheet formulas](https://docs.sanda-os.com.au/reference/sheet-formulas): The formulas a report sheet accepts. ::: --- # Release notes > What changed in sanda each month, in plain words: new features, behaviour changes, visible fixes and pricing changes. Each month has its own page, newest first. Within a month, changes are listed by date range with the latest at the top, and grouped by product area. They are what a customer can notice: features, changes in behaviour and fixes to bugs you could see. Fixes start with "Fixed:". Prices and limits change over time, so a release note that mentions a price change never quotes the figure. Read current prices and limits on [editions](https://docs.sanda-os.com.au/billing/editions). :::links - [October 2026](https://docs.sanda-os.com.au/releases/2026-10): Fixes from a security review: two-step sign-in, the SQL shell, connected apps, private reports and cherry's workspace memory. - [September 2026](https://docs.sanda-os.com.au/releases/2026-09): Reports become canvases you can share and publish. sanda search, modelling, the OData feed, cherry across the console, three editions with a trial credit, and the reports portal. - [August 2026](https://docs.sanda-os.com.au/releases/2026-08): The first month. Sign-in, warehouses and connections, the semantic fluid as a map, the learning dataset and chat over your own definitions. ::: --- # October 2026 > becca is now sanda, numbers from different tables in one answer, Push to Salesforce, a data region at signup, and fixes to sign-in, the SQL shell and reports. ## 6 October **sanda** - becca is now sanda. The name, the logo (three salmon circles) and the colours have changed across the website, the console, the reports portal, emails and these docs. Nothing else has: your workspace, warehouse, connections and credentials stay exactly as they were. - sanda has new addresses, on sanda-os.com.au: the website is sanda-os.com.au, the console is console.sanda-os.com.au, and the MCP endpoint and OData feeds are under `https://console.sanda-os.com.au/api/`. The reports portal and these docs moved the same way. Every old becca-os.com address, and the same names on sanda-os.com, takes you to its new home, so bookmarks and links in old emails still work. A Claude connection or OData feed you set up on console.becca-os.com keeps working where it is; point it at the new address when you next touch it. You sign in again once, on the new console. Email moved too: sign-in links and reports now come from noreply@sanda-os.com.au, so if your IT team allows senders by name, add that one. Write to us at hello@sanda-os.com.au. See [connect Claude](https://docs.sanda-os.com.au/mcp/connect-claude) and [OData](https://docs.sanda-os.com.au/odata). - Text is darker and easier to read everywhere, small print and labels most of all, and the dark panels are now a warm charcoal. - sanda is now run by Sanda Technologies Pty Ltd (ABN 63 702 995 015, ACN 702 995 015). The terms and the privacy policy name the company from 6 October 2026, and the footer shows both numbers. The next time you open the console it asks you to read and accept the privacy policy again. Account holders are told by email before the updated terms apply to a workspace that signed up earlier. See [privacy](https://docs.sanda-os.com.au/workspace/privacy). - Report colours have a new default, **salmon**, to match. A workspace that never picked its colours now draws reports in salmon. If you chose **cherry** before, it is still there and still yours. See [workspace settings](https://docs.sanda-os.com.au/workspace). - An authenticator app set up from today lists your two-step code under sanda. One you set up earlier keeps the label it had, and its codes still work. **Semantic fluid** - Numbers measured on different tables sit in one answer everywhere sanda reads the semantic fluid, the OData feed and pushes included. Put `revenue`, summed over order lines, beside `order count`, counted over orders, and each order is counted once however many lines it has. A table counted this way needs its key columns set. See [query the fluid](https://docs.sanda-os.com.au/semantic-fluid/query-the-fluid#numbers-from-different-tables). - A metric built from metrics on other tables runs wherever a relationship joins the tables. Each part is worked out on its own table, so `{revenue} / nullif({order count}, 0)` is revenue per order. See [metrics](https://docs.sanda-os.com.au/semantic-fluid/metrics#build-on-other-metrics). - A metric's own condition applies to that number alone. Two metrics whose conditions disagree sit side by side instead of being refused, and a metric that counts only paid invoices no longer narrows the invoice count beside it. A metric built from others keeps each part's own condition. See [metrics](https://docs.sanda-os.com.au/semantic-fluid/metrics#filters-on-a-metric). - A row whose relationship finds no match is kept, with `null` on the other side, rather than left out, so a breakdown still adds up to its total. - Every question now goes straight to your warehouse. One asked again within a few seconds is answered from the first answer, and a report with many blocks fills in from the top. See [blocks](https://docs.sanda-os.com.au/reports/blocks#what-every-data-block-stores). ## 1 to 3 October **Push** - Push is in the console, under **Activate · Push**, once it is switched on for your console. Until then it isn't listed. Connect a Salesforce org by signing in to Salesforce as the user sanda writes as, then create a push: the object, how each record is found, and the sanda value for each field. Each run sends only the records whose values changed, and a run where nothing changed makes no Salesforce call. See [Push](https://docs.sanda-os.com.au/push). - A new push opens with **Preview the next run**: how many records a run would send, the first twenty, and the rows that can't be sent as they stand. Saving sends nothing, so you can check the preview before the first run. Anyone who can open the console can preview, read-only members included. - A push's **Runs** says what each run did. Open one to see the records it couldn't deliver, with Salesforce's own reason and sanda's hint beside it. See [runs and refusals](https://docs.sanda-os.com.au/push/runs). - Push to Salesforce is included on Standard and Enterprise, and in a trial. A workspace holds up to 1 on Standard and any number on Enterprise. The editions table, **Settings · Plan & budget** and the pricing page list it. See [editions](https://docs.sanda-os.com.au/billing/editions). **Workspace and security** - Settings is now six tabs across the top of the page instead of one long page: **Workspace**, **Plan & budget**, **Team**, **cherry**, **Security** and **Integrations**. Report colours have moved into **Workspace**, and closing or deleting the workspace is at the foot of that tab. The tab you are on is kept in the address, so a link or a reload opens the same one. See [workspace settings](https://docs.sanda-os.com.au/workspace). - Signup asks where your data is held. Under **Data region**, you choose the region a new workspace lives in: its warehouse is created there, and loading and modelling run there. Workspaces can be created in Australia (Sydney). See [quickstart](https://docs.sanda-os.com.au/get-started/quickstart). - A workspace stays in the region it was created in. **Settings · Workspace** now shows its **Data region** rather than offering a choice. See [workspace settings](https://docs.sanda-os.com.au/workspace#workspace). - The global data plane, which was announced and never opened, is no longer listed on the pricing page or in Settings. - Codes tried when turning off two-step sign-in now count toward the same hourly limit as codes tried at sign-in. See [two-step verification](https://docs.sanda-os.com.au/workspace/two-step). - Connecting an assistant to a workspace that requires two-step sign-in now asks you to have it set up, even when you are signed in to a different workspace. See [Connect Claude](https://docs.sanda-os.com.au/mcp/connect-claude). - A portal handoff link opened from another website no longer signs anyone in. See [the reports portal](https://docs.sanda-os.com.au/reports/portal). - A closed workspace narrows every credential to the reading it already had. A credential that could only add rows or send reports can no longer do either. See [permissions and approvals](https://docs.sanda-os.com.au/mcp/permissions). - Fixed: the SQL shell could be made to run a second statement after the first, so a read-only statement could commit a change. It now runs exactly one statement, as it says. - Fixed: duplicating a private report, or saving it as a template, made a copy everyone in the workspace could open. The copy is now private. See [share a report](https://docs.sanda-os.com.au/reports/sharing). - Fixed: the Report schedules page listed the schedules of private reports to people who could not open them. - Fixed: a report's shared conversation could be reached from cherry's thread list by the person who typed first in it, after they lost access to the report. **cherry** - Only owners and admins add to or remove the workspace's memory. Everyone else keeps personal notes. See [memory and conversations](https://docs.sanda-os.com.au/cherry/memory-and-history). - cherry no longer runs SQL of its own when the **sql** switch is off under **Settings · cherry**, even when a model asks to. --- # September 2026 > Reports become canvases, sanda search, the modelling layer, the OData feed, cherry across the console, three editions and the reports portal. ## 28 to 30 September **Documentation** - docs.sanda-os.com.au is open: guides for every part of sanda, a page for each connector, and the MCP and OData references. - Every console page has a **Docs** link in the top bar that opens the guide to that page. The product page links each chapter to its guide, the pricing page links the billing guide, and the site footer has a Docs column. **Plans and billing** - Owners and admins move between Basic and Standard themselves: **Move to Standard** or **Move to Basic** in **Settings · Plan & budget**, then confirm. The new edition's features and limits apply at once. A move up bills Standard's fee for the month you moved in, and a move down bills Basic's from the next month. A move to Basic waits until the workspace fits it, and the message says what to delete or trim. Leaving a trial, Enterprise and agreed fees are still asked for. See [editions](https://docs.sanda-os.com.au/billing/editions). - A change of edition now applies the new edition's query and connection limits to every warehouse straight away, not at the warehouse's next measurement. - sanda search holds an index to your edition's row limit when an agent points it at a different table, and counts a view that has no row estimate instead of passing it on a sample. - Enterprise adds up to 10 times faster analytical queries from an in-memory columnar engine, a 99.99% uptime SLA that includes maintenance, read replicas for reports and dashboards, and continuous backup with point-in-time recovery. See [editions](https://docs.sanda-os.com.au/billing/editions). - MCP and agents are included on Basic, so every edition can connect Claude and other assistants. sanda search stays on Standard and Enterprise. - Standard holds up to 3 warehouses of 100 GB each and 5 search services. A warehouse's compute can burst to 4 vCPU on Basic and 8 on Standard. Enterprise runs on a plane of its own with its own storage and compute rates. See [editions](https://docs.sanda-os.com.au/billing/editions). - A trial now freezes when it reaches its end date, as the terms and the pricing page say, as well as when its credit is spent. The banner, the top bar pill (**Trial · ended**), the email and every refusal name which of the two it was. A paid edition lifts either. - Messages that send you to the budget now name its real place, **Settings · Plan & budget · Budget & alerts**, and the budget emails link straight to it. The **Remind me at** hint says the suspension notice goes whenever alert emails are on. - All AI is billed under a daily limit. Every AI call sanda makes on your workspace's data counts against it, including sanda's own learning work and calls from console buttons and from MCP. Owners and admins set the limit in the **Daily AI limit** field under **Settings · Plan & budget**, up to a ceiling. sanda support can set a higher one on request. The top bar meter shows today's spend, and its card explains what counts and shows the month. - A trial is A$10 of free credit. Everything an invoice would meter since signup draws it down at the invoice's own prices, and nothing is billed. When the credit is spent, the workspace freezes: every warehouse is suspended and sanda makes no AI calls for it. Your data, connections and reports are kept. See [trial](https://docs.sanda-os.com.au/billing/trial). - The AI meter updates while a reply is running, and a reply's cost chip shows the billed amount rather than the supplier's, learning passes included. **Workspace and security** - An expired sign-in link says so and asks for a new one. If you asked for the link from the same browser, your address is already filled in, so one press of **Email me a sign-in link** sends it. - The console's left rail names your role as **Settings · Team** does ("Read-only", not "viewer"), and the **Admin** hint lists everything that stays with the owner. - Region copy says what is true today: every workspace runs in Australia (Sydney). - Owners can delete a workspace immediately. **Delete workspace now** in **Settings · Close or delete** asks for the workspace's short name, then closes the workspace and deletes its warehouse databases, its sync connections and its data. Anyone whose only workspace it was is removed too, so their address can sign up again. You land on signup, or on sign-in if your address belongs to another workspace. - Fixed: deleting a workspace failed if it had ever accepted the terms or been invoiced. Deleting now works, and sanda rehearses every delete before it removes anything. - Signup is one short form: name, work email, company, an optional role, the terms box and **Start free trial**. It no longer asks about your systems. The console asks when it matters. - The privacy policy and terms are rebuilt from an audit of what sanda does, including exactly what is sent to AI models. A closed workspace stays read-only for 90 days before deletion, up from 30, and the governing law moves from New South Wales to Victoria. - **Settings · Team** can invite a **Reader**, who sees the reports portal and nothing else. **cherry** - Setup is warehouse first. A checklist reads the workspace as five steps in order: a warehouse, a source, learn from my data, review the suggestions and a first report. A CSV loaded into the warehouse counts as a source, and the source step is done once rows have landed, not when a connection exists. A first sync that has not started, is running, failed or finished empty says so, with a link to the connection. - Setup runs one step per press. Nothing runs by itself: a first sync lands tables and stops, and learn counts as done only after a pass has run. cherry does not chain steps or move you to another page beside a checklist that already offers the button. It no longer opens with a recap or a correction of an earlier reply. - The first report step asks for a name and a purpose, then builds a whole report: KPIs, trends, breakdowns and an analysis. - **Learn from my data** in the checklist takes you to the semantic fluid first, with cherry beside it, so you watch the pass land on the map. Once suggestions are accepted, cherry offers to tidy the map, and the map highlights its arrange button with a tip when relationships appear. - The conversation docks beside the page it opens instead of disappearing mid-reply. When the checklist scrolls out of view, a compact pinned copy sits at the top of the thread. You can unpin it. - Fixed: the text cursor in the Overview's question box was clipped by the box's rounded corner. - Fixed: after **Learn from my data**, every question over the learned tables was refused with "permission denied for schema raw". Learn now publishes each table it proposes before writing the proposal, and a map already written on landing tables moves onto its published views the next time it is read. **Connections** - **Replace** on a loaded CSV works when something is built on the table. sanda swaps the rows in place, so your views keep working, and rebuilds the semantic map's own view when the new file drops or retypes a column it reads. If one of your own views reads such a column, the load is refused and names the view. See [load a CSV](https://docs.sanda-os.com.au/connections/upload-a-csv). - **Next sync** is written in the schedule's own time zone, which the line under it names, so it agrees with the schedule wherever you open the page. - Only owners and admins can add a connection, as only they can register, run or delete one. - Zuora and BigCommerce are offered by arrangement: their connectors are no longer maintained. Connector notes no longer mention controls sanda does not have. - A running sync says how far it has got and how fast. The **Status** tab shows rows this run, rows per second, data this run and running time. A queued run reads "Queued for 3s · waiting for the sync engine", and the page checks as soon as it opens. When a run settles, a note says how it ended, such as "Sync finished: 1,990,700 rows in 6m 33s". Every successful run in the Timeline shows its average rows per second. A syncing row in the Connections list shows rows so far and rows per second, and cherry and MCP report the same progress. - A sync reads as a run: queued, launching the connector, syncing each stream, landed. Fixed: a running sync showed 0 rows and 0 B for minutes before jumping to its final count. **MCP and OData** - The consent screen says what the grant you are giving can do. It says "can write nothing" only when no ticked permission writes, and it names each write permission you tick. It points to **Settings · Integrations** to disconnect. - The workspace brief and context resources need the **fluid:read** permission, like the tools that read the fluid. A connection without it is listed no resources. - Approval requests are emailed to every owner and admin, since either can decide them, and follow the workspace's alert email switch. - The OData feed declares `time` columns as a time of day, and `interval` and `money` columns as text, so they arrive with their values instead of empty. See [OData reference](https://docs.sanda-os.com.au/odata/reference). **Semantic fluid** - Fixed: editing a metric in the metrics panel, or renaming one that others refer to, reset its **Shown as** to a plain number. Every field the panel does not show now keeps its value. - The list view's forms fill in from what is stored. The pencil on a row opens its current values, and typing a name that already exists does the same, so re-saving a table no longer blanks its grain, key, event time and notes or resets its row count. The bin on a table retires it, as removing it from the map does, instead of deleting it. - A new metric's or category's name keeps a hyphen you type: `on-time delivery %` stays `on-time delivery %`. - A metric built from metrics on other tables is refused by name in the workbench and `run_query`, instead of running against one table. The metrics panel's examples and **Insert** chips offer only metrics on the same table. - Learn from one connection reads that connection's own tables, so a small connection beside a large one is no longer told nothing has landed. - Fixed: the Ask the fluid card slid over the top bar as you scrolled, and its lists handed the scroll wheel to the page at their ends. **sanda search** - The consent screen says plainly what is sent to be embedded: the text of the columns a service indexes, and the text of every search. Owners who agreed to the earlier wording will be asked to read the new one. - The personal details warning looks only at the columns that are sent, and suggests keeping such a column as a filter. - A table only suggested for the semantic map, or retired from it, can no longer be a service's source. The picker offers the tables the map draws. **Warehouse and modelling** - Columns such as `passenger_count` or `compass_heading` are no longer withheld as secrets: sanda matches the words in a column's name, not letters inside them. - Dropping a view that another view reads is refused before anything changes, so the view stays on the semantic fluid. - The visual builder can draw on tables in `mapping`. - The compute limit hint says what happens at the limit: sanda narrows queries and pauses loading, and the hours used are billed. - Pipeline copy says task and step, as the console does, instead of graph. **Reports portal** - The reports portal launches for the people a workspace builds reports for. They sign in, see the reports published to them, open one as a page and ask cherry about the numbers. They never see the console. See [reports portal](https://docs.sanda-os.com.au/reports/portal). - An owner or admin publishes from a **Publish** pane on the report canvas, to everyone or to chosen people, with a group and an optional featured flag. Later canvas edits stay on the canvas until someone presses **Update**, and the pane says when a report has changed since it was published and how many people have read it. - A reader can be given data rules: a field and the values that reader may see. Rules apply to every portal query, both report blocks and cherry's answers, and cannot be replaced by a report's own filters. A block that cannot reach a rule's field is refused, never shown whole. Portal cherry can search the fluid, describe tables and run queries, and nothing else. An owner or admin can view the portal as any member. - The **Reports** page in the console has two tabs, **Reports** and **Portal**. The **Portal** tab sets the portal's name and welcome text, whether it is open, whether cherry is on, what a reader with no rules sees, the order of published reports and each reader's rules. - The portal home opens with a greeting, your workspace's welcome text and a box for cherry with first questions as chips, with the report library underneath. Asking takes you to a full page with your conversations down the left, and a **Conversations** link in the top bar returns to the list. On a phone the list is a drawer. A published report fills the page and sits centred, with a zoom bar for zooming out, zooming in, returning to the width and fitting the whole report on screen. **Reports** - A kpi's comparison with the previous period shows on shared links and the reports portal, as it does on the canvas. - A metric's **Shown as** and **Unit** decide how its figures read on kpi, bar and line blocks, everywhere a report goes, the email included. - An Excel download keeps hidden sheet columns as hidden columns, so formulas that read them stay live. - A formula is refused, with the reason, past 500 characters, and the **formula** button says so when a sheet already holds 12. - A logo over 146 KB is refused with its size. Changing a theme's preset keeps the logo. - Read-only members are no longer offered Duplicate, new report from this, or save as template. - cherry can change a report that already exists. It can find a report, read its filters and blocks, set report-wide filters, change blocks in place and remove blocks. "Last 7 days" becomes one filter that every block runs with, including blocks added later. Before saving, sanda checks each chart against the fluid with the filters applied. If any block could not run, nothing changes and that block is named. On an open report the change arrives live as one edit you can undo, and any other report is saved as a new version. - The report toolbar sits in its own row above the board instead of floating over blocks, and every control has the same size and border. ## 24 and 25 September **Workspace and security** - Emailed sign-in links now need a click. Opening a link shows a button on the sign-in page, and you are signed in when you press it. This stops mail scanners spending your link and closes a cross-site sign-in trick. - A session ends after 14 days unused. Settings lists your sessions, with **Sign out everywhere else**. - Two-step sign-in works with an authenticator app and comes with recovery codes. An owner can require it for the whole workspace. See [workspace security](https://docs.sanda-os.com.au/workspace/security). - Workspace credentials are one list. Owners and admins can revoke any credential, and an owner can hand the workspace to an admin. - An approval queue for risky agent actions is available and is off by default. - Invitations no longer reveal whether an address already has a workspace. Pages shared by link return rows only, never SQL or database errors. Some actions that any member could take are now for owners and admins: the SQL preview in the view builder, and creating a schedule that emails a private report to any address. - Fixed: a single question could spend about twice the daily AI limit, because the limit was compared in the wrong currency. - Fixed: after signing in, sanda now goes to the page you were heading for, so approving Claude's connection survives the sign-in. - Pages load faster. The first download is about a third of its former size. **Reports** - A delivered report email is drawn like the canvas: a masthead, then each block in its place, in your report's colours. KPI cards show the change on the previous period, tables keep their sheet formatting, and on phones the blocks stack. Each email attaches the report as a PDF and a PNG. If the files would make the message too large, sanda leaves the PNG off first, then the PDF. - Reports can also be delivered to Slack or to a webhook, and you can limit recipients to your own email domains. Delivery schedules keep their local time across daylight saving changes. - Fixed: a delivery that failed to start stayed queued for ever, and blocks with the same id could reuse each other's results. - Fixed in sheets: `=` now works on warehouse numbers, `ROUND` matches Excel, and a CSV export can no longer run a synced value as a formula. - Fixed: filter chips showed the UTC date instead of yours. The hourly schedule now shows its next run. **Semantic fluid** - **Wipe** clears the semantic fluid (tables, datasets, metrics, relationships, suggestions and the map layout) so a workspace can start fresh. It is for owners and admins, behind a typed confirmation, in a danger zone at the bottom of the page. It is a hard delete, so re-adding a table does not bring back its old name. - **Accept all** on suggestions is faster. It accepts six at a time, and a failure puts back only the rows that were refused. **Modelling** - The modelling assistant always drafts. It takes the most reasonable reading of your columns, states its assumptions, and asks any follow-up question alongside the code instead of in place of it. It can switch a new view between a view and a materialized view when you ask. **Connections** - Fixed: once a table was on the semantic fluid, a sync could fail on its next run with an error about dependent objects. sanda now lands each stream so that views on the fluid survive a full refresh and a schema change. **Check status** repairs any view still reading the sync engine's own tables, says what it found, and restarts the run when it removes the cause. A connection page shows which views still read those tables, and a repair that cannot run says why instead of failing silently. - Connector forms show only the fields that need an answer up front. PostgreSQL asks for host, database, username and password, and the update method defaults to a user-defined cursor, because change data capture needs a replication slot and a publication in your database. See [connections](https://docs.sanda-os.com.au/connections). - The stream picker offers the five sync modes and defaults new streams to Full refresh · Overwrite. It shows a cursor and primary key only where the mode uses them, will not save a stream that is missing a required one, and has a bar that sets the mode, cursor or key on every visible stream at once. Streams saved with the older values keep syncing exactly as before. **cherry** - cherry arrives as one data agent across the console. It is a pane docked on every page (⌘ J or the top bar toggle), a full page at **cherry chat**, and a thread that follows you around. In one thread you can ask a question of your data, define a relationship, build a view, create a report and be walked through setting up the workspace. See [cherry](https://docs.sanda-os.com.au/cherry). - Threads can be pinned, renamed, archived, deleted and searched. Chat fills the window, shows your question straight away with a typing indicator, and draws tables and charts in replies. - **Settings · cherry** chooses how cherry acts. In **Ask first**, each change appears as a card with its SQL and a 50 row preview for you to **Confirm** or **Decline**. In **Autonomous**, changes run at once. Dropping a view, retiring a table and deleting a definition always ask. The same section has a model library (up to five models on at a time, with a default), permissions per capability, and memory. - A reply shows all of cherry's words, with the steps between them as one quiet line saying what they were, how many and how long. The Overview opens on cherry. - cherry and MCP can schedule and send reports. Six MCP tools list, create, change, send, pause and delete deliveries, behind a new privileged permission. They are email only, for owners and admins, and follow the workspace's recipient domains and the schedule grid. A delivery given a timezone keeps its local time across daylight saving. - **Ask cherry to learn** reads one source at a time with progress in the step label, runs one relationship pass over the whole map, keeps the full result as a card in the thread, then refreshes the map and opens the suggestions. It draws the joins your learning dataset declares, considers the whole map rather than only the tables it just proposed, profiles map tables it missed, and never proposes a join you rejected. - Several changes of one kind show as one grouped card, with **Confirm all** and **Decline all**. The active thread survives closing the pane and reloading, and the pane header has a labelled **new chat** button. - All cherry usage counts against your daily AI limit. The allowance card lists today's spend by model. **Plans and billing** - sanda now sells three editions: Basic, Standard and Enterprise. They bill storage and compute at the same rates and differ in fee, room and features. MCP and agents and sanda search are not part of Basic, and included support is part of Enterprise. Enterprise is **Talk to us**: its pricing card opens the enquiry form. A trial has every feature and is priced and limited as Standard. Prices and limits are on [editions](https://docs.sanda-os.com.au/billing/editions). - Every feature stays in the navigation, tagged with the edition that has it. A page your edition does not include says so and links to **Settings · Plan & budget** and to the pricing page. Settings shows the fee as it will bill and all three editions, and an owner can ask to move, which files a support ticket. A warehouse above its edition's storage limit pauses loading until it is back under, and a workspace above its search service limit keeps its services but cannot add more. - Faults and billing questions are supported at no charge on every edition. How-to help is included on Enterprise and billed by the hour below it. - Each pricing card lists only what its edition includes. - Fixed: compute readings taken close together were ignored, so an active workspace read "not metered" and its compute was missing from invoices. Every reading now gets a rate. - Fixed: deleting a warehouse and provisioning a new one made the Overview read $0.00 for the month, and dropped the deleted warehouse's month from billing. That usage is kept, and the card is now **Account usage and spend**, which sums every warehouse including deleted ones. - Tax invoices carry a validated ABN and have an A4 print layout. Draft and void invoices are no longer emailed. ## 21 to 23 September **Reports** - You can build a block by hand. A drawn box asks which door first. **pick from the fluid** opens a builder pane on the right with the fluid browser, aggregate and grain pickers and a condition builder, and the drawn box previews the result live. sanda suggests the mark and title from your selection: one measure becomes a KPI, a measure over time becomes a line, otherwise a bar, and no measure a table. **ask cherry** keeps the sentence box, with written analysis, a visual or "cherry decides" as chips. Edit a chart later from its toolbar's **edit**. - Fixed: reports and KPIs could not be built in a workspace whose fluid had tables but no connection, such as uploaded or hand-added tables. It said there was no data behind it. The map now decides whether there is data. - Date filters gain last 3 months and last 6 months, which resolve to whole calendar months ending with the previous month. - You can deliver a report by email on a schedule. Choose the report, the recipients, a cadence and a closing note, then send now, pause or remove it from the Report schedules page. A KPI in the email shows the figure, the change on the previous period and its distance from any target. - A KPI or line block can hold a target. A KPI states the gap in words, and a line draws a dashed rule. The target appears on shared pages and in delivered emails, in neutral terms: a distance and a direction, never a colour. **Pipeline** - **Monitor · Pipeline** puts every stage's timing on one canvas, in four columns: Sync, Transform, Fluid and Deliver. A wire is drawn only where a real link exists, such as a task that runs after a sync or a delivery that reads the fluid. Press a card to open an inspector with the same schedule picker everywhere: sync time and **Sync now** for a connection, schedule, run-after-sync, **Run now** and **Pause** for a task, and cadence and recipients for a delivery. A next 24 hours list shows every upcoming run. The Sync times and Report schedules entries leave the navigation, and tasks now sit inside Modelling. **Semantic fluid** - **Learn from my data** draws the joins your schema declares, then finds more by checking that the values on one side exist on the other. The table picker now infers joins from column names, as MCP imports already did, and no longer stores the first id-looking column as the key of every table, which was wrong for bridge tables. - Fixed: **Learn from my data** returned nothing when a workspace held both the learning dataset and its own tables of the same shape. It now compares only against your active model. **Plans and billing** - A budget is a ceiling. When the month's metered spend reaches the budget you set under **Settings · Plan & budget**, sanda suspends every warehouse in the workspace. Nothing can be queried, loaded, rebuilt or indexed, by the console, by agents over MCP, by the OData feed or by syncs. Your data is kept. The suspension never lifts on its own: an owner or admin resumes it once spend is back under the budget or the budget has been raised or removed. - Prices, invoices, budgets and allowances are in Australian dollars. **Workspace and security** - Everyone who signs in to the console reads and accepts the current privacy policy before anything else. It is shown once per person, and again whenever the policy changes. Machine credentials are not asked. **MCP and agents** - An owner or admin can open a table for intake, and an agent with the intake permission can then append rows to that table, but never create one. **Settings** has an **Intake tables** panel with a close button. Members, viewers included, can grant this permission, because its reach is only the tables the owner has opened. - `define` can now set a table's grain, primary key, time column, description and kind over MCP, and rename it. ## 14 to 17 September **Site** - The public site is redesigned around the semantic fluid, sanda search and MCP. The homepage leads with the business problems they solve, and a new product page walks through sanda with interactive samples that are labelled as illustrations. Pricing, signup, sign-in, terms, privacy and the not-found page share the new look. Signup checks each step as you go and works with the keyboard. **Console** - The console gets the same design, with grouped navigation, a drawer on phones and section navigation in Settings. Semantic fluid, sanda search and Agents sit together under Intelligence. The report toolbar works on narrow screens. - The **Search your data** link in the top bar is gone. sanda search stays under Intelligence. **sanda search** - sanda search is rebuilt around finding records. Pick an index, ask in the query box, add typed filters, read results as cards and open a full record. Index settings and build logs move to their own tabs, which keep your current query. Creating an index needs a current cost estimate, and changing a field that affects price discards it. **Semantic fluid** - Fixed: **Learn from my data** could propose nothing and reject all of its own suggestions as "must be fully qualified". Its results also no longer stretch the page header. - Fixed: one column could get two dimensions, such as `customer_country` beside `customer country`. Names now treat underscores like spaces, a second dimension on a column that already has one is refused with the existing name, and existing duplicates are merged with the old names kept as synonyms. **Connections** - Fixed: a connection set to sync at 7:00 AM ran every morning while sanda kept saying "Last sync 11h ago". sanda now sees runs the sync engine starts on its own schedule. - Fixed: a run could appear two or three times in a connection's Timeline under one job number, and a failed run showed no reason. Each run appears once, and a failure shows its reason, or says plainly that none was given. **Workspace and security** - From **Settings · Team**, owners and admins invite people as an admin or a read-only member, change roles and remove people. An invitation opens a join page in the workspace that sent it. One address belongs to one workspace, nobody edits their own role, an admin cannot change an owner, and the last owner stays. See [workspace](https://docs.sanda-os.com.au/workspace). - Owners can close a workspace by typing its short name under **Settings · Close or delete**. The workspace goes read-only, for the console and for agents over MCP, and a banner shows the date it will be deleted. The owner can reopen it until then. The closed period is 30 days at this point and is extended on 29 September. **Warehouse** - The **SQL shell** lets owners and admins run one SQL statement at a time against a warehouse. It is read-only by default, enforced by a read-only transaction. In read-write mode each statement commits at once, and the page says so. Results are capped, statements stop at your account's time limit, every statement is recorded, and the page opens on a warning you must acknowledge. **Reports** - A table block is a sheet. You can add formula columns in Excel's own language (`[net revenue]` is this row, and the same reference inside `SUM` or `AVERAGE` is the whole column), set a format per column, and add a totals row, a sort and hidden columns. A sheet stores instructions, never rows, so a margin column defined in June is a margin column in July. Exports to Excel keep the formulas live. Cells cannot be overtyped, so every figure stays traceable to the semantic fluid. - A report can be private, visible to its author, owners and admins and the people you name, with edit rights if you choose. It can also be shared by link with people who have no sanda account. A link can expire, can be revoked and counts its opens. It opens a page rather than a canvas, and a visitor can sort and export a table but cannot ask for anything the author did not put on the page. See [reports](https://docs.sanda-os.com.au/reports). **OData** - The OData feed lets Excel, Power BI and Power Query Online connect to the semantic fluid without installing anything. Columns arrive under your names, metrics arrive already calculated, and you refresh instead of exporting. Each refresh is a live warehouse read, billed like any other question. See [OData](https://docs.sanda-os.com.au/odata). - The credential is now an integration token, and the Settings panel is **Integrations** rather than Agents, because Excel can hold one too. Tokens you already hold keep working. The panel lists the Excel steps: choose Basic, leave the user name empty and paste the token as the password. **MCP and agents** - Fixed: `define`, `set_table_kind` and `retire_tables` could act on the wrong copy of a table when the learning dataset was present, ending in "can't tell which table sales is measured on". They now resolve names through your active model, so what `describe_table` shows is what they can write to. Answers come back sorted, and table and column names match without regard to case. - `execute_sql` lets an agent run a SQL statement in your warehouse under a new privileged permission. It reads landed tables and your own modelling layer and writes only to your own modelling layer, with the same row cap, time limit and audit record as the SQL shell, plus the reason the agent gave. ## 9 to 13 September **sanda search** - sanda search launches. Point a service at one table on the semantic map, choose a key column and the text columns worth searching, and sanda keeps an index that finds rows by meaning and by exact words. A query returns the keys of matching rows, and you can use those keys as a filter on a metric. The index refreshes after the sync that feeds it. See [sanda search](https://docs.sanda-os.com.au/search). - Search is off until an owner accepts a data-processing permission, because the text of the columns you index is processed outside Australia to create search vectors. Withdrawing the permission pauses your services, cancels queued builds and drops queries to exact words. - Indexing is metered like other AI use. sanda shows an estimate before you create a service, and pauses indexing before your daily AI limit is spent so you can still ask questions. - Fixed: creating, estimating and editing a search service failed with an internal error. - A queued build shows when it will start, and **Run now** indexes the next slice straight away. - Results say why a row is in the answer: meaning, words, or meaning + words. This replaces a number that looked like a confidence score but was only a rank. When no row contains your words, the page says the results are the nearest by meaning alone. - The build panel says what a build is doing. It reads "Indexing now" only while a slice runs, otherwise "Part way through, not running" or "Waiting to start", with progress such as "640 of ~3,000". A build that runs out of time names the step the warehouse was too slow on. - Large tables finish. A table too big for one pass used to cycle between queued and running without end. sanda now keeps the embeddings it has already computed and carries on from where it stopped. Requests run in parallel and in larger batches, so big tables index several times faster. - Fixed: a search build against an unreachable warehouse failed with an unhelpful message. Connecting to a warehouse now has a time limit, and the build names what failed. **Modelling** - Schedules, tasks and search builds run on a grid so that sanda's scheduler can rest between ticks. The grid is 30 minutes from 9 September and 15 minutes from 13 September, and it applies to connection sync schedules too. A time off the grid is not accepted, the time picker only offers legal times, and queued work says when it will start. **Warehouse** - The learning dataset is rebuilt every night over a rolling ten-year window that ends today, so its newest orders are never months old. **MCP and agents** - MCP tools let an agent create, query and manage search indexes and start a build. See [MCP](https://docs.sanda-os.com.au/mcp). - **Intelligence · Agents** shows whether your MCP setup is working. **Harness** lists the four things that must be true before an agent can do anything, each linking to the page that fixes it. **Agents** lists each token and connected app with what it may do and when it was last used. **Activity** shows sessions, the call feed, refusal rates per tool, and what sanda refused outright. **Workspace and security** - Signup asks you to accept the terms and privacy policy with an unticked box, and signup is refused without it. sanda records which dated version you were shown, and when. **Plans and billing** - Fixed: the pricing page could quote a fee that differed from what invoices use. The page now shows the price sanda bills. **Site** - A new **Why sanda** page explains, first in plain terms and then in technical detail, how sanda compares with a conventional cloud data warehouse. A chapter rail keeps your place on a wide screen. ## 7 and 8 September **Plans and billing** - Storage and compute prices changed. Compute bills at the warehouse's minimum size for every minute it is awake, plus the work it does on top, instead of at its ceiling. A warehouse that idles no longer pays as though it were busy. Current prices are on [editions](https://docs.sanda-os.com.au/billing/editions). - The estimator on the pricing page prices what the meter bills: the storage you hold and the compute hours your team's questions are expected to use. It shows one figure instead of a range, and the number of people asking questions drives the compute estimate. **Warehouse** - **Data · Explorer** opens the warehouse like a database console. One tree lists each warehouse, its schemas, tables and views with row estimates. Choose a table or view to see its columns, a preview of its first 50 rows and, for a view, its definition. The explorer is read-only and open to every member. - **Optimise** reads what is slowing a warehouse down or costing it storage, and offers fixes you choose: indexes your relationships would use, unused or duplicate indexes, indexes left broken by a cancelled build, and vacuum and analyse. sanda shows each statement before it runs, and reading the advice uses none of your AI limit. It never runs a full vacuum by default, because that locks the table while it rewrites. Partitioning is advice only. See [warehouse](https://docs.sanda-os.com.au/warehouse). **Modelling** - A new **Modelling** section under Data builds your own layer on top of landed data: views, materialized views and procedures, with schedules that rebuild and run them. Draw a view in the drag-and-drop builder, using your warehouse's tables and joins suggested from the relationships you have drawn, or write the SQL. Each object shows what it affects: the fluid table it is published as, the objects that read it and the schedules that rebuild it. New views publish to the semantic fluid by default. See [modelling](https://docs.sanda-os.com.au/modelling). - **Save as view** in Ask the fluid turns your current selection into a view or a materialized view and publishes it. - Tables you have modelled lead in the semantic fluid, and chat is told to prefer them where they can answer the question. - Every rebuild is measured and billed as compute. A scheduled run that fails emails owners, at most once a day for each task, and alert settings have a switch for it. - Fixed: the view builder listed the tables of the wrong warehouse, or none, and said "Nothing has landed in this warehouse yet" when tables were there. It lists the tables of the warehouse you select, and a warehouse selector always shows. **MCP and agents** - Eleven MCP tools let an agent create and refresh views, create and call procedures, and create, run and pause schedules, behind a new permission. Three more list a warehouse's sources and read and apply its optimisation advice. ## 4 to 6 September **MCP and agents** - Agents can hold a token. Under Settings you mint a token that lets Claude Code, the Claude API's MCP connector or your own agents work with the semantic fluid over [MCP](https://docs.sanda-os.com.au/mcp). A token is shown once, and it stops working when the person who made it leaves the workspace. - sanda is now its own sign-in server for MCP, so a person can add it as a custom connector in Claude and approve access on a consent screen. Connected apps are listed under Settings, each with a **Disconnect** button. - Fixed: adding sanda to Claude failed with "Couldn't register with sanda's sign-in service". - Fixed: Claude's consent screen offered a single permission. It now lists every permission, and the ones that reach beyond asking questions start unticked. - Twelve more MCP tools let an agent change the semantic fluid (import tables, define metrics and relationships, review suggestions), see the workspace (connections, sync status, warehouses, never a credential or connection string) and start a sync. Each sits behind its own permission, and a tool you have not granted is not listed. Definitions an agent writes go live at once, so only an owner or admin can grant that permission. - `load_rows` lets an agent land its own table of rows in your warehouse, by replacing or appending, with types inferred or declared, and optionally put it on the semantic map in the same call. It sits behind its own privileged permission and respects your storage limit. **Semantic fluid** - An **arrange** button lays every table out by how they join, with the most-joined table in the middle and dimensions at the edge. The fact or dimension badge on a card is now a one-click switch, and dragging near the edge of the map pans it. - A metric you describe to sanda can be saved as a metric straight away, instead of only as a proposal you then review. - Fixed: dragging one column onto another could draw extra relationships when several columns shared a key. sanda now warns when the key you type already sits on other columns, and before an unlink would take other relationships with it. - Fixed: a table you removed from the map could not be added back. Tables in the picker are compared against your active model only, and groups collapse and show how many of their tables are on the map. **Workspace and security** - The guard in front of free-form SQL now refuses more ways of hiding a second statement, and every response carries browser security headers, so another site cannot frame the console. - Signup no longer reveals whether an email address already has an account. **Plans and billing** - Usage is the bill. Storage is billed on the gigabyte-months you hold and compute on the hours you run. Nothing is bought in advance, so the storage plan ladder is gone from the pricing page and from Settings. Editions set limits (warehouses, storage and compute per warehouse, and features), not a different price for the same storage. Current prices are on [editions](https://docs.sanda-os.com.au/billing/editions). - Compute is billed by the compute hour, which is one vCPU running queries for an hour. The Overview shows a month of usage as three charts that share a cursor (storage, compute and accrued cost) in place of two meters. - Fixed: compute was not being metered on any warehouse, so the Overview read "not metered" and compute did not appear on invoices. Compute is now metered per warehouse. - The sizing calculator becomes the pricing page. It has an enquiry form for Enterprise and for sizing questions. Fixed: **Email me this estimate** never sent an email. It does now. ## 1 to 3 September **Reports** - Reports become canvases. A report is now a pan-and-zoom page of blocks: KPI tiles, bar and line charts, tables and written analysis. Draw a box on empty space and describe what you want in it, or ask cherry for a whole pack ("Build a June board pack: P&L vs budget, cash, top debtors"). Blocks drag by their header, resize from a corner, snap to a grid, and can be redrawn as a KPI, bar, line or table. A block stores the question rather than the rows, so next month's report shows next month's numbers. See [reports](https://docs.sanda-os.com.au/reports). - Blocks appear as they are placed, while cherry is still working, and you can draw several boxes at once. When a request could mean two things, cherry asks with buttons instead of guessing. - A filters pane applies conditions to every block on a report. Add one by writing a sentence or with the condition builder. Named periods such as last 12 months stay unresolved, so they move with the calendar. - A selected chart gets a grain picker (day, week, month, quarter or year). Bar charts show as many bars as the block's height allows and say what they left out. - Fixed: a chart of sales by month showed one row per day. - The canvas is drawn as a white page on a darker desk, and the view is held so the page can never scroll out of sight. - **Report colours** in **Settings · Appearance** sets the accent and status colours every report uses, from eight presets or your own. A single report can override them from its toolbar. - You can delete a report from the list or the canvas, duplicate it, and save it as a template. Templates sit in their own list with **new report from this**. - Recent versions of a report are kept, and you can restore one from a history menu. Undo and redo work as you expect (⌘ Z). - Reports print to PDF with **export pdf** in the **more** menu. A header block carries your logo, the report's name and a subtitle. You can leave comments on any block, refresh one block or all of them, and click a bar to filter the whole report by that category. - A generated board pack lands in tidy bands across the page, and new blocks come in sizes that tile it: four KPI tiles fill a row. **Connections** - Each stream in a connection's list has its own live status (queued, syncing, in or failed) and the rows it has read so far. The columns stay lined up however long a table name is. - Fixed: for a short time on 3 September sanda could not reach its sync engine, so syncs could not start. Syncing is restored. **Semantic fluid** - Fixed: choosing a metric and a dimension that are joined through a table you did not pick, such as revenue by an employee's job title by way of orders, failed with "no stated relationship". sanda now finds the shortest route through the relationships you have drawn and joins the tables along it. - **Learn from my data** says why it dropped a proposal, and shows the model's own notes and unanswered questions, such as "which currency are these amounts in". It runs one pass per connection, so each source gets the whole table budget. **Workspace and security** - Password sign-in is removed, and you sign in with an emailed link. When sanda cannot send email, the sign-in page says so instead of promising an inbox. - Fixed: Settings showed a blank page in some workspaces. **Site** - Every page on the site has its own title and description, the site publishes a sitemap, and an address that does not exist now returns a proper "not found" page. --- # August 2026 > The first month of sanda: sign-in and the console, warehouses and connections, the semantic fluid as a map, and chat over your own definitions. ## 13 August **cherry** - Fixed: after the 8 August change, pressing send in chat could appear to do nothing. A reply now has a time limit and says so when it runs out. ## 7 and 8 August **Semantic fluid** - sanda's learning dataset gives every workspace a sample business to explore: millions of rows of trading history with the tables, relationships and metrics already modelled. You can ask questions of it before you connect anything of your own. - **Ask the fluid** builds a query from the semantic catalogue. Pick metrics, dimensions, filters and columns, and sanda runs the query on every pick, shows the SQL it composed, and returns rows as a table or a chart. It opens as a full card, with the catalogue on the left and results on the right. A filters card holds named filters. - Scrolling over the map no longer zooms it. Use the zoom buttons or fit to content. **cherry** - Chat now works as a loop. Instead of being handed the whole semantic fluid and asked to write SQL, it searches the fluid, looks up table definitions and builds queries from your metrics and dimensions through the same planner as Ask the fluid. It writes custom SQL, with an explanation, only when it has to. It remembers the conversation, so a follow-up can refer to an earlier answer. Row limits and the read-only rule are unchanged. ## 6 August **Connections** - Choosing streams is now part of setup. The wizard gains a **Streams** step, and nothing syncs until you have chosen. A connection with credentials but no streams reads "Needs streams". - The schedule picker offers Manual, Hourly, Daily and Weekly cadences with day buttons, a time and a timezone. It appears in the wizard and on a connection's **Settings** tab, so a schedule can be changed after the connection exists. - A connection page refreshes on its own while a sync runs, so you no longer press **Check status** to see it finish. **Warehouse** - The Overview gains a **Warehouse size** panel that shows storage and compute against their limits. Warehouses are measured whenever you open a page that shows capacity. **Semantic fluid** - The semantic fluid becomes a map. Tables are cards you drag around a canvas. Datasets group tables in colour, and glue joins two datasets, written by hand or matched by sanda. A Map and List toggle keeps the list view for detailed edits. - **Add tables** lists the tables in your warehouse and imports the ones you tick, grouped by the connector they came from. Each table is a fact or a dimension, so sanda does not sum a price list. The badge on a card switches it. - A table enters the fluid as the columns you choose. Each column carries the words your team uses for it and the categories you group by. You draw a relationship by dragging one column name onto another. - Sync bookkeeping columns stay out of the fluid. Removing a table retires it rather than deleting it, so its history is kept. - Relationships show their cardinality, one to one or one to many. Suggestions open in a review queue you can drive from the keyboard: ↑ and ↓ to move, Enter to accept, Delete to reject. - Metrics can be edited. Renaming a metric updates every metric that referred to it, and the form lists which ones before you save. Every card on the map has its own search. **Plans and billing** - Every AI call is recorded. What you ask counts against a daily allowance for the workspace, and the workspace picks which model answers. **Workspace and security** - Only owners and admins can add, change, delete or sync a connection. Members can still see them. - The audit log can no longer be changed or deleted. - Before sanda serves a workspace's data, it now checks that isolation between workspaces is in force, and it refuses to serve if it is not. - Fixed: sign-in emails were not being sent. ## 5 August **Connections** - A new Connections page has a searchable list, a three-step wizard (define the source, connect, configure) and a full page for each connection with its status, timeline and settings. Each connector has a setup guide beside its form. - You can upload a CSV straight into a warehouse. Drag in a file, name the table, and choose whether to append or replace. sanda detects the delimiter and infers each column's type from every row. - You can choose which tables a connection syncs. For incremental streams you pick the cursor and the primary key. Changes reach the running connection at once. - Setup forms for PostgreSQL and other connectors that offer several options no longer fail with a validation error. Forms open on the simplest option, check the fields of the option you have open, and fold SSL, update method and tunnel settings under **Advanced**. PostgreSQL goes from eleven questions to four. - sanda checks a database host against public DNS before it tries to connect, and tells you when a host can never work from the internet, such as `localhost` or a private address. - Connection errors say what to do. A hostname that cannot be resolved, a refused connection and a rejected password each get their own plain-language explanation. - Deleting a connection now removes it from the sync engine too, so nothing keeps syncing or holding your credentials. - Fixed: a connection created without choosing streams failed every sync. Fixed: syncing a source that already carries another loader's bookkeeping columns failed with a duplicate column error. **Warehouse** - A workspace can have more than one warehouse, up to the limit of its edition. Each connection lands in the warehouse you choose. You can delete a warehouse, and retry one whose provisioning failed. **Plans and billing** - Warehouse pricing is published on the site and in the console, built on storage and compute. Prices changed during the month, so read current prices on [editions](https://docs.sanda-os.com.au/billing/editions). **Site** - Sydney is the default region, and the only one available. The console moves to its own address, separate from the public site. ## 3 and 4 August **Site** - The sanda site opens with a homepage and free trial signup. The trial runs for 7 days. - A sizing calculator on the site estimates what a warehouse costs for your data size and team. **Workspace and security** - You sign in with a one-time link sent to your email. A link works once and expires in 30 minutes. - You can also set a password from **Settings** and sign in with it. Password sign-in is removed again on 2 September (see [September 2026](https://docs.sanda-os.com.au/releases/2026-09)). - Fixed: the sign-in link opened the homepage and never signed you in. - Fixed: mail programs and link scanners that open a link before you do no longer use it up. Opening a link you have already used, while you are signed in, takes you to the console instead of an "expired" message. - Console errors now say which request failed and with what status, in place of "Something went wrong." **Warehouse** - **Provision warehouse** in **Data · Warehouse** creates your own PostgreSQL database in Sydney, kept apart from every other workspace. See [warehouse](https://docs.sanda-os.com.au/warehouse). **Semantic fluid** - The semantic fluid arrives: tables, dimensions, metrics and mappings that give your data the meaning your business uses. See [semantic fluid](https://docs.sanda-os.com.au/semantic-fluid). - **Learn from my data** profiles your warehouse and proposes definitions for you to review. Nothing goes live until you accept it. **Connections** - The Connections page builds each connector's setup form from the connector catalogue, so any listed connector can be configured without a hand-built form.