# AI settings (/admin/ai-settings) How system administrators enable AI Gateway models, pick default models per use case, edit per-function instructions, and set the agent policy and runtime defaults under /admin/ai. ## Overview [#overview] AI settings is the platform-level AI configuration at `/admin/ai`, under **Admin > Operations > AI**. It has three tabs: | Tab | Route | Purpose | | ------------ | ------------------------ | ----------------------------------------------------------------- | | Models | `/admin/ai` | Enable models and choose the default model per use case. | | Instructions | `/admin/ai/instructions` | Edit system instructions and generation settings per AI function. | | Policy | `/admin/ai/policy` | Set the agent execution policy and runtime defaults. | These settings apply to every organization. Organization-level AI preferences and user-facing chat are described in [Chat](/product/chat). ## Key concepts [#key-concepts] * **Model.** A model from the Vercel AI Gateway catalog, identified as `provider/model`. Only enabled models can be used. * **Use case.** A product surface with its own default model: Chat, Embeddings, Product recognition, Suggestions, Transcribe and Web search. * **AI function.** One prompt-driven capability, such as `chat.ask`, `chat.agent` or `contextual.customer.summarize`. Each function has English system instructions and generation settings. * **Customized or Default.** A function with saved overrides shows **Customized**; one that uses the shipped values shows **Default**. ## Enable models [#enable-models] The Models tab lists the AI Gateway catalog grouped by provider. Each provider row shows how many of its models are enabled, for example "4 of 84 enabled". Expand a provider to see its models with context window, release date, capability tags (shown in the Permissions column), and input and output cost. Toggle **Enable model** on a row to enable or disable it. Use **Search providers or models…** and **Filters** (Provider, Permissions, Status) to narrow the list. ## Choose default models [#choose-default-models] The **Default models** card has one selector per use case. Pick an enabled model for each; you must enable at least one model before you can set a default. The Chat default also powers the public docs assistant on the documentation site. The docs app reads the enabled Chat default and refreshes it every five minutes. When no enabled Chat default exists, the docs assistant falls back to `google/gemini-3.7-flash`. Embedding defaults must produce 1536-dimensional vectors, because knowledge chunks are stored in that size. See [Knowledge](/admin/knowledge). ## Edit function instructions [#edit-function-instructions] The Instructions tab lists every AI function with its name, key, category, status and last update. Categories include Chat, Contextual, Suggestions and Recognition. Filter by category or status, or search by name. Open a function at `/admin/ai/instructions/{functionKey}`, for example `/admin/ai/instructions/chat.ask`. Depending on the function, you can edit: | Field | Purpose | | ------------------- | --------------------------------------------------------------- | | System instructions | The English system prompt for this function. | | Input template | The constructed input text sent with each request. | | Max steps | The maximum number of tool-calling steps. | | Max output tokens | The cap on generated tokens. | | Timeout (ms) | How long a call may run. | | Temperature | Optional sampling temperature. Empty uses the provider default. | Select **Save** to store an override, or **Reset to default** to remove it. Instructions stay in English. The output language and date and time formatting are applied at runtime from the signed-in user's preferences. ## Set the agent policy [#set-the-agent-policy] The **Agent policy** card on the Policy tab controls what Agent chats may do: * **Execution ceiling**: Agent, Review or Read. Agent allows mutations. Review and Read refuse mutations even after the user selects Approve. The default is Agent. * **Approval risk threshold**: Low, Medium, High or Critical. Tools at this risk level or higher need approval. The default is High. Access changes, admin actions and workflow starts always need approval. Ordinary create, update and delete tools run without approval unless they are high risk or always gated. ## Set runtime defaults [#set-runtime-defaults] The **Runtime defaults** card sets platform defaults that users can still override per turn: | Setting | Default | | ------------------------------------------ | -------------- | | Default composer mode (Ask, Plan or Agent) | Agent | | Default reasoning | high | | Default speed (Normal or Fast) | Normal | | Default context window | 200,000 tokens | | Suggestion count | 3 | | Suggestion min words and max words | 8 and 22 | | Knowledge retrieve limit and preload limit | 8 and 6 | | Memory always-on limit and semantic limit | 4 and 6 | | Memory min confidence | 0.85 | The agent policy and runtime defaults are stored as the `ai_agent_policy` and `ai_runtime_defaults` keys in [platform settings](/admin/platform-settings). ## Permissions [#permissions] | Key | Effect | | ------------------------- | ------------------------------------------------------------------------------- | | `system.admin.access` | Opens the AI tabs and is checked by the model, default and instruction actions. | | `system.ai-models.manage` | Required by the database to write the agent policy and runtime defaults. | ## Related pages [#related-pages] * [Knowledge](/admin/knowledge) * [Platform settings](/admin/platform-settings) * [Chat](/product/chat) * [AI agents](/developers/ai-agents) * [MCP server](/developers/mcp) # Analytics (/admin/analytics) The cross-organization business analytics page at /admin/analytics, with platform KPIs, deterministic forecasts, rule-based insights and source data exports for system administrators. ## Overview [#overview] Admin Analytics at `/admin/analytics` shows business analytics across every organization on the platform. Open it from **Admin > Operations > Analytics**. It follows the same rules as tenant analytics at `/{orgSlug}/insights/analytics`, but it reads platform data such as subscriptions and usage instead of one organization's sales. See [Insights](/product/insights) for the tenant version. The page has two tabs: | Tab | Purpose | | -------------- | -------------------------------------------------------------- | | Overview | KPIs, a trend chart, composition, biggest movers and insights. | | Data & exports | The source evidence behind the numbers, with export. | ## Key concepts [#key-concepts] * **Internal data only.** Analytics reads CentraKit's own subscription and usage records. It does not pull data from external analytics tools. * **Separate currencies.** Amounts in different currencies are never added together. * **Deterministic forecasts.** Forecasts are computed from history with fixed rules, not from AI. A forecast needs at least six history points; with fewer, the chart shows "Forecast unavailable" and how many points exist. * **Rule-based insights.** Insights are triggered by fixed rules on the selected range. When no rule fires, the page says so. ## Choose a range [#choose-a-range] Both tabs share a filter bar: * **Date range**: Last 30 days, Last 90 days, Last 365 days or Custom. The selected dates show next to the selector. * **Comparison**: Previous period, Previous year or No comparison. The range uses the signed-in user's time zone. ## Read the overview [#read-the-overview] The Overview tab shows these KPIs, each compared with the comparison period when enough data exists: | KPI | Meaning | | ------------------------ | ------------------------------------------------------ | | MRR | Monthly recurring revenue from platform subscriptions. | | Paid organization growth | The number of organizations on a paid plan. | | Plan mix | How organizations are spread across plans. | | Usage | Metered platform usage across organizations. | Below the KPIs are the trend chart with observed periods and any forecast, a **Composition** panel that ranks organizations and plans, **Biggest movers** between the two periods, and **Insights**. ## Export source data [#export-source-data] The Data & exports tab lists the source evidence: Date, Item, Metric, Source and Value. Rows include subscription MRR entries and usage meters such as AI tokens, API requests, inbox messages and file storage, with a usage cost proxy. Filter the table with **Filters**. Select **Export** to download the rows as CSV or Excel (XLSX). Export needs `system.analytics.export`; without it, the button is not available. ## Permissions [#permissions] | Key | Effect | | ------------------------- | -------------------------------------------------------------- | | `system.admin.access` | Opens the admin area. | | `system.analytics.view` | Opens Admin Analytics. Without it, the page returns not found. | | `system.analytics.export` | Exports the source data. | System Admin has both analytics keys. System Support has neither. ## Related pages [#related-pages] * [Operations dashboard](/admin/dashboard) * [Billing](/admin/billing) * [Plans](/admin/plans) * [Insights](/product/insights) # Audit log (/admin/audit-log) How system administrators search platform-wide audit events, inspect event details, reveal restricted fields and export events as CSV under /admin/audit-log. ## Overview [#overview] The admin audit log at `/admin/audit-log` shows audit events from every organization and from the platform itself. Open it from **Admin > Platform > Audit log**. | Route | Purpose | | ---------------------------- | ----------------------------------- | | `/admin/audit-log` | Search and filter all audit events. | | `/admin/audit-log/{eventId}` | One event with its full details. | Organization users with `organization.audit-log.view` see their own organization's events at `/{orgSlug}/audit-log`. See [Audit log](/product/audit-log). How events are captured and stored is described in [Audit logs architecture](/architecture/audit-logs). ## Key concepts [#key-concepts] Each event records: * **Event**: a readable name such as "Updated Usage Periods" and a key such as `organization-usage-periods.updated`. * **Actor**: who caused the event. Actor kinds are user, platform admin, system, API key, integration, workflow and webhook. * **Target**: the affected record and its type. * **Organization**: the tenant the event belongs to. Platform events have no organization. * **Category**: authentication, users, permissions, billing, data, settings, integrations, security or system. * **Source**: where the event came from: the SaaS app, the API, MCP, AI, a workflow, a webhook, the database or the system. * **Outcome**: success, failure, denied or attempted. Denied attempts are logged too. For example, a user without `system.users.manage` who tries to invite a user produces a denied event. ## Search events [#search-events] The list shows Timestamp, Event, Actor, Target, Organization, Category and Source / outcome, newest first. Search with **Search events and targets…** and narrow the list with **Filters**: * Event types, Categories, Actor kinds, Outcomes and Sources. * Actor ID, Target type, Target ID, Correlation ID and Organization ID. * Occurred on, for a date range. The row menu offers **View details**, **Open affected resource**, **View related events**, **Filter by actor**, **Filter by event type**, **Copy event ID** and **Copy request ID**. ## Inspect an event [#inspect-an-event] The detail page at `/admin/audit-log/{eventId}` shows: * The event name, key and outcome. * Event ID, actor, organization, local time and UTC time, source, category, target and target type. * **Structured changes**, such as the list of changed columns. Redacted columns are never shown. * **Request context**: request ID and correlation ID. * **Related events**, when other events are linked to this one. ## Reveal restricted details [#reveal-restricted-details] Some fields, such as IP address, session ID and user agent, are hidden by default. Select **Reveal restricted details** to show them. Revealing requires `system.audit-log.reveal`, and the reveal itself is recorded as an audit event. ## Export events [#export-events] 1. Filter the list to the events you want. The export uses the current filters, and the **Occurred on** range must be valid and no longer than 90 days. 2. Open **Exports** from the toolbar. 3. Optionally select **Include restricted details**. This needs `system.audit-log.reveal`. 4. Select **Create export**. The export runs as a background workflow. Its status moves through Pending, Running and Completed, or ends as Failed. Completed exports later show Expired. Select **Refresh** to update the list, then **Download export** to get a CSV file. The download link is short-lived, and a completed export expires after 24 hours. ## Permissions [#permissions] | Key | Effect | | ------------------------- | ------------------------------------------------------------------ | | `system.admin.access` | Opens the admin area. | | `system.audit-log.view` | Opens the admin audit log. Without it, the page returns not found. | | `system.audit-log.export` | Creates and downloads exports. | | `system.audit-log.reveal` | Reveals restricted details and includes them in exports. | System Admin has all three audit log keys. System Support has `system.audit-log.view` only. ## Related pages [#related-pages] * [Audit log](/product/audit-log) * [Audit logs architecture](/architecture/audit-logs) * [Users](/admin/users) * [Roles and permissions](/admin/roles-and-permissions) * [Plans](/admin/plans) for retention per plan # Billing (/admin/billing) How system administrators review and change organization subscriptions, manage subscription invoices, download invoice PDFs and refresh billing from Stripe under /admin/subscriptions and /admin/invoices. ## Overview [#overview] Admin billing covers the subscriptions organizations pay for the platform itself. It lives under **Admin > Finance**: | Route | Purpose | | -------------------------------- | -------------------------------------------------------------- | | `/admin/subscriptions` | Every organization with its plan, status and open invoices. | | `/admin/subscriptions/{orgSlug}` | One organization's subscription, invoices and webhook history. | | `/admin/invoices` | Every subscription invoice across organizations. | | `/admin/invoices/{invoiceId}` | One subscription invoice with amounts and lifecycle. | | `/admin/payouts` | Not yet available. The page shows "Payouts coming soon". | These are platform subscription invoices from Stripe, not the invoices organizations send to their own customers. Tenant invoicing is described in [Invoices](/product/invoices). The Stripe application fee taken on customer invoice payments is set in [Platform settings](/admin/platform-settings). ## Key concepts [#key-concepts] ### Subscription statuses [#subscription-statuses] Active, Trialing, Past due, Unpaid, Paused, Incomplete, Incomplete expired, Canceled and No subscription. ### Invoice statuses [#invoice-statuses] Draft, Open, Paid, Uncollectible, Void, Upcoming and Deleted. ### Source of truth [#source-of-truth] Stripe owns subscriptions, invoices and payment methods. CentraKit keeps a local copy that Stripe webhooks update. **Refresh from Stripe** re-reads the latest state when the local copy looks stale. ## Review subscriptions [#review-subscriptions] The Subscriptions page starts with summary cards for organizations, active subscriptions, monthly recurring revenue and open invoices. The table shows Organization, Plan, Status, Monthly / MRR, Period end and Open invoices. Search with **Search organizations…** and filter with **Filters**. Open a row to see the organization's billing detail: * **Plan**, **Stripe customer** and **Billing email**. * **Operations**: **Refresh from Stripe** reads the latest subscription, invoices and payment methods. * **Plan and billing cycle**: the current plan, credit package and interval. * **Current period**, **Default payment method** and **Open invoices**. * **Invoice history** for this organization. * **Recent webhook processing**: recent Stripe events with the status Processed, Pending or Failed. ## Change a subscription [#change-a-subscription] 1. Open the organization at `/admin/subscriptions/{orgSlug}`. 2. In **Plan and billing cycle**, pick the plan, credit package and billing interval (Monthly or Yearly). 3. Select **Change subscription** and confirm. Changes use Stripe's standard proration and may immediately create an adjustment invoice. ## Schedule or undo a cancellation [#schedule-or-undo-a-cancellation] Select **Cancel at period end** and confirm to schedule a cancellation at the end of the current billing period. While a cancellation is scheduled, select **Undo cancellation** to keep the subscription active. ## Manage subscription invoices [#manage-subscription-invoices] The Invoices page lists Invoice, Organization, Plan, Status, Due, Paid, Remaining, Issued and Due date. Search with **Search invoices or organizations…**. The row menu offers **Open invoice**, **View hosted invoice** and **Download PDF**. The invoice detail page shows identifiers (local invoice ID, Stripe invoice ID, Stripe subscription ID and organization ID), amounts, lifecycle dates and the subscription context. For an invoice with the status Open, you can: * **Void invoice**: cancel the invoice in Stripe. * **Mark uncollectible**: record that the invoice will not be paid. Both actions ask for confirmation. ## Download an invoice PDF [#download-an-invoice-pdf] **Download PDF** calls the API route below, which checks `system.billing.view` and then redirects to the Stripe invoice PDF. ```text GET /api/v1/admin/subscriptions/{orgSlug}/invoices/{invoiceId}/pdf ``` The route returns an RFC 9457 problem response with the code `BILLING_FORBIDDEN` (403), `BILLING_VALIDATION` (400) or `BILLING_NOT_FOUND` (404) when the request cannot be served. See [Errors](/developers/errors). ## Permissions [#permissions] | Key | Effect | | ----------------------- | ------------------------------------------------------------------------------------------------------ | | `system.admin.access` | Opens the admin area. | | `system.billing.view` | Reads subscriptions and invoices, downloads PDFs and refreshes from Stripe. | | `system.billing.manage` | Changes subscriptions, schedules or undoes cancellations, voids invoices and marks them uncollectible. | System Admin has both billing keys. System Support has `system.billing.view` only. ## Related pages [#related-pages] * [Plans](/admin/plans) * [Platform settings](/admin/platform-settings) * [Operations dashboard](/admin/dashboard) * [Billing and usage](/product/billing-and-usage) * [Stripe billing operations](/operations/stripe-billing) # Operations dashboard (/admin/dashboard) The system admin landing page at /admin that summarizes tenants, subscriptions, revenue, failed payments, usage anomalies and operational failures across the platform. ## Overview [#overview] The operations dashboard is the landing page of the admin area at `/admin`. It gives system administrators a live, cross-organization view of platform health and a queue of items that may need action. Open it from **Admin > Dashboard** in the sidebar. The admin area is only available to users whose system role grants `system.admin.access`; other users are redirected to the app home. Locally, the seeded `test@example.com` user holds the System Admin role. The dashboard reads existing operational data at request time and caches it for a few minutes. It does not store incidents, keep historical analytics or send notifications. For trends over time, use [Analytics](/admin/analytics). ## Key concepts [#key-concepts] ### Metric cards [#metric-cards] | Card | What it counts | | -------------------- | -------------------------------------------------------------------------------------------- | | Active organizations | Enabled platform tenants. Disabled organizations are excluded. | | Active subscriptions | Subscriptions with the status active or past due. | | MRR | Catalog recurring revenue from active and past due subscriptions. | | Failed payments | Subscription invoices that are overdue or uncollectible. | | Usage anomalies | Current-period overages, failed Stripe usage syncs and usage spikes. | | Operational failures | Failures from agents, workflows, webhooks, integrations and deliveries in the last 24 hours. | MRR uses the recurring catalog price of each subscription. Annual prices are divided by twelve, and each currency is kept separate. Trials, canceled or paused subscriptions, usage overages and quote-only Enterprise amounts are not included. ### Subscriptions by plan [#subscriptions-by-plan] The plan distribution chart groups enabled organizations by their effective plan: Starter, Pro, Business or Enterprise. An organization without a paid subscription counts toward Starter. ### Recent signups [#recent-signups] This panel lists the latest user profiles created in the last 30 days. When nobody signed up in that window, it shows "No recent signups". ### Attention queue [#attention-queue] The attention queue merges payment, usage and operational signals into one list, newest first. Each item shows the organization, a short description, the time it happened and a severity of **Warning** or **Critical**. | Signal | Source | | ------------------------------- | -------------------------------------------------------------- | | Payment needs attention | A subscription invoice that is overdue or uncollectible. | | Usage limit exceeded | A meter over its included quantity in the current period. | | Stripe usage sync failed | Usage that could not be reported to Stripe. | | Unusual usage spike | A material 24-hour spike against the prior seven-day baseline. | | Workflow run failed | A failed workflow run. | | Agent run needs attention | An AI agent run that failed. | | Stripe webhook failed | A Stripe webhook event that failed processing. | | Integration installation error | An integration installation in an error state. | | Calendar integration error | A calendar connection in an error state. | | Channel connection is unhealthy | An inbox channel that reports an unhealthy connection. | | Channel delivery failed | An outbound inbox message that failed to deliver. | | Channel webhook job failed | An inbound channel webhook job that failed. | | Email delivery failed | A transactional email that bounced or failed. | A payment item is Critical when the invoice is uncollectible or the subscription is unpaid, and Warning otherwise. Usage and operational items carry the severity the platform assigns to that signal. When there is nothing to act on, the queue shows "Nothing needs attention". ## Follow up on an attention item [#follow-up-on-an-attention-item] Select an item to open the admin page that owns it: * Payment items open the organization's subscription at `/admin/subscriptions/{orgSlug}`, where you can review open invoices and refresh billing from Stripe. See [Billing](/admin/billing). * Workflow run failures open the run at `/admin/workflows/{workflowId}/runs/{runId}`. See [Workflows](/admin/workflows). * Integration failures open the catalog entry at `/admin/integrations/{integrationSlug}`. See [Integrations](/admin/integrations). * Usage anomalies and the remaining operational failures link to the organization when the failure belongs to one. Items without an organization have no link. If the dashboard fails to load, it shows "Operations dashboard is unavailable" with a **Try again** button. ## Permissions [#permissions] | Key | Effect | | --------------------- | --------------------------------------------------------- | | `system.admin.access` | Opens the admin area, including the operations dashboard. | The built-in System Admin and System Support roles both include `system.admin.access`. See [Roles and permissions](/admin/roles-and-permissions). ## Related pages [#related-pages] * [Analytics](/admin/analytics) * [Billing](/admin/billing) * [Organizations](/admin/organizations) * [Audit log](/admin/audit-log) * [Stripe billing operations](/operations/stripe-billing) # Feature flags (/admin/feature-flags) The status of the system admin feature flags page at /admin/feature-flags, which is a placeholder and does not manage flags yet. ## Overview [#overview] Feature flags has a route at `/admin/feature-flags` and a sidebar entry under **Admin > Platform > Feature Flags**. Not yet available: the page shows "Feature flags coming soon" with the note "Manage platform feature flags and rollouts here." There is no flag storage, rollout control or per-organization override behind it yet. ## Permissions [#permissions] | Key | Effect | | --------------------- | ------------------------------------------ | | `system.admin.access` | Opens the admin area, including this page. | There is no dedicated feature flag permission yet. ## Configure platform behavior today [#configure-platform-behavior-today] Until feature flags ship, platform-wide behavior is configured on these pages: * [Platform settings](/admin/platform-settings) for the invoice platform fee, inbound email domain and route distance services. * [AI settings](/admin/ai-settings) for enabled models, default models, the agent policy and runtime defaults. * [Integrations](/admin/integrations) for marketplace visibility and install behavior. * [Plans](/admin/plans) for the plan catalog and the features each plan includes. Environment configuration for deployments is described in [Environment variables](/operations/environment-variables). ## Related pages [#related-pages] * [Operations dashboard](/admin/dashboard) * [Platform settings](/admin/platform-settings) # Integrations (/admin/integrations) How system administrators manage the integration marketplace catalog under /admin/integrations, including availability, install scopes, categories, logos and resource links. ## Overview [#overview] Admin Integrations manages the marketplace catalog that organizations browse at `/{orgSlug}/integrations`. It lives at `/admin/integrations`, under **Admin > Operations > Integrations**. | Route | Purpose | | --------------------------------------- | -------------------------------------- | | `/admin/integrations` | List every integration in the catalog. | | `/admin/integrations/{integrationSlug}` | Edit one integration's catalog entry. | The catalog entries themselves ship with the platform. This page edits how each entry appears and how it can be installed; it does not create new integrations or change how a provider connects. Tenant installation and connection flows are described in [Integrations](/product/integrations). ## Key concepts [#key-concepts] ### Status [#status] Each integration has a catalog status: **Available**, **Coming soon**, **Planned**, **Suggested** or **Deprecated**. Only Available integrations are ready to install; the other statuses label roadmap or retired entries in the marketplace. Separately, an integration is **Enabled** or disabled. Disabled integrations are hidden from tenant marketplace pages. ### Categories [#categories] Categories control marketplace filters: Inbox, Agenda, Messaging, Calendar, Native, Automation, Billing, Files and Coming soon. ## Review the catalog [#review-the-catalog] The list shows Integration (name, provider and slug), Status, Categories, Usage and Requests. Usage is the number of installations. Requests is the number of times organizations requested a requestable integration. Search the list and use **Filters** for visibility, catalog status and category. ## Edit an integration [#edit-an-integration] Open an integration, for example `/admin/integrations/google-calendar`. The page has four sections, each with **Save changes** and **Cancel**. ### Catalog details [#catalog-details] Edit the public name, provider, short description, long description and keywords. Upload, replace or remove the logo; logos accept SVG, PNG or WebP up to 5 MB. ### Availability [#availability] | Setting | Effect | | ----------------------- | ------------------------------------------------------ | | Enabled | Shows or hides the integration in tenant marketplaces. | | Status | Sets the catalog status described above. | | Featured | Pins the integration to the top of the marketplace. | | Requestable | Lets organizations request the integration. | | Allow multiple installs | Allows more than one installation. | | Organization installs | Allows installation for the whole organization. | | User installs | Allows installation for an individual user. | ### Categories [#categories-1] Select the categories the integration appears under. ### Resources [#resources] Set the website, support, documentation, terms and privacy policy URLs shown on the integration detail page. ## Troubleshoot installations [#troubleshoot-installations] Integration installation errors appear in the attention queue on the [operations dashboard](/admin/dashboard) and link back to the integration's admin page. ## Permissions [#permissions] | Key | Effect | | --------------------- | ---------------------------------------------------------------------- | | `system.admin.access` | Opens Admin Integrations and is checked when saving changes and logos. | ## Related pages [#related-pages] * [Integrations](/product/integrations) * [Inbox](/product/inbox) * [Agenda](/product/agenda) * [Webhooks](/developers/webhooks) * [Operations dashboard](/admin/dashboard) # Knowledge (/admin/knowledge) How system administrators manage platform-wide knowledge collections under /admin/knowledge, and how ingestion and hybrid retrieval work for AI chat. ## Overview [#overview] System knowledge holds platform-wide content for retrieval-augmented generation (RAG) in AI chat and tools. Manage it at `/admin/knowledge`, under **Admin > Operations > Knowledge**. | Route | Purpose | | --------------------------------- | --------------------------------------- | | `/admin/knowledge` | List system collections. | | `/admin/knowledge/create` | Create a collection. | | `/admin/knowledge/{collectionId}` | Edit a collection and manage its files. | System collections are shared with every organization. Each organization also has its own collections at `/{orgSlug}/knowledge`. The seeded "CentraKit product guide" collection is an example of a system collection. ## Key concepts [#key-concepts] ### Collections [#collections] A collection groups related files. It has a name, a description, a retrieval priority and a visibility: | Visibility | Meaning | | ---------- | --------------------------------------------------------------- | | Automatic | Everyone with knowledge access can use the collection in chats. | | Restricted | Only granted teams and users can use the collection. | | Disabled | The collection is excluded from retrieval. | Team and user grants are managed in the Access section of organization collections. System collection pages have no Access section, so use Automatic or Disabled for system content. Retrieval priority breaks ties between equally relevant results; a lower number ranks first. ### Files and statuses [#files-and-statuses] Files are uploads or web pages. Each file shows a status: **Pending**, **Processing**, **Ready**, **Ready (text only)**, **Failed**, **Stale** or **Deleted**. A failed file shows the reason next to its status. ## Create a collection [#create-a-collection] 1. Select **New collection**. 2. Enter a name, a retrieval priority and a description. 3. Choose a visibility. 4. Select **Create collection**. ## Add content [#add-content] On the collection detail page: * **Upload files**: drop files on the upload area or click to browse. Accepted formats are text, Markdown, CSV, JSON, XML, YAML and HTML, up to 10 MB each. * **Add a web page**: select **Add URL** and enter the address. Pages larger than 5 MB fail ingestion. GitHub and API connections are not yet available. From the files table you can **Delete** a file or **Reingest** it, for example after changing the embedding model. ## How ingestion works [#how-ingestion-works] Each new file starts the system `knowledge-ingestion` workflow: 1. The workflow extracts text. HTML is converted to plain text. 2. The text is split into chunks along headings and blocks, with a budget of about 1,600 characters per chunk. 3. Chunks are embedded with the Embeddings default model from [AI settings](/admin/ai-settings). 4. Chunks are stored with a 1536-dimension vector and a full-text index, and the file becomes Ready. Only configure an embedding default that produces exactly 1536 dimensions. A model with the wrong size fails the file with an actionable error. A temporary provider outage marks the file **Ready (text only)**, so full-text search still works; reingest the file later to add vectors. ## How retrieval works [#how-retrieval-works] Chat and AI tools search knowledge with hybrid retrieval. Vector similarity and full-text rank are combined with reciprocal rank fusion, then ordered by retrieval priority. If the query embedding fails, search falls back to full text so chat keeps working. Retrieval always runs in the caller's organization and permission context. Row Level Security limits results to collections the user may read, so restricted collections need a grant and disabled collections never appear. The number of chunks retrieved per turn is set by the knowledge limits in [AI settings](/admin/ai-settings). ## Permissions [#permissions] | Key | Effect | | ------------------------- | -------------------------------------------------------------- | | `system.admin.access` | Opens the admin area. | | `system.knowledge.manage` | Creates, edits and deletes system collections and their files. | Organization collections use the `organization.knowledge.*` keys instead. ## Related pages [#related-pages] * [AI settings](/admin/ai-settings) * [Workflows](/admin/workflows) * [Content](/product/content) * [Chat](/product/chat) * [Workflow operations](/operations/workflows) # Organizations (/admin/organizations) How system administrators list, create, edit, disable and delete tenant organizations under /admin/organizations. ## Overview [#overview] Admin Organizations is the platform-wide list of tenants at `/admin/organizations`. System administrators use it to find any organization, create new ones and change settings on behalf of a tenant. Each organization is addressed by its slug, not an internal ID: | Route | Purpose | | ----------------------------------------- | --------------------------------- | | `/admin/organizations` | List and filter organizations. | | `/admin/organizations/create` | Create a new organization. | | `/admin/organizations/{orgSlug}/settings` | Edit one organization's settings. | Tenant admins manage their own organization at `/{orgSlug}/settings`. See [Settings and branding](/product/settings-and-branding). ## Key concepts [#key-concepts] * **Slug.** The URL identifier for an organization, used in every tenant route. See [Tenancy](/architecture/tenancy). * **Enabled or disabled.** A disabled organization keeps all of its data, but tenant routes are blocked for its members. Disabling is reversible. * **Deleted.** Deleting an organization permanently removes it and all of its data. ## Find an organization [#find-an-organization] The list shows Organization, Slug, Website, Members and Updated. Search with **Search organizations…** and use **Filters**: * **Profile**: Has website or Has logo. * **Account status**: Enabled or Disabled. The row menu offers **Settings** and **Copy slug**. ## Create an organization [#create-an-organization] 1. Select **New organization**. 2. Fill in **General**: organization name, slug and website. 3. Optionally fill in **Company information**: address, Chamber of Commerce (KVK) number, VAT number (BTW), IBAN and phone. 4. Optionally add **Socials**: LinkedIn, X, Facebook, Instagram and YouTube. These links appear in the customer portal. 5. Optionally set **Branding**: a logo (PNG, JPEG or WebP up to 5 MB), primary and secondary colors, and up to 12 brand images of up to 8 MB each. 6. Select **Create organization**. The admin who creates the organization becomes its owner, and the app opens the new organization's admin settings page. ## Edit organization settings [#edit-organization-settings] The settings page at `/admin/organizations/{orgSlug}/settings` shows the same sections a tenant owner sees, each with its own **Save** button: | Section | Fields | | ------------------- | --------------------------------------------------------------------------------------------------------------------------------- | | General | Name, slug and website. | | Company information | Address, KVK number, VAT number, IBAN and phone, used on invoices and official correspondence. | | Socials | Social links shown in the portal. | | Finance | Base currency, default hourly rate, quote validity days, fiscal year start, number prefixes, invoice number format and VAT rates. | | Email | Inbox, forwarding and reply-to addresses. | | Other settings | Operating country, default export format and export date format. | | Branding | Logo, primary and secondary colors, and brand images. | | Admin tools | Enable, disable or delete the organization. | The invoice number format accepts the tokens `{id}`, `{year}`, `{year2}`, `{month}`, `{day}` and `{customer_id}`. Including a year token resets the counter each year. ## Disable or enable an organization [#disable-or-enable-an-organization] In **Admin tools**, select **Disable organization** and confirm. Members lose access to tenant routes, but nothing is deleted. Select **Enable organization** to restore access. ## Delete an organization [#delete-an-organization] In **Admin tools**, select **Delete organization** and confirm. This cannot be undone; the organization and all of its data are permanently deleted. ## Permissions [#permissions] | Key | Effect | | ----------------------------- | ------------------------------------------------------------------------ | | `system.admin.access` | Opens the admin area. | | `system.organizations.view` | Reads organizations, including disabled ones. | | `system.organizations.manage` | Edits settings, branding and lifecycle state, and deletes organizations. | System Admin has both organization keys. System Support has `system.organizations.view` only. Creating, disabling, enabling and deleting an organization is recorded in the [audit log](/admin/audit-log). ## Related pages [#related-pages] * [Organizations](/product/organizations) * [Settings and branding](/product/settings-and-branding) * [Users](/admin/users) * [Billing](/admin/billing) * [Tenancy architecture](/architecture/tenancy) * [RBAC architecture](/architecture/rbac) # Plans (/admin/plans) How system administrators review the Starter, Pro, Business and Enterprise plan catalog, edit credit package prices, and publish prices to Stripe under /admin/plans. ## Overview [#overview] Plans is the subscription catalog at `/admin/plans`, under **Admin > Finance > Plans**. It lists every plan with its price, included credits and Stripe state, and lets system administrators edit package prices and publish them to Stripe. | Route | Purpose | | ------------------------ | -------------------------------------- | | `/admin/plans` | List all plans. | | `/admin/plans/{planKey}` | Edit one plan's credit package prices. | CentraKit is the source of truth for the plan catalog, usage events and period totals. Stripe is the source of truth for subscriptions, invoices and payment methods. Customers see the result in [Billing and usage](/product/billing-and-usage). ## Key concepts [#key-concepts] ### Plans [#plans] | Plan | Key | Type | | ---------- | ------------ | ------------------------------------------------------ | | Starter | `starter` | The free default plan. It has no credit rollover. | | Pro | `pro` | Self-serve plan for small teams. | | Business | `business` | Self-serve plan with a larger standard credit package. | | Enterprise | `enterprise` | Quote-only plan. Its price shows "On request". | There is no separate Free plan; Starter is the free tier. ### Credit packages [#credit-packages] Pro and Business are sold in credit packages that set the included monthly credits and the matching Stripe price: | Plan | Packages (credits) | | -------- | -------------------------------- | | Pro | 100, 200, 400, 800 and 1,000 | | Business | 400, 600, 1,000, 2,000 and 5,000 | Each package has a monthly and a yearly price. The package marked **Default** is the plan's standard credit amount. Credits cover AI chat and agents, inbox messages, API requests and MCP calls, and workflow steps. Prices are stored in euro cents. Unused credits on Pro and Business roll over for one month on monthly plans, or until one month after the end of an annual plan. Rollover grants are tracked per organization and expire on their own schedule. ### Stripe state [#stripe-state] Each plan shows **In Stripe** when its prices exist in Stripe, or **Not in Stripe** when they still need to be published. Enterprise shows "On request" because it has no self-serve price. ## Review the catalog [#review-the-catalog] The list shows Plan, Base price, Standard credits, Packages, Annual discount and Stripe. Search with **Search plans…** and use **Filters** for plan type (Self-serve or On request) and Stripe status. Open a plan from its row or the row menu. ## Edit a package price [#edit-a-package-price] 1. Open a plan, for example `/admin/plans/pro`. 2. In **Credit packages**, find the package and interval you want to change. 3. Enter the new amount per month or per year. 4. Select **Save** on that row. The new amount is stored in the local catalog. Existing Stripe prices do not change until you publish. ## Publish prices to Stripe [#publish-prices-to-stripe] On the plan detail page, select **Publish prices to Stripe**. CentraKit creates or updates the Stripe product and a price for each package and interval of that plan, using a lookup key such as `centrakit_pro_100_month`, and records the published version. Enterprise is skipped because it is quote-only. Publish after every price change so that new checkouts use the new amounts. Billing does not work locally, because the local Stripe keys are placeholders. ## Customer-facing features [#customer-facing-features] The plan detail page also lists the features shown in plan comparison and checkout, such as AI chat and agents, inbox channels, API and MCP access, workflows and audit logs. Audit log retention depends on the plan: 1 day on Starter, 7 days on Pro, 90 days on Business, and 365 days or a custom override on Enterprise. ## Permissions [#permissions] | Key | Effect | | --------------------- | ------------------------------------------------------------- | | `system.admin.access` | Opens Plans and is checked when saving and publishing prices. | ## Related pages [#related-pages] * [Billing](/admin/billing) * [Billing and usage](/product/billing-and-usage) * [Stripe billing operations](/operations/stripe-billing) * [Audit log](/admin/audit-log) # Platform settings (/admin/platform-settings) How system administrators configure the customer invoice platform fee, the inbound email domain and route distance services at /admin/settings, and how platform settings are stored. ## Overview [#overview] Platform settings holds operable configuration that applies to the whole platform. The settings page is at `/admin/settings`, under **Admin > Finance > Settings**. Settings are stored in the database, not in environment variables, so administrators can change them without a deployment. Secrets and deployment configuration still belong in [environment variables](/operations/environment-variables). ## Key concepts [#key-concepts] Each setting is a row in the `platform_settings` table with a key, a JSON value, the update time and the user who changed it. Values are validated per key in application code. When a key has no row, the platform uses its built-in default. | Key | Edited on | Default | | --------------------- | ------------------------------------------------ | ----------------------------- | | `invoice_payment_fee` | Platform settings, Customer invoice platform fee | 150 basis points (1.5%) | | `email_inbound` | Platform settings, Inbound email | Off (no domain) | | `routing` | Platform settings, Route distance | Off (no URLs) | | `ai_agent_policy` | [AI settings](/admin/ai-settings), Policy tab | Agent ceiling, High threshold | | `ai_runtime_defaults` | [AI settings](/admin/ai-settings), Policy tab | See AI settings | Any signed-in user can read platform settings, because features such as invoice sending and chat need them at runtime. Writes are limited per key, as described under Permissions. ## Set the customer invoice platform fee [#set-the-customer-invoice-platform-fee] When an organization's customer pays an invoice online, the platform is the merchant of record. The fee is taken as the Stripe application fee on the destination charge before the funds are transferred to the organization. Choose a **Fee type**: * **Percentage of invoice total**: enter **Fee (basis points)** between 0 and 10000. For example, 150 bps is 1.5%. * **Fixed amount per transaction**: enter **Fixed fee amount** in major currency units, for example 0.30. The fixed fee is applied in the invoice currency and capped at the invoice total. Select **Save**. See [Invoices](/product/invoices) and [Stripe billing operations](/operations/stripe-billing). ## Turn on inbound email [#turn-on-inbound-email] **Inbound email** sets the receiving domain configured in Resend, such as `inbound.example.com`. Employees forward receipts to their personal `expenses+` address at this domain to create draft expense claims. Leave the field empty to turn receipt forwarding off. ## Turn on route distance [#turn-on-route-distance] **Route distance** takes a **Geocoder URL** for a Nominatim-compatible geocoder and a **Router URL** for an OSRM-compatible router. Together they calculate mileage distances for expense claims. Both must be full URLs; leave either empty to keep distance entry manual. ## Audit trail [#audit-trail] Every platform setting change is recorded in the [audit log](/admin/audit-log) as a platform setting mutation. ## Permissions [#permissions] | Key | Effect | | ------------------------- | ---------------------------------------------------------------------------------------- | | `system.billing.manage` | Opens `/admin/settings` and writes `invoice_payment_fee`, `email_inbound` and `routing`. | | `system.ai-models.manage` | Writes `ai_agent_policy` and `ai_runtime_defaults`. | System Admin has both keys. System Support has neither. ## Related pages [#related-pages] * [Billing](/admin/billing) * [AI settings](/admin/ai-settings) * [Expenses](/product/expenses) * [Invoices](/product/invoices) * [Stripe billing operations](/operations/stripe-billing) * [Environment variables](/operations/environment-variables) # Roles and permissions (/admin/roles-and-permissions) How system administrators manage built-in and global roles, edit permission titles, and set platform approval rules under /admin/roles. ## Overview [#overview] Admin Roles and Permissions is the platform-level RBAC surface at `/admin/roles`. It has three tabs: | Tab | Route | Purpose | | -------------- | ----------------------------- | -------------------------------------------------------------- | | Roles | `/admin/roles` | Built-in roles and global custom roles. | | Permissions | `/admin/roles/permissions` | The permission catalog, with editable titles and descriptions. | | Approval rules | `/admin/roles/approval-rules` | Platform approval floors that apply to every organization. | Detail pages live at `/admin/roles/{roleId}`, `/admin/roles/create` and `/admin/roles/permissions/{permissionId}`. Organization-owned roles are managed by tenant admins at `/{orgSlug}/roles`. See [Access control](/product/access-control). ## Key concepts [#key-concepts] ### Permission keys [#permission-keys] Every permission key has the form `scope.feature.action`, for example `system.users.manage` or `organization.quotes.send`. The registry in code is the source of truth; the admin UI cannot add, rename or delete keys. The local catalog has 185 permissions across the system, organization and user scopes. The system permissions are: | Key | Grants | | ----------------------------- | ----------------------------------------------------- | | `system.admin.access` | Access to the admin area. | | `system.organizations.view` | Read organizations, including disabled ones. | | `system.organizations.manage` | Edit, disable and delete organizations. | | `system.billing.view` | Read subscriptions, invoices and billing state. | | `system.billing.manage` | Change subscriptions, invoices and platform settings. | | `system.users.view` | Read platform users. | | `system.users.manage` | Invite, edit, disable, ban and delete users. | | `system.audit-log.view` | Read the platform audit log. | | `system.audit-log.export` | Export audit events. | | `system.audit-log.reveal` | Reveal restricted audit details. | | `system.analytics.view` | Read platform analytics. | | `system.analytics.export` | Export platform analytics. | | `system.roles.manage` | Create, edit and delete roles and permission labels. | | `system.ai-models.manage` | Change AI agent policy and runtime defaults. | | `system.knowledge.manage` | Manage system knowledge collections. | | `system.workflows.manage` | Manage system workflows and lifecycles. | | `system.approvals.manage` | Manage platform approval rules. | ### Built-in roles [#built-in-roles] | Role | Scope | Default permissions | | -------------- | ------------ | ------------------------------------------------------------------------------------ | | System Admin | System | Every system permission. | | System Support | System | Admin access, organization view, billing view, user view and manage, audit log view. | | Owner | Organization | Billing, delete and ownership transfer, on top of everything Admin has. | | Admin | Organization | All organization permissions except the owner-only ones. | | Member | Organization | View plus create and update for everyday records, without deletes. | | Viewer | Organization | View permissions only. | Owner, Admin, Member and Viewer are global templates that every organization uses. Built-in roles show a lock icon. On their detail page the key, scope and permission matrix are read-only, and the role cannot be deleted. System administrators can still change the display name and description. In tenant role pages, the four organization templates appear as locked rows that cannot be opened. ### Global custom roles [#global-custom-roles] A role created here has no organization, so it is global. A global role can use the system, organization, user or team scope. Tenant admins see global roles as read-only rows next to their own organization roles. ## Create a role [#create-a-role] 1. On the Roles tab, select **New role**. 2. Enter a name. The key is generated from the name and must be lowercase with dashes. 3. Add an optional description and choose the **Role scope**. 4. In **Permissions**, select permissions individually or use **Select all** per group. Search and filter the matrix to find keys. 5. Select **Create role**. Role keys must be unique. ## Edit or delete a role [#edit-or-delete-a-role] Open a role from the Roles tab. Save the name, description and scope in **Details**, and save the permission matrix separately in **Permissions**. Save stays disabled until something changes. To delete a custom role, use **Delete role** in the danger zone or the row menu, and type the role name to confirm. Built-in roles show "Built-in system roles cannot be deleted." ## Edit a permission label [#edit-a-permission-label] The Permissions tab lists every permission with its title and scope. Open one to change its **Title** and **Description**. The key and scope are defined in application code and cannot be changed here. ## Set platform approval rules [#set-platform-approval-rules] The Approval rules tab defines platform floors. They apply to every organization, and organizations can only add stricter rules on top. Without rules, risky AI actions still ask for confirmation. Select **Add rule** and configure: * **Name** and **Description**. * **Action** and **Effect**, such as Customer-facing, Moves money, Destructive, Changes access, Calls an outside system or Bulk change. * **Who is acting**: People, AI agents, Workflows, MCP clients or API keys. Selecting none means everyone. * **Only when** conditions on a path, operator and value. * **Requires**: someone else approves, or the requester confirms. * **Approvers**, **Approval mode** (any approver, every approver or in order) and **Approvals needed**. * **Expires after (hours)**, **Escalate after (hours)** and **Escalate to**. * Whether the requester may approve their own request, and whether the rule is active. See [Approvals](/product/approvals) for how requests are decided. ## Permissions [#permissions] | Key | Effect | | ------------------------- | ------------------------------------------------------------- | | `system.admin.access` | Opens the admin area and these tabs. | | `system.roles.manage` | Creates, edits and deletes roles and edits permission labels. | | `system.approvals.manage` | Creates, edits and deletes platform approval rules. | Role writes are checked on the server and recorded in the [audit log](/admin/audit-log). Client-side checks only hide controls. ## Related pages [#related-pages] * [Users](/admin/users) * [Access control](/product/access-control) * [Approvals](/product/approvals) * [RBAC architecture](/architecture/rbac) # Users (/admin/users) How system administrators list, invite, edit, disable, ban and delete platform users, and assign system and organization roles, under /admin/users. ## Overview [#overview] Admin Users is the platform-wide user directory at `/admin/users`. It lists every user across all organizations, shows their access, and gives system administrators lifecycle tools that tenant admins do not have. The page has two tabs: | Tab | Route | Purpose | | ------------------- | -------------------------- | ------------------------------------- | | Users | `/admin/users` | Every user profile on the platform. | | Pending invitations | `/admin/users/invitations` | Invitations that grant a system role. | Organization-scoped user management for tenant admins lives at `/{orgSlug}/users`. See [Access control](/product/access-control). ## Key concepts [#key-concepts] * **System role.** A role with system scope, such as System Admin or System Support. It grants `system.*` permissions and is managed only here. * **Organization role.** A user's role inside one organization, such as Owner, Admin, Member or Viewer. * **Disabled.** A reversible CentraKit state. A disabled user cannot use the app, but their Supabase Auth account still exists. * **Banned.** An authentication-level block in Supabase Auth. A banned user cannot sign in at all. * **Deleted.** A permanent hard delete of the user. ## Find a user [#find-a-user] The Users tab shows Name, Email, Role, Organizations, Joined and Last active. Search by name or email with **Search users…**, sort by Name, Email, Joined or Last active, and narrow the list with **Filters**: * **Access**: System access, Organization access or No assigned access. * **Account status**: Enabled or Disabled. Open a row, or use the row menu to **Open** the user or **Copy email**. ## Edit a user [#edit-a-user] The user detail page at `/admin/users/{userId}` has four sections. ### Personal information [#personal-information] Change the profile photo, first name, last name and username. Photos accept PNG, JPEG or WebP up to 5 MB and can be cropped before upload. Usernames use lowercase letters, numbers and underscores, with at least 3 characters. ### Organizations [#organizations] This table lists every organization the user belongs to, with the role and join date. Change the role in a row and select **Save** to update it. The first row, labeled with the product name and "System-wide organization", holds the user's system role. Pick a system role there to grant platform access, or choose **None** to remove it. ### Statistics [#statistics] A read-only summary of email, username, account creation time, last activity, organization count, system roles, CentraKit access (Enabled or Disabled) and sign-in state (Allowed or Banned). ### Admin tools [#admin-tools] | Action | Effect | | ------------ | --------------------------------------------------------------------------------------- | | Disable user | Blocks CentraKit access without touching the auth account. **Enable user** restores it. | | Ban user | Blocks authentication in Supabase Auth. **Unban user** lets the user sign in again. | | Delete user | Permanently deletes the user. You must type the confirmation text before deleting. | The admin tools protect against lockouts. You cannot disable, ban or delete your own user, and you cannot disable, ban or delete the last active system admin. ## Invite a system user [#invite-a-system-user] 1. Open the Pending invitations tab and select **Invite**, or go to `/admin/users/invitations/create`. 2. Enter the email address and pick a system role. 3. Optionally add a first and last name to prefill the profile. 4. Select **Send invitation**. The invitee receives an email with a link to `/invitations/{token}`. If the email cannot be sent, the invitation is still saved and the error is shown, so you can resend it later. ## Manage pending invitations [#manage-pending-invitations] The Pending invitations tab lists Email, Role, Sent and Expires. Filter by status (Active or Expired) and search by email. * Open an invitation at `/admin/users/invitations/{invitationId}` to edit its details while it is still pending. * Use the row menu to **Resend invitation** or **Revoke invitation**. * Select several rows to revoke them in one step. Accepted invitations can no longer be edited. ## Permissions [#permissions] | Key | Effect | | --------------------- | -------------------------------------------------------------------------- | | `system.admin.access` | Opens the admin area. | | `system.users.view` | Reads platform users. | | `system.users.manage` | Invites, edits, disables, bans and deletes users, and changes their roles. | Both System Admin and System Support include `system.users.manage`. Every admin user mutation, including denied attempts, is recorded in the [audit log](/admin/audit-log). ## Related pages [#related-pages] * [Organizations](/admin/organizations) * [Roles and permissions](/admin/roles-and-permissions) * [Access control](/product/access-control) * [Profile](/product/profile) * [RBAC architecture](/architecture/rbac) * [Authentication architecture](/architecture/authentication) # Workflows (/admin/workflows) How system administrators manage the system automations and record lifecycles that apply to every organization under /admin/workflows, including versions, default lifecycles and run history. ## Overview [#overview] Admin workflows are the system-scoped counterpart of tenant workflows. They live at `/admin/workflows`, under **Admin > Operations > Workflows**, and apply to every organization. | Route | Purpose | | -------------------------------------------- | ---------------------------------------- | | `/admin/workflows` | List system workflows and lifecycles. | | `/admin/workflows/create` | Create a system workflow. | | `/admin/workflows/{workflowId}` | Editor tab: the visual graph. | | `/admin/workflows/{workflowId}/details` | Details tab: name and description. | | `/admin/workflows/{workflowId}/runs` | Runs tab: run history for this workflow. | | `/admin/workflows/{workflowId}/runs/{runId}` | One run with its graph and step details. | Organization workflows are built under `/{orgSlug}/workflows`. See [Workflows](/product/workflows). ## Key concepts [#key-concepts] ### Purpose [#purpose] Each system workflow has a purpose, shown as **Kind** in the list: * **Automation** workflows react to domain events, schedules, webhooks or manual starts. With automatic visibility, they run for every organization unless the organization turns the workflow off or publishes its own workflow with the same key. * **Lifecycle** workflows describe the stages of one record type. They do not match events directly. A record runs the lifecycle chosen by its template, then the organization default, then the system default. ### Versions [#versions] A workflow key has at most one published and one draft version. Publishing archives the previous version, removes its schedules and carries access grants and watchers forward. A running lifecycle stays on the version it started with; only records that start later use the new version. System schedules fan out into one run per enabled organization that has not turned the workflow off. ### Shipped system lifecycles [#shipped-system-lifecycles] | Lifecycle | Record type | What it tracks | | ------------------------ | -------------- | ------------------------------------------------------------------------------------------------------- | | Quote lifecycle | Quote | Send, views and the customer decision, reminders before expiry, expiry, then auto-reject after 30 days. | | Invoice lifecycle | Invoice | Send, views and payments, reminders, overdue, the final notice, then collection costs. | | Task lifecycle | Task | A root task through work and holds to completion. | | Order lifecycle | Order | A confirmed order through prepayment, picking, shipping or pickup, delivery and invoicing. | | Purchase order lifecycle | Purchase order | A sent purchase order through supplier confirmation, transit and receipt until the supplier bill is in. | | Shipment lifecycle | Shipment | A shipment from the warehouse to delivery or pickup, including carrier problems. | ### Shipped system automations [#shipped-system-automations] The seed also ships automations such as Quote accepted (which creates a draft invoice and a follow-up task), Quote rejected, Invoice paid, Invoice cancelled, Invoice payment failed, Invoice disputed, Workflow failed alert, Task assigned, Task due soon, Task overdue, Task due scan, Agenda item follow-up, Customer welcome, Inbox unanswered, Invoice overdue digest, Stale draft digest and Billing usage threshold. Platform jobs such as Knowledge ingestion, Product lookup from image and Customer details lookup also run as system workflows. ## Find a workflow [#find-a-workflow] The list shows Name, Status, Version and Updated. Statuses are Draft, Published and Archived. Search with **Search workflows…** and use **Filters** to show only Automation or Lifecycle workflows. Row actions include **Duplicate** and **Delete**. ## Edit and publish a workflow [#edit-and-publish-a-workflow] 1. Open the workflow. The **Editor** tab shows the graph. 2. Add nodes from the palette: triggers (Domain event, Schedule, Manual, Workflow failed, Webhook), flow nodes (Condition, Merge, Delay, Stage, Clear flag, Wait for event), actions (Send email, AI agent, Call tool, HTTP request, Send webhook, Approval gate, Domain action, Start workflow) and the domain actions for quotes, invoices, tasks, orders, purchasing, knowledge and more. 3. Use **Test run** to try the draft, or **Edit with AI** to change the graph with the assistant. 4. Publish the draft to make it the active version. Change the name and description on the **Details** tab. ## Set the default lifecycle [#set-the-default-lifecycle] Filter the list by Lifecycle, open the row menu of a lifecycle and select **Set as default**. Exactly one system lifecycle per record type is the default, and newer published versions of that key take over automatically. Organizations cannot edit a system lifecycle. They open it read-only and use **Duplicate to customize**. The copy remembers which system version it came from and shows **Upstream updated** when a newer system version is published. ## Review runs [#review-runs] The **Runs** tab lists runs for every version of the key, with statuses such as Queued, Running, Waiting, Succeeded, Failed, Cancelled and Stuck. Select **Run now** to start a run manually. Open a run to see its graph and steps. Select a step to view its input, output, error and attempts. A run waiting on an approval gate shows **Approve** and **Deny**. Use **Cancel run** to stop a queued, running or waiting run; a cancelled run cannot be resumed. Failed runs also appear in the attention queue on the [operations dashboard](/admin/dashboard). ## Safety [#safety] Automatic system graphs notify people or create drafts. For example, Quote accepted creates a draft invoice instead of sending one. Lifecycles may email customers, such as quote and invoice reminders and the final notice. Those emails are sent within business hours in the organization's time zone, and the record is checked again before sending, so a settled record does not get a reminder. System graphs do not bypass tenant permissions when they change records. Lifecycle pause, resume and manual advance run with admin context only after the caller's route, action or AI tool has checked the record permission. ## Permissions [#permissions] | Key | Effect | | ------------------------- | ------------------------------------------------------------------------------------------------- | | `system.admin.access` | Opens the admin area. | | `system.workflows.manage` | Creates, edits, publishes and deletes system workflows, sets default lifecycles and manages runs. | ## Related pages [#related-pages] * [Workflows](/product/workflows) * [Approvals](/product/approvals) * [Workflow operations](/operations/workflows) * [Webhooks](/developers/webhooks) * [Operations dashboard](/admin/dashboard) # Agent notes (/agents/README) Things coding agents get wrong in CentraKit, grouped by topic. These pages collect corrections that agents needed more than once. `AGENTS.md` links here, so an agent can load only the topic it is working on. ## When to add a note [#when-to-add-a-note] Add a note when you correct the same mistake a second time. Put it in the page for that topic, or start a new page if none fits. Write one short paragraph per mistake: what goes wrong, why, and what to do instead. ## Topics [#topics] * [Toolchain](/agents/toolchain): pnpm, TypeScript 7, Oxlint and Oxfmt. * [Git and releases](/agents/git-and-release): branches, commit messages, hooks and Changesets. * [Supabase schema](/agents/supabase-schema): declarative files, ordering, migrations and audit triggers. * [Tenancy and routing](/agents/tenancy-and-routing): organization slugs, page shells and Suspense. * [Translations](/agents/translations): `.po` catalogs and extracted messages. * [Testing](/agents/testing): where tests live and how they resolve paths. * [Writing docs](/agents/writing-docs): how to write public docs pages from the shipped code. # Git and releases (/agents/git-and-release) Branches, commit messages, hooks and Changesets. ## Branches [#branches] Commit and push to `develop`. Never commit to `main`; it only moves through the `develop` to `main` pull request. ## Commit messages [#commit-messages] Commits follow Conventional Commits and commitlint checks them. The header is at most 72 characters and body lines are at most 100. Mark breaking changes with `!` and a `BREAKING CHANGE:` footer. ## Hooks stay on [#hooks-stay-on] Lefthook runs format and lint before a commit and the affected typecheck and test gate before a push. Do not pass `--no-verify`. When a hook fails, fix the cause and commit again. ## Changesets [#changesets] Every user-visible change adds a changeset with `pnpm changeset`. The apps share one version through a `fixed` group, so pick any app and describe the change for users. Releases do not use release-please. ## Uncommitted work belongs to the user [#uncommitted-work-belongs-to-the-user] Do not run `git checkout`, `git restore`, `git reset` or `git stash` on files you did not change in this task. The one routine exception is restoring the `.po` catalogs described in [Translations](/agents/translations). # Supabase schema (/agents/supabase-schema) Declarative schema files, ordering, migrations and audit triggers. ## Edit schemas, generate migrations [#edit-schemas-generate-migrations] Change `supabase/schemas/**/*.sql`, then run `pnpm supabase:diff` or `supabase db diff -f `. Do not write migration SQL by hand. The exceptions are the documented declarative caveats: data changes, column comments, and the `supabase_auth_admin` grants that must be appended after an init migration is regenerated. ## Column grants never reach migrations [#column-grants-never-reach-migrations] `db diff` does not emit column-level grants such as `grant update (first_name, ...) on public.profiles`. A generated migration can still revoke the table-level grant, which leaves the column grant missing in production. Write column grants into a migration by hand and cover them with a check in `supabase/tests/database/grants.test.sql`. ## File numbers set the order [#file-numbers-set-the-order] Files live in domain folders, but the global number in each file name decides when it runs. `schema_paths` in `supabase/config.toml` lists every file sorted by file name. When a file depends on another file with the same number, give it the next free number instead of relying on alphabetical order. After any move or rename, `pnpm supabase:diff` must report no schema changes. ## One definition per function [#one-definition-per-function] Define each function in exactly one file. With two definitions, the file that runs last wins without any error, and a reorder can silently deploy the wrong body. ## Registry data never reaches migrations [#registry-data-never-reaches-migrations] `db diff` compares structure, not rows. Data inserted by a schema file, such as `audit_log_table_registry` rows, exists only while the diff runs. Anything production needs must end up in DDL. For example, audit column lists travel as trigger arguments. ## New tenant tables [#new-tenant-tables] A new table with `organization_id` and `id` gets the `*_capture_audit_log` trigger automatically. Add ignored columns (timestamps, leases, search vectors) and redacted columns (tokens, secrets, credentials) in `platform/067_triggers_audit_log.sql`. If a table must not be audited, add it to the exemption list in `supabase/tests/database/audit.test.sql` with a reason. Then run `pnpm supabase:test`. ## Generated types [#generated-types] `pnpm supabase:types` generates `public` and `graphql_public` together. Generating `public` alone drops `graphql_public`. ## Demo seeds [#demo-seeds] Files `012` to `063` in `supabase/seeds/` hold platform data (auth users, profiles, roles, catalogs and settings). Files `100` to `170` hold the demo tenants, one domain per file, and each is one `do` block that starts with a `-- Covers:` comment. The header of `100_organizations.sql` is the registry: the six organizations, every persona and the table codes used in seeded ids. * Seeded ids follow `{TT}{O}00000-0000-4000-8000-{NNNNNNNNNNNN}`. Pick a table code that the registry does not list yet and add it there. `demo-seeds-static.test.ts` fails on an unregistered code. * Delete earlier rows with the exact pattern `'TT_00000-0000-4000-8000-%'`. A bare `'TT%'` also matches rows that triggers created with random uuids. * Write every scenario by hand. Message text cites names and document numbers (`TASK-1`, `Q-8`, `INV-2026-4`), never uuids. * Use only product and supplier names that exist in `111_catalog.sql`. * No seeded person shares a surname with another seeded person, and near-duplicates such as Jansen and Janssen are avoided too. Check before you add a contact, candidate or invitation. * Audit logs are append-only. Wrap a seed delete in `set_config('app.audit_retention', 'on', true)` and set it back to `off`. * `170_integrity.sql` runs last. It recomputes allocations, order totals and number sequences, so add new numbered documents before it. * Storage objects for seeded rows live in `supabase/seed-buckets//` and load on `pnpm supabase:reset`. Columns store the object path only. * Test a single file without a reset by piping it through `begin; ... rollback;` into `docker exec -i supabase_db_CentraKit psql`. ## Seeds on the persistent develop branch [#seeds-on-the-persistent-develop-branch] The Supabase integration records every seed file it runs in `supabase_migrations.seed_files`. On a push it runs only seed files it has not recorded yet. It skips a file that already ran, even when the file changed. Adding new seed files on top of an older seed set therefore runs them against data they did not expect, and edits to `012` or other earlier files never reach develop. * The seeds must still survive a rerun locally. `012_auth_users.sql` deletes the seeded organizations by slug and by id before it deletes the seeded users, with `app.skip_allocation_release` and `app.shipment_transition` switched on. Team ids are pinned in `101_teams.sql` because later files reference them. * When a seed restructure breaks the Supabase Preview check, reset the develop database. *Reset branch* in the dashboard does not drop data; it only redeploys. * Reset develop from the CLI. `branches get` returns the develop connection string with its password, and never returns one for `main`: ```sh pnpm exec supabase db reset --db-url "$(pnpm exec supabase branches get develop --project-ref gunugimgrrnxqynjghqe -o env | grep '^POSTGRES_URL=' | cut -d= -f2- | tr -d '"')" ``` * Never use `--linked` for this. The local link points at production. * A remote reset drops every object in one transaction and fails with `out of shared memory` at the default lock limit. Develop runs with `max_locks_per_transaction=1024`. If a new branch needs it, set it with `pnpm exec supabase postgres-config update --experimental --project-ref --config max_locks_per_transaction=1024`, which restarts that database. * Production keeps seeding disabled, so seed changes never need a production reset. # Tenancy and routing (/agents/tenancy-and-routing) Organization slugs, synchronous page shells and Suspense islands. ## The slug is the tenant [#the-slug-is-the-tenant] Tenant routes live under `app/[locale]/(app)/[orgSlug]/`. There is no current-organization cookie and no organization header. Pass `orgSlug` from route `params` into Suspense islands with `params.then(...)`, and resolve access with `requireOrganizationAccess({ orgSlug, permission })`. ## Page shells stay synchronous [#page-shells-stay-synchronous] Pages and layouts do not await data or import feature queries. Put `` around async feature components and export each skeleton beside the component it stands in for. Do not register `orgSlug` in `next/root-params`; only `locale` uses root params. ## Read the installed Next.js docs [#read-the-installed-nextjs-docs] Next.js 16 differs from older examples. Read the relevant guide in `node_modules/next/dist/docs/` before changing caching, prefetching, or routing behavior. ## Do not redirect to login from the app [#do-not-redirect-to-login-from-the-app] The proxy is the authentication boundary. Adding an in-app redirect to `/login` when a request context has no session can create a refresh loop. # Testing (/agents/testing) Where tests live and how they resolve paths. ## Where a test goes [#where-a-test-goes] A test that covers one workspace lives in that workspace's `tests/` folder, never next to the source. A test that imports from several workspaces goes in `tests/integration/cross-package//`. Browser flows go in `tests/e2e/saas/`, and database checks go in `supabase/tests/database/` as pgTAP. ## Shared helpers [#shared-helpers] Import setup files and helpers from `@workspace/test-fixtures`. Keep that package free of internal workspace dependencies, because workspaces depend on it and anything else creates a cycle. ## Resolve paths from the test file [#resolve-paths-from-the-test-file] Tests run from different working directories under Turbo, Vitest projects and editors. Build file paths from `import.meta.dirname`, not `process.cwd()`. When a test reads a schema file, include its domain folder, for example `supabase/schemas/platform/058_functions_audit_log.sql`. ## Commands [#commands] `pnpm test` runs everything through Turbo. `pnpm test:unit`, `pnpm test:integration`, `pnpm test:e2e` and `pnpm supabase:test` run one layer. Inside a workspace, `pnpm exec vitest run ` runs a single file. # Toolchain (/agents/toolchain) pnpm, TypeScript 7, Oxlint and Oxfmt mistakes to avoid. ## Versions come from the catalog [#versions-come-from-the-catalog] Look up a version with `pnpm view version` when you add or bump it, and pin it exactly in the `pnpm-workspace.yaml` catalog. Workspaces reference it as `"catalog:"`. Do not rely on a version you remember. ## The release-age gate stays on [#the-release-age-gate-stays-on] `minimumReleaseAge` (1440 minutes) blocks packages published less than a day ago. Do not disable it or add broad excludes to install a new release. Pick the newest version that clears the gate, and add a narrow, commented exclude only for the packages already listed in the toolchain rule. ## TypeScript 7 only [#typescript-7-only] Typecheck runs `tsc --noEmit --checkers 4` from TypeScript 7. Do not add TypeScript 5 or 6 shims, and do not enable Oxlint's `typeCheck` option. Omit optional properties instead of passing `undefined`, and read index signatures with `obj["key"]`. ## Lint is per workspace [#lint-is-per-workspace] Each workspace has its own `oxlint.config.ts` that extends presets from `@workspace/ox-config`. Run `pnpm lint` (Turbo) for workspaces and `pnpm lint:root` for root tooling files. Fix the code when a rule fires. Do not turn a rule off or add a disable comment to get a green run. ## Type assertions need a reason [#type-assertions-need-a-reason] The anti-slop plugin requires a `// SAFETY:` comment above every `as` assertion that explains why it holds. Prefer a schema parse or a typed helper, and never write `as unknown as T`. ## Formatting [#formatting] Oxfmt formats with semicolons at print width 80. Run `pnpm format` (root files plus every workspace). A direct `oxfmt .` at the root also rewrites the vendored `packages/ox-config/anti-slop` code, which is out of format scope. # Translations (/agents/translations) The .po catalogs and extracted messages. ## Use extracted messages [#use-extracted-messages] Copy uses `useExtracted` in client components and `getExtracted` on the server. Do not use `useTranslations`. ## Every catalog needs every message [#every-catalog-needs-every-message] An empty `msgstr` renders as blank text in the app. When you add or change copy, fill in the entry in every `.po` catalog under `apps/saas/translations/`. ## The dev server rewrites the catalogs [#the-dev-server-rewrites-the-catalogs] A running SaaS dev server re-extracts messages and can rewrite `en.po` and `nl.po` with entries removed. Before you run tests or commit, check `git diff` on those files. If you did not change copy, restore them with `git checkout HEAD -- apps/saas/translations/en.po apps/saas/translations/nl.po`. # Writing docs (/agents/writing-docs) How to write and update pages in the public docs so they match the shipped product. The public docs site renders the root `docs/` folder. AI agents read the same pages through `/llms.txt`, `/llms-full.txt`, the per-page `.md` URLs and the docs MCP server, so every page has to be accurate on its own. ## Describe shipped behavior from code [#describe-shipped-behavior-from-code] Agents tend to rewrite an old page or copy `PRODUCT.md`, and both drift from the product. Write each page from the source instead: 1. Read the matching `PRODUCT.md` section for intent and vocabulary. 2. Read the feature slice in `apps/saas/features//`: the `*-actions.ts` and `*-queries.ts` files and the component names. 3. Read the route folder under `apps/saas/app/[locale]/(app)/[orgSlug]/` (or `admin/`) for the real paths and tabs. 4. Read the permission entries in `packages/identity/src/rbac/features.ts`. That registry is the source of truth, not `permissions.ts`. 5. Read the API routes under `apps/api/app/api/v1/` and the MCP allowlist `mcpToolDefinitions` in `packages/api-client/src/mcp.ts`. 6. Confirm labels, tabs and flows in the running app with `agent-browser` on `https://app.localhost` and the seeded logins, or read the English strings in `apps/saas/translations/en.po`. Describe only what ships. For a roadmap item, write one line that starts with "Not yet available" instead of prose about what the feature is intended to do. ## Page template [#page-template] Use these H2 sections in this order and leave out the ones that would be empty: 1. Overview: what the feature is and the route where it lives. 2. Key concepts: entities, statuses and lifecycle stages. 3. How-to sections for the main flows, one H2 or H3 per task. 4. Permissions: the `organization.*` or `system.*` keys that gate the feature. 5. Automation and AI: workflow triggers, MCP tools and AI actions. 6. API: the REST resources, with links to the generated `/openapi/...` pages. 7. Related pages. The frontmatter `description` is one specific sentence. `llms.txt` is built from it, so it must say what the page covers. ## Conventions [#conventions] * Prefer plain Markdown over MDX components. `Cards` belongs only on `index.mdx`. Use Markdown tables for reference data. * Link to other pages with root-relative paths such as `/product/quotes`. The docs app is mounted at the root, so never prefix `/docs`. * Every page must be listed in its folder's `meta.json`. `tests/integration/docs/docs-tree.test.ts` fails on orphan pages, missing frontmatter and broken internal links. * When you remove or rename a page, add a redirect in `apps/docs/next.config.ts`. * Follow the repo writing rules: complete sentences, no em dashes or arrow chains in prose, no emojis or exclamation marks, and none of "simply", "seamless", "robust" or "leverage". # Audit Logs (/architecture/audit-logs) Immutable semantic security events, access boundaries, retention, and export workflows. CentraKit audit logs are the product-facing security record for tenant and platform mutations. They are separate from operational telemetry, PostgreSQL/PGAudit diagnostics, and log drains, which remain debugging and infrastructure-observability tools. ## Architecture [#architecture] The domain follows the standard provider-swappable service layers: * `packages/platform/src/audit-log/` defines event schemas, the semantic event catalog, cursor encoding, errors, and `AuditLogService`. * `packages/supabase-platform/src/audit-log/` implements the service against Supabase. * `packages/core` wires request-scoped services and trusted actors; apps never import the provider directly. Coverage is hybrid. Database triggers capture registered table mutations as a durable baseline, while application mutation adapters emit semantic events with business-level summaries, targets, outcomes, request IDs, correlation IDs, and idempotency keys. Auth and webhook boundaries use the same semantic model. Audit recording must not add user-visible latency or turn a successful business mutation into a failure. Request paths schedule best-effort recording after the primary mutation; durable workflows record their own outcomes. Trigger capture remains the fallback for registered tables. ## Data Safety And Immutability [#data-safety-and-immutability] Audit rows are append-only. Application roles receive no update or delete grants; retention cleanup runs through privileged database functions. Safe metadata is limited to 4 KiB and rejects credential, cookie, password, secret, session, and token-like keys. Restricted request context is stored separately and excludes secrets and tokens. Organization views mask platform-admin actor identity. Revealing restricted fields requires a dedicated permission and writes another semantic audit event. ## Reading And Routes [#reading-and-routes] Overview lists use offset pages with an exact filtered count so the table can share the same sort, filter, and pagination chrome as other hubs. Export workflows still walk stable keyset cursors over `(occurred_at, id)`. * Organization list/detail: `/{orgSlug}/audit-log` and `/{orgSlug}/audit-log/{eventId}` * Platform list/detail: `/admin/audit-log` and `/admin/audit-log/{eventId}` Organization reads are always scoped by the organization resolved from the route. Platform reads may span organizations. The navigation entries and page guards require `OrgPerm.auditLog.view` or `SysPerm.auditLog.view`. Permissions are split by operation: * `organization.audit-log.view|export|reveal` * `system.audit-log.view|export|reveal` ## Exports And Retention [#exports-and-retention] CSV exports are asynchronous Workflow jobs. Requests normalize a bounded date range, then the workflow reads cursor pages, applies row and byte limits, writes a private storage object, and exposes it for 24 hours. Restricted fields are available only for authorized platform exports. Export request, completion, failure, download, and reveal operations are audited. A daily workflow deletes expired export objects before marking jobs expired and purges audit events in bounded batches. Default retention is 1 day for Starter, 7 days for Pro, 90 days for Business, and 365 days for Enterprise. Enterprise subscriptions may set a platform-managed organization override. # Authentication (/architecture/authentication) Supabase Auth ownership, session posture, OAuth linking, and deferred controls. CentraKit uses Supabase Auth for user identity. Application code validates inputs, maps provider errors to domain errors, and avoids returning password material, while Supabase owns password hashing, access-token issuance, refresh-token rotation, and browser/server session cookie format. ## Current Security Posture [#current-security-posture] * Access JWTs are configured for 15 minutes locally via `supabase/config.toml`; mirror this in hosted Supabase settings. * Refresh tokens are Supabase-managed and rotated on use. Local config uses a seven-day session timebox to cap the refresh chain. * Email confirmation is required for email/password signups. * Password hashing is handled by Supabase Auth. Local seed users use `gen_salt('bf', 12)`. * Session cookies are managed by `@supabase/ssr` with Supabase-compatible options. * Logout calls `supabase.auth.signOut()`. Immediate app-owned token blacklisting is deferred. * SaaS application routes are protected at the proxy/router level. Link and Partial Prefetching requests still receive locale rewrites, but skip session cookie refresh so a prefetch cannot consume the single-use refresh token. * API Route Handlers use the policy-driven `withApiRoute` adapter. User routes accept either a refreshed SSR cookie or an OAuth bearer; a present but invalid bearer never falls back to cookies. Cron and provider-webhook policies retain their own credential contracts. * The adapter composes `@supabase/server` after credential normalization. Its RLS-scoped Supabase client is always safe by default, `supabaseAdmin` stays lazy and request-memoized, and direct Postgres clients are available only on routes that explicitly opt into `postgres: "scoped" | "admin" | "both"`. ## JWT Claims And Signing Keys [#jwt-claims-and-signing-keys] Use `supabase.auth.getClaims()` for server-side checks that only need verified identity claims, such as authenticated state, current user ID (`sub`), current email, session ID, or coarse role hints. Use `supabase.auth.getUser()` only when a flow needs the fresh Auth user record, linked identities/providers, email confirmation state, or other Auth-server metadata. Use `getSession()` only when raw access or refresh tokens are needed. Hosted Supabase projects should use the new JWT signing keys system with an asymmetric key, preferably ES256. `getClaims()` can then verify tokens with cached JWKS/WebCrypto instead of putting the Auth server in the hot path. Before rotating away from the legacy JWT secret, confirm no app, API, Edge Function, or integration verifies tokens with the legacy shared secret directly. After rotation, wait at least one access-token lifetime plus safety margin before revoking the previously used legacy key; local config uses a 15-minute JWT lifetime. `public.custom_access_token_hook` enriches access tokens with `system_roles`, `organization_roles`, and compact permission sets (`system_permissions` and `organization_permissions`) for UI and access hints. An organization where the user holds the full catalog is stored as the `["*"]` sentinel, and system admins omit `organization_permissions` entirely. Keep the hook lean because it runs when Supabase issues or refreshes tokens, claims are stale until the next token refresh, and large claims bloat the chunked SSR cookies, which can bounce post-login redirects back to `/login`. See [RBAC](/architecture/rbac) for how the claims are read. ## Google OAuth [#google-oauth] The SaaS login and register forms start Google OAuth through Supabase Auth. The hosted Supabase project owns the Google provider credentials; Vercel environment variables may mirror those values but do not configure the hosted Supabase provider by themselves. Supabase owns account linking. When a confirmed email/password user later signs in with Google and Google returns the same verified email address, Supabase automatically links the Google identity to the existing user. Do not add a public email-existence lookup or app-owned merge path. For users created through Google first, add email/password login only after the user has proved email ownership and has an active Supabase session. Use Supabase recovery or `updateUser({ password })` through the app's existing auth service methods. ## Local OAuth [#local-oauth] For Google Cloud OAuth clients, use the Supabase callback URL as the authorized redirect URI. Local Google OAuth uses Portless aliases under `localtest.me` because Google rejects `.localhost` subdomain callbacks. Register this callback for local testing: ```text https://supabase.localtest.me:1355/auth/v1/callback ``` Then start the OAuth dev target: ```bash pnpm dev:oauth ``` ## Email Confirmation Flow [#email-confirmation-flow] Signup and other token-hash emails (confirmation, recovery, magic link) link to the SaaS app's `/auth/confirm` route, which calls `verifyOtp({ token_hash, type })`. This avoids the PKCE `code` exchange at `/auth/callback`, which requires the verifier cookie set when `signUp` ran and breaks when the email is opened in a different browser, profile, or in-app webview. Locally, the confirmation template lives at `supabase/templates/confirmation.html` and is registered in `supabase/config.toml` under `[auth.email.template.confirmation]`. The `` href uses `{{ .RedirectTo }}&token_hash={{ .TokenHash }}&type=signup`, where `RedirectTo` is the `emailRedirectTo` URL the app sets when calling `signUp` (`https://app.localhost/auth/confirm?redirectPath=...`). After editing the template or config, restart Supabase: `pnpm exec supabase stop && pnpm supabase:start`. For hosted Supabase projects, mirror this in the dashboard: * On the **URL configuration** page under **Authentication**: * Set **Site URL** to the production SaaS origin (`https://app.`). * Add `https:///auth/confirm` (and any preview hosts you use) to **Redirect URLs**, alongside the existing `/auth/callback` entries kept for OAuth. * In the **Confirm sign up** template under **Authentication** and **Email templates**, paste the same HTML as `supabase/templates/confirmation.html` (adjust copy only as needed). The recovery and magic link templates still use the default `/auth/callback` link until those flows are migrated. OAuth must keep using `/auth/callback` because providers return a `code`, not a `token_hash`. ## Deferred Controls [#deferred-controls] The repo does not currently maintain app-owned login counters, account lockout tables, unlock tokens, or a token blacklist. Revisit those controls if Supabase-native limits, short JWTs, and generic errors are not enough for the deployed threat model. ## Resend Boundary [#resend-boundary] Use Supabase's built-in email flows for confirmation and password recovery. If a future security email is not covered by Supabase, add a small Resend boundary with `RESEND_API_KEY`, a verified transactional sending domain, idempotency keys, and disabled click/open tracking for sensitive transactional email. # Bundling and file tracing (/architecture/bundling-and-file-tracing) Keep runtime assets available in Next.js and Workflow server bundles. Next.js uses `@vercel/nft` during production builds to emit an `.nft.json` file for every server route. Each trace lists the files that must be available when that route runs in a Vercel function or another minimal deployment. ## Monorepo tracing root [#monorepo-tracing-root] Every CentraKit app passes the repository root to the shared config factory as `outputFileTracingRoot`. This is explicit even though Next.js can infer the same root from the workspace lockfile. The explicit root is required for two repository conventions: * pnpm stores real package files under the repository-level `node_modules/.pnpm/` directory. * Some apps read generated or authored files from sibling apps at runtime. Do not move the root back to an inferred default. A lockfile or build invocation change could otherwise make valid workspace dependencies fall outside the trace. ## When to add an include [#when-to-add-an-include] Normal static `import`, `require`, and recognizable `fs` calls should be left to nft. Add `outputFileTracingIncludes` only when runtime code deliberately prevents static discovery, or when a deployment treats the file separately from the server function. Current examples: * **Generated OpenAPI spec:** the API MCP route reads `apps/api/public/openapi.json` at request time. Vercel serves `public/` separately, so `/api/mcp` explicitly includes the file in its function trace. * **Cross-app docs sources:** the docs app reads the API spec and app `CHANGELOG.md` files outside `apps/docs`. Its server traces include those sibling files. * **Barcode WASM:** the zxing route assembles the module specifier from string parts so Turbopack serves the Emscripten binary as bytes instead of compiling it. That also hides the file from nft, so the route explicitly includes `zxing_reader.wasm`. * **PDF fonts:** `@workspace/quote-pdf` resolves its Inter TTFs through a runtime `createRequire` because Turbopack rewrites `new URL(..., import.meta.url)`. The quote, invoice, order, and purchase-order PDF routes, the quote/invoice export routes, and the Workflow flow route include the fonts plus the package `package.json` used for the `exports` lookup. Include keys are route globs and include values are resolved from the app directory, not the monorepo root. Keep both as narrow as possible. Fully static routes do not produce server trace files, so includes do not apply to them. ## PDF and Workflow bundles [#pdf-and-workflow-bundles] `@react-pdf/renderer` and `qrcode` stay in `serverExternalPackages`. Their runtime require graphs are visible to nft, and the monorepo tracing root lets nft follow those graphs into pnpm's store. Do not add a broad PDF package include unless a build trace proves a required asset is missing. Workflow v5 owns transpilation for `workflow` and `@workflow/*`; do not add those packages to `serverExternalPackages`. `withWorkflow` generates `app/.well-known/workflow/v1/flow/route.js`, which Next.js traces like any other server route. Step-only imports therefore belong in the flow trace, including React PDF when an invoice or quote step renders a document. Keep `app/.well-known/workflow/**` in `apps/saas/turbo.json` and `apps/api/turbo.json` build outputs (Workflow SDK [Turborepo caching](https://workflow-sdk.dev/docs/getting-started/next#turborepo-caching)). Local builds resolve `@workflow/world-local`; Vercel builds resolve `@workflow/world-vercel`. Because both services ship Workflow consumers in one Vercel project, pin distinct `WORKFLOW_QUEUE_NAMESPACE` values (`saas` / `api`) so queue topics become `__saas_wkf_workflow_*` and `__api_wkf_workflow_*` instead of colliding on `__wkf_workflow_*` / consumer `default`. ## Trace size hygiene [#trace-size-hygiene] Only exclude a package when the server can never execute it. Streamdown performs syntax highlighting during SSR, so Shiki language and theme assets are real server dependencies and must not be excluded. CentraKit instead overrides `@streamdown/code` to the catalog Shiki version. This prevents pnpm and nft from carrying two complete grammar bundles while preserving server-side highlighting. ## Verification [#verification] Build the traced apps and run the regression guard: ```bash pnpm test:tracing ``` The task builds all five Next.js apps, then checks: * every app reports the repository as `outputFileTracingRoot`; * the API and docs runtime data files are present; * the zxing WASM binary is present; * both a PDF route and the Workflow flow route contain React PDF dependencies; * the PDF routes and the Workflow flow route contain the Inter PDF fonts; * the SaaS trace contains one Shiki language-bundle version. The guard lives in `tests/integration/build/output-file-tracing.test.ts`. It resolves every path relative to its `.nft.json` file, matching Next.js trace semantics. For manual investigation, inspect: * `apps//.next/required-server-files.json` for the serialized tracing configuration; * `apps//.next/server/app/**/route.js.nft.json` for route dependencies; * `apps/saas/.next/server/app/.well-known/workflow/v1/flow/route.js.nft.json` for the combined Workflow step bundle. # Cache Components And Streaming (/architecture/cache-components-and-streaming) Next.js static shells, Suspense islands, access gates, and cache tag conventions. The SaaS app uses Next.js Cache Components and a static-first app shell. The goal is perceived instant navigation: persistent chrome, static page shells, and dynamic work confined to small Suspense islands. ## Scopes [#scopes] 1. **App providers + chrome:** `app/[locale]/(app)/layout.tsx` owns persistent providers (`SidebarProvider`, command-palette state, viewport sync) and one `AppShell`. Session chrome streams into a sidebar island; page children stay mounted as siblings. 2. **Tenant islands:** `[orgSlug]/layout.tsx` runs slug membership and brand chrome in Suspense. Navigation presentation (`organization` / `customer` / `admin`) is client-side and independent of authoritative access gates. 3. **Page shell:** the page default export stays synchronous and mostly static. 4. **Suspense islands:** dynamic data and access checks (`requireOrganizationAccess`, `requireCustomerPortalAccess`, `requireSystemAccessAdmin`) run inside the smallest useful server component boundary, never in shell layouts. ## Rules [#rules] * Keep layouts synchronous unless they own dynamic chrome. * Treat route `page.tsx` files as synchronous compositors. Pages arrange static shell, error boundaries, Suspense, and feature panels; they do not own domain reads. * Put one async server feature panel behind one Suspense boundary. * Prefer sibling boundaries for independent fetches. * Use shape-matched skeletons to avoid layout shift. * Run `requireOrganizationAccess()` and `requireSystemAccessAdmin()` inside page islands, not layouts. * Keep feature panel skeletons beside the panel they mirror. Route-level `loading.tsx` files should reuse those feature skeletons for cold navigations instead of defining a second fallback shape. * Follow `nextjs-app-architecture` v1.3.9. Page Suspense is primary; shaped `loading.tsx` is a CentraKit exception for hard/cold nav and Partial Prefetch chrome (not next-beats' zero-loader model). A page `` cannot animate the swap out of `loading.tsx`. * Cached query arguments are the cache key: normalized primitives or a fully-normalized filter object produced before the `'use cache'` call. Never clamp or default inside the cached function, and never pass raw `searchParams`. * Keep `data-pending` on the pending node. Do not add `group-has-data-pending:` on wrappers that also contain Links; name the reason (`data-filtering`, `data-saving`) if a subtree has more than one pending source. * Dense skeleton grids use a non-animated `Skeleton` (`animated={false}`); keep the pulse for a handful of bars. ## Feature Panel Ownership [#feature-panel-ownership] CentraKit uses singular feature-sliced folders under `apps/saas/features//`: * `apps/saas/features//-queries.ts` owns cached, server-only reads. * `apps/saas/features//-actions.ts` owns `'use server'` mutations (not route-colocated `app/**/actions.ts` or `lib//actions.ts`). * `apps/saas/features//components/` owns domain UI, request access checks, service orchestration, and colocated skeletons. * `apps/saas/components/` holds only shared app chrome (sidebar, breadcrumbs, page-shell, kanban presentation, `ui/`). * `apps/saas/app/` pages stay sync shells that compose feature components inside Suspense; they contain no domain logic. * Panels use `@workspace/core` services and explicit subpaths from bounded contract packages. They never import provider implementations such as `@workspace/supabase-work` and should not copy direct database-access patterns from example apps. * Client components stay leaf-level and import their feature's Server Actions directly. Generic design components may still accept `action` or `*Action` callback props when they own reusable async coordination. Feature folders represent user-recognizable domains, not only aggregate roots. `tag` remains a standalone feature because Tags have dedicated list/create/detail routes, canonical `tag-queries.ts` / `tag-actions.ts` / `tag-cache.ts` contracts, and consumers across catalog, customer, product, and task features. Folding those contracts into one consumer would invert their ownership and make the other domains depend on an unrelated feature. ## DataGrid Rendering Profiles [#datagrid-rendering-profiles] Choose one profile before composing a list. The profile describes ownership and streaming behavior; it is not a set of cosmetic component flags. 1. **Canonical hub:** A standalone collection route. Its synchronous page owns the static `PageShell`, title, and Suspense boundary. The feature panel owns the `DataGrid` and uses `DataGridListFrame` for the standard toolbar, scroll area, table, pagination, empty state, and bulk-action composition. 2. **Embedded:** A grid inside a detail section, tab, or dashboard panel. The parent surface owns its heading, padding, and outer border. The grid may use `DataGridListFrame` for the same internal toolbar/table/pagination rhythm; configure `DataGridContainer` at the embedding boundary instead of adding visual-mode booleans to the frame. 3. **Server-driven:** A canonical hub or embedded grid whose sorting, filters, or pagination are backed by a server query. Keep that state stable across renders, set the authoritative row count on `DataGrid`, and pass the server-aware pagination control through `DataGridListFrame.pagination`. Server-driven is a data-ownership modifier, not a separate visual frame. 4. **Specialized:** A grid with materially different interaction or layout requirements, such as hierarchy, inline editing, grouped rows, a file browser, or a toolbar shared with another view. Compose the narrow ReUI `DataGrid` primitives directly and document the reason near the component. Do not grow `DataGridListFrame` with one-off booleans to absorb it. ### PPR instant-navigation contract [#ppr-instant-navigation-contract] * A hub route's `page.tsx` remains synchronous. It renders the static page shell immediately and places only the data/access feature panel behind a shape-matched Suspense fallback. * The final grid and fallback reserve the same title, toolbar, table, and pagination regions so streamed data does not shift the page. * URL-backed server state is read inside the feature island. Do not await `params`, `searchParams`, cookies, headers, or uncached data in the page shell merely to configure a grid. * Keep controlled grid state stable: disable automatic page-index resets, use stable pagination change handlers, and memoize column visibility so streaming and navigation do not create render loops or reset the user's place. * Embedded grids inherit the nearest useful parent boundary unless they fetch independently; independent grids use sibling boundaries. Specialized grids are not exempt from the same static-shell and shape-matched-fallback rules. ### Remembered list state [#remembered-list-state] List filters, sort, and rows-per-page are remembered per pathname in `localStorage` (`packages/ui/src/lib/list-state-storage.ts`). Nothing is stored in the database or in cookies. * The URL stays the source of truth. `useDataGridUrlState` and `useDataGridQueryFilters` remember automatically (opt out with `remember: false`). Feature nuqs surfaces call `useRememberedQueryStates` with a `*RememberedSearchParams` subset exported beside their `*-search-params.ts`. Grids with local React sort or filter state use `useRememberSorting` / `useRememberFilters` with a unique `id`. * Restoring runs in an effect after hydration and only when the URL has none of the remembered keys. Server, prerender, and prefetched output never read storage, so the static shell and Partial Prefetch cache stay valid. * Routes revealed from Next's hidden `` reconnect with stale state, so the "URL has a value" check reads `location.search`, not React state. An in-flight navigation can drop the restored shallow URL update; the hook re-applies it (bounded) until the keys appear in the URL and never saves the empty values in between. * Links and prefetch keep pointing at clean URLs. Do not rewrite hrefs from storage. Server-driven grids restore through their nuqs `startTransition` (`history: "replace"`), so cached rows stay painted with pending feedback until the filtered island arrives. * Search text (`q` / `search`), page number, column visibility, navigation keys (Files scope/drive/folder, analytics tab, agenda/planning date), and view choices are never remembered. Signing out clears every remembered entry. ## Caching [#caching] Use `"use cache: private"` for cookie-derived data when cacheable, and pair cached readers with matching `updateTag(...)` (Server Actions) or `revalidateTag(tag, { expire: 0 })` (Route Handlers) calls in mutations. App access and UI permission hints are loaded through one private cached `getCurrentAppAccess()` payload in the authenticated app layout, then reused by guards, the sidebar, the command palette, and feature-panel permission flag helpers. Private cache entries live only in browser memory. They are never stored on the server, do not survive a reload, and re-execute on every server render. For private scopes, only `cacheLife`'s `stale` value affects the client router window; `revalidate` and `expire` are inert until a read moves to a shared cache. The built-in presets `minutes`, `hours`, `days`, and `max` all use the same 5-minute `stale`, so switching among them does not lengthen private caches. Use three profiles: | Profile | `stale` | When | | ----------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `reference` | 15 minutes | Slow-changing tenant data whose every write path is a SaaS Server Action (customers, products, quotes, invoices, users, teams, roles, templates, knowledge, integrations, admin catalogs) | | `minutes` | 5 minutes | Frequent edits, or writers outside `apps/saas` that cannot invalidate the SaaS cache (tasks, agenda, files, workflows, billing, usage, inbox) | | `seconds` | 30 seconds | Chat threads, notification badges, unread inbox counts | `reference` is defined in `packages/next-config` (`cacheLife.reference`). Tenant list tags are `organizationId`-scoped builders (for example `organization-customers:{organizationId}`); numeric detail tags that can collide across tenants also carry the organization id. Never use public `"use cache"` for cookie-dependent data. ## Prefetch and request-time suspension [#prefetch-and-request-time-suspension] Prefer `await io()` from `next/cache` over `await connection()` when an island must exclude synchronous nondeterministic values (`new Date()`, `Math.random()`, …) from the static shell. `connection()` blocks prefetches; `io()` suspends like any other async boundary and stays compatible with `partialPrefetching` / `cachedNavigations`. Most feature data islands do not need either call when they only await cookie/header reads or async queries. Caveat: `requireOrganizationAccess()` / `getCurrentAppAccess()` run inside `"use cache: private"`, so cookies stay inside that cache scope. Islands that then call `new Date()` (agenda/planning when `?date=` is omitted) still need `await io()`. Keep `connection()` only when rendering must wait for a real user request (public token pages, invite accept, similar one-shot handlers). ### Island payloads [#island-payloads] Cached and prefetched overviews, hub tabs, and details must commit real content instead of a destination skeleton when the private cache already has the island. Skeletons remain correct on hard loads, `next dev`, and cold/uncached data. Copy `getProductsOverviewPayload` / `ProductsOverviewDataLoader`: 1. Add `getXPayload(orgSlug, …)` in that feature’s `*-queries.ts` (`"use cache: private"`, matching existing `cacheLife` / `cacheTag`s). Do not call `notFound()` or `redirect()` inside the cache. 2. Call `readOrganizationPayloadAccess` (or the portal/admin snapshot helpers) only from that cached function. Return `null` on miss or denied access. 3. Loader: `const payload = await getXPayload(…); if (!payload) { await require…(); notFound() }`. 4. Mark loaded UI with `data-instant-content="…"`, never the `isLoading` skeleton. 5. The happy path must not await `requireOrganizationAccess`, `requireCustomerPortalAccess`, `requireSystemAccessAdmin`, `getCurrentClaimsOrNull()`, or `io()`. Use `getAppShellAccess().authenticatedUserId` inside the payload instead of claims. Call `io()` only when the island itself needs request-time `Date`. Keep nested `get*` helpers so `updateTag` still busts the payload. Use one payload function per Suspense sibling, not one mega-payload for a whole detail page. Unbounded rows stay `IntentPrefetchLink`; after hover or a prior visit the island must be able to commit. Guest portal quote and invoice details follow the same contract without a membership gate: cache `getQuoteForPortal` / `getInvoiceForPortal` plus `getAppShellAccess`, return `null` on miss, then `notFound()`. Nested Stripe client-secret fetches stay outside that payload. ## Proxy Boundary [#proxy-boundary] The proxy enforces session routing and locale rewrites only. Keep proxy work minimal so streaming and Cache Components stay healthy. Do not inject an organization slug header, and do not resolve organization membership there; tenant identity is URL `params.orgSlug` resolved inside Suspense islands. Locale rewrites still run on Link / Partial Prefetching requests (`next-router-prefetch`, `next-router-segment-prefetch`, `Purpose: prefetch`). Session refresh (`getClaims()` / cookie rotation) does not: refresh tokens are single-use, and a prefetch racing the click can invalidate the session. Unauthenticated redirects and admin prefix checks run on the real navigation. ### Not-found status codes [#not-found-status-codes] Unknown or non-member `orgSlug` values are rejected in the `[orgSlug]` layout via `OrganizationSlugAccessGate` / `requireOrganizationSlugAccess` in a **non-blocking** Suspense island beside `AppShell`, so cold tenant documents can paint chrome immediately. Feature permission failures stay in page Suspense islands: | Failure | Where it is decided | HTTP status | | ---------------------------------------------------------- | ---------------------------------------------------------------------- | ----------------------------------------------------------------- | | Unknown or non-member `orgSlug` | `OrganizationSlugAccessGate` Suspense island in `[orgSlug]/layout.tsx` | 404 before the shell streams, otherwise 200 with the not-found UI | | Missing organization permission for an existing membership | `requireOrganizationAccess({ permission })` inside a Suspense island | 404 before the shell streams, otherwise 200 with the not-found UI | `notFound()` only produces an HTTP 404 while the response status is still open. Once `AppShell` has started streaming the status is committed, so in-island `notFound()` calls may render the not-found UI over HTTP 200. That remains an accepted Cache Components tradeoff for tenant membership and feature gates. Do not move those checks into the proxy. Page-level `notFound()` and unmatched URLs under a valid shell segment render that segment's `not-found.tsx` inside `AppShell`. Layout-level `notFound()` (unknown or non-member `orgSlug`) still uses the locale 404 and unmounts the shell. ## Instant Navigation triad [#instant-navigation-triad] Cache Components, Partial Prefetch, and View Transitions are one contract. Instant-shell references are Agenda (`agenda-hub`), Planning (`planning-hub`), and Files (`files-hub`). Do not copy public `'use cache'` from calendar demos; tenant reads stay `"use cache: private"`. ### Prefetch tiers [#prefetch-tiers] | Tier | When | What it warms | | -------------------- | ---------------------------------------------------------------- | --------------------------------------------------------------------------------------- | | Default `` | Ordinary same-origin navigation | One shared App Shell per route | | `IntentPrefetchLink` | Unbounded collections (mini-cal days, DataGrid rows, Files rail) | Shell first; upgrades to per-link content on pointer, keyboard focus, or `onTouchStart` | | `prefetch={true}` | Bounded, URL-keyed destinations we want resolved before click | URL (date/view/tab) plus the cached slice behind it; uncached data still streams | Keep `prefetch={true}` off every mini-calendar day and off unbounded DataGrid rows. The architecture-guard budget in `tests/integration/cross-package/saas/architecture-guards.test.ts` is the allowlist. Sidebar eager prefetch is the same rule: org Dashboard, Notifications, Inbox, Chats, Tasks, Agenda, Quotes, Invoices, Customers, and Products (10); portal Dashboard, Quotes, Invoices, and Assets; admin Dashboard. Planning, Files, payments, expenses, and collapsible children stay default Links so the sidebar RSC fan-out stays ≤ 16 (`apps/saas/tests/navigation-prefetch.test.ts`). Org-switcher rows use `IntentPrefetchLink`. Navigation-mode homes and breadcrumb parent hubs that match those eager destinations use `prefetch={true}`. ### Instant shells [#instant-shells] Hard navigation and Partial Prefetch should commit chrome before the data island. Share one sync shell between the page and `loading.tsx`: * **Agenda:** `AgendaHubShell` (left rail + mobile toolbar) wraps a Suspense island for `AgendaGridData`. Period title and the schedule use named View Transitions (`agenda-period-title`, `agenda-schedule`); the rail uses `default="none"`. Date/view hops are real Links (`?date=` / `?view=`) so prefetch can resolve the period. Toolbar prev/today/next and the view switcher use `prefetch={true}`; mini-cal days use `IntentPrefetchLink`. * **Planning:** `PlanningHubShell` (`data-instant-shell-marker="planning-hub"`) is the page/`loading.tsx` fallback. Cold loads paint `PlanningGridPlaceholder`; a warm private-cache hit commits `data-instant-content="planning-grid"` with no placeholder flash. Toolbar prev/today/next and the view switcher are real Links (`prefetch={true}`) like Agenda; mini-cal days use `IntentPrefetchLink`. Sidebar Planning stays off the eager prefetch set. * **Files:** `FilesHubShell` (rail + main column) is the page/`loading.tsx` fallback. Scope and folder hops use `IntentPrefetchLink`. Do not eager-prefetch every folder row. * **CRUD hubs:** Tab chrome already lives in `(tabs)/layout.tsx` outside page Suspense. Do not blanket-split toolbar vs grid. Add a nested Suspense only when a hub still flashes a full-frame skeleton. Mark instant chrome with `data-instant-shell-marker` and loaded islands with `data-instant-content` so e2e (`hub-instant-navigation.spec.ts`) can assert shell-first paint. ### View Transitions checklist [#view-transitions-checklist] * Named VT / `default="none"` on stable chrome (sidebar, agenda rail, Files rail, hub toolbars). * Crossfade or directional slide only the changing island (agenda schedule, Files browser). * List to detail: `IntentPrefetchLink` + `transitionTypes={["nav-forward"]}` and `text-morph` on titles (`SharedElementTransition`). * Detail to hub: `nav-back`. Wrap detail pages in `DirectionalPageTransition`. * No blanket page VT and no `SuspenseReveal` on every stream. ### Cache tags [#cache-tags] Pair private cache readers with `updateTag` (SaaS Actions) or `revalidateTag(tag, { expire: 0 })` (API writers). Agenda tags the org (`organization-agenda:{id}`), the fetched range, and the UTC Monday week (`organization-agenda-week:{id}:{yyyy-mm-dd}`). Mutations `updateTag` the org plus affected week(s), meaning source and destination on move when both instants are known. # Internationalization (/architecture/i18n) next-intl extraction, locale routing, gettext catalogs, and translation rules. The SaaS app uses `next-intl` with extracted messages and gettext `.po` catalogs. ## Source Locale [#source-locale] English is the source language. UI code writes English messages through extraction APIs and translators fill non-English catalogs. ## APIs [#apis] Use only: * `useExtracted` in synchronous components. * `getExtracted` in async server functions. Do not use `useTranslations` or `getTranslations` in this codebase. ## Catalogs [#catalogs] Translations live under `apps/saas/translations/`. Extraction is enabled in `apps/saas/next.config.ts` via `experimental.extract: true` with `messages.sourceLocale: "en"`. Committed `.po` location comments use path-only references (no line numbers), matching next-intl's default PO codec. Empty `msgstr` values render as blank copy at runtime. Keep Dutch translations complete and run catalog tests after extraction. ## Locale Routing [#locale-routing] SaaS uses `localePrefix: "never"`: locale never appears in the URL. Preference is stored in the `profile-locale` cookie; the proxy rewrites onto the internal `app/[locale]/...` segment so `next/root-params` can resolve the locale. Showcase uses the same prefix mode. Locale codes (`en`, `nl`) are reserved and cannot be organization slugs. Unknown or invalid locale values resolve to the default English locale instead of returning 404. Locale switchers should use native names such as English and Nederlands. ### Root params and Cache Components [#root-params-and-cache-components] CentraKit follows the [next-intl Partial Prefetching e2e sample](https://github.com/amannn/next-intl/tree/main/e2e/partial-prefetching/src) and the [next/root-params blog guidance](https://next-intl.dev/blog/nextjs-root-params) for Cache Components, not the classic setup docs that still recommend `setRequestLocale` for static rendering. There is deliberately **no** pass-through `app/layout.tsx`. The document root is `app/[locale]/layout.tsx` so `locale` stays a root param. The `[locale]` layout enumerates every locale with `generateStaticParams`, resolves `lang` via `await getLocale()`, and mounts `NextIntlClientProvider`. It does **not** call `setRequestLocale` or validate `params.locale`; locale resolution belongs in `getRequestConfig`. `getRequestConfig` in `apps/saas/lib/i18n/request.ts` mirrors the sample shape (`import * as rootParams`, optional `locale` override, then `rootParams.locale()`). Outside a route context (Server Actions / Route Handlers), `next/root-params` is unavailable; catch the error, call `unstable_rethrow(error)` first so Next.js prerender and control-flow sentinels are preserved, then fall back to next-intl's `requestLocale` (proxy-supplied header). Do not swallow hanging fallback-param signals with a broad catch, because that converts them into header reads and blocks App Shells / Partial Prefetching. Invalid locale values still resolve to `defaultLocale` rather than `notFound()`. Do not register `orgSlug` in `next/root-params`. Tenant slug stays URL data resolved inside Suspense islands. ### Global not-found [#global-not-found] `experimental.globalNotFound` is enabled in shared Next config. `apps/saas/app/global-not-found.tsx` handles unmatched URLs that never enter the `[locale]` tree (for example paths the i18n proxy does not rewrite). It returns a full document via `AppDocument`, supplies the default English catalog to `NextIntlClientProvider`, and reuses the same `NotFoundContent` as `app/[locale]/not-found.tsx`. In-app misses keep `AppShell`. Each shell segment (`[orgSlug]`, `admin`, `organizations`, `profile`, `feedback`) has its own `not-found.tsx` and a `[...rest]` catch-all so unmatched URLs stay inside the sidebar. `app/[locale]/not-found.tsx` remains the fallback for requests that never enter a shell segment and for `notFound()` thrown from a shell layout itself (unknown org slug). ### Sitemaps [#sitemaps] Public SEO sitemaps live on marketing, docs, and showcase, not on SaaS. SaaS is an authenticated product surface with `localePrefix: "never"` (locale in the `profile-locale` cookie, one URL per route), so a GrahamQuan-style multi-locale sitemap with `hreflang` alternates does not apply. ## Message IDs [#message-ids] Reuse message IDs when meaning stays the same. Create a new ID when meaning changes so translators get a clear signal. ## Translator descriptions [#translator-descriptions] Every `t({ id, message, description })` call must include a concrete `description`. That text becomes the `#.` comment in the `.po` catalog and is what AI/human translators use for context. Good descriptions name: 1. Which screen or feature the string belongs to 2. What UI role it plays (button, column header, placeholder, toast, aria-label, watermark) 3. Disambiguation for short or overloaded English words Examples: * `Clear` on an invoice date field: "Button that clears the picked invoice date; not a filter reset" * `tag` in the invoice editor: "Inserts a merge token placeholder; not the Tags feature" * `unit` on a line item: "Unit of measure suffix, for example hour or piece" * `Concept` on a draft invoice: "Draft watermark standing in for the invoice number" Avoid one-word descriptions such as `label` or `col`. ## Dutch terminology [#dutch-terminology] Prefer natural SaaS Dutch over literal calques. Keep these glossary terms stable: | English | Dutch | | --------------------- | ---------------------- | | Resources (nav hub) | Middelen | | Access | Toegang | | Roles & Permissions | Rollen & Rechten | | Customers | Klanten | | Contacts | Contactpersonen | | Tags | Tags | | Templates | Template(s) | | Knowledge | Kennis | | Customer assets | Assets | | Workflow | Workflow | | Search / Clear search | Zoeken / Zoeken wissen | | Notes (free-form) | Notities | | Location (address) | Locatie | | Size (file) | Grootte | After extraction, fill every blank `msgstr` in `nl.po`. Empty Dutch strings render as blank UI. Run `tests/integration/saas/i18n-catalog.test.ts` before handoff. # Monorepo (/architecture/monorepo) CentraKit Turborepo app and package topology. CentraKit is a Turborepo monorepo using pnpm workspaces. Internal packages use the `@workspace/*` alias. ## Apps [#apps] | App | Framework | Purpose | | ---------------- | ------------------- | ------------------------------------------------------------ | | `apps/saas` | Next.js | Auth, dashboard, customer UI, organization UI, and admin UI. | | `apps/api` | Next.js | REST API and MCP server. | | `apps/docs` | Fumadocs on Next.js | Public documentation site generated from root `docs/`. | | `apps/marketing` | Next.js | Marketing website. | | `apps/showcase` | Next.js | Component and product UI showcase. | | `apps/cli` | Node.js | `centrakit` CLI. | ## Packages [#packages] ### Bounded contracts [#bounded-contracts] | Package | Purpose | | -------------------------- | ---------------------------------------------------------------------------------------------------------------- | | `@workspace/foundation` | List/API primitives, validation, env contracts, opaque ID helpers. | | `@workspace/identity` | Auth, users, and RBAC contracts. | | `@workspace/tenant` | Organizations, teams, and tags. | | `@workspace/crm` | Customers and collaboration contracts. | | `@workspace/catalog` | Products, inventory, customer assets, and files. | | `@workspace/work` | Tasks and agenda. | | `@workspace/people` | Employees, HR settings, schedules, time, time off, approvals, equipment, onboarding, hiring and people insights. | | `@workspace/commerce` | Quotes, invoices, orders, shipments, promotions, purchase orders, expenses, templates, and document primitives. | | `@workspace/conversations` | AI settings, chat, channels/inbox, agent runs, knowledge, and memory. | | `@workspace/automation` | Workflows and integrations. | | `@workspace/platform` | Billing/usage, audit log, notifications, analytics, settings, and the command center contract. | | `@workspace/portal` | Customer-portal aggregate read models. | ### Wiring and providers [#wiring-and-providers] | Package | Purpose | | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `@workspace/core` | Request context, auth, RBAC, and audit helpers (no all-services bag). | | `@workspace/core-*` | Domain-scoped request factories (`core-tenant`, `core-crm`, `core-catalog`, `core-work`, `core-people`, `core-commerce`, `core-conversations`, `core-automation`, `core-platform`, `core-portal`). | | `@workspace/core-ai-runtime` | Server runtime for assistant chat and specialist streams, AI approval gates, and the agent chat workflows. | | `@workspace/core-workflow-runtime` | Workflow graph dispatcher, scheduler, lifecycle nodes, and the commerce and order event reactors. | | `@workspace/core-command-center` | Command center record search and commands, composed from several `core-*` factories. | | `@workspace/supabase-runtime` | Clients, generated DB types, shared provider primitives. | | `@workspace/supabase-*` | Bounded Supabase implementations aligned to contract packages, including `supabase-people`. | ### Feature and integration packages [#feature-and-integration-packages] | Package | Purpose | | ------------------------------ | --------------------------------------------------------------------------------------------- | | `@workspace/ai` | AI Gateway models, assistant tools, prompts, knowledge retrieval, memory, and AI suggestions. | | `@workspace/inbox` | Chat SDK channel adapters, inbound channel webhook jobs, and the inbox agent tools. | | `@workspace/email` | Resend client, inbound email parsing, and the React Email templates. | | `@workspace/stripe` | Stripe client, billing and usage providers, invoice payments, and webhook verification. | | `@workspace/seo` | Brand defaults, metadata helpers, icons, and JSON-LD components. | | `@workspace/product-pdf` | Product page and product label PDFs with QR codes. | | `@workspace/quote-pdf` | Quote, invoice, and order document PDFs and their fonts. | | `@workspace/business-registry` | KVK and VIES business lookups, in `packages/integrations/business-registry`. | | `@workspace/google-calendar` | Read-only Google Calendar API client and schemas, in `packages/integrations/google-calendar`. | `packages/integrations/` is a folder of packages, not a package itself. `pnpm-workspace.yaml` lists it separately as `packages/integrations/*`. ### Shared app packages [#shared-app-packages] | Package | Purpose | | ------------------------------ | -------------------------------------------------------------------- | | `@workspace/ui` | Shared React components and Tailwind CSS 4 design tokens. | | `@workspace/api-client` | OpenAPI-generated typed API client. | | `@workspace/next-config` | Shared Next.js config factory. | | `@workspace/typescript-config` | Shared TypeScript configuration. | | `@workspace/ox-config` | Oxlint presets, the Oxfmt config, and the vendored anti-slop plugin. | ## Import Boundaries [#import-boundaries] ```text apps/* -> @workspace/core, @workspace/core-*, bounded contracts, @workspace/ui, @workspace/api-client, shared config apps/* -> never @workspace/supabase-* or another app packages/core / core-* -> may import contracts and matching @workspace/supabase-* packages/supabase-* -> may import their contracts + supabase-runtime only; never apps/core/other providers contract packages -> zod/better-result and lower contracts only; never core or providers ``` `pnpm boundaries` enforces these rules through the `tags` in each workspace `turbo.json` and the `boundaries` block in the root `turbo.json`: | Tag | Packages | May depend on | | ------------- | ---------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- | | `contract` | The bounded contract packages, including `people`. | `contract`, `config` | | `integration` | `email`, `stripe`, `seo`, `product-pdf`, `quote-pdf`, `api-client`, `business-registry`, `google-calendar` | `contract`, `config` | | `provider` | `supabase-runtime` and every `supabase-*` package. | `contract`, `config`, `provider` | | `core` | `core`, every `core-*` package (including `core-ai-runtime`, `core-workflow-runtime` and `core-command-center`), `ai` and `inbox`. | `core`, `contract`, `provider`, `integration`, `config` | | `ui` | `ui` | `config` and `@workspace/foundation` | | `app` | Everything under `apps/`. | Anything except another `app` | The `app` tag does not block `provider` packages by itself. Apps stay off Supabase implementations because no app lists a `@workspace/supabase-*` package in its dependencies. Contract packages expose explicit subpaths (`./schemas`, `./service`, `./errors`, domain roots). They do not expose a runtime `.` barrel that pulls Zod into client graphs. Keep the contract graph acyclic. `identity` and `tenant` build on `foundation`. CRM depends on identity and tenant. Catalog and work depend on CRM and tenant. Commerce depends on CRM and work. Portal depends on catalog and commerce. People depends only on foundation. Conversations depends on identity and tenant. Automation depends on catalog, commerce, conversations and CRM. Platform depends on tenant and must not import conversations; orchestration joins those packages in `core-*`. ## Payload Baselines [#payload-baselines] Use package-owned `baseline:payload` scripts, which the CI `Build` job runs after the build, to capture SaaS and CLI size artifacts. The bundle analyzer runs only on a manual workflow run. See [CI And Release](/operations/ci-and-release). # RBAC (/architecture/rbac) Role defaults, typed permissions, per-organization overrides, JWT hints, and enforcement layers. CentraKit's role-based access control has a single source of truth, a six-tier role model, typed permissions, per-organization overrides, and two enforcement layers: fast UI hints and authoritative DB checks. ## Source Of Truth [#source-of-truth] The **feature registry** in `packages/identity/src/rbac/features.ts` is the single source of truth for permission keys. Each feature declares its scope and actions; CRUD actions (`view`/`create`/`update`/`delete`) are generated by a `crud()` helper and never hand-written. `OrgPerm`, `SysPerm`, and `UserPerm` expose the typed keys (e.g. `OrgPerm.tasks.create`), and `ALL_PERMISSION_KEYS` / `FEATURE_GROUPS` are derived from the registry. `packages/identity/src/rbac/permissions.ts` derives the display catalog (`PERMISSION_CATALOG`) and the role-to-permission defaults (`RoleDefaults`, `ROLE_CATALOG`) from that registry. The permission seed in `supabase/schemas/identity/055_functions_rbac.sql` (`ensure_all_permissions` + `ensure_system_roles`) mirrors it. The database tables `public.permissions`, `public.role_permissions`, and `public.organization_permission_overrides` are the durable projection of that catalog. Never add a permission directly in SQL without also adding the feature/action to `features.ts`, and vice versa. Add it in TypeScript first, regenerate types, and update the SQL seed in the same change. ### Key Format [#key-format] Every permission key is `{scope}.{feature}.{action}`: * `organization.tasks.view`, `organization.quotes.send`, `organization.general.transfer-ownership` * `system.admin.access`, `system.users.manage`, `system.roles.manage` * The one legacy two-segment key is `user.manage-own-profile`. Following GitLab's permission conventions, prefer specific actions over a broad `manage`; reuse a single action where subject + action match; domain-language actions (`send`, `mark-accepted`, `transfer-ownership`) are allowed where the action is a distinct lifecycle step. Settings-panel features (`billing`, `api-keys`, `branding`, `integrations`) keep a single `manage` action. ## Roles [#roles] | Scope | Role | Purpose | | -------------- | ---------------- | -------------------------------------------------- | | `system` | `system-admin` | Full system access. | | `system` | `system-support` | Limited system access for support workflows. | | `organization` | `owner` | Final authority in an organization; can delete it. | | `organization` | `admin` | Manage users, roles, workflows, and settings. | | `organization` | `member` | Day-to-day operational access. | | `organization` | `viewer` | Read-only. | `public.organization_users.is_owner` does not exist. A user is an owner of an organization when they hold the `owner` role there. The `organization_users_owner_invariant` trigger enforces that every organization has at least one owner at transaction commit. ## Permissions [#permissions] Permissions are typed string constants grouped by scope: * `system.*` for platform-level permissions, such as `system.admin.access`. * `organization.*` for tenant-level permissions, such as `organization.general.update`. * `user.*` for reserved future per-user permissions. Audit logs use operation-specific permissions at both scopes: * `organization.audit-log.view|export|reveal` for tenant events. * `system.audit-log.view|export|reveal` for platform and cross-organization events. The sidebar and page guards use the matching `view` permission. Export and restricted-detail actions are checked independently; a user who can read events does not automatically receive sensitive context or CSV access. See [Audit Logs](/architecture/audit-logs). Per-organization overrides can grant or revoke a specific permission from a role within a single organization. Override precedence in `public.authorize_scope` and `public.has_org_permission` is: 1. Explicit `granted = false` override denies access. 2. Explicit `granted = true` override allows access. 3. Role default allows access. 4. Otherwise access is denied. ## Enforcement Layers [#enforcement-layers] ### Authoritative Checks [#authoritative-checks] * `public.authorize_scope(scope, scope_id, permission)` is the general DB check used by RLS and permission wrappers. * `public.org_ids_with_permission(permission)` returns the organizations where the current user holds a permission; RLS policies use it as `organization_id in (select ...)` so the lookup runs once per statement. * `public.has_org_permission(org_id, permission)` is the single-row form used inside SQL functions and RPC guards. * Server actions and API route handlers wrap handlers with `withOrgPermission` or `withSystemPermission`. ### Page Guards [#page-guards] `requireOrganizationAccess({ permission })` is called inside each tenant page's Suspense island. A missing permission triggers `notFound()` (404) rather than a redirect, so feature existence is not leaked to members without access. The static page shell still paints while the guard resolves. The guard reuses the organization permission set loaded by `getCurrentAppAccess()` and only falls back to an authoritative batch check if a requested key was not in that payload. This keeps page guards from issuing an extra single-permission RPC after the app access payload has already loaded. ### UI Hints [#ui-hints] * `` is a server component that runs the authoritative `hasPermission` check; use it to gate server-rendered buttons and sections. * `` and `usePermission` are client hints hydrated from the app-layout `PermissionProvider`. * The sidebar, command palette, AI availability, and client permission context all share one `getCurrentAppAccess()` payload from the authenticated app layout. * Client toolbars and row-action menus receive boolean permission props (e.g. `canCreate`, `canUpdate`, `canDelete`) computed server-side from the loaded permission set via `getOrganizationPermissionFlags()` / `getSystemPermissionFlags()`, with one batch fallback only when needed. UI hints are decorative only. Writes must still go through a permission-wrapped action, API handler, or RLS policy. ### Role Form [#role-form] The role create/edit form renders a feature-grouped `PermissionMatrix`: one card per feature with a checkbox per action, a per-feature "select all", and a global select-all toggle. Groups and labels derive from the feature registry. The same form serves tenant and admin (system) roles. ## JWT Claims [#jwt-claims] `custom_access_token_hook` enriches Supabase access tokens with `system_roles`, `organization_roles`, the resolved `system_permissions` / `organization_permissions` / `user_permissions` sets (compacted: a full organization catalog is stored as `["*"]`, and system admins omit `organization_permissions`), and `datetime_preferences` (`timezone`, `week_start`, `date_format`, `time_format` from `profiles`). The SaaS app resolves tenant access from these claims plus a shared public `"use cache"` lookup from slug to organization (`apps/saas/lib/organizations/access.ts`), so opening a tenant route costs no per-user database read. Deeper authorization always goes through `authorize_scope`, RLS, or a permission wrapper. Claims are only as fresh as the token. Mutations that change what the claims describe (creating an organization, accepting an invitation, saving date/time preferences) call `refreshSessionClaims()` from `@workspace/core/auth` so the session is re-minted immediately instead of at the next scheduled refresh. ## Invitations [#invitations] Invitations live in `public.organization_invitations`, separate from `organization_users` because pending invitees may not have an auth user yet. `public.accept_organization_invitation` creates the membership row on acceptance. ## Tests [#tests] * `packages/core/tests/rbac-with-permission.test.ts` covers wrapper denial, allow, and `ctx.onSuccess`. * `apps/saas/tests/permission-gates.test.tsx` covers `` and `PermissionProvider`. * `tests/integration/saas/admin-roles.test.tsx` and `tests/integration/saas/organization-role-form.test.tsx` cover role forms. * pgTAP coverage for database functions is tracked as follow-up work. # Service Layers (/architecture/service-layers) Data contracts, Supabase implementations, request containers, and Result-based domain flows. Domain logic follows a three-layer pattern that decouples apps from infrastructure providers. ## Layers [#layers] ```text bounded contract packages -> interfaces, schemas, errors, read models packages/supabase-* -> Supabase-backed implementations packages/core + core-* -> request context and domain-scoped factories ``` ## Bounded Contract Packages [#bounded-contract-packages] The source/JIT packages `foundation`, `identity`, `tenant`, `crm`, `catalog`, `work`, `people`, `commerce`, `conversations`, `automation`, `platform`, and `portal` define TypeScript interfaces, Zod schemas, and domain error types. They may depend only on lower contract packages and infrastructure-neutral libraries. Use one shared schema per domain when practical instead of creating duplicate DTOs. Prefer purpose-built list/picker/board/summary/detail read models when callers materially differ. ## `packages/supabase-*` [#packagessupabase-] `packages/supabase-runtime` owns clients, generated database types, and shared provider primitives. The aligned bounded packages (`supabase-identity`, `supabase-tenant`, and so on) implement contract interfaces without importing other providers. Core injects cross-domain dependencies and does not expose Supabase details to apps. ## `packages/core` and `packages/core-*` [#packagescore-and-packagescore-] Create a typed `RequestContext` with `createRequestContext({ bearerToken? })`. Cookie and bearer paths create context only; they must not instantiate every domain service. Each feature or route imports a direct factory such as `createTasksService(context)` from `@workspace/core-work/tasks`. When a use case spans domains, compose the two or three explicit factories in that orchestration module. Do not hide them behind string keys, proxies, or an all-services container. The domain-scoped packages are `core-tenant`, `core-crm`, `core-catalog`, `core-work`, `core-people`, `core-commerce`, `core-conversations`, `core-automation`, `core-platform`, and `core-portal`. Three more `core-*` packages hold cross-domain runtimes rather than one domain's factories: * `@workspace/core-ai-runtime` runs assistant chat and specialist streams for the AI routes in `apps/api/app/api/v1/ai/`, including the AI approval gate and the agent chat workflows. * `@workspace/core-workflow-runtime` holds the workflow dispatcher (`dispatchWorkflowEvent`), the scheduler, lifecycle nodes, and the commerce and order event reactors. See [Workflows](/operations/workflows). * `@workspace/core-command-center` composes the tenant, CRM, platform, portal, and automation factories into command center search and commands. ## Feature and integration packages [#feature-and-integration-packages] Some packages sit beside the three layers instead of inside one of them: * `@workspace/ai` and `@workspace/inbox` carry the `core` boundary tag. `ai` owns AI Gateway models, assistant tools, prompts, knowledge retrieval, and memory. `inbox` owns the Chat SDK channel adapters and inbound channel webhook jobs. * `@workspace/email`, `@workspace/stripe`, `@workspace/seo`, `@workspace/product-pdf`, `@workspace/quote-pdf`, and the packages under `packages/integrations/` carry the `integration` tag. They wrap one external service or renderer, may import only contracts and config, and are composed by `core-*` packages or apps. ## Result Model [#result-model] Async service methods return `Promise>` using `better-result`. Domain exceptions should become typed error results rather than thrown exceptions. Use `Result.codec` only at action/API/AI wire boundaries. ## Provider Swapping [#provider-swapping] To replace Supabase, create a new provider package that implements the same bounded contract interfaces, then change the factories wired by the matching `@workspace/core-*` package. # Supabase Schema (/architecture/supabase-schema) Declarative schema workflow, generated migrations, RLS placement, seeds, and generated types. CentraKit uses Supabase declarative schema management. ## Source Of Truth [#source-of-truth] `supabase/schemas/` is the source of truth for database structure. Do not create or write migration files directly during normal schema work. ## Workflow [#workflow] 1. Edit the desired final state in `supabase/schemas/**/*.sql`. 2. Stop the local Supabase environment when ready to diff. 3. Generate a migration with `supabase db diff -f `. 4. Review the migration to confirm it contains only the intended incremental change. 5. Reset or apply locally as needed. For rollbacks, revert declarative schema files and generate a new forward migration instead of rewriting history. ## Layout And Ordering [#layout-and-ordering] `supabase/schemas/` starts with `010_extensions.sql`, `015_shared/` and `020_types/`, then one folder per domain (`tenant`, `identity`, `crm`, `catalog`, `work`, `people`, `commerce`, `conversations`, `ai`, `automation`, `platform`, `portal`), and ends with `090_grants.sql`. Every file keeps a global number that sets when it runs, whichever folder it lives in: | Range | Layer | | ----------- | --------------------------------------- | | `035`–`053` | Tables, plus RLS that only needs tables | | `054`–`059` | Functions | | `060`–`067` | Triggers | | `070`–`079` | RLS policies that need helper functions | | `080`–`089` | Storage policies | `supabase/config.toml` lists every file in `schema_paths`, sorted by file name. When a new file depends on another file with the same number, give it the next free number. Otherwise the alphabetical order decides which one runs first. Define each function in one file only; with two definitions, the file that runs last silently wins. After any move or rename, `pnpm supabase:diff` must report no schema changes. ## RLS [#rls] Every table must have RLS enabled. Policies that reference later-defined schema or helper functions belong in dedicated RLS files rather than table definition files. Tenant-table policies use the init-plan form `organization_id in (select public.org_ids_with_permission('key'))`, which Postgres evaluates once per statement (a hashed SubPlan) instead of calling a plpgsql helper for every candidate row. Chat rows use `thread_id in (select public.accessible_chat_thread_ids())`. Keep `public.has_org_permission(org_id, key)` for single-row checks inside SQL functions and `public.authorize_scope(...)` for the general case. A few policies still call `public.has_org_permission` per row because the permission key or the organization comes from the row itself: `workflow_lifecycles_select` in `supabase/schemas/automation/078_rls_workflows.sql` (the key depends on the subject type), the `realtime_messages_select` policy in `supabase/schemas/conversations/078_rls_inbox.sql` (the organization is parsed from the Realtime topic), and the contextual artifact policies in `supabase/schemas/ai/078_rls_ai_platform.sql` (the key is the row's `origin_permission`). Several RLS files, including `supabase/schemas/catalog/073_rls_assets.sql`, keep a header comment that mentions `has_org_permission` even though their policies already use the init-plan form. ## Seeds And Types [#seeds-and-types] Seed data lives under `supabase/seeds/`. After schema changes, regenerate Supabase TypeScript types for both `public` and `graphql_public` schemas so generated code stays complete. # Tenancy (/architecture/tenancy) URL-based organization tenancy, slug extraction, access guards, and tenant navigation. CentraKit encodes the active organization directly in the URL. There is no `current-organization` cookie, hidden precompute segment, or in-memory tenant switch. ## URL Structure [#url-structure] Tenant-scoped routes sit under `[orgSlug]` inside the authenticated SaaS app group: ```text app/[locale]/(app)/ layout.tsx page.tsx [orgSlug]/ layout.tsx page.tsx portal/ users/ teams/ roles/ workflows/ billing/ usage/ settings/ ``` System routes such as `/admin/*`, `/profile`, `/organizations/new`, and `/auth/*` live outside the `[orgSlug]` segment because they are not scoped to a tenant. ## Slug Resolution Flow [#slug-resolution-flow] 1. Tenant URLs carry the organization as `[orgSlug]` under `app/[locale]/(app)/[orgSlug]/...`. 2. `extractOrgSlug(pathname)` in `apps/saas/lib/organizations/url.ts` strips reserved root segments, validates the slug shape, and lowercases it when callers need pathname parsing (for example navigation helpers). 3. The proxy does **not** inject an org-slug request header. Org identity stays URL data so Cache Components / Partial Prefetching can upgrade param-specific shells. 4. Tenant page shells stay synchronous and do not `await params` at the page or layout root. Access gates and data loaders run inside Suspense islands and receive `orgSlug` via `params.then(({ orgSlug }) => …)`. 5. `getCurrentAppAccess(orgSlug)` / `requireOrganizationAccess({ orgSlug, permission? })` resolve membership by the explicit slug. Pass `null` only for session-only chrome (`/profile`, `/admin`, `/organizations`). Unknown slugs return `notFound()` rather than redirecting to a canonical path. ## Guards [#guards] * `requireOrganizationAccess({ orgSlug, permission? })` runs inside tenant-page Suspense islands. Callers pass the slug from route `params`; the guard checks membership and optionally enforces a permission using the loaded organization permission set. * `requireCustomerPortalAccess({ orgSlug })` allows organization members and contact-linked non-members into `/{orgSlug}/portal`. Unknown organizations 404. Portal record lists remain scoped to linked `customer_contacts` (empty for org members without a contact link). * `requireSystemAccessAdmin()` gates `/admin/*` pages. * `AppShell` mounts once in `(app)/layout.tsx`. `[orgSlug]/layout.tsx` runs `OrganizationSlugAccessGate` in Suspense so unknown/non-member slugs still `notFound()` without blocking cold chrome paint (may be HTTP 200 with the not-found UI after streaming). `/profile`, `/admin`, `/organizations`, and `/feedback` share that same shell without a slug gate. Sidebar nav hrefs still re-derive the active organization from the pathname on the client. The proxy enforces the coarse authenticated-app boundary. Page guards enforce organization membership, customer linkage, and permissions. Staff membership remains the default access kind when staff and customer access overlap, but the compact app-shell organization payload preserves both available access kinds. ## Navigation And Links [#navigation-and-links] * `apps/saas/lib/navigation.ts` defines nav sections with a `scope` and optional `permission`. * Tenant-scoped links use organization-relative hrefs like `/settings`; `resolveNavHref` prefixes them with `/{orgSlug}` at render time. * Organization switchers are `Link`-based. Choosing an organization navigates to `/{slug}`. * Navigation mode defaults from the URL: organization routes show Organization navigation and `/{orgSlug}/portal/**` shows Customer navigation. Organization / Customer / Admin mode switches in the user menu are in-memory presentation previews; clicking a nav item navigates to the real route. Admin mode remains system-admin-only. Route guards remain authoritative. * Breadcrumbs strip locale and active organization slug before matching route labels, then re-prefix hrefs back onto the tenant segment. ## Creating And Deleting Organizations [#creating-and-deleting-organizations] * `createOrganizationAction` returns `{ organizationId, slug }` and redirects to `/{slug}`. * `deleteOrganizationAction` returns `{ nextOrganizationSlug }` and redirects to the next available organization or `/organizations/new`. ## Why URL Tenancy [#why-url-tenancy] * RSC caching behaves naturally because there is no private request-scoped current-organization cookie. * Links are shareable and open the right tenant directly. * The proxy stays thin: it handles locale rewrites and session routing only, with no org-slug header and no database calls. * Admin and per-user pages do not inherit a slug they do not need. ## Tests [#tests] * `apps/saas/tests/rbac-url.test.ts` covers `extractOrgSlug`. * `tests/integration/saas/proxy.test.ts` covers proxy redirects and public-path behavior. * `tests/integration/saas/customer-portal-guards.test.ts` covers customer-only, staff portal access, and unknown-organization portal access. * `tests/integration/saas/app-organization-access.test.ts` covers merged staff/customer access and route-specific shell context. * `apps/saas/tests/app-navigation-mode.test.ts` covers route-derived and available navigation modes. # 0000: Template (/decisions/0000-template) Copy this file to record a new decision. * Status: Proposed, Accepted, Superseded by 00XX, or Deprecated * Date: YYYY-MM-DD ## Context [#context] What forces are at play? State the standard item or the problem, and what makes the default answer a poor fit here. ## Decision [#decision] What we do, stated in one or two sentences. ## Consequences [#consequences] What becomes easier, what becomes harder, and what would make us revisit this record. # 0001: Next.js route handlers for the API and MCP (/decisions/0001-next-route-handlers-for-api-and-mcp) The REST API and MCP server stay as Next.js route handlers in apps/api instead of Hono, oRPC, and a separate apps/mcp. * Status: Accepted * Date: 2026-09-30 ## Context [#context] The repo standard asks for a Hono shell with an oRPC contract in `packages/contract`, and a separate `apps/mcp` built on orpc-mcp. CentraKit already ships more than 200 versioned route handlers under `apps/api/app/api/v1/`, an MCP server at `/api/mcp` built on `mcp-handler`, and an OpenAPI 3.2 document generated by `next-openapi-gen` from Zod schemas and route JSDoc. ## Decision [#decision] Keep Next.js route handlers, `next-openapi-gen`, and the MCP server inside `apps/api`. Scalar stays at `/api-docs`. Errors follow RFC 9457 Problem Details, so the wire format already matches the standard. ## Consequences [#consequences] * The committed `apps/api/public/openapi.json` stays the contract, and CI fails when it drifts. * MCP tools stay curated in `apps/api/lib/mcp` and do not mirror REST one to one. * A later plan moves the API layer to Hono and an oRPC contract. That plan also carries the Valibot migration (see ADR 0004). # 0002: Bounded package topology (/decisions/0002-bounded-package-topology) Domain logic stays split into bounded contract, core, and supabase packages instead of domain, contract, and services. * Status: Accepted * Date: 2026-09-30 ## Context [#context] The repo standard groups domain logic into `packages/domain`, `packages/contract`, `packages/services`, and `packages/supabase`. CentraKit uses a provider-swappable three-layer model per bounded context: * Contract packages (`foundation`, `identity`, `tenant`, `crm`, `catalog`, `work`, `commerce`, `conversations`, `automation`, `platform`, `portal`, `people`) own interfaces, schemas, and domain errors. * `packages/supabase-*` packages own the Supabase implementations. * `packages/core` and `packages/core-*` own request-scoped service wiring. Integrations with third parties live under `packages/integrations/*`, which is an extra workspace glob. ## Decision [#decision] Keep the bounded topology and the `packages/integrations/*` glob. Turbo boundary tags encode the same import rules the architecture rule describes. ## Consequences [#consequences] * Apps construct only the services they need through `@workspace/core-*` factories. * The workspace list keeps `packages/integrations/*` in addition to the standard globs. * Turbo tags are `app`, `core`, `provider`, `contract`, `ui`, `config`, `integration`, and `test` instead of the standard package names. # 0003: The apps/saas name and host-based routing (/decisions/0003-saas-app-name-and-host-routing) The product app stays apps/saas, and every surface is served from its own subdomain instead of path prefixes. * Status: Accepted * Date: 2026-09-30 ## Context [#context] The repo standard names the product app `apps/app` and serves surfaces through path rewrites (`/app`, `/api`, `/docs`). CentraKit serves each surface from its own host through Vercel Services host rewrites: `app.*`, `api.*`, `docs.*`, the marketing sample host, and the showcase site on the apex. The extra `apps/showcase` app is the CentraKit template site. ## Decision [#decision] Keep `apps/saas`, `apps/showcase`, and host-based routing. The docs app stays root-mounted on `docs.*`, so it uses no `basePath`. Portless names stay `app`, `api`, `docs`, `marketing`, and `showcase`. ## Consequences [#consequences] * Public URLs still derive from one `NEXT_PUBLIC_SITE_URL` through `packages/foundation/src/public-urls.ts`. * Per-page Markdown for docs lives at `/.md` and `/.mdx` on the docs host, not under `/docs`. * Moving to path-based routing would need a separate plan that covers cookies, OAuth redirect URLs, and the PWA scope. # 0004: Zod until the API layer migration (/decisions/0004-zod-until-api-layer-migration) Zod 4 stays the single schema library until the API layer moves to oRPC. * Status: Accepted * Date: 2026-09-30 ## Context [#context] The repo standard uses Valibot for env, domain, contract, forms, and AI structured output. CentraKit generates its OpenAPI document with `next-openapi-gen`, which only reads Zod schemas or TypeScript types. Moving to Valibot while keeping `next-openapi-gen` would break the committed OpenAPI document and the generated API client. ## Decision [#decision] Keep Zod 4 as the only schema library. Forms use TanStack Form with Zod through Standard Schema. `no-restricted-imports` blocks other schema libraries so the codebase keeps one. ## Consequences [#consequences] * Valibot adoption moves with the later API layer plan (ADR 0001), where an oRPC contract with `@orpc/valibot` replaces `next-openapi-gen`. * The `valibot` skill is not installed until that plan starts. # 0005: Audit log naming (/decisions/0005-audit-log-naming) The audit log keeps the audit_logs table family and capture_audit_log_row() instead of audit_events and audit_row_change(). * Status: Accepted * Date: 2026-09-30 ## Context [#context] The repo standard names the audit table `audit_events` and the row trigger function `audit_row_change()`. CentraKit already ships `audit_logs`, `audit_log_restricted_details`, `audit_log_export_jobs`, and `audit_log_table_registry`, with `capture_audit_log_row()` attached through the registry. Production data exists in these tables. ## Decision [#decision] Keep the current names. Each domain table still gets the trigger, its ignored and redacted column lists in the registry, and a pgTAP case. ## Consequences [#consequences] * Renaming would need a data migration and a coordinated release for no functional gain. * `docs/architecture/audit-logs.mdx` stays the reference for how to register a new table. # 0006: Env file layout (/decisions/0006-env-file-layout) pnpm env:pull writes development values to .env.local so .env.development.local can hold local Supabase overrides. * Status: Accepted * Date: 2026-09-30 ## Context [#context] The repo standard has `pnpm env:pull` write `.env.development.local`, `.env.preview.local`, and `.env.production.local`. CentraKit uses `.env.development.local` for hand-written overrides that point the apps at local Supabase and Portless hosts. Pulling into that file would erase those overrides on every pull. ## Decision [#decision] `pnpm env:pull` writes the development environment to `.env.local`, and also writes `.env.preview.local` and `.env.production.local`. Local overrides stay in `.env.development.local`, which Next.js loads after `.env.local` in development. Apps load the root files through `loadMonorepoEnv` in `packages/next-config`. ## Consequences [#consequences] * Root scripts load env through `loadMonorepoEnv` instead of `dotenv -e` wrappers. * `.env.example` lists every key, and the overrides file is documented in `AGENTS.md`. # 0007: Deferred better-supabase and PermDock (/decisions/0007-deferred-better-supabase-and-permdock) The hand-written Supabase packages and the custom RBAC stay until dedicated migration plans. * Status: Accepted * Date: 2026-09-30 ## Context [#context] The repo standard uses better-supabase for typed repositories and PermDock for roles and permissions. better-supabase is at version 0.1.0, and PermDock is not published on npm yet. CentraKit already has typed `supabase-*` packages that return `Result` values and run as the caller, and an RBAC catalog in `packages/identity/src/rbac` mirrored by SQL seeds and init-plan RLS policies. ## Decision [#decision] Keep the hand-written `supabase-*` packages and the custom RBAC. The `db:gen` script, `better-supabase doctor`, `packages/policy`, `permdock rls generate`, and `permdock doctor` wait for their own plans. ## Consequences [#consequences] * `pnpm supabase:types` generates `database.types.ts` in the meantime. * Permission changes still update `packages/identity/src/rbac/permissions.ts`, the SQL seeds, and the RLS policies together. # 0008: CLI on citty, clack and c12 (/decisions/0008-cli-unjs-stack) The centrakit CLI moves from commander and hand-written helpers to citty, @clack/prompts, c12 and Node 24 built-ins, and the guard tests scan files with fs.glob and oxc-parser. * Status: Accepted * Date: 2026-10-02 ## Context [#context] The CLI was built on commander with a custom runner, hand-written runtime flag parsing, a JSON-only config file and a `spawn`-based browser opener. It never prompted for missing values, so `register` took the password as a positional argument and every organization command needed a UUID typed by hand. The guard tests in `tests/integration` and `packages/ui/tests` each had their own recursive directory walker and matched imports with regular expressions, which also matched text inside comments and strings. ## Decision [#decision] * Commands and flags use citty. Each command is a `defineCommand` default export loaded lazily from `src/root.ts`, so `--version` and help stay fast without a separate fast path. * Interactive flows use `@clack/prompts`, wrapped in `src/ui.ts`. Prompts only appear when stdin and stdout are terminals, `--json` is off and the session is not non-interactive. Otherwise a missing value is an error. * Config files load through c12, which uses jiti for TypeScript configs. The package exports `defineConfig` from `centrakit/config`. * Colors use `node:util` `styleText`, the browser opener uses `open`, and command suggestions use `fastest-levenshtein`. * The CLI targets Node 24 and later. The build script runs with plain `node` through type stripping, so `tsx` is no longer needed. * Guard tests list files with the `listFiles` and `listFilesSync` helpers from `@workspace/test-fixtures/source-files`, built on `fs.glob`, and read imports with `importSpecifiers`, built on `oxc-parser`. ## Consequences [#consequences] * commander is removed from the catalog. `std-env` was considered for CI detection but left out, because it reads the environment once at import and the tests change it per case. The CLI reads `CI` through its t3 env schema at call time instead. * citty does not support repeated flags, so `--social` is read from the raw arguments with `collectRepeatedFlag` in `src/args.ts`. * citty only passes a subcommand the arguments after its name, so `runCli` moves leading global flags behind the command path before parsing. * `register` takes the password from a hidden prompt or `--password-stdin` and no longer accepts it as an argument. * New guard tests should use the shared helpers instead of adding another walker or import regex. # Architecture decision records (/decisions/README) Why CentraKit differs from the shared repo standard, and which migrations are deferred. Architecture decision records (ADRs) capture choices that a future contributor could otherwise mistake for drift. Each record states the context, the decision, and the consequences. ## When to add a record [#when-to-add-a-record] Add a record when you: * Keep a deliberate deviation from the shared repo standard. * Replace a library in the one-library-per-concern list. * Add a vendor that is not a first-party Vercel product, a Vercel Marketplace integration, or a Vercel Connect connector. * Defer a standard item to a later plan. Copy `0000-template.md`, take the next free number, and add the file to `meta.json`. ## Records [#records] | Number | Title | Status | | ------ | ------------------------------------------- | -------- | | 0001 | Next.js route handlers for the API and MCP | Accepted | | 0002 | Bounded package topology | Accepted | | 0003 | The `apps/saas` name and host-based routing | Accepted | | 0004 | Zod until the API layer migration | Accepted | | 0005 | Audit log naming | Accepted | | 0006 | Env file layout | Accepted | | 0007 | Deferred better-supabase and PermDock | Accepted | | 0008 | CLI on citty, clack and c12 | Accepted | # Docs for AI agents (/developers/ai-agents) How AI agents and LLM tools read the CentraKit docs through llms.txt, Markdown page URLs, the docs MCP server and Ask AI, and how they act on CentraKit data through the product MCP server. Every page of these docs is available as Markdown, and the site has an index, a full-text export and an MCP server for agents. Use this page to point an assistant, a coding agent or a retrieval pipeline at the docs, and to choose between reading docs and acting on CentraKit data. ## Choose an entry point [#choose-an-entry-point] | You want to | Use | | -------------------------------------------------- | ------------------------------------------------------------- | | Give an agent a map of the docs | `https://docs.centrakit.com/llms.txt` | | Load all docs into one context | `https://docs.centrakit.com/llms-full.txt` | | Fetch one page as Markdown | The page URL with `.md`, or `Accept: text/markdown` | | Let an agent search and read docs on demand | The docs MCP server at `https://docs.centrakit.com/mcp` | | Ask a question in the browser | Ask AI on any docs page | | Read or change customers, tasks, quotes and orders | The product MCP server at `https://api.centrakit.com/api/mcp` | ## llms.txt and llms-full.txt [#llmstxt-and-llms-fulltxt] [`/llms.txt`](/llms.txt) is a plain-text index of every page with its title, URL and description. It is small enough to put in a system prompt, and agents use it to pick the pages to fetch. [`/llms-full.txt`](/llms-full.txt) is the Markdown of every page in one file. It is large, so prefer the index plus single pages unless you are building a retrieval index or the model has a large context window. Both cover the guides, the OpenAPI operation pages and the changelogs. ## Markdown pages [#markdown-pages] Every page has a Markdown version. Add `.md` or `.mdx` to the page path: ```bash curl https://docs.centrakit.com/developers/mcp.md curl https://docs.centrakit.com/developers/mcp/index.mdx ``` Or ask for Markdown on the normal URL with the `Accept` header: ```bash curl -H "Accept: text/markdown" https://docs.centrakit.com/developers/mcp ``` When a request prefers `text/markdown`, the docs site returns the Markdown instead of HTML and sets `Vary: Accept`, so caches keep both versions apart. Responses use `Content-Type: text/markdown; charset=utf-8`. Each Markdown page starts with `# (<url>)` and the page description, followed by the page body. OpenAPI operation pages contain only that page's operations and the component schemas they reference, not the whole specification. ## Page actions [#page-actions] Every docs page has two buttons under its title: * **Copy Markdown** copies the page's Markdown to the clipboard, ready to paste into a chat. * **Open** shows a menu with **Open in GitHub** (the page source), **View as Markdown**, **Open in Scira AI**, **Open in ChatGPT**, **Open in Claude** and **Open in Cursor**. The AI options open the tool with a prompt that points it at the page's Markdown URL. ## Docs MCP server [#docs-mcp-server] The docs MCP server lets an agent search and read the docs as tools instead of fetching URLs. It is public, read-only and needs no authentication. | Environment | Endpoint | | ----------- | -------------------------------- | | Production | `https://docs.centrakit.com/mcp` | | Local | `https://docs.localhost/mcp` | It has three tools: | Tool | Input | Returns | | ------------ | ------------------------------------------------ | ------------------------------------------------------------- | | `list_pages` | none | The page index, the same content as `/llms.txt`. | | `search` | `query`, optional `locale` | Matching sections with their page URLs. | | `get_page` | `url`, a page pathname such as `/developers/mcp` | The page as Markdown, or `page not found` for an unknown URL. | Add it to a client that supports remote MCP servers: ```json { "mcpServers": { "centrakit-docs": { "url": "https://docs.centrakit.com/mcp" } } } ``` For one-click installs in Cursor, VS Code and Claude Code, see the connect table on [MCP server](/developers/mcp). ## Ask AI [#ask-ai] The floating **Ask AI** button on every docs page opens a chat panel. The assistant searches the docs with a `search` tool, answers from the results, and cites the pages it used as links. When the docs do not cover a question, it says so and suggests a better search. Ask AI uses the platform's default chat model, which administrators set in [AI settings](/admin/ai-settings). It reads only the public docs and cannot see your organization data. ## Product MCP server [#product-mcp-server] To let an agent act on your CentraKit data, connect the product MCP server at `https://api.centrakit.com/api/mcp`. It needs OAuth, runs with your permissions, and exposes 74 tools for tasks, customers, products, quotes, commerce, agenda, files, workflows, webhooks and the audit log. Mutations can be held by approval policies. See [MCP server](/developers/mcp). A typical setup connects both servers: the docs server so the agent knows how CentraKit works, and the product server so it can do the work. ## For contributors [#for-contributors] The repository has instructions for coding agents that work on CentraKit itself: * `AGENTS.md` at the repository root describes the layout, commands, architecture invariants, conventions and hard rules. `CLAUDE.md` points to it. * `docs/agents/` holds notes on mistakes agents have repeated, one page per topic: the toolchain, git and release, the Supabase schema, tenancy and routing, translations, testing and writing docs. Agents load the page for the topic they are working on. * `.agents/rules/` and `.agents/skills/` hold rules and skills that attach to specific files or tasks. Docs pages are written from the shipped code. Before you change a page in this site, read [Writing docs](/agents/writing-docs). ## Related pages [#related-pages] * [MCP server](/developers/mcp) * [REST API](/developers/api) * [OpenAPI](/developers/openapi) * [Writing docs](/agents/writing-docs) # REST API (/developers/api) CentraKit REST API base URLs, authentication, organization scoping, pagination, and a map of every route under /api/v1. The CentraKit API app serves a versioned REST API under `/api/v1` and the product MCP server at `/api/mcp`. The SaaS app, the CLI and MCP clients all call the same routes, with the same permission checks and the same error format. ## Overview [#overview] | Environment | Base URL | | ---------------- | ------------------------------- | | Production | `https://api.centrakit.com/api` | | Local (Portless) | `https://api.localhost/api` | Every path in the OpenAPI spec starts with `/v1`, so a full URL looks like `https://api.centrakit.com/api/v1/organizations`. Keep `/api` in any base URL you configure, because the route handlers live under that prefix. Locally, run `pnpm dev:portless` (or `pnpm dev:api:portless` for the API only) to serve `https://api.localhost`. ```bash curl https://api.centrakit.com/api/v1/health ``` ```json { "ok": true, "service": "api", "version": "v1" } ``` ## Authentication [#authentication] Routes accept either a Supabase browser session cookie or a bearer access token: ```http Authorization: Bearer <access-token> ``` When a request carries a bearer token, the API verifies its issuer, audience, signature and expiry before it runs the route. Without a bearer token, the API reads the Supabase session cookie. Use bearer tokens for scripts, CI, the CLI and MCP hosts. See [Authentication](/developers/authentication) for how to obtain a token. With the CLI: ```bash centrakit --token <access-token> orgs list ``` ## Organization scoping [#organization-scoping] Tenant data lives under `/api/v1/organizations/{organizationId}/...`, where `organizationId` is the organization UUID (not its slug). There is no current-organization header or cookie: every organization-scoped request names its organization in the path. To find the IDs you can use, call `GET /api/v1/organizations`. `PATCH /api/v1/users/me/active-organization` stores a preferred default organization on your profile for clients that need one, but it never replaces the organization ID in the path or any permission check. Each operation under `/organizations/{organizationId}` checks one organization permission before it reads or writes data. Reads require the matching `view` permission, and mutations require the operation's create, update, delete or lifecycle permission, for example `organization.webhooks.update` for rotating a webhook secret. The check runs the same way for cookie and bearer requests: * An unauthenticated request gets `401`. * An authenticated user without the permission gets `403` with code `ORGANIZATION_PERMISSION_DENIED`. Row Level Security in the database still applies on top of this check. See [RBAC](/architecture/rbac) and [Tenancy](/architecture/tenancy) for the permission model. ## Pagination [#pagination] Most list endpoints use offset pagination with these query parameters: | Parameter | Default | Limits | Meaning | | --------- | ------- | -------------- | ----------------------------------------------- | | `offset` | `0` | 0 or more | Number of items to skip. | | `limit` | `50` | 1 to 100 | Page size. | | `q` | none | 200 characters | Free-text search, on endpoints that support it. | A list response wraps its items in `data` and reports the range in `pagination`: ```json { "data": [{ "id": "..." }], "pagination": { "offset": 0, "limit": 50, "total": 1240, "hasMore": true, "totalIsEstimate": true } } ``` * `hasMore` is the reliable signal for whether another page exists. Request the next page with `offset` plus `limit`. * `total` is exact unless `totalIsEstimate` is `true`. Large collections such as tasks, quotes, invoices, customer assets, file drives, organizations and members use an estimated count by default, so treat `total` as approximate when the flag is set. A few endpoints use their own limits. Collaboration reference search defaults to 20 results (maximum 50), command center search to 24 (maximum 40), inbox threads to 30 (maximum 100) and the notification inbox to 10 (maximum 20). Chat message lists count `offset` from the newest message window. The OpenAPI reference lists the parameters for each operation. ## Organization resource map [#organization-resource-map] Paths in these tables are relative to `/api/v1/organizations/{organizationId}`. Operation names, request bodies and response schemas are in the generated reference described on [OpenAPI](/developers/openapi). ### Organization, users and teams [#organization-users-and-teams] | Path | Methods | | --------------------------------------- | ------------------ | | (the organization itself) | GET, PATCH, DELETE | | `/members` | GET, POST (invite) | | `/members/{userId}` | GET, PATCH (role) | | `/invitations` | GET | | `/invitations/{invitationId}` | GET, PATCH, DELETE | | `/teams` | GET, POST | | `/teams/{teamId}` | GET, PATCH, DELETE | | `/teams/{teamId}/members` | PUT | | `/branding/logo` | POST, DELETE | | `/branding/brand-images` | POST | | `/branding/brand-images/{brandImageId}` | DELETE | ### Customers and assets [#customers-and-assets] | Path | Methods | | -------------------------------------------------------- | ------------------ | | `/customers` | GET, POST | | `/customers/{customerId}` | GET, PATCH, DELETE | | `/customers/{customerId}/contacts` | POST | | `/customers/{customerId}/contacts/{contactId}` | GET, PATCH, DELETE | | `/customers/{customerId}/contacts/{contactId}/primary` | POST | | `/customers/{customerId}/locations` | POST | | `/customers/{customerId}/locations/{locationId}` | GET, PATCH, DELETE | | `/customers/{customerId}/locations/{locationId}/primary` | POST | | `/customers/{customerId}/notes` | POST | | `/customers/{customerId}/notes/{noteId}` | PATCH, DELETE | | `/customer-assets` | GET, POST | | `/customer-assets/{customerAssetId}` | GET, PATCH, DELETE | | `/customer-assets/{customerAssetId}/notes` | GET, POST | | `/customer-assets/{customerAssetId}/notes/{noteId}` | PATCH, DELETE | ### Catalog and content [#catalog-and-content] | Path | Methods | | ------------------------------------------ | ------------------ | | `/products` | GET, POST | | `/products/{productId}` | GET, PATCH, DELETE | | `/products/{productId}/suppliers` | POST | | `/products/{productId}/suppliers/{linkId}` | PATCH, DELETE | | `/product-categories` | GET, POST | | `/product-categories/{categoryId}` | GET, PATCH, DELETE | | `/product-categories/{categoryId}/move` | POST | | `/suppliers` | GET, POST | | `/suppliers/{supplierId}` | GET, PATCH, DELETE | | `/brands` | GET, POST | | `/brands/{brandId}` | GET, PATCH, DELETE | | `/tags` | GET, POST | | `/tags/{tagId}` | GET, PATCH, DELETE | | `/templates` | GET, POST | | `/templates/{templateId}` | GET, PATCH, DELETE | Templates cover quote, invoice and task templates. ### Inventory [#inventory] | Path | Methods | | ------------------------------------------ | ----------------------- | | `/inventory/levels` | GET | | `/inventory/locations` | GET, POST | | `/inventory/stock-records` | GET, POST | | `/inventory/stock-records/{stockRecordId}` | PATCH, DELETE (archive) | | `/inventory/stock-adjustments` | POST | | `/inventory/movements` | GET | | `/inventory/allocations` | GET | ### Quotes [#quotes] | Path | Methods | | ------------------------------------- | ------------------ | | `/quotes` | GET, POST | | `/quotes/{quoteId}` | GET, PATCH, DELETE | | `/quotes/{quoteId}/send` | POST | | `/quotes/{quoteId}/accept` | POST | | `/quotes/{quoteId}/reject` | POST | | `/quotes/{quoteId}/duplicate` | POST | | `/quotes/{quoteId}/assets` | PUT | | `/quotes/{quoteId}/tasks` | PUT | | `/quotes/{quoteId}/lifecycle` | GET | | `/quotes/{quoteId}/lifecycle/advance` | POST | | `/quotes/{quoteId}/notes` | GET, POST | | `/quotes/{quoteId}/notes/{noteId}` | PATCH, DELETE | See [Quotes](/product/quotes). ### Invoices [#invoices] | Path | Methods | | ----------------------------------------- | ------------------ | | `/invoices` | GET, POST | | `/invoices/from-quote` | POST | | `/invoices/{invoiceId}` | GET, PATCH, DELETE | | `/invoices/{invoiceId}/send` | POST | | `/invoices/{invoiceId}/payments` | POST | | `/invoices/{invoiceId}/mark-paid` | POST | | `/invoices/{invoiceId}/write-off` | POST | | `/invoices/{invoiceId}/cancel` | POST | | `/invoices/{invoiceId}/duplicate` | POST | | `/invoices/{invoiceId}/pause-reminders` | POST | | `/invoices/{invoiceId}/resume-reminders` | POST | | `/invoices/{invoiceId}/lifecycle` | GET | | `/invoices/{invoiceId}/lifecycle/advance` | POST | | `/invoices/{invoiceId}/notes` | GET, POST | | `/invoices/{invoiceId}/notes/{noteId}` | PATCH, DELETE | See [Invoices](/product/invoices). ### Orders, shipments, purchasing and promotions [#orders-shipments-purchasing-and-promotions] | Path | Methods | | -------------------------------------------- | --------- | | `/orders` | GET, POST | | `/orders/{orderId}` | GET | | `/orders/{orderId}/confirm` | POST | | `/orders/{orderId}/cancel` | POST | | `/orders/{orderId}/lifecycle` | GET | | `/orders/{orderId}/lifecycle/advance` | POST | | `/shipments` | GET | | `/shipments/{shipmentId}` | GET | | `/shipments/{shipmentId}/ship` | POST | | `/purchase-orders` | GET, POST | | `/purchase-orders/{purchaseOrderId}` | GET | | `/purchase-orders/{purchaseOrderId}/receive` | POST | | `/promotions/validate` | POST | Creating, confirming and cancelling orders, shipping a shipment, and creating or receiving a purchase order run the organization's approval policy. When a policy requires approval, the route answers `409` with code `APPROVAL_REQUIRED` and an `approvalId`. See [Errors](/developers/errors), [Orders](/product/orders) and [Approvals](/product/approvals). ### Tasks and agenda [#tasks-and-agenda] | Path | Methods | | ---------------------------------------- | ------------------ | | `/tasks` | GET, POST | | `/tasks/reorder` | POST | | `/tasks/{taskId}` | GET, PATCH, DELETE | | `/tasks/{taskId}/status` | PATCH | | `/tasks/{taskId}/assignees` | PUT | | `/tasks/{taskId}/comments` | GET, POST | | `/tasks/{taskId}/comments/{commentId}` | PATCH, DELETE | | `/tasks/{taskId}/time-entries` | GET, POST | | `/tasks/{taskId}/time-entries/{entryId}` | DELETE | | `/tasks/{taskId}/lifecycle` | GET | | `/tasks/{taskId}/lifecycle/advance` | POST | | `/agenda/items` | GET, POST | | `/agenda/items/{agendaItemId}` | GET, PATCH, DELETE | | `/agenda/items/{agendaItemId}/move` | POST | See [Tasks](/product/tasks) and [Agenda](/product/agenda). ### People [#people] | Path | Methods | | ----------------------------------- | ---------- | | `/employees` | GET, POST | | `/employees/{employeeId}` | GET, PATCH | | `/time-entries` | GET, POST | | `/time-off-requests` | GET, POST | | `/expenses` | GET, POST | | `/approvals` | GET, POST | | `/hiring/jobs` | GET | | `/hiring/jobs/{jobId}/applications` | GET | `GET /approvals` lists the time-off requests, timesheets and expense claims waiting for the caller's decision, including delegated ones. `POST /approvals` approves or rejects up to 100 of them at once, and each decision succeeds or fails on its own. ### Files [#files] | Path | Methods | | ----------------------------------------- | ------- | | `/files/drives` | GET | | `/files/nodes/{nodeId}` | GET | | `/files/nodes/{nodeId}/children` | GET | | `/files/nodes/{nodeId}/download` | GET | | `/files/copy` | POST | | `/files/uploads` | POST | | `/files/uploads/{reservationId}/finalize` | POST | | `/files/uploads/{reservationId}/cancel` | POST | | `/files/zip-exports` | POST | | `/files/zip-exports/{exportId}` | GET | Uploads are reserved first, then finalized or cancelled. See [Files](/product/files). ### Inbox, chat, knowledge and AI [#inbox-chat-knowledge-and-ai] | Path | Methods | | ----------------------------------------------- | ----------------- | | `/inbox/threads` | GET | | `/inbox/threads/{threadId}/messages` | GET, POST (reply) | | `/inbox/threads/{threadId}/suggestions` | POST | | `/inbox/attachments` | POST | | `/inbox/attachments/{attachmentId}` | GET | | `/inbox/deliveries/{deliveryId}` | GET | | `/knowledge/retrieve` | POST | | `/collaboration/references` | GET | | `/ai/threads` | GET, POST | | `/ai/threads/{threadId}/messages` | GET, POST | | `/ai/models` | GET | | `/ai/activity` | GET | | `/ai/agent-runs/{runId}` | GET | | `/ai/agent-runs/{runId}/events` | GET | | `/ai/agent-runs/{runId}/handoff` | POST | | `/ai/agent-runs/{runId}/approvals/{approvalId}` | POST | | `/ai/contextual-actions/{actionId}` | GET | | `/ai/contextual-artifacts` | GET | | `/ai/contextual-artifacts/{artifactId}` | GET, PATCH | | `/channels/{connectionId}/webhook` | GET, POST | Only `/ai/threads/{threadId}/messages` from the `/ai` group is in the OpenAPI spec. The other `/ai` routes serve the SaaS assistant and agent-run viewer and can change without notice. `/channels/{connectionId}/webhook` receives provider callbacks for an inbox channel connection (WhatsApp) and queues them for processing; it is not for API clients. See [Inbox](/product/inbox), [Chat](/product/chat) and [Knowledge](/admin/knowledge). ### Notifications [#notifications] | Path | Methods | | ---------------------------------------------- | --------- | | `/notifications` | GET | | `/notifications/inbox` | GET | | `/notifications/{notificationId}` | PATCH | | `/notifications/mark-all-read` | POST | | `/notification-preferences` | GET, PUT | | `/notification-subscriptions` | GET, POST | | `/notification-subscriptions/{subscriptionId}` | DELETE | ### Workflows and webhooks [#workflows-and-webhooks] | Path | Methods | | ------------------------------------------------------ | ------------------ | | `/workflows/definitions` | GET | | `/workflows/definitions/{definitionId}` | GET | | `/workflows/definitions/{definitionId}/publish` | POST | | `/workflows/definitions/{definitionId}/runs` | POST (start a run) | | `/workflows/runs` | GET | | `/workflows/runs/{runId}` | GET | | `/workflows/runs/{runId}/approval` | POST | | `/workflows/runs/{runId}/cancel` | POST | | `/webhooks/destinations` | GET, POST | | `/webhooks/destinations/{destinationId}` | GET, PATCH, DELETE | | `/webhooks/destinations/{destinationId}/rotate-secret` | POST | | `/webhooks/endpoints` | GET, POST | | `/webhooks/endpoints/{endpointId}` | GET, PATCH, DELETE | | `/webhooks/endpoints/{endpointId}/rotate-secret` | POST | | `/webhooks/endpoints/{endpointId}/rotate-token` | POST | | `/webhooks/deliveries` | GET | | `/webhooks/deliveries/{deliveryId}` | GET | See [Webhooks](/developers/webhooks) and [Workflows](/product/workflows). ### Audit log [#audit-log] | Path | Methods | | --------------------------- | ------- | | `/audit-events` | GET | | `/audit-exports` | POST | | `/audit-exports/{exportId}` | GET | See [Audit log](/product/audit-log). ## Top-level routes [#top-level-routes] These paths are relative to `/api/v1`. ### Account and session [#account-and-session] | Path | Methods | Purpose | | ------------------------------------- | --------- | -------------------------------------------------------------------------- | | `/health` | GET | Versioned health check. No authentication. | | `/auth/login` | POST | Email and password sign-in. Creates a cookie session and returns the user. | | `/auth/signup` | POST | Create an account with email and password. | | `/auth/logout` | POST | Clear the current session. | | `/auth/oauth/start` | GET | Create the authorization URL for an OAuth sign-in provider. | | `/auth/password/reset` | POST | Send a password reset email. | | `/auth/password/update` | POST | Set a new password for the signed-in user. | | `/users/me` | GET | The authenticated user. | | `/users/me/active-organization` | PATCH | Store the preferred default organization. | | `/organizations` | GET, POST | List organizations you can access, or create one. | | `/command-center/search` | GET | Search records for the command center. | | `/agenda/google-calendar/oauth/start` | GET | Start the Google Calendar integration authorization. | ### Assistant [#assistant] The `/ai/chat` routes (`/ai/chat`, `/ai/chat/threads`, `/ai/chat/{threadId}` with `participants`, `share`, `stop` and `stream`, `/ai/chat/attachments`, `/ai/chat/follow-ups` and `/ai/chat/starter-suggestions`), `/ai/specialists` and `/ai/transcribe` power the in-app assistant. `/workflows/graph-operations` powers AI edits in the workflow editor. They are session transports for the SaaS app, are not in the OpenAPI spec, and are not a stable integration surface. ### Public token routes [#public-token-routes] These routes take no session. The token or the published record in the path is the credential. | Path | Methods | Purpose | | ----------------------------------------------------------- | --------- | ---------------------------------------------------- | | `/public/calendars/{token}` | GET | iCalendar feed for a shared agenda calendar. | | `/public/time-off/{token}` | GET | iCalendar feed of approved team leave. | | `/public/chats/{token}` | GET | Read-only shared chat. | | `/public/files/{token}` | GET | Redirect to a signed download URL for a shared file. | | `/public/onboarding/{token}` | GET, POST | New-hire pre-boarding page and details form. | | `/public/onboarding/{token}/documents` | POST | Upload a pre-boarding document. | | `/public/portal/{orgSlug}/quotes/{quoteId}/request-changes` | POST | A customer requests changes to a sent quote. | | `/public/webhooks/{token}` | POST | Receive an incoming organization webhook. | The pre-boarding, quote change request and incoming webhook routes are rate limited and answer `429` when a caller sends too many requests. See [Customer portal](/product/customer-portal) and [Webhooks](/developers/webhooks). ### Provider webhooks [#provider-webhooks] | Path | Methods | Caller | | ------------------------- | ------- | ----------------------------------------------------- | | `/billing/stripe/webhook` | POST | Stripe, verified with the `stripe-signature` header. | | `/email/resend/webhook` | POST | Resend, verified with the `svix-*` signature headers. | | `/email/supabase/auth` | POST | The Supabase Auth send-email hook. | `/organizations/{organizationId}/channels/{connectionId}/webhook` (listed above) is the per-connection inbox channel callback. See [Stripe billing](/operations/stripe-billing). ### Scheduled jobs [#scheduled-jobs] Vercel Cron calls these `GET` routes with `Authorization: Bearer <CRON_SECRET>`. They answer `401` to any other caller. | Path | Schedule (UTC) | | --------------------------------------- | ---------------- | | `/internal/webhook-deliveries` | Every minute | | `/internal/channel-webhook-jobs` | Every minute | | `/internal/files/zip-exports` | Every minute | | `/internal/inventory/signals` | Every minute | | `/internal/google-calendar-sync` | Every 5 minutes | | `/workflows/scheduler/watchdog` | Every 5 minutes | | `/internal/approvals/sweep` | Every 10 minutes | | `/internal/files/upload-reconciliation` | Every 15 minutes | | `/workflows/audit-log/retention` | Daily at 02:17 | | `/internal/people/daily` | Daily at 06:07 | | `/internal/commerce/daily` | Daily at 06:17 | `/admin/subscriptions/{orgSlug}/invoices/{invoiceId}/pdf` serves subscription invoice PDFs to platform administrators with `system.billing.view`. ## Errors [#errors] Every error is an RFC 9457 problem details document served as `application/problem+json`, with a stable `code` and a `messageKey`. See [Errors](/developers/errors). ## Generated client [#generated-client] The OpenAPI spec is committed at `apps/api/public/openapi.json`. Inside the monorepo, `@workspace/api-client` wraps a Hey API client generated from that spec, and the CLI and the MCP server both use it. See [OpenAPI](/developers/openapi). ## Related pages [#related-pages] * [Authentication](/developers/authentication) * [Errors](/developers/errors) * [Webhooks](/developers/webhooks) * [MCP server](/developers/mcp) * [CLI](/developers/cli) * [OpenAPI](/developers/openapi) * [Service layers](/architecture/service-layers) # Authentication (/developers/authentication) How the CentraKit API authenticates browser sessions and bearer tokens, and how clients get tokens through Supabase OAuth 2.1, PKCE and dynamic client registration. The CentraKit API trusts one identity provider: Supabase Auth. A request is authenticated either by a Supabase session cookie (browsers) or by a Supabase access token sent as a bearer token (scripts, the CLI and MCP clients). Both paths end in the same request context, so permissions, Row Level Security, audit logging and errors behave the same way. ## Session cookies [#session-cookies] The SaaS app signs users in with Supabase and stores the session in the `sb-auth-token` cookie. Supabase splits large sessions into chunked cookies such as `sb-auth-token.0`. When a request to the API has no `Authorization: Bearer` header, the API reads this cookie session. Cookies suit same-site browser flows. Use bearer tokens for everything else. ## Bearer tokens [#bearer-tokens] Send a Supabase access token (a JWT) in the `Authorization` header: ```http GET /api/v1/users/me HTTP/1.1 Host: api.centrakit.com Authorization: Bearer <access-token> ``` Before the route runs, the API verifies: * the signature, against the Supabase JWKS at `https://<project-ref>.supabase.co/auth/v1/.well-known/jwks.json`; * the issuer, which must be `https://<project-ref>.supabase.co/auth/v1`; * the expiry; * the audience, which must be Supabase's default `authenticated`, the MCP resource URL `https://api.centrakit.com/api/mcp`, or the `MCP_RESOURCE_AUDIENCE` value a deployment configures. As soon as the request has a `Bearer` authorization scheme, the cookie is ignored. An invalid or expired token gets `401` with an `AUTH_INVALID_TOKEN` problem and a `WWW-Authenticate` header that points to the protected resource metadata. The API does not fall back to the cookie session. ### Scopes on bearer tokens [#scopes-on-bearer-tokens] Supabase access tokens usually carry no CentraKit scopes. When a token does carry `mcp:read` or `mcp:write` in its `scope` claim, the REST API enforces them: | Request | Required scope | | ---------------------------------------------------------------------------------- | -------------- | | `GET` and `HEAD` | `mcp:read` | | `POST /organizations/{organizationId}/tasks` and `PATCH .../tasks/{taskId}/status` | `mcp:read` | | Every other method | `mcp:write` | A token without the required scope gets `403` with code `AUTH_INSUFFICIENT_SCOPE`. Scopes only narrow what a token can do. The user's organization permissions still decide every request. The MCP server applies its own, stricter per-tool scope check; see [MCP server](/developers/mcp). ## Getting a token [#getting-a-token] ### CLI login with PKCE [#cli-login-with-pkce] `centrakit login` runs an OAuth 2.1 authorization code flow with PKCE against Supabase: 1. The CLI creates a PKCE verifier and challenge and a random `state`. 2. It opens `https://<project-ref>.supabase.co/auth/v1/oauth/authorize` in your browser with the challenge and a loopback redirect URI, `http://127.0.0.1:8976/callback` by default. 3. You complete the Supabase authorization step for your CentraKit account in the browser. 4. Supabase redirects to the loopback server. The CLI checks `state` and exchanges the code and verifier at `/auth/v1/oauth/token`. 5. The CLI calls `GET /api/v1/users/me` with the new token and stores the access and refresh tokens in `session.json` (file mode `600`). It refreshes the access token when it expires. Browser login needs an OAuth client ID (`CENTRAKIT_OAUTH_CLIENT_ID`) and the Supabase URL (`CENTRAKIT_SUPABASE_URL` or `NEXT_PUBLIC_SUPABASE_URL`). See [CLI](/developers/cli) for every setting. ### OAuth 2.1 for your own client [#oauth-21-for-your-own-client] The Supabase project runs an OAuth 2.1 authorization server with dynamic client registration enabled. A client that wants to call the API on behalf of a user: 1. Fetches the protected resource metadata (below) and reads `authorization_servers`. 2. Fetches that server's OAuth authorization server metadata to find the authorization, token and registration endpoints. 3. Registers itself through dynamic client registration, or uses a client ID it already has. 4. Runs the authorization code flow with PKCE, sending the user to the authorization endpoint and receiving the code on its redirect URI. 5. Sends the resulting access token as a bearer token and uses the refresh token to renew it. MCP clients do all of this automatically when you add the server. ### Headless and CI use [#headless-and-ci-use] For scripts and CI, pass an access token you obtained earlier: ```bash CENTRAKIT_TOKEN=<access-token> centrakit orgs list curl -H "Authorization: Bearer $CENTRAKIT_TOKEN" https://api.centrakit.com/api/v1/organizations ``` Access tokens expire. Long-running jobs need to refresh them with the refresh token from the same flow. Not yet available: organization or personal API keys. Use OAuth tokens instead. ## Protected resource metadata [#protected-resource-metadata] The API publishes RFC 9728 protected resource metadata: | Environment | URL | | ----------- | ---------------------------------------------------------------- | | Production | `https://api.centrakit.com/.well-known/oauth-protected-resource` | | Local | `https://api.localhost/.well-known/oauth-protected-resource` | ```json { "resource": "https://api.centrakit.com/api/mcp", "authorization_servers": ["https://<project-ref>.supabase.co/auth/v1"] } ``` The document adds `scopes_supported` only when the deployment sets `MCP_REQUIRED_SCOPES`. The endpoint answers CORS preflight requests so browser-based clients can read it. ## Error handling [#error-handling] Authentication failures are problem details with an `AUTH_*` code: | Code | Status | When | | ------------------------------------ | ------ | ----------------------------------------------------------- | | `AUTH_UNAUTHENTICATED` | 401 | No session and no bearer token on a route that needs one. | | `AUTH_INVALID_TOKEN` | 401 | The bearer token failed verification or expired. | | `AUTH_INVALID_CREDENTIALS` | 401 | Wrong email or password on `/auth/login`. | | `AUTH_INSUFFICIENT_SCOPE` | 403 | The token lacks a required `mcp:*` scope. | | `AUTH_CONFIRMATION_REQUIRED` | 403 | The account email is not confirmed yet. | | `AUTH_VALIDATION_ERROR` | 400 | The request body is invalid. | | `AUTH_PROVIDER_ERROR` | 502 | Supabase Auth failed. | | `AUTH_RESOURCE_SERVER_MISCONFIGURED` | 500 | The deployment's audience or issuer configuration is wrong. | A valid user without the organization permission for a route gets `403` with `ORGANIZATION_PERMISSION_DENIED`. See [Errors](/developers/errors). ## Related pages [#related-pages] * [Authentication architecture](/architecture/authentication) * [RBAC](/architecture/rbac) * [REST API](/developers/api) * [MCP server](/developers/mcp) * [CLI](/developers/cli) * [Environment variables](/operations/environment-variables) # CLI (/developers/cli) Every centrakit CLI command and global flag, how it signs in and resolves the API base URL, its configuration files and environment variables, and how releases are packaged. The `centrakit` CLI calls the CentraKit REST API from a terminal, a script or CI. It signs in with a browser PKCE flow or takes a bearer token, and it can call any OpenAPI operation by its ID. The source lives in `apps/cli`. ## Install [#install] The CLI needs Node.js 24 or later. Each published GitHub release has the packaged CLI tarball attached by the CLI release workflow. Install it globally with npm: ```bash npm install -g ./centrakit-<version>.tgz centrakit --version ``` Inside the monorepo, build and run it with: ```bash pnpm --filter centrakit build pnpm --filter centrakit cli -- --help ``` ## Global flags [#global-flags] Global flags go before or after the command name. | Flag | Effect | | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `--base-url <url>` | Use this API base URL. Include `/api`, for example `https://api.localhost/api`. | | `--json` | Print output as indented JSON. Also turns off prompts. | | `--token <token>` | Use this bearer token instead of the stored session. | | `--debug` | Print stack traces for errors. Also set by `CENTRAKIT_CLI_DEBUG=1`. | | `--no-color` | Disable ANSI colors. Also set by `NO_COLOR`. Colors are also off when output is not a terminal. | | `--non-interactive` | Never prompt or open a browser. Also set by `CI=true` or `CENTRAKIT_CLI_NON_INTERACTIVE=1`. Pass `--no-non-interactive` or `--non-interactive=false` to override the environment. | | `-V`, `--version` | Print the CLI version. | | `-h`, `--help` | Print help for the CLI or a command, for example `centrakit orgs create --help`. | An unknown command fails with a suggestion, for example ``Unknown command `org`. Did you mean `orgs`?``. This also works for subcommands, such as `centrakit orgs lst`. ## Interactive use [#interactive-use] When the CLI runs in a terminal, it asks for values you leave out instead of failing: * `register` and `forgot-password` ask for the email, and `register` also asks for the first name, last name and password. * Commands that take an `<organizationId>` show a picker of your organizations. * `logout` and `orgs delete` ask for confirmation. Pass `--yes` (or `-y`) to skip it. * `login` shows a spinner while it waits for the browser. Prompts never appear when standard input or output is not a terminal, when `--json` is set, or in non-interactive mode. In those cases a missing value fails with a message such as `Missing the EMAIL argument.`, and confirmations are skipped. Pressing Ctrl+C at a prompt prints `Cancelled.` and exits with status 130. ## Commands [#commands] | Command | What it does | | ---------------------------------------------- | ----------------------------------------------------- | | `login` | Sign in with the browser and store a local session. | | `logout` | Delete the stored session. | | `register <email>` | Create an account. | | `forgot-password <email>` | Send a password reset email. | | `whoami` | Show the authenticated user. | | `health` | Show the API health status. | | `api <operationId>` | Call any OpenAPI operation directly. | | `orgs list` | List organizations you can access. | | `orgs get <organizationId>` | Show one organization. | | `orgs create` | Create an organization. | | `orgs update <organizationId>` | Update organization settings. | | `orgs delete <organizationId>` | Delete an organization. | | `orgs switch <organizationId>` | Set your preferred active organization. | | `orgs branding logo set <organizationId>` | Upload the organization logo. | | `orgs branding logo remove <organizationId>` | Remove the organization logo. | | `orgs branding images add <organizationId>` | Upload a brand image. | | `orgs branding images remove <organizationId>` | Remove a brand image. | | `users me` | Show the authenticated user. Same output as `whoami`. | Every `<organizationId>` must be the organization UUID. The CLI rejects anything else before calling the API. In a terminal you can leave it out and pick the organization from a list. ### login [#login] ```bash centrakit login ``` Opens the Supabase OAuth authorization page in your browser, waits for the redirect on a loopback server, exchanges the code with PKCE, and stores the session. The CLI always prints the authorization URL, so you can open it yourself when the browser does not start. The command fails when: * `--non-interactive`, `CI=true` or `CENTRAKIT_CLI_NON_INTERACTIVE=1` is set; * `--token` or `CENTRAKIT_TOKEN` is set; * `CENTRAKIT_OAUTH_CLIENT_ID` or the Supabase URL is missing; * the callback port is already in use. See [Authentication](/developers/authentication) for the flow. ### logout [#logout] ```bash centrakit logout ``` Asks for confirmation, then deletes `session.json`. Pass `--yes` to skip the question. Tokens already issued stay valid until they expire. ### register and forgot-password [#register-and-forgot-password] ```bash centrakit register centrakit register ada@example.com --first-name Ada --last-name Lovelace printf '%s' "$PASSWORD" | centrakit register ada@example.com --first-name Ada --last-name Lovelace --password-stdin centrakit forgot-password ada@example.com centrakit forgot-password ada@example.com --redirect-path /reset-password ``` `register` never takes the password as an argument, so it does not end up in your shell history. In a terminal it asks for the password without echoing it. In scripts, pipe it in with `--password-stdin`; one trailing newline is removed. `--redirect-path` is the app path the recovery link opens. It defaults to `/reset-password`. ### whoami, users me and health [#whoami-users-me-and-health] ```bash centrakit whoami centrakit users me --json centrakit health ``` ### api [#api] ```bash centrakit api <operationId> [--path <json>] [--query <json>] [--body <json>] ``` Calls any operation from the OpenAPI spec by its operation ID and always prints JSON. Each option takes a JSON object: ```bash centrakit api listCustomers \ --path '{"organizationId":"1f0c2d3e-4b5a-4c6d-8e7f-9a0b1c2d3e4f"}' \ --query '{"limit":20,"q":"acme"}' centrakit api setTaskStatus \ --path '{"organizationId":"1f0c2d3e-4b5a-4c6d-8e7f-9a0b1c2d3e4f","taskId":"7a8b9c0d-1e2f-4a3b-8c4d-5e6f7a8b9c0d"}' \ --body '{"status":"done","position":0}' ``` Find operation IDs on [OpenAPI](/developers/openapi) or in [REST API](/developers/api). ### orgs list, get, delete and switch [#orgs-list-get-delete-and-switch] ```bash centrakit orgs list centrakit orgs get 1f0c2d3e-4b5a-4c6d-8e7f-9a0b1c2d3e4f --json centrakit orgs switch 1f0c2d3e-4b5a-4c6d-8e7f-9a0b1c2d3e4f centrakit orgs delete 1f0c2d3e-4b5a-4c6d-8e7f-9a0b1c2d3e4f ``` `orgs delete` asks for confirmation unless you pass `--yes`. `orgs switch` stores a preferred active organization on your profile, which clients can use as a default. It does not change which organization later CLI commands use, because organization routes always take the organization ID explicitly. ### orgs create and update [#orgs-create-and-update] ```bash centrakit orgs create \ --name "Green Acre Service" \ --slug green-acre \ --website https://greenacre.example \ --address-city Utrecht \ --address-country NL \ --social "LinkedIn|https://linkedin.com/company/greenacre" \ --primary-color "#1F7A4D" ``` | Option | Required | Value | | ------------------------------- | -------- | -------------------------------------------------------------- | | `--name <name>` | Yes | Organization name. | | `--slug <slug>` | Yes | URL slug. | | `--website <url>` | No | Website URL. | | `--address-line1 <value>` | No | Address line 1. | | `--address-line2 <value>` | No | Address line 2. | | `--address-city <value>` | No | City. | | `--address-state <value>` | No | State or province. | | `--address-postal-code <value>` | No | Postal code. | | `--address-country <value>` | No | Country. | | `--social <label\|url>` | No | A social link as `label\|url`. Repeat the flag for more links. | | `--primary-color <hex>` | No | Primary brand color. | | `--secondary-color <hex>` | No | Secondary brand color. | `orgs update <organizationId>` takes the same options, and `--name` and `--slug` are required there too. The API requires the full settings object, so any option you leave out is cleared, as are the phone, IBAN, VAT and Chamber of Commerce fields, which these flags cannot set. To keep existing values, read them with `orgs get`, then send the complete object with `centrakit api updateOrganization --path '{"organizationId":"..."}' --body '<json>'`. ### orgs branding [#orgs-branding] ```bash centrakit orgs branding logo set 1f0c2d3e-4b5a-4c6d-8e7f-9a0b1c2d3e4f --file ./logo.png centrakit orgs branding logo remove 1f0c2d3e-4b5a-4c6d-8e7f-9a0b1c2d3e4f centrakit orgs branding images add 1f0c2d3e-4b5a-4c6d-8e7f-9a0b1c2d3e4f --file ./hero.webp centrakit orgs branding images remove 1f0c2d3e-4b5a-4c6d-8e7f-9a0b1c2d3e4f --image-id <brandImageId> ``` `--file` must be a `.png`, `.jpg`, `.jpeg` or `.webp` file; the CLI picks the content type from the extension. `images remove` requires `--image-id`. ## Authentication [#authentication] The CLI picks a token in this order: 1. `--token <token>`. 2. `CENTRAKIT_TOKEN`. 3. The stored session from `centrakit login`. When its access token has expired, the CLI refreshes it with the refresh token and saves the new session. If the refresh fails, the request is sent without a token. ```bash CENTRAKIT_TOKEN=<access-token> centrakit orgs list centrakit --token <access-token> orgs list --json ``` ## API base URL [#api-base-url] The CLI resolves the base URL in this order: 1. `--base-url <url>`. 2. `CENTRAKIT_API_URL`. 3. `apiUrl` from the config files, see [Configuration](#configuration). 4. `https://api.localhost/api` when `NODE_ENV` is `development`, otherwise `https://api.centrakit.com/api`. Always include `/api` in the base URL, because the CLI appends paths such as `/v1/health`. ## Configuration [#configuration] The config directory is `~/.config/centrakit`, or `CENTRAKIT_CONFIG_DIR` when set. It holds: | File | Contents | | -------------- | -------------------------------------------------------------------------------------------------- | | `session.json` | Access token, refresh token, expiry and user from `centrakit login`. Written with file mode `600`. | | `config.json` | Optional defaults. Only `apiUrl` is supported. | Settings can also come from rc and config files. The CLI merges them in this order, highest first: 1. `centrakit.config.ts` (or `.mts`, `.js`, `.mjs`, `.json`) in the working directory. 2. `.centrakitrc` in the working directory. 3. `.centrakitrc` in `$XDG_CONFIG_HOME`, or in your home directory when `XDG_CONFIG_HOME` is not set. 4. `config.json` in the config directory. Every source accepts the same keys, and only `apiUrl` is supported. An unknown key or an invalid URL fails with `Invalid CLI config`. ```json { "apiUrl": "https://api.localhost/api" } ``` An rc file uses `key=value` lines: ```ini apiUrl=https://api.localhost/api ``` When the project installs `centrakit` as a dependency, a TypeScript config file can import `defineConfig` for type checking: ```ts import { defineConfig } from "centrakit/config"; export default defineConfig({ apiUrl: "https://api.localhost/api", }); ``` ### Environment variables [#environment-variables] | Variable | Purpose | | ------------------------------- | --------------------------------------------------------------------------- | | `CENTRAKIT_TOKEN` | Bearer token to use instead of the stored session. | | `CENTRAKIT_API_URL` | API base URL. | | `CENTRAKIT_CONFIG_DIR` | Config directory. | | `CENTRAKIT_OAUTH_CLIENT_ID` | OAuth client ID for `login`. Required for browser login. | | `CENTRAKIT_SUPABASE_URL` | Supabase project URL for `login`. Falls back to `NEXT_PUBLIC_SUPABASE_URL`. | | `CENTRAKIT_OAUTH_SCOPES` | Requested scopes. Defaults to `openid email profile`. | | `CENTRAKIT_OAUTH_REDIRECT_HOST` | Loopback callback host. Defaults to `127.0.0.1`. | | `CENTRAKIT_OAUTH_REDIRECT_PORT` | Loopback callback port. Defaults to `8976`. | | `CENTRAKIT_CLI_NON_INTERACTIVE` | `1` enables non-interactive mode. | | `CENTRAKIT_CLI_DEBUG` | `1` prints stack traces for errors. | | `CI` | `true` enables non-interactive mode. | | `NO_COLOR` | Any non-empty value disables colors. | The OAuth client must allow the redirect URI `http://<host>:<port>/callback`, which is `http://127.0.0.1:8976/callback` by default. ## Output and exit codes [#output-and-exit-codes] Commands print plain text for strings and indented JSON for objects. `--json` always prints JSON. `orgs list` prints the organization array without the pagination wrapper. When an API call fails, the CLI prints the problem `detail` (or `title`) and the code to stderr, for example `The organization could not be found. (ORGANIZATION_NOT_FOUND)`, and exits with status 1. Help and `--version` exit with 0, and a cancelled prompt exits with 130. See [Errors](/developers/errors). ## Releases [#releases] CLI releases go through Changesets. When a GitHub release is published, the Release CLI Assets workflow generates the OpenAPI artifacts, lints, typechecks and builds the CLI, packs it with `pnpm pack`, checks that the tarball contains `dist/cli.js`, and uploads the tarball to the release. A maintainer can rerun it manually for an existing release tag. See [CI and release](/operations/ci-and-release). ## Related pages [#related-pages] * [Authentication](/developers/authentication) * [REST API](/developers/api) * [MCP server](/developers/mcp) * [OpenAPI](/developers/openapi) # Errors (/developers/errors) The RFC 9457 problem details format that every CentraKit API and MCP error uses, how codes and statuses are derived, and how to handle APPROVAL_REQUIRED. Every CentraKit API error is an [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457) problem details document served with `Content-Type: application/problem+json` and `Cache-Control: private, no-store`. MCP tool errors carry the same document, so clients can branch on one set of codes. ## Shape [#shape] ```json { "type": "https://docs.centrakit.com/developers/errors#CUSTOMER_NOT_FOUND", "title": "Customer not found", "status": 404, "detail": "The customer could not be found.", "code": "CUSTOMER_NOT_FOUND", "messageKey": "customer.error.notFound" } ``` | Member | Meaning | | ------------ | ------------------------------------------------------------------------------------------------------- | | `type` | URI that identifies the problem. Always `https://docs.centrakit.com/developers/errors#<code>`. | | `title` | Short summary derived from the code, such as `Customer not found`. It is the same for every occurrence. | | `status` | HTTP status code, repeated in the body. | | `detail` | Human-readable explanation of this occurrence. Optional, and not stable. | | `instance` | URI reference for this occurrence. Optional. | | `code` | Stable machine code. Branch on this, not on `detail` or `title`. | | `messageKey` | Translation key a client can use to show localized copy. | | `errors` | Optional list of `{ "detail": "...", "pointer": "..." }` validation issues. | Some problems add extension members next to these, such as `approvalId`. The standard members always win over an extension with the same name. ## Codes and statuses [#codes-and-statuses] Domain services return tagged errors such as `CustomerNotFoundError`. The API turns the tag into the code by dropping `Error` and converting to upper snake case, so `CustomerNotFoundError` becomes `CUSTOMER_NOT_FOUND` with the message key `customer.error.notFound`. Unless a route sets the status explicitly, the status follows from the tag: | Tag contains | Status | Example code | | --------------------------------------------------------- | ------ | -------------------------------- | | `Validation` | 400 | `CUSTOMER_VALIDATION` | | `Unauthenticated` | 401 | `CRON_UNAUTHENTICATED` | | `Forbidden` or `PermissionDenied` | 403 | `ORGANIZATION_PERMISSION_DENIED` | | `NotFound` or `TokenInvalid` | 404 | `ORGANIZATION_NOT_FOUND` | | `Conflict`, `Expired`, `StateError` or `ApprovalRequired` | 409 | `APPROVAL_REQUIRED` | | `Unavailable` | 503 | `CRON_UNAVAILABLE` | | Anything else | 500 | `CUSTOMER_UNEXPECTED` | Routes set other statuses where a fixed mapping does not fit: | Code | Status | Where | | ------------------------------------------------- | -------------------------- | ----------------------------------------------------------------- | | `AUTH_*` | varies | Authentication. See [Authentication](/developers/authentication). | | `RATE_LIMITED` | 429 | Incoming webhooks, with a `retry-after` header. | | `PAYLOAD_TOO_LARGE` | 413 | Incoming webhook bodies over 256 KB. | | `NOT_FOUND`, `UNAUTHORIZED`, `DISABLED`, `FAILED` | 404, 401, 404, 500 | Incoming webhook receiver results. | | `API_UPSTREAM` | 502 or the upstream status | An MCP tool got a non-problem error from the REST API. | Pre-boarding links and the portal quote change request also answer `429` when a caller sends too many requests. ### AI chat transport codes [#ai-chat-transport-codes] The assistant transports under `/api/v1/ai/chat` use the same problem format with lowercase codes, for example `rate_limited` (429, with `Retry-After`), `billing_limit_reached`, `model_unavailable`, `unsupported_attachment` and `stale_message_delta` (409). These routes serve the SaaS app and are not part of the public API. ### Agent run approvals [#agent-run-approvals] `POST /api/v1/organizations/{organizationId}/ai/agent-runs/{runId}/approvals/{approvalId}` adds `continued` and `decided` booleans to its problems, so the client knows whether the run moved on. ## APPROVAL\_REQUIRED [#approval_required] An organization can require approval before certain actions run. When a mutation waits on an approval policy, it answers `409` with code `APPROVAL_REQUIRED` and an `approvalId`: ```json { "type": "https://docs.centrakit.com/developers/errors#APPROVAL_REQUIRED", "title": "Approval required", "status": 409, "detail": "This order needs approval before it can be confirmed.", "code": "APPROVAL_REQUIRED", "messageKey": "order.error.approvalRequired", "approvalId": "0f1d2c3b-4a5e-4f60-8a7b-9c0d1e2f3a4b" } ``` Over REST, the order, shipment and purchase order routes run the policy themselves: creating, confirming and cancelling an order, shipping a shipment, and creating or receiving a purchase order. Over MCP, every mutation tool that maps to an action in the approval catalog runs the policy before it calls the REST route. Those problems also include: | Member | Meaning | | -------------------- | --------------------------------------------------------------------------------------------------------------- | | `requirement` | `confirm` when the user has to confirm it in Notifications, or `authorize` when someone else has to approve it. | | `approverPermission` | The permission an approver needs, or `null`. | To handle it, tell the user who has to act, wait until the request is decided, and send the same request with the same input again. See [Approvals](/product/approvals). ## Reading errors in TypeScript [#reading-errors-in-typescript] Inside the monorepo, `@workspace/foundation/api/problem-details` reads and validates problem documents: ```ts import { readProblemDetails } from "@workspace/foundation/api/problem-details"; const response = await fetch(url, { headers }); if (!response.ok) { const problem = await readProblemDetails(response); if (problem?.code === "APPROVAL_REQUIRED") { console.info("Waiting for approval", problem.approvalId); } } ``` The generated `@workspace/api-client` SDK returns the problem as `result.error`. Use `getApiProblem(result.error)` or `getApiErrorMessageKey(result.error)` from `@workspace/api-client/client` to read it. Any other client can parse the JSON body and branch on `code`. ## MCP [#mcp] A failed MCP tool call returns `isError: true`. Its `structuredContent` is the problem document, and its text content is the same document as JSON. For `APPROVAL_REQUIRED`, the `detail` already tells the agent to call the tool again with the same input once the request is approved. ## CLI [#cli] The CLI prints the problem `detail` (or `title`) followed by the code, for example `The organization could not be found. (ORGANIZATION_NOT_FOUND)`, and exits with status 1. ## Related pages [#related-pages] * [REST API](/developers/api) * [Authentication](/developers/authentication) * [MCP server](/developers/mcp) * [Approvals](/product/approvals) # MCP server (/developers/mcp) Connect AI clients to the CentraKit product MCP server and the docs MCP server, with OAuth setup, scopes, approvals and the full list of 74 product tools. CentraKit runs two MCP servers: * The product server at `https://api.centrakit.com/api/mcp` acts on your organization data with your permissions. It needs OAuth. * The docs server at `https://docs.centrakit.com/mcp` reads these docs. It is public and read-only. ## Connect [#connect] | Server | Cursor | VS Code | Claude Code | | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | | Product API | [Add to Cursor](cursor://anysphere.cursor-deeplink/mcp/install?name=centrakit\&config=eyJ1cmwiOiJodHRwczovL2FwaS5jZW50cmFraXQuY29tL2FwaS9tY3AifQ==) | [Add to VS Code](vscode:mcp/install?%7B%22name%22%3A%22centrakit%22%2C%22type%22%3A%22http%22%2C%22url%22%3A%22https%3A//api.centrakit.com/api/mcp%22%7D) | `claude mcp add --transport http centrakit https://api.centrakit.com/api/mcp` | | Docs | [Add to Cursor](cursor://anysphere.cursor-deeplink/mcp/install?name=centrakit-docs\&config=eyJ1cmwiOiJodHRwczovL2RvY3MuY2VudHJha2l0LmNvbS9tY3AifQ==) | [Add to VS Code](vscode:mcp/install?%7B%22name%22%3A%22centrakit-docs%22%2C%22type%22%3A%22http%22%2C%22url%22%3A%22https%3A//docs.centrakit.com/mcp%22%7D) | `claude mcp add --transport http centrakit-docs https://docs.centrakit.com/mcp` | The product server starts an OAuth sign-in on first use. The docs server needs no authentication. ## Docs MCP server [#docs-mcp-server] The docs server at `https://docs.centrakit.com/mcp` (locally `https://docs.localhost/mcp`) is built on `fumadocs-core/mcp` and has three read-only tools: | Tool | Input | Returns | | ------------ | ------------------------------------------------ | ------------------------------------------------ | | `list_pages` | none | The page index, the same content as `/llms.txt`. | | `search` | `query`, optional `locale` | Matching docs sections with their page URLs. | | `get_page` | `url`, a page pathname such as `/developers/mcp` | The page as Markdown. | A typical agent calls `list_pages` or `search` first, then `get_page` for the pages it needs. The same content is available without MCP; see [Docs for AI agents](/developers/ai-agents). ## Transport [#transport] The product server speaks the stateless MCP `2026-07-28` protocol over Streamable HTTP, and serves clients that use the 2025-era Streamable HTTP protocol through the same endpoint. | Environment | Endpoint | | ----------- | ----------------------------------- | | Production | `https://api.centrakit.com/api/mcp` | | Local | `https://api.localhost/api/mcp` | The current protocol has no transport sessions and no `Mcp-Session-Id`. Legacy HTTP+SSE endpoints such as `/api/sse` and `/api/message` do not exist. Clients can call `server/discover` for the supported protocol versions, server identity (`centrakit-api`) and capabilities. `server/discover` and `tools/list` responses carry a one-hour public cache hint because the catalog only changes with a deployment. Tool results are never cached. ## Authentication [#authentication] Every request to the product server needs a bearer token. 1. An unauthenticated request gets `401` with a `WWW-Authenticate` header that points to `https://api.centrakit.com/.well-known/oauth-protected-resource`. 2. That metadata names the Supabase OAuth 2.1 authorization server, `https://<project-ref>.supabase.co/auth/v1`. 3. Dynamic client registration is enabled, so the MCP client registers itself, runs the authorization code flow with PKCE in your browser, and receives an access token. 4. The client sends the token on every request. The server verifies the token's issuer, signature, expiry and audience. Supabase OAuth tokens usually keep the default `authenticated` audience, and the server accepts it as well as the resource URL `https://api.centrakit.com/api/mcp` and any `MCP_RESOURCE_AUDIENCE` the deployment configures. See [Authentication](/developers/authentication). ### Scopes [#scopes] Each tool requires one OAuth scope, which must be present in the token's `scope` claim: | Tool kind | Required scope | | --------- | -------------- | | Read | `mcp:read` | | Mutation | `mcp:write` | Every mutation tool requires `mcp:write`; there are no exceptions. A call without the scope fails with `OAuth scope mcp:write is required for this MCP tool.` The scope only allows the call to start. The REST route then applies your organization permissions, so a token can never do more than its user. ### Tokens from the CLI and CI [#tokens-from-the-cli-and-ci] For local agent work, `centrakit login` runs the same PKCE flow and stores a refreshable session. For CI and hosts that inject their own credentials, pass a bearer token directly (`centrakit --token <access-token>` or `CENTRAKIT_TOKEN`). See [CLI](/developers/cli). ## Configure a client [#configure-a-client] ### Direct connection [#direct-connection] Hosts that support Streamable HTTP and OAuth connect to the URL directly: ```json { "mcpServers": { "centrakit": { "url": "https://api.centrakit.com/api/mcp" } } } ``` ### `mcp-remote` [#mcp-remote] Hosts that only launch local commands can bridge through `mcp-remote`, which also handles the OAuth flow: ```json { "mcpServers": { "centrakit": { "command": "npx", "args": ["-y", "mcp-remote", "https://api.centrakit.com/api/mcp"] } } } ``` ### Client config files [#client-config-files] | Client | Config file | Notes | | -------------- | ----------------------------------------------------------------- | ----------------------------------------------------- | | Cursor | `~/.cursor/mcp.json` | Direct URL. Fall back to `mcp-remote` if OAuth fails. | | Claude Desktop | `~/Library/Application Support/Claude/claude_desktop_config.json` | Use `mcp-remote`. | | Windsurf | `~/.codeium/windsurf/mcp_config.json` | Direct URL or `mcp-remote`. | ## Tool input [#tool-input] Each tool maps to one REST operation and takes a nested input: ```json { "pathParams": { "organizationId": "1f0c2d3e-4b5a-4c6d-8e7f-9a0b1c2d3e4f" }, "query": { "limit": 50, "offset": 0, "q": "pump" }, "body": {} } ``` * `pathParams` holds the path parameters, such as `organizationId`, `taskId` or `quoteId`. Organization-scoped tools always need the organization UUID. No tool lists organizations and `getCurrentUser` returns only the user's ID, email and sign-in dates, so get the ID from the user, from `centrakit orgs list`, or from `GET /api/v1/organizations`. * `query` is optional and holds list filters and pagination. * `body` holds the request body for mutations. Inputs are validated against the generated operation schemas before the call, and the REST route validates them again. The server then calls the matching REST operation with the same bearer token, so validation, permissions, audit logging and errors match the REST API. Results include a text fallback and `structuredContent` when the API returns JSON. Each tool also publishes `_meta` entries `com.centrakit/operation-kind` (`read` or `mutate`) and `com.centrakit/required-oauth-scope`, plus annotations: `readOnlyHint` for reads, and `destructiveHint`, `openWorldHint` and `idempotentHint` for mutations, derived from the action catalog. ## Tools [#tools] The catalog is a deliberate allowlist of 74 REST operations: 41 read tools and 33 mutation tools. Tool names are the OpenAPI operation IDs. | Group | Read tools | Mutation tools | | ------------------------ | ---------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | | Account | `getApiHealthV1`, `getCurrentUser` | | | Tasks | `listTasks`, `getTask` | `createTask`, `updateTask`, `setTaskStatus` | | Customers and assets | `listCustomers`, `getCustomer`, `listCustomerAssets`, `getCustomerAsset` | `createCustomer`, `updateCustomer` | | Products | `listProducts`, `getProduct` | `createProduct`, `updateProduct` | | Quotes | `listQuotes`, `getQuote` | `createQuote`, `updateQuote` | | Tags | `listTags`, `getTag` | `createTag`, `updateTag` | | Orders | `listOrders`, `getOrder` | `createOrder`, `confirmOrder`, `cancelOrder` | | Shipments | `listShipments`, `getShipment` | `shipShipment` | | Purchase orders | `listPurchaseOrders`, `getPurchaseOrder` | `createPurchaseOrder`, `receivePurchaseOrder` | | Inventory and promotions | `listInventoryLevels`, `validatePromotion` | | | Agenda | `listAgendaItems`, `getAgendaItem` | `createAgendaItem`, `updateAgendaItem`, `deleteAgendaItem`, `moveAgendaItem` | | Files | `listFileDrives`, `listFileNodeChildren`, `getFileNode`, `createFileDownload` | | | Workflows | `listWorkflowDefinitions`, `getWorkflowDefinition`, `listWorkflowRuns`, `getWorkflowRun` | `startWorkflowRun` | | Knowledge and inbox | `retrieveKnowledge` | `sendInboxThreadReply` | | Webhook deliveries | `listWebhookDeliveries`, `getWebhookDelivery` | | | Webhook destinations | `listWebhookDestinations`, `getWebhookDestination` | `createWebhookDestination`, `updateWebhookDestination`, `deleteWebhookDestination`, `rotateWebhookDestinationSecret` | | Webhook endpoints | `listWebhookEndpoints`, `getWebhookEndpoint` | `createWebhookEndpoint`, `updateWebhookEndpoint`, `deleteWebhookEndpoint`, `rotateWebhookEndpointSecret`, `rotateWebhookEndpointToken` | | Audit log | `listOrganizationAuditEvents`, `getOrganizationAuditExport` | `createOrganizationAuditExport` | `createFileDownload` returns a signed download URL and `validatePromotion` checks a promotion code without changing anything, so both count as read tools. Operations that are not in this table are only available through the REST API. ## Approvals [#approvals] Mutation tools that map to an action in the approval catalog run the organization's approval policy before they call the API. Order, shipment and purchase order mutations run the policy inside the REST route instead, so one call raises one request. When a policy holds the call, the tool fails with code `APPROVAL_REQUIRED`, an `approvalId`, a `requirement` (`confirm` or `authorize`) and the `approverPermission` when one applies. Tell the user who has to act, and call the same tool with the same input once the request is approved. See [Approvals](/product/approvals) and [Errors](/developers/errors). ## Errors [#errors] A failed tool call returns `isError: true`. Its `structuredContent` is the REST problem details document, and the text content is the same document as JSON, so the `code` values match the API. ## Usage [#usage] Each organization-scoped tool call counts once on the organization's MCP usage meter. The REST request the tool makes is not counted again as an API request. ## MCP Apps and widgets [#mcp-apps-and-widgets] Not yet available: MCP Apps resources, prompts and interactive widgets. The product server exposes tools only. ## Troubleshooting [#troubleshooting] | Symptom | Likely cause | | ----------------------------------------------- | ---------------------------------------------------------------------------------------------------- | | `401 Unauthorized` on connect | No bearer token, an expired token, or a token whose audience the server does not accept. | | `OAuth scope ... is required for this MCP tool` | The token lacks `mcp:read` or `mcp:write`. | | Tool error `ORGANIZATION_PERMISSION_DENIED` | Your user lacks the organization permission for that operation. | | Tool error `APPROVAL_REQUIRED` | An approval policy holds the mutation. Retry after it is approved. | | OAuth discovery fails | `/.well-known/oauth-protected-resource` is unreachable or the authorization server is misconfigured. | | Local connection fails | The API app is not running on `https://api.localhost`. Start it with `pnpm dev:api:portless`. | ## Related pages [#related-pages] * [Docs for AI agents](/developers/ai-agents) * [Authentication](/developers/authentication) * [REST API](/developers/api) * [Errors](/developers/errors) * [Webhooks](/developers/webhooks) * [AI settings](/admin/ai-settings) # OpenAPI (/developers/openapi) Where the CentraKit OpenAPI 3.2 specification lives, how it is generated from route JSDoc and Zod schemas, and how the TypeScript client, reference pages and drift check are built from it. The CentraKit REST API is described by one OpenAPI 3.2 document. It is generated from the route handlers in `apps/api`, committed to git, and used to build the TypeScript client, the MCP tool catalog, the CLI `api` command and the API reference pages in these docs. ## Where to find it [#where-to-find-it] | Format | Location | | --------------------- | --------------------------------------------------------------------------------------- | | Raw spec | `https://api.centrakit.com/openapi.json`, committed as `apps/api/public/openapi.json` | | Interactive reference | `https://api.centrakit.com/api-docs`, a Scalar reference. The API root redirects there. | | Docs reference pages | The OpenAPI section of these docs, one page per operation. | The spec currently has 175 paths and 259 operations in 36 tags. Paths start with `/v1`, and the servers are `https://api.centrakit.com/api` and `https://api.localhost/api`, so full URLs look like `https://api.centrakit.com/api/v1/health`. The Scalar reference prefills your bearer token when you are signed in on the same domain, so you can try requests from the page. It shows operation IDs, which are the names used by the TypeScript client, the CLI and the MCP tools. ## Security schemes [#security-schemes] | Scheme | Meaning | | --------------- | ------------------------------------------------------------------- | | `SessionCookie` | The Supabase session cookie set by the SaaS app or the login route. | | `BearerAuth` | `Authorization: Bearer <access-token>`. | Most operations accept either. See [Authentication](/developers/authentication). ## How the spec is generated [#how-the-spec-is-generated] The pipeline has two steps, both run by one command: ```bash pnpm openapi:generate ``` 1. `pnpm --filter api openapi:generate` runs `openapi-gen generate` from `next-openapi-gen` with `apps/api/openapi-gen.config.mts`. It scans `apps/api/app/api` and writes `apps/api/public/openapi.json`. 2. `pnpm --filter @workspace/api-client generate` runs Hey API (`@hey-api/openapi-ts`) over that file and writes the client to `packages/api-client/src/generated`. ### Route JSDoc [#route-jsdoc] Only route handlers with an `@openapi` tag in their JSDoc are included. Request and response shapes come from Zod schemas, which the generator resolves by name from `apps/api/app/api` and the shared contract packages listed in the config's `schemaDir`. A typical handler: ```ts /** * List tags. * @description Return tags for an organization, optionally filtered by kind. * @operationId listTags * @tag Tags * @auth SessionCookie,bearer * @pathParams tagsParamsSchema * @queryParams tagsOpenApiListQuerySchema * @response 200:tagsListResponseSchema * @responseDescription Returns paginated tags. * @responseSet resource * @openapi */ export const GET = withApiRoute(/* ... */); ``` | Tag | Purpose | | ------------------------- | ------------------------------------------------------------------------------- | | `@operationId` | Stable operation name, used by the client, the CLI and MCP. | | `@tag` | Group in the reference. Tags are declared in the config. | | `@description` | Longer operation description. | | `@auth` | Accepted security schemes. | | `@pathParams` | Zod schema for path parameters. | | `@queryParams` | Zod schema for query parameters. | | `@body` or `@requestBody` | Zod schema for the request body. `@requestBody ... required` marks it required. | | `@bodyDescription` | Request body description. | | `@contentType` | Non-JSON request content type, such as `multipart/form-data` uploads. | | `@response` | Success status and Zod schema, such as `201:organizationTagSchema`. | | `@responseDescription` | Success response description. | | `@responseSet` | Which shared error responses to add. | | `@openapi` | Includes the handler in the spec. | ### Error responses [#error-responses] Error responses use problem details, `application/problem+json` with the `problemDetailsSchema` component. `@responseSet` picks the statuses that are added to an operation: | Set | Statuses | | --------------- | ---------------------------- | | `public` | 400, 500. The default. | | `auth` | 400, 401, 403, 500 | | `organizations` | 400, 401, 403, 404, 409, 500 | | `resource` | 400, 401, 403, 404, 409, 500 | Each shared response includes an example whose `type` points to a code on [Errors](/developers/errors). The generator runs with diagnostics set to fail on errors, so a broken schema reference stops generation. ## TypeScript client [#typescript-client] `@workspace/api-client` is the generated client. Hey API runs with three plugins: * `@hey-api/typescript` for request and response types; * `zod` for request and response schemas; * `@hey-api/sdk` for one function per operation, returning `{ data, error }` fields and validating requests with the Zod schemas. | Export | Contents | | ------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | | `@workspace/api-client` | `createApiClient`, `requestApiOperation`, `apiSdk`, types, Zod schemas and the operation catalog. | | `@workspace/api-client/generated/sdk` | The generated operation functions. | | `@workspace/api-client/generated/zod` | The generated Zod schemas. | | `@workspace/api-client/operations` | `getOperationById` and `getOperationCatalog`. | | `@workspace/api-client/mcp` | The MCP tool definitions and catalog, and `executeMcpApiOperation`, which the MCP server uses to call operations. | ```ts import { apiSdk, createApiClient } from "@workspace/api-client"; const client = createApiClient({ baseUrl: "https://api.centrakit.com/api", accessToken, }); const { data, error } = await apiSdk.listTasks({ client, path: { organizationId }, query: { limit: 50 }, }); ``` `requestApiOperation` calls an operation by its runtime operation ID. The CLI `api` command uses it. It is untyped on purpose, because Hey API does not emit an operation ID map that could type a dynamic call. The client package is internal to the monorepo and is not published to npm. External integrations can generate their own client from `openapi.json` with any OpenAPI 3.2 generator. ### Generation details [#generation-details] Hey API 0.99 needs the classic TypeScript compiler API, so the generate script loads `scripts/register-typescript6.mjs`, which redirects `typescript` imports to the `typescript6` package during generation only. The committed output is type-checked by the repository's TypeScript 7 compiler with strict settings, including `exactOptionalPropertyTypes`. The generated fetch runtime passes explicit `undefined` for a few optional fields. `scripts/postprocess-generated-runtime.mjs` replaces those known assignments in the generated `client` and `core` runtime files with conditional properties. The transform is idempotent, has its own tests, and fails generation when the upstream code no longer matches. The SDK functions, types and Zod schemas are not changed. ## Reference pages in these docs [#reference-pages-in-these-docs] The docs app reads `apps/api/public/openapi.json` with `fumadocs-openapi` and generates one page per operation under the OpenAPI section. Each page also has a Markdown version that contains only that page's operations and the component schemas they reference, which keeps it small enough for AI agents. See [Docs for AI agents](/developers/ai-agents). ## Keeping the spec in sync [#keeping-the-spec-in-sync] After you change route JSDoc, a route's Zod schema or a shared contract schema: 1. Run `pnpm openapi:generate`. 2. Check that the named components you touched are not emitted as `{}` in `openapi.json`. An empty component means the generator could not resolve the schema, usually because its file is outside `schemaDir`. 3. Update the MCP tool catalog if the operation is exposed there. See [MCP server](/developers/mcp). 4. Commit `apps/api/public/openapi.json` and `packages/api-client/src/generated` together with the route change. CI runs an OpenAPI drift check in the verify workflow: ```bash pnpm openapi:generate git diff --exit-code -- apps/api/public/openapi.json packages/api-client/src/generated ``` The check fails when the committed artifacts do not match a fresh generation. Run the same commands locally before you push. See [CI and release](/operations/ci-and-release). ## Known gaps [#known-gaps] * Some routes are not in the spec: the assistant and AI chat routes, the channel provider webhooks and the scheduled job routes. See [REST API](/developers/api). * Several list operations declare `limit` and `offset` as strings instead of integers, so the generated client types them as strings: `listTags`, `listCustomerAssets`, `listNotifications`, `listTemplates`, the webhook delivery, destination and endpoint lists, the workflow definition and run lists, and the inventory allocation, movement and stock record lists. `listOrganizationAuditEvents` has the same issue for `offset`. ## Related pages [#related-pages] * [REST API](/developers/api) * [Errors](/developers/errors) * [CLI](/developers/cli) * [MCP server](/developers/mcp) * [Docs for AI agents](/developers/ai-agents) # Webhooks (/developers/webhooks) Send signed CentraKit events to your HTTPS endpoints and receive external webhooks that start workflows, including signatures, secret and token rotation, delivery retries, and the REST and MCP operations. CentraKit has two kinds of webhooks per organization: * Outgoing destinations receive signed `POST` requests when events happen in CentraKit. * Incoming endpoints give an external system a URL to call. Each call publishes a `webhook.received` event that starts matching workflows. Manage both in the SaaS app under Integrations, on the Webhooks integration. It lists Incoming endpoints and Outgoing destinations, with New incoming webhook and New outgoing webhook actions that open `/{orgSlug}/integrations/webhooks/incoming/create` and `/{orgSlug}/integrations/webhooks/outgoing/create`. Everything is also available through the REST API and the MCP server. ## Key concepts [#key-concepts] | Concept | Description | | ----------- | ----------------------------------------------------------------------------------------------------------------------------------- | | Destination | An outgoing target: name, HTTPS URL, subscribed event kinds and an enabled flag. It has a `whsec_` signing secret. | | Endpoint | An incoming receiver: name, optional bound workflow definition and an enabled flag. It has a `wh_` URL token and a `whsec_` secret. | | Delivery | One queued outgoing request to a destination, with its attempts and the last observed response. | | Event kind | The event name a destination subscribes to, such as `invoice.paid` or `task.completed`. | ## Outgoing destinations [#outgoing-destinations] ### Create a destination [#create-a-destination] ```bash curl -X POST \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ https://api.centrakit.com/api/v1/organizations/$ORG_ID/webhooks/destinations \ -d '{ "name": "ERP sync", "url": "https://erp.example.com/hooks/centrakit", "eventKinds": ["invoice.paid", "order.shipped"] }' ``` The URL must use HTTPS. The `201` response includes the destination and its `secret`. Store the secret now: list and get responses never return it, and only an explicit rotation returns a new one. `eventKinds` defaults to an empty list. A destination with no event kinds receives no event deliveries; it only gets test sends and workflow webhook actions, and the overview shows it as Workflow only. ### Event kinds [#event-kinds] The destination form offers these event kinds, which are the same domain events workflows can trigger on: | Area | Event kinds | | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Quotes | `quote.sent`, `quote.viewed`, `quote.accepted`, `quote.rejected`, `quote.changes-requested`, `quote.expired`, `quote.withdrawn`, `quote.deposit-paid` | | Invoices | `invoice.sent`, `invoice.viewed`, `invoice.overdue`, `invoice.reminder-sent`, `invoice.partially-paid`, `invoice.paid`, `invoice.uncollectible`, `invoice.cancelled`, `invoice.payment-failed`, `invoice.disputed`, `invoice.reminders-paused`, `invoice.reminders-resumed` | | Orders | `order.created`, `order.confirmed`, `order.paid`, `order.backordered`, `order.shipped`, `order.ready-for-pickup`, `order.delivered`, `order.picked-up`, `order.fulfilled`, `order.invoiced`, `order.completed`, `order.returned`, `order.cancelled` | | Shipments | `shipment.shipped`, `shipment.out-for-delivery`, `shipment.ready-for-pickup`, `shipment.delivered`, `shipment.exception` | | Purchase orders | `purchase-order.sent`, `purchase-order.in-transit`, `purchase-order.partially-received`, `purchase-order.received`, `purchase-order.overdue`, `purchase-order.cancelled` | | Inventory | `inventory.low-stock`, `inventory.out-of-stock`, `inventory.back-in-stock`, `inventory.level-changed` | | Promotions | `promotion.expiring`, `promotion.limit-reached` | | Tasks | `task.created`, `task.assigned`, `task.status-changed`, `task.stage-changed`, `task.completed`, `task.due-soon`, `task.overdue` | | Agenda | `agenda.item.created`, `agenda.item.updated`, `agenda.item.deleted` | | Customers | `customer.created`, `customer.lookup.requested` | | Other | `knowledge.file.ingest-requested`, `product.lookup.requested`, `inbox.message.received`, `billing.usage-threshold`, `workflow.run.failed`, `webhook.received` | The API accepts any event kind string up to 160 characters, but only the kinds above are published. Two more kinds appear in delivery logs without a subscription: `webhook.test` for the Send test action on a destination, and `workflow.action` for a workflow step that sends a webhook to a destination. ### Request format [#request-format] Each delivery is a `POST` to the destination URL: ```http POST /hooks/centrakit HTTP/1.1 Content-Type: application/json Idempotency-Key: 9b2f0c1e-6a3d-4f7e-8b19-2c4d5e6f7a80 X-Centrakit-Timestamp: 1790000000 X-Centrakit-Signature: v1=5f2b...e91c {"invoiceId":"...","status":"paid"} ``` | Header | Value | | ----------------------- | ------------------------------------------------------------------------------------------ | | `Content-Type` | `application/json` | | `Idempotency-Key` | The delivery ID. It stays the same across retries of one delivery. | | `X-Centrakit-Timestamp` | Unix time in seconds when the request was signed. | | `X-Centrakit-Signature` | `v1=` followed by the hex HMAC-SHA256 of `{timestamp}.{body}` with the destination secret. | The body is the event payload as JSON. There is no envelope and no event-kind header. To tell event kinds apart, read identifying fields from the payload or create one destination per event kind. ### Verify the signature [#verify-the-signature] Compute the HMAC over the raw request body, compare in constant time, and reject old timestamps: ```ts import { createHmac, timingSafeEqual } from "node:crypto"; export function verifyCentrakitWebhook(input: { body: string; secret: string; signature: string | null; timestamp: string | null; }): boolean { if (!input.signature || !input.timestamp) return false; const age = Math.abs(Date.now() / 1000 - Number(input.timestamp)); if (!Number.isFinite(age) || age > 300) return false; const expected = `v1=${createHmac("sha256", input.secret) .update(`${input.timestamp}.${input.body}`) .digest("hex")}`; const a = Buffer.from(expected); const b = Buffer.from(input.signature); return a.length === b.length && timingSafeEqual(a, b); } ``` Use the `Idempotency-Key` header to ignore a delivery you already processed. ### Deliveries and retries [#deliveries-and-retries] Events are queued as deliveries for every enabled destination subscribed to the event kind. The `/api/v1/internal/webhook-deliveries` job runs every minute, claims up to 25 due deliveries, and sends them: | Outcome | Result | | --------------------------------------------- | ------------------------------- | | `2xx` response | `completed`. | | `429` or `5xx` response, or a network error | `failed`, retried with backoff. | | Any other `4xx` response | `dead_lettered`. No retry. | | URL blocked by the outbound request policy | `dead_lettered`. No retry. | | Destination disabled when the delivery is due | `canceled`. | | Destination or its secret cannot be loaded | `dead_lettered`. | A delivery gets at most 5 attempts. The waits after attempts 1 to 4 are 30, 60, 120 and 240 seconds, and the job picks the delivery up on its next run after that. A fifth failure dead-letters the delivery. CentraKit stores the response status, the first 2,000 characters of the response body, the error and the duration on each delivery. Delivery statuses are `pending`, `processing`, `completed`, `failed`, `dead_lettered` and `canceled`. In the app, the deliveries table on a destination has a Retry action that queues a delivery again right away. Retry does not reset the attempt count, so a delivery that already used 5 attempts is dead-lettered again if the next attempt fails. Send test queues a `webhook.test` delivery with `{ "kind": "webhook.test", "sentAt": "..." }`. Workflow webhook actions send their request immediately from the workflow run, signed the same way, and record it as a `workflow.action` delivery. ### Rotate a destination secret [#rotate-a-destination-secret] `POST /webhooks/destinations/{destinationId}/rotate-secret` replaces the secret and returns the new one once. Deliveries sent after rotation use the new secret, so update your receiver at the same time. ## Incoming endpoints [#incoming-endpoints] ### Create an endpoint [#create-an-endpoint] ```bash curl -X POST \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ https://api.centrakit.com/api/v1/organizations/$ORG_ID/webhooks/endpoints \ -d '{ "name": "Shop orders" }' ``` The `201` response includes the endpoint, its `token` and its `secret`. The receiving URL is: ```text https://api.centrakit.com/api/v1/public/webhooks/{token} ``` List and get responses return neither the token nor the secret. Only creation and the matching rotation return them. Pass `workflowDefinitionId` to bind the endpoint to one workflow. ### Send to an endpoint [#send-to-an-endpoint] The receiver accepts a `POST` with a body of at most 256 KB. Authenticate each request in one of two ways: 1. Signature (preferred): send `X-Centrakit-Timestamp` and `X-Centrakit-Signature: v1=<hex>`, the HMAC-SHA256 of `{timestamp}.{body}` with the endpoint secret. The timestamp must be within 5 minutes of the server clock. The header may list several comma-separated signatures, and the request is accepted when one matches. 2. Shared secret: for senders that can only set a fixed header, send the secret in `X-Webhook-Secret` or as `Authorization: Bearer <secret>`. When a signature header is present it must verify on its own; the shared secret is not used as a fallback. ```bash BODY='{"orderId":"ord_123"}' TS=$(date +%s) SIG=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$WEBHOOK_SECRET" -hex | sed 's/^.* //') curl -X POST "https://api.centrakit.com/api/v1/public/webhooks/$WEBHOOK_TOKEN" \ -H "Content-Type: application/json" \ -H "X-Centrakit-Timestamp: $TS" \ -H "X-Centrakit-Signature: v1=$SIG" \ -H "Idempotency-Key: ord_123" \ -d "$BODY" ``` A valid request answers `202`: ```json { "ok": true, "eventId": "5b657e4b-6f4b-4e5f-8c03-c0bc0d3f5f4a", "runId": "6c768f5c-7f5c-4f60-9d14-d1cd1e40665b" } ``` `runId` is `null` when no workflow started. Send an `Idempotency-Key` header to deduplicate retries from your side. ### What happens with the payload [#what-happens-with-the-payload] * A JSON object body is used as is. Other JSON values are wrapped as `{ "body": <value> }`, and non-JSON text as `{ "body": "<text>" }`. * CentraKit adds `endpointId` and publishes a `webhook.received` event for the organization. * Workflows with a webhook trigger start from that event. If the endpoint is bound to a workflow definition that did not already match, its published version starts too. * Outgoing destinations subscribed to `webhook.received` receive the payload as well. Each endpoint tracks `receiveCount`, `lastReceivedAt` and `lastStatus` (`accepted`, `unauthorized`, `disabled` or `failed`). ### Receiver errors [#receiver-errors] | Status | Code | Cause | | ------ | ------------------- | -------------------------------------------------------------------------------------------------------------- | | 401 | `UNAUTHORIZED` | Missing or wrong signature or secret, or a stale timestamp. | | 404 | `NOT_FOUND` | Unknown token. | | 404 | `DISABLED` | The endpoint is disabled. | | 413 | `PAYLOAD_TOO_LARGE` | The body is larger than 256 KB. | | 429 | `RATE_LIMITED` | More than 60 requests per minute for one token and client IP. The `retry-after` header says when to try again. | | 500 | `FAILED` | The event could not be published. | ### Rotate the secret or the token [#rotate-the-secret-or-the-token] * `POST /webhooks/endpoints/{endpointId}/rotate-secret` returns a new secret once. Senders must switch to it. * `POST /webhooks/endpoints/{endpointId}/rotate-token` returns a new token and invalidates the previous URL immediately. ## Permissions [#permissions] | Permission | Allows | | ------------------------------ | ------------------------------------------------------------------ | | `organization.webhooks.view` | Listing and reading destinations, endpoints and deliveries. | | `organization.webhooks.create` | Creating destinations and endpoints. | | `organization.webhooks.update` | Updating, rotating secrets and tokens, sending tests and retrying. | | `organization.webhooks.delete` | Deleting destinations and endpoints. | ## Automation and AI [#automation-and-ai] The [MCP server](/developers/mcp) exposes every webhook operation as a tool: * Deliveries: `listWebhookDeliveries`, `getWebhookDelivery` * Destinations: `listWebhookDestinations`, `createWebhookDestination`, `getWebhookDestination`, `updateWebhookDestination`, `deleteWebhookDestination`, `rotateWebhookDestinationSecret` * Endpoints: `listWebhookEndpoints`, `createWebhookEndpoint`, `getWebhookEndpoint`, `updateWebhookEndpoint`, `deleteWebhookEndpoint`, `rotateWebhookEndpointSecret`, `rotateWebhookEndpointToken` The mutation tools require the `mcp:write` scope and can be held for approval. See [Workflows](/product/workflows) for webhook triggers and the send-webhook action. ## API [#api] Paths are relative to `/api/v1/organizations/{organizationId}`. | Operation | Method and path | | -------------------------------- | ------------------------------------------------------------ | | `listWebhookDestinations` | `GET /webhooks/destinations` | | `createWebhookDestination` | `POST /webhooks/destinations` | | `getWebhookDestination` | `GET /webhooks/destinations/{destinationId}` | | `updateWebhookDestination` | `PATCH /webhooks/destinations/{destinationId}` | | `deleteWebhookDestination` | `DELETE /webhooks/destinations/{destinationId}` | | `rotateWebhookDestinationSecret` | `POST /webhooks/destinations/{destinationId}/rotate-secret` | | `listWebhookEndpoints` | `GET /webhooks/endpoints` | | `createWebhookEndpoint` | `POST /webhooks/endpoints` | | `getWebhookEndpoint` | `GET /webhooks/endpoints/{endpointId}` | | `updateWebhookEndpoint` | `PATCH /webhooks/endpoints/{endpointId}` | | `deleteWebhookEndpoint` | `DELETE /webhooks/endpoints/{endpointId}` | | `rotateWebhookEndpointSecret` | `POST /webhooks/endpoints/{endpointId}/rotate-secret` | | `rotateWebhookEndpointToken` | `POST /webhooks/endpoints/{endpointId}/rotate-token` | | `listWebhookDeliveries` | `GET /webhooks/deliveries` (optional `destinationId` filter) | | `getWebhookDelivery` | `GET /webhooks/deliveries/{deliveryId}` | | `receivePublicWebhook` | `POST /api/v1/public/webhooks/{token}` (no session) | List operations use the standard `offset` and `limit` pagination. Each operation has a generated reference page; see [OpenAPI](/developers/openapi). ## Related pages [#related-pages] * [Integrations](/product/integrations) * [Workflows](/product/workflows) * [REST API](/developers/api) * [Errors](/developers/errors) # Deployment (/getting-started/deployment) How the five CentraKit apps deploy as services in one Vercel project, including domains, routing rewrites and the 11 crons in vercel.json. CentraKit is deployed as five independently built services in one Vercel project rooted at the repository root. The root `vercel.json` owns service definitions, public routing, and cron schedules. Shared packages continue to build through Turborepo. Each service installs with `pnpm install --filter <app>...` and builds with `turbo run build --filter=<app>`. Per-service `ignoreCommand` runs pinned `turbo-ignore` (with `--turbo-version` so the pnpm catalog pin does not break version inference) so unaffected apps skip the build (for example an api-only change rebuilds `api`, and `docs` when OpenAPI or `docs/**` inputs change, but not `saas` / `marketing` / `showcase`). Never list `.git` in `.vercelignore`. Selective skips and Turbo remote cache need git metadata on the build machine. Keep ignoring agent trees, `tests`, `apps/cli`, and other non-runtime paths instead. `saas` and `api` both enable the Workflow SDK. Their Turbo `build` outputs include `app/.well-known/workflow/**`, and each build command sets a distinct `WORKFLOW_QUEUE_NAMESPACE` so the shared deployment does not register duplicate `__wkf_workflow_*` queue consumers. Production builds use `saas` and `api`; every other environment appends `preview` (`saaspreview`, `apipreview`). ## App Mapping [#app-mapping] | Service | App root | Production domain | Preview domain (`develop`) | Notes | | ----------- | ---------------- | --------------------------------------- | --------------------------------------- | ------------------------------------------ | | `saas` | `apps/saas` | `app.centrakit.com` | `app.centrakit.dev` | Signed-in SaaS app. | | `api` | `apps/api` | `api.centrakit.com` | `api.centrakit.dev` | REST API, MCP server, OAuth and crons. | | `docs` | `apps/docs` | `docs.centrakit.com` | `docs.centrakit.dev` | Fumadocs site generated from root `docs/`. | | `marketing` | `apps/marketing` | `sample.centrakit.com` | `sample.centrakit.dev` | Public marketing template. | | `showcase` | `apps/showcase` | `centrakit.com` and `www.centrakit.com` | `centrakit.dev` and `www.centrakit.dev` | Template showcase. | Only `develop` and `main` deploy. `git.deploymentEnabled` in `vercel.json` turns deployments off for every other branch, including `changeset-release/*`. ## Environment Variables [#environment-variables] Use the single project's Vercel-managed environment for all services. Shared values such as the Supabase connection, Sentry, and one public origin (`NEXT_PUBLIC_SITE_URL` or the Vercel production domain) are configured once. AI Gateway uses OIDC (`VERCEL_OIDC_TOKEN`). Run `pnpm env:pull` from the repository root to refresh `.env.local` (development) and `.env.production.local` (production). Turborepo filters build-time variables through each app's `turbo.json`; add every build-time secret there when an app needs it during `build`, `typecheck`, or another task. Project environment variables are visible to every service. Vercel Services do not provide a general per-service environment allowlist, so treat server-only credentials as project-wide secrets. Use a separate Vercel project when a surface requires an independent credential or deployment-protection boundary. ## Function Region [#function-region] The first-party project runs Vercel Functions in `fra1` (Frankfurt) to match its Supabase `eu-central-1` region. Keep the function and database regions aligned. Static assets remain globally distributed by Vercel's CDN. ## Public Routing [#public-routing] The rewrites in `vercel.json` run in order. Path rules come first and send these paths to the API service on any host: * `/api/v1/:path*`, `/api/mcp/:path*` and `/.well-known/oauth-protected-resource`. * `/portal/calendars/:token` and `/portal/files/:token`, which map to the public calendar and file routes under `/api/v1/public/`. Host rules then pick the service for each production and preview domain in the table above. The final catch-all sends any other host, including generated deployment URLs, to SaaS. Marketing, docs, and showcase previews therefore need their preview domain for browser checks. ## Crons [#crons] `vercel.json` defines 11 crons. Vercel calls each path with `GET` on the production deployment, and the `/api/v1/:path*` rewrite routes all of them to the API service. Schedules are in UTC. | Path | Schedule | Job | | ---------------------------------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------- | | `/api/v1/internal/google-calendar-sync` | `*/5 * * * *` | Syncs connected Google calendars into the agenda. | | `/api/v1/workflows/audit-log/retention` | `17 2 * * *` | Starts the audit log retention workflow. | | `/api/v1/workflows/scheduler/watchdog` | `*/5 * * * *` | Checks that the workflow scheduler is running and starts it when it is not. | | `/api/v1/internal/channel-webhook-jobs` | `* * * * *` | Processes queued inbound channel webhooks for the inbox. | | `/api/v1/internal/webhook-deliveries` | `* * * * *` | Sends due outbound webhook deliveries. | | `/api/v1/internal/files/upload-reconciliation` | `*/15 * * * *` | Expires stale file uploads and removes their Storage objects. | | `/api/v1/internal/files/zip-exports` | `* * * * *` | Builds requested ZIP exports of files. | | `/api/v1/internal/people/daily` | `7 6 * * *` | People reminders, time-off accrual, sick leave follow-up, forgotten timers and attendance, and expired candidate cleanup. | | `/api/v1/internal/approvals/sweep` | `*/10 * * * *` | Escalates and expires pending approval requests and sends the matching notifications. | | `/api/v1/internal/inventory/signals` | `* * * * *` | Publishes inventory level events such as low stock to workflows. | | `/api/v1/internal/commerce/daily` | `17 6 * * *` | Publishes overdue purchase order and promotion events. See [Workflows](/operations/workflows). | Each cron route reports to a Sentry Cron monitor through `withMonitor`. Set `CRON_SECRET` (at least 32 characters) in the project environment. Without it, cron routes answer with a `CRON_UNAVAILABLE` problem response instead of running. Keep the `/api/v1/:path*` rewrite ahead of the SaaS catch-all. ## Generated Artifacts [#generated-artifacts] * API OpenAPI output lives at `apps/api/public/openapi.json`. * The typed API client is generated into `packages/api-client`. * The docs app combines authored root docs with generated OpenAPI pages at runtime. Regenerate committed API artifacts after route handler or schema documentation changes. # Local Development (/getting-started/local-development) Install CentraKit, start the local Supabase stack and the apps with or without Portless, and sign in with the seeded personas. ## Prerequisites [#prerequisites] * Node.js 24 and pnpm 12, as pinned in the root `package.json`. * Docker, for the local Supabase stack. * A root `.env.local`. With the repository linked to Vercel, `pnpm env:pull` writes `.env.local` (development) and `.env.production.local` (production). Without a link, copy `.env.example` to `.env.local` and fill in the required values. Install dependencies from the repository root: ```bash pnpm install ``` Every app reads the root env files through `loadMonorepoEnv`, so there are no per-app `.env` files. See [Environment variables](/operations/environment-variables). ## Local overrides [#local-overrides] Keep local-only values in `.env.development.local`. `pnpm env:pull` rewrites `.env.local` but leaves this file alone. ```bash NEXT_PUBLIC_SUPABASE_URL=http://127.0.0.1:54321 NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=<publishable key from pnpm exec supabase status> # Leave NEXT_PUBLIC_SITE_URL unset, or set it to https://localhost, # so the apps resolve to https://app.localhost and https://api.localhost. # NEXT_PUBLIC_SITE_URL=https://localhost ``` ## Supabase [#supabase] Start the local stack, then apply the schema and seeds: ```bash pnpm supabase:start pnpm supabase:sync ``` `pnpm supabase:start` generates a local JWT signing key if needed and starts the Supabase containers. `pnpm supabase:sync` pushes pending migrations and seeds to the local database, falls back to a full reset when the push fails, and then seeds the Storage buckets. Use `pnpm supabase:reset` only when you want a clean database. It drops all local data and replays every migration and seed. After a reset, sign in again, because existing browser sessions refer to the previous auth database. The local stack runs Postgres, Auth, Storage, Realtime and `imgproxy`, so Storage `/render/image` URLs behave like hosted image transforms. The `pooler` service is intentionally stopped. More detail is in [Supabase local dev](/operations/supabase-local-dev). ## Apps [#apps] | Command | Starts | | ----------------------------- | -------------------------------------------------------------------- | | `pnpm dev:portless` | SaaS and API on `https://app.localhost` and `https://api.localhost`. | | `pnpm dev` | SaaS and API with plain `next dev` on localhost ports. | | `pnpm dev:api:portless` | Only the API, on `https://api.localhost`. | | `pnpm dev:api` | Only the API, with plain `next dev`. | | `pnpm dev:docs:portless` | The API and the docs site on `https://docs.localhost`. | | `pnpm dev:marketing:portless` | The API and the marketing site. | | `pnpm dev:showcase:portless` | Only the showcase. | | `pnpm dev:all:portless` | Every app that has a `dev:portless` task. | Use the Portless commands for browser testing. The SaaS app calls the API for chat, inbox, AI suggestions and other features, which is why `pnpm dev` and `pnpm dev:portless` always start both. The plain `next dev` commands are useful in cloud and container environments where Portless is not available. Each of the docs, marketing and showcase commands also has a variant without `:portless`. `pnpm dev:oauth` starts the SaaS app on a `localtest.me` host for testing Google sign-in. See [Authentication](/architecture/authentication). ## Seeded logins [#seeded-logins] Every seeded user has the password `Password123!`. `test@example.com` (Lieke Hofstra) owns the four shop organizations, and each one covers its own industry: | Organization | Business | | ------------------- | ------------------------------------------------------------------------------- | | `tractor-service` | Tractor and garden machinery repair. The deepest seed, with every domain state. | | `auto-garage` | Car garage with APK inspections, tyre storage and courtesy cars. | | `bike-scooter-shop` | Bicycle and scooter shop with retail sales, repairs and a webshop. | | `equestrian-center` | Riding school and livery stable with boarders, lessons and farrier visits. | | Email | Access | | ------------------- | ----------------------------------------------------------------------------------------------------------- | | `test@example.com` | Owner of all four shop organizations, the seeded system admin, and a portal contact of De Haan Landscaping. | | `test1@example.com` | Member of `tractor-service`, set up in People as a field service mechanic (Jeroen Bakker). | | `test2@example.com` | Viewer in `bike-scooter-shop` (Sanne Visser). | | `test3@example.com` | Portal-only private customer of both `tractor-service` and `auto-garage` (Pieter Brouwer). | | `test4@example.com` | Portal-only contact for the business customer Green Acre Farms BV in `tractor-service` (Hendrik van Laar). | | `test5@example.com` | Portal-only boarder at `equestrian-center` (Charlotte Veenstra). | The seed also creates role-specific accounts for permission testing: | Email | Access | | ------------------------------- | -------------------------------------------------------------------------------------------------------------------- | | `support@example.com` | The `system-support` system role, with no organization membership. | | `owner@example.com` | Finance and settings role in all four shop organizations. | | `operator@example.com` | Admin in `tractor-service` and `auto-garage`. | | `team.lead@example.com` | Service planner in `tractor-service` and `auto-garage`. | | `crm.editor@example.com` | Sales advisor in `tractor-service` and `bike-scooter-shop`. | | `assets.editor@example.com` | Parts manager in `tractor-service` and `bike-scooter-shop`. | | `viewer@example.com` | Read-only custom role in `tractor-service`. | | `bike.mechanic@example.com` | Mechanic in `bike-scooter-shop`. | | `bike.retail@example.com` | Sales advisor in `bike-scooter-shop`. | | `garage.apk@example.com` | Mechanic and APK inspector in `auto-garage`. | | `garage.advisor@example.com` | Service advisor in `auto-garage`. | | `stable.manager@example.com` | Admin in `equestrian-center`. | | `stable.instructor@example.com` | Instructor with the service planner role in `equestrian-center`. | | `stable.viewer@example.com` | Read-only custom role in `equestrian-center`. | | `disabled@example.com` | A mechanic in `tractor-service` whose profile is disabled. | | `external@example.com` | Owner of `harbor-marine-service` and of `cedar-hvac-rental`, which is disabled. No access to the shop organizations. | Each shop organization has customers, contacts, customer assets, products and inventory, tasks, agenda items, quotes, invoices and orders in every status, plus files, inbox threads, chat history, notifications, employees, time, expenses, hiring, workflows, audit logs and billing data. Seed files live in `supabase/seeds/`, and each one starts with a `-- Covers:` comment that lists the scenarios it seeds. ## Common issues [#common-issues] * `pnpm supabase:sync` warns about unset Google OAuth variables. Google sign-in is optional locally, so the warning is safe to ignore. * The Stripe keys in `.env.local` are placeholders, so billing flows do not work locally. Everything else does. * If a browser session behaves strangely after a reset, clear the cookies for the `.localhost` hosts and sign in again. # Overview (/getting-started/overview) The CentraKit apps, every product area with its route and guide, and the local URLs for each app. CentraKit is an AI-native, multi-tenant platform for running a service business. Organizations, customers, work, commerce, conversations and automation share one data model, and the same data is available through the SaaS app, an assistant, a REST API, an MCP server and a CLI. The repository is a pnpm and Turborepo monorepo. [Monorepo](/architecture/monorepo) describes the packages behind the apps. ## Apps [#apps] | App | Purpose | | ---------------- | -------------------------------------------------------------------------------------------------------- | | `apps/saas` | The signed-in web app: organization workspaces, the customer portal, profile pages and system admin. | | `apps/api` | Every route handler: the REST API under `/api/v1`, the MCP server, OAuth, crons, uploads and AI streams. | | `apps/docs` | This documentation site, built with Fumadocs from the root `docs/` folder. | | `apps/marketing` | The public marketing site template. | | `apps/showcase` | The template showcase served on the apex domain. | | `apps/cli` | The `centrakit` command-line client for the REST API. | ## Product map [#product-map] Organization routes live under `/{orgSlug}`, for example `/tractor-service/quotes`. The table paths below are relative to that prefix unless they start with `/admin`, `/profile` or `/organizations`. ### Workspace [#workspace] | Area | Route | What it covers | | ----------------------------------------- | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------- | | [Dashboard](/product/dashboard) | `/{orgSlug}` | The organization home page with key figures and recent activity. | | [Command center](/product/command-center) | `Cmd+K` or `Ctrl+K` | Search across records, recent items and quick create commands, plus a hand-off to the assistant. | | [Notifications](/product/notifications) | `/notifications` | Mentions, alerts and pending approval requests for the signed-in user. | | [Approvals](/product/approvals) | `/roles/approval-rules` | Rules that hold an action until an approver signs off, whether a person, an AI agent, a workflow, MCP or the API started it. | | [Chat](/product/chat) | `/chats` | Conversations with the AI assistant, including activity history. | | [Inbox](/product/inbox) | `/inbox` | Shared customer messages from connected channels such as email and WhatsApp. | ### Customers and sales [#customers-and-sales] | Area | Route | What it covers | | ------------------------------------------- | ------------ | ------------------------------------------------------------------------------ | | [Customers](/product/customers) | `/customers` | Customers, their contacts and the assets they own. | | [Customer portal](/product/customer-portal) | `/portal` | The customer-facing view of their assets, quotes, invoices and orders. | | [Quotes](/product/quotes) | `/quotes` | The quote pipeline, drafts and quote templates. | | [Invoices](/product/invoices) | `/invoices` | The invoice pipeline, reminders and online invoice payments under `/payments`. | | [Orders](/product/orders) | `/orders` | Sales orders, shipments and promotions. | | [Products](/product/products) | `/products` | The product catalog, inventory, suppliers, purchase orders and labels. | | [Content](/product/content) | `/content` | Shared templates, brands, categories and tags. | ### Work [#work] | Area | Route | What it covers | | ------------------------- | --------- | ------------------------------------------------------------------------------ | | [Tasks](/product/tasks) | `/tasks` | Task board, task list and the tasks assigned to you. | | [Agenda](/product/agenda) | `/agenda` | Agenda items, shared calendars and the planning view under `/agenda/planning`. | | [Files](/product/files) | `/files` | Organization files and drives. | ### People [#people] | Area | Route | What it covers | | ----------------------------- | ----------- | ---------------------------------------------------------------------------------------------- | | [People](/product/people) | `/people` | Employees, org chart, onboarding, skills, equipment, performance and payroll under `/payroll`. | | [Hiring](/product/hiring) | `/hiring` | Jobs, applications and candidate email templates. | | [Time](/product/time) | `/time` | Timesheets, team time and time off. | | [Expenses](/product/expenses) | `/expenses` | Expense claims, trips, approvals and reimbursements. | ### Insights and automation [#insights-and-automation] | Area | Route | What it covers | | ------------------------------------- | --------------- | ---------------------------------------------------------------- | | [Insights](/product/insights) | `/insights` | Analytics and reporting across sales, work and people. | | [Workflows](/product/workflows) | `/workflows` | Workflow graphs, runs and the starter automations for templates. | | [Integrations](/product/integrations) | `/integrations` | Connected apps, messaging channels and webhooks. | ### Organization [#organization] | Area | Route | What it covers | | ------------------------------------------------------- | ---------------------------- | -------------------------------------------------------------------- | | [Organizations](/product/organizations) | `/organizations` | Listing, creating and switching between organizations. | | [Settings and branding](/product/settings-and-branding) | `/settings` | Organization profile, branding and AI settings under `/ai/settings`. | | [Access control](/product/access-control) | `/users`, `/teams`, `/roles` | Users and invitations, teams, and roles with their permissions. | | [Billing and usage](/product/billing-and-usage) | `/billing`, `/usage` | The organization subscription, plan invoices and usage limits. | | [Audit log](/product/audit-log) | `/audit-log` | Who changed what in the organization, with export. | | [Profile](/product/profile) | `/profile` | Personal details, sign-in methods and communication preferences. | ### System admin [#system-admin] System admins manage the whole platform under `/admin`: users, organizations, system roles, feature flags, the platform audit log, subscriptions, platform invoices, plans, payouts, platform settings, analytics, integrations, knowledge, workflows and AI settings. Start at the [admin dashboard](/admin/dashboard). ## Local URLs [#local-urls] `pnpm dev:portless` serves the apps on named HTTPS hosts through Portless: | App | URL | | ---- | ------------------------ | | SaaS | `https://app.localhost` | | API | `https://api.localhost` | | Docs | `https://docs.localhost` | The docs app starts with `pnpm dev:docs:portless`. Without Portless, `pnpm dev` runs plain `next dev`, and each app prints the localhost port it chose. See [Local development](/getting-started/local-development) for setup. # CentraKit Docs (/) Start here for CentraKit product guides, system admin, the REST API, MCP server and CLI, AI agent access, and the architecture and operations notes. CentraKit is an AI-native, multi-tenant platform for running a service business. Customers, quotes, invoices, orders, products, tasks, the agenda, people, conversations and workflows share one data model, and you can work with that data in the web app, through the assistant, or over the REST API, the MCP server and the CLI. ## Get started [#get-started] - [Overview](/getting-started/overview): The apps, every product area and where each one lives. - [Local development](/getting-started/local-development): Run Supabase and the apps locally and sign in with the seeded personas. - [Deployment](/getting-started/deployment): Vercel services, domains, routing and crons. ## Product guides [#product-guides] - [Dashboard](/product/dashboard): The organization home page. - [Chat](/product/chat): Work with the AI assistant. - [Inbox](/product/inbox): Customer messages from connected channels. - [Customers](/product/customers): Customers, contacts and customer assets. - [Quotes](/product/quotes): Draft, send and follow up on quotes. - [Invoices](/product/invoices): Invoice, remind and collect payment. - [Orders](/product/orders): Sales orders, shipments and promotions. - [Products](/product/products): Catalog, inventory and purchasing. - [Tasks](/product/tasks): Plan and track work. - [Agenda](/product/agenda): Appointments, calendars and planning. - [People](/product/people): Employees, onboarding and payroll. - [Workflows](/product/workflows): Automate lifecycles and approvals. The [overview](/getting-started/overview) lists every product area, including the customer portal, time, expenses, hiring, insights, integrations and organization settings. ## System admin [#system-admin] - [Admin dashboard](/admin/dashboard): Platform health and operations at a glance. - [Users](/admin/users): Every user account on the platform. - [Organizations](/admin/organizations): Create and manage tenants. - [Roles and permissions](/admin/roles-and-permissions): System roles and approval rules. ## Developers [#developers] - [REST API](/developers/api): Resources, pagination and conventions for /api/v1. - [MCP server](/developers/mcp): Connect AI clients to CentraKit tools. - [CLI](/developers/cli): The centrakit command-line client. - [Webhooks](/developers/webhooks): Receive events from CentraKit. ## For AI agents [#for-ai-agents] - [AI agents](/developers/ai-agents): How agents read these docs and act on CentraKit data. Agents can read the whole site as plain text. `/llms.txt` lists every page with its description, and `/llms-full.txt` contains the full text of every page in one file. ## Architecture and operations [#architecture-and-operations] - [Monorepo](/architecture/monorepo): Apps, packages and import boundaries. - [Service layers](/architecture/service-layers): Contracts, Supabase implementations and request wiring. - [Tenancy](/architecture/tenancy): Organization slugs in the URL and access guards. - [RBAC](/architecture/rbac): Roles, permissions and enforcement. - [CI and release](/operations/ci-and-release): GitHub Actions, branches and Changesets. - [Workflows runtime](/operations/workflows): System graphs, schedules and inspecting runs. # CI And Release (/operations/ci-and-release) GitHub Actions workflows, branch protection, verification jobs, and CLI release flow. CentraKit uses a small set of focused GitHub Actions workflows. ## Workflows [#workflows] * `.github/workflows/ci.yml` runs on pull requests, pushes to `develop` and `main`, and manual dispatch. It calls `verify.yml` (affected workspaces only on pull requests), then builds and checks payload budgets. The E2E suite runs locally with `pnpm test:e2e` and is not part of CI. * `.github/workflows/verify.yml` is the reusable `fail-fast: false` matrix: format, lint, Knip, typecheck, tests, Turbo boundaries, `pnpm audit:high`, OpenAPI drift, and the i18n catalog test. * `.github/workflows/database.yml` runs when `supabase/**` changes. It starts the local stack, runs the pgTAP suite with `pnpm supabase:test`, and lints SQL with `supabase db lint`. * `.github/workflows/release.yml` runs as the last job of CI on pushes to `main`, after `Verify` and `Build` pass, so it does not check, build or test the same commit again. It uses Changesets to open or update the `chore: version packages` PR. Merging that PR tags the release, creates the GitHub Releases, and merges `main` back into `develop`. * `.github/workflows/release-cli.yml` runs for published CLI releases and manual retries to package the tarball and attach it to the GitHub release. * `.github/workflows/react-doctor.yml` runs [React Doctor](https://www.react.doctor/ci) on pull requests and on pushes to `develop` and `main`. On a pull request it scans the changed files, posts a summary comment with the new findings, and fails on new warnings or errors (`blocking: warning`). Run the same scan locally with `pnpm doctor`. * `.github/dependabot.yml` opens one grouped weekly PR each for npm and GitHub Actions, after a two-day cooldown. * `.github/actions/setup` is the shared composite action: Turborepo Remote Cache over OIDC, then `pnpm/setup` for pnpm, the Node 24 runtime, the store cache, and a frozen install. ## Required Repository Configuration [#required-repository-configuration] ### Variables [#variables] * `TURBO_TEAM`: Vercel team slug for Turborepo remote cache. Use `scaledock`. * `NEXT_PUBLIC_SITE_URL`: optional CI override for Next.js env validation. Defaults to `https://localhost`. * `NEXT_PUBLIC_SUPABASE_URL`: optional CI placeholder override for Next.js env validation. * `NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY`: optional CI placeholder override for Next.js env validation. ### Secrets [#secrets] * `SENTRY_AUTH_TOKEN`: optional. CI does not upload source maps or create Sentry releases; Vercel production builds do. ### Remote cache (OIDC) [#remote-cache-oidc] CI uses [`vercel/setup-turborepo-remote-cache-action`](https://github.com/vercel/setup-turborepo-remote-cache-action) to exchange a GitHub OIDC token for a short-lived Turborepo token. That token is team-scoped and can write cache artifacts. Workflows need `id-token: write`. Create the matching Vercel policy once. In the team **Build and Deployment** settings, open [OIDC Policies for CLI Access](https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fsettings%2Fbuild-and-deployment%3FaddOidcPolicy%3Dturborepo-cli\&title=Add+a+Turborepo+CLI+OIDC+Policy) and add a Turborepo CLI policy for `ScaleDockHQ/CentraKit`. Workflows do not set `TURBO_TOKEN` themselves, so the token from the OIDC exchange is the only one Turborepo sees. Without the policy the setup step logs `Failed to exchange OIDC token` and every task runs without remote cache. ```bash gh variable set TURBO_TEAM --body "scaledock" --repo ScaleDockHQ/CentraKit gh variable set NEXT_PUBLIC_SITE_URL --body "https://localhost" --repo ScaleDockHQ/CentraKit ``` Vercel deployments do not need `TURBO_TOKEN`: when builds run `turbo run build` (configured per service in root `vercel.json`), [Vercel Remote Cache](https://vercel.com/docs/monorepos/remote-caching) is authenticated automatically. Root `turbo.json` sets `remoteCache.teamSlug` / `teamId` for the linked `centrakit` project. ### Local remote cache [#local-remote-cache] From the repo root (one-time per machine): ```bash pnpm exec turbo login pnpm exec turbo link ``` This writes gitignored `.turbo/config.json`. After linking, local `turbo run build` shares artifacts with Vercel and GitHub Actions. ## Branch Model [#branch-model] * `develop` is the default branch. Commit and push to it directly; feature branches are the exception and get no Vercel preview. * `main` moves only through the pull request from `develop` to `main`, merged whenever a release is wanted. Never commit to `main`. * Vercel deploys `develop` as preview (`app.centrakit.dev`, `api.centrakit.dev`, `docs.centrakit.dev`) and `main` as production. `git.deploymentEnabled` in root `vercel.json` denies every other branch. * Supabase: `main` maps to the production project and `develop` to the persistent Supabase branch. The Supabase GitHub integration applies migrations and `config.toml` on push; do not `supabase db push` from Actions. * Dependabot targets `develop`. The Changesets version PR targets `main` from `changeset-release/main`; `vercel.json` skips Vercel deployments for `changeset-release/*` branches. The repository plan is private without rulesets, so there is no enforced branch protection. The checks below are the ones to require on the release PR once protection is available: * `Format` * `Lint` * `Knip` * `Typecheck` * `Test` * `Boundaries` * `Audit` * `OpenAPI drift` * `i18n catalogs` * `Build` * `React Doctor` The `Build` job also captures SaaS and CLI payload artifacts and compares deterministic SaaS build metrics with the latest successful artifact on the base branch. Regressions emit warnings by default. Set the `PAYLOAD_COMPARE_MODE` repository variable to `enforce`, or choose `enforce` for a manual workflow run, to make budget regressions fail the job. ## Payload Baselines [#payload-baselines] The `Build` job records shipped-cost artifacts after `turbo run build`, so the payload steps reuse that build instead of compiling the SaaS app again: * SaaS: `pnpm --filter saas baseline:payload` reads the `next build` output, writes `.next/diagnostics/payload-artifact.json`, and uploads static CSS/chunks, manifests, and `.nft.json` traces. * Bundle analyzer: `pnpm --filter saas analyze:payload` runs `next experimental-analyze --output`, a second full compile of about eight minutes on CI. It runs only on a manual workflow run, and its output lands in the same `saas-payload-baseline` artifact. * CLI: `pnpm --filter centrakit baseline:payload` packs a baseline tarball and prints `dist/cli.js` / tarball byte sizes. Locally: ```bash pnpm exec turbo run baseline:payload --filter=saas --filter=./apps/cli ``` The versioned SaaS artifact records, per audited route, uncompressed production JS, route-only JS after removing chunks shared by every audited route, obtainable prerendered Flight/RSC bytes, server entry bytes, and the unique files/bytes referenced by its NFT trace. Audited routes are Dashboard, Planning, Tasks/all, Products, Admin subscriptions, Audit log, Agenda detail, and Product detail. CI selects only audited routes affected by changed source paths. A shared App Shell change selects all audited routes. Shared production JS is compared once with a zero-growth budget, so the same shared regression is not repeated for every route. Route budgets allow the larger of the absolute or percentage allowance: 25 KiB/5% route JS, 10 KiB/5% Flight and server entry, and 100 KiB/2% server trace. Missing or schema-incompatible base artifacts warn and skip comparison, including in enforce mode. Runtime timings are intentionally excluded because CI compares build artifacts only. ## Verification Strategy [#verification-strategy] * Pull requests and pushes to `develop` and `main` run the same check set. Payload baselines compare against the latest successful run on the PR base branch (or the pushed branch). * Independent jobs run in parallel to reduce wall-clock time. * The OpenAPI job runs `pnpm openapi:generate` and fails if committed API artifacts drift. ## E2E Runtime Notes [#e2e-runtime-notes] * Playwright starts two production servers: the API on port 3101 (ready on `/api/v1/health`) and the SaaS app on port 3100 (ready on `/login`). * Both apps are built with `NEXT_PUBLIC_SITE_URL=http://localhost:3101`, so the SaaS `/api/v1` rewrite resolves to `http://api.localhost:3101`. The root `pnpm test:e2e` script sets this variable, which means it rebuilds `apps/saas/.next` and `apps/api/.next` for the E2E origin. * The API server runs without `CK_DB_TRACE_FILE`, so the database-call budget only counts SaaS requests. ## Versioning [#versioning] Every user-visible change gets a changeset (`pnpm changeset`). The apps share one fixed version (`.changeset/config.json` `fixed` group with `privatePackages.version` and `tag`), so the product has a single release number. `pnpm version-packages` runs `scripts/root-changelog.ts`, which reads the pending changesets, runs `changeset version` (per-app `CHANGELOG.md` files), and prepends a dated section with every changeset summary to the root `CHANGELOG.md`. It then formats the changelogs. The docs site renders the root changelog. ## CLI Release Flow [#cli-release-flow] Release automation is split into two workflows: 1. `release.yml` opens or updates the `chore: version packages` PR from the pending changesets. 2. Merging that PR tags `centrakit@<version>` and publishes the GitHub release, and `release-cli.yml` packages the tarball and uploads it to the release. `release-cli.yml` also keeps `workflow_dispatch` for retrying an asset upload against an existing release tag. # Environment Variables (/operations/environment-variables) Root env files, Vercel env management, Supabase values, Stripe values, and Turbo task env forwarding. Apps load environment variables from the **repository root**. Deployed services share one Vercel project environment. Next.js apps call `loadMonorepoEnv` (via `@workspace/next-config`) so they read the root files even though each app lives under `apps/<name>`. ## Local Files [#local-files] | File | Purpose | | ----------------------------------------------- | -------------------------------------------------------------------------------------- | | `.env.example` | Tracked template (no secrets) | | `.env.local` | From `pnpm env:pull:development` (gitignored) | | `.env.development.local` | Local overrides that survive pulls (Portless URLs, local Supabase, `DEV_LOGIN_*`) | | `.env.production.local` | From `pnpm env:pull:production` for local production builds | | `supabase/.env.example` → `supabase/.env.local` | Supabase CLI Google OAuth (`GOOGLE_OAUTH_CLIENT_ID` / `_SECRET`) and CLI-only settings | Copy `.env.example` to `.env.local` when you are not linked to Vercel yet, then fill required values. Prefer `pnpm env:pull` (development + production) when the root project is linked. For local Supabase, common values (in `.env.development.local`) are: ```bash NEXT_PUBLIC_SUPABASE_URL=http://127.0.0.1:54321 NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=<local-publishable-key> # Optional; unset falls back to https://localhost → app.localhost / api.localhost # NEXT_PUBLIC_SITE_URL=https://localhost ``` ## Vercel [#vercel] Link the repository root to the CentraKit Vercel project and manage deployed values there (including marketplace integrations for Sentry, Supabase, Stripe, and Resend). ```bash pnpm env:pull # development → .env.local + production → .env.production.local pnpm env:pull:development pnpm env:pull:production pnpm env:pull:preview # → .env.preview.local (optional; Next does not auto-load it) ``` Prefer shared integration names (`NEXT_PUBLIC_SENTRY_DSN` / `SENTRY_*`, Supabase, Stripe, Resend). AI Gateway and Vercel Connect use `VERCEL_OIDC_TOKEN` from `pnpm env:pull`. Sentry SDK initialization is disabled in local development. Keep local-only overrides in `.env.development.local` so development pulls into `.env.local` do not wipe Portless or local Supabase settings. ## Turbo Forwarding [#turbo-forwarding] Turborepo only forwards variables listed in a task's `env` array or global env configuration. If a build needs a secret or public value, add it to that app's `turbo.json` task configuration, root `globalEnv`, or `globalPassThroughEnv`. `NEXT_PUBLIC_SITE_URL` is in root `globalEnv` so CI and local `next build` hash it. Off Vercel, unset values fall back to `https://localhost` (SaaS `https://app.localhost`, API `https://api.localhost`). `SENTRY_AUTH_TOKEN` is in build `passThroughEnv` so the Sentry plugin can see it without affecting the cache hash. ## Sensitive Values [#sensitive-values] Do not commit `.env.local` files or credentials. Document variable names and ownership, not real secret values. # Stripe Billing (/operations/stripe-billing) Stripe Billing and customer-invoice payment surfaces, environment variables, Connect setup, webhooks, and go-live checks. CentraKit uses Stripe for two distinct money flows: 1. **SaaS subscription billing:** organization plans, Customer Portal, metered overage. Stripe is the source of truth for subscription status; CentraKit keeps a local read model and usage aggregates. 2. **Customer invoice payments:** tenants collect payment on CentraKit invoices via Stripe Connect destination charges. CentraKit invoices remain the accounting source of truth (numbering, PDF, status history). Stripe PaymentIntents collect funds; CentraKit is the **merchant of record**. Never collect or store raw card numbers, CVC, bank account details, legacy Tokens, Sources, Charges, or Card Element data. Prefer the Stripe Payment Element. Never persist PaymentIntent client secrets. ## SaaS Subscription Surfaces [#saas-subscription-surfaces] * Checkout Sessions in `subscription` mode start new subscriptions and upgrades. * Stripe Customer Portal handles subscription management, payment methods, invoice history, billing details, and tax IDs where Stripe supports the flow. * Verified webhooks update local billing tables. Checkout and Portal return pages are UX hints only. * Subscription invoice PDF access is mediated by CentraKit before redirecting to Stripe `invoice_pdf` or Hosted Invoice Page URLs. * Metered usage events are sent to Stripe only for overage after subtracting plan-included quantities. * Audit-log retention is a local plan entitlement, not a Stripe meter: Starter 1 day, Pro 7 days, Business 90 days, and Enterprise 365 days or a platform-managed subscription override. Keep the seeded `billing_plan_features` copy aligned with the retention workflow. ## Customer Invoice Payment Surfaces [#customer-invoice-payment-surfaces] * Organizations connect a Stripe Express account under `/{orgSlug}/payments` (`organization.payments.manage`). * Online invoice send is blocked until the connected account can receive destination transfers (`charges_enabled` + `enabled` status). * On send, CentraKit creates or reuses one PaymentIntent for the immutable invoice total with `transfer_data.destination` and `application_fee_amount` from the admin `platform_settings.invoice_payment_fee` row (percentage bps or fixed minor units; default `150` bps = 1.5%). * Customers pay on `/{orgSlug}/portal/invoices/[invoiceId]` via the embedded Payment Element. Dynamic payment methods (card, iDEAL, etc.) follow Stripe Dashboard + currency/country configuration. * Mapping lives in `invoice_payments`. Webhooks update payment + invoice status atomically; browser returns only refresh UI. * Manual mark-paid and invoice cancel cancel collectible PaymentIntents. Refunds and disputes are owned by CentraKit as merchant of record and mirrored into `invoice_payments` status. ## Required Environment [#required-environment] Set these per environment in Vercel (Stripe integration covers the keys): ```bash STRIPE_SECRET_KEY=sk_test_... STRIPE_WEBHOOK_SECRET=whsec_... NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_... ``` Plan price IDs are not environment variables. Keep them in Stripe and the platform database. Use separate test and live products, prices, Portal configuration, Connect settings, and webhook signing secrets. Enable Connect in the Stripe Dashboard and complete platform profile / approval prerequisites before going live with destination charges. ## Local Setup [#local-setup] 1. Create Stripe test products and recurring prices for Starter, Pro, and Business monthly/yearly plans; store the mapping in the platform DB (not env). 2. Create Stripe meters matching the seeded usage event names such as `centrakit_ai_input_tokens`, `centrakit_ai_output_tokens`, `centrakit_inbox_messages_sent`, `centrakit_api_requests`, and `centrakit_mcp_tool_calls`. 3. Ensure Stripe keys come from the Vercel Stripe integration (`pnpm env:pull`). 4. Forward webhooks to the CentraKit Stripe webhook route with the Stripe CLI (include `payment_intent.*`, `charge.refunded`, `charge.dispute.created`, and `account.updated` for invoice payments). 5. Configure Stripe Customer Portal in test mode for subscription changes, payment method updates, invoice history, billing details, and tax IDs. 6. Open `/{orgSlug}/billing` and `/{orgSlug}/usage` with `organization.billing.manage`, and `/{orgSlug}/payments` with `organization.payments.manage`. 7. Complete Connect onboarding for a test organization, send an invoice, and pay via the public and portal surfaces. ## Webhook Events [#webhook-events] The webhook endpoint verifies signatures and records each Stripe event idempotently. ### Subscription billing [#subscription-billing] * `checkout.session.completed` * `customer.subscription.created` * `customer.subscription.updated` * `customer.subscription.deleted` * `customer.updated` * `payment_method.attached` * `payment_method.detached` * `invoice.created` * `invoice.finalized` * `invoice.finalization_failed` * `invoice.paid` * `invoice.payment_failed` * `invoice.payment_action_required` * `invoice.upcoming` ### Customer invoice payments [#customer-invoice-payments] Routed by PaymentIntent/charge metadata (`centrakit_purpose=customer_invoice_payment`) or Connect `account.updated`: * `payment_intent.succeeded` * `payment_intent.payment_failed` * `payment_intent.canceled` * `payment_intent.requires_action` * `charge.refunded` * `charge.dispute.created` * `account.updated` ## Go-Live Checklist [#go-live-checklist] * Confirm live Stripe products, prices, and lookup keys match configured environment variables. * Confirm live Stripe meters use the same event names as `billing_usage_meters.stripe_event_name`. * Register the live webhook endpoint and verify `STRIPE_WEBHOOK_SECRET` is the live signing secret. * Configure Customer Portal, invoice branding, support details, tax settings, Smart Retries, and dunning. * Complete Stripe Connect platform onboarding/approval; confirm Express connected accounts, destination charges, and negative-balance / dispute handling ownership. * Confirm `/admin/settings` invoice payment fee matches the commercial agreement. * Replay sandbox webhooks and test card, iDEAL, SCA, retry, failure, refund, dispute, cancellation, reactivation, and invoice download scenarios for both subscription and customer-invoice flows. * Test usage overage sync idempotency by retrying a period sync and confirming Stripe does not receive duplicate billable quantities. * Confirm no application code path handles raw payment credentials or legacy Stripe APIs. * Confirm all four plans expose audit logs and the daily retention workflow applies their configured tier, including any Enterprise override. * Monitor webhook failures, payment failures, subscription drift, invoice download failures, Connect account restrictions, and reconciliation errors. # Supabase Local Dev (/operations/supabase-local-dev) Local Supabase startup, sync, reset, seeds, storage, and generated types. The local Supabase stack backs the SaaS and API apps during development. ## Startup [#startup] ```bash pnpm supabase:start pnpm supabase:sync ``` The sync task pushes pending migrations and seeds to the local database (falling back to a full reset when the push fails) and then seeds the Storage buckets. OAuth-related warnings about missing Google credentials are safe unless you are testing OAuth. ## Reset [#reset] Use reset only when a clean local database is intended: ```bash pnpm supabase:reset ``` After reset, browser sessions from the previous local auth database are stale. Sign in again with the seeded test account. ## Seed Account [#seed-account] * Email: `test@example.com` * Password: `Password123!` * Organizations: `tractor-service`, `auto-garage`, `bike-scooter-shop`, `equestrian-center` `test@example.com` is also the seeded system admin. All seeded users share the same password. [Local development](/getting-started/local-development#seeded-logins) lists every persona, including the portal-only customers `test3@example.com`, `test4@example.com` and `test5@example.com` and the role-specific accounts such as `owner@example.com`, `crm.editor@example.com` and `viewer@example.com`. Seed files provide deterministic coverage for every shop organization: customers, contacts, roles, teams, products, inventory, customer assets, tasks, agenda items, quotes, invoices, orders, files, inbox threads, AI chat history, notifications, employees, time, expenses, hiring, workflows, webhooks, audit logs and billing read models. Files `100` to `170` in `supabase/seeds/` hold the demo data, one domain per file, and each starts with a `-- Covers:` comment. `docs/agents/supabase-schema.md` describes the id scheme and the conventions for adding rows. ## Storage [#storage] Storage buckets are configured through `supabase/config.toml` and seeded from `supabase/seed-buckets/`. Bucket ids match product features: `users`, `customers`, `organization`, `assets`, `products`, `files`, `knowledge`, `ai`, `integrations`, `audit-logs`, and `workflows`. The reset flow recreates those local buckets and loads public seed objects from the matching folders. Object paths are UUID-first (`{orgId|userId}/…`); never use org slugs as the tenant folder. On a hosted project, create the eleven new buckets and delete the old ids after deploy. Existing hosted objects are discarded for this cutover; path columns 404 until objects are re-uploaded or the remote database is reset. ## Generated Types [#generated-types] Regenerate `packages/supabase-runtime/src/types/database.generated.ts` after schema changes. Include both `public` and `graphql_public` schemas so generated types do not drop GraphQL-related definitions. # Vercel metrics pass (/operations/vercel-metrics-pass) How to run a metrics-first vercel-optimize / CDN + PPR health check for CentraKit apps. Use this after shipping Cache Components or Functions changes. Do **not** invent cost or caching recommendations from a code-only grep. Follow the `vercel-optimize` skill in `.agents/skills/vercel-optimize/SKILL.md`: collect signals first, apply its gate, then investigate. ## Prerequisites [#prerequisites] * Repository root linked to the single CentraKit project via `vercel link` * Observability Plus (or equivalent) enabled on the project * Production (or a representative preview) traffic available ## Checklist [#checklist] 1. **CDN and PPR.** Sample `vercel metrics` grouped by `cache_result`, `ppr_state`, and service for SaaS hub routes. Rising `blocking` or unexpected `BYPASS` on authenticated shells is a regression signal (see the `cdn-caching` skill in `.agents/skills/cdn-caching/SKILL.md`). 2. **Functions.** Compare duration and CPU by service for the AI routes in `apps/api/app/api/v1/ai/` (chat and specialists set `maxDuration = 300`, transcription `60`) and for the API service's per-minute crons, such as `/api/v1/internal/channel-webhook-jobs`. 3. **Crons.** Confirm Sentry Cron monitors receive check-ins from the App Router handlers (`withMonitor` from `apps/api/lib/cron-monitor.ts` in each cron route; `automaticVercelMonitors` is off for App Router). 4. **Instant nav.** E2E uses a production build with `EXPOSE_TESTING_API=1` (see `instant-nav.rig.md` at the repository root). Record findings in the PR or ops runbook; only change platform memory/region/Fluid settings when metrics justify it. # Workflows (/operations/workflows) Inspect durable graph runs for quote, invoice, task, agenda, and scheduled system automations. Product automations are published system graphs in `supabase/seeds/063_system_workflows.sql`. Domain mutations, the Stripe webhook, and the workflow scheduler call `dispatchWorkflowEvent` or start a scheduled run. The Workflow SDK executes each matched graph via `runWorkflowGraph`. ## Quote and invoice cadences [#quote-and-invoice-cadences] | Graph | Start | Notes | | --------------------------------------------- | ---------------- | --------------------------------------------------------------------------------------- | | `quote-lifecycle` | `quote.sent` | 7-day and 1-day customer reminders, owner nudge, expire, 30-day auto-reject | | `quote-accepted` | `quote.accepted` | Emails, then draft invoice + follow-up task. Cancels the lifecycle sleep for that quote | | `quote-rejected` | `quote.rejected` | Owner notify. Cancels the lifecycle sleep | | `invoice-lifecycle` | `invoice.sent` | Template reminder cadence (negative `daysAfterDue` allowed), overdue, owner nudge | | `invoice-paid` / `invoice-cancelled` | matching events | Notify; cancel the lifecycle sleep for that invoice | | `invoice-payment-failed` / `invoice-disputed` | Stripe webhook | Notify only; lifecycle reminders keep running | Sibling cancellation uses `cancelRunsForDomainRecord` so a run asleep in `delay` does not wait for the next status check. On a terminal quote or invoice event it cancels every active run on that record, whichever workflow key started it, so organization copies of the lifecycle graphs stop too. ## Order, purchase order, and shipment lifecycles [#order-purchase-order-and-shipment-lifecycles] | Graph | Start | Happy path | | -------------------------- | --------------------- | ----------------------------------------------------------------- | | `order-lifecycle` | `order.confirmed` | Confirmed, paid, picking, shipped, delivered, invoiced, completed | | `purchase-order-lifecycle` | `purchase-order.sent` | Sent, confirmed (manual), in transit, received, billed (manual) | | `shipment-lifecycle` | `shipment.shipped` | In transit, out for delivery, delivered | The dispatcher routes an event to one subject by its prefix, so an order lifecycle only hears `order.*` events. `reactToCommerceEvent` (`packages/core-workflow-runtime/src/workflow-runtime/order-events.ts`) mirrors related events onto the order and purchase order: * `invoice.sent` / `invoice.paid` become `order.invoiced` / `order.paid`. * Outbound shipment events become `order.shipped`, `order.ready-for-pickup`, `order.delivered`, or `order.picked-up`. A delivered return becomes `order.returned`. * An inbound shipment with a purchase order becomes `purchase-order.in-transit`. The same reactor creates an order when a quote is accepted and its template has **create order on accept** set. It also sends the built-in staff notifications for confirmed and backordered orders, shipment exceptions, received and overdue purchase orders, and low stock. `GET /api/v1/internal/commerce/daily` (`17 6 * * *`) publishes `purchase-order.overdue` once per promised date, `promotion.expiring` for active promotions ending within 3 days, and `promotion.limit-reached` when committed redemptions reach the total limit. ## Starter automations [#starter-automations] Templates offer one-click starters from `packages/automation/src/workflows/starter-automations.ts`. Most are attached to the template. The starters triggered by stock events are created organization-wide instead, because `inventory.*` events carry no template. | Starter | Trigger | Does | | ------------------------ | --------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | | Low stock reorder | `inventory.low-stock` (organization-wide) | Drafts a purchase order from the reorder point and sends it. The `purchase-order.send` authorize floor can require approval | | Purchase order overdue | `purchase-order.overdue` | Reminds the supplier and notifies the purchaser | | Back in stock | `inventory.back-in-stock` (organization-wide) | Fills backorders for the variant and tells customers whose order is complete again | | Prepaid order to picking | `order.paid` when the order is paid up front | Creates a draft shipment as the pick list | | Ready for pickup | `order.ready-for-pickup` | Messages the customer (WhatsApp when connected, otherwise email) and reminds them after 7 days | | Delivered, then invoice | `order.delivered` for on-fulfillment orders | Invoices the delivered lines and sends the invoice | ## Scheduled ops [#scheduled-ops] | Graph | Cron (Europe/Amsterdam) | Action | | ------------------------- | ----------------------- | ------------------------------------------------------------------------------ | | `task-due-scan` | `0 7 * * *` | Publish `task.due-soon` / `task.overdue` with idempotency `kind:taskId:dueAt` | | `invoice-overdue-digest` | `0 8 * * 1` | Email overdue invoices | | `draft-stale-digest` | `0 8 * * 1` | Email draft quotes/invoices older than 14 days | | `billing-usage-threshold` | `0 * * * *` | Publish usage-threshold events; `billing-usage-threshold-notify` emails owners | System schedules run once per enabled organization that has not opted out of the key and has no published workflow of its own on it. Each run carries the organization, and the scans and digests only read that organization's records. ## Inspecting runs [#inspecting-runs] ### Local development [#local-development] There is no root `workflow:*` script. The Workflow SDK CLI (`workflow`, also installed as `wf`) is a dependency of `apps/saas` and `apps/api`, so run it through pnpm from the app whose runs you want to see. Start the apps (`pnpm dev:portless`), then open the local run viewer: ```bash pnpm --filter api exec workflow web ``` For non-interactive output, list runs, steps or events with `workflow inspect`: ```bash pnpm --filter api exec workflow inspect runs pnpm --filter api exec workflow inspect steps --runId=<runId> ``` Use `--filter saas` instead for runs started by the SaaS app. Add `--backend vercel --env preview` (or `--env production`) to inspect a deployed environment with your `vc login` session. ### Production / preview [#production--preview] Open the Vercel dashboard and go to **Workflows** under **Observability**. Runs keep the domain id (`quoteId`, `invoiceId`, `taskId`, `agendaItemId`) on the trigger payload. ## REST [#rest] Organization workflows expose `POST …/workflows/definitions/{definitionId}/publish`, `POST …/workflows/runs/{runId}/cancel`, and `POST …/workflows/runs/{runId}/approval` (approve or deny a waiting approval step; the caller also needs the permission the approval node requires). `GET …/workflows/runs?definitionKey=` lists runs across every version of a key. ## When a workflow goes wrong [#when-a-workflow-goes-wrong] `workflow-failed-alert` emails organization owners when another published graph fails. It does not rematch itself. Check the failed run's step error, then replay or fix the domain record and republish if the definition changed. # Access control (/product/access-control) How to manage an organization's users, invitations, teams, roles and approval rules, and how built-in and custom roles grant permissions. ## Overview [#overview] Access control decides who can open an organization and what they can do there. It lives in the sidebar under Organization, Access: | Route | Purpose | | ------------------------------------- | -------------------------------------------------------- | | `/{orgSlug}/users` | Members tab with every user of the organization. | | `/{orgSlug}/users/{userId}` | One user's details, role and access. | | `/{orgSlug}/users/invitations` | Pending invitations tab. | | `/{orgSlug}/users/invitations/create` | Invite a user. | | `/{orgSlug}/teams` | Teams list. | | `/{orgSlug}/teams/create` | Create a team. | | `/{orgSlug}/roles` | Roles & Permissions, with the Roles tab. | | `/{orgSlug}/roles/create` | Create a custom role. | | `/{orgSlug}/roles/approval-rules` | Approval rules tab. See [Approvals](/product/approvals). | Customer contacts who only use the [customer portal](/product/customer-portal) are not users of the organization and do not appear here. ## Key concepts [#key-concepts] ### Users [#users] A user is a person with a role in the organization. Each user has exactly one organization role. Users can be disabled, which blocks their access without removing them, or removed from the organization. ### Roles [#roles] A role is a set of permissions. Every organization has four built-in roles that cannot be edited or deleted: | Role | Access | | ------ | ----------------------------------------------------------------------------------------------- | | Owner | Everything an admin can do, plus billing, deleting the organization and transferring ownership. | | Admin | Manage users, settings, roles and integrations. | | Member | Standard write access to business data. | | Viewer | Read-only access. | You can add custom roles with any combination of organization permissions. Built-in roles show as Locked in the roles list. ### Permissions [#permissions] Permissions follow the pattern `organization.{feature}.{action}`, for example `organization.quotes.send`. Most features have view, create, update and delete permissions, and some add their own actions. Every page of these docs lists the permissions it needs. See [RBAC](/architecture/rbac) for how permissions are checked. ### Teams [#teams] A team groups users who work together, such as Field Operations. Teams are used to assign work, share calendars and mention groups with `@`. Teams do not grant permissions. A team has a name, a slug, an icon and a description, and can be archived when it is no longer used. ### Invitations [#invitations] An invitation asks someone to join the organization with a role. It is sent by email and is valid for seven days. Pending invitations can be edited, resent or revoked until they are accepted. ## Manage users [#manage-users] ### Invite a user [#invite-a-user] 1. Open Users and select Invite, or open `/{orgSlug}/users/invitations/create`. 2. Enter the email address and pick a role. First and last name are optional. 3. Select Send invitation. The invitee opens the link at `/invitations/{token}`. They log in or create an account, and then join the organization with the chosen role. If they are signed in with a different email address, the page tells them to switch accounts. Expired, revoked and already accepted invitations show a clear message instead. ### Manage pending invitations [#manage-pending-invitations] The Pending invitations tab lists email, role, sent date and expiry, with Active and Expired filters. Row actions are Open, Copy email, Resend invitation and Revoke invitation. Select several rows to revoke them together. ### Change a user's role or access [#change-a-users-role-or-access] Open a user from the Members tab. On the detail page you can: * Choose a different role. * Update the user's profile details and photo. * Disable or enable the user under Member access. * Remove the user from the organization. Type the confirmation text to confirm. The Members tab can be searched and filtered by Access and Account status. Row actions are Open and Copy email. ## Manage teams [#manage-teams] 1. Open Teams and select New team. 2. Enter the name, slug, icon and description, and select Create team. 3. On the team page, use Add team members to pick existing users of the organization, and save. Archive a team to hide it from active lists, or unarchive it to bring it back. Deleting a team is permanent. Both actions are available on the team page under Danger zone and as row and bulk actions in the list. ## Manage roles [#manage-roles] 1. Open Roles & Permissions and select New role. 2. Enter a name. The key is generated from the name and must be lowercase with dashes. 3. Add an optional description. 4. Select the permissions the role grants, and save. To change a custom role, open it from the list and edit its details or permissions. Delete a role from its row actions. Give its users and pending invitations another role first, because a role that is still assigned cannot be deleted. ## Permissions [#permissions-1] | Permission | Allows | | ---------------------------------------------------------- | ---------------------------------------------- | | `organization.members.view` | See users and invitations. | | `organization.members.invite` | Invite users and resend or revoke invitations. | | `organization.members.update` | Change roles, profile details and access. | | `organization.members.remove` | Remove users from the organization. | | `organization.teams.view`, `.create`, `.update`, `.delete` | Work with teams. | | `organization.roles.view`, `.create`, `.update`, `.delete` | Work with custom roles. | | `organization.approvals.manage` | Manage approval rules. | ## Automation and AI [#automation-and-ai] The assistant can read teams, users, invitations, roles and permissions. It can also create and update teams, invite users, change roles, edit custom roles and set per-user permission overrides. Every change to access asks for approval first. See [Chat](/product/chat). ## API [#api] All routes live under `/api/v1/organizations/{organizationId}/`: | Method and path | Purpose | | ------------------------------------------------------ | -------------------------------------- | | `GET members` | List users. | | `POST members` | Invite a user. | | `GET members/{userId}` | Read one user. | | `PATCH members/{userId}` | Change a user's role. | | `GET teams` and `POST teams` | List and create teams. | | `GET`, `PATCH` and `DELETE teams/{teamId}` | Read, update and delete a team. | | `PUT teams/{teamId}/members` | Replace a team's members. | | `GET invitations` | List invitations. | | `GET`, `PATCH` and `DELETE invitations/{invitationId}` | Read, update and revoke an invitation. | Not yet available: REST endpoints for roles and approval rules. See [API](/developers/api) and [OpenAPI](/developers/openapi). ## Related pages [#related-pages] * [Approvals](/product/approvals) * [Organizations](/product/organizations) * [Audit log](/product/audit-log) * [RBAC](/architecture/rbac) * [Roles and permissions](/admin/roles-and-permissions) # Agenda (/product/agenda) How to schedule agenda items in shared and personal calendars, plan task work on the Planning board, subscribe through iCalendar feeds and import Google Calendar events. ## Overview [#overview] Agenda is the organization's calendar. It holds agenda items in calendars that people own or share, and a Planning board that schedules task work onto people, teams or customers. Agenda lives under Workspace, Work in the sidebar, next to Tasks and Planning. The routes are: | Route | Purpose | | ------------------------------------------ | -------------------------------------------------------------------------- | | `/{orgSlug}/agenda` | Calendar with Day, Week, Month and Agenda list views. Week is the default. | | `/{orgSlug}/agenda/{agendaItemId}` | Agenda item detail. | | `/{orgSlug}/agenda/calendars` | Calendars you can view or manage. | | `/{orgSlug}/agenda/calendars/{calendarId}` | Calendar settings. | | `/{orgSlug}/agenda/planning` | Planning board. | ## Key concepts [#key-concepts] ### Calendars [#calendars] Every agenda item belongs to one calendar. A calendar has a name, description, color, default time zone and an owner, and its owner can share it with users and teams as Viewer or Editor. Each person chooses which calendars are shown in their own view. Three calendars are reserved. They cannot be deleted or transferred: | Calendar | Purpose | | ------------ | --------------------------------------------------------------------------------------------------- | | Organization | The default company calendar. | | Personal | Each user's own calendar. | | Planning | One organization-wide calendar that holds scheduled task work. It is always shared with every user. | Calendars imported from Google Calendar are read-only in CentraKit. Their details, ownership, sharing and deletion are managed at the source. ### Agenda items [#agenda-items] An agenda item follows standard calendar semantics so it can travel through iCalendar feeds: * Title, rich text description, start and end, or all day. * Location and meeting URL. * Show as Busy or Free, and status Confirmed, Tentative or Cancelled. * Invitees: people, teams, customers or any email address. * Recurrence: daily, weekly, monthly or yearly, with an interval, weekdays, a day of the month or weekday position, and an end date. * Notifications a number of minutes or days before the start, shown in the app or sent by email. * A related task, which links the item to work. When you edit or delete a recurring item, choose This event or Entire series. ### Planning [#planning] The Planning board is a timeline of people, teams or customers (Group rows by). It has Day, Week and Month scales and respects working hours. Open tasks that are not scheduled yet sit in a side panel that you can filter and sort by due date or priority. Assigning a task on the board creates an agenda item on the Planning calendar and links it to the task. The reserved time shows up on the task as a draft time entry until someone confirms it. The board flags scheduling clashes and people who are missing a task's required skills. ## Work with Agenda [#work-with-agenda] ### Create an agenda item [#create-an-agenda-item] Select New agenda item, or select a time slot in the Day, Week or Month view, and fill in the schedule, invitees, recurrence and notifications. Drag an item to move it, or drag its edge to resize it. Open the item for its full detail page, where you can also assign or create a related task. ### Create and share a calendar [#create-and-share-a-calendar] Open Calendars and select New calendar. In the calendar settings you can: * Change the name, color, description and default time zone. * Grant users and teams Viewer or Editor access under Sharing. * Transfer the calendar to yourself, the organization or another user. * Set your own default reminders and choose which changes email you: new, changed and cancelled events, and event responses. * Generate a private iCalendar URL. * Delete the calendar from the Danger zone. Move or delete its agenda items first. ### Plan task work [#plan-task-work] Open Planning, choose a date and scale, then drag open tasks from the side panel onto a row. Use Reassign task or Remove from planning on a scheduled block. You can also create a task directly from the board. Suggestions shows AI-proposed draft bars for unscheduled tasks. Accept a suggestion to schedule it, dismiss it, or drag it to another slot. The Suggestions filter shows or hides the draft bars. ### Subscribe with iCalendar [#subscribe-with-icalendar] Each calendar's settings page can generate a private iCalendar URL for you. Paste it into another calendar app to subscribe to that calendar. The feed is served from `/portal/calendars/{token}` and is checked against your current access on every request. Anyone with the URL can read the calendar, so use Rotate private URL to invalidate an old one. ### Import Google Calendar [#import-google-calendar] Open the Google Calendar integration from [Integrations](/product/integrations) and select Connect Google Calendar. After you sign in with Google, your primary calendar is enabled and imported right away. Enable other calendars under Enable calendar import. Imported events are read-only and appear under your own calendars. * A background job syncs enabled calendars incrementally every 5 minutes. Select Sync now to refresh immediately. * If the first import fails, the connection shows a notice and Sync now retries it. * If Google access is revoked or expires, the connection is flagged and offers Reconnect. Reconnecting keeps your calendar settings and sync state. The import is one way. To show CentraKit items in Google Calendar, subscribe to a private iCalendar URL instead. Not yet available: two-way sync, CalDAV and outbound invitation emails with RSVP handling. ## Permissions [#permissions] | Key | Grants | | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | | `organization.agenda.view` | View the agenda and calendars, set personal calendar preferences and notifications, and manage your own private iCalendar URLs. | | `organization.agenda.create` | Create agenda items and calendars. | | `organization.agenda.update` | Edit and move agenda items, change calendar details and sharing, and transfer calendars. | | `organization.agenda.delete` | Delete agenda items and calendars. | Calendar sharing applies on top of these keys: a calendar Viewer cannot edit its items. Planning also needs `organization.tasks.update` to assign or unassign tasks and `organization.tasks.create` to create a task from the board. Opening the Google Calendar integration page and changing import settings requires `organization.integrations.manage`. ## Automation and AI [#automation-and-ai] The automatic Agenda item follow-up workflow notifies assignees when an item starts and again one hour after it ends, if the item still exists. See [Workflows](/product/workflows). MCP tools for agenda items: | Tool | Kind | | ------------------ | ----- | | `listAgendaItems` | Read | | `getAgendaItem` | Read | | `createAgendaItem` | Write | | `updateAgendaItem` | Write | | `moveAgendaItem` | Write | | `deleteAgendaItem` | Write | The assistant side panel receives the visible agenda as context. See [MCP](/developers/mcp). ## API [#api] Organization paths are relative to `/api/v1/organizations/{organizationId}`. | Resource | Operations | | ---------------------------------- | ------------------------------------------------------------------------------ | | `agenda/items` | `listAgendaItems` (GET), `createAgendaItem` (POST) | | `agenda/items/{agendaItemId}` | `getAgendaItem` (GET), `updateAgendaItem` (PATCH), `deleteAgendaItem` (DELETE) | | `agenda/items/{agendaItemId}/move` | `moveAgendaItem` (POST) | Two routes sit outside the organization prefix: * `GET /api/v1/public/calendars/{token}` (`downloadPublicCalendarFeed`) serves the iCalendar feed behind `/portal/calendars/{token}`. * `GET /api/v1/agenda/google-calendar/oauth/start` (`startGoogleCalendarOAuth`) starts the Google sign-in. The Google Calendar sync runs as the cron job `GET /api/v1/internal/google-calendar-sync` every 5 minutes. See [API](/developers/api) and [OpenAPI](/developers/openapi). ## Related pages [#related-pages] * [Tasks](/product/tasks) for the work that Planning schedules. * [Time](/product/time) for confirmed time entries. * [Integrations](/product/integrations) for the Google Calendar connection. * [Notifications](/product/notifications) for reminders and calendar emails. # Approvals (/product/approvals) How approval rules make risky actions by people, AI agents, workflows, MCP clients and API keys wait for a human decision, and how approvers decide, escalate and expire requests. ## Overview [#overview] Approvals put a human checkpoint in front of risky actions. When an action matches an approval rule, CentraKit records an approval request and the action waits until someone confirms or approves it. Approval rules are managed on the Approval rules tab of Roles & Permissions at `/{orgSlug}/roles/approval-rules`. Requests are decided from [Notifications](/product/notifications). Approvals cover actions from every source: the assistant, workflows, MCP clients, API keys and people in the app. Requests for time off, timesheets and expenses use the same requests but have their own review flow in [People](/product/people), [Time](/product/time) and [Expenses](/product/expenses). ## Key concepts [#key-concepts] ### Actions and effects [#actions-and-effects] Every gated action has a key, such as `quote.send`, and a risk level of low, medium, high or critical. Actions are also tagged with effects that describe why they are risky: | Effect | Example | | ----------------------- | --------------------------------- | | Customer-facing | Sending a quote to a customer. | | Moves money | Writing off an invoice. | | Destructive | Deleting or archiving records. | | Changes access | Inviting users or changing roles. | | Calls an outside system | Sending data to an integration. | | Bulk change | Updating many records at once. | A rule can target one action, every action with an effect, or any action. ### Approval rules [#approval-rules] A rule has these parts: | Field | Meaning | | ------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Name and Description | How the rule appears in the list and to approvers. | | Action | One action, or Any action. | | Effect | One effect, or Any effect. | | Who is acting | People, AI agents, Workflows, MCP clients and API keys. Selecting none means everyone. | | Only when | Up to ten conditions on the action input, such as `input.quantity` more than 50. Operators are equals, is not, more than, at least, less than, at most, is one of, contains and is set. | | Requires | Someone else approves, or The requester confirms. | | Approvers | Anyone allowed to do the action, or holders of a chosen permission. | | Approval mode | Any approver, Every approver or In order. | | Approvals needed | The quorum, from 1 to 10. | | Expires after (hours) | The request expires if nobody decides in time. | | Escalate after (hours) and Escalate to | Who is notified when the request is still open after that time. Set both or neither. | | The requester may approve their own request | Allows self-approval. | | Rule is active | Turns the rule on or off without deleting it. | ### Platform rules [#platform-rules] Platform administrators can set rules that apply to every organization. They appear in the list with a lock and the note "Set by the platform. Organizations can add stricter rules but not remove this one." Organization rules can only add approvals, never remove one that the platform requires. See [Roles and permissions](/admin/roles-and-permissions). ### Approval requests [#approval-requests] A request records the action, who asked for it and from where (the assistant, a workflow, an MCP client, the app or the API), and an explanation of the intent, the impact, the data it touches and how to roll it back. | Status | Meaning | | ---------------------- | ----------------------------------------- | | Requested | Waiting for a decision. | | Approved | Approved and ready to run once. | | Denied | Refused. The action does not run. | | Expired | Nobody decided before the deadline. | | Superseded or Canceled | Replaced by a newer request or withdrawn. | | Consumed | The approved action ran. | An approval is tied to the exact input that was requested. If the input changes, the action needs a new approval. Records that wait on a request show Awaiting approval, Approved or Approval denied in their progress bar. ### Actions that are gated today [#actions-that-are-gated-today] Besides AI and workflow actions, these domain actions check approval rules: * Sending a quote and writing off an invoice, in the app and through the API. * Creating, confirming and cancelling orders, and order proposals from the [Inbox](/product/inbox). * Shipping a shipment. * Creating and receiving purchase orders. ## Manage approval rules [#manage-approval-rules] ### Add a rule [#add-a-rule] 1. Open Roles & Permissions and select the Approval rules tab. 2. Select Add rule. 3. Fill in the New approval rule dialog and select Save. When the organization has no rules yet, the tab shows "No approval rules yet". Risky AI actions still ask the requester for confirmation without a rule. Add a rule to require a second person. ### Edit, pause or delete a rule [#edit-pause-or-delete-a-rule] Use the row actions Edit rule and Delete rule. To pause a rule, turn off Rule is active. Deleting a rule means actions it covers no longer wait for approval. ## Decide a request [#decide-a-request] Approvers get an approval notification in the topbar and in the Notifications hub. Select Approve or Deny on the row. When a rule needs more than one approver, the toast reads "Decision recorded; more approvals are needed" until the quorum is met. For actions started in the app, through the API or from an MCP client, run the action again with the same input after it is approved. The assistant and workflows continue on their own. ### Escalation and expiry [#escalation-and-expiry] A scheduled job runs every ten minutes. It notifies the escalation target for requests that passed their escalation time and marks requests as expired when they pass their expiry time. The requester is notified of the outcome. ## Permissions [#permissions] | Permission | Allows | | ------------------------------- | ---------------------------------------------------------- | | `organization.approvals.manage` | Create, edit and delete the organization's approval rules. | | `system.approvals.manage` | Manage platform rules in Admin. | Deciding a request needs the approver permission set on the rule. By default that is the permission of the action itself. ## Automation and AI [#automation-and-ai] * The assistant asks for approval before risky actions and shows the request in the chat. See [Chat](/product/chat). * Workflows can include an approval step that pauses the run until someone decides. See [Workflows](/product/workflows). * MCP clients receive an `APPROVAL_REQUIRED` error with the `approvalId` when a tool call needs approval. Confirm the request in Notifications or wait for the approver, then call the same tool again with the same input. See [MCP](/developers/mcp). ## API [#api] REST calls that hit an approval rule return `409` with the `APPROVAL_REQUIRED` code and an `approvalId` extension. Retry the same request after approval. See [Errors](/developers/errors). `GET` and `POST /api/v1/organizations/{organizationId}/approvals` list and decide pending time off, timesheet and expense requests. They do not cover approval rules or other approval requests. Not yet available: REST endpoints for approval rules and general approval requests. ## Related pages [#related-pages] * [Notifications](/product/notifications) * [Access control](/product/access-control) * [Workflows](/product/workflows) * [Audit log](/product/audit-log) * [RBAC](/architecture/rbac) # Audit log (/product/audit-log) How to review, filter, inspect and export the organization audit log, including restricted details, CSV exports, retention per plan and the audit API. ## Overview [#overview] The audit log is the organization's security and change record. It lists who did what, to which record, from which surface and with what outcome. Events are append-only: nobody in the organization can edit or delete them. The audit log lives under Organization, Account, Audit log in the sidebar. The routes are: | Route | Purpose | | -------------------------------- | ----------------------------------------- | | `/{orgSlug}/audit-log` | The event table with filters and exports. | | `/{orgSlug}/audit-log/{eventId}` | Event detail. | Platform administrators have a separate log across organizations at [Admin audit log](/admin/audit-log). For how events are captured and stored, see [Audit logs architecture](/architecture/audit-logs). ## Key concepts [#key-concepts] ### Event fields [#event-fields] Each event records: * Timestamp, shown in local time and UTC. * Actor: a user, platform admin, system, API key, integration, workflow or webhook. Platform admin identities are masked in the organization view. * Event type and category. * Target: the affected record and its type. * Source: SaaS app, API, MCP, AI, workflow, webhook, database or system. * Outcome: success, failure, denied or attempted. * Structured changes and request context, including request ID and correlation ID. ### Categories [#categories] Events are grouped into Authentication, Users, Permissions, Billing, Data, Settings, Integrations, Security and System. ### Restricted details [#restricted-details] Some events carry restricted request context: IP address, session ID and user agent. These fields are hidden by default. Revealing them requires a dedicated permission, and every reveal writes its own audit event. ### Retention [#retention] Retention depends on the organization's plan: | Plan | Retention | | ---------- | ------------------------------------------------ | | Starter | 1 day | | Pro | 7 days | | Business | 90 days | | Enterprise | 365 days, or a custom period set by the platform | A daily job removes events older than the retention period. See [Billing and usage](/product/billing-and-usage). ## Work with the audit log [#work-with-the-audit-log] ### Find events [#find-events] The table has columns for Timestamp, Actor, Event, Target and Source and outcome. Use Search events and targets to search, and the filters for date, actor kinds, event types, categories, sources and outcomes. Filters and paging live in the URL, so you can share a filtered view. ### Inspect an event [#inspect-an-event] Select a row, or View details in its menu, to open the event detail page. It shows the event ID, actor, organization, local and UTC time, source, category, target, structured changes and request context. From there you can: * Open affected resource to go to the record, when you have permission to view it. * View related events that share the event's correlation ID. * Reveal restricted details, if you hold the reveal permission. * Copy the event ID or request ID from the row menu. ### Export to CSV [#export-to-csv] Select Exports in the table toolbar, then Create export. The export uses the current filters and needs a date range of no more than 90 days. Exports run in the background: refresh the panel to see Pending, Running, Completed, Failed or Expired. Download a completed export with Download export. Files expire after 24 hours. Organization exports never include restricted details. Requesting, completing and downloading an export are audited. ## Permissions [#permissions] | Key | Grants | | ------------------------------- | -------------------------------------- | | `organization.audit-log.view` | Open the audit log and event details. | | `organization.audit-log.export` | Create and download CSV exports. | | `organization.audit-log.reveal` | Reveal restricted details on an event. | Opening an affected resource also requires the view permission of that record type, such as `organization.customers.view` or `organization.tasks.view`. See [Access control](/product/access-control). ## Automation and AI [#automation-and-ai] The assistant side panel can summarize the visible results, explain a visible event and suggest filters. MCP tools for the audit log: | Tool | Kind | | ------------------------------- | ----- | | `listOrganizationAuditEvents` | Read | | `createOrganizationAuditExport` | Write | | `getOrganizationAuditExport` | Read | See [MCP](/developers/mcp). ## API [#api] Paths are relative to `/api/v1/organizations/{organizationId}`. | Resource | Operations | | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | | `audit-events` | `listOrganizationAuditEvents` (GET) returns a cursor-capable page of redacted event summaries. | | `audit-exports` | `createOrganizationAuditExport` (POST) queues a CSV export of redacted events. | | `audit-exports/{exportId}` | `getOrganizationAuditExport` (GET) returns an export job owned by the caller, with a five-minute download URL while the file is available. | `audit-events` requires `organization.audit-log.view`. Both export routes require `organization.audit-log.export`. Restricted details and sensitive metadata are never returned through the API. See [API](/developers/api) and [OpenAPI](/developers/openapi). ## Related pages [#related-pages] * [Audit logs architecture](/architecture/audit-logs) * [Admin audit log](/admin/audit-log) * [Access control](/product/access-control) * [Billing and usage](/product/billing-and-usage) # Billing and usage (/product/billing-and-usage) How an organization chooses a plan, pays through Stripe Checkout and the Stripe Customer Portal, and tracks metered usage, credits and limits on the Usage and Limits page. ## Overview [#overview] Billing covers the organization's subscription to the platform. Usage covers what the organization consumes during the billing period and how that compares with its plan. Both pages live under Organization, Account in the sidebar: | Route | Purpose | | ------------------------------------ | ---------------------------------------------------------------------------- | | `/{orgSlug}/billing` | Billing: current plan, billing details, payment methods and invoice history. | | `/{orgSlug}/billing/checkout/return` | Landing page after Stripe Checkout. | | `/{orgSlug}/billing/manage/return` | Landing page after the Stripe Customer Portal. | | `/{orgSlug}/usage` | Usage and Limits: current-period usage per meter. | This page is about paying for the platform. For invoices that the organization sends to its own customers, see [Invoices](/product/invoices). ## Key concepts [#key-concepts] ### Plans [#plans] | Plan key | Name | Summary | | ------------ | ---------- | ------------------------------------------------------------------------------------------- | | `starter` | Starter | Free and limited, for evaluation. Every organization starts here. | | `pro` | Pro | Self-serve plan for small teams that need AI, inbox, API and workflow credits. | | `business` | Business | Higher included credits, unlimited active users and longer audit log retention. | | `enterprise` | Enterprise | Custom, quote-only plan with volume-based credit pricing, dedicated support and onboarding. | Pro and Business are sold as credit packages, billed monthly or yearly. Larger packages include more credits, and unused credits roll over: the page shows the current package credits plus unexpired rollover credits, and the oldest balance is used first. Enterprise is arranged with the platform team and cannot be selected in the app. Plans, packages, prices and limits are managed by platform administrators in [Admin plans](/admin/plans), so the exact numbers can differ per deployment. ### Meters [#meters] Usage is recorded per meter in an append-only ledger and summed for the current billing period. Billable meters count toward the plan and can produce overage: | Meter | Counts | | ------------------------- | -------------------------------------------------------------------------------- | | `ai.input_tokens` | Input tokens for AI chat, suggestions, agents, inbox auto-replies and workflows. | | `ai.output_tokens` | Output tokens for the same features. | | `inbox.messages_received` | Inbound messages on connected inbox channels. | | `inbox.messages_sent` | Outbound human and AI replies on connected inbox channels. | | `api.requests` | Organization-scoped REST API requests. | | `mcp.tool_calls` | External MCP tool calls. | | `workflow.steps` | Weighted workflow steps. | | `files.storage_bytes` | File storage, which also sets the Files storage quota. | `workflow.steps` is the billable workflow meter. Actions and AI steps count once, paid registry lookups such as a KvK lookup carry a higher weight, and flow control such as triggers, conditions, delays, stages, waits and approvals is free. Steps that are skipped bill nothing. Lifecycle runs and test runs are not billed. `workflow.runs` is tracked for reporting only. Other meters are tracked without billing: knowledge retrievals, memory operations, file uploads and extractions, media processing, email deliveries, notifications and active users. Active users are still a plan limit. ### Limits [#limits] Each plan sets an included quantity and an enforcement mode per meter: | Mode | Behavior | | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | | Hard limit | Features that check an allowance, such as AI turns and workflow steps, are blocked once the included quantity is used. Starter uses this mode. | | Included plus overage | Usage above the included quantity is billed as metered overage. Pro and Business use this mode. | | Soft limit | Usage is tracked against a custom or unlimited allowance. Enterprise uses this mode. | AI chat checks the AI allowance before a turn starts, and starting a workflow action checks the step allowance first. Overage is reported to Stripe metered billing, while the local ledger stays the source of truth for the Usage and Limits page. An hourly workflow emails organization owners when usage crosses 80 percent and 100 percent of an included quantity. ## Manage billing [#manage-billing] ### Choose or change a plan [#choose-or-change-a-plan] Open Billing. The Current plan section shows the active plan, its features and credits, and Choose a plan. Pick a plan, a credit package and Monthly or Yearly, then continue to Stripe Checkout. After payment you land on the checkout return page. The return page is only a hint. Stripe sends the final subscription update through a verified webhook, so the Billing page can show Stripe is finalizing your plan for a short time. Refresh if the change is not visible yet. ### Manage the subscription in Stripe [#manage-the-subscription-in-stripe] Select Manage in Stripe, or Manage payment methods in the Payment methods section, to open the Stripe Customer Portal. Cards and payment methods are managed in Stripe; the app stores only safe display metadata for them. After you leave the portal you land on the manage return page. ### Billing details [#billing-details] The Billing details section holds the company name, billing email, invoice email recipients, address and tax ID with its type, such as EU VAT or Dutch BTW. These details appear on subscription invoices. ### Invoice history [#invoice-history] Invoice history lists the subscription invoices from Stripe with their status. Select a row to download the Stripe invoice PDF. ## Review usage [#review-usage] Open Usage and Limits to see, for each meter, the current-period quantity against the included quantity, credits used and estimated overage. The page also shows the total credit limit, whether auto top-up is enabled and the latest usage events recorded in the period. Explain usage asks the assistant side panel to walk through the numbers. ## Permissions [#permissions] | Key | Grants | | ----------------------------- | ------------------------------------------------------------------------------------------------------------ | | `organization.billing.manage` | Open Billing and Usage and Limits, start checkout, open the Stripe Customer Portal and edit billing details. | Platform administrators manage plans and subscriptions with `system.billing.view` and `system.billing.manage`. See [Admin billing](/admin/billing) and [Access control](/product/access-control). ## Automation and AI [#automation-and-ai] The automatic Billing usage threshold workflows publish an event when usage crosses a threshold and email the organization owners. See [Workflows](/product/workflows). Billing has no MCP tools and no organization REST resources. Checkout, the portal and billing details run as server actions in the app. Stripe calls `POST /api/v1/billing/stripe/webhook` to update the local billing state. ## Related pages [#related-pages] * [Stripe billing operations](/operations/stripe-billing) for environment variables, webhook events and go-live checks. * [Admin plans](/admin/plans) for plan and limit configuration. * [Audit log](/product/audit-log) for retention per plan. * [Files](/product/files) for the storage quota. # Chat (/product/chat) How the CentraKit AI assistant works in the Chats page and side panel, including Ask, Plan and Agent modes, models, attachments, commands, approvals, agent runs, sharing, contextual actions, knowledge retrieval and organization AI settings. ## Overview [#overview] Chat is the AI assistant of CentraKit. It answers questions about the organization, plans work and carries out actions with the same permissions and approvals as the person using it. A prompt can never grant access that the user does not already have. The assistant is available in two places: * The **Chats** page, at the top of the organization sidebar, with the chat history beside the conversation. * The **side panel**, which opens from the top bar on any organization page without leaving it. | Route | Purpose | | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | | `/{orgSlug}/chats` | Chat history and a new chat. | | `/{orgSlug}/chats/{threadId}` | One chat. | | `/{orgSlug}/chats/activity` | Opens [Notifications](/product/notifications) filtered to items that need action, such as AI approvals and proposed artifacts. | | `/{orgSlug}/ai/settings` | Organization AI settings. | | `/portal/chats/{token}` | Read-only shared chat, opened from a share link. | ## Key concepts [#key-concepts] ### Chats [#chats] A chat is a thread of messages. Chats are private to their owner by default. The history groups chats into Pinned and recent periods and can be searched. The actions menu on a chat offers Share, Rename, Download (a transcript), Mute notifications, Pin or Unpin, Archive and Delete. ### Modes [#modes] | Mode | Use it to | | ----- | ------------------------------------------------------------------------ | | Ask | Get answers about the organization from its data and knowledge. | | Plan | Get a structured plan. Review the plan before moving into Agent mode. | | Agent | Let the assistant carry out the work with tools, as a durable agent run. | The highest mode an organization can use and the risk level that needs approval are set by platform administrators. See [Admin AI settings](/admin/ai-settings). ### Models [#models] **Auto** chooses the model, reasoning effort and context window for each request. You can also pick one of the enabled models, a reasoning effort, a context window and a speed (Normal or Fast). A chosen model must support the attached files. When it does not, CentraKit asks you to choose Auto or a compatible model instead of switching silently. Each settled answer shows the model that actually produced it. ### Tools and approvals [#tools-and-approvals] The assistant works through tools for customers, assets, tasks, quotes, invoices, orders, purchase orders, promotions, agenda, files, knowledge, inbox, time off, HR approvals, analytics, workflows, integrations, notifications, access, billing and usage, plus web search. Every tool checks the user's permission. Routine reads run without asking and are summarized as gathered context. Changes show an approval card with **Approve** and **Deny**. When several changes are proposed at once, they appear as one checklist, and you can approve all, deny all or approve a subset. Only approved tools run. ### Agent runs [#agent-runs] An Agent mode request runs as an agent run with steps for planning, tasks, tool calls, approvals, checks and handoff. A run can be queued, planning, awaiting approval, running, checking, retrying, succeeded, partially succeeded, failed, canceled, stopped or escalated. If a connection drops, the chat reconnects to the run. **Request human handoff** asks a person to take over and records why. Approvals are always decided by people, never by an agent. ### Contextual actions and artifacts [#contextual-actions-and-artifacts] Many pages offer AI actions for what is on screen, such as summarizing a customer, reviewing a quote before sending, drafting an inbox reply or explaining an analytics report. Drafts are applied to the form as reviewable changes, and nothing is saved until you save the form. Larger results are stored as artifacts, such as plans, reviews, summaries, customer follow-ups and workflow suggestions. An artifact is proposed, applying, applied, rejected or stale when the page it was made for has changed. ## Work with chats [#work-with-chats] ### Start a chat [#start-a-chat] Select **New chat**, or open the side panel. A new chat offers three starter suggestions from your organization context. Type a message and send it. After each answer, the assistant suggests follow-up questions, and **Regenerate suggestions** fetches three new ones. ### Add context and commands [#add-context-and-commands] * Type `@` or `#` to reference users, teams and records such as customers, tasks, quotes and invoices. References appear as chips with a preview on hover. * Type `/web` to search the public web. * Type `/workflow` and pick a published organization workflow to run. ### Attach files and use your voice [#attach-files-and-use-your-voice] Select **Add attachment** to upload files, take a picture or take a screenshot. Images, audio and PDFs are checked against the chosen model. Select **Voice input** to dictate a message, which is transcribed into the composer. ### Queue, stop and retry [#queue-stop-and-retry] You can keep typing while the assistant answers. Extra messages wait under Queued messages, where you can edit or delete them. Stop ends the current answer. When a response fails, the notice explains the cause, such as a rate limit, the organization usage limit, an unsupported attachment or an unavailable model, and offers the matching retry or fix. ### Rate an answer [#rate-an-answer] Mark an answer as **Good response** or **Poor response**. Ratings are private to you and store no message text. ### Share a chat [#share-a-chat] Select **Share** in the chat's actions menu: 1. Add organization users as **Participants** to give them access to the chat. 2. Select **Create link** under Read-only links to copy a link that anyone can open at `/portal/chats/{token}` without signing in. 3. Select **Revoke** to end a link at once. A revoked, expired or invalid link shows "Shared chat unavailable". ## Knowledge retrieval [#knowledge-retrieval] The assistant searches the organization's uploaded knowledge, such as manuals, policies and handbooks, and can list and read the knowledge files and their versions. Knowledge search is for documents, not for app records like customers or invoices. Organizations upload knowledge under AI, Knowledge in the sidebar, and platform administrators manage platform knowledge in [Admin knowledge](/admin/knowledge). MCP clients use the same retrieval through the `retrieveKnowledge` tool. ## Organization AI settings [#organization-ai-settings] `/{orgSlug}/ai/settings` controls AI for one organization: | Setting | Meaning | | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Inference region | EU or US pins inference and provider storage to that zone. Global routes for availability and latency with no residency guarantee. Pinning a region can cost more. | | Chat instructions | Optional terminology and tone for Ask, Plan and Agent. They cannot override permissions, approvals, modes, tools or platform safety rules. | | Background actions | Whether AI actions may run in the background. | | Private memory | Whether the assistant keeps private memory per user, and for how many days. | | External integrations | Whether AI actions may use connected integrations. | | Daily credit budget | A daily limit on AI credits for the organization. | | Contextual actions | Every contextual action per page, each with its own on and off switch. | ## Permissions [#permissions] | Permission | Allows | | ----------------------------- | ------------------------------------------------------------ | | `organization.chats.view` | Open Chats and the side panel and read chats you can access. | | `organization.chats.create` | Start chats, send messages and start specialists. | | `organization.chats.update` | Rename, pin and archive chats. | | `organization.chats.delete` | Delete chats. | | `organization.knowledge.view` | Let the assistant search organization knowledge. | | `organization.general.update` | Edit organization AI settings. | Only the owner of a chat can change, share or delete it and manage its participants. Participants can read and post in the chat. Each tool also needs the permission of the feature it touches. See [Access control](/product/access-control) and [RBAC](/architecture/rbac). ## Automation and AI [#automation-and-ai] * **Research specialist** and **Review specialist** run a focused, read-only investigation or review from fresh context. They are started through the API. * AI actions, approvals and proposed artifacts that need you appear in [Notifications](/product/notifications). * MCP clients can retrieve knowledge with `retrieveKnowledge`. See [MCP](/developers/mcp) and [AI agents](/developers/ai-agents). ## API [#api] The chat transport lives under `/api/v1/ai/`: | Resource | Routes | | ------------------------ | ----------------------------------------------------------------------------------------- | | Chat stream | `POST ai/chat`, `GET ai/chat/{threadId}/stream` to resume, `POST ai/chat/{threadId}/stop` | | Threads | `GET ai/chat/threads`, `PATCH` and `DELETE ai/chat/{threadId}` | | Participants and sharing | `ai/chat/{threadId}/participants`, `ai/chat/{threadId}/share` | | Attachments | `POST ai/chat/attachments`, `GET ai/chat/attachments/{attachmentId}` | | Suggestions | `POST ai/chat/starter-suggestions`, `POST ai/chat/follow-ups` | | Voice | `POST ai/transcribe` | | Specialists | `GET` and `POST ai/specialists` | Organization routes live under `/api/v1/organizations/{organizationId}/ai/`: `threads`, `threads/{threadId}/messages`, `activity`, `models`, `agent-runs/{runId}` with `events`, `approvals/{approvalId}` and `handoff`, `contextual-actions/{actionId}` and `contextual-artifacts`. Shared chats are read from `GET /api/v1/public/chats/{token}`, and knowledge from `POST /api/v1/organizations/{organizationId}/knowledge/retrieve`. See [API](/developers/api) and [OpenAPI](/developers/openapi). ## Related pages [#related-pages] * [Notifications](/product/notifications) * [Inbox](/product/inbox) * [Workflows](/product/workflows) * [Admin AI settings](/admin/ai-settings) * [Admin knowledge](/admin/knowledge) # Command center (/product/command-center) How to use the command center to jump to pages, search records, create items, run workflows and start AI prompts from one keyboard-driven dialog. ## Overview [#overview] The command center is a search dialog that works on every page of the app. Open it with Cmd+K on macOS or Ctrl+K on Windows and Linux, or select the search trigger in the sidebar. The dialog is titled "Command center" and its search field reads "Search pages, actions, and more". What the command center offers depends on the navigation mode you are in (Organization, Customer or Admin) and on your permissions. You only see commands and records you are allowed to use. ## Key concepts [#key-concepts] ### Groups [#groups] Results are grouped in this order: | Group | Contents | | ----------- | --------------------------------------------------------------------------------------------------------------------------------------- | | Recent | Up to 12 pages and records you opened from the command center. The list is stored in your browser per navigation mode and organization. | | Create | Shortcuts to create a record. | | Actions | Workflow runs and other actions. | | AI | Prompts that open the assistant with the current page as context. | | Records | Records that match your search text. | | Page groups | Every page in the sidebar, grouped the same way as the sidebar. | ### Create commands [#create-commands] In an organization you can create a customer, task, product, quote, invoice or workflow, as long as you have the matching create permission. Create organization and View all organizations are always available because they live outside a tenant. In Admin mode the command center adds Create workflow, Invite user and Run system workflow for administrators with the matching `system.*` permission. ### Record search [#record-search] Type at least one character to search records. The search waits briefly after you stop typing and then returns up to 24 results, mixed across record types so one type does not crowd out the others. | Mode | Record types | | ------------ | ------------------------------------------------------------------------------------ | | Organization | Customers, tasks, products, customer assets, quotes and invoices. | | Customer | Your quotes, invoices and assets in the [customer portal](/product/customer-portal). | | Admin | Organizations, users, platform invoices and system workflows. | ## Use the command center [#use-the-command-center] ### Jump to a page or record [#jump-to-a-page-or-record] Open the command center, type part of the page title or record name, and press Enter on the result. Use the arrow keys to move between results and Escape to close the dialog. ### Run a workflow [#run-a-workflow] 1. Choose Run workflow in the Actions group. 2. Search for a published workflow with a manual trigger. The list shows up to 20 matches. 3. Confirm in the "Run workflow?" dialog with Run now. CentraKit starts the run and opens its run page. See [Workflows](/product/workflows). ### Ask the assistant [#ask-the-assistant] The AI group has three prompts: Ask AI to summarize this page, Ask AI to draft a follow-up and Ask AI to find related records. Each one opens the side-panel assistant with a draft message and the current page attached as context. The message is not sent until you review it and send it. See [Chat](/product/chat). The AI group only appears when AI is available for the organization and you can start chats. ## Permissions [#permissions] Each command and record type uses the permission of the feature behind it. | Item | Permission | | ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Create customer, task, product, quote, invoice or workflow | `organization.customers.create`, `organization.tasks.create`, `organization.products.create`, `organization.quotes.create`, `organization.invoices.create`, `organization.workflows.create` | | Run workflow | `organization.workflows.run` | | AI prompts | `organization.chats.create` | | Record results in an organization | `organization.customers.view`, `organization.tasks.view`, `organization.products.view`, `organization.assets.view`, `organization.quotes.view`, `organization.invoices.view` | | Admin record results | `system.organizations.view`, `system.users.view`, `system.billing.view`, `system.workflows.manage` | | Admin create commands | `system.workflows.manage`, `system.users.manage` | ## API [#api] `GET /api/v1/command-center/search` returns the same record results as the dialog. It takes these query parameters: | Parameter | Description | | --------- | ----------------------------------------------------- | | `mode` | `organization`, `customer` or `admin`. | | `query` | Search text, 1 to 160 characters. | | `orgSlug` | Required for the `organization` and `customer` modes. | | `limit` | 1 to 40 results. The default is 24. | Each result has an `id`, `kind`, `label`, `description`, `href` and `organizationId`. The `admin` mode requires `system.admin.access`. Responses are never cached. See [API](/developers/api) and [OpenAPI](/developers/openapi). ## Related pages [#related-pages] * [Dashboard](/product/dashboard) * [Workflows](/product/workflows) * [Chat](/product/chat) * [Access control](/product/access-control) # Content (/product/content) The /content hub for document and task templates with their lifecycles and automations, product brands, the product category tree, and organization tags. ## Overview [#overview] Content holds the reusable building blocks that other features draw on. It lives under **Resources > Content** at `/{orgSlug}/content` and has four tabs: | Tab | Route | Detail page | What it manages | | ---------- | --------------------- | ------------------------------------ | ---------------------------------------------------------------- | | Templates | `/content/templates` | `/content/templates/[templateId]` | Defaults for invoices, quotes, tasks, orders and purchase orders | | Brands | `/content/brands` | `/content/brands/[brandId]` | Product brands and manufacturers | | Tags | `/content/tags` | `/content/tags/[tagKind]/[tagSlug]` | Reusable labels for tasks, customers and products | | Categories | `/content/categories` | `/content/categories/[categorySlug]` | The product category tree | Each tab has a create page at `/content/<tab>/create`. Opening `/content` redirects to the first tab you have permission to view, and tabs you cannot view are hidden. ## Templates [#templates] ### Key concepts [#key-concepts] A template has a type, a language and optional default status for its type. | Type | Used for | | -------------- | -------------------------------------------------------------------------------------------------------------------------------- | | Invoice | New invoices, with default due days and selectable standard costs | | Quote | New quotes, with default validity, closing text, email message, prefilled lines, and the option to create an order when accepted | | Task | New tasks, seeding title, description, status, priority, assignees, tags, an optional customer and draft subtasks | | Order | New orders, with default delivery and invoicing settings | | Purchase order | New purchase orders, with purchase terms, a default receive location and the email to the supplier | The language is English or Dutch. Quote and invoice templates also carry the title, introduction, closing, footer, currency, VAT mode, VAT regime, discount and terms and conditions as a URL, rich text or attached PDF. The default template of a type is used for new documents of that type. You cannot delete the last template of a type, and deleting a template does not affect documents already created from it. The public product page and the product PDF use a fixed layout and do not read a template. ### Create and edit a template [#create-and-edit-a-template] 1. Open **Content > Templates** and select **New template**, or open **More template types** to pick a type. 2. Fill in the **Template identity** section: name, description, language, and whether it is the default for its type. 3. Edit the remaining sections. They are the same sections as the live task, quote or invoice page, without the parts that only make sense on a real record, such as the customer picker, send actions, absolute dates, and task comments, time and materials. 4. Save. To use a template, choose **From template** in the New split button on the quote, invoice or task lists. See [Quotes](/product/quotes), [Invoices](/product/invoices) and [Tasks](/product/tasks). ### Workflow tab [#workflow-tab] Every template has a **Workflow** tab at `/content/templates/[templateId]/workflow` with two sections. **Lifecycle** sets the stages and automatic steps that records created from the template go through. The template follows the organization default lifecycle, or the platform default when the organization has none, unless you pick another one. Select **Customize** to make an organization copy of the lifecycle before you change it. For invoice templates, the **Payment reminders** editor shows the reminder ladder as a visual cadence. Each reminder is set in days relative to the due date, from -365 to 365, so a reminder can fire before the due date. A lifecycle can have up to 20 reminders, each on a unique day, with an optional custom email subject and message. Saving reminders publishes the lifecycle, including any unpublished changes made in the workflow editor. **Automations** react to what happens to records from the template, while the lifecycle stays in charge of the status. The section lists: * Automations attached to this template. **Detach** removes one. * Automations that run for every template. Switch one off to skip it for this template. * **Attach existing** to attach a published automation, **Add from starter** to create one from a starter, and **New automation** to open the workflow editor. | Starter | Template type | What it does | | ------------------------ | -------------- | -------------------------------------------------------------------------------------- | | Big-ticket quote | Quote | Tells the team when a quote over 10,000 goes out | | Viewed, not accepted | Quote | Reminds the team to follow up when a quote was viewed but not decided in 3 days | | B2C 14-day letter | Invoice | Sends individual customers the 14-day letter once an invoice is overdue | | Payment failed | Invoice | Flags the invoice and alerts the team when an online payment fails | | Low stock reorder | Purchase order | Drafts a purchase order when stock runs low and sends it to the supplier | | Purchase order overdue | Purchase order | Reminds the supplier and tells the purchaser when a purchase order is late | | Back in stock | Order | Fills backorders when stock arrives and tells customers their order is complete | | Prepaid order to picking | Order | Releases a prepaid order to picking as soon as it is paid | | Ready for pickup | Order | Messages the customer when an order is ready for pickup, and reminds them after 7 days | | Delivered, then invoice | Order | Invoices what was delivered and sends it, for orders invoiced on fulfillment | Starters triggered by stock events, such as Low stock reorder and Back in stock, run for all products or all orders instead of being attached to one template. See [Workflows](/product/workflows) for editing automations. ## Brands [#brands] A brand has a name, slug, website, description, a logo and gallery images, and tags. Products link to one brand. Create one with **New brand**, or create it inline from a product's brand picker. Deleting a brand removes its images and tag assignments. Products that used it keep their details but lose the brand link. ## Categories [#categories] Categories form a tree that organizes products. A category has a name, slug, icon, description and an optional parent. * Select **New category** for a top-level category, or **New subcategory** on a category to nest one under it. * Reorder categories, or use **Move category** to change the parent. * Products can belong to several categories. A parent category does not automatically include its subcategories' products. Deleting a category removes it from products, and its subcategories move to the top level. ## Tags [#tags] Tags are reusable labels with a name, color and description. Each tag has a kind: | Kind | Applied to | | -------- | ---------- | | Task | Tasks | | Customer | Customers | | Product | Products | Brands can carry tags as well. Create tags with **New tag**, or inline from a tag picker. A tag opens at `/content/tags/[tagKind]/[tagSlug]`. Deleting a tag removes all of its assignments. ## Permissions [#permissions] | Key | Grants | | --------------------------------------------------------------- | ------------------------------ | | `organization.templates.view`, `.create`, `.update`, `.delete` | Manage templates | | `organization.brands.view`, `.create`, `.update`, `.delete` | Manage brands and brand images | | `organization.categories.view`, `.create`, `.update`, `.delete` | Manage categories | | `organization.tags.view`, `.create`, `.update`, `.delete` | Manage tags | On the Workflow tab, customizing a lifecycle or adding a starter automation also needs `organization.workflows.create`, and editing reminders needs `organization.workflows.update`. See [Access control](/product/access-control). ## Automation and AI [#automation-and-ai] * The assistant can read and change templates, create and edit brands and categories, and create, update, delete and assign tags. See [Chat](/product/chat). * MCP tools: `listTags`, `getTag`, `createTag` and `updateTag`. See [MCP](/developers/mcp). Not yet available: MCP tools for templates, brands and categories. ## API [#api] All paths are under `/api/v1/organizations/{organizationId}`. | Resource | Operations | | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `/templates` | [List](/openapi/listTemplates), [create](/openapi/createTemplate), [get](/openapi/getTemplate), [update](/openapi/updateTemplate), [delete](/openapi/deleteTemplate) | | `/brands` | [List](/openapi/listBrands), [create](/openapi/createBrand), [get](/openapi/getBrand), [update](/openapi/updateBrand), [delete](/openapi/deleteBrand) | | `/product-categories` | [List](/openapi/listProductCategories), [create](/openapi/createProductCategory), [get](/openapi/getProductCategory), [update](/openapi/updateProductCategory), [delete](/openapi/deleteProductCategory), [move](/openapi/moveProductCategory) | | `/tags` | [List](/openapi/listTags), [create](/openapi/createTag), [get](/openapi/getTag), [update](/openapi/updateTag), [delete](/openapi/deleteTag) | ## Related pages [#related-pages] * [Products](/product/products) * [Quotes](/product/quotes), [Invoices](/product/invoices) and [Orders](/product/orders) * [Tasks](/product/tasks) and [Customers](/product/customers) * [Workflows](/product/workflows) and [Workflow operations](/operations/workflows) * [Access control](/product/access-control) # Customer portal (/product/customer-portal) What customers see in the customer portal, how they accept, reject or request changes to quotes, pay invoices, follow orders and view assets, and which portal pages and token links work without signing in. ## Overview [#overview] The customer portal is where an organization's customers follow their business with it. It lives at `/{orgSlug}/portal` and shows the quotes, invoices, orders and assets of the customer that the signed-in contact belongs to. | Route | Purpose | | --------------------------------------------------------------------------- | ----------------------------------------- | | `/{orgSlug}/portal` | Portal dashboard. | | `/{orgSlug}/portal/quotes` and `/{orgSlug}/portal/quotes/{quoteId}` | Quotes and quote detail. | | `/{orgSlug}/portal/invoices` and `/{orgSlug}/portal/invoices/{invoiceId}` | Invoices and invoice detail with payment. | | `/{orgSlug}/portal/orders` and `/{orgSlug}/portal/orders/{orderId}` | Orders and order progress. | | `/{orgSlug}/portal/assets` and `/{orgSlug}/portal/assets/{customerAssetId}` | Installed assets. | | `/{orgSlug}/portal/products/{productId}` | Public product page. | | `/{orgSlug}/portal/careers` and `/{orgSlug}/portal/careers/{jobId}` | Public careers page with open positions. | The portal sidebar has a My group with Dashboard and Assets, and a Finance group with Quotes, Invoices and Orders. The product and careers pages are reached through shared links. ## Key concepts [#key-concepts] ### Who can open the portal [#who-can-open-the-portal] * Portal-only users are customer contacts whose contact record is linked to a CentraKit account. When they open `/{orgSlug}`, CentraKit sends them to the portal. The organizations overview shows their role as Customer. * Users of the organization can switch to the portal with Navigation mode, Customer in the user menu. Their lists stay empty unless they are also a customer contact. Not yet available: inviting a customer contact to the portal from the app. ### What customers see [#what-customers-see] Customers only see records of the customers they are a contact for: * Quotes and invoices that have been sent. Drafts stay internal. * Orders, with their deliveries, invoices and downloads. * Assets, except archived ones. Internal notes, purchase prices, CRM notes and files stay internal. The portal uses the organization's logo, colors and social links from [settings](/product/settings-and-branding). ### Public links [#public-links] Some portal pages work without signing in, so the organization can share them by email or link: | Link | What it shows | | ---------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | | `/{orgSlug}/portal/quotes/{quoteId}` | A sent quote, with its PDF and terms. The customer can respond without an account. | | `/{orgSlug}/portal/invoices/{invoiceId}` | A sent invoice, with its PDF and payment. | | `/{orgSlug}/portal/products/{productId}` | A product page. | | `/{orgSlug}/portal/careers` | Open positions and the application form. | | `/portal/chats/{token}` | A read-only shared chat. | | `/portal/onboarding/{token}` | A pre-boarding form where a new hire fills in private details and uploads requested documents. See [People](/product/people). | | `/portal/calendars/{token}` | An iCalendar feed to subscribe to. See [Agenda](/product/agenda). | | `/portal/files/{token}` | A shared file download. See [Files](/product/files). | Token links stop working when they expire or are revoked. ## Use the portal [#use-the-portal] ### Dashboard [#dashboard] The dashboard shows metric cards for open quotes awaiting review, unpaid invoices with the overdue count, the outstanding balance and installed assets with warranties ending soon. Needs your attention lists quotes to review and invoices to pay. Quick links, Recent assets and Recent activity complete the page. When nothing is open, it reads "You're all caught up". ### Respond to a quote [#respond-to-a-quote] Open a quote to read it and download the PDF. The Quote response section has three choices: * Accept: enter your name and email, choose any optional lines, and confirm. When the quote requires a deposit, the button reads Accept and pay deposit, and a deposit invoice is created for you to pay. * Reject: enter your name, email and a reason. * Request changes: describe what should change. The quote moves to Changes requested, its reminders stop, and the team is notified. You receive a revised quote. If the organization publishes a newer version while you are reading, the page tells you that this version is no longer valid. The team sees when you viewed the quote. See [Quotes](/product/quotes). ### Pay an invoice [#pay-an-invoice] Open an invoice from Invoices or the dashboard. When the invoice can be paid online, the page shows a card and payment form powered by Stripe. The invoice list can be filtered by Sent, Paid, Overdue and Cancelled. See [Invoices](/product/invoices). ### Follow an order [#follow-an-order] An order shows its progress (Ordered, Ready for pickup or Shipped, Completed), the items and totals, deliveries with Track shipment links, its invoices with amounts to pay and PDF downloads, and any downloads and license keys. See [Orders](/product/orders). ### View assets [#view-assets] The Assets page lists the equipment installed at your customer. An asset shows its serial number, asset tag, condition, installed date, warranty details, service interval, location and description. ### Apply for a job [#apply-for-a-job] The careers page lists open positions. Open one and fill in your name, email, phone, LinkedIn profile, CV (PDF or image, up to 10 MB) and motivation, agree to the processing of your details, and select Send application. You can also agree to have your details kept for one year. See [Hiring](/product/hiring). ## Permissions [#permissions] Portal access does not use roles. A contact sees the portal of each organization where they are linked to a customer, and only that customer's records. Staff permissions decide what an organization publishes: for example `organization.quotes.send` to send a quote and `organization.invoices.send` to send an invoice. ## Automation and AI [#automation-and-ai] Customer responses start workflows. Accepting, rejecting or requesting changes to a quote raises the matching quote events, cancels the quote's pending reminder runs and notifies the team. See [Workflows](/product/workflows) and [Notifications](/product/notifications). ## API [#api] `POST /api/v1/public/portal/{orgSlug}/quotes/{quoteId}/request-changes` records a change request without signing in. The quote must be sent and still open. See [API](/developers/api) and [OpenAPI](/developers/openapi). To try the portal locally, sign in with the seeded portal-only logins `test3@example.com` (a private customer of two organizations), `test4@example.com` (a business contact) or `test5@example.com` (a stable boarder). See [Local development](/getting-started/local-development). ## Related pages [#related-pages] * [Customers](/product/customers) * [Quotes](/product/quotes) * [Invoices](/product/invoices) * [Orders](/product/orders) * [Hiring](/product/hiring) # Customers (/product/customers) How to manage customers, their contacts, locations, installed assets, notes and files, and how KvK and VIES lookups and @ and ## Overview [#overview] Customers are the businesses and people an organization works for. A customer record holds business details, contacts, locations, the assets installed at the customer, internal notes and files. Quotes, invoices, orders, tasks and conversations all link back to it. Customers live under Workspace in the sidebar. | Route | Purpose | | ------------------------------------------------------------ | -------------------------------------------------------------------------------------- | | `/{orgSlug}/customers` | Customers list, with the Overview, All contacts and All assets tabs. | | `/{orgSlug}/customers/create` | Create a customer. | | `/{orgSlug}/customers/{customerId}` | Customer detail, with the Overview, Contacts, Notes, Locations, Assets and Files tabs. | | `/{orgSlug}/customers/{customerId}/contacts/{contactId}` | Contact detail. | | `/{orgSlug}/customers/{customerId}/locations/{locationId}` | Location detail. | | `/{orgSlug}/customers/{customerId}/assets/{customerAssetId}` | Asset detail, with the Overview and Tasks tabs. | ## Key concepts [#key-concepts] ### Customer [#customer] A customer is either a Business or an Individual. Its status is Prospect, Active or Archived. | Section | Fields | | --------------- | -------------------------------------------------------------------------------- | | Overview | Status, description, tags and the users it is assigned to. | | Primary contact | First name, last name, job title, email and phone. | | Business | Company name, billing name, KVK, VAT or tax ID, billing email, website and logo. | | Address | Location name, address lines, city, postal code, state or province and country. | | Properties | Created and updated details, and the archive date and reason. | A customer needs at least a primary contact name or email. Invoices are sent to the billing email, or to the contact email for individuals, so the form warns when it is missing. ### Contacts [#contacts] A contact is a person at the customer, with a photo, name, job title, email and phone. One contact is the primary contact. The same person can be a contact for several customers. A contact can also sign in to the [customer portal](/product/customer-portal) when their contact record is linked to a CentraKit account. ### Locations [#locations] A location is an address of the customer, such as a head office or a site. One location is primary. Assets and work can be tied to a location. ### Customer assets [#customer-assets] A customer asset is one installed unit of a [product](/product/products) at the customer, such as a tractor or a bike. It records: * Serial number, asset tag and barcode. * Location, installed date, purchase date, purchase price and currency. * Warranty end date, provider and reference, and a service interval in days. * Condition: new, good, fair, poor or unknown. * Status: active, inactive, maintenance, retired or archived. * Tags, custom attributes, files and notes, and the quote or invoice it came from. The Tasks tab on an asset lists the work done on it. Customers see their active assets in the portal. ### Notes [#notes] The Notes tab is a timeline of internal notes from your team. Notes support rich text, `@` mentions of users and teams, and `#` references to records such as tasks, customers, quotes and invoices. Mentioned users are notified. Customers never see notes. ### Files [#files] The Files tab shows the customer's folder in organization [Files](/product/files). Every customer gets its own folder in the customers drive. Files stay internal and are not shown in the portal. ## Work with customers [#work-with-customers] ### Create a customer [#create-a-customer] 1. Open Customers and select New customer. 2. Choose Business or Individual. 3. Fill in the primary contact, business details and address. 4. Select Save. ### Retrieve business details [#retrieve-business-details] In the Business or Address section of the customer form, select Retrieve information. CentraKit searches the KvK (Dutch Chamber of Commerce) register, reads the KvK profile, checks the VAT ID in VIES and searches the web, then combines what it found. KvK and VIES are treated as authoritative. Only empty fields are filled, and nothing is saved until you review the form and save it. The result lists its sources. If nothing new is found, the form tells you that filled fields were left as they are. When you leave the KVK or VAT field, CentraKit also checks the number and shows Verified or Not found. The check never blocks saving. ### Add contacts, locations and assets [#add-contacts-locations-and-assets] Use the Contacts, Locations and Assets tabs on the customer. Each tab has a create action, and the row actions let you make a contact or location primary. The All contacts and All assets tabs on the list page show these records across all customers. ### Archive or delete a customer [#archive-or-delete-a-customer] Open the customer and use the Danger zone. Archive customer asks for a reason and hides the customer from active lists. Unarchive brings it back. Delete customer removes it permanently. You can also archive, unarchive and delete from the list, one at a time or in bulk. ### Find customers [#find-customers] Search the list and filter by status, type, owner and tags. Unassigned shows customers without an owner. Select several rows to change status, owner or tags together. ## Permissions [#permissions] | Permission | Allows | | ----------------------------------------------------------- | ------------------------------------------------------ | | `organization.customers.view` | See customers, contacts, locations and notes. | | `organization.customers.create` | Create customers. | | `organization.customers.update` | Edit customers, contacts and locations, and add notes. | | `organization.customers.delete` | Archive and delete customers. | | `organization.assets.view`, `.create`, `.update`, `.delete` | Work with customer assets. | Notes can be edited by their author and deleted by anyone with update rights. The Files tab also needs permission to view organization Files. ## Automation and AI [#automation-and-ai] * Workflows can start when a customer is created or when a lookup is requested. Steps include Send customer welcome email, Search KvK, KvK profile, VIES check and web search. See [Workflows](/product/workflows). * The assistant can list, read, summarize, create and update customers, contacts, locations, notes and assets. Changes ask for approval based on their risk. See [Chat](/product/chat). * MCP clients can list, read, create and update customers and list and read customer assets. See [MCP](/developers/mcp). ## API [#api] All routes live under `/api/v1/organizations/{organizationId}/`: | Resource | Routes | | --------------- | ------------------------------------------------------------------------------------------------------ | | Customers | `customers`, `customers/{customerId}` | | Contacts | `customers/{customerId}/contacts`, `.../contacts/{contactId}`, `.../contacts/{contactId}/primary` | | Locations | `customers/{customerId}/locations`, `.../locations/{locationId}`, `.../locations/{locationId}/primary` | | Notes | `customers/{customerId}/notes`, `.../notes/{noteId}` | | Customer assets | `customer-assets`, `customer-assets/{customerAssetId}`, `.../notes`, `.../notes/{noteId}` | | Mentions | `GET collaboration/references` searches records for `#` references. | See [API](/developers/api) and [OpenAPI](/developers/openapi). ## Related pages [#related-pages] * [Customer portal](/product/customer-portal) * [Quotes](/product/quotes) * [Invoices](/product/invoices) * [Products](/product/products) * [Tasks](/product/tasks) * [Files](/product/files) # Dashboard (/product/dashboard) What the organization dashboard shows, including metric cards, the operations pulse, capacity allocation, recent activity and the people reminders for absences, celebrations and HR deadlines. ## Overview [#overview] The dashboard is the home page of an organization. It lives at `/{orgSlug}` and is the first item in the sidebar. It gives staff one view of open work, conversations, sales, billing and the latest activity that involves them. Users who reach the organization only as a customer contact are redirected to the [customer portal](/product/customer-portal) at `/{orgSlug}/portal` instead. If the dashboard data cannot be loaded, the page shows "Dashboard is unavailable" with a retry action. Other pages keep working. ## Key concepts [#key-concepts] ### Metric cards [#metric-cards] The top row shows four metric cards. Each card links to the hub where you act on the numbers. | Card | Metric | Secondary line | Opens | | ------- | --------------------------------------------------------- | --------------------------- | ------------------------- | | Work | Open tasks that need attention | Tasks due within three days | `/{orgSlug}/tasks/all` | | Inbox | Active external conversation threads with unread messages | Threads updated today | `/{orgSlug}/inbox` | | Sales | Open quote value of draft and sent quotes | Number of active quotes | `/{orgSlug}/quotes/all` | | Billing | Open invoices | Number of overdue invoices | `/{orgSlug}/invoices/all` | The numbers are computed from a sample of up to 20 recent records per area, so they describe current activity rather than full historical totals. Use [Insights](/product/insights) for complete reporting. ### Operations pulse [#operations-pulse] The Operations pulse card summarizes how work is flowing: * Tasks done: completed tasks in the sample. * Quote win rate: accepted quotes divided by accepted, rejected and expired quotes. * Billing risk: the total of overdue invoices. * Pipeline progress: the share of top-level tasks that are done and invoices that are paid. * Recent signals: closed task work, accepted quotes and invoices that need follow-up. ### Capacity allocation [#capacity-allocation] The Capacity allocation card has Today, Week and Month tabs. It shows the exposure for the selected period and a flow chart that splits attention between Tasks, Messages, Quotes, Invoices and Customers. Select a segment to open that hub. ### Recent activity [#recent-activity] Recent activity is a timeline of the latest ten items from your [notifications](/product/notifications), such as mentions, assignments, inbound messages, invitations, running AI work, approvals that need a decision and failures. Items that need a reply or a review carry a Reply or Review badge. When nothing has happened yet, the card shows "No recent activity". ### People reminders [#people-reminders] When the organization uses [People](/product/people), the dashboard adds side cards that only appear when they have content: * Who's out: approved time off in the next seven days, with a link to the team calendar. * Celebrations: birthdays and work anniversaries in the next two weeks. Employees choose whether theirs are shown on their [profile](/product/profile). * HR reminders: documents and certificates that are about to expire and contracts that are ending. ## Use the dashboard [#use-the-dashboard] ### Go to the work behind a number [#go-to-the-work-behind-a-number] Select a metric card, a capacity segment or an activity item to open the matching list or record. The dashboard does not edit records itself. ### Ask the assistant about the dashboard [#ask-the-assistant-about-the-dashboard] The dashboard shares its metrics, the operations summary and recent activity with the side-panel assistant as page context. Open the assistant and ask, for example, which overdue invoices need follow-up. See [Chat](/product/chat). ## Permissions [#permissions] Any user of the organization can open the dashboard. Each section only counts records the user is allowed to see, so a user without `organization.quotes.view` sees no quote figures. | Section | Permission | | ----------------------------- | ---------------------------- | | Who's out | `organization.time-off.view` | | Celebrations and HR reminders | `organization.people.view` | ## Related pages [#related-pages] * [Notifications](/product/notifications) * [Command center](/product/command-center) * [Tasks](/product/tasks) * [Inbox](/product/inbox) * [Quotes](/product/quotes) * [Invoices](/product/invoices) * [Insights](/product/insights) # Expenses (/product/expenses) How to create expense claims from receipts, mileage and per diem, group them in trips, check them against policies, approve and reimburse them, and rebill billable expenses to customers. ## Overview [#overview] Expenses handles the costs employees make for work. An employee snaps or forwards a receipt, the details fill in automatically, and the claim goes to their manager for approval. Finance then marks approved claims as reimbursed, and billable expenses become invoice suggestions for the customer. Expenses lives under Workspace, Finance, Expenses in the sidebar. | Route | Purpose | | --------------------------------- | ---------------------------------------------------------------------------- | | `/{orgSlug}/expenses` | My claims tab with your own claims and your receipt forwarding address. | | `/{orgSlug}/expenses/approvals` | To approve tab: claims waiting for your decision. | | `/{orgSlug}/expenses/all` | All tab: every claim you can see. | | `/{orgSlug}/expenses/reimburse` | To reimburse tab: approved out-of-pocket claims that still need paying back. | | `/{orgSlug}/expenses/trips` | Trips tab: claims grouped per business trip. | | `/{orgSlug}/expenses/new` | Create a claim. | | `/{orgSlug}/expenses/{expenseId}` | One claim, with its receipt, flags and approval. | You need an employee profile in [People](/product/people) to create claims. ## Key concepts [#key-concepts] ### Claims [#claims] A claim has a date, merchant, category, total including VAT, currency, VAT amount and rate, business purpose, an optional trip and a receipt. Claims in another currency record the exchange rate to the organization currency. | Type | Meaning | | ------------ | -------------------------------------------------------------- | | Paid myself | The employee paid and is paid back. | | Company card | Paid with a company card, so nothing is reimbursed. | | Mileage | Distance in km times the mileage rate per km from HR settings. | | Per diem | Days times the per diem rate per day from HR settings. | A claim is Draft, Awaiting approval, Approved, Rejected or Reimbursed. ### Categories [#categories] Expense categories, such as Travel, Meals or Software, define what people can claim for. Each has a default VAT rate and an optional ledger code that travels with exports. Receipt reading picks the closest category. ### Policies and flags [#policies-and-flags] An expense policy sets a limit per expense, per day or per trip for one category or all categories, and a receipt requirement above an amount. A policy either warns or blocks submission, and can be paused. When a claim breaks a rule it gets a flag that the approver sees with the reason: | Flag | Meaning | | --------------------- | ------------------------------------------------------------------------------------------- | | Over the policy limit | The amount is above a policy limit. | | Receipt missing | A receipt is required but not attached. | | Possible duplicate | A claim with the same details already exists. Duplicates are checked even without policies. | | Overlaps a per diem | The claim falls on days already covered by a per diem. | ### Trips [#trips] A trip groups the claims of one business trip, with a name, destination and dates, so you see what a visit cost in one place. Deleting a trip keeps its claims. ## Work with claims [#work-with-claims] ### Create a claim from a receipt [#create-a-claim-from-a-receipt] 1. Select **New claim**. 2. Drop a receipt (image or PDF, up to 9.5 MB) or select **Take a picture**. The merchant, date, amounts, VAT and category fill in from the receipt. Check them before saving. 3. Add the business purpose and, if it applies, a trip. 4. Select **Save draft** to finish later, or **Submit** to send it for approval. If the receipt cannot be read, fill in the details yourself. ### Forward receipts by email [#forward-receipts-by-email] The My claims tab shows your personal receipt forwarding address when receipts by email is on in HR settings. Forward receipts from your own email address, and each attachment becomes a draft claim with the details filled in. Creating a new address stops the old one from working. ### Claim mileage or per diem [#claim-mileage-or-per-diem] Choose **Mileage** as the type, enter the From and To addresses and select **Calculate distance**. Turn on **Round trip** to count both ways. When the route cannot be calculated, enter the one-way distance in km yourself. For **Per diem**, enter the number of days. The amount follows the rates in HR settings. ### Bill an expense to a customer [#bill-an-expense-to-a-customer] In the Customer billing section, choose the customer and optionally the task, and turn on **Bill this expense to the customer**. Once approved, the expense shows up as an invoice suggestion. See [Invoices](/product/invoices). ### Withdraw or delete a claim [#withdraw-or-delete-a-claim] A submitted claim can be withdrawn, which moves it back to draft. Deleting a claim removes it and its receipt permanently. ## Approve and reimburse [#approve-and-reimburse] ### Decide on claims [#decide-on-claims] Claims go to the employee's manager, or to people who can approve expenses. The To approve tab lists claims waiting for you. Select several to approve or reject them at once, or open a claim to review its flags and add a note to the employee. Claims below the automatic approval amount in HR settings are approved as soon as they are submitted. ### Mark claims reimbursed [#mark-claims-reimbursed] The To reimburse tab lists approved out-of-pocket claims. After the money has been paid back, select the claims and mark them reimbursed, with an optional payment reference. ## Set up expenses [#set-up-expenses] Expense categories, expense policies, the mileage and per diem rates, the automatic approval amount and receipts by email are set in [HR settings](/product/people) at `/{orgSlug}/people/settings`. ## Permissions [#permissions] | Permission | Allows | | ---------------------------------------------------- | ------------------------------------------------------------- | | `organization.expenses.view` | See all claims in the organization. | | `organization.expenses.create`, `.update`, `.delete` | Create, edit and delete claims and trips for other employees. | | `organization.expenses.approve` | Decide claims for employees without a manager. | | `organization.expenses.reimburse` | See the To reimburse tab and mark claims reimbursed. | | `organization.expenses.manage-policies` | Manage expense categories and policies. | Every employee can create, edit and delete their own claims and trips, and managers see their reports' claims. ## Automation and AI [#automation-and-ai] * Receipt reading uses AI to fill in the claim from a photo, PDF or forwarded email. * Mileage uses route distance between two addresses. * Workforce insights flag expense outliers. See [People](/product/people). * The assistant lists and decides expense claims waiting for your approval together with leave and timesheets. See [Time](/product/time) and [Chat](/product/chat). ## API [#api] All routes live under `/api/v1/organizations/{organizationId}/`: | Resource | Routes | | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | Expense claims | `GET expenses` lists claims for a scope (your own, all, to approve or to reimburse), `POST expenses` creates a draft claim, mileage entry or per diem. | | HR approvals | `GET` and `POST approvals` | The API accepts JSON only, so upload receipts in the app. See [API](/developers/api) and [OpenAPI](/developers/openapi). ## Related pages [#related-pages] * [People](/product/people) * [Time](/product/time) * [Invoices](/product/invoices) * [Approvals](/product/approvals) # Files (/product/files) How to store, share and download organization files in drives and folders, with uploads, versions, comments, share links, ZIP downloads and the Files API. ## Overview [#overview] Files is the organization's document store. Files and folders live in drives, each drive has its own access list, and every file keeps immutable versions, comments and an activity history. Files lives under Workspace, Resources in the sidebar. The routes are: | Route | Purpose | | ----------------------------------------- | -------------------------------------------------------------------------------------- | | `/{orgSlug}/files` | The file browser. The query string selects the drive, scope, folder, view and filters. | | `/{orgSlug}/files/drives/{driveId}` | Drive settings. | | `/{orgSlug}/customers/{customerId}/files` | A customer's folder, embedded in the customer detail page. | | `/portal/files/{token}` | Public share link for one file. | ## Key concepts [#key-concepts] ### Drives [#drives] The left rail groups drives under File locations: | Drive | Rail label | Purpose | | --------- | ------------- | --------------------------------------------------------------------------------------------------------- | | Company | Organization | The organization-wide drive, created with the organization. | | Customers | Customers | One folder per customer, created automatically. Files go inside a customer folder, not at the drive root. | | Personal | My | Each user's own drive. | | Shared | Shared drives | Drives that users create for a team or project with New shared drive. | Items can be moved within a drive but not between drives. Copy items to put them in another drive. A drive is Active, Archived or Trashed. Archiving hides a drive and its files until it is restored. When a user is removed, their personal drive is either transferred to another user or marked as having no owner. ### Access roles [#access-roles] Access is granted to users and teams on a drive or on an item, and inherits to everything inside a folder. | Role | Can | | --------- | -------------------------------------------------------------- | | Viewer | Open, preview and download. | | Commenter | Everything a Viewer can, plus comment. | | Editor | Everything a Commenter can, plus change the files and folders. | These roles apply on top of the organization permissions listed below. ### Shortcuts and filters [#shortcuts-and-filters] File shortcuts in the rail are Shared with me, Recent, Starred and Bin. The browser has list and grid views, sorting, search, and filters for files, folders and owner. The rail also shows storage used against the plan limit, split into Videos, Audio, Photos, Documents, Other and Bin. The total counts every drive, including the bin and older versions. ### Versions, comments and the bin [#versions-comments-and-the-bin] Selecting a file opens the inspector with three tabs: * Info shows type, size, owner, location and dates. * Comments holds the discussion on the file. * Versions lists every uploaded version. Upload version adds a new one, and you can download or restore an older one. Versions are immutable. Move to bin removes items without deleting them. From Bin you can restore items, delete them permanently, or use Restore all and Delete all. Items in the bin and older versions are kept for a plan-based period: 7 days on Starter, 30 on Pro, 90 on Business and 365 on Enterprise, unless an Enterprise override is set. ## Work with Files [#work-with-files] ### Upload files [#upload-files] Drop files onto the browser, or select Upload files. Uploads go straight to storage in resumable chunks and show progress in the Uploads panel. Each upload first reserves storage quota, and finalizing checks the byte size and file signature before the file appears. Uploads that are never finished expire after 15 minutes and are cleaned up by a background job. ### Organize items [#organize-items] Create folders with New folder, then rename, move, duplicate or star items from the row menu. Select several items to move them to the bin, restore, delete permanently or star them in one step. ### Share a file [#share-a-file] Select Share on an item to grant people and teams Viewer, Commenter or Editor access. For a file you can also create a share link under Share links. Anyone with the link can download the file from `/portal/files/{token}`, which redirects to a short-lived signed URL. Revoke link turns the link off. Not yet available: share links for folders. Share folders with people or teams instead. ### Download as ZIP [#download-as-zip] Select several items and choose Download ZIP. Selections of up to 100 items and 25 MB stream immediately. Larger selections are queued, built in the background, and offered as a download that expires after one hour. ### Manage a drive [#manage-a-drive] Open a drive's menu and choose Drive settings. The settings page has these sections: * General: rename the drive. * Access: grant users and teams a role and set the default role for new grants. * Owner: transfer the drive to another organization user. * Lifecycle: archive or restore the drive, or permanently delete it with its files. ## Permissions [#permissions] | Key | Grants | | --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | | `organization.files.view` | Browse drives, preview and download files, star items, comment, and see storage and activity. | | `organization.files.create` | Create shared drives and folders, upload files, copy items and request ZIP downloads. | | `organization.files.update` | Rename, move and restore items, restore versions, change sharing and share links, and rename, archive, restore or transfer drives. | | `organization.files.delete` | Delete items permanently and delete drives. | Drive and item roles still decide what each person can do inside a drive. See [Access control](/product/access-control). ## Automation and AI [#automation-and-ai] MCP tools for files are read-only: | Tool | Kind | | ---------------------- | ---- | | `listFileDrives` | Read | | `listFileNodeChildren` | Read | | `getFileNode` | Read | | `createFileDownload` | Read | `createFileDownload` returns a short-lived signed URL without exposing the storage path. See [MCP](/developers/mcp). ## API [#api] Organization paths are relative to `/api/v1/organizations/{organizationId}`. | Resource | Operations | | ---------------------------------------- | ----------------------------------------------------------------------------------------- | | `files/drives` | `listFileDrives` (GET) | | `files/nodes/{nodeId}` | `getFileNode` (GET) | | `files/nodes/{nodeId}/children` | `listFileNodeChildren` (GET) | | `files/nodes/{nodeId}/download` | `createFileDownload` (GET) | | `files/uploads` | `createFileUpload` (POST) reserves quota and returns a signed resumable upload transport. | | `files/uploads/{reservationId}/finalize` | `finalizeFileUpload` (POST) | | `files/uploads/{reservationId}/cancel` | `cancelFileUpload` (POST) | | `files/copy` | `copyFileNodes` (POST) copies folder trees recursively. | | `files/zip-exports` | `createFileZipExport` (POST) | | `files/zip-exports/{exportId}` | `getFileZipExport` (GET) | `GET /api/v1/public/files/{token}` (`downloadPublicSharedFile`) serves share links. Two cron jobs support Files: `internal/files/upload-reconciliation` expires stale uploads and removes their objects every 15 minutes, and `internal/files/zip-exports` builds queued ZIP downloads every minute. See [API](/developers/api) and [OpenAPI](/developers/openapi). ## Related pages [#related-pages] * [Customers](/product/customers) for customer folders. * [Billing and usage](/product/billing-and-usage) for storage limits. * [Audit log](/product/audit-log) for file activity history. # Hiring (/product/hiring) How to open jobs, publish them on the careers page, move candidates through stages on a board, score and interview them, analyze CVs, send candidate emails and hire a candidate into People. ## Overview [#overview] Hiring tracks vacancies and candidates from the first application to the first working day. Each job has its own stages and candidate board, and hiring a candidate creates an employee in [People](/product/people) and starts onboarding. Hiring lives under Workspace, People, Hiring in the sidebar. | Route | Purpose | | ------------------------------------------------ | --------------------------------------------------------- | | `/{orgSlug}/hiring` | Jobs list, with the Jobs and Email templates tabs. | | `/{orgSlug}/hiring/email-templates` | Candidate email templates. | | `/{orgSlug}/hiring/{jobId}` | Job detail with the Candidates, Stages and Insights tabs. | | `/{orgSlug}/hiring/applications/{applicationId}` | One candidate's application. | | `/{orgSlug}/portal/careers` | Public careers page with all published jobs. | | `/{orgSlug}/portal/careers/{jobId}` | Public job page with the application form. | ## Key concepts [#key-concepts] ### Jobs [#jobs] A job is a vacancy with a title, location, employment type, contract, hours per week, monthly salary range, description and hiring manager. Its status is Draft, Open or Closed. An open job can be published on the careers page. Closing a job keeps its applications, while deleting a job removes the job and all its applications. ### Stages [#stages] Stages are the columns of a job's candidate board, such as Applied, Screening, Interview, Offer and Hired. Each stage can have scorecard criteria, and a required criterion must be scored before a candidate moves past that stage. A stage can also send an email to the candidate. When you remove a stage that still holds candidates, you choose where they go. ### Applications [#applications] An application links a candidate to a job. It records the name, email, phone, LinkedIn profile, CV, motivation and source (careers page, added manually, referral or agency). Its status is In progress, Rejected, Hired or Withdrawn. A candidate who stays in one stage longer than the threshold in HR settings is marked as stalled. ### Candidate data retention [#candidate-data-retention] Candidates agree to the processing of their details when they apply. Their data is deleted after the application closes, unless they opt in to being kept for one year for future positions. The application shows "Data kept until" once the application closes. A daily job anonymizes candidates whose retention period has ended. The retention period for rejected candidates is set in [HR settings](/product/people). ## Work with jobs [#work-with-jobs] ### Open a job [#open-a-job] 1. Open Hiring and select **New job**. 2. Fill in the title, status, location, employment type, contract, hours per week, hiring manager, monthly salary range and description. 3. Turn on **Show on the public careers page** to publish an open job, and select **Create**. 4. Open the job's **Stages** tab to adjust stages, criteria and stage emails, then select **Save**. The job summary on that tab shows the public job link and an **Edit job** button. ### Add a candidate [#add-a-candidate] Candidates who apply on the careers page appear in the first stage automatically. To add one yourself, open the job and select **Add candidate**. A PDF or image CV is summarized automatically. ### Move candidates [#move-candidates] Drag a candidate card to another column on the Candidates tab. The board also supports the keyboard: press space to pick up a card, the arrow keys to move it and space again to drop it. Cards show the source, average rating, days in the stage and skills from the CV. ### Score a candidate [#score-a-candidate] Open the application and use **Scorecards**. Each interviewer gives an overall rating and a rating per criterion on a scale of Strong no, No, Yes and Strong yes, with optional notes. The application shows the average rating and everyone's scores. ### Schedule an interview [#schedule-an-interview] Select **Schedule interview** on the application, choose the interviewers and a date, and pick a slot. CentraKit suggests shared free time in the next five days from the interviewers' agendas. The interview is added to the agenda of every interviewer. See [Agenda](/product/agenda). ### Analyze a CV [#analyze-a-cv] Select **Analyze CV** to let AI summarize the uploaded CV with years of experience and skills. The summary is labeled as AI generated and should be checked against the original. Only PDF and image files can be read. ### Reject or hire [#reject-or-hire] * **Reject** closes the application and sends the rejection email to the candidate. Open the candidate first to add a reason or skip the email. * **Hire** creates an employee profile, starts the default onboarding checklist and keeps the CV with the employee. ## Candidate emails [#candidate-emails] The Email templates tab holds the emails sent automatically to candidates: | Template | Sent when | | -------------------- | ---------------------------------------------------------------------------------- | | Application received | A candidate applies. | | Rejection | An application is rejected. | | Stage email | A candidate enters a stage that has an email. Set these per job on the Stages tab. | Templates support the `{{firstName}}`, `{{jobTitle}}` and `{{organizationName}}` placeholders. Candidates without an email address receive no email. ## Careers page [#careers-page] The careers page at `/{orgSlug}/portal/careers` lists every published job with its location, type and hours. Visitors open a job, read the description and apply with their name, email, phone, LinkedIn profile, CV (PDF or image, up to 10 MB) and motivation. The form asks for consent before it can be sent and limits repeated applications from one connection. ## Insights [#insights] The Insights tab on a job shows the number of applications, candidates in progress, candidates waiting too long and hires. A funnel lists how many candidates reached each stage, the conversion between stages and the average days spent in each stage. A sources breakdown shows where candidates came from. ## Permissions [#permissions] | Permission | Allows | | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | | `organization.hiring.view` | See jobs and applications, download CVs and fill in your own scorecards. | | `organization.hiring.create` | Create jobs and add candidates. | | `organization.hiring.update` | Edit jobs and stages, move, reject and hire candidates, analyze CVs, schedule interviews and edit email templates. | | `organization.hiring.delete` | Delete jobs with their applications. | | `organization.hiring.manage-careers-page` | Publish or unpublish jobs on the careers page. | The careers page is public and needs no sign-in. ## Automation and AI [#automation-and-ai] * AI CV analysis summarizes experience and skills on the application. * Interview slot suggestions use the interviewers' agendas. * A daily job anonymizes candidates after their retention period. ## API [#api] All routes live under `/api/v1/organizations/{organizationId}/`: | Resource | Routes | | ------------ | -------------------------------------- | | Jobs | `GET hiring/jobs` | | Applications | `GET hiring/jobs/{jobId}/applications` | See [API](/developers/api) and [OpenAPI](/developers/openapi). ## Related pages [#related-pages] * [People](/product/people) * [Agenda](/product/agenda) * [Customer portal](/product/customer-portal) * [Access control](/product/access-control) # Inbox (/product/inbox) How the Inbox shows customer conversations from WhatsApp and web channels, how staff reply with attachments and AI suggestions, take over from the AI, approve AI order proposals and track delivery. ## Overview [#overview] Inbox is where an organization talks with its customers over external channels. Every inbound message lands in a conversation thread, the AI can answer or draft replies, and staff can take over, reply, attach files and turn a conversation into a customer, task, appointment or order. Inbox sits at the top of the organization sidebar. | Route | Purpose | | ----------------------------- | ------------------------------------------------------------ | | `/{orgSlug}/inbox` | Conversations list with the selected conversation beside it. | | `/{orgSlug}/inbox/{threadId}` | One conversation. | Channel connections are not configured in Inbox. They are set up under [Integrations](/product/integrations), and **Manage integrations** in the Inbox opens that page. ## Key concepts [#key-concepts] ### Channels and connections [#channels-and-connections] A connection links one external account to the organization. WhatsApp Business Cloud is the channel you can connect today, and web threads also appear in the Inbox. A connection is Draft, Active, Disabled or Error, and has a health status. A WhatsApp connection holds: | Setting | Meaning | | ----------------------------------------- | ----------------------------------------------------------------------------------- | | Name and phone number ID | The WhatsApp Business number to use. | | Connection scope | The organization's company WhatsApp, or your own company phone number. | | Access token, app secret and verify token | Credentials from the Meta app. Leave them blank on edit to keep the current values. | | Auto-reply mode | Off, Draft or Auto, described under AI replies below. | | Reply model | The AI model for replies, or the organization chat default. | | Escalation keywords | Words that make the AI ask a person to take over. | | Fallback message | The reply sent when the AI cannot answer. | | Webhook URL | The callback URL to enter in the Meta app dashboard together with the verify token. | ### Threads [#threads] A thread is one conversation with a customer or contact on one connection. Threads link to the matching customer or contact when one is known. The list groups threads into Pinned and recent periods and can be searched. Each thread is read or unread, can be pinned or archived, and shows the channel it came from. A thread also has a handling status: the AI is handling it, a takeover is requested, a person is handling it, or it is resolved. ### Messages, attachments and delivery [#messages-attachments-and-delivery] Messages show who sent them and when. Staff replies can include file attachments, and photos and other media in the thread are collected in a media strip with a full-screen viewer. Every outbound message has a delivery record that moves through queued, sent, delivered, failed, dead-lettered or canceled, with the history of each attempt. ## Work with conversations [#work-with-conversations] ### Reply to a customer [#reply-to-a-customer] 1. Open a conversation. 2. Type a reply, pick one of the suggested replies above the composer, or use voice input. 3. Add attachments with **Add attachment** and select send. If delivery fails, the message shows **Delivery failed** with its attempts, and you can retry it. ### Start a conversation [#start-a-conversation] Select **New chat**, choose a customer or contact and a WhatsApp connection, and write the first message. The recipient needs a WhatsApp or phone number. Outbound chats work with WhatsApp connections only. ### Take over from the AI [#take-over-from-the-ai] When the AI requests a takeover, or whenever you want to answer yourself, select **Take over**. The AI stops replying in that thread until you select **Resume AI**. ### Organize threads [#organize-threads] Use the conversation actions to mark a thread as read or unread, pin or unpin it, archive it or delete it. Deleting a thread permanently removes the conversation and its messages. ## AI replies [#ai-replies] The auto-reply mode of the connection decides what the AI does with new messages: | Mode | Behavior | | ----- | ----------------------------------------------------------------------------------- | | Off | The AI does not reply. Staff answer every message. | | Draft | The AI writes an AI reply draft. Staff review and edit it, then send or discard it. | | Auto | The AI replies directly. | In Draft and Auto mode, the AI requests a takeover instead of replying when a message contains an escalation keyword, when a message is longer than the automatic reply limit, or after repeated delivery failures on the connection. Threads that a person is handling, or where a takeover is requested, get no AI replies. The AI can look up products and variants while it answers, but it never creates records on its own. ### Approve an AI proposal [#approve-an-ai-proposal] When a customer asks for something, the AI records a **Proposed action** card in the thread. A proposal can include a new customer with contact details, a task, an appointment and an order with product lines and the delivery method. Select **Create** to create everything in the proposal, or **Dismiss** to discard it. The created records are linked back to the conversation. ### Contextual AI actions [#contextual-ai-actions] The thread header offers **Summarize thread**, **Draft reply** and **Suggest next steps**. These open the assistant with the thread as context, and drafts are filled into the composer for review. See [Chat](/product/chat). ## How inbound messages arrive [#how-inbound-messages-arrive] 1. The channel provider calls the connection's webhook URL. WhatsApp verifies the URL with a `GET` request first. 2. CentraKit stores each inbound webhook as a job and answers right away. 3. A worker processes due jobs every minute, adds the message to the right thread and runs the AI reply policy. ## Permissions [#permissions] | Permission | Allows | | ---------------------------------- | ---------------------------------------------------------------------------- | | `organization.chats.view` | See the Inbox and mark threads read or unread. | | `organization.chats.create` | Reply, send AI drafts, retry deliveries and start outbound chats. | | `organization.chats.update` | Pin, archive, take over, resume the AI, discard drafts and decide proposals. | | `organization.chats.delete` | Delete threads. | | `organization.integrations.manage` | Create and edit channel connections. | Creating records from a proposal also needs the matching create permission: `organization.customers.create`, plus `organization.tasks.create`, `organization.agenda.create` or `organization.orders.create` when the proposal includes a task, appointment or order. ## Automation and AI [#automation-and-ai] * The **Inbox message received** workflow trigger starts workflows for inbound messages. A system workflow waits 4 hours and notifies the assignees if the thread is still unanswered. It never replies automatically. See [Workflows](/product/workflows). * The assistant can list and read Inbox threads and mark them read or unread. * MCP clients can send a staff reply with the `sendInboxThreadReply` tool. See [MCP](/developers/mcp). ## API [#api] All routes live under `/api/v1/organizations/{organizationId}/`: | Resource | Routes | | ----------------- | ----------------------------------------------------------------------------------------------------- | | Threads | `GET inbox/threads` | | Messages | `GET inbox/threads/{threadId}/messages`, `POST inbox/threads/{threadId}/messages` sends a staff reply | | Reply suggestions | `POST inbox/threads/{threadId}/suggestions` | | Attachments | `POST inbox/attachments`, `GET inbox/attachments/{attachmentId}` | | Deliveries | `GET inbox/deliveries/{deliveryId}` | | Channel webhooks | `GET` and `POST channels/{connectionId}/webhook`, called by the channel provider | See [API](/developers/api) and [OpenAPI](/developers/openapi). ## Related pages [#related-pages] * [Integrations](/product/integrations) * [Chat](/product/chat) * [Customers](/product/customers) * [Orders](/product/orders) * [Notifications](/product/notifications) # Insights (/product/insights) How the Insights hub reports revenue, quotes, receivables, margin, products, customers and stock from internal CentraKit data, with deterministic forecasts, rule-based insights and CSV or Excel exports. ## Overview [#overview] Insights is the business analytics surface of an organization. It reads only the organization's own CentraKit data, such as invoices, payments, quotes, products, customers and stock, and shows key figures, trends, rankings, forecasts and rule-based insights for a date range. Insights lives under Workspace, Resources, Insights in the sidebar. | Route | Purpose | | ------------------------------- | ----------------------------------------------------- | | `/{orgSlug}/insights` | Opens the first tab you can use. | | `/{orgSlug}/insights/analytics` | Analytics, with the Overview and Data & exports tabs. | | `/{orgSlug}/insights/reporting` | Not yet available. | Workforce figures such as headcount and absence live on the Insights tab of [People](/product/people), and hiring funnels on each job in [Hiring](/product/hiring). Platform-wide analytics for system admins is a separate surface. See [Admin analytics](/admin/analytics). ## Key concepts [#key-concepts] ### Filters [#filters] Every view uses the same filters, kept in the URL so a view can be shared: | Filter | Options | | ---------- | -------------------------------------------------------------------------------- | | Date range | Last 30 days, Last 90 days, Last 365 days or a custom range of up to five years. | | Comparison | No comparison, Previous period or Previous year. | | Currency | All currencies or one currency. | ### Metrics [#metrics] Metrics cover paid, issued and total revenue, collections, receivables, overdue receivables, average collection days and overdue cash risk; quote win rate and weighted quote pipeline; gross margin, gross margin rate and margin trajectory; product revenue, gross margin, units and demand; customer revenue, customer concentration and repeat customer rate; and stock value, stock velocity, dead stock value and stock depletion. Two rules apply to every metric: * Monetary values keep currencies separate and never add unlike currencies into one total. * Gross margin is revenue less recorded direct costs. It is not net profit, because overhead, tax and payroll are outside the data model. Margin metrics show how much of the revenue has recorded costs, and an insight warns when that coverage is limited. ### Forecasts [#forecasts] Forecasts are deterministic projections with a named method (run rate, moving average, seasonal average or linear trend), a confidence band and the number of history points used. When there is not enough history, coverage is too low or data is stale, the chart says why no forecast is shown. ### Insights [#insights] Insights are generated from transparent rules, not from generative AI. Examples are elevated overdue cash risk, concentrated customer revenue, declining gross margin, limited gross margin coverage, dead stock and stock depletion risk, and material increases or declines in a metric. Each insight links to the evidence behind it. ## Use the Overview tab [#use-the-overview-tab] The Overview tab shows: * Key figures, such as paid revenue, product revenue, customer revenue and stock value, with the change against the comparison period. * A revenue trend with its forecast. * Composition rankings of the top customers and products. * Biggest movers, the figures that changed most against the comparison period. * The rule-based insights for the range. ## Use the Data & exports tab [#use-the-data--exports-tab] The Data & exports tab shows the records behind the overview as a searchable, filterable table. Choose a report: | Report | Contents | | ------------------ | ------------------------------------------------------------------------------------------------ | | Source evidence | One row per source record, such as an invoice or quote, with its date, metric, source and value. | | Rankings | The ranked customers and products. | | Forecasts | Forecast points with method, bounds and confidence, or the reason a forecast is unavailable. | | Metric definitions | What each metric measures. | ### Export a report [#export-a-report] 1. Open the Data & exports tab and set the filters and report. 2. Select **Export**. 3. Choose **CSV** for plain text that opens in any spreadsheet, or **Excel** for a workbook with one row per record. The file is named after the report and the end date of the range. ## Permissions [#permissions] | Permission | Allows | | ------------------------------- | ---------------------------- | | `organization.analytics.view` | Open Insights and Analytics. | | `organization.analytics.export` | Export analytics reports. | ## Automation and AI [#automation-and-ai] * **Explain visible report** and **Compare visible period** ask the assistant to narrate what is on screen. The narrative is labeled as not authoritative and never produces new metrics. See [Chat](/product/chat). * The assistant can read organization analytics for a date range with the same permission. ## Related pages [#related-pages] * [Dashboard](/product/dashboard) * [Invoices](/product/invoices) * [Quotes](/product/quotes) * [Products](/product/products) * [Customers](/product/customers) * [Admin analytics](/admin/analytics) # Integrations (/product/integrations) How the integrations marketplace connects WhatsApp Business inbox channels, Google Calendar and signed incoming and outgoing webhooks to an organization. ## Overview [#overview] Integrations connect the organization to outside services. The marketplace lists what is available, and each integration has a detail page where you set up its connections. | Route | Purpose | | ----------------------------------------------------------- | --------------------------------------------------------- | | `/{orgSlug}/integrations` | Marketplace of all integrations. | | `/{orgSlug}/integrations/{integrationSlug}` | Detail page with resources and the configuration section. | | `/{orgSlug}/integrations/whatsapp/create` | New WhatsApp connection. | | `/{orgSlug}/integrations/whatsapp/{connectionId}` | Edit a WhatsApp connection. | | `/{orgSlug}/integrations/webhooks/incoming/create` | New incoming webhook endpoint. | | `/{orgSlug}/integrations/webhooks/incoming/{endpointId}` | Edit an incoming endpoint. | | `/{orgSlug}/integrations/webhooks/outgoing/create` | New outgoing webhook destination. | | `/{orgSlug}/integrations/webhooks/outgoing/{destinationId}` | Edit an outgoing destination. | ## Key concepts [#key-concepts] ### The marketplace [#the-marketplace] The marketplace shows Featured, Recently Added, Native Integrations and All Integrations. You can search, sort by popularity and filter by category and availability. Each integration has one availability: | Availability | Meaning | | ------------ | ---------------------------------- | | Available | You can set it up now. | | Coming soon | Being built. | | Planned | On the roadmap. | | Deprecated | Still works, but will be removed. | | Unavailable | Cannot be used in this deployment. | These integrations are available today: | Integration | Slug | What it does | | ----------------- | ----------------- | -------------------------------------------------------------------------------- | | WhatsApp Business | `whatsapp` | Receives and sends WhatsApp messages as [Inbox](/product/inbox) conversations. | | Google Calendar | `google-calendar` | Imports Google calendars into the [Agenda](/product/agenda). | | Webhooks | `webhooks` | Receives JSON that starts workflows and posts signed events to your own systems. | Not yet available: Slack and Resend are coming soon. Email, Stripe, Exact Online, Microsoft Teams, Google Drive and OneDrive are planned. For an integration that is not available yet, select Request this integration to help prioritize it. The detail page also lists integration resources such as the website, documentation, support, terms and privacy policy, and whether it installs for the organization or per user. ### Connections [#connections] A connection is one configured instance of an integration, such as one WhatsApp phone number. Inbox connections show a health status: Healthy, Degraded, Unhealthy, Error or Unknown. ## Connect WhatsApp Business [#connect-whatsapp-business] 1. Open Integrations and choose WhatsApp Business, then New WhatsApp connection. 2. Enter a name, the connection scope (the organization company WhatsApp or your own company phone number), the phone number ID, access token, app secret and verify token from your Meta app. 3. Choose the auto-reply mode: Off, Draft (the assistant drafts replies for a person to send) or Auto. Optionally set the reply model, escalation keywords that hand a conversation to a person, and a fallback message. 4. Select Save connection, then copy the webhook URL. In the Meta app dashboard, configure it under WhatsApp webhooks together with your verify token. Incoming messages then create or continue conversations in the inbox. To change the access token or app secret later, enter a new value; leave the field blank to keep the current one. ## Connect Google Calendar [#connect-google-calendar] Google Calendar connects per user. On the Google Calendar detail page, connect your Google account and choose which calendars to import. Imported calendars are read-only in the agenda and sync in the background. See [Agenda](/product/agenda) for sync behavior and reconnecting. ## Webhooks [#webhooks] The Webhooks detail page has three sections: Incoming endpoints, Outgoing destinations and Recent deliveries. ### Incoming endpoints [#incoming-endpoints] An incoming endpoint is a public URL that receives JSON and starts matching workflows. A workflow uses it through a webhook trigger, and only published workflows start runs. 1. Select New incoming webhook, enter a name and create it. 2. Copy the shared secret now. It is not shown again. 3. Share the webhook URL with the external system. It posts to `POST /api/v1/public/webhooks/{token}`. Each request must prove it knows the secret. Sign the request with the secret as `X-Centrakit-Signature` over `timestamp.body`, or send the secret as `X-Webhook-Secret` or a Bearer token. Rotate URL issues a new public token and invalidates the previous one immediately. Rotate secret issues a new shared secret. ### Outgoing destinations [#outgoing-destinations] An outgoing destination is a signed HTTPS URL that receives domain events and workflow webhook actions. 1. Select New outgoing webhook and enter a name and an HTTPS URL. 2. Select the events to post automatically, or leave all unchecked to use the destination from workflows only. 3. Create it and copy the signing secret now. It is not shown again. Each delivery is JSON signed with `X-CentraKit-Timestamp` and `X-CentraKit-Signature`, so your system can verify it with the signing secret. Send test queues a delivery with event kind `webhook.test`, which does not start a workflow. Rotate secret issues a new signing secret. ### Deliveries [#deliveries] Recent deliveries lists each outgoing delivery with its status: pending, processing, completed, failed, dead lettered or canceled. Failed deliveries are retried automatically, and you can retry one by hand. ## Permissions [#permissions] | Key | Grants | | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | `organization.integrations.manage` | Open the marketplace and integration detail pages, and create and edit WhatsApp connections and Google Calendar import settings. | | `organization.webhooks.view` | View incoming endpoints, outgoing destinations and deliveries. | | `organization.webhooks.create` | Create endpoints and destinations. | | `organization.webhooks.update` | Edit endpoints and destinations, rotate URLs and secrets, send tests and retry deliveries. | | `organization.webhooks.delete` | Delete endpoints and destinations. | Any member can request an integration. Platform administrators manage the marketplace catalog in [Admin integrations](/admin/integrations). See [Access control](/product/access-control) for roles. ## Automation and AI [#automation-and-ai] Workflows use webhooks in two ways: a webhook trigger starts a run when an incoming endpoint receives JSON, and a webhook action posts to an outgoing destination. See [Workflows](/product/workflows). The assistant and MCP clients can manage webhooks with these tools: | MCP tool | Access | | ---------------------------------------------------------------------------------- | ------ | | `listWebhookEndpoints`, `getWebhookEndpoint` | Read | | `createWebhookEndpoint`, `updateWebhookEndpoint`, `deleteWebhookEndpoint` | Write | | `rotateWebhookEndpointSecret`, `rotateWebhookEndpointToken` | Write | | `listWebhookDestinations`, `getWebhookDestination` | Read | | `createWebhookDestination`, `updateWebhookDestination`, `deleteWebhookDestination` | Write | | `rotateWebhookDestinationSecret` | Write | | `listWebhookDeliveries`, `getWebhookDelivery` | Read | WhatsApp connections and Google Calendar have no MCP tools. ## API [#api] Paths are relative to `/api/v1/organizations/{organizationId}`. | Method | Path | Permission | | ------------------ | ------------------------------------------------------ | ------------------------------------------------------------ | | GET, POST | `/webhooks/endpoints` | `organization.webhooks.view`, `organization.webhooks.create` | | GET, PATCH, DELETE | `/webhooks/endpoints/{endpointId}` | view, update, delete | | POST | `/webhooks/endpoints/{endpointId}/rotate-secret` | `organization.webhooks.update` | | POST | `/webhooks/endpoints/{endpointId}/rotate-token` | `organization.webhooks.update` | | GET, POST | `/webhooks/destinations` | `organization.webhooks.view`, `organization.webhooks.create` | | GET, PATCH, DELETE | `/webhooks/destinations/{destinationId}` | view, update, delete | | POST | `/webhooks/destinations/{destinationId}/rotate-secret` | `organization.webhooks.update` | | GET | `/webhooks/deliveries` | `organization.webhooks.view` | | GET | `/webhooks/deliveries/{deliveryId}` | `organization.webhooks.view` | Meta calls `GET` and `POST /channels/{connectionId}/webhook` under the same organization path to verify and deliver WhatsApp messages. Background jobs process channel messages and outgoing deliveries every minute. See [Webhooks for developers](/developers/webhooks) for payloads and signature verification, and [OpenAPI](/developers/openapi) for schemas. ## Related pages [#related-pages] * [Inbox](/product/inbox) for conversations from connected channels. * [Agenda](/product/agenda) for imported Google calendars. * [Workflows](/product/workflows) for webhook triggers and actions. * [Admin integrations](/admin/integrations) for the catalog. # Invoices (/product/invoices) How invoices work at /invoices, including creation from quotes and tasks, sending, online payment through Stripe, recorded payments, credit notes, write-offs, reminders and the Payments page. ## Overview [#overview] Invoices bill customers for quotes, tasks, orders and other work. They live under **Finance > Invoices** at `/{orgSlug}/invoices`, which has two tabs: * **Pipeline** (`/invoices`): a board with the columns Suggested, Draft, Sent, Overdue, Written off, Paid and Cancelled. Every open invoice shows. Paid and cancelled columns default to invoices closed since last month; switch the period to All time for older ones. * **All invoices** (`/invoices/all`): a table with search, filters, sorting and bulk actions. New invoices start at `/invoices/create` and open at `/invoices/[invoiceId]`. The detail page is a branded document preview with the email message, payments, notes and linked records below it. Online payment is set up on the **Payments** page at `/{orgSlug}/payments`, under **Finance > Payments**. ## Key concepts [#key-concepts] ### Kinds [#kinds] | Kind | Meaning | | ----------- | --------------------------------------------------------------------------------------------------- | | Invoice | A regular invoice | | Deposit | The deposit invoice created when a quote with a deposit is accepted | | Credit note | A correcting document with its own number, inverted amounts and a reference to the original invoice | ### Statuses [#statuses] | Status | Meaning | | ----------- | ------------------------------------- | | Draft | Editable, including the customer | | Sent | Issued to the customer and immutable | | Overdue | Past the due date and still unpaid | | Written off | Uncollectible, with a required reason | | Paid | Settled in full | | Cancelled | Voided before it was paid | On the board you can drag a draft to Sent, a sent or overdue invoice to Paid or Cancelled, and a written-off invoice to Paid. ### Lines, charges and totals [#lines-charges-and-totals] Line types are section headers, products, time (hourly), fixed price and custom lines. Invoice templates can define standard costs, which you add one at a time with **Add standard cost**; each one copies an editable snapshot onto the invoice. Discounts can be a percentage or a fixed amount, or come from a [promotion](/product/orders). VAT works as on quotes: organization presets plus Custom, totals per rate, and the regimes standard, reverse charge, exempt, KOR and margin scheme. An invoice can carry a customer reference, such as a purchase-order number, and a delivery date that defaults to the invoice date when it is sent. Collection costs, statutory interest and late fees are separate charges that count toward the amount due. The **Add charge** dialog shows the statutory maximum for collection costs. ### Lifecycle [#lifecycle] The **Invoice progress** strip follows the default invoice lifecycle: Sent, Viewed and Paid, with Partially paid as a flag. Its reminder ladder sends reminders 3 days before the due date and 7 and 14 days after it. The invoice becomes Overdue on the due date, a final notice goes out 21 days after the due date, and 15 days later collection costs are added and the invoice moves to In collection. Customer emails run within business hours and re-check the invoice first. Invoice templates can pick a different lifecycle or customize its reminders. See [Content](/product/content). ## Create an invoice [#create-an-invoice] From the Invoices page: * **New invoice** starts an empty invoice. * **More create options > From template** starts from an invoice template. * **More create options > From quote** starts from an accepted quote. You can also select **Create invoice** on the accepted quote. From billable work: * The **Suggested** column groups billable tasks, logged time, materials and expenses per customer. Drag a suggestion to Draft, or select several, to create draft invoices. **Mark as not billable** excludes its time, materials and expenses from future invoices. * Link tasks on a draft in the **Quotes and tasks** section to bill them. * On an order, **Create invoice** bills what is ready to invoice. See [Orders](/product/orders). Then select the customer and optional contact, add lines, set the invoice date, due date and options, and save. **Scan image** on the line editor looks a product up from a photo. See [Products](/product/products). The **Options** panel holds the workflow, payment term, template, currency, VAT and online payment settings. Online payment is on by default; turn it off to offer bank transfer only. ## Send an invoice [#send-an-invoice] Select **Send** on a draft. Sending needs a due date, a customer billing or contact email, and a customer VAT number when reverse charge applies. When online payment is on, the organization's Stripe account must be able to receive payments. When an approval policy applies, approvers are notified and you send again once it is approved. See [Approvals](/product/approvals). The first send snapshots the seller and buyer details, so later customer or organization edits do not change the issued invoice. The email attaches the invoice PDF, plus the terms and conditions PDF when the template sets one. Sent and overdue invoices can be sent again from the **Other** menu. Several invoices can be sent at once from **All invoices**. Sending an invoice created from a quote consumes the stock the quote held. ## Get paid [#get-paid] ### Online payment [#online-payment] The customer opens the invoice at `/{orgSlug}/portal/invoices/[invoiceId]`, including as a guest, and selects **Pay invoice**. The embedded Stripe Payment Element offers the payment methods the account supports, such as cards and iDEAL. Stripe webhooks are authoritative: the invoice becomes paid when Stripe confirms the payment, and a returning browser only refreshes the page. Failed payments, refunds and disputes are mirrored on the invoice. See [Customer portal](/product/customer-portal). ### Record a payment [#record-a-payment] For bank transfers, cash and other payments, open the **Payments** section and select **Record payment**. Enter the amount, method, date received and reference. An invoice can have several payments; the section shows the total, paid and outstanding amounts, and the payment that settles the balance marks the invoice paid. Recorded payments can be removed. ### Mark paid [#mark-paid] **Mark paid** settles a sent, overdue or written-off invoice in one step. Marking paid cancels any open online payment. ## Correct or close an invoice [#correct-or-close-an-invoice] * **Credit note** creates a draft credit note for a sent, overdue, written-off or paid invoice. You can return the items to stock. Sending a credit note leaves the original invoice unchanged and cancels its open online payment when the original is unpaid. * **Cancel invoice** voids a sent, overdue or written-off invoice that was never paid, with an optional reason and the option to return items to stock. Cancelled invoices keep their number. * **Write off** marks a sent or overdue invoice as uncollectible with a required reason. Reminders stop, and payments that still arrive are recorded. * **Pause reminders** stops reminders and fees with a reason, until a date you choose or until you select **Resume reminders**. * **Archive** hides an invoice from active lists and the pipeline. Archived invoices can be restored from the Archived filter. Drafts are archived instead of cancelled. The **Other** menu also offers **Download PDF**, **Show online** and **Duplicate**. Bulk actions in **All invoices** are send, mark paid, cancel and archive. Export the filtered list as CSV, Excel or PDFs. ## Notes and linked records [#notes-and-linked-records] Notes are an internal thread that stays off the PDF and the customer-facing invoice. Mention users and teams with `@`, or work and customer records with `#`. Notes copy onto invoices created from a quote, duplicates and credit notes. The **Quotes and tasks** section shows the quote and tasks the invoice bills for. ## Set up online payment [#set-up-online-payment] 1. Open **Finance > Payments**. 2. Select **Connect Stripe** and complete the Stripe onboarding. Use **Continue setup** if Stripe needs more information. 3. Select **Refresh status** to update the account status and whether charges are enabled. The account shows as Not connected, Setup required or Enabled. Invoices can only be sent with online payment once charges are enabled. The platform fee per transaction is set by platform admins in [Platform settings](/admin/platform-settings). See [Stripe billing](/operations/stripe-billing) for the operator setup. ## Permissions [#permissions] | Key | Grants | | --------------------------------------- | ----------------------------------------------------------------------------- | | `organization.invoices.view` | See invoices and download PDFs | | `organization.invoices.create` | Create invoices, credit notes and invoices from quotes, orders or suggestions | | `organization.invoices.update` | Edit drafts, notes, charges and linked tasks | | `organization.invoices.delete` | Archive and restore invoices | | `organization.invoices.send` | Send invoices | | `organization.invoices.mark-paid` | Mark invoices paid | | `organization.invoices.record-payment` | Record and remove manual payments | | `organization.invoices.write-off` | Write off invoices | | `organization.invoices.pause-reminders` | Pause and resume reminders | | `organization.invoices.cancel` | Cancel invoices | | `organization.invoices.duplicate` | Duplicate invoices | | `organization.payments.manage` | Open the Payments page and manage the Stripe account | Accepting or dismissing a suggestion also needs `organization.tasks.update`. See [Access control](/product/access-control). ## Automation and AI [#automation-and-ai] * Invoice events such as `invoice.sent`, `invoice.viewed`, `invoice.partially-paid`, `invoice.paid`, `invoice.overdue`, `invoice.reminder-sent`, `invoice.final-notice-sent`, `invoice.payment-failed`, `invoice.disputed`, `invoice.uncollectible`, `invoice.cancelled`, `invoice.reminders-paused` and `invoice.reminders-resumed` can trigger [workflows](/product/workflows) and outgoing [webhooks](/developers/webhooks). * Starter automations for invoice templates include **B2C 14-day letter** (send individual customers the statutory letter once an invoice is overdue) and **Payment failed** (flag the invoice and alert the team). * The assistant can create, update, send, cancel, duplicate and archive invoices, record payments, write off, pause reminders and move lifecycle stages. See [Chat](/product/chat). Not yet available: invoice tools on the MCP server. ## API [#api] All paths are under `/api/v1/organizations/{organizationId}`. | Resource | Operations | | --------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `/invoices` | [List](/openapi/listInvoices), [create](/openapi/createInvoice), [get](/openapi/getInvoice), [update](/openapi/updateInvoice), [delete](/openapi/deleteInvoice) | | `/invoices/from-quote` | [Create from a quote](/openapi/createInvoiceFromQuote) | | `/invoices/{invoiceId}/send` | [Send](/openapi/sendInvoice) | | `/invoices/{invoiceId}/mark-paid` | [Mark paid](/openapi/markInvoicePaid) | | `/invoices/{invoiceId}/payments` | [Record a payment](/openapi/recordInvoicePayment) | | `/invoices/{invoiceId}/cancel` | [Cancel](/openapi/cancelInvoice) | | `/invoices/{invoiceId}/write-off` | [Write off](/openapi/writeOffInvoice) | | `/invoices/{invoiceId}/pause-reminders` and `/resume-reminders` | [Pause](/openapi/pauseInvoiceReminders) and [resume](/openapi/resumeInvoiceReminders) reminders | | `/invoices/{invoiceId}/duplicate` | [Duplicate](/openapi/duplicateInvoice) | | `/invoices/{invoiceId}/notes` | [List](/openapi/listInvoiceNotes), [add](/openapi/addInvoiceNote), [update](/openapi/updateInvoiceNote), [delete](/openapi/deleteInvoiceNote) | | `/invoices/{invoiceId}/lifecycle` | [Get lifecycle](/openapi/getInvoiceLifecycle) and [advance a stage](/openapi/advanceInvoiceLifecycle) | ## Related pages [#related-pages] * [Quotes](/product/quotes) * [Orders](/product/orders) * [Tasks](/product/tasks), [Time](/product/time) and [Expenses](/product/expenses) for billable work * [Content](/product/content) for invoice templates and reminders * [Customer portal](/product/customer-portal) * [Billing and usage](/product/billing-and-usage) for the organization's own subscription, which is separate from customer invoices * [Stripe billing](/operations/stripe-billing) # Notifications (/product/notifications) How notifications reach you in the topbar and the Notifications hub, how to act on approvals and replies inline, and how watch levels and channel preferences control what you receive. ## Overview [#overview] Notifications tell you when something needs you or has happened to work you follow. They appear in two places: * The bell in the topbar shows the unread count and the latest items. It updates live while you work. * The Notifications hub at `/{orgSlug}/notifications` lists everything for the organization, with search, filters and bulk actions. It is the second item in the sidebar. Opening `/notifications` without an organization takes you to the hub of your preferred organization. ## Key concepts [#key-concepts] ### What the hub contains [#what-the-hub-contains] The hub combines several sources in one list: * Notifications stored for you, such as assignments, mentions, comments, messages and status changes. * Live AI activity: agent runs, artifacts and workflow runs that are running, waiting or failed. * Pending invitations to join another organization. * [Approval requests](/product/approvals) that you can decide. ### Groups, subjects and types [#groups-subjects-and-types] Each notification belongs to a group: | Group | Meaning | | ------------ | ---------------------------------------------------- | | Needs action | You are expected to decide, reply or review. | | Running | AI or workflow work is in progress. | | Happened | Information about something that already took place. | The subject is the kind of record the notification is about, for example Task, Quote, Invoice, Order, Chat, Inbox, Workflow, Approval, Time off, Timesheet, Expense or Application. The type is the specific event, such as a task assignment, a mention, a quote acceptance, an overdue invoice or an escalated approval. Notifications also carry a priority (low, normal, high or urgent) and show who caused them: a user, a customer or the system. ### Watch levels [#watch-levels] You can watch a record to hear about it even when you are not assigned. Watching is available on tasks, chat threads, inbox threads, workflows, workflow runs, agenda items, customers, quotes, invoices, orders and hiring applications. | Level | What you receive | | --------------------------- | -------------------------------------------------- | | All activity | Every update to the record. | | Participating and @mentions | Only updates where you take part or are mentioned. | | Ignore | Nothing for this record. | ### Channels [#channels] Each notification type can be delivered in the app, by email or by push. You set preferences for one organization or as an account default that applies everywhere. Notifications are delivered in the app today. Email and push preferences are saved, but delivery through those channels is not yet available. Not yet available: WhatsApp notifications. ## Work with notifications [#work-with-notifications] ### Read and clear notifications [#read-and-clear-notifications] Open the bell to see the latest items. Select Mark all as read to clear the count, or Show all to open the hub. In the hub you can: * Search notifications and filter by Status (All, Unread, Read), Group, Subject and Type. * Sort by subject, type, status or date. * Open a row, copy its link, dismiss it, or mark it as read or unread. * Select several rows and choose Mark selected read. ### Act inline [#act-inline] Notifications that need action have buttons in the row, so you can respond without opening the record: | Notification | Action | | --------------------------- | --------------- | | Approval request | Approve or Deny | | Message that needs a reply | Reply | | AI output to check | Review | | Running AI or workflow work | Stop | ### Watch or ignore a record [#watch-or-ignore-a-record] On a record that supports watching, use Watch, Unwatch or Stop ignoring to change your level. The Notification settings link opens your channel preferences. ### Choose your channels [#choose-your-channels] Open your [profile](/product/profile) and go to the Communication tab. The Channels table has In-app, Email and Push toggles. ## Permissions [#permissions] Notifications are personal. Every user sees only their own notifications and subscriptions, and only for records they can open. Acting on a notification uses the permission of the record behind it, for example deciding an approval needs the approver permission set on the approval rule. ## Automation and AI [#automation-and-ai] * Workflows can notify the people involved with a quote, invoice, task or order through the Send in-app notification step. Watchers who ignore the record are skipped. See [Workflows](/product/workflows). * The assistant can list your notifications, count unread items, mark items as read and dismiss them. These actions do not ask for approval. See [Chat](/product/chat). ## API [#api] All routes live under `/api/v1/organizations/{organizationId}/`: | Method and path | Purpose | | ---------------------------------------------------- | ----------------------------------------------------- | | `GET notifications` | List your notifications. | | `PATCH notifications/{notificationId}` | Mark a notification as read or unread, or dismiss it. | | `GET notifications/inbox` | Read the combined hub list. | | `POST notifications/mark-all-read` | Mark all notifications as read. | | `GET notification-preferences` | List your channel preferences. | | `PUT notification-preferences` | Set a preference for one type and channel. | | `GET notification-subscriptions` | List the records you watch or ignore. | | `POST notification-subscriptions` | Watch or ignore a record. | | `DELETE notification-subscriptions/{subscriptionId}` | Remove a watch. | See [API](/developers/api) and [OpenAPI](/developers/openapi). To push events to your own systems, use [webhooks](/developers/webhooks). ## Related pages [#related-pages] * [Approvals](/product/approvals) * [Dashboard](/product/dashboard) * [Inbox](/product/inbox) * [Profile](/product/profile) # Orders (/product/orders) Sales orders at /orders, with stock reservation, fulfillment through shipments, invoicing policies, digital delivery, returns and promotions. ## Overview [#overview] Sales orders sit between an accepted quote or an incoming request and delivery and invoicing. They live under **Finance > Orders** at `/{orgSlug}/orders`, which has three tabs: | Tab | Route | What it shows | | ---------- | -------------------- | --------------------------------------------------------------------- | | Orders | `/orders` | The order list with search and filters | | Shipments | `/orders/shipments` | Outgoing shipments, expected supplier deliveries and customer returns | | Promotions | `/orders/promotions` | Discount rules for quotes, orders and invoices | An order opens at `/orders/[orderId]` with its lines, delivery and invoicing settings, fulfillment, invoices, digital delivery and lifecycle. A promotion opens at `/orders/promotions/[promotionId]`. The Promotions tab only shows for users who can view promotions. ## Key concepts [#key-concepts] ### Order statuses [#order-statuses] | Status | Meaning | | --------- | ----------------------------------------------------------- | | Draft | Lines and settings can change | | Confirmed | Stock is reserved and the order can ship and be invoiced | | On hold | Paused; it still holds its stock | | Completed | Fulfilled and invoiced | | Cancelled | The unshipped remainder is cancelled and its stock released | Each order also shows a derived fulfillment status (not shipped, partly shipped or shipped) and payment status (not invoiced, unpaid, partly paid or paid). ### Sources [#sources] An order records where it came from: manual, quote, inbox, API or integration. Quotes create orders when their quote template has **Create an order when accepted** switched on, or when staff select **Create order** on an accepted quote. Inbox orders arrive as a proposal with an approval card. API orders are idempotent per source and external ID. ### Lines [#lines] Lines are product variants, custom lines or shipping costs. An order can have an order discount as a percentage or fixed amount, or a promotion. A line cannot go below what already shipped, and shipped lines cannot be removed. ### Delivery and invoicing [#delivery-and-invoicing] The **Delivery and invoicing** section sets the delivery method, the invoicing policy, a requested delivery date, the ship-to address, a note for the customer and internal notes. | Delivery method | Meaning | | --------------- | ---------------------------------------------------------- | | Pickup | The customer collects the goods | | Own delivery | Delivered by hand; record a shipment when it has been sent | | Carrier | Shipped with a carrier, with tracking number and link | | Digital | Delivered as files, license keys or links | | No delivery | Nothing is delivered | | Invoicing policy | Meaning | | ----------------------- | -------------------------------------- | | Invoice before shipping | Prepaid; picking waits for payment | | Invoice per shipment | Invoice what was delivered | | Invoice manually | Staff create invoices when they choose | For digital sales to a consumer in another EU country, the order offers **VAT at the buyer's rate (OSS)**. ### Lifecycle [#lifecycle] The **Order progress** strip follows the default order lifecycle: confirmed, paid, picking, shipped, delivered, invoiced and completed. Prepaid orders wait for payment before picking. ## Create and confirm an order [#create-and-confirm-an-order] 1. Open **Orders** and select **New order**, then pick the customer. You add products on the draft. 2. Add product lines by name or SKU, custom lines and shipping costs, and set the delivery and invoicing options. 3. Select **Confirm**. Confirming reserves stock for the lines; anything that is not in stock becomes a backorder. When an approval policy applies, the order is sent for approval and goes ahead once approved. See [Approvals](/product/approvals). On a confirmed order: * **Reserve stock** allocates stock that arrived for backordered lines. * **Put on hold** and **Release hold** pause and resume the order. * **Create invoice** bills what is ready to invoice under the order's policy. See [Invoices](/product/invoices). * **Cancel order** cancels the unshipped remainder and releases its stock, with an optional reason. Shipped goods stay on the order. Drafts and cancelled orders with nothing shipped can be deleted. The order offers two PDFs: an order confirmation and a packing slip. ## Ship an order [#ship-an-order] 1. In the order's **Fulfillment** section, select **Create shipment**. 2. Pick what goes in the shipment. Shipping takes the reserved stock; the rest stays reserved for a later shipment, so partial shipments are allowed. 3. Choose the delivery method, carrier and tracking number. Use **Create and ship** with **Ship right away**, or **The customer picks it up now** for pickups, to complete it in one step. 4. Otherwise open the shipment later and select **Ship**, or **Mark picked up** for pickups. Shipment statuses are draft, packed, ready for pickup, in transit, delivered, picked up, problem and cancelled. Each shipment holds the carrier, number of packages, tracking number and tracking link, and the customer can follow it from the portal. Shipping fails when there is not enough stock. ## Handle a return [#handle-a-return] 1. On a confirmed or completed order, select **Register return**. 2. Pick what the customer sends back and what happens to each item: restock, quarantine or write off. 3. When the return arrives, select **Receive into stock**. 4. Select **Create credit note** to credit the returned items. See [Invoices](/product/invoices). Supplier deliveries are inbound shipments announced from a purchase order. See [Products](/product/products). ## Deliver digital products [#deliver-digital-products] Digital lines never create shipments. When the order is confirmed or paid, according to its invoicing policy, the order issues license keys from the product's key pool and emails secure download links. The **Digital delivery** section shows how much of each line was delivered, downloads and keys left, and revoked grants. Select **Deliver now** to deliver manually. When keys run out, the order delivers what is available and the remaining lines wait for license keys. Customers find their downloads and keys on the order in the portal. ## Promotions [#promotions] Promotions are discounts you pick on quotes, orders and invoices, or that customers apply with a code. | Setting | Options | | ------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | Discount | Percentage, fixed amount or free shipping | | Applies to | The whole document, or lines with specific products, variants or categories | | Code | Optional, such as `SPRING10`; must be unique | | Usable on | Quotes, orders and invoices | | Eligibility | Everyone, business customers, private customers, specific customers or customer tags; a customer qualifies when any rule matches | | Schedule and limits | Start and end dates, minimum subtotal, minimum quantity, total uses and uses per customer | Promotions start as drafts. Create one with **New promotion**, then set targets, eligibility and dates on its detail page and set the status to active. The list shows each promotion's code, discount, status (draft, scheduled, active, expired or archived) and how often it was used. The detail page lists the documents that used it. To apply a promotion on a quote, order or invoice, open **Apply a promotion** and pick one or type its code. Picking a promotion copies its discount onto the document; editing the discount by hand unlinks it. On orders, promotions are set before confirming. A use counts when a quote is accepted, an order is confirmed, or an invoice without an order is sent. Cancelling and credit notes give the use back. Deleting removes an unused promotion. A promotion that was already used is archived instead, so documents keep their history. ## Permissions [#permissions] | Key | Grants | | --------------------------------------------------------------- | ---------------------------------------------------------------------- | | `organization.orders.view` | See orders and shipments | | `organization.orders.create` | Create orders, including from an accepted quote | | `organization.orders.update` | Edit drafts, hold and release, and reserve stock | | `organization.orders.delete` | Delete draft or cancelled orders | | `organization.orders.confirm` | Confirm orders | | `organization.orders.fulfill` | Create and ship shipments, register returns, and deliver digital items | | `organization.orders.cancel` | Cancel orders | | `organization.promotions.view`, `.create`, `.update`, `.delete` | Manage promotions | Creating an invoice or a return credit note also needs `organization.invoices.create`. Applying a promotion needs update access on the quote, order or invoice. See [Access control](/product/access-control). ## Automation and AI [#automation-and-ai] * Order events include `order.created`, `order.confirmed`, `order.backordered`, `order.paid`, `order.shipped`, `order.ready-for-pickup`, `order.picked-up`, `order.delivered`, `order.invoiced`, `order.completed`, `order.cancelled` and `order.returned`. Shipments emit `shipment.shipped`, `shipment.out-for-delivery`, `shipment.ready-for-pickup`, `shipment.delivered` and `shipment.exception`. They can trigger [workflows](/product/workflows) and outgoing [webhooks](/developers/webhooks). * Shipments follow their own lifecycle from in transit through out for delivery to delivered, and flag carrier problems. * The `internal/commerce/daily` cron runs every morning and emits `promotion.expiring` and `promotion.limit-reached` for promotions, and `purchase-order.overdue` for late purchase orders. * The `internal/inventory/signals` cron runs every minute and publishes stock level changes. See [Products](/product/products). * Starter automations for order templates include **Prepaid order to picking**, **Ready for pickup** (message the customer and remind them after 7 days) and **Delivered, then invoice**. Add them from the template's Workflow tab under [Content](/product/content). * The assistant can create, update, confirm and cancel orders, move lifecycle stages, and manage promotions. See [Chat](/product/chat). * MCP tools: `listOrders`, `getOrder`, `createOrder`, `confirmOrder`, `cancelOrder`, `listShipments`, `getShipment`, `shipShipment` and `validatePromotion`. See [MCP](/developers/mcp). ## API [#api] All paths are under `/api/v1/organizations/{organizationId}`. | Resource | Operations | | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------- | | `/orders` | [List](/openapi/listOrders), [create](/openapi/createOrder) (idempotent per `source` and `externalId`, with optional `confirm`), [get](/openapi/getOrder) | | `/orders/{orderId}/confirm` | [Confirm](/openapi/confirmOrder) | | `/orders/{orderId}/cancel` | [Cancel](/openapi/cancelOrder) | | `/orders/{orderId}/lifecycle` | [Get lifecycle](/openapi/getOrderLifecycle) and [advance a stage](/openapi/advanceOrderLifecycle) | | `/shipments` | [List](/openapi/listShipments) and [get](/openapi/getShipment) | | `/shipments/{shipmentId}/ship` | [Ship](/openapi/shipShipment); returns `409 APPROVAL_REQUIRED` when an approval policy applies | | `/promotions/validate` | [Validate a promotion or code](/openapi/validatePromotion) for a customer without redeeming it | Webshops can combine `createOrder`, `validatePromotion` and [inventory levels](/openapi/listInventoryLevels) for checkout and stock sync. ## Related pages [#related-pages] * [Quotes](/product/quotes) * [Invoices](/product/invoices) * [Products](/product/products) for stock, suppliers and purchase orders * [Customer portal](/product/customer-portal) for order and shipment tracking * [Inbox](/product/inbox) for orders proposed from conversations * [Content](/product/content) for order templates * [Approvals](/product/approvals) # Organizations (/product/organizations) How organizations work as the tenant boundary in CentraKit, and how to create, switch between, transfer and delete them. ## Overview [#overview] An organization is one business in CentraKit. Customers, work, finance, people, conversations and automation all belong to exactly one organization, and no data is shared between organizations. The organization is part of every URL as `/{orgSlug}`, for example `/tractor-service/customers`. A user can belong to several organizations and switches between them by following a link to another slug. Two pages live outside any organization: | Route | Purpose | | -------------------- | -------------------------------------------- | | `/organizations` | Overview of every organization you can open. | | `/organizations/new` | Form to create an organization. | When you sign in or open `/`, CentraKit takes you to your preferred organization. If you have none, it takes you to `/organizations/new`. ## Key concepts [#key-concepts] ### Slugs [#slugs] The slug is the short name in the URL. It is created from the organization name and can be changed in [settings](/product/settings-and-branding). Old links stop working after a slug change, because an unknown slug returns a not found page. ### How you belong to an organization [#how-you-belong-to-an-organization] You reach an organization in one of two ways: * As a user with a role such as Owner, Admin, Member or Viewer. See [Access control](/product/access-control). * As a customer contact. You only see the [customer portal](/product/customer-portal) for that organization. The organizations overview shows your role per organization, with an Owner badge for organizations you own and a Customer badge for organizations you reach through the portal. ### Owner [#owner] Every organization has one owner. The owner holds billing, deletion and ownership transfer rights in addition to everything an admin can do. ## Work with organizations [#work-with-organizations] ### Switch organizations [#switch-organizations] Open the organization switcher at the top of the sidebar and select another organization. All organizations opens the overview, and Create organization opens the form. You can also press Cmd+K or Ctrl+K and use the [command center](/product/command-center). On the overview you can search, filter by Membership, open an organization, jump to its settings or copy its slug. ### Create an organization [#create-an-organization] Any signed-in user can create an organization. 1. Open `/organizations/new`, or select New organization on the overview. 2. Enter a name. The slug fills in from the name and can be edited. 3. Optionally add a website, company information, social links and branding. 4. Select Create organization. You become the owner. CentraKit adds default document templates and VAT rates and opens the new organization's dashboard. ### Transfer ownership [#transfer-ownership] On the settings page, the last section has Transfer ownership. Pick another active user as the new owner and type the organization name to confirm. The previous owner becomes an admin. The organization needs at least one other active user. ### Delete an organization [#delete-an-organization] On the settings page, select Delete organization and type the organization name to confirm. Deletion is permanent and removes the organization's data. CentraKit then opens your next organization, or the create form when none is left. Platform administrators can also disable an organization. See [Organizations](/admin/organizations). ## Permissions [#permissions] | Permission | Allows | | ----------------------------------------- | ---------------------------------- | | `organization.general.view` | Open the organization. | | `organization.general.update` | Edit settings, including the slug. | | `organization.general.transfer-ownership` | Transfer ownership. | | `organization.general.delete` | Delete the organization. | ## Automation and AI [#automation-and-ai] The assistant can read the active organization and list your organizations. Changing organization settings through the assistant always asks for approval. See [Chat](/product/chat). ## API [#api] | Method and path | Purpose | | ----------------------------------------------- | --------------------------------- | | `GET /api/v1/organizations` | List organizations you belong to. | | `POST /api/v1/organizations` | Create an organization. | | `GET /api/v1/organizations/{organizationId}` | Read one organization. | | `PATCH /api/v1/organizations/{organizationId}` | Update an organization. | | `DELETE /api/v1/organizations/{organizationId}` | Delete an organization. | | `PATCH /api/v1/users/me/active-organization` | Set your preferred organization. | API routes use the organization ID, not the slug. See [API](/developers/api) and [OpenAPI](/developers/openapi). ## Related pages [#related-pages] * [Settings and branding](/product/settings-and-branding) * [Access control](/product/access-control) * [Billing and usage](/product/billing-and-usage) * [Tenancy](/architecture/tenancy) # People (/product/people) How to manage employees, contracts, documents, skills, working hours, equipment, onboarding and offboarding checklists, pre-boarding links, HR settings and workforce insights in the People hub. ## Overview [#overview] People is the HR module of CentraKit. It keeps one profile per employee, contractor or intern, with their contract, documents, skills, working hours, equipment and onboarding progress. The same employee records feed time off, time tracking, expenses, planning and the assistant. People lives under Workspace, People, Employees in the sidebar. A gear icon in the hub tab bar opens HR settings. | Route | Purpose | | ----------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | | `/{orgSlug}/people` | Employees hub with the Employees, Org chart, Onboarding, Performance, Equipment, Skills and Insights tabs. | | `/{orgSlug}/people/create` | Create an employee. | | `/{orgSlug}/people/import` | Import employees from a CSV file. | | `/{orgSlug}/people/{employeeId}` | Employee detail with the Overview, Contract, Time off, Documents, Skills, Equipment, Onboarding and History tabs. | | `/{orgSlug}/people/onboarding/templates` | Onboarding templates. | | `/{orgSlug}/people/onboarding/templates/{templateId}` | Edit one template. | | `/{orgSlug}/people/onboarding/{journeyId}` | One onboarding or offboarding checklist. | | `/{orgSlug}/people/settings` | HR settings. | | `/portal/onboarding/{token}` | Pre-boarding page for a new hire without an account. | Not yet available: the Performance tab at `/{orgSlug}/people/performance`. Not yet available: Payroll at `/{orgSlug}/payroll`. ## Key concepts [#key-concepts] ### Employees [#employees] Every organization user gets an employee profile automatically when "Create employees for new members" is on in HR settings. You can also add employees who have no app account, such as freelancers or interns, and link them to a user later from the App access section. | Field | Values | | -------------- | ----------------------------------------- | | Type | Employee, contractor or intern. | | Status | Onboarding, active, offboarding or ended. | | Reporting line | Department (a team) and manager. | | Dates | Start date and optional end date. | Private details (date of birth, personal email and phone, IBAN, emergency contact and home address) are visible only to the employee and to people with `organization.people.view-sensitive`. Employees can hide their birthday and work anniversary from colleagues. ### Contracts [#contracts] A contract records the type (permanent, fixed term, on call or freelance), period, hours per week, pay basis (hourly, monthly, annual or fixed fee), pay frequency, holiday allowance, notice period and bonuses. The current contract drives leave entitlement and FTE. Pay is only shown to people who can view compensation. ### Working hours [#working-hours] Each employee follows the organization's default weekly schedule or personal working hours, including alternating two-week schedules. Agenda, Planning, leave hour calculation and timesheets all read the same schedule. Public holidays are set per organization and are never counted as leave. ### Documents, skills and certifications [#documents-skills-and-certifications] Documents are ID copies, driver's licences, certificates, contracts and other files, each with an optional issue and expiry date. Skills and certifications are defined once on the Skills tab and assigned to employees with an optional level. Certifications have a validity period, and expired certifications are flagged. Planning and tasks use skills as requirements, and HR settings decide whether an expired certificate warns or blocks an assignment. ### Equipment [#equipment] Equipment items are company property such as laptops, phones, tools and vehicle keys, with an asset tag, serial number, category, purchase price and warranty date. An item is Available, Handed out, In repair or Retired. Handing an item out records who holds it and its condition, and the holder confirms receipt from their own profile. ### Onboarding and offboarding [#onboarding-and-offboarding] An onboarding template has two lists of steps: Onboarding for a new hire and Offboarding for a leaver. Each step has a kind (task, equipment request, document request or personal details form), an owner (the employee, their manager, HR or a named user) and a due date relative to the start or end date. Negative days fall before the date, so -7 is a week before day one. Handed-out equipment gets a return step on offboarding automatically. Starting a checklist from a template creates a journey for one employee. A journey is in progress, completed or cancelled, and shows its buddy, first or last working day and progress. The default template starts automatically for new hires and leavers. ## Manage employees [#manage-employees] ### Add an employee [#add-an-employee] 1. Open People and select **New employee**. 2. Enter the name, work email, job title, type, department, manager and start date. 3. Select **Create**. Fill in working hours, private details and contracts on the detail page. ### Import employees [#import-employees] Open **Import** on the Employees tab and upload a CSV file with a header row. Supported columns are first name, last name, email, job title, kind, start date (YYYY-MM-DD), manager email, team and hours per week. Existing employees are matched on email and updated. ### Archive or delete an employee [#archive-or-delete-an-employee] The Danger zone on the Overview tab archives an employee, which hides them from the directory and pickers but keeps their history. Only employees without an app account can be deleted. ### Review history [#review-history] The History tab shows who changed what on the employee. Sensitive values are never shown. See [Audit log](/product/audit-log). ## Run onboarding [#run-onboarding] ### Edit a template [#edit-a-template] Open **Onboarding**, then **Templates**, and select a template or **New template**. Add steps to the Onboarding and Offboarding lists and select **Save**. ### Start a checklist [#start-a-checklist] Select **Start checklist** on the Onboarding tab, or **Start offboarding** on the employee's Onboarding tab. Pick the template and dates. Add extra items on the checklist page, and mark items done as they are completed. ### Send a pre-boarding link [#send-a-pre-boarding-link] New hires without an account can fill in their details before day one: 1. Open the checklist and select **Pre-boarding link**. 2. Choose how long the link stays valid (1 to 60 days, 14 by default) and optionally enter the new hire's personal email to send it. 3. The new hire opens `/portal/onboarding/{token}`, enters their private details and uploads the requested documents (up to 10 MB each). The checklist shows until when the link is active, and **Revoke link** ends it at once. An expired or revoked link shows "Link unavailable". ## HR settings [#hr-settings] `/{orgSlug}/people/settings` holds the organization-wide rules: | Section | What it controls | | ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------- | | General & pay | Full-time hours per week, pay frequency, pay day and automatic employee profiles for new users. | | Time off | Approval requirement, allowed negative balance, leave year start, public holiday country, team capacity warning and the HR handbook Knowledge collection. | | Time tracking | Timesheet approval, clock in and out, approximate location, billable rounding, budget warning and stopping forgotten timers. | | Expenses | Mileage and per diem rates, automatic approval threshold and receipts by email. | | Compliance & retention | Expired certificate behavior and how long former employee and rejected candidate data is kept. | | Default working hours | The weekly schedule for everyone without personal hours. | | Public holidays | Organization holidays, with a button to add the official holidays for the configured country. | | Leave types, leave policies and blackout periods | See [Time](/product/time). | | Expense categories and expense policies | See [Expenses](/product/expenses). | ## Insights [#insights] The Insights tab shows headcount, hires, leavers, turnover, absence rate and overtime for the last 12 months, a monthly headcount chart, labor cost per team (logged hours times each employee's cost rate) and a Needs attention list. Needs attention flags sustained overtime, planning mismatches, expense outliers and frequent short sickness from the last four weeks. People with `organization.people.view-insights` see the whole organization. Managers see the people who report to them. Sickness data is hidden without `organization.people.view-sensitive` because it is health data. ## Permissions [#permissions] | Permission | Allows | | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | | `organization.people.view` | See the employee directory and org chart, and edit your own private details and privacy settings. | | `organization.people.create` | Create and import employees. | | `organization.people.update` | Edit employees, documents and skills, and see the Performance tab. | | `organization.people.delete` | Delete employees without an app account. | | `organization.people.view-compensation` | See contract pay. | | `organization.people.manage-compensation` | Add, edit and delete contracts. | | `organization.people.view-sensitive` | See private details and sickness data. | | `organization.people.view-insights` | See workforce insights for the whole organization. | | `organization.equipment.view`, `.create`, `.update`, `.delete` | Work with equipment items and hand-outs. | | `organization.onboarding.view`, `.create`, `.update`, `.delete` | See, start, change and archive checklists and templates. | | `organization.hr-settings.manage` | Edit HR settings, default working hours and public holidays. | Any organization user can complete checklist items assigned to them and confirm receipt of their own equipment. ## Automation and AI [#automation-and-ai] * A daily job sends reminders for expiring documents and certifications and ending contracts, runs leave accrual and sick-leave follow-up, stops forgotten timers, closes forgotten clock-ins and anonymizes expired candidate data. * The dashboard shows who is out, birthdays and work anniversaries for people who share them. See [Dashboard](/product/dashboard). * The assistant reads leave balances and policies and decides HR approvals. See [Time](/product/time) and [Chat](/product/chat). ## API [#api] All routes live under `/api/v1/organizations/{organizationId}/`: | Resource | Routes | | --------- | -------------------------------------------------------------------- | | Employees | `GET` and `POST employees`, `GET` and `PATCH employees/{employeeId}` | Employee responses never include private details. The pre-boarding page uses the public `GET` and `POST /api/v1/public/onboarding/{token}` and `POST /api/v1/public/onboarding/{token}/documents` routes. See [API](/developers/api) and [OpenAPI](/developers/openapi). ## Related pages [#related-pages] * [Time](/product/time) * [Expenses](/product/expenses) * [Hiring](/product/hiring) * [Approvals](/product/approvals) * [Access control](/product/access-control) # Products (/product/products) The product catalog at /products, with variants, inventory levels and stock movements, suppliers, purchase orders, image lookup, and public product pages. ## Overview [#overview] Products is the organization's sellable catalog and its stock. It lives under **Resources > Products** at `/{orgSlug}/products` and has four tabs: | Tab | Route | What it shows | | --------------- | --------------------------- | --------------------------------------------------------------------------------- | | Overview | `/products` | The product list with search, filters, sorting by name or price, and bulk actions | | Inventory | `/products/inventory` | Every stock record across products and locations | | Suppliers | `/products/suppliers` | The organization's supplier masters | | Purchase orders | `/products/purchase-orders` | Supplier orders and their receiving progress | A product opens at `/products/[productId]`, where the ID in the URL is the product's slug. The detail page has the tabs Overview, Variants (`/variants`), Inventory (`/inventory`) and Suppliers (`/suppliers`). Suppliers open at `/products/suppliers/[supplierId]` and purchase orders at `/products/purchase-orders/[purchaseOrderId]`. Products are separate from customer assets. A customer asset is equipment installed at or owned by a customer and lives on the customer record (see [Customers](/product/customers)). Products use the `organization.products.*` permissions, while customer assets use `organization.assets.*`. ## Key concepts [#key-concepts] ### Products and variants [#products-and-variants] A product carries its identity (name, description, brand, categories, tags and images), codes (SKU, EAN, manufacturer part number, HS code, country of origin and external catalog ID), commercial defaults (selling price, purchase price, currency and VAT percentage) and a unit of measure such as piece, box, hour, kilogram or a custom unit. Every product has at least one variant. A simple product keeps a single default variant. Add up to three options, such as Size or Color, and **Generate variants** creates one variant per combination, up to 100. Each variant can override the SKU, price and image. An empty SKU or price falls back to the product. A variant can allow backorders so it keeps selling when stock runs out. Quote, order, invoice and task material lines always reference the exact variant. A product has a status of active, discontinued or archived, and a type: * **Physical** products ship and can track inventory. * **Digital** products are delivered as a downloadable file, a license key, an access link, or manually. Digital lines never create shipments. Not yet available: uploading digital files or loading license keys onto a product from the app or the API. ### Stock records and stock status [#stock-records-and-stock-status] Stock is tracked per variant in stock records. A stock record holds a location, an optional bin or shelf, lot number, serial number and expiration date, and a status of available, quarantined, expired or archived. Quantities are shown as: * **On hand**: what is physically in stock. * **Reserved**: what quotes, orders, invoices and tasks hold. * **Available**: on hand minus reserved. * **Incoming**: open quantities on sent purchase orders. Each variant gets a derived stock status: not tracked, discontinued, out of stock, backordered, low stock (at or below the reorder point), or in stock. The inventory strip on a product shows stock health by variant. ### Inventory movements [#inventory-movements] Every quantity change writes an inventory movement, so the stock history is auditable. Movement types are initial count, purchase receipt, sale, reservation, reservation release, reservation transfer, adjustment, transfer in, transfer out, return, write-off and cycle count. The product Inventory tab shows recent movements with the reason and the on-hand quantity after each change. ### Allocations [#allocations] Reservations are inventory allocations with a state of reserved, consumed or backordered. Sending a quote holds its tracked lines, creating an invoice from that quote moves the hold to the invoice, and sending the invoice consumes it. Confirming a sales order reserves free stock per line and backorders the rest. A shortfall never blocks sending a document; the document shows which lines are short. See [Quotes](/product/quotes), [Invoices](/product/invoices) and [Orders](/product/orders). ### Suppliers [#suppliers] A supplier has a name, status (active, inactive or archived), business identifiers (KVK and VAT number), contact details (email, ordering email, billing email, phone and website), an address, commercial defaults (currency, payment terms, default lead time and default receive location), external IDs and internal notes. The supplier detail page also lists that supplier's purchase orders. A product can link to many suppliers. Each link stores the supplier SKU, order code, unit cost, lead time, minimum order quantity and order multiple, and one supplier per variant can be the preferred supplier that reorders go to. ### Purchase orders [#purchase-orders] A purchase order moves through these statuses: | Status | Meaning | | ------------------ | ------------------------------------------------------------ | | Draft | Lines can still change | | Sent | Lines are locked and open quantities count as incoming stock | | Partially received | Some lines arrived | | Received | Everything arrived | | Closed | Closed short; the open remainder was cancelled | | Cancelled | Cancelled before anything arrived | ## Create a product [#create-a-product] 1. Open **Products** and select **New product**. 2. Fill in the identity, codes and prices, then save. 3. Open the **Variants** tab to add options, or the **Inventory** tab to add stock. To start from a photo, open **More create options** and choose **From image**. Take or upload a JPEG, PNG or WebP photo of up to 8 MB. Codes on the label, such as a barcode, are read first, and AI reads the rest. Pick the matching result and select **Create from these details** to prefill the form. Each lookup runs the product lookup workflow and counts toward the organization's workflow step allowance. The same lookup is available as **Scan image** on invoice lines. ## Track stock [#track-stock] 1. Open **Products > Inventory** or a product's **Inventory** tab and select **New stock record**. 2. Choose the product and variant, type or pick a location, and enter the initial count. 3. To change a quantity later, use **Adjust/count** from the row actions. Choose a movement type and give a reason. Editing a stock record only changes its metadata; quantities always change through an adjustment. Set a reorder point and reorder quantity per variant to get low-stock signals and purchase suggestions. Select several records to move them to another location, archive them, or delete them. ## Link suppliers [#link-suppliers] 1. Create the supplier under **Products > Suppliers > New supplier**. 2. On the product, open the **Suppliers** tab and select **Link supplier**. 3. Enter the supplier SKU, unit cost and ordering details, and mark the preferred supplier. Deleting a supplier removes its links from products. Products keep their own details. ## Order stock from suppliers [#order-stock-from-suppliers] 1. Open **Products > Purchase orders** and select **New purchase order**, then pick the supplier. You add lines on the draft. 2. Or open **Reorder suggestions** to see variants whose available plus incoming stock is at or under the reorder point. Selected lines become one draft purchase order per supplier. 3. Select **Send** to email the PDF to the supplier's ordering email. When the supplier has no ordering email, use **Mark as sent** instead. 4. When goods arrive, select **Receive** and enter the quantity per line, the location, and any lot or serial numbers. Received quantities go into stock, update the average cost, and fill waiting backorders first. Receiving more than was ordered asks for confirmation. 5. If the rest will never arrive, use **Close short** to cancel the open remainder. A purchase order with nothing received can be cancelled instead. When the supplier confirms a shipment, use **Announce delivery** on the purchase order to record an inbound shipment, then receive it into stock when it arrives. ## Print and share [#print-and-share] The product actions offer: * **Print QR stickers**: a printable sticker sheet. Select several products in the list to print stickers for up to 100 at once. * **Print product page**: a one-page PDF with the image, brand, identifiers, price and a QR code. * **Open public page** and **Copy public link**: the public product page at `/{orgSlug}/portal/products/[productId]`, which anyone with the link can open, including guests. It uses a fixed layout and does not read a template. The product list and the suppliers list can be exported. Bulk actions on products are archive, delete, assign a tag and clear tags. A product with linked inventory cannot be deleted until those stock records are deleted; archive it instead. ## Permissions [#permissions] | Key | Grants | | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | | `organization.products.view` | See products, variants, inventory and the public link actions | | `organization.products.create` | Create products and stock records, and start an image lookup for a new product | | `organization.products.update` | Edit products, variants, images, supplier links, reorder rules and stock adjustments | | `organization.products.delete` | Archive or delete products and stock records | | `organization.suppliers.view`, `.create`, `.update`, `.delete` | Manage suppliers | | `organization.purchase-orders.view`, `.create`, `.update`, `.delete` | Manage purchase orders and drafts | | `organization.purchase-orders.send` | Send a purchase order or mark it as sent | | `organization.purchase-orders.receive` | Receive goods and close a purchase order short | Brands and categories have their own keys and are managed under [Content](/product/content). Customer assets use `organization.assets.*`. See [Access control](/product/access-control) for roles. ## Automation and AI [#automation-and-ai] * Stock changes publish `inventory.level-changed`, `inventory.low-stock`, `inventory.out-of-stock` and `inventory.back-in-stock` events every minute through the `internal/inventory/signals` cron. Workflows and outgoing webhooks can react to them. * A daily cron flags sent purchase orders past their expected date with `purchase-order.overdue`. * Purchase orders follow the default purchase-order lifecycle: sent, confirmed, in transit, received and billed. * Starter automations include **Low stock reorder** (draft and send a purchase order when stock runs low), **Purchase order overdue** (remind the supplier) and **Back in stock** (fill backorders when stock arrives). Add them from a template's Workflow tab under [Content](/product/content). * The assistant can create, update, archive and delete products, manage variants, supplier links and stock records, and work with purchase orders. See [Chat](/product/chat). * MCP tools: `listProducts`, `getProduct`, `createProduct`, `updateProduct`, `listInventoryLevels`, `listPurchaseOrders`, `getPurchaseOrder`, `createPurchaseOrder` and `receivePurchaseOrder`. See [MCP](/developers/mcp). Receiving a purchase order through the API returns `409 APPROVAL_REQUIRED` with an `approvalId` when an approval policy applies. See [Approvals](/product/approvals). ## API [#api] All paths are under `/api/v1/organizations/{organizationId}`. | Resource | Operations | | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `/products` | [List](/openapi/listProducts), [create](/openapi/createProduct), [get](/openapi/getProduct), [update](/openapi/updateProduct), [delete](/openapi/deleteProduct) | | `/products/{productId}/suppliers` | [Add](/openapi/addProductSupplierLink), [update](/openapi/updateProductSupplierLink) and [remove](/openapi/removeProductSupplierLink) supplier links | | `/inventory/levels` | [List levels](/openapi/listInventoryLevels) for webshop stock sync | | `/inventory/stock-records` | [List](/openapi/listInventoryStockRecords), [create](/openapi/createInventoryStockRecord), [update](/openapi/updateInventoryStockRecord), [archive](/openapi/archiveInventoryStockRecord) | | `/inventory/stock-adjustments` | [Adjust stock](/openapi/adjustInventoryStock) | | `/inventory/locations` | [List](/openapi/listInventoryLocations) and [create](/openapi/createInventoryLocation) locations | | `/inventory/movements` | [List movements](/openapi/listInventoryMovements) | | `/inventory/allocations` | [List allocations](/openapi/listInventoryAllocations) | | `/suppliers` | [List](/openapi/listSuppliers), [create](/openapi/createSupplier), [get](/openapi/getSupplier), [update](/openapi/updateSupplier), [delete](/openapi/deleteSupplier) | | `/purchase-orders` | [List](/openapi/listPurchaseOrders), [create](/openapi/createPurchaseOrder), [get](/openapi/getPurchaseOrder), [receive](/openapi/receivePurchaseOrder) | ## Related pages [#related-pages] * [Content](/product/content) for brands, categories, tags and templates * [Orders](/product/orders) for sales orders, shipments and promotions * [Quotes](/product/quotes) and [Invoices](/product/invoices) for stock holds on documents * [Customers](/product/customers) for customer assets * [Customer portal](/product/customer-portal) * [Workflows](/product/workflows) * [API](/developers/api) and [Webhooks](/developers/webhooks) # Profile (/product/profile) How to manage your personal details, language, regional formats, theme, sign-in methods and notification channels, and which account pages live outside an organization. ## Overview [#overview] Your profile holds the settings that belong to you rather than to an organization. Open it at `/profile` or through Account in the user menu at the bottom of the sidebar. The same profile applies in every organization you belong to. The profile has three tabs: Info, Authentication and Communication. Other pages outside an organization: | Route | Purpose | | -------------------- | ---------------------------------------------------------------- | | `/organizations` | Your organizations. See [Organizations](/product/organizations). | | `/organizations/new` | Create an organization. | | `/feedback` | Feedback page, linked from Feedback in the user menu. | Not yet available: a feedback form. The `/feedback` page is a placeholder. ## Key concepts [#key-concepts] ### User menu [#user-menu] The user menu has Homepage, Docs, Feedback, Changelog, Account and Log out. It also has Navigation mode, which switches the sidebar between Organization, Customer and Admin when you have access to more than one. ### Regional preferences [#regional-preferences] Your timezone and formats decide how dates and times are shown to you across the app. They do not change what other users see. ## Edit your profile [#edit-your-profile] ### Info [#info] | Section | Settings | | -------------------- | ---------------------------------------------------------------------------------------------------------------- | | Personal information | Photo, first name, last name and username. | | Language | English or Nederlands. | | Regional preferences | Timezone, week starts on Monday or Sunday, date format and time format (24-hour or 12-hour). | | Appearance | System, Light or Dark theme. The theme is saved on this device only. | | Celebrations | Show my birthday and Show my work anniversary. Only shown when you are an employee in [People](/product/people). | Select your photo or its change button to upload a JPEG, PNG or WebP image under 5 MB. You can crop, rotate and zoom before saving. A username has 3 to 30 characters: lowercase letters, numbers and underscores. Date formats are dd/MM/yyyy, dd-MM-yyyy, dd.MM.yyyy, MM/dd/yyyy and yyyy-MM-dd. ### Authentication [#authentication] The Sign-in methods table lists Email and Google: * Email: add a password, or update it by entering your current password and the new one twice. * Google: shows Linked when your Google account is connected. Select Use Google to connect it. ### Communication [#communication] The Channels table turns notification delivery on or off for In-app, Email and Push. WhatsApp is marked Coming soon. See [Notifications](/product/notifications) for what each channel delivers today. ### Delete your account [#delete-your-account] At the bottom of the Info tab, select Delete account and type the confirmation text. Deleting your account removes your access to every organization. While you are the owner of an organization, transfer its ownership or delete the organization first. See [Organizations](/product/organizations). ## Automation and AI [#automation-and-ai] The assistant can read your contact details and update your profile, contact details and contact methods. Changes ask for approval based on their risk. See [Chat](/product/chat). ## API [#api] | Method and path | Purpose | | -------------------------------------------- | ------------------------------------------------------- | | `GET /api/v1/users/me` | Read the signed-in user. Also available as an MCP tool. | | `PATCH /api/v1/users/me/active-organization` | Set your preferred organization. | See [API](/developers/api), [Authentication](/developers/authentication) and [MCP](/developers/mcp). ## Related pages [#related-pages] * [Notifications](/product/notifications) * [Organizations](/product/organizations) * [Access control](/product/access-control) # Quotes (/product/quotes) How quotes work at /quotes, from the pipeline board and versioned sending to customer acceptance, deposits, PDFs, and turning a quote into an invoice, order or task. ## Overview [#overview] Quotes are priced offers to a customer. They live under **Finance > Quotes** at `/{orgSlug}/quotes`, which has two tabs: * **Pipeline** (`/quotes`): a board with one column per status. Open quotes always show. Accepted, rejected, expired and withdrawn quotes default to those closed since last month; switch the period to All time to see older ones. * **All quotes** (`/quotes/all`): a table with search, filters, sorting and bulk actions. A drafts-only table is also available at `/quotes/drafts`. New quotes start at `/quotes/create`, and a quote opens at `/quotes/[quoteId]`, where the ID in the URL is the quote number. The quote detail page is a branded document preview with the letterhead, dates, lines, totals and closing text. The email message, internal notes and linked records sit below the document. ## Key concepts [#key-concepts] ### Statuses [#statuses] | Status | Meaning | | ----------------- | --------------------------------------------------------------------- | | Draft | Not sent yet. Everything stays editable, including the customer. | | Sent | Published to the customer, who can accept, reject or request changes. | | Changes requested | The customer asked for changes with a comment. | | Expired | The valid-until date passed without a decision. | | Accepted | The customer or staff accepted the quote. It is locked. | | Rejected | The customer declined with a reason, or staff marked it rejected. | | Withdrawn | Staff withdrew a live quote so the customer can no longer accept it. | On the board you can drag a draft to Sent, a sent quote to Accepted or Rejected, and an expired quote back to Sent or to Rejected. Accepted, rejected and withdrawn quotes do not move. ### Versions [#versions] Sending publishes version 1 on the same quote number and URL. After that, edits stay private until you select **Send new version**, which publishes the next version. Customers see the version history (v1, v2 and so on), with the latest published version as current. The customer is locked once the quote is sent. Accepting an older version than the latest published one is refused. ### Lines and pricing [#lines-and-pricing] A quote has a title, introduction, closing text and lines. Line types are section headers, products, time, fixed price, subscription (weekly, monthly, quarterly or yearly, with a number of billing periods) and custom lines. A document discount can be a percentage or a fixed amount, and a [promotion](/product/orders) can supply it. Prices can be shown including or excluding VAT. VAT rates come from the organization's presets plus Custom, and totals show VAT per rate. The VAT regime can be standard, reverse charge, exempt, KOR or margin scheme. Reverse charge requires the customer's VAT number before sending. ### Options [#options] The **Options** panel holds the workflow, validity, currency, VAT and deposit settings: * **Valid until** comes from the template's default validity in days. Sending needs a valid-until date at least two days from now. * **Deposit** can be none, a percentage of the total, or a fixed amount. With **Start the work only after the deposit is paid**, the quote only counts as accepted once the customer has paid the deposit. Quote templates add **Create an order when accepted**, which turns the accepted lines of quotes from that template into a confirmed sales order, so stock is reserved and fulfillment can start. ### Lifecycle [#lifecycle] The **Quote progress** strip shows the lifecycle stages. The default quote lifecycle runs Sent, Viewed, Accepted and Invoiced, with branch stages for Changes requested, Deposit paid, Rejected, Expired and Withdrawn. It sends the quote email, records the first customer view, reminds the customer before expiry, marks the quote expired, and rejects an expired quote automatically after 30 days unless a new version is sent. Stages that the lifecycle marks as manual can be moved by hand. Templates can point a quote at a different lifecycle. See [Content](/product/content). ## Create a quote [#create-a-quote] 1. Open **Quotes** and select **New quote**, or open **More create options** and choose **From template**. 2. Select the customer, or type a name to create one. Pick an optional contact. 3. Edit the title, introduction, lines and closing on the document, and set dates and options. 4. Write the email message that goes with the quote. It stays off the PDF. 5. Save the draft. Creating a quote from a customer or asset page prefills that customer and returns you to the page after saving. ## Send a quote [#send-a-quote] Select **Send** on a draft. Sending checks that the customer has a billing or contact email, a valid VAT number when reverse charge applies, and a valid-until date far enough ahead. The send email attaches the quote PDF, plus the terms and conditions PDF when the template sets one. When an approval policy applies, sending notifies the approvers and you send again once the quote is approved. See [Approvals](/product/approvals). Sending holds stock for tracked product lines until the quote expires, is rejected or withdrawn, or you select **Release stock**. A shortfall never blocks sending; the dialog lists the lines that are short. See [Products](/product/products). You can also select several quotes in **All quotes** and send them together. ## Handle the customer's response [#handle-the-customers-response] The customer opens the quote at `/{orgSlug}/portal/quotes/[quoteId]`, including as a guest, and can download the PDF there. From the portal they can: * **Accept**, or **Accept and pay deposit** when the quote has a deposit. * **Reject** with a reason. * **Request changes** with a comment describing what should change. Staff can also select **Mark accepted** or **Mark rejected** with an optional reason. To revise a quote after changes were requested, or after it was rejected, expired or withdrawn, edit it and select **Send new version**. To stop a sent or expired quote from being accepted, select **Withdraw**. See [Customer portal](/product/customer-portal) for the customer's view. ## After acceptance [#after-acceptance] Accepting a quote emails the customer, notifies the team, creates a draft invoice and opens a follow-up task. It never sends the invoice. With a deposit, accepting creates the deposit invoice, and the final invoice deducts the deposit per VAT rate. When the quote template has **Create an order when accepted** switched on, a confirmed sales order is created instead and invoicing follows the order's policy. On an accepted quote you can also: * **Create task**, once per quote, for the work the quote agreed. * **Create invoice**, which opens a new invoice prefilled from the quote. See [Invoices](/product/invoices). * **Create order** or **Open order**. See [Orders](/product/orders). ## Notes and linked records [#notes-and-linked-records] Notes are an internal thread that stays off the PDF and the customer-facing quote. Mention users and teams with `@`, or work and customer records with `#`. Notes stay editable after sending for anyone who can update the quote. The **Tasks and invoices** section shows the work that carries out the quote and what has been billed for it. Use **Link tasks** to connect existing tasks. Customer assets can be linked to a quote through the API or from the asset on the customer record. ## Other actions [#other-actions] The **Other** menu and the row actions offer **Show online** (the customer view), **Duplicate**, **Download PDF** and **Delete**. Deleting a draft removes it permanently; other quotes are hidden from lists. Bulk actions in **All quotes** are send, mark accepted, mark rejected and delete. Export the filtered list as CSV, Excel or PDFs. ## Permissions [#permissions] | Key | Grants | | ----------------------------------- | -------------------------------------------------------------------------------- | | `organization.quotes.view` | See quotes and download PDFs | | `organization.quotes.create` | Create quotes | | `organization.quotes.update` | Edit quotes, notes, linked tasks and stock holds, and create a task from a quote | | `organization.quotes.delete` | Delete or hide quotes | | `organization.quotes.send` | Send a quote or a new version | | `organization.quotes.mark-accepted` | Mark a quote accepted | | `organization.quotes.mark-rejected` | Mark a quote rejected | | `organization.quotes.withdraw` | Withdraw a sent or expired quote | | `organization.quotes.duplicate` | Duplicate a quote | | `organization.quotes.approve` | Decide on quote approval requests | Creating a task from a quote also needs `organization.tasks.create`, and creating a customer inline needs `organization.customers.create`. See [Access control](/product/access-control). ## Automation and AI [#automation-and-ai] * Quote events such as `quote.sent`, `quote.viewed`, `quote.accepted`, `quote.rejected`, `quote.changes-requested`, `quote.expired`, `quote.withdrawn` and `quote.deposit-paid` can trigger [workflows](/product/workflows) and outgoing [webhooks](/developers/webhooks). * Starter automations for quote templates include **Big-ticket quote** (tell the team when a quote over 10,000 goes out) and **Viewed, not accepted** (follow up when a viewed quote has no decision after 3 days). * The assistant can create, update, send, accept, reject, duplicate and archive quotes, manage notes and links, and move lifecycle stages. See [Chat](/product/chat). * MCP tools: `listQuotes`, `getQuote`, `createQuote` and `updateQuote`. See [MCP](/developers/mcp). ## API [#api] All paths are under `/api/v1/organizations/{organizationId}`. | Resource | Operations | | ---------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | | `/quotes` | [List](/openapi/listQuotes), [create](/openapi/createQuote), [get](/openapi/getQuote), [update](/openapi/updateQuote), [delete](/openapi/deleteQuote) | | `/quotes/{quoteId}/send` | [Send](/openapi/sendQuote) | | `/quotes/{quoteId}/accept` and `/reject` | [Accept](/openapi/acceptQuote) and [reject](/openapi/rejectQuote) | | `/quotes/{quoteId}/duplicate` | [Duplicate](/openapi/duplicateQuote) | | `/quotes/{quoteId}/notes` | [List](/openapi/listQuoteNotes), [add](/openapi/addQuoteNote), [update](/openapi/updateQuoteNote), [delete](/openapi/deleteQuoteNote) | | `/quotes/{quoteId}/tasks` and `/assets` | [Set linked tasks](/openapi/setQuoteTasks) and [set linked assets](/openapi/setQuoteAssets) | | `/quotes/{quoteId}/lifecycle` | [Get lifecycle](/openapi/getQuoteLifecycle) and [advance a stage](/openapi/advanceQuoteLifecycle) | The customer's change request goes through the public portal endpoint [`requestPortalQuoteChanges`](/openapi/requestPortalQuoteChanges). ## Related pages [#related-pages] * [Invoices](/product/invoices) * [Orders](/product/orders) * [Products](/product/products) * [Content](/product/content) for quote templates and lifecycles * [Customer portal](/product/customer-portal) * [Approvals](/product/approvals) * [Customers](/product/customers) and [Tasks](/product/tasks) # Settings and branding (/product/settings-and-branding) The organization settings page, covering general details, company information, social links, finance defaults, email addresses, export formats, lifecycles, logo, colors and brand images. ## Overview [#overview] Organization settings live at `/{orgSlug}/settings`, under Organization, Account, Settings in the sidebar. One page holds every organization-wide default, in sections you save one at a time. The organization's AI settings have their own page at `/{orgSlug}/ai/settings`. See [Chat](/product/chat). ## Key concepts [#key-concepts] Settings belong to the organization, not to a team or a user. Teams are collaboration groups and do not carry their own finance or inbox defaults. Logos and brand images are stored as uploaded files and shown wherever the organization appears, such as the sidebar, quote and invoice documents, emails and the [customer portal](/product/customer-portal). ## Settings sections [#settings-sections] ### General [#general] Name, slug and website. Changing the slug changes every URL of the organization, and old links stop working. ### Company information [#company-information] Address, Chamber of Commerce (KVK) number, VAT number (BTW), IBAN and phone. These details are printed on invoices and official correspondence. ### Socials [#socials] Facebook, Instagram, LinkedIn, X and YouTube links. They are shown to users in the portal. ### Finance [#finance] | Setting | Purpose | | ------------------------------------- | ------------------------------------------------------------------ | | Base currency | The currency for quotes, invoices and reports. | | Default hourly rate | Used for time and labor lines. | | Quote validity days | How long a new quote stays valid. | | Fiscal year start | The month and day the fiscal year starts. | | Task, quote and invoice number prefix | The prefix of each document number. | | Invoice number format | The pattern for invoice numbers. | | VAT rates | The rates available on documents. Exactly one rate is the default. | ### Email [#email] The Inbox address, the Forwarding address and the Reply-to address that the [Inbox](/product/inbox) uses for customer email. ### Other settings [#other-settings] Operating country, default export format (CSV or Excel workbook) and export date format (ISO or Localized). ### Lifecycles [#lifecycles] The lifecycle used for invoices, quotes, tasks, orders, purchase orders and shipments. Each one uses the Platform default unless you choose another lifecycle. ### Branding [#branding] | Item | Rules | | --------------- | ----------------------------------- | | Logo | PNG, JPEG or WebP, up to 5 MB. | | Primary color | Hex color. | | Secondary color | Hex color. | | Brand images | PNG, JPEG or WebP, up to 8 MB each. | Organizations have a primary and a secondary color only. Each uploaded image is stored once, and the app resizes it when it is shown. ### Ownership and deletion [#ownership-and-deletion] The last section has Transfer ownership and Delete organization. See [Organizations](/product/organizations). ## Edit settings [#edit-settings] 1. Open Settings in the sidebar. 2. Change the fields in a section. 3. Save that section. Other sections keep their saved values. When you leave the KVK or VAT number field, CentraKit looks the number up in the official registry (KvK for KVK numbers, VIES for VAT numbers) and shows Verified or Not found next to the field. The check never blocks saving. ## Permissions [#permissions] | Permission | Allows | | ----------------------------------------- | ------------------------------------------------------------ | | `organization.general.update` | Open and edit the settings page, including branding. | | `organization.branding.manage` | Upload and remove the logo and brand images through the API. | | `organization.general.transfer-ownership` | Transfer ownership. | | `organization.general.delete` | Delete the organization. | ## Automation and AI [#automation-and-ai] The assistant can read the organization and propose changes to its settings. Every change asks for approval first. See [Chat](/product/chat). ## API [#api] | Method and path | Purpose | | ------------------------------------------------------------------------------------ | ---------------------------------------- | | `GET /api/v1/organizations/{organizationId}` | Read the organization with its settings. | | `PATCH /api/v1/organizations/{organizationId}` | Update settings. | | `POST /api/v1/organizations/{organizationId}/branding/logo` | Upload the logo. | | `DELETE /api/v1/organizations/{organizationId}/branding/logo` | Remove the logo. | | `POST /api/v1/organizations/{organizationId}/branding/brand-images` | Upload a brand image. | | `DELETE /api/v1/organizations/{organizationId}/branding/brand-images/{brandImageId}` | Remove a brand image. | See [API](/developers/api) and [OpenAPI](/developers/openapi). ## Related pages [#related-pages] * [Organizations](/product/organizations) * [Quotes](/product/quotes) * [Invoices](/product/invoices) * [Content](/product/content) * [Customer portal](/product/customer-portal) # Tasks (/product/tasks) How to plan, track, bill and finish work with tasks, including the board, subtasks, materials, time entries, comments and the task lifecycle. ## Overview [#overview] Tasks are the organization's units of work. A task can belong to a customer and one of their assets, link to quotes and invoices, carry the materials and hours spent on it, and end in an invoice through the finish flow. Tasks live under Workspace, Work, Tasks in the sidebar. The routes are: | Route | Purpose | | --------------------------- | ------------------------------------------------- | | `/{orgSlug}/tasks` | Board: a Kanban board with one column per status. | | `/{orgSlug}/tasks/all` | All tasks: a searchable, filterable table. | | `/{orgSlug}/tasks/mine` | My tasks: open tasks assigned to you. | | `/{orgSlug}/tasks/create` | Create a task. | | `/{orgSlug}/tasks/{taskId}` | Task detail. | ## Key concepts [#key-concepts] ### Status and priority [#status-and-priority] | Status | Meaning | | ----------- | ---------------------------------------------------- | | Todo | Not started. | | In progress | Work has started. | | On hold | Paused. Putting a task on hold pauses its lifecycle. | | Done | Finished. | Priority is Low, Medium, High or Urgent. Lists sort open work before done work, then by nearest due date and highest priority, unless you sort on a column yourself. Tasks can also be archived. Archived tasks stay available for reference but disappear from active lists. The All tasks filters can include done and archived tasks again. ### Task fields [#task-fields] * Title, description, status, priority and tags. * Customer and, optionally, one of that customer's assets. You can create a customer or asset inline from the pickers. * Assignees, start and end time, and due date. The form warns when the planned end is after the due date. * Required skills, which Planning uses to flag people who lack them. * Linked quotes and invoices for the same customer. Each task gets a per-organization number, shown as `#{number}`. ### Subtasks [#subtasks] Subtasks are child tasks of a root task. They have their own status and can be reordered, edited and deleted from the Subtasks section. Boards and lists show root tasks only, with a `{done}/{total} done` progress count. Generate subtasks asks AI for suggestions based on the task, and you accept or discard each one. ### Materials [#materials] Materials are products used on the task. Each line has a quantity, a unit that follows the product, a read-only unit price, a billable flag and a stock status: | Stock status | When it applies | | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------- | | Reserved | The product tracks stock and the quantity is held for this task. Reserving more than is available is allowed; the shortfall is backordered. | | Consumed | The quantity has been taken out of stock. | | Untracked | The product does not track stock. | A new material starts as Reserved for tracked products and Untracked for others. Removing a material releases any reserved stock. When the task links a sent or accepted quote, you can add the quote's products and take over the stock the quote still holds. The section flags products that are over the quoted quantity or not on the quote, and offers Order at supplier for products with a preferred supplier. Suggest materials asks AI to propose lines from your product catalog. See [Products](/product/products) for stock tracking and [Quotes](/product/quotes) for quote holds. ### Time entries [#time-entries] The Time tracking section logs hours and minutes per user with an optional note and a billable flag. Time reserved on [Planning](/product/agenda) appears as a draft entry until you confirm or dismiss it. When a quote is linked, the section compares logged hours with quoted hours. Task time also appears in [Time](/product/time). Time entries and materials are billed per line. Once an invoice bills a line, it is locked and shows a billed icon that names the invoice. ### Comments [#comments] Comments use a rich text composer. Type `@` to mention users or teams and `#` to reference work and customer records. Mentioned users, and active members of mentioned teams, receive a notification. See [Notifications](/product/notifications). ## Work with tasks [#work-with-tasks] ### Create a task [#create-a-task] Select New task on the board or the All tasks page, or pick a task template from the From template menu next to it. Templates are managed under [Content](/product/content). Assistants and integrations can also create tasks through the API and MCP. ### Use the board [#use-the-board] The board shows every open task plus tasks completed since the start of last month. Switch the period to All time to see older completed work. Drag a card to another column to change its status, or within a column to change its manual order. Select several cards to change status, priority, assignees or tags in bulk, or to archive or delete them. ### Finish a task [#finish-a-task] Select Finish task on the detail page, or drag the card to Done on the board. Both open the same checks dialog: * Hours logged. * Materials added. * All subtasks done. * A customer linked, which is required to invoice. * Invoices already linked. Depending on what is still unbilled, the dialog offers one of these actions: * Finish and create invoice opens a new draft invoice for the customer with the unbilled hours and materials of the task and its done subtasks. * Finish and add to invoice appends them to a linked draft invoice. * Finish and invoice remaining items bills what linked sent invoices left over. * Finish anyway closes the task without invoicing. Finishing marks the task Done and consumes its materials. When a quote that was invoiced has no open tasks left, it is archived off the quotes board. See [Invoices](/product/invoices). ### Archive or delete [#archive-or-delete] The Danger zone section archives a task or deletes it permanently with its assignees and comments. Deleting a subtask also deletes its related data. ## Permissions [#permissions] | Key | Grants | | ---------------------------- | --------------------------------------------------------------------------------------------- | | `organization.tasks.view` | View the board, lists, task detail, comments and the lifecycle. | | `organization.tasks.create` | Create tasks. | | `organization.tasks.update` | Edit tasks, status, order, assignees, subtasks, materials and time entries, and finish tasks. | | `organization.tasks.delete` | Archive and delete tasks. | | `organization.tasks.comment` | Add, edit and delete comments. | Finishing with a new invoice also requires `organization.invoices.create`, and appending to a draft requires `organization.invoices.update`. Creating a customer or asset from the task form requires `organization.customers.create` or `organization.assets.create`. See [Access control](/product/access-control). ## Automation and AI [#automation-and-ai] Every root task runs the Task lifecycle from the moment it is created. It tracks In progress, On hold and Done, and the progress shows in the status section on the detail page. The task Options popover shows the lifecycle workflow, which you can change only while the task is Todo. See [Workflows](/product/workflows). Automatic workflows email assignees when they are added to a task, remind them when a task is due within 24 hours, and remind assignees and the reporter when it is overdue. MCP tools for tasks: | Tool | Kind | | --------------- | ----- | | `listTasks` | Read | | `getTask` | Read | | `createTask` | Write | | `updateTask` | Write | | `setTaskStatus` | Write | AI also powers Generate subtasks and Suggest materials on the detail page. See [MCP](/developers/mcp) and [AI agents](/developers/ai-agents). ## API [#api] All paths are relative to `/api/v1/organizations/{organizationId}`. | Resource | Operations | | --------------------------------------- | ------------------------------------------------------------ | | `tasks` | `listTasks` (GET), `createTask` (POST) | | `tasks/{taskId}` | `getTask` (GET), `updateTask` (PATCH), `deleteTask` (DELETE) | | `tasks/{taskId}/status` | `setTaskStatus` (PATCH) | | `tasks/{taskId}/assignees` | `setTaskAssignees` (PUT) | | `tasks/{taskId}/comments` | `listTaskComments` (GET), `addTaskComment` (POST) | | `tasks/{taskId}/comments/{commentId}` | `updateTaskComment` (PATCH), `deleteTaskComment` (DELETE) | | `tasks/{taskId}/time-entries` | `listTaskTimeEntries` (GET), `addTaskTimeEntry` (POST) | | `tasks/{taskId}/time-entries/{entryId}` | `deleteTaskTimeEntry` (DELETE) | | `tasks/{taskId}/lifecycle` | `getTaskLifecycle` (GET) | | `tasks/{taskId}/lifecycle/advance` | `advanceTaskLifecycle` (POST) | | `tasks/reorder` | `reorderTasks` (POST) | | `quotes/{quoteId}/tasks` | `setQuoteTasks` (PUT) | `listTasks` filters by status, priority, due date bucket, assignee, customer and tag, and supports `q`, `limit` and `offset`. Materials and subtask editing are not exposed through the REST API. See [API](/developers/api) and [OpenAPI](/developers/openapi) for request and response schemas. ## Related pages [#related-pages] * [Agenda](/product/agenda) for Planning and scheduled work. * [Quotes](/product/quotes) and [Invoices](/product/invoices) for the commercial side of a task. * [Customers](/product/customers) for customers and their assets. * [Workflows](/product/workflows) for the Task lifecycle. # Time (/product/time) How to log hours with the weekly timesheet, timer and clock in and out, submit and approve timesheets, request and approve time off, report sick, and set up leave types, policies and blackout periods. ## Overview [#overview] Time covers the hours people work and the hours they are away. Employees log work on a weekly timesheet, run a timer, clock in and out, request time off and report sick. Managers approve timesheets and leave for the people who report to them. Everything is counted in hours against each employee's working hours from [People](/product/people). Time lives under Workspace, People, Time in the sidebar. | Route | Purpose | | ------------------------------------------ | -------------------------------------------------------------------------- | | `/{orgSlug}/time` | My timesheet tab: your week, timer, attendance and suggestions. | | `/{orgSlug}/time/team` | Team tab: timesheets awaiting your approval and the team week. | | `/{orgSlug}/time/time-off` | My time off tab: your balances and requests. | | `/{orgSlug}/time/time-off/calendar` | Team calendar of approved and pending leave. | | `/{orgSlug}/time/time-off/requests` | Requests tab: leave awaiting your decision, sick reports and all requests. | | `/{orgSlug}/time/time-off/new` | Request time off. | | `/{orgSlug}/time/time-off/{requestId}` | One time-off request. | | `/{orgSlug}/time/timesheets/{timesheetId}` | One employee's timesheet week. | You need an employee profile to track time or request time off. ## Key concepts [#key-concepts] ### Time entries [#time-entries] A time entry is a number of hours on a day, logged against a task, a customer or general internal work. Entries come from the timesheet grid, the timer, agenda and planning items, or accepted suggestions. Billable time is rounded according to the rounding rule in HR settings. ### Timesheets [#timesheets] A timesheet is one employee's week. Its status is Open, Submitted, Approved or Rejected. The week shows logged hours against expected hours from the working schedule, the running overtime balance and clocked attendance hours. Submitted and approved weeks are locked, so new entries for those days are rejected. When timesheet approval is off in HR settings, employees close their week instead of submitting it. ### Time off [#time-off] Time off is booked in hours from leave types and policies: | Concept | Meaning | | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Leave type | A kind of leave, such as vacation, sick leave, special leave, unpaid leave, parental leave or compensatory time. A type is paid or unpaid and does or does not use a balance. | | Leave policy | A yearly entitlement in hours for a leave type, granted up front or monthly. Part-timers get a prorated share from their contract hours. Carried-over hours can expire after a set number of months. | | Balance | Entitled, taken, planned and awaiting-approval hours per leave type, with a ledger of every grant, accrual, booking, correction, carry-over and expiry. | | Blackout period | A busy period, for all teams or one team, that warns about or blocks leave requests. | A request is Pending, Approved, Rejected or Cancelled. Colleagues only see that someone is absent, never the reason. ### Sick leave [#sick-leave] Reporting sick opens a sick report with the start date and an expected return date. A report is Sick, Partially back (with the percentage worked) or closed when the person is fully back. Long reports are marked long-term. ## Track time [#track-time] ### Fill in your timesheet [#fill-in-your-timesheet] 1. Open Time. The My timesheet tab shows the current week. 2. Select **Add a task row** to add a task, or use the General row for internal work. 3. Type hours into each day. The footer shows logged against expected hours per day. 4. Select **Submit for approval** when the week is complete, or **Close week** when approval is off. Use **Withdraw** to pull back a submitted week before it is decided. A rejected week is sent back for changes. ### Use the timer [#use-the-timer] Start the timer from the Timer card, from the top bar, or from a task. Pick a task or describe the work, then stop the timer to turn the elapsed time into a time entry. Forgotten timers stop automatically after the number of minutes set in HR settings. ### Clock in and out [#clock-in-and-out] When clock in and out is on, the Attendance card records the start and end of the working day. With location recording on, a coarse location is stored and employees are told about it. A missing clock-out is closed at the scheduled end of the day. ### Accept suggestions [#accept-suggestions] "Suggested from your week" lists hours from your agenda and planning. Untick anything you did not work on and add the rest to your timesheet, or dismiss them all. ### Approve timesheets [#approve-timesheets] The Team tab lists timesheets awaiting your approval and the status of everyone in your team this week. Select several and choose **Approve** or **Deny**, or open one week to review it line by line. A decided week stays locked until it is reopened with **Reopen** on the timesheet page. Task pages show hours logged against the hours quoted on linked quotes, with a warning at the budget percentage from HR settings. See [Tasks](/product/tasks). ## Take time off [#take-time-off] ### Request time off [#request-time-off] 1. Select **Request time off** on the My time off tab. 2. Choose the leave type and the first and last day. Each end can be a full day, morning, afternoon or specific hours. 3. Add an optional note for your manager and select **Send request**. The summary shows how many hours the request takes and warns when it falls in a blackout period, when a teammate is also off, when too many people in the team would be away, when planned work overlaps, when you already have leave on those dates or when the balance is too low. People with HR rights can book time off for someone else. Requests go to the employee's manager. Without a manager, they go to people with `organization.time-off.approve`. Whether a request needs approval follows the leave type, or the Leave requires approval setting in HR settings when the type does not override it. Sick leave never needs approval. ### Decide on requests [#decide-on-requests] The Requests tab lists leave awaiting your decision. Select several to approve or deny them at once, or open one request to add a note to the employee. Approvers are also notified in [Notifications](/product/notifications). Cancelling an approved request returns its hours to the balance. ### Report sick and register a return [#report-sick-and-register-a-return] Select **Report sick** on the My time off tab, or report sick for someone else as HR. You can turn booked vacation in the sick period into sick days. On the Requests tab, **Register return** records a full return, which closes the report, or a partial return with the percentage worked. ### Subscribe to the team calendar [#subscribe-to-the-team-calendar] The Team calendar tab shows approved and pending leave per person for a month and can be filtered by team. **Subscribe** creates a private link that you can add to Google Calendar, Outlook or Apple Calendar. Anyone with the link can see the leave, so reset it if it leaks. ## Set up leave [#set-up-leave] Leave types, leave policies and blackout periods live in [HR settings](/product/people) at `/{orgSlug}/people/settings`. **Grant entitlements now** adds the yearly entitlements to balances at once. **Correct balance** on an employee's Time off tab adds or removes hours with a note that is kept in the balance history. ## Permissions [#permissions] | Permission | Allows | | --------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | | `organization.time-tracking.view` | Open Time and log your own hours. | | `organization.time-tracking.create`, `.update`, `.delete` | Work with time entries through the API and AI tools. | | `organization.time-tracking.approve` | Approve and deny timesheets. | | `organization.time-off.view` | Request time off, report sick and see the team calendar. Managers decide requests for their reports. | | `organization.time-off.create`, `.update`, `.delete` | Book, change and cancel time off through the API and AI tools. | | `organization.time-off.approve` | Decide requests for employees without a manager. | | `organization.time-off.manage-policies` | Manage leave types, policies and blackout periods, grant entitlements and correct balances. | Approvals can be delegated. See [Approvals](/product/approvals). ## Automation and AI [#automation-and-ai] * A daily job runs scheduled leave accrual, follows up on sick reports, stops forgotten timers and closes forgotten clock-ins. * The assistant reads balances, leave types, requests, who is off and leave policy passages from the HR handbook Knowledge collection set in HR settings. It can request, change or cancel your own time off and report you sick. * The assistant lists HR approvals waiting for you, including leave, timesheets and expense claims, and decides them one by one after you confirm. See [Chat](/product/chat). ## API [#api] All routes live under `/api/v1/organizations/{organizationId}/`: | Resource | Routes | | ----------------- | -------------------------------------------------------------------------------------------- | | Time entries | `GET` and `POST time-entries` | | Task time entries | `GET` and `POST tasks/{taskId}/time-entries`, `DELETE tasks/{taskId}/time-entries/{entryId}` | | Time-off requests | `GET` and `POST time-off-requests` | | HR approvals | `GET approvals` lists approvals waiting for you, `POST approvals` decides up to 100 at once. | The team leave feed is served from `GET /api/v1/public/time-off/{token}`. See [API](/developers/api) and [OpenAPI](/developers/openapi). ## Related pages [#related-pages] * [People](/product/people) * [Expenses](/product/expenses) * [Approvals](/product/approvals) * [Agenda](/product/agenda) * [Tasks](/product/tasks) # Workflows (/product/workflows) How lifecycles move quotes, invoices, tasks, orders, purchase orders and shipments through their stages, and how automations react to events, schedules and webhooks. ## Overview [#overview] Workflows run work for you. A workflow is a graph of steps that starts from a trigger, such as an event, a schedule or a webhook, and runs in the background. Every run is recorded step by step, so you can see what happened and why. | Route | Purpose | | ------------------------------------------------ | ------------------------------------------------------------------------------- | | `/{orgSlug}/workflows` | List of lifecycles and automations, with search and filters by status and type. | | `/{orgSlug}/workflows/create` | New workflow form. | | `/{orgSlug}/workflows/{workflowId}` | Editor tab with the canvas. | | `/{orgSlug}/workflows/{workflowId}/details` | Details tab with name, description and access. | | `/{orgSlug}/workflows/{workflowId}/runs` | Runs tab with the run history. | | `/{orgSlug}/workflows/{workflowId}/runs/{runId}` | One run with its graph, steps, input and output. | ## Key concepts [#key-concepts] ### Lifecycles and automations [#lifecycles-and-automations] A workflow has one of two purposes: * A **lifecycle** owns the status of a record. It defines the stages a quote, invoice, task, order, purchase order or shipment goes through, and the automatic steps between them, such as sending reminders. * An **automation** reacts to what happens. It runs when its trigger fires and does not own the record status. Platform workflows are marked System and Maintained by the platform. You cannot edit them directly. Select Duplicate to customize to make an organization copy; your copy replaces the platform version once you publish it. When the platform version changes later, the list shows Upstream updated and the editor offers View update. Nothing is merged automatically, so you compare and apply the changes you want. You can also Turn off for this organization any platform automation you do not want, and Turn on for this organization to restore it. ### Lifecycles [#lifecycles] Each lifecycle starts when its record reaches a starting event: | Record | Starts on | Main stages | | -------------- | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Quote | Quote sent | Sent, Viewed, Accepted, Invoiced. Also changes requested, deposit paid, rejected, withdrawn and expired. | | Invoice | Invoice sent | Sent, Viewed, Paid. Also partially paid, overdue, final notice, in collection, cancelled and written off. | | Task | Task created | In progress, Done. On hold is a flag. | | Order | Order confirmed | Confirmed, Paid, Picking, Shipped or Ready for pickup, Delivered or Picked up, Invoiced, Completed. Backordered is a flag; Cancelled and Returned end the lifecycle. | | Purchase order | Purchase order sent | Sent, Confirmed, In transit, Received, Billed. Partially received is a sub-stage, Overdue is a flag and Cancelled ends the lifecycle. | | Shipment | Shipment shipped | In transit, Out for delivery or Ready for pickup, Delivered. Exception is a flag. | The platform invoice lifecycle sends payment reminders 3 days before and 7 and 14 days after the due date, and a final notice 21 days after. The platform quote lifecycle reminds the customer and rejects a quote 30 days after it expires. Emails to customers go out only during business hours, 09:00 to 17:00 on weekdays. A stage is shown as a main step, a sub-stage or a flag. Stages that end the lifecycle are marked Ends the lifecycle. Some stages advance on their own and others wait for a person. ### Which lifecycle a record uses [#which-lifecycle-a-record-uses] A record uses the first lifecycle it finds in this order: 1. The lifecycle chosen on the record itself, for tasks, quotes and invoices. 2. The lifecycle chosen on its template. 3. The organization default, set with Set as default in the workflow list or in the Lifecycles section of the organization settings. 4. The platform default. A running lifecycle can be paused, either indefinitely or until a date, with an optional reason. Delays and customer follow-ups wait while it is paused. Putting a task on hold pauses its lifecycle, and leaving hold resumes it. ### Templates and automations [#templates-and-automations] Open a template in [Content](/product/content) and choose its Workflow tab to connect it to workflows: * **Lifecycle** shows the lifecycle that records from this template follow and where it comes from (chosen for this template, organization default or platform default). Customize copies it for editing, and Open in full editor opens the workflow. * **Payment reminders** on invoice templates lets you adjust the reminder days. Saving publishes the lifecycle change. * **Automations** lists automations attached to this template. Attach existing, Add from starter or New automation add one, and Detach removes it. Under Also applies you see automations that run for every template, and you can switch one off for this template. Starter automations are ready-made automations you add from a template: | Starter | What it does | | ------------------------ | ----------------------------------------------------------------------------------------------------- | | Big-ticket quote | Tells the team when a quote over 10,000 goes out. | | Viewed, not accepted | Reminds the team to follow up when a quote was viewed but not decided in 3 days. | | B2C 14-day letter | Sends individual customers the 14-day letter once an invoice is overdue. | | Payment failed | Flags the invoice and alerts the team when an online payment fails. | | Low stock reorder | Drafts a purchase order when stock runs low and sends it to the supplier. Runs for all products. | | Purchase order overdue | Reminds the supplier and tells the purchaser when a purchase order is late. | | Back in stock | Fills backorders when stock arrives and tells customers their order is complete. Runs for all orders. | | Prepaid order to picking | Releases a prepaid order to picking as soon as it is paid. | | Ready for pickup | Messages the customer when an order is ready for pickup, and reminds them after 7 days. | | Delivered, then invoice | Invoices what was delivered and sends it, for orders invoiced on fulfillment. | ### Platform automations [#platform-automations] The platform also runs automations for every organization. Among them: confirming an accepted quote with a draft invoice and a follow-up task, notifying assignees when a task is assigned, due soon or overdue, following up on agenda items, welcoming new customers, flagging inbox conversations that are unanswered after 4 hours, weekly digests of overdue invoices and stale drafts, looking up customer details in the KvK register, VIES and on the web, reading product details from a photo, and alerting you when a workflow fails. ### Steps [#steps] The editor palette groups steps as follows: | Group | Steps | | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | | Triggers | Domain event, schedule, manual, workflow failed and webhook. | | Flow | Condition, delay, merge, stage, clear flag and wait for event. | | Actions | Send email, AI agent, call tool, HTTP request, webhook, approval and start workflow. | | Domain actions | Record-specific steps such as rendering a PDF, sending a reminder, creating an invoice or order, notifying assignees or looking up a company. | An approval step pauses the run until people decide. It can require any one approver, all approvers or a fixed order. ### Status and runs [#status-and-runs] A workflow is Draft, Published or Archived. Only published workflows run, and only published workflows can be archived. A run is Queued, Running, Waiting, Completed, Failed or Cancelled. Runs start from a trigger, by hand, from the API or MCP, or from a channel message. ## Create and edit a workflow [#create-and-edit-a-workflow] 1. Open Workflows and select New workflow. 2. Enter a name, a description and a key, then create it. 3. On the Editor tab, drag a trigger from the palette onto the canvas and connect steps after it. Select a step to configure it. 4. Changes save as you go. Undo, Redo, Copy, Paste, Tidy up, Import and Export are in the toolbar, and Show stages only hides everything but stages. 5. Select Publish. Problems are listed under Fix these before publishing. If someone else changes the workflow at the same time, you can Keep my changes or Load server version. To edit with the assistant, open the side panel on a workflow and describe the change. The assistant proposes graph changes that you review before they are applied. On the Details tab, Access controls who can use the workflow. Automatic means anyone with workflow permissions in this organization can view and run it. Restricted means only the teams and users you grant. ## Test and run [#test-and-run] * **Test run** runs the saved graph with every action simulated. Nothing is sent and no records change. Choose where to start from and enter a JSON trigger payload. Test runs are not billed. * **Run now** on the Runs tab starts a manual run. * **Cancel run** stops a queued, running or waiting run. It cannot be resumed. The run detail page shows the graph, the list of steps with their status and attempts, and an inspector with the input and output of the selected step. When a run waits on an approval, approvers see Approve and Deny with an optional comment. ## Permissions [#permissions] | Key | Grants | | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | | `organization.workflows.view` | View workflows, runs and run status. | | `organization.workflows.create` | Create and duplicate workflows, and customize platform workflows. | | `organization.workflows.update` | Edit details, access and the graph, publish, archive, turn platform automations off or on and set the default lifecycle. | | `organization.workflows.delete` | Delete workflows and their run history. | | `organization.workflows.run` | Start manual and test runs, cancel runs and decide approvals. | Adding a starter automation to a template also needs `organization.templates.update`. Platform administrators manage platform workflows with `system.workflows.manage`; see [Admin workflows](/admin/workflows). See [Access control](/product/access-control) for roles. ## Automation and AI [#automation-and-ai] The assistant can read workflows and runs and start runs through the same tools as MCP: | MCP tool | Access | Purpose | | ------------------------- | ------ | -------------------------------- | | `listWorkflowDefinitions` | Read | List workflows. | | `getWorkflowDefinition` | Read | Get one workflow with its graph. | | `listWorkflowRuns` | Read | List runs. | | `getWorkflowRun` | Read | Get one run with its steps. | | `startWorkflowRun` | Write | Start a run. | Workflow steps count toward the `workflow.steps` usage meter. See [Billing and usage](/product/billing-and-usage). ## API [#api] Paths are relative to `/api/v1/organizations/{organizationId}`. | Method | Path | Operation | Permission | | ------ | ----------------------------------------------- | --------------------------- | ------------------------------- | | GET | `/workflows/definitions` | `listWorkflowDefinitions` | `organization.workflows.view` | | GET | `/workflows/definitions/{definitionId}` | `getWorkflowDefinition` | `organization.workflows.view` | | POST | `/workflows/definitions/{definitionId}/publish` | `publishWorkflowDefinition` | `organization.workflows.update` | | POST | `/workflows/definitions/{definitionId}/runs` | `startWorkflowRun` | `organization.workflows.run` | | GET | `/workflows/runs` | `listWorkflowRuns` | `organization.workflows.view` | | GET | `/workflows/runs/{runId}` | `getWorkflowRun` | `organization.workflows.view` | | POST | `/workflows/runs/{runId}/cancel` | `cancelWorkflowRun` | `organization.workflows.run` | | POST | `/workflows/runs/{runId}/approval` | `decideWorkflowRunApproval` | `organization.workflows.run` | Tasks, quotes, invoices and orders also have `GET .../lifecycle` and `POST .../lifecycle/advance` routes on the record. Purchase orders and shipments do not have lifecycle routes yet. Incoming webhooks start workflows from outside; see [Integrations](/product/integrations). See [OpenAPI](/developers/openapi) for schemas. ## Related pages [#related-pages] * [Operations workflows](/operations/workflows) for the runtime, scheduler and retries. * [Integrations](/product/integrations) for webhook endpoints and destinations. * [Quotes](/product/quotes), [Invoices](/product/invoices), [Tasks](/product/tasks) and [Orders](/product/orders). * [Approvals](/product/approvals) for approval requests. # Start Google Calendar OAuth. (/openapi/startGoogleCalendarOAuth) Authorize an organization integration through Vercel Connect. ## GET /v1/agenda/google-calendar/oauth/start ```json { "operationId": "startGoogleCalendarOAuth", "summary": "Start Google Calendar OAuth.", "description": "Authorize an organization integration through Vercel Connect.", "tags": [ "Agenda" ], "parameters": [ { "in": "query", "name": "orgSlug", "required": true, "schema": { "$ref": "#/components/schemas/organizationSlugSchema" }, "example": "example" } ], "responses": { "302": { "description": "Redirects to Vercel Connect / Google consent or the integration settings page.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/googleCalendarOAuthRedirectResponseSchema" } } } } } } ``` ## Referenced schemas ```json { "googleCalendarOAuthRedirectResponseSchema": { "type": "string", "format": "uri" }, "organizationSlugSchema": { "type": "string", "minLength": 2, "maxLength": 64, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" } } ``` # List agenda items. (/openapi/listAgendaItems) Return agenda items that overlap the requested range and match the optional filters. ## GET /v1/organizations/{organizationId}/agenda/items ```json { "operationId": "listAgendaItems", "summary": "List agenda items.", "description": "Return agenda items that overlap the requested range and match the optional filters.", "tags": [ "Agenda" ], "parameters": [ { "in": "query", "name": "startAt", "required": true, "schema": { "$ref": "#/components/schemas/agendaIsoDateTime" }, "example": "example" }, { "in": "query", "name": "endAt", "required": true, "schema": { "$ref": "#/components/schemas/agendaIsoDateTime" }, "example": "example" }, { "in": "query", "name": "calendarIds", "required": true, "schema": { "type": "array", "items": { "type": "string", "format": "uuid" }, "maxItems": 100, "default": [] }, "example": "example" }, { "in": "query", "name": "q", "required": false, "schema": { "type": "string", "maxLength": 240 }, "example": "example" }, { "in": "query", "name": "userIds", "required": true, "schema": { "type": "array", "items": { "type": "string", "format": "uuid" }, "maxItems": 100, "default": [] }, "example": "example" }, { "in": "query", "name": "visibility", "required": true, "schema": { "type": "string", "enum": [ "all", "all-day", "timed" ], "default": "all" }, "example": "all" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns matching agenda items.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/agendaItemsHttpResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "agendaAttendeeRoleSchema": { "type": "string", "enum": [ "required", "optional", "resource", "chair" ] }, "agendaAttendeeSchema": { "type": "object", "properties": { "displayName": { "type": [ "string", "null" ], "minLength": 1, "maxLength": 240 }, "email": { "type": [ "string", "null" ], "format": "email" }, "id": { "type": [ "string", "null" ], "format": "uuid" }, "isOptional": { "type": "boolean" }, "isOrganizer": { "type": "boolean" }, "participationStatus": { "$ref": "#/components/schemas/agendaParticipationStatusSchema" }, "role": { "$ref": "#/components/schemas/agendaAttendeeRoleSchema" }, "userId": { "type": [ "string", "null" ], "format": "uuid" }, "customerId": { "type": [ "integer", "null" ] }, "customerContactId": { "type": [ "string", "null" ], "format": "uuid" } }, "required": [ "displayName", "email", "id", "isOptional", "isOrganizer", "participationStatus", "role", "userId", "customerId", "customerContactId" ] }, "agendaBlockSourceSchema": { "type": "string", "enum": [ "item", "google-calendar" ] }, "agendaBlockStateSchema": { "type": "string", "enum": [ "confirmed", "tentative", "cancelled" ] }, "agendaIsoDateTime": { "type": "string", "format": "date-time" }, "agendaItemHttpSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "organizationId": { "type": "string", "format": "uuid" }, "source": { "$ref": "#/components/schemas/agendaBlockSourceSchema" }, "state": { "$ref": "#/components/schemas/agendaBlockStateSchema" }, "visibility": { "$ref": "#/components/schemas/agendaItemVisibilitySchema" }, "ownerUserId": { "type": [ "string", "null" ], "format": "uuid" }, "title": { "type": "string", "minLength": 1 }, "description": { "type": [ "string", "null" ] }, "descriptionHtml": { "$ref": "#/components/schemas/agendaRichDescriptionSchema" }, "location": { "type": [ "string", "null" ] }, "onlineMeetingUrl": { "type": [ "string", "null" ], "format": "uri" }, "transparency": { "$ref": "#/components/schemas/agendaTransparencySchema" }, "startAt": { "$ref": "#/components/schemas/agendaIsoDateTime" }, "endAt": { "$ref": "#/components/schemas/agendaIsoDateTime" }, "isAllDay": { "type": "boolean" }, "taskId": { "type": [ "string", "null" ], "format": "uuid" }, "taskNumber": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 }, "taskDisplayNumber": { "type": [ "string", "null" ], "minLength": 1 }, "taskTitle": { "type": [ "string", "null" ], "minLength": 1 }, "priority": { "anyOf": [ { "$ref": "#/components/schemas/taskPrioritySchema" }, { "type": "null" } ] }, "taskStatus": { "anyOf": [ { "$ref": "#/components/schemas/taskStatusSchema" }, { "type": "null" } ] }, "users": { "type": "array", "items": { "$ref": "#/components/schemas/agendaItemUserSchema" } }, "assignees": { "type": "array", "items": { "$ref": "#/components/schemas/agendaItemUserSchema" } }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "createdAt": { "$ref": "#/components/schemas/agendaIsoDateTime" }, "updatedAt": { "$ref": "#/components/schemas/agendaIsoDateTime" }, "timeZone": { "anyOf": [ { "$ref": "#/components/schemas/agendaTimeZoneSchema" }, { "type": "null" } ] }, "organizerUserId": { "type": [ "string", "null" ], "format": "uuid" }, "organizerEmail": { "type": [ "string", "null" ], "format": "email" }, "organizerDisplayName": { "type": [ "string", "null" ] }, "recurrenceParentItemId": { "type": [ "string", "null" ], "format": "uuid" }, "recurrenceIdAt": { "anyOf": [ { "$ref": "#/components/schemas/agendaIsoDateTime" }, { "type": "null" } ] }, "recurrenceRule": { "anyOf": [ { "$ref": "#/components/schemas/agendaRecurrenceRuleSchema" }, { "type": "null" } ] }, "recurrenceDates": { "type": "array", "items": { "$ref": "#/components/schemas/agendaRecurrenceDateSchema" } }, "reminders": { "type": "array", "items": { "$ref": "#/components/schemas/agendaReminderSchema" } }, "attendees": { "type": "array", "items": { "$ref": "#/components/schemas/agendaAttendeeSchema" } }, "calendarId": { "type": "string", "format": "uuid" }, "isReadOnly": { "type": "boolean" }, "reason": { "anyOf": [ { "$ref": "#/components/schemas/agendaSuggestionReasonSchema" }, { "type": "null" } ] }, "confidence": { "type": [ "number", "null" ], "minimum": 0, "maximum": 1 } }, "required": [ "id", "organizationId", "source", "state", "visibility", "ownerUserId", "title", "description", "descriptionHtml", "location", "onlineMeetingUrl", "transparency", "startAt", "endAt", "isAllDay", "taskId", "taskNumber", "taskDisplayNumber", "taskTitle", "priority", "taskStatus", "users", "assignees", "createdBy", "createdAt", "updatedAt", "timeZone", "organizerUserId", "organizerEmail", "organizerDisplayName", "recurrenceParentItemId", "recurrenceIdAt", "recurrenceRule", "recurrenceDates", "reminders", "attendees", "calendarId", "isReadOnly", "reason", "confidence" ] }, "agendaItemUserSchema": { "$ref": "#/components/schemas/taskAssigneeSchema" }, "agendaItemVisibilitySchema": { "type": "string", "enum": [ "public", "private" ] }, "agendaItemsHttpResponseSchema": { "type": "array", "items": { "$ref": "#/components/schemas/agendaItemHttpSchema" } }, "agendaParticipationStatusSchema": { "type": "string", "enum": [ "needs-action", "accepted", "declined", "tentative", "delegated" ] }, "agendaRecurrenceDateKindSchema": { "type": "string", "enum": [ "rdate", "exdate" ] }, "agendaRecurrenceDateSchema": { "type": "object", "properties": { "id": { "type": [ "string", "null" ], "format": "uuid" }, "kind": { "$ref": "#/components/schemas/agendaRecurrenceDateKindSchema" }, "occursAt": { "$ref": "#/components/schemas/agendaIsoDateTime" } }, "required": [ "id", "kind", "occursAt" ] }, "agendaRecurrenceFrequencySchema": { "type": "string", "enum": [ "daily", "weekly", "monthly", "yearly" ] }, "agendaRecurrenceRuleSchema": { "type": "object", "properties": { "count": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 }, "frequency": { "$ref": "#/components/schemas/agendaRecurrenceFrequencySchema" }, "intervalCount": { "type": "integer", "exclusiveMinimum": 0 }, "monthDay": { "type": [ "integer", "null" ], "minimum": 1, "maximum": 31 }, "monthOrdinal": { "anyOf": [ { "anyOf": [ { "type": "string" }, { "type": "integer", "enum": [ 1 ] }, { "type": "integer", "enum": [ 2 ] }, { "type": "integer", "enum": [ 3 ] }, { "type": "integer", "enum": [ 4 ] } ] }, { "type": "null" } ] }, "monthWeekday": { "anyOf": [ { "$ref": "#/components/schemas/agendaWeekdaySchema" }, { "type": "null" } ] }, "untilAt": { "anyOf": [ { "$ref": "#/components/schemas/agendaIsoDateTime" }, { "type": "null" } ] }, "weekDays": { "type": "array", "items": { "$ref": "#/components/schemas/agendaWeekdaySchema" }, "maxItems": 7 } }, "required": [ "count", "frequency", "intervalCount", "monthDay", "monthOrdinal", "monthWeekday", "untilAt", "weekDays" ] }, "agendaReminderMethodSchema": { "type": "string", "enum": [ "display", "email" ] }, "agendaReminderSchema": { "type": "object", "properties": { "id": { "type": [ "string", "null" ], "format": "uuid" }, "method": { "$ref": "#/components/schemas/agendaReminderMethodSchema" }, "offsetMinutes": { "type": "integer", "minimum": -40320, "maximum": 40320 } }, "required": [ "id", "method", "offsetMinutes" ] }, "agendaRichDescriptionSchema": { "type": [ "string", "null" ], "maxLength": 50000 }, "agendaSuggestionReasonSchema": { "type": "object", "properties": { "title": { "type": "string", "minLength": 1 }, "description": { "type": "string", "minLength": 1 }, "signals": { "type": "array", "items": { "type": "string", "minLength": 1 }, "maxItems": 8 } }, "required": [ "title", "description", "signals" ] }, "agendaTimeZoneSchema": { "type": "string", "minLength": 1, "maxLength": 120 }, "agendaTransparencySchema": { "type": "string", "enum": [ "opaque", "transparent" ] }, "agendaWeekdaySchema": { "type": "string", "enum": [ "mo", "tu", "we", "th", "fr", "sa", "su" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "taskAssigneeSchema": { "type": "object", "properties": { "userId": { "type": "string", "format": "uuid" }, "displayName": { "type": "string", "minLength": 1 }, "avatarUrl": { "type": [ "string", "null" ], "format": "uri" }, "disabledAt": { "type": [ "string", "null" ] } }, "required": [ "userId", "displayName", "avatarUrl", "disabledAt" ] }, "taskPrioritySchema": { "type": "string", "enum": [ "low", "medium", "high", "urgent" ] }, "taskStatusSchema": { "type": "string", "enum": [ "todo", "in_progress", "on_hold", "done" ] } } ``` # Create an agenda item. (/openapi/createAgendaItem) Create an agenda item in an editable calendar for the specified organization. ## POST /v1/organizations/{organizationId}/agenda/items ```json { "operationId": "createAgendaItem", "summary": "Create an agenda item.", "description": "Create an agenda item in an editable calendar for the specified organization.", "tags": [ "Agenda" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/agendaItemInputSchema" } } }, "description": "The agenda item payload.", "required": true }, "responses": { "201": { "description": "Returns the created agenda item.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/agendaItemHttpSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "agendaAttendeeInputSchema": { "type": "object", "properties": { "displayName": { "$ref": "#/components/schemas/agendaOptionalShortText" }, "email": { "type": [ "string", "null" ], "format": "email", "default": null }, "isOptional": { "type": "boolean", "default": false }, "isOrganizer": { "type": "boolean", "default": false }, "participationStatus": { "allOf": [ { "$ref": "#/components/schemas/agendaParticipationStatusSchema" } ], "default": "needs-action" }, "role": { "allOf": [ { "$ref": "#/components/schemas/agendaAttendeeRoleSchema" } ], "default": "required" }, "userId": { "type": [ "string", "null" ], "format": "uuid", "default": null }, "customerId": { "type": [ "integer", "null" ], "default": null }, "customerContactId": { "type": [ "string", "null" ], "format": "uuid", "default": null } }, "required": [ "displayName", "email", "isOptional", "isOrganizer", "participationStatus", "role", "userId", "customerId", "customerContactId" ] }, "agendaAttendeeRoleSchema": { "type": "string", "enum": [ "required", "optional", "resource", "chair" ] }, "agendaAttendeeSchema": { "type": "object", "properties": { "displayName": { "type": [ "string", "null" ], "minLength": 1, "maxLength": 240 }, "email": { "type": [ "string", "null" ], "format": "email" }, "id": { "type": [ "string", "null" ], "format": "uuid" }, "isOptional": { "type": "boolean" }, "isOrganizer": { "type": "boolean" }, "participationStatus": { "$ref": "#/components/schemas/agendaParticipationStatusSchema" }, "role": { "$ref": "#/components/schemas/agendaAttendeeRoleSchema" }, "userId": { "type": [ "string", "null" ], "format": "uuid" }, "customerId": { "type": [ "integer", "null" ] }, "customerContactId": { "type": [ "string", "null" ], "format": "uuid" } }, "required": [ "displayName", "email", "id", "isOptional", "isOrganizer", "participationStatus", "role", "userId", "customerId", "customerContactId" ] }, "agendaBlockSourceSchema": { "type": "string", "enum": [ "item", "google-calendar" ] }, "agendaBlockStateSchema": { "type": "string", "enum": [ "confirmed", "tentative", "cancelled" ] }, "agendaIsoDateTime": { "type": "string", "format": "date-time" }, "agendaItemHttpSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "organizationId": { "type": "string", "format": "uuid" }, "source": { "$ref": "#/components/schemas/agendaBlockSourceSchema" }, "state": { "$ref": "#/components/schemas/agendaBlockStateSchema" }, "visibility": { "$ref": "#/components/schemas/agendaItemVisibilitySchema" }, "ownerUserId": { "type": [ "string", "null" ], "format": "uuid" }, "title": { "type": "string", "minLength": 1 }, "description": { "type": [ "string", "null" ] }, "descriptionHtml": { "$ref": "#/components/schemas/agendaRichDescriptionSchema" }, "location": { "type": [ "string", "null" ] }, "onlineMeetingUrl": { "type": [ "string", "null" ], "format": "uri" }, "transparency": { "$ref": "#/components/schemas/agendaTransparencySchema" }, "startAt": { "$ref": "#/components/schemas/agendaIsoDateTime" }, "endAt": { "$ref": "#/components/schemas/agendaIsoDateTime" }, "isAllDay": { "type": "boolean" }, "taskId": { "type": [ "string", "null" ], "format": "uuid" }, "taskNumber": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 }, "taskDisplayNumber": { "type": [ "string", "null" ], "minLength": 1 }, "taskTitle": { "type": [ "string", "null" ], "minLength": 1 }, "priority": { "anyOf": [ { "$ref": "#/components/schemas/taskPrioritySchema" }, { "type": "null" } ] }, "taskStatus": { "anyOf": [ { "$ref": "#/components/schemas/taskStatusSchema" }, { "type": "null" } ] }, "users": { "type": "array", "items": { "$ref": "#/components/schemas/agendaItemUserSchema" } }, "assignees": { "type": "array", "items": { "$ref": "#/components/schemas/agendaItemUserSchema" } }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "createdAt": { "$ref": "#/components/schemas/agendaIsoDateTime" }, "updatedAt": { "$ref": "#/components/schemas/agendaIsoDateTime" }, "timeZone": { "anyOf": [ { "$ref": "#/components/schemas/agendaTimeZoneSchema" }, { "type": "null" } ] }, "organizerUserId": { "type": [ "string", "null" ], "format": "uuid" }, "organizerEmail": { "type": [ "string", "null" ], "format": "email" }, "organizerDisplayName": { "type": [ "string", "null" ] }, "recurrenceParentItemId": { "type": [ "string", "null" ], "format": "uuid" }, "recurrenceIdAt": { "anyOf": [ { "$ref": "#/components/schemas/agendaIsoDateTime" }, { "type": "null" } ] }, "recurrenceRule": { "anyOf": [ { "$ref": "#/components/schemas/agendaRecurrenceRuleSchema" }, { "type": "null" } ] }, "recurrenceDates": { "type": "array", "items": { "$ref": "#/components/schemas/agendaRecurrenceDateSchema" } }, "reminders": { "type": "array", "items": { "$ref": "#/components/schemas/agendaReminderSchema" } }, "attendees": { "type": "array", "items": { "$ref": "#/components/schemas/agendaAttendeeSchema" } }, "calendarId": { "type": "string", "format": "uuid" }, "isReadOnly": { "type": "boolean" }, "reason": { "anyOf": [ { "$ref": "#/components/schemas/agendaSuggestionReasonSchema" }, { "type": "null" } ] }, "confidence": { "type": [ "number", "null" ], "minimum": 0, "maximum": 1 } }, "required": [ "id", "organizationId", "source", "state", "visibility", "ownerUserId", "title", "description", "descriptionHtml", "location", "onlineMeetingUrl", "transparency", "startAt", "endAt", "isAllDay", "taskId", "taskNumber", "taskDisplayNumber", "taskTitle", "priority", "taskStatus", "users", "assignees", "createdBy", "createdAt", "updatedAt", "timeZone", "organizerUserId", "organizerEmail", "organizerDisplayName", "recurrenceParentItemId", "recurrenceIdAt", "recurrenceRule", "recurrenceDates", "reminders", "attendees", "calendarId", "isReadOnly", "reason", "confidence" ] }, "agendaItemInputSchema": { "type": "object", "properties": { "title": { "type": "string", "minLength": 1, "maxLength": 240 }, "description": { "$ref": "#/components/schemas/agendaOptionalText" }, "descriptionHtml": { "allOf": [ { "$ref": "#/components/schemas/agendaRichDescriptionSchema" } ], "default": null }, "calendarId": { "type": "string", "format": "uuid" }, "location": { "$ref": "#/components/schemas/agendaOptionalShortText" }, "onlineMeetingUrl": { "type": [ "string", "null" ], "format": "uri", "default": null }, "timeZone": { "anyOf": [ { "$ref": "#/components/schemas/agendaTimeZoneSchema" }, { "type": "null" } ], "default": null }, "transparency": { "type": "string", "enum": [ "opaque", "transparent" ], "default": "opaque" }, "status": { "type": "string", "enum": [ "confirmed", "tentative", "cancelled" ], "default": "confirmed" }, "startAt": { "$ref": "#/components/schemas/agendaIsoDateTime" }, "endAt": { "$ref": "#/components/schemas/agendaIsoDateTime" }, "isAllDay": { "type": "boolean", "default": false }, "taskId": { "type": [ "string", "null" ], "format": "uuid", "default": null }, "userIds": { "type": "array", "items": { "type": "string", "format": "uuid" }, "maxItems": 100 }, "recurrenceRule": { "anyOf": [ { "$ref": "#/components/schemas/agendaRecurrenceRuleInputSchema" }, { "type": "null" } ], "default": null }, "recurrenceDates": { "type": "array", "items": { "$ref": "#/components/schemas/agendaRecurrenceDateInputSchema" }, "maxItems": 366 }, "reminders": { "type": "array", "items": { "$ref": "#/components/schemas/agendaReminderInputSchema" }, "maxItems": 20 }, "attendees": { "type": "array", "items": { "$ref": "#/components/schemas/agendaAttendeeInputSchema" }, "maxItems": 200 } }, "required": [ "title", "description", "location", "transparency", "status", "startAt", "endAt", "isAllDay", "taskId", "userIds", "recurrenceRule", "recurrenceDates", "reminders", "attendees" ] }, "agendaItemUserSchema": { "$ref": "#/components/schemas/taskAssigneeSchema" }, "agendaItemVisibilitySchema": { "type": "string", "enum": [ "public", "private" ] }, "agendaOptionalShortText": { "type": [ "string", "null" ], "maxLength": 500 }, "agendaOptionalText": { "type": [ "string", "null" ], "maxLength": 5000 }, "agendaParticipationStatusSchema": { "type": "string", "enum": [ "needs-action", "accepted", "declined", "tentative", "delegated" ] }, "agendaRecurrenceDateInputSchema": { "type": "object", "properties": { "kind": { "$ref": "#/components/schemas/agendaRecurrenceDateKindSchema" }, "occursAt": { "$ref": "#/components/schemas/agendaIsoDateTime" } }, "required": [ "kind", "occursAt" ] }, "agendaRecurrenceDateKindSchema": { "type": "string", "enum": [ "rdate", "exdate" ] }, "agendaRecurrenceDateSchema": { "type": "object", "properties": { "id": { "type": [ "string", "null" ], "format": "uuid" }, "kind": { "$ref": "#/components/schemas/agendaRecurrenceDateKindSchema" }, "occursAt": { "$ref": "#/components/schemas/agendaIsoDateTime" } }, "required": [ "id", "kind", "occursAt" ] }, "agendaRecurrenceFrequencySchema": { "type": "string", "enum": [ "daily", "weekly", "monthly", "yearly" ] }, "agendaRecurrenceRuleInputSchema": { "type": "object", "properties": { "count": { "type": [ "integer", "null" ], "exclusiveMinimum": 0, "default": null }, "frequency": { "$ref": "#/components/schemas/agendaRecurrenceFrequencySchema" }, "intervalCount": { "type": "integer", "exclusiveMinimum": 0, "default": 1 }, "monthDay": { "type": [ "integer", "null" ], "minimum": 1, "maximum": 31, "default": null }, "monthOrdinal": { "anyOf": [ { "anyOf": [ { "type": "string" }, { "type": "integer", "enum": [ 1 ] }, { "type": "integer", "enum": [ 2 ] }, { "type": "integer", "enum": [ 3 ] }, { "type": "integer", "enum": [ 4 ] } ], "default": null }, { "type": "null" } ] }, "monthWeekday": { "anyOf": [ { "$ref": "#/components/schemas/agendaWeekdaySchema" }, { "type": "null" } ], "default": null }, "untilAt": { "anyOf": [ { "$ref": "#/components/schemas/agendaIsoDateTime" }, { "type": "null" } ], "default": null }, "weekDays": { "type": "array", "items": { "$ref": "#/components/schemas/agendaWeekdaySchema" }, "maxItems": 7 } }, "required": [ "count", "frequency", "intervalCount", "monthDay", "monthOrdinal", "monthWeekday", "untilAt", "weekDays" ] }, "agendaRecurrenceRuleSchema": { "type": "object", "properties": { "count": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 }, "frequency": { "$ref": "#/components/schemas/agendaRecurrenceFrequencySchema" }, "intervalCount": { "type": "integer", "exclusiveMinimum": 0 }, "monthDay": { "type": [ "integer", "null" ], "minimum": 1, "maximum": 31 }, "monthOrdinal": { "anyOf": [ { "anyOf": [ { "type": "string" }, { "type": "integer", "enum": [ 1 ] }, { "type": "integer", "enum": [ 2 ] }, { "type": "integer", "enum": [ 3 ] }, { "type": "integer", "enum": [ 4 ] } ] }, { "type": "null" } ] }, "monthWeekday": { "anyOf": [ { "$ref": "#/components/schemas/agendaWeekdaySchema" }, { "type": "null" } ] }, "untilAt": { "anyOf": [ { "$ref": "#/components/schemas/agendaIsoDateTime" }, { "type": "null" } ] }, "weekDays": { "type": "array", "items": { "$ref": "#/components/schemas/agendaWeekdaySchema" }, "maxItems": 7 } }, "required": [ "count", "frequency", "intervalCount", "monthDay", "monthOrdinal", "monthWeekday", "untilAt", "weekDays" ] }, "agendaReminderInputSchema": { "type": "object", "properties": { "method": { "$ref": "#/components/schemas/agendaReminderMethodSchema" }, "offsetMinutes": { "type": "integer", "minimum": -40320, "maximum": 40320 } }, "required": [ "method", "offsetMinutes" ] }, "agendaReminderMethodSchema": { "type": "string", "enum": [ "display", "email" ] }, "agendaReminderSchema": { "type": "object", "properties": { "id": { "type": [ "string", "null" ], "format": "uuid" }, "method": { "$ref": "#/components/schemas/agendaReminderMethodSchema" }, "offsetMinutes": { "type": "integer", "minimum": -40320, "maximum": 40320 } }, "required": [ "id", "method", "offsetMinutes" ] }, "agendaRichDescriptionSchema": { "type": [ "string", "null" ], "maxLength": 50000 }, "agendaSuggestionReasonSchema": { "type": "object", "properties": { "title": { "type": "string", "minLength": 1 }, "description": { "type": "string", "minLength": 1 }, "signals": { "type": "array", "items": { "type": "string", "minLength": 1 }, "maxItems": 8 } }, "required": [ "title", "description", "signals" ] }, "agendaTimeZoneSchema": { "type": "string", "minLength": 1, "maxLength": 120 }, "agendaTransparencySchema": { "type": "string", "enum": [ "opaque", "transparent" ] }, "agendaWeekdaySchema": { "type": "string", "enum": [ "mo", "tu", "we", "th", "fr", "sa", "su" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "taskAssigneeSchema": { "type": "object", "properties": { "userId": { "type": "string", "format": "uuid" }, "displayName": { "type": "string", "minLength": 1 }, "avatarUrl": { "type": [ "string", "null" ], "format": "uri" }, "disabledAt": { "type": [ "string", "null" ] } }, "required": [ "userId", "displayName", "avatarUrl", "disabledAt" ] }, "taskPrioritySchema": { "type": "string", "enum": [ "low", "medium", "high", "urgent" ] }, "taskStatusSchema": { "type": "string", "enum": [ "todo", "in_progress", "on_hold", "done" ] } } ``` # Get an agenda item. (/openapi/getAgendaItem) Return one agenda item the authenticated user can view. ## GET /v1/organizations/{organizationId}/agenda/items/{agendaItemId} ```json { "operationId": "getAgendaItem", "summary": "Get an agenda item.", "description": "Return one agenda item the authenticated user can view.", "tags": [ "Agenda" ], "parameters": [ { "in": "path", "name": "agendaItemId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns the requested agenda item.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/agendaItemHttpSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "agendaAttendeeRoleSchema": { "type": "string", "enum": [ "required", "optional", "resource", "chair" ] }, "agendaAttendeeSchema": { "type": "object", "properties": { "displayName": { "type": [ "string", "null" ], "minLength": 1, "maxLength": 240 }, "email": { "type": [ "string", "null" ], "format": "email" }, "id": { "type": [ "string", "null" ], "format": "uuid" }, "isOptional": { "type": "boolean" }, "isOrganizer": { "type": "boolean" }, "participationStatus": { "$ref": "#/components/schemas/agendaParticipationStatusSchema" }, "role": { "$ref": "#/components/schemas/agendaAttendeeRoleSchema" }, "userId": { "type": [ "string", "null" ], "format": "uuid" }, "customerId": { "type": [ "integer", "null" ] }, "customerContactId": { "type": [ "string", "null" ], "format": "uuid" } }, "required": [ "displayName", "email", "id", "isOptional", "isOrganizer", "participationStatus", "role", "userId", "customerId", "customerContactId" ] }, "agendaBlockSourceSchema": { "type": "string", "enum": [ "item", "google-calendar" ] }, "agendaBlockStateSchema": { "type": "string", "enum": [ "confirmed", "tentative", "cancelled" ] }, "agendaIsoDateTime": { "type": "string", "format": "date-time" }, "agendaItemHttpSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "organizationId": { "type": "string", "format": "uuid" }, "source": { "$ref": "#/components/schemas/agendaBlockSourceSchema" }, "state": { "$ref": "#/components/schemas/agendaBlockStateSchema" }, "visibility": { "$ref": "#/components/schemas/agendaItemVisibilitySchema" }, "ownerUserId": { "type": [ "string", "null" ], "format": "uuid" }, "title": { "type": "string", "minLength": 1 }, "description": { "type": [ "string", "null" ] }, "descriptionHtml": { "$ref": "#/components/schemas/agendaRichDescriptionSchema" }, "location": { "type": [ "string", "null" ] }, "onlineMeetingUrl": { "type": [ "string", "null" ], "format": "uri" }, "transparency": { "$ref": "#/components/schemas/agendaTransparencySchema" }, "startAt": { "$ref": "#/components/schemas/agendaIsoDateTime" }, "endAt": { "$ref": "#/components/schemas/agendaIsoDateTime" }, "isAllDay": { "type": "boolean" }, "taskId": { "type": [ "string", "null" ], "format": "uuid" }, "taskNumber": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 }, "taskDisplayNumber": { "type": [ "string", "null" ], "minLength": 1 }, "taskTitle": { "type": [ "string", "null" ], "minLength": 1 }, "priority": { "anyOf": [ { "$ref": "#/components/schemas/taskPrioritySchema" }, { "type": "null" } ] }, "taskStatus": { "anyOf": [ { "$ref": "#/components/schemas/taskStatusSchema" }, { "type": "null" } ] }, "users": { "type": "array", "items": { "$ref": "#/components/schemas/agendaItemUserSchema" } }, "assignees": { "type": "array", "items": { "$ref": "#/components/schemas/agendaItemUserSchema" } }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "createdAt": { "$ref": "#/components/schemas/agendaIsoDateTime" }, "updatedAt": { "$ref": "#/components/schemas/agendaIsoDateTime" }, "timeZone": { "anyOf": [ { "$ref": "#/components/schemas/agendaTimeZoneSchema" }, { "type": "null" } ] }, "organizerUserId": { "type": [ "string", "null" ], "format": "uuid" }, "organizerEmail": { "type": [ "string", "null" ], "format": "email" }, "organizerDisplayName": { "type": [ "string", "null" ] }, "recurrenceParentItemId": { "type": [ "string", "null" ], "format": "uuid" }, "recurrenceIdAt": { "anyOf": [ { "$ref": "#/components/schemas/agendaIsoDateTime" }, { "type": "null" } ] }, "recurrenceRule": { "anyOf": [ { "$ref": "#/components/schemas/agendaRecurrenceRuleSchema" }, { "type": "null" } ] }, "recurrenceDates": { "type": "array", "items": { "$ref": "#/components/schemas/agendaRecurrenceDateSchema" } }, "reminders": { "type": "array", "items": { "$ref": "#/components/schemas/agendaReminderSchema" } }, "attendees": { "type": "array", "items": { "$ref": "#/components/schemas/agendaAttendeeSchema" } }, "calendarId": { "type": "string", "format": "uuid" }, "isReadOnly": { "type": "boolean" }, "reason": { "anyOf": [ { "$ref": "#/components/schemas/agendaSuggestionReasonSchema" }, { "type": "null" } ] }, "confidence": { "type": [ "number", "null" ], "minimum": 0, "maximum": 1 } }, "required": [ "id", "organizationId", "source", "state", "visibility", "ownerUserId", "title", "description", "descriptionHtml", "location", "onlineMeetingUrl", "transparency", "startAt", "endAt", "isAllDay", "taskId", "taskNumber", "taskDisplayNumber", "taskTitle", "priority", "taskStatus", "users", "assignees", "createdBy", "createdAt", "updatedAt", "timeZone", "organizerUserId", "organizerEmail", "organizerDisplayName", "recurrenceParentItemId", "recurrenceIdAt", "recurrenceRule", "recurrenceDates", "reminders", "attendees", "calendarId", "isReadOnly", "reason", "confidence" ] }, "agendaItemUserSchema": { "$ref": "#/components/schemas/taskAssigneeSchema" }, "agendaItemVisibilitySchema": { "type": "string", "enum": [ "public", "private" ] }, "agendaParticipationStatusSchema": { "type": "string", "enum": [ "needs-action", "accepted", "declined", "tentative", "delegated" ] }, "agendaRecurrenceDateKindSchema": { "type": "string", "enum": [ "rdate", "exdate" ] }, "agendaRecurrenceDateSchema": { "type": "object", "properties": { "id": { "type": [ "string", "null" ], "format": "uuid" }, "kind": { "$ref": "#/components/schemas/agendaRecurrenceDateKindSchema" }, "occursAt": { "$ref": "#/components/schemas/agendaIsoDateTime" } }, "required": [ "id", "kind", "occursAt" ] }, "agendaRecurrenceFrequencySchema": { "type": "string", "enum": [ "daily", "weekly", "monthly", "yearly" ] }, "agendaRecurrenceRuleSchema": { "type": "object", "properties": { "count": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 }, "frequency": { "$ref": "#/components/schemas/agendaRecurrenceFrequencySchema" }, "intervalCount": { "type": "integer", "exclusiveMinimum": 0 }, "monthDay": { "type": [ "integer", "null" ], "minimum": 1, "maximum": 31 }, "monthOrdinal": { "anyOf": [ { "anyOf": [ { "type": "string" }, { "type": "integer", "enum": [ 1 ] }, { "type": "integer", "enum": [ 2 ] }, { "type": "integer", "enum": [ 3 ] }, { "type": "integer", "enum": [ 4 ] } ] }, { "type": "null" } ] }, "monthWeekday": { "anyOf": [ { "$ref": "#/components/schemas/agendaWeekdaySchema" }, { "type": "null" } ] }, "untilAt": { "anyOf": [ { "$ref": "#/components/schemas/agendaIsoDateTime" }, { "type": "null" } ] }, "weekDays": { "type": "array", "items": { "$ref": "#/components/schemas/agendaWeekdaySchema" }, "maxItems": 7 } }, "required": [ "count", "frequency", "intervalCount", "monthDay", "monthOrdinal", "monthWeekday", "untilAt", "weekDays" ] }, "agendaReminderMethodSchema": { "type": "string", "enum": [ "display", "email" ] }, "agendaReminderSchema": { "type": "object", "properties": { "id": { "type": [ "string", "null" ], "format": "uuid" }, "method": { "$ref": "#/components/schemas/agendaReminderMethodSchema" }, "offsetMinutes": { "type": "integer", "minimum": -40320, "maximum": 40320 } }, "required": [ "id", "method", "offsetMinutes" ] }, "agendaRichDescriptionSchema": { "type": [ "string", "null" ], "maxLength": 50000 }, "agendaSuggestionReasonSchema": { "type": "object", "properties": { "title": { "type": "string", "minLength": 1 }, "description": { "type": "string", "minLength": 1 }, "signals": { "type": "array", "items": { "type": "string", "minLength": 1 }, "maxItems": 8 } }, "required": [ "title", "description", "signals" ] }, "agendaTimeZoneSchema": { "type": "string", "minLength": 1, "maxLength": 120 }, "agendaTransparencySchema": { "type": "string", "enum": [ "opaque", "transparent" ] }, "agendaWeekdaySchema": { "type": "string", "enum": [ "mo", "tu", "we", "th", "fr", "sa", "su" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "taskAssigneeSchema": { "type": "object", "properties": { "userId": { "type": "string", "format": "uuid" }, "displayName": { "type": "string", "minLength": 1 }, "avatarUrl": { "type": [ "string", "null" ], "format": "uri" }, "disabledAt": { "type": [ "string", "null" ] } }, "required": [ "userId", "displayName", "avatarUrl", "disabledAt" ] }, "taskPrioritySchema": { "type": "string", "enum": [ "low", "medium", "high", "urgent" ] }, "taskStatusSchema": { "type": "string", "enum": [ "todo", "in_progress", "on_hold", "done" ] } } ``` # Update an agenda item. (/openapi/updateAgendaItem) Replace an agenda item or one recurring occurrence in an editable calendar. ## PATCH /v1/organizations/{organizationId}/agenda/items/{agendaItemId} ```json { "operationId": "updateAgendaItem", "summary": "Update an agenda item.", "description": "Replace an agenda item or one recurring occurrence in an editable calendar.", "tags": [ "Agenda" ], "parameters": [ { "in": "path", "name": "agendaItemId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/agendaItemUpdateHttpInputSchema" } } }, "description": "The complete agenda item payload plus the optional recurrence mutation scope.", "required": true }, "responses": { "200": { "description": "Returns the updated agenda item.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/agendaItemHttpSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "agendaAttendeeInputSchema": { "type": "object", "properties": { "displayName": { "$ref": "#/components/schemas/agendaOptionalShortText" }, "email": { "type": [ "string", "null" ], "format": "email", "default": null }, "isOptional": { "type": "boolean", "default": false }, "isOrganizer": { "type": "boolean", "default": false }, "participationStatus": { "allOf": [ { "$ref": "#/components/schemas/agendaParticipationStatusSchema" } ], "default": "needs-action" }, "role": { "allOf": [ { "$ref": "#/components/schemas/agendaAttendeeRoleSchema" } ], "default": "required" }, "userId": { "type": [ "string", "null" ], "format": "uuid", "default": null }, "customerId": { "type": [ "integer", "null" ], "default": null }, "customerContactId": { "type": [ "string", "null" ], "format": "uuid", "default": null } }, "required": [ "displayName", "email", "isOptional", "isOrganizer", "participationStatus", "role", "userId", "customerId", "customerContactId" ] }, "agendaAttendeeRoleSchema": { "type": "string", "enum": [ "required", "optional", "resource", "chair" ] }, "agendaAttendeeSchema": { "type": "object", "properties": { "displayName": { "type": [ "string", "null" ], "minLength": 1, "maxLength": 240 }, "email": { "type": [ "string", "null" ], "format": "email" }, "id": { "type": [ "string", "null" ], "format": "uuid" }, "isOptional": { "type": "boolean" }, "isOrganizer": { "type": "boolean" }, "participationStatus": { "$ref": "#/components/schemas/agendaParticipationStatusSchema" }, "role": { "$ref": "#/components/schemas/agendaAttendeeRoleSchema" }, "userId": { "type": [ "string", "null" ], "format": "uuid" }, "customerId": { "type": [ "integer", "null" ] }, "customerContactId": { "type": [ "string", "null" ], "format": "uuid" } }, "required": [ "displayName", "email", "id", "isOptional", "isOrganizer", "participationStatus", "role", "userId", "customerId", "customerContactId" ] }, "agendaBlockSourceSchema": { "type": "string", "enum": [ "item", "google-calendar" ] }, "agendaBlockStateSchema": { "type": "string", "enum": [ "confirmed", "tentative", "cancelled" ] }, "agendaIsoDateTime": { "type": "string", "format": "date-time" }, "agendaItemHttpSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "organizationId": { "type": "string", "format": "uuid" }, "source": { "$ref": "#/components/schemas/agendaBlockSourceSchema" }, "state": { "$ref": "#/components/schemas/agendaBlockStateSchema" }, "visibility": { "$ref": "#/components/schemas/agendaItemVisibilitySchema" }, "ownerUserId": { "type": [ "string", "null" ], "format": "uuid" }, "title": { "type": "string", "minLength": 1 }, "description": { "type": [ "string", "null" ] }, "descriptionHtml": { "$ref": "#/components/schemas/agendaRichDescriptionSchema" }, "location": { "type": [ "string", "null" ] }, "onlineMeetingUrl": { "type": [ "string", "null" ], "format": "uri" }, "transparency": { "$ref": "#/components/schemas/agendaTransparencySchema" }, "startAt": { "$ref": "#/components/schemas/agendaIsoDateTime" }, "endAt": { "$ref": "#/components/schemas/agendaIsoDateTime" }, "isAllDay": { "type": "boolean" }, "taskId": { "type": [ "string", "null" ], "format": "uuid" }, "taskNumber": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 }, "taskDisplayNumber": { "type": [ "string", "null" ], "minLength": 1 }, "taskTitle": { "type": [ "string", "null" ], "minLength": 1 }, "priority": { "anyOf": [ { "$ref": "#/components/schemas/taskPrioritySchema" }, { "type": "null" } ] }, "taskStatus": { "anyOf": [ { "$ref": "#/components/schemas/taskStatusSchema" }, { "type": "null" } ] }, "users": { "type": "array", "items": { "$ref": "#/components/schemas/agendaItemUserSchema" } }, "assignees": { "type": "array", "items": { "$ref": "#/components/schemas/agendaItemUserSchema" } }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "createdAt": { "$ref": "#/components/schemas/agendaIsoDateTime" }, "updatedAt": { "$ref": "#/components/schemas/agendaIsoDateTime" }, "timeZone": { "anyOf": [ { "$ref": "#/components/schemas/agendaTimeZoneSchema" }, { "type": "null" } ] }, "organizerUserId": { "type": [ "string", "null" ], "format": "uuid" }, "organizerEmail": { "type": [ "string", "null" ], "format": "email" }, "organizerDisplayName": { "type": [ "string", "null" ] }, "recurrenceParentItemId": { "type": [ "string", "null" ], "format": "uuid" }, "recurrenceIdAt": { "anyOf": [ { "$ref": "#/components/schemas/agendaIsoDateTime" }, { "type": "null" } ] }, "recurrenceRule": { "anyOf": [ { "$ref": "#/components/schemas/agendaRecurrenceRuleSchema" }, { "type": "null" } ] }, "recurrenceDates": { "type": "array", "items": { "$ref": "#/components/schemas/agendaRecurrenceDateSchema" } }, "reminders": { "type": "array", "items": { "$ref": "#/components/schemas/agendaReminderSchema" } }, "attendees": { "type": "array", "items": { "$ref": "#/components/schemas/agendaAttendeeSchema" } }, "calendarId": { "type": "string", "format": "uuid" }, "isReadOnly": { "type": "boolean" }, "reason": { "anyOf": [ { "$ref": "#/components/schemas/agendaSuggestionReasonSchema" }, { "type": "null" } ] }, "confidence": { "type": [ "number", "null" ], "minimum": 0, "maximum": 1 } }, "required": [ "id", "organizationId", "source", "state", "visibility", "ownerUserId", "title", "description", "descriptionHtml", "location", "onlineMeetingUrl", "transparency", "startAt", "endAt", "isAllDay", "taskId", "taskNumber", "taskDisplayNumber", "taskTitle", "priority", "taskStatus", "users", "assignees", "createdBy", "createdAt", "updatedAt", "timeZone", "organizerUserId", "organizerEmail", "organizerDisplayName", "recurrenceParentItemId", "recurrenceIdAt", "recurrenceRule", "recurrenceDates", "reminders", "attendees", "calendarId", "isReadOnly", "reason", "confidence" ] }, "agendaItemUpdateHttpInputSchema": { "type": "object", "properties": { "title": { "type": "string", "minLength": 1, "maxLength": 240 }, "description": { "$ref": "#/components/schemas/agendaOptionalText" }, "descriptionHtml": { "allOf": [ { "$ref": "#/components/schemas/agendaRichDescriptionSchema" } ], "default": null }, "calendarId": { "type": "string", "format": "uuid" }, "location": { "$ref": "#/components/schemas/agendaOptionalShortText" }, "onlineMeetingUrl": { "type": [ "string", "null" ], "format": "uri", "default": null }, "timeZone": { "anyOf": [ { "$ref": "#/components/schemas/agendaTimeZoneSchema" }, { "type": "null" } ], "default": null }, "transparency": { "type": "string", "enum": [ "opaque", "transparent" ], "default": "opaque" }, "status": { "type": "string", "enum": [ "confirmed", "tentative", "cancelled" ], "default": "confirmed" }, "startAt": { "$ref": "#/components/schemas/agendaIsoDateTime" }, "endAt": { "$ref": "#/components/schemas/agendaIsoDateTime" }, "isAllDay": { "type": "boolean", "default": false }, "taskId": { "type": [ "string", "null" ], "format": "uuid", "default": null }, "userIds": { "type": "array", "items": { "type": "string", "format": "uuid" }, "maxItems": 100 }, "recurrenceRule": { "anyOf": [ { "$ref": "#/components/schemas/agendaRecurrenceRuleInputSchema" }, { "type": "null" } ], "default": null }, "recurrenceDates": { "type": "array", "items": { "$ref": "#/components/schemas/agendaRecurrenceDateInputSchema" }, "maxItems": 366 }, "reminders": { "type": "array", "items": { "$ref": "#/components/schemas/agendaReminderInputSchema" }, "maxItems": 20 }, "attendees": { "type": "array", "items": { "$ref": "#/components/schemas/agendaAttendeeInputSchema" }, "maxItems": 200 }, "occurrenceStartAt": { "allOf": [ { "$ref": "#/components/schemas/agendaIsoDateTime" } ] }, "scope": { "type": "string", "enum": [ "occurrence", "series" ], "default": "series" } }, "required": [ "title", "description", "location", "transparency", "status", "startAt", "endAt", "isAllDay", "taskId", "userIds", "recurrenceRule", "recurrenceDates", "reminders", "attendees", "scope" ] }, "agendaItemUserSchema": { "$ref": "#/components/schemas/taskAssigneeSchema" }, "agendaItemVisibilitySchema": { "type": "string", "enum": [ "public", "private" ] }, "agendaOptionalShortText": { "type": [ "string", "null" ], "maxLength": 500 }, "agendaOptionalText": { "type": [ "string", "null" ], "maxLength": 5000 }, "agendaParticipationStatusSchema": { "type": "string", "enum": [ "needs-action", "accepted", "declined", "tentative", "delegated" ] }, "agendaRecurrenceDateInputSchema": { "type": "object", "properties": { "kind": { "$ref": "#/components/schemas/agendaRecurrenceDateKindSchema" }, "occursAt": { "$ref": "#/components/schemas/agendaIsoDateTime" } }, "required": [ "kind", "occursAt" ] }, "agendaRecurrenceDateKindSchema": { "type": "string", "enum": [ "rdate", "exdate" ] }, "agendaRecurrenceDateSchema": { "type": "object", "properties": { "id": { "type": [ "string", "null" ], "format": "uuid" }, "kind": { "$ref": "#/components/schemas/agendaRecurrenceDateKindSchema" }, "occursAt": { "$ref": "#/components/schemas/agendaIsoDateTime" } }, "required": [ "id", "kind", "occursAt" ] }, "agendaRecurrenceFrequencySchema": { "type": "string", "enum": [ "daily", "weekly", "monthly", "yearly" ] }, "agendaRecurrenceRuleInputSchema": { "type": "object", "properties": { "count": { "type": [ "integer", "null" ], "exclusiveMinimum": 0, "default": null }, "frequency": { "$ref": "#/components/schemas/agendaRecurrenceFrequencySchema" }, "intervalCount": { "type": "integer", "exclusiveMinimum": 0, "default": 1 }, "monthDay": { "type": [ "integer", "null" ], "minimum": 1, "maximum": 31, "default": null }, "monthOrdinal": { "anyOf": [ { "anyOf": [ { "type": "string" }, { "type": "integer", "enum": [ 1 ] }, { "type": "integer", "enum": [ 2 ] }, { "type": "integer", "enum": [ 3 ] }, { "type": "integer", "enum": [ 4 ] } ], "default": null }, { "type": "null" } ] }, "monthWeekday": { "anyOf": [ { "$ref": "#/components/schemas/agendaWeekdaySchema" }, { "type": "null" } ], "default": null }, "untilAt": { "anyOf": [ { "$ref": "#/components/schemas/agendaIsoDateTime" }, { "type": "null" } ], "default": null }, "weekDays": { "type": "array", "items": { "$ref": "#/components/schemas/agendaWeekdaySchema" }, "maxItems": 7 } }, "required": [ "count", "frequency", "intervalCount", "monthDay", "monthOrdinal", "monthWeekday", "untilAt", "weekDays" ] }, "agendaRecurrenceRuleSchema": { "type": "object", "properties": { "count": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 }, "frequency": { "$ref": "#/components/schemas/agendaRecurrenceFrequencySchema" }, "intervalCount": { "type": "integer", "exclusiveMinimum": 0 }, "monthDay": { "type": [ "integer", "null" ], "minimum": 1, "maximum": 31 }, "monthOrdinal": { "anyOf": [ { "anyOf": [ { "type": "string" }, { "type": "integer", "enum": [ 1 ] }, { "type": "integer", "enum": [ 2 ] }, { "type": "integer", "enum": [ 3 ] }, { "type": "integer", "enum": [ 4 ] } ] }, { "type": "null" } ] }, "monthWeekday": { "anyOf": [ { "$ref": "#/components/schemas/agendaWeekdaySchema" }, { "type": "null" } ] }, "untilAt": { "anyOf": [ { "$ref": "#/components/schemas/agendaIsoDateTime" }, { "type": "null" } ] }, "weekDays": { "type": "array", "items": { "$ref": "#/components/schemas/agendaWeekdaySchema" }, "maxItems": 7 } }, "required": [ "count", "frequency", "intervalCount", "monthDay", "monthOrdinal", "monthWeekday", "untilAt", "weekDays" ] }, "agendaReminderInputSchema": { "type": "object", "properties": { "method": { "$ref": "#/components/schemas/agendaReminderMethodSchema" }, "offsetMinutes": { "type": "integer", "minimum": -40320, "maximum": 40320 } }, "required": [ "method", "offsetMinutes" ] }, "agendaReminderMethodSchema": { "type": "string", "enum": [ "display", "email" ] }, "agendaReminderSchema": { "type": "object", "properties": { "id": { "type": [ "string", "null" ], "format": "uuid" }, "method": { "$ref": "#/components/schemas/agendaReminderMethodSchema" }, "offsetMinutes": { "type": "integer", "minimum": -40320, "maximum": 40320 } }, "required": [ "id", "method", "offsetMinutes" ] }, "agendaRichDescriptionSchema": { "type": [ "string", "null" ], "maxLength": 50000 }, "agendaSuggestionReasonSchema": { "type": "object", "properties": { "title": { "type": "string", "minLength": 1 }, "description": { "type": "string", "minLength": 1 }, "signals": { "type": "array", "items": { "type": "string", "minLength": 1 }, "maxItems": 8 } }, "required": [ "title", "description", "signals" ] }, "agendaTimeZoneSchema": { "type": "string", "minLength": 1, "maxLength": 120 }, "agendaTransparencySchema": { "type": "string", "enum": [ "opaque", "transparent" ] }, "agendaWeekdaySchema": { "type": "string", "enum": [ "mo", "tu", "we", "th", "fr", "sa", "su" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "taskAssigneeSchema": { "type": "object", "properties": { "userId": { "type": "string", "format": "uuid" }, "displayName": { "type": "string", "minLength": 1 }, "avatarUrl": { "type": [ "string", "null" ], "format": "uri" }, "disabledAt": { "type": [ "string", "null" ] } }, "required": [ "userId", "displayName", "avatarUrl", "disabledAt" ] }, "taskPrioritySchema": { "type": "string", "enum": [ "low", "medium", "high", "urgent" ] }, "taskStatusSchema": { "type": "string", "enum": [ "todo", "in_progress", "on_hold", "done" ] } } ``` # Delete an agenda item. (/openapi/deleteAgendaItem) Delete an agenda series or one recurring occurrence from an editable calendar. ## DELETE /v1/organizations/{organizationId}/agenda/items/{agendaItemId} ```json { "operationId": "deleteAgendaItem", "summary": "Delete an agenda item.", "description": "Delete an agenda series or one recurring occurrence from an editable calendar.", "tags": [ "Agenda" ], "parameters": [ { "in": "query", "name": "occurrenceStartAt", "required": false, "schema": { "allOf": [ { "$ref": "#/components/schemas/agendaIsoDateTime" } ] }, "example": "example" }, { "in": "query", "name": "scope", "required": true, "schema": { "type": "string", "enum": [ "occurrence", "series" ], "default": "series" }, "example": "occurrence" }, { "in": "path", "name": "agendaItemId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns a success payload when the agenda item is deleted.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/publicApiSuccessResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "agendaIsoDateTime": { "type": "string", "format": "date-time" }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "publicApiSuccessResponseSchema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [ true ] } }, "required": [ "success" ] } } ``` # Move an agenda item. (/openapi/moveAgendaItem) Move or resize an agenda item or one recurring occurrence in an editable calendar. ## POST /v1/organizations/{organizationId}/agenda/items/{agendaItemId}/move ```json { "operationId": "moveAgendaItem", "summary": "Move an agenda item.", "description": "Move or resize an agenda item or one recurring occurrence in an editable calendar.", "tags": [ "Agenda" ], "parameters": [ { "in": "path", "name": "agendaItemId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/agendaItemMoveInputSchema" } } }, "description": "The new temporal range and optional recurrence mutation scope.", "required": true }, "responses": { "200": { "description": "Returns the moved agenda item.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/agendaItemHttpSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "agendaAttendeeRoleSchema": { "type": "string", "enum": [ "required", "optional", "resource", "chair" ] }, "agendaAttendeeSchema": { "type": "object", "properties": { "displayName": { "type": [ "string", "null" ], "minLength": 1, "maxLength": 240 }, "email": { "type": [ "string", "null" ], "format": "email" }, "id": { "type": [ "string", "null" ], "format": "uuid" }, "isOptional": { "type": "boolean" }, "isOrganizer": { "type": "boolean" }, "participationStatus": { "$ref": "#/components/schemas/agendaParticipationStatusSchema" }, "role": { "$ref": "#/components/schemas/agendaAttendeeRoleSchema" }, "userId": { "type": [ "string", "null" ], "format": "uuid" }, "customerId": { "type": [ "integer", "null" ] }, "customerContactId": { "type": [ "string", "null" ], "format": "uuid" } }, "required": [ "displayName", "email", "id", "isOptional", "isOrganizer", "participationStatus", "role", "userId", "customerId", "customerContactId" ] }, "agendaBlockSourceSchema": { "type": "string", "enum": [ "item", "google-calendar" ] }, "agendaBlockStateSchema": { "type": "string", "enum": [ "confirmed", "tentative", "cancelled" ] }, "agendaIsoDateTime": { "type": "string", "format": "date-time" }, "agendaItemHttpSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "organizationId": { "type": "string", "format": "uuid" }, "source": { "$ref": "#/components/schemas/agendaBlockSourceSchema" }, "state": { "$ref": "#/components/schemas/agendaBlockStateSchema" }, "visibility": { "$ref": "#/components/schemas/agendaItemVisibilitySchema" }, "ownerUserId": { "type": [ "string", "null" ], "format": "uuid" }, "title": { "type": "string", "minLength": 1 }, "description": { "type": [ "string", "null" ] }, "descriptionHtml": { "$ref": "#/components/schemas/agendaRichDescriptionSchema" }, "location": { "type": [ "string", "null" ] }, "onlineMeetingUrl": { "type": [ "string", "null" ], "format": "uri" }, "transparency": { "$ref": "#/components/schemas/agendaTransparencySchema" }, "startAt": { "$ref": "#/components/schemas/agendaIsoDateTime" }, "endAt": { "$ref": "#/components/schemas/agendaIsoDateTime" }, "isAllDay": { "type": "boolean" }, "taskId": { "type": [ "string", "null" ], "format": "uuid" }, "taskNumber": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 }, "taskDisplayNumber": { "type": [ "string", "null" ], "minLength": 1 }, "taskTitle": { "type": [ "string", "null" ], "minLength": 1 }, "priority": { "anyOf": [ { "$ref": "#/components/schemas/taskPrioritySchema" }, { "type": "null" } ] }, "taskStatus": { "anyOf": [ { "$ref": "#/components/schemas/taskStatusSchema" }, { "type": "null" } ] }, "users": { "type": "array", "items": { "$ref": "#/components/schemas/agendaItemUserSchema" } }, "assignees": { "type": "array", "items": { "$ref": "#/components/schemas/agendaItemUserSchema" } }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "createdAt": { "$ref": "#/components/schemas/agendaIsoDateTime" }, "updatedAt": { "$ref": "#/components/schemas/agendaIsoDateTime" }, "timeZone": { "anyOf": [ { "$ref": "#/components/schemas/agendaTimeZoneSchema" }, { "type": "null" } ] }, "organizerUserId": { "type": [ "string", "null" ], "format": "uuid" }, "organizerEmail": { "type": [ "string", "null" ], "format": "email" }, "organizerDisplayName": { "type": [ "string", "null" ] }, "recurrenceParentItemId": { "type": [ "string", "null" ], "format": "uuid" }, "recurrenceIdAt": { "anyOf": [ { "$ref": "#/components/schemas/agendaIsoDateTime" }, { "type": "null" } ] }, "recurrenceRule": { "anyOf": [ { "$ref": "#/components/schemas/agendaRecurrenceRuleSchema" }, { "type": "null" } ] }, "recurrenceDates": { "type": "array", "items": { "$ref": "#/components/schemas/agendaRecurrenceDateSchema" } }, "reminders": { "type": "array", "items": { "$ref": "#/components/schemas/agendaReminderSchema" } }, "attendees": { "type": "array", "items": { "$ref": "#/components/schemas/agendaAttendeeSchema" } }, "calendarId": { "type": "string", "format": "uuid" }, "isReadOnly": { "type": "boolean" }, "reason": { "anyOf": [ { "$ref": "#/components/schemas/agendaSuggestionReasonSchema" }, { "type": "null" } ] }, "confidence": { "type": [ "number", "null" ], "minimum": 0, "maximum": 1 } }, "required": [ "id", "organizationId", "source", "state", "visibility", "ownerUserId", "title", "description", "descriptionHtml", "location", "onlineMeetingUrl", "transparency", "startAt", "endAt", "isAllDay", "taskId", "taskNumber", "taskDisplayNumber", "taskTitle", "priority", "taskStatus", "users", "assignees", "createdBy", "createdAt", "updatedAt", "timeZone", "organizerUserId", "organizerEmail", "organizerDisplayName", "recurrenceParentItemId", "recurrenceIdAt", "recurrenceRule", "recurrenceDates", "reminders", "attendees", "calendarId", "isReadOnly", "reason", "confidence" ] }, "agendaItemMoveInputSchema": { "type": "object", "properties": { "startAt": { "$ref": "#/components/schemas/agendaIsoDateTime" }, "endAt": { "$ref": "#/components/schemas/agendaIsoDateTime" }, "isAllDay": { "type": "boolean", "default": false }, "occurrenceStartAt": { "allOf": [ { "$ref": "#/components/schemas/agendaIsoDateTime" } ] }, "scope": { "type": "string", "enum": [ "occurrence", "series" ], "default": "series" } }, "required": [ "startAt", "endAt", "isAllDay", "scope" ] }, "agendaItemUserSchema": { "$ref": "#/components/schemas/taskAssigneeSchema" }, "agendaItemVisibilitySchema": { "type": "string", "enum": [ "public", "private" ] }, "agendaParticipationStatusSchema": { "type": "string", "enum": [ "needs-action", "accepted", "declined", "tentative", "delegated" ] }, "agendaRecurrenceDateKindSchema": { "type": "string", "enum": [ "rdate", "exdate" ] }, "agendaRecurrenceDateSchema": { "type": "object", "properties": { "id": { "type": [ "string", "null" ], "format": "uuid" }, "kind": { "$ref": "#/components/schemas/agendaRecurrenceDateKindSchema" }, "occursAt": { "$ref": "#/components/schemas/agendaIsoDateTime" } }, "required": [ "id", "kind", "occursAt" ] }, "agendaRecurrenceFrequencySchema": { "type": "string", "enum": [ "daily", "weekly", "monthly", "yearly" ] }, "agendaRecurrenceRuleSchema": { "type": "object", "properties": { "count": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 }, "frequency": { "$ref": "#/components/schemas/agendaRecurrenceFrequencySchema" }, "intervalCount": { "type": "integer", "exclusiveMinimum": 0 }, "monthDay": { "type": [ "integer", "null" ], "minimum": 1, "maximum": 31 }, "monthOrdinal": { "anyOf": [ { "anyOf": [ { "type": "string" }, { "type": "integer", "enum": [ 1 ] }, { "type": "integer", "enum": [ 2 ] }, { "type": "integer", "enum": [ 3 ] }, { "type": "integer", "enum": [ 4 ] } ] }, { "type": "null" } ] }, "monthWeekday": { "anyOf": [ { "$ref": "#/components/schemas/agendaWeekdaySchema" }, { "type": "null" } ] }, "untilAt": { "anyOf": [ { "$ref": "#/components/schemas/agendaIsoDateTime" }, { "type": "null" } ] }, "weekDays": { "type": "array", "items": { "$ref": "#/components/schemas/agendaWeekdaySchema" }, "maxItems": 7 } }, "required": [ "count", "frequency", "intervalCount", "monthDay", "monthOrdinal", "monthWeekday", "untilAt", "weekDays" ] }, "agendaReminderMethodSchema": { "type": "string", "enum": [ "display", "email" ] }, "agendaReminderSchema": { "type": "object", "properties": { "id": { "type": [ "string", "null" ], "format": "uuid" }, "method": { "$ref": "#/components/schemas/agendaReminderMethodSchema" }, "offsetMinutes": { "type": "integer", "minimum": -40320, "maximum": 40320 } }, "required": [ "id", "method", "offsetMinutes" ] }, "agendaRichDescriptionSchema": { "type": [ "string", "null" ], "maxLength": 50000 }, "agendaSuggestionReasonSchema": { "type": "object", "properties": { "title": { "type": "string", "minLength": 1 }, "description": { "type": "string", "minLength": 1 }, "signals": { "type": "array", "items": { "type": "string", "minLength": 1 }, "maxItems": 8 } }, "required": [ "title", "description", "signals" ] }, "agendaTimeZoneSchema": { "type": "string", "minLength": 1, "maxLength": 120 }, "agendaTransparencySchema": { "type": "string", "enum": [ "opaque", "transparent" ] }, "agendaWeekdaySchema": { "type": "string", "enum": [ "mo", "tu", "we", "th", "fr", "sa", "su" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "taskAssigneeSchema": { "type": "object", "properties": { "userId": { "type": "string", "format": "uuid" }, "displayName": { "type": "string", "minLength": 1 }, "avatarUrl": { "type": [ "string", "null" ], "format": "uri" }, "disabledAt": { "type": [ "string", "null" ] } }, "required": [ "userId", "displayName", "avatarUrl", "disabledAt" ] }, "taskPrioritySchema": { "type": "string", "enum": [ "low", "medium", "high", "urgent" ] }, "taskStatusSchema": { "type": "string", "enum": [ "todo", "in_progress", "on_hold", "done" ] } } ``` # List chat messages. (/openapi/listChatMessages) Return a latest-relative page of messages. Offset zero is the newest window; each page is chronological and increasing offsets retrieve older windows. ## GET /v1/organizations/{organizationId}/ai/threads/{threadId}/messages ```json { "operationId": "listChatMessages", "summary": "List chat messages.", "description": "Return a latest-relative page of messages. Offset zero is the newest window; each page is chronological and increasing offsets retrieve older windows.", "tags": [ "AI" ], "parameters": [ { "in": "query", "name": "limit", "required": true, "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 50 }, "example": 1 }, { "in": "query", "name": "offset", "required": true, "schema": { "type": "integer", "minimum": 0, "default": 0 }, "example": 1 }, { "in": "path", "name": "organizationId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "path", "name": "threadId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns a paginated chronological message window.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/chatMessagesListResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "chatMessageItemSchema": { "type": "object", "properties": { "clientMessageId": { "type": [ "string", "null" ], "minLength": 1, "maxLength": 240 }, "createdAt": { "type": "string", "format": "date-time" }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "id": { "type": "string", "format": "uuid" }, "metadata": { "type": "object", "additionalProperties": { "$ref": "#/components/schemas/jsonValueSchema" }, "propertyNames": { "type": "string" } }, "organizationId": { "type": [ "string", "null" ], "format": "uuid" }, "parts": { "type": "array", "items": {} }, "role": { "type": "string", "enum": [ "system", "user", "assistant", "tool" ] }, "status": { "type": "string", "enum": [ "complete", "partial", "failed" ] }, "threadId": { "type": "string", "format": "uuid" } }, "required": [ "clientMessageId", "createdAt", "createdBy", "id", "metadata", "organizationId", "parts", "role", "status", "threadId" ] }, "chatMessagesListResponseSchema": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/chatMessageItemSchema" } }, "pagination": { "type": "object", "properties": { "hasMore": { "type": "boolean" }, "limit": { "type": "integer", "minimum": 1, "maximum": 100 }, "offset": { "type": "integer", "minimum": 0 }, "total": { "type": "integer", "minimum": 0 } }, "required": [ "hasMore", "limit", "offset", "total" ] } }, "required": [ "data", "pagination" ] }, "jsonValueSchema": { "anyOf": [ { "type": "string" }, { "type": "number" }, { "type": "boolean" }, { "type": "null" }, { "type": "array", "items": { "$ref": "#/components/schemas/jsonValueSchema" } }, { "type": "object", "additionalProperties": { "$ref": "#/components/schemas/jsonValueSchema" }, "propertyNames": { "type": "string" } } ] } } ``` # Append a chat message. (/openapi/appendChatMessage) Append a message to an organization chat thread. ## POST /v1/organizations/{organizationId}/ai/threads/{threadId}/messages ```json { "operationId": "appendChatMessage", "summary": "Append a chat message.", "description": "Append a message to an organization chat thread.", "tags": [ "AI" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "path", "name": "threadId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/appendChatMessageBodySchema" } } }, "description": "The chat message payload to append.", "required": true }, "responses": { "201": { "description": "Returns the created chat message.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/appendChatMessageResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "appendChatMessageBodySchema": { "type": "object", "properties": { "clientMessageId": { "type": [ "string", "null" ], "minLength": 1, "maxLength": 240, "default": null }, "metadata": { "type": "object", "additionalProperties": { "$ref": "#/components/schemas/jsonValueSchema" }, "propertyNames": { "type": "string" }, "default": {} }, "parts": { "type": "array", "items": {}, "minItems": 1 }, "role": { "type": "string", "enum": [ "system", "user", "assistant", "tool" ] }, "status": { "type": "string", "enum": [ "complete", "partial", "failed" ], "default": "complete" } }, "required": [ "clientMessageId", "metadata", "parts", "role", "status" ] }, "appendChatMessageResponseSchema": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/chatMessageItemSchema" } }, "required": [ "data" ] }, "chatMessageItemSchema": { "type": "object", "properties": { "clientMessageId": { "type": [ "string", "null" ], "minLength": 1, "maxLength": 240 }, "createdAt": { "type": "string", "format": "date-time" }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "id": { "type": "string", "format": "uuid" }, "metadata": { "type": "object", "additionalProperties": { "$ref": "#/components/schemas/jsonValueSchema" }, "propertyNames": { "type": "string" } }, "organizationId": { "type": [ "string", "null" ], "format": "uuid" }, "parts": { "type": "array", "items": {} }, "role": { "type": "string", "enum": [ "system", "user", "assistant", "tool" ] }, "status": { "type": "string", "enum": [ "complete", "partial", "failed" ] }, "threadId": { "type": "string", "format": "uuid" } }, "required": [ "clientMessageId", "createdAt", "createdBy", "id", "metadata", "organizationId", "parts", "role", "status", "threadId" ] }, "jsonValueSchema": { "anyOf": [ { "type": "string" }, { "type": "number" }, { "type": "boolean" }, { "type": "null" }, { "type": "array", "items": { "$ref": "#/components/schemas/jsonValueSchema" } }, { "type": "object", "additionalProperties": { "$ref": "#/components/schemas/jsonValueSchema" }, "propertyNames": { "type": "string" } } ] } } ``` # List brands. (/openapi/listBrands) Return brands for an organization the authenticated user can access. ## GET /v1/organizations/{organizationId}/brands ```json { "operationId": "listBrands", "summary": "List brands.", "description": "Return brands for an organization the authenticated user can access.", "tags": [ "Assets" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns paginated brands.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/brandsListResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "brandsListResponseSchema": { "type": "object" }, "organizationIdSchema": { "type": "string", "format": "uuid" } } ``` # Create a brand. (/openapi/createBrand) Create a brand for the specified organization. ## POST /v1/organizations/{organizationId}/brands ```json { "operationId": "createBrand", "summary": "Create a brand.", "description": "Create a brand for the specified organization.", "tags": [ "Assets" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/brandInputSchema" } } }, "description": "The brand payload." }, "responses": { "201": { "description": "Returns the created brand.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/brandSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "brandIdSchema": { "type": "string", "format": "uuid" }, "brandImageIdSchema": { "type": "string", "format": "uuid" }, "brandImageSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/brandImageIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "brandId": { "$ref": "#/components/schemas/brandIdSchema" }, "url": { "type": "string", "format": "uri" }, "altText": { "type": [ "string", "null" ] }, "position": { "type": "integer", "minimum": 0 }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "brandId", "url", "altText", "position", "createdAt", "updatedAt" ] }, "brandInputSchema": { "type": "object", "properties": { "name": { "type": "string", "minLength": 1, "maxLength": 240 }, "slug": { "type": "string", "minLength": 1, "maxLength": 160, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" }, "description": { "allOf": [ { "$ref": "#/components/schemas/nullableLongText" } ] }, "website": { "allOf": [ { "$ref": "#/components/schemas/nullableUrlInputSchema" } ] } }, "required": [ "name" ] }, "brandSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/brandIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 240 }, "slug": { "type": "string", "minLength": 1, "maxLength": 160, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" }, "description": { "type": [ "string", "null" ] }, "website": { "type": [ "string", "null" ] }, "logoUrl": { "type": [ "string", "null" ], "format": "uri" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" }, "tags": { "type": "array", "items": { "$ref": "#/components/schemas/organizationTagSchema" } }, "images": { "type": "array", "items": { "$ref": "#/components/schemas/brandImageSchema" } } }, "required": [ "id", "organizationId", "name", "slug", "description", "website", "logoUrl", "createdAt", "updatedAt", "tags", "images" ] }, "nullableLongText": { "type": [ "string", "null" ], "maxLength": 4000 }, "nullableUrlInputSchema": { "anyOf": [ { "type": "string", "maxLength": 400, "format": "uri" }, { "type": "null" } ] }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "organizationTagSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/tagIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "kind": { "$ref": "#/components/schemas/tagKindSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 80 }, "slug": { "$ref": "#/components/schemas/tagSlugSchema" }, "color": { "anyOf": [ { "$ref": "#/components/schemas/tagColorSchema" }, { "type": "null" } ] }, "description": { "type": [ "string", "null" ] }, "position": { "type": "integer", "minimum": 0 }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "kind", "name", "slug", "color", "description", "position", "createdAt", "updatedAt" ] }, "tagColorSchema": { "type": "string", "pattern": "^#(?:[0-9a-f]{3}|[0-9a-f]{6})$" }, "tagIdSchema": { "type": "string", "format": "uuid" }, "tagKindSchema": { "type": "string", "enum": [ "task", "customer", "product" ] }, "tagSlugSchema": { "type": "string", "minLength": 1, "maxLength": 160, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" } } ``` # List customer assets. (/openapi/listCustomerAssets) Return customer assets for an organization. ## GET /v1/organizations/{organizationId}/customer-assets ```json { "operationId": "listCustomerAssets", "summary": "List customer assets.", "description": "Return customer assets for an organization.", "tags": [ "Assets" ], "parameters": [ { "in": "query", "name": "customerId", "required": false, "schema": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 }, "example": 123 }, { "in": "query", "name": "limit", "required": false, "schema": { "type": "string" }, "example": "example" }, { "in": "query", "name": "offset", "required": false, "schema": { "type": "string" }, "example": "example" }, { "in": "query", "name": "q", "required": false, "schema": { "type": "string" }, "example": "example" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns paginated customer assets.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/customerAssetsListResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "customerAssetsListResponseSchema": { "type": "object" }, "organizationIdSchema": { "type": "string", "format": "uuid" } } ``` # Create a customer asset. (/openapi/createCustomerAsset) Create a customer asset for the specified organization. ## POST /v1/organizations/{organizationId}/customer-assets ```json { "operationId": "createCustomerAsset", "summary": "Create a customer asset.", "description": "Create a customer asset for the specified organization.", "tags": [ "Assets" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/customerAssetInputSchema" } } }, "description": "The customer asset payload." }, "responses": { "201": { "description": "Returns the created customer asset.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/customerAssetSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "assetCustomAttributeSchema": { "type": "object", "properties": { "valueType": { "$ref": "#/components/schemas/customAttributeValueTypeSchema" }, "textValue": { "type": [ "string", "null" ] }, "numberValue": { "type": [ "number", "null" ] }, "booleanValue": { "type": [ "boolean", "null" ] }, "dateValue": { "$ref": "#/components/schemas/nullableDate" }, "urlValue": { "type": [ "string", "null" ] }, "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "productId": { "anyOf": [ { "$ref": "#/components/schemas/productIdSchema" }, { "type": "null" } ] }, "customerAssetId": { "anyOf": [ { "$ref": "#/components/schemas/customerAssetIdSchema" }, { "type": "null" } ] }, "key": { "type": "string", "minLength": 1, "maxLength": 120 }, "label": { "type": "string", "minLength": 1, "maxLength": 160 }, "position": { "type": "integer", "minimum": 0 }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "valueType", "textValue", "numberValue", "booleanValue", "dateValue", "urlValue", "id", "organizationId", "productId", "customerAssetId", "key", "label", "position", "createdAt", "updatedAt" ] }, "assetImageIdSchema": { "type": "string", "format": "uuid" }, "assetImageSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/assetImageIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "productId": { "anyOf": [ { "$ref": "#/components/schemas/productIdSchema" }, { "type": "null" } ] }, "url": { "type": "string", "format": "uri" }, "altText": { "type": [ "string", "null" ] }, "position": { "type": "integer", "minimum": 0 }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "productId", "url", "altText", "position", "createdAt", "updatedAt" ] }, "brandIdSchema": { "type": "string", "format": "uuid" }, "currencyCodeSchema": { "type": "string", "pattern": "^[A-Z]{3}$" }, "customAttributeValueTypeSchema": { "type": "string", "enum": [ "text", "number", "boolean", "date", "url" ] }, "customerAddressSchema": { "type": "object", "properties": { "line1": { "$ref": "#/components/schemas/nullableShortText" }, "line2": { "$ref": "#/components/schemas/nullableShortText" }, "city": { "$ref": "#/components/schemas/nullableShortText" }, "state": { "$ref": "#/components/schemas/nullableShortText" }, "postalCode": { "$ref": "#/components/schemas/nullableShortText" }, "country": { "$ref": "#/components/schemas/nullableShortText" } }, "required": [ "line1", "line2", "city", "state", "postalCode", "country" ] }, "customerAssetConditionSchema": { "type": "string", "enum": [ "new", "good", "fair", "poor", "unknown" ] }, "customerAssetFileIdSchema": { "type": "string", "format": "uuid" }, "customerAssetFileSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/customerAssetFileIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "customerAssetId": { "$ref": "#/components/schemas/customerAssetIdSchema" }, "url": { "type": "string", "format": "uri" }, "fileName": { "type": "string", "minLength": 1 }, "contentType": { "type": "string", "minLength": 1 }, "byteSize": { "type": "integer", "exclusiveMinimum": 0 }, "altText": { "type": [ "string", "null" ] }, "position": { "type": "integer", "minimum": 0 }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "customerAssetId", "url", "fileName", "contentType", "byteSize", "altText", "position", "createdBy", "createdAt", "updatedAt" ] }, "customerAssetIdSchema": { "type": "integer", "exclusiveMinimum": 0 }, "customerAssetInputSchema": { "type": "object", "properties": { "productId": { "$ref": "#/components/schemas/productIdSchema" }, "customerId": { "$ref": "#/components/schemas/customerIdSchema" }, "customerLocationId": { "type": [ "string", "null" ], "format": "uuid" }, "serialNumber": { "allOf": [ { "$ref": "#/components/schemas/nullableShortText" } ] }, "assetTag": { "allOf": [ { "$ref": "#/components/schemas/nullableShortText" } ] }, "barcode": { "allOf": [ { "$ref": "#/components/schemas/nullableShortText" } ] }, "installedOn": { "allOf": [ { "$ref": "#/components/schemas/nullableDate" } ] }, "warrantyExpiresOn": { "allOf": [ { "$ref": "#/components/schemas/nullableDate" } ] }, "condition": { "allOf": [ { "$ref": "#/components/schemas/customerAssetConditionSchema" } ] }, "status": { "allOf": [ { "$ref": "#/components/schemas/customerAssetStatusSchema" } ] }, "description": { "allOf": [ { "$ref": "#/components/schemas/nullableLongText" } ] }, "purchasedOn": { "allOf": [ { "$ref": "#/components/schemas/nullableDate" } ] }, "purchasePrice": { "allOf": [ { "$ref": "#/components/schemas/nullableMoney" } ] }, "currency": { "allOf": [ { "$ref": "#/components/schemas/nullableCurrency" } ] }, "serviceIntervalDays": { "type": [ "integer", "null" ], "minimum": 1 }, "warrantyProvider": { "allOf": [ { "$ref": "#/components/schemas/nullableShortText" } ] }, "warrantyReference": { "allOf": [ { "$ref": "#/components/schemas/nullableShortText" } ] }, "quoteId": { "type": [ "string", "null" ], "format": "uuid" }, "invoiceId": { "type": [ "string", "null" ], "format": "uuid" } }, "required": [ "productId", "customerId" ] }, "customerAssetSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/customerAssetIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "productId": { "$ref": "#/components/schemas/productIdSchema" }, "product": { "anyOf": [ { "$ref": "#/components/schemas/productListItemSchema" }, { "type": "null" } ] }, "customerId": { "$ref": "#/components/schemas/customerIdSchema" }, "customerName": { "type": [ "string", "null" ] }, "customerLocationId": { "type": [ "string", "null" ], "format": "uuid" }, "customerLocation": { "anyOf": [ { "$ref": "#/components/schemas/customerLocationSchema" }, { "type": "null" } ] }, "serialNumber": { "type": [ "string", "null" ] }, "assetTag": { "type": [ "string", "null" ] }, "barcode": { "type": [ "string", "null" ] }, "installedOn": { "$ref": "#/components/schemas/nullableDate" }, "warrantyExpiresOn": { "$ref": "#/components/schemas/nullableDate" }, "condition": { "$ref": "#/components/schemas/customerAssetConditionSchema" }, "status": { "$ref": "#/components/schemas/customerAssetStatusSchema" }, "description": { "type": [ "string", "null" ] }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "updatedBy": { "type": [ "string", "null" ], "format": "uuid" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" }, "tags": { "type": "array", "items": { "$ref": "#/components/schemas/organizationTagSchema" } }, "files": { "type": "array", "items": { "$ref": "#/components/schemas/customerAssetFileSchema" } }, "customAttributes": { "type": "array", "items": { "$ref": "#/components/schemas/assetCustomAttributeSchema" } }, "purchasedOn": { "$ref": "#/components/schemas/nullableDate" }, "purchasePrice": { "type": [ "number", "null" ] }, "currency": { "type": [ "string", "null" ] }, "serviceIntervalDays": { "type": [ "integer", "null" ], "minimum": 1 }, "warrantyProvider": { "type": [ "string", "null" ] }, "warrantyReference": { "type": [ "string", "null" ] }, "quoteId": { "type": [ "string", "null" ], "format": "uuid" }, "invoiceId": { "type": [ "string", "null" ], "format": "uuid" } }, "required": [ "id", "organizationId", "productId", "product", "customerId", "customerName", "customerLocationId", "customerLocation", "serialNumber", "assetTag", "barcode", "installedOn", "warrantyExpiresOn", "condition", "status", "description", "createdBy", "updatedBy", "createdAt", "updatedAt", "tags", "files", "customAttributes", "purchasedOn", "purchasePrice", "currency", "serviceIntervalDays", "warrantyProvider", "warrantyReference", "quoteId", "invoiceId" ] }, "customerAssetStatusSchema": { "type": "string", "enum": [ "active", "inactive", "maintenance", "retired", "archived" ] }, "customerIdSchema": { "type": "integer", "exclusiveMinimum": 0 }, "customerLocationSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "customerId": { "$ref": "#/components/schemas/customerIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 120 }, "address": { "$ref": "#/components/schemas/customerAddressSchema" }, "isPrimary": { "type": "boolean" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "customerId", "organizationId", "name", "address", "isPrimary", "createdAt", "updatedAt" ] }, "iconNameSchema": { "type": "string", "minLength": 1, "maxLength": 120, "pattern": "^[A-Za-z0-9]+Icon$" }, "inventoryLocationIdSchema": { "type": "string", "format": "uuid" }, "nullableCurrency": { "anyOf": [ { "$ref": "#/components/schemas/currencyCodeSchema" }, { "type": "null" } ] }, "nullableDate": { "type": [ "string", "null" ], "format": "date" }, "nullableLongText": { "type": [ "string", "null" ], "maxLength": 4000 }, "nullableMoney": { "type": [ "number", "null" ], "minimum": 0 }, "nullableShortText": { "type": [ "string", "null" ], "maxLength": 240 }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "organizationTagSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/tagIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "kind": { "$ref": "#/components/schemas/tagKindSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 80 }, "slug": { "$ref": "#/components/schemas/tagSlugSchema" }, "color": { "anyOf": [ { "$ref": "#/components/schemas/tagColorSchema" }, { "type": "null" } ] }, "description": { "type": [ "string", "null" ] }, "position": { "type": "integer", "minimum": 0 }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "kind", "name", "slug", "color", "description", "position", "createdAt", "updatedAt" ] }, "productCategorySchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "parentId": { "type": [ "string", "null" ], "format": "uuid" }, "name": { "type": "string", "minLength": 1, "maxLength": 120 }, "slug": { "$ref": "#/components/schemas/slugSchema" }, "icon": { "$ref": "#/components/schemas/iconNameSchema" }, "description": { "type": [ "string", "null" ] }, "position": { "type": "integer", "minimum": 0 }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "parentId", "name", "slug", "icon", "description", "position", "createdAt", "updatedAt" ] }, "productIdSchema": { "type": "string", "format": "uuid" }, "productListItemSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/productIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "categoryIds": { "type": "array", "items": { "type": "string", "format": "uuid" } }, "categories": { "type": "array", "items": { "$ref": "#/components/schemas/productCategorySchema" } }, "name": { "type": "string", "minLength": 1 }, "slug": { "$ref": "#/components/schemas/slugSchema" }, "brand": { "type": [ "object", "null" ], "properties": { "id": { "$ref": "#/components/schemas/brandIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 240 }, "slug": { "type": "string", "minLength": 1, "maxLength": 160, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" }, "description": { "type": [ "string", "null" ] }, "website": { "type": [ "string", "null" ] }, "logoUrl": { "type": [ "string", "null" ], "format": "uri" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "name", "slug", "description", "website", "logoUrl", "createdAt", "updatedAt" ] }, "manufacturer": { "type": [ "string", "null" ] }, "sku": { "type": [ "string", "null" ] }, "ean": { "type": [ "string", "null" ] }, "defaultSellingPrice": { "type": [ "number", "null" ] }, "vatPercentage": { "type": [ "number", "null" ] }, "status": { "$ref": "#/components/schemas/productStatusSchema" }, "updatedAt": { "type": "string", "format": "date-time" }, "tags": { "type": "array", "items": { "$ref": "#/components/schemas/organizationTagSchema" } }, "images": { "type": "array", "items": { "$ref": "#/components/schemas/assetImageSchema" } }, "suppliers": { "type": "array", "items": { "$ref": "#/components/schemas/productSupplierLinkSchema" } }, "manufacturerPartNumber": { "type": [ "string", "null" ] }, "currency": { "type": [ "string", "null" ] }, "unitOfMeasure": { "type": [ "string", "null" ] }, "stockStatus": { "anyOf": [ { "$ref": "#/components/schemas/stockStatusSchema" }, { "type": "null" } ] } }, "required": [ "id", "organizationId", "categoryIds", "categories", "name", "slug", "brand", "manufacturer", "sku", "ean", "defaultSellingPrice", "vatPercentage", "status", "updatedAt", "tags", "images", "suppliers", "manufacturerPartNumber", "currency", "unitOfMeasure" ] }, "productStatusSchema": { "type": "string", "enum": [ "active", "discontinued", "archived" ] }, "productSupplierLinkSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "productId": { "$ref": "#/components/schemas/productIdSchema" }, "supplierId": { "$ref": "#/components/schemas/supplierIdSchema" }, "supplier": { "anyOf": [ { "$ref": "#/components/schemas/supplierSchema" }, { "type": "null" } ] }, "supplierSku": { "type": [ "string", "null" ] }, "orderCode": { "type": [ "string", "null" ] }, "unitCost": { "type": [ "number", "null" ] }, "currency": { "type": [ "string", "null" ] }, "isPreferred": { "type": "boolean" }, "minOrderQuantity": { "type": [ "number", "null" ] }, "orderMultiple": { "type": [ "number", "null" ] }, "leadTimeDays": { "type": [ "integer", "null" ] }, "isActive": { "type": "boolean" }, "externalId": { "type": [ "string", "null" ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "productId", "supplierId", "supplier", "supplierSku", "orderCode", "unitCost", "currency", "isPreferred", "minOrderQuantity", "orderMultiple", "leadTimeDays", "isActive", "externalId", "createdAt", "updatedAt" ] }, "slugSchema": { "type": "string", "minLength": 1, "maxLength": 160, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" }, "stockStatusSchema": { "type": "string", "enum": [ "not_tracked", "discontinued", "out_of_stock", "backordered", "low_stock", "in_stock" ] }, "supplierIdSchema": { "type": "string", "format": "uuid" }, "supplierSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/supplierIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 240 }, "email": { "type": [ "string", "null" ] }, "phone": { "type": [ "string", "null" ] }, "website": { "type": [ "string", "null" ] }, "notes": { "type": [ "string", "null" ] }, "status": { "$ref": "#/components/schemas/supplierStatusSchema" }, "code": { "type": [ "string", "null" ] }, "accountNumber": { "type": [ "string", "null" ] }, "kvk": { "type": [ "string", "null" ] }, "btw": { "type": [ "string", "null" ] }, "orderingEmail": { "type": [ "string", "null" ] }, "billingEmail": { "type": [ "string", "null" ] }, "addressLine1": { "type": [ "string", "null" ] }, "addressLine2": { "type": [ "string", "null" ] }, "addressCity": { "type": [ "string", "null" ] }, "addressState": { "type": [ "string", "null" ] }, "addressPostalCode": { "type": [ "string", "null" ] }, "addressCountry": { "type": [ "string", "null" ] }, "currency": { "type": [ "string", "null" ] }, "paymentTermsDays": { "type": [ "integer", "null" ] }, "defaultLeadTimeDays": { "type": [ "integer", "null" ] }, "defaultInventoryLocationId": { "anyOf": [ { "$ref": "#/components/schemas/inventoryLocationIdSchema" }, { "type": "null" } ] }, "source": { "type": [ "string", "null" ] }, "externalId": { "type": [ "string", "null" ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "name", "email", "phone", "website", "notes", "status", "code", "accountNumber", "kvk", "btw", "orderingEmail", "billingEmail", "addressLine1", "addressLine2", "addressCity", "addressState", "addressPostalCode", "addressCountry", "currency", "paymentTermsDays", "defaultLeadTimeDays", "defaultInventoryLocationId", "source", "externalId", "createdAt", "updatedAt" ] }, "supplierStatusSchema": { "type": "string", "enum": [ "active", "inactive", "archived" ] }, "tagColorSchema": { "type": "string", "pattern": "^#(?:[0-9a-f]{3}|[0-9a-f]{6})$" }, "tagIdSchema": { "type": "string", "format": "uuid" }, "tagKindSchema": { "type": "string", "enum": [ "task", "customer", "product" ] }, "tagSlugSchema": { "type": "string", "minLength": 1, "maxLength": 160, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" } } ``` # List product categories. (/openapi/listProductCategories) Return product categories for an organization. ## GET /v1/organizations/{organizationId}/product-categories ```json { "operationId": "listProductCategories", "summary": "List product categories.", "description": "Return product categories for an organization.", "tags": [ "Assets" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns paginated product categories.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/productCategoriesListResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "organizationIdSchema": { "type": "string", "format": "uuid" }, "productCategoriesListResponseSchema": { "type": "object" } } ``` # Create a product category. (/openapi/createProductCategory) Create a product category for the specified organization. ## POST /v1/organizations/{organizationId}/product-categories ```json { "operationId": "createProductCategory", "summary": "Create a product category.", "description": "Create a product category for the specified organization.", "tags": [ "Assets" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/productCategoryInputSchema" } } }, "description": "The product category payload." }, "responses": { "201": { "description": "Returns the created product category.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/productCategorySchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "iconNameInputSchema": { "type": "string", "minLength": 1, "maxLength": 120, "pattern": "^[A-Za-z0-9]+Icon$" }, "iconNameSchema": { "type": "string", "minLength": 1, "maxLength": 120, "pattern": "^[A-Za-z0-9]+Icon$" }, "nullableLongText": { "type": [ "string", "null" ], "maxLength": 4000 }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "productCategoryInputSchema": { "type": "object", "properties": { "parentId": { "type": [ "string", "null" ], "format": "uuid" }, "name": { "type": "string", "minLength": 1, "maxLength": 120 }, "slug": { "allOf": [ { "$ref": "#/components/schemas/slugInputSchema" } ] }, "icon": { "allOf": [ { "$ref": "#/components/schemas/iconNameInputSchema" } ] }, "description": { "allOf": [ { "$ref": "#/components/schemas/nullableLongText" } ] }, "position": { "type": "integer", "minimum": 0 } }, "required": [ "name" ] }, "productCategorySchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "parentId": { "type": [ "string", "null" ], "format": "uuid" }, "name": { "type": "string", "minLength": 1, "maxLength": 120 }, "slug": { "$ref": "#/components/schemas/slugSchema" }, "icon": { "$ref": "#/components/schemas/iconNameSchema" }, "description": { "type": [ "string", "null" ] }, "position": { "type": "integer", "minimum": 0 }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "parentId", "name", "slug", "icon", "description", "position", "createdAt", "updatedAt" ] }, "slugInputSchema": { "type": "string", "minLength": 1, "maxLength": 160, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" }, "slugSchema": { "type": "string", "minLength": 1, "maxLength": 160, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" } } ``` # List products. (/openapi/listProducts) Return products for an organization the authenticated user can access. ## GET /v1/organizations/{organizationId}/products ```json { "operationId": "listProducts", "summary": "List products.", "description": "Return products for an organization the authenticated user can access.", "tags": [ "Assets" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns paginated products.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/productsListResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "organizationIdSchema": { "type": "string", "format": "uuid" }, "productsListResponseSchema": { "type": "object" } } ``` # Create a product. (/openapi/createProduct) Create a product for the specified organization. ## POST /v1/organizations/{organizationId}/products ```json { "operationId": "createProduct", "summary": "Create a product.", "description": "Create a product for the specified organization.", "tags": [ "Assets" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/productInputSchema" } } }, "description": "The product payload.", "required": true }, "responses": { "201": { "description": "Returns the created product.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/productSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "assetCustomAttributeSchema": { "type": "object", "properties": { "valueType": { "$ref": "#/components/schemas/customAttributeValueTypeSchema" }, "textValue": { "type": [ "string", "null" ] }, "numberValue": { "type": [ "number", "null" ] }, "booleanValue": { "type": [ "boolean", "null" ] }, "dateValue": { "$ref": "#/components/schemas/nullableDate" }, "urlValue": { "type": [ "string", "null" ] }, "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "productId": { "anyOf": [ { "$ref": "#/components/schemas/productIdSchema" }, { "type": "null" } ] }, "customerAssetId": { "anyOf": [ { "$ref": "#/components/schemas/customerAssetIdSchema" }, { "type": "null" } ] }, "key": { "type": "string", "minLength": 1, "maxLength": 120 }, "label": { "type": "string", "minLength": 1, "maxLength": 160 }, "position": { "type": "integer", "minimum": 0 }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "valueType", "textValue", "numberValue", "booleanValue", "dateValue", "urlValue", "id", "organizationId", "productId", "customerAssetId", "key", "label", "position", "createdAt", "updatedAt" ] }, "assetImageIdSchema": { "type": "string", "format": "uuid" }, "assetImageSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/assetImageIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "productId": { "anyOf": [ { "$ref": "#/components/schemas/productIdSchema" }, { "type": "null" } ] }, "url": { "type": "string", "format": "uri" }, "altText": { "type": [ "string", "null" ] }, "position": { "type": "integer", "minimum": 0 }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "productId", "url", "altText", "position", "createdAt", "updatedAt" ] }, "brandIdSchema": { "type": "string", "format": "uuid" }, "currencyCodeSchema": { "type": "string", "pattern": "^[A-Z]{3}$" }, "customAttributeValueTypeSchema": { "type": "string", "enum": [ "text", "number", "boolean", "date", "url" ] }, "customerAssetIdSchema": { "type": "integer", "exclusiveMinimum": 0 }, "digitalDeliveryKindSchema": { "type": "string", "enum": [ "file", "license_key", "link", "manual" ] }, "iconNameSchema": { "type": "string", "minLength": 1, "maxLength": 120, "pattern": "^[A-Za-z0-9]+Icon$" }, "inventoryLocationIdSchema": { "type": "string", "format": "uuid" }, "nullableCurrency": { "anyOf": [ { "$ref": "#/components/schemas/currencyCodeSchema" }, { "type": "null" } ] }, "nullableDate": { "type": [ "string", "null" ], "format": "date" }, "nullableLongText": { "type": [ "string", "null" ], "maxLength": 4000 }, "nullableMoney": { "type": [ "number", "null" ], "minimum": 0 }, "nullableShortText": { "type": [ "string", "null" ], "maxLength": 240 }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "organizationTagSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/tagIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "kind": { "$ref": "#/components/schemas/tagKindSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 80 }, "slug": { "$ref": "#/components/schemas/tagSlugSchema" }, "color": { "anyOf": [ { "$ref": "#/components/schemas/tagColorSchema" }, { "type": "null" } ] }, "description": { "type": [ "string", "null" ] }, "position": { "type": "integer", "minimum": 0 }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "kind", "name", "slug", "color", "description", "position", "createdAt", "updatedAt" ] }, "productCategorySchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "parentId": { "type": [ "string", "null" ], "format": "uuid" }, "name": { "type": "string", "minLength": 1, "maxLength": 120 }, "slug": { "$ref": "#/components/schemas/slugSchema" }, "icon": { "$ref": "#/components/schemas/iconNameSchema" }, "description": { "type": [ "string", "null" ] }, "position": { "type": "integer", "minimum": 0 }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "parentId", "name", "slug", "icon", "description", "position", "createdAt", "updatedAt" ] }, "productIdSchema": { "type": "string", "format": "uuid" }, "productInputSchema": { "type": "object", "properties": { "categoryIds": { "type": "array", "items": { "type": "string", "format": "uuid" } }, "tagIds": { "type": "array", "items": { "type": "string", "format": "uuid" }, "maxItems": 100 }, "name": { "type": "string", "minLength": 1, "maxLength": 240 }, "slug": { "allOf": [ { "$ref": "#/components/schemas/slugInputSchema" } ] }, "description": { "allOf": [ { "$ref": "#/components/schemas/nullableLongText" } ] }, "brandId": { "anyOf": [ { "$ref": "#/components/schemas/brandIdSchema" }, { "type": "null" } ] }, "manufacturer": { "allOf": [ { "$ref": "#/components/schemas/nullableShortText" } ] }, "model": { "allOf": [ { "$ref": "#/components/schemas/nullableShortText" } ] }, "sku": { "type": [ "string", "null" ], "maxLength": 120 }, "ean": { "type": [ "string", "null" ], "maxLength": 120 }, "defaultPurchasePrice": { "allOf": [ { "$ref": "#/components/schemas/nullableMoney" } ] }, "defaultSellingPrice": { "allOf": [ { "$ref": "#/components/schemas/nullableMoney" } ] }, "vatPercentage": { "type": [ "number", "null" ], "minimum": 0, "maximum": 100 }, "status": { "allOf": [ { "$ref": "#/components/schemas/productStatusSchema" } ] }, "manufacturerPartNumber": { "allOf": [ { "$ref": "#/components/schemas/nullableShortText" } ] }, "currency": { "allOf": [ { "$ref": "#/components/schemas/nullableCurrency" } ] }, "unitOfMeasure": { "allOf": [ { "$ref": "#/components/schemas/unitOfMeasureInputSchema" } ] }, "productType": { "allOf": [ { "$ref": "#/components/schemas/productTypeSchema" } ] }, "trackInventory": { "type": "boolean" }, "weightGrams": { "type": [ "integer", "null" ], "minimum": 0 }, "hsCode": { "type": [ "string", "null" ], "pattern": "^[0-9]{6,10}$" }, "countryOfOrigin": { "type": [ "string", "null" ], "pattern": "^[A-Z]{2}$" }, "digitalDelivery": { "anyOf": [ { "$ref": "#/components/schemas/digitalDeliveryKindSchema" }, { "type": "null" } ] } }, "required": [ "name" ] }, "productOptionSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "name": { "type": "string" }, "position": { "type": "integer" }, "values": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "value": { "type": "string" }, "position": { "type": "integer" } }, "required": [ "id", "value", "position" ] } } }, "required": [ "id", "name", "position", "values" ] }, "productSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/productIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "categoryIds": { "type": "array", "items": { "type": "string", "format": "uuid" } }, "categories": { "type": "array", "items": { "$ref": "#/components/schemas/productCategorySchema" } }, "name": { "type": "string", "minLength": 1 }, "slug": { "$ref": "#/components/schemas/slugSchema" }, "description": { "type": [ "string", "null" ] }, "brandId": { "anyOf": [ { "$ref": "#/components/schemas/brandIdSchema" }, { "type": "null" } ] }, "brand": { "type": [ "object", "null" ], "properties": { "id": { "$ref": "#/components/schemas/brandIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 240 }, "slug": { "type": "string", "minLength": 1, "maxLength": 160, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" }, "description": { "type": [ "string", "null" ] }, "website": { "type": [ "string", "null" ] }, "logoUrl": { "type": [ "string", "null" ], "format": "uri" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "name", "slug", "description", "website", "logoUrl", "createdAt", "updatedAt" ] }, "manufacturer": { "type": [ "string", "null" ] }, "model": { "type": [ "string", "null" ] }, "sku": { "type": [ "string", "null" ] }, "ean": { "type": [ "string", "null" ] }, "defaultPurchasePrice": { "type": [ "number", "null" ] }, "defaultSellingPrice": { "type": [ "number", "null" ] }, "vatPercentage": { "type": [ "number", "null" ] }, "status": { "$ref": "#/components/schemas/productStatusSchema" }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "updatedBy": { "type": [ "string", "null" ], "format": "uuid" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" }, "tags": { "type": "array", "items": { "$ref": "#/components/schemas/organizationTagSchema" } }, "images": { "type": "array", "items": { "$ref": "#/components/schemas/assetImageSchema" } }, "suppliers": { "type": "array", "items": { "$ref": "#/components/schemas/productSupplierLinkSchema" } }, "customAttributes": { "type": "array", "items": { "$ref": "#/components/schemas/assetCustomAttributeSchema" } }, "manufacturerPartNumber": { "type": [ "string", "null" ] }, "currency": { "type": [ "string", "null" ] }, "unitOfMeasure": { "type": [ "string", "null" ] }, "productType": { "$ref": "#/components/schemas/productTypeSchema" }, "trackInventory": { "type": "boolean" }, "weightGrams": { "type": [ "integer", "null" ] }, "hsCode": { "type": [ "string", "null" ] }, "countryOfOrigin": { "type": [ "string", "null" ] }, "digitalDelivery": { "anyOf": [ { "$ref": "#/components/schemas/digitalDeliveryKindSchema" }, { "type": "null" } ] }, "options": { "type": "array", "items": { "$ref": "#/components/schemas/productOptionSchema" } }, "variants": { "type": "array", "items": { "$ref": "#/components/schemas/productVariantSchema" } } }, "required": [ "id", "organizationId", "categoryIds", "categories", "name", "slug", "description", "brandId", "brand", "manufacturer", "model", "sku", "ean", "defaultPurchasePrice", "defaultSellingPrice", "vatPercentage", "status", "createdBy", "updatedBy", "createdAt", "updatedAt", "tags", "images", "suppliers", "customAttributes", "manufacturerPartNumber", "currency", "unitOfMeasure", "productType", "trackInventory", "weightGrams", "hsCode", "countryOfOrigin", "digitalDelivery", "options", "variants" ] }, "productStatusSchema": { "type": "string", "enum": [ "active", "discontinued", "archived" ] }, "productSupplierLinkSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "productId": { "$ref": "#/components/schemas/productIdSchema" }, "supplierId": { "$ref": "#/components/schemas/supplierIdSchema" }, "supplier": { "anyOf": [ { "$ref": "#/components/schemas/supplierSchema" }, { "type": "null" } ] }, "supplierSku": { "type": [ "string", "null" ] }, "orderCode": { "type": [ "string", "null" ] }, "unitCost": { "type": [ "number", "null" ] }, "currency": { "type": [ "string", "null" ] }, "isPreferred": { "type": "boolean" }, "minOrderQuantity": { "type": [ "number", "null" ] }, "orderMultiple": { "type": [ "number", "null" ] }, "leadTimeDays": { "type": [ "integer", "null" ] }, "isActive": { "type": "boolean" }, "externalId": { "type": [ "string", "null" ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "productId", "supplierId", "supplier", "supplierSku", "orderCode", "unitCost", "currency", "isPreferred", "minOrderQuantity", "orderMultiple", "leadTimeDays", "isActive", "externalId", "createdAt", "updatedAt" ] }, "productTypeSchema": { "type": "string", "enum": [ "physical", "digital" ] }, "productVariantIdSchema": { "type": "string", "format": "uuid" }, "productVariantSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/productVariantIdSchema" }, "productId": { "$ref": "#/components/schemas/productIdSchema" }, "title": { "type": "string" }, "sku": { "type": [ "string", "null" ] }, "ean": { "type": [ "string", "null" ] }, "manufacturerPartNumber": { "type": [ "string", "null" ] }, "sellingPrice": { "type": [ "number", "null" ] }, "purchasePrice": { "type": [ "number", "null" ] }, "averageCost": { "type": [ "number", "null" ] }, "weightGrams": { "type": [ "integer", "null" ] }, "status": { "$ref": "#/components/schemas/productStatusSchema" }, "allowBackorder": { "type": "boolean" }, "isDefault": { "type": "boolean" }, "position": { "type": "integer" }, "imageId": { "type": [ "string", "null" ], "format": "uuid" }, "optionValueIds": { "type": "array", "items": { "type": "string", "format": "uuid" } } }, "required": [ "id", "productId", "title", "sku", "ean", "manufacturerPartNumber", "sellingPrice", "purchasePrice", "averageCost", "weightGrams", "status", "allowBackorder", "isDefault", "position", "imageId", "optionValueIds" ] }, "slugInputSchema": { "type": "string", "minLength": 1, "maxLength": 160, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" }, "slugSchema": { "type": "string", "minLength": 1, "maxLength": 160, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" }, "supplierIdSchema": { "type": "string", "format": "uuid" }, "supplierSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/supplierIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 240 }, "email": { "type": [ "string", "null" ] }, "phone": { "type": [ "string", "null" ] }, "website": { "type": [ "string", "null" ] }, "notes": { "type": [ "string", "null" ] }, "status": { "$ref": "#/components/schemas/supplierStatusSchema" }, "code": { "type": [ "string", "null" ] }, "accountNumber": { "type": [ "string", "null" ] }, "kvk": { "type": [ "string", "null" ] }, "btw": { "type": [ "string", "null" ] }, "orderingEmail": { "type": [ "string", "null" ] }, "billingEmail": { "type": [ "string", "null" ] }, "addressLine1": { "type": [ "string", "null" ] }, "addressLine2": { "type": [ "string", "null" ] }, "addressCity": { "type": [ "string", "null" ] }, "addressState": { "type": [ "string", "null" ] }, "addressPostalCode": { "type": [ "string", "null" ] }, "addressCountry": { "type": [ "string", "null" ] }, "currency": { "type": [ "string", "null" ] }, "paymentTermsDays": { "type": [ "integer", "null" ] }, "defaultLeadTimeDays": { "type": [ "integer", "null" ] }, "defaultInventoryLocationId": { "anyOf": [ { "$ref": "#/components/schemas/inventoryLocationIdSchema" }, { "type": "null" } ] }, "source": { "type": [ "string", "null" ] }, "externalId": { "type": [ "string", "null" ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "name", "email", "phone", "website", "notes", "status", "code", "accountNumber", "kvk", "btw", "orderingEmail", "billingEmail", "addressLine1", "addressLine2", "addressCity", "addressState", "addressPostalCode", "addressCountry", "currency", "paymentTermsDays", "defaultLeadTimeDays", "defaultInventoryLocationId", "source", "externalId", "createdAt", "updatedAt" ] }, "supplierStatusSchema": { "type": "string", "enum": [ "active", "inactive", "archived" ] }, "tagColorSchema": { "type": "string", "pattern": "^#(?:[0-9a-f]{3}|[0-9a-f]{6})$" }, "tagIdSchema": { "type": "string", "format": "uuid" }, "tagKindSchema": { "type": "string", "enum": [ "task", "customer", "product" ] }, "tagSlugSchema": { "type": "string", "minLength": 1, "maxLength": 160, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" }, "unitOfMeasureInputSchema": { "type": [ "string", "null" ], "maxLength": 240 } } ``` # List suppliers. (/openapi/listSuppliers) Return suppliers for an organization. ## GET /v1/organizations/{organizationId}/suppliers ```json { "operationId": "listSuppliers", "summary": "List suppliers.", "description": "Return suppliers for an organization.", "tags": [ "Assets" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns suppliers.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/suppliersResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "inventoryLocationIdSchema": { "type": "string", "format": "uuid" }, "listPaginationSchema": { "type": "object", "properties": { "offset": { "type": "integer", "minimum": 0 }, "limit": { "type": "integer", "minimum": 1, "maximum": 100 }, "total": { "type": "integer", "minimum": 0 }, "hasMore": { "type": "boolean" }, "totalIsEstimate": { "type": "boolean" } }, "required": [ "offset", "limit", "total", "hasMore" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "supplierIdSchema": { "type": "string", "format": "uuid" }, "supplierSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/supplierIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 240 }, "email": { "type": [ "string", "null" ] }, "phone": { "type": [ "string", "null" ] }, "website": { "type": [ "string", "null" ] }, "notes": { "type": [ "string", "null" ] }, "status": { "$ref": "#/components/schemas/supplierStatusSchema" }, "code": { "type": [ "string", "null" ] }, "accountNumber": { "type": [ "string", "null" ] }, "kvk": { "type": [ "string", "null" ] }, "btw": { "type": [ "string", "null" ] }, "orderingEmail": { "type": [ "string", "null" ] }, "billingEmail": { "type": [ "string", "null" ] }, "addressLine1": { "type": [ "string", "null" ] }, "addressLine2": { "type": [ "string", "null" ] }, "addressCity": { "type": [ "string", "null" ] }, "addressState": { "type": [ "string", "null" ] }, "addressPostalCode": { "type": [ "string", "null" ] }, "addressCountry": { "type": [ "string", "null" ] }, "currency": { "type": [ "string", "null" ] }, "paymentTermsDays": { "type": [ "integer", "null" ] }, "defaultLeadTimeDays": { "type": [ "integer", "null" ] }, "defaultInventoryLocationId": { "anyOf": [ { "$ref": "#/components/schemas/inventoryLocationIdSchema" }, { "type": "null" } ] }, "source": { "type": [ "string", "null" ] }, "externalId": { "type": [ "string", "null" ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "name", "email", "phone", "website", "notes", "status", "code", "accountNumber", "kvk", "btw", "orderingEmail", "billingEmail", "addressLine1", "addressLine2", "addressCity", "addressState", "addressPostalCode", "addressCountry", "currency", "paymentTermsDays", "defaultLeadTimeDays", "defaultInventoryLocationId", "source", "externalId", "createdAt", "updatedAt" ] }, "supplierStatusSchema": { "type": "string", "enum": [ "active", "inactive", "archived" ] }, "suppliersResponseSchema": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/supplierSchema" } }, "pagination": { "$ref": "#/components/schemas/listPaginationSchema" } }, "required": [ "data", "pagination" ] } } ``` # Create a supplier. (/openapi/createSupplier) Create a supplier for an organization. ## POST /v1/organizations/{organizationId}/suppliers ```json { "operationId": "createSupplier", "summary": "Create a supplier.", "description": "Create a supplier for an organization.", "tags": [ "Assets" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/supplierInputSchema" } } }, "description": "The supplier payload." }, "responses": { "201": { "description": "Returns the created supplier.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/supplierSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "inventoryLocationIdSchema": { "type": "string", "format": "uuid" }, "nullableEmailInputSchema": { "type": [ "string", "null" ], "format": "email", "maxLength": 320 }, "nullableLongText": { "type": [ "string", "null" ], "maxLength": 4000 }, "nullableNonNegativeInt": { "type": [ "integer", "null" ], "minimum": 0 }, "nullablePhoneInputSchema": { "type": [ "string", "null" ], "maxLength": 20 }, "nullableShortText": { "type": [ "string", "null" ], "maxLength": 240 }, "nullableUrlInputSchema": { "anyOf": [ { "type": "string", "maxLength": 400, "format": "uri" }, { "type": "null" } ] }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "supplierIdSchema": { "type": "string", "format": "uuid" }, "supplierInputSchema": { "type": "object", "properties": { "name": { "type": "string", "minLength": 1, "maxLength": 240 }, "email": { "allOf": [ { "$ref": "#/components/schemas/nullableEmailInputSchema" } ] }, "phone": { "allOf": [ { "$ref": "#/components/schemas/nullablePhoneInputSchema" } ] }, "website": { "allOf": [ { "$ref": "#/components/schemas/nullableUrlInputSchema" } ] }, "notes": { "allOf": [ { "$ref": "#/components/schemas/nullableLongText" } ] }, "status": { "allOf": [ { "$ref": "#/components/schemas/supplierStatusSchema" } ] }, "code": { "allOf": [ { "$ref": "#/components/schemas/nullableShortText" } ] }, "accountNumber": { "allOf": [ { "$ref": "#/components/schemas/nullableShortText" } ] }, "kvk": { "type": [ "string", "null" ], "maxLength": 40 }, "btw": { "type": [ "string", "null" ], "maxLength": 40 }, "orderingEmail": { "allOf": [ { "$ref": "#/components/schemas/nullableEmailInputSchema" } ] }, "billingEmail": { "allOf": [ { "$ref": "#/components/schemas/nullableEmailInputSchema" } ] }, "addressLine1": { "allOf": [ { "$ref": "#/components/schemas/nullableShortText" } ] }, "addressLine2": { "allOf": [ { "$ref": "#/components/schemas/nullableShortText" } ] }, "addressCity": { "allOf": [ { "$ref": "#/components/schemas/nullableShortText" } ] }, "addressState": { "allOf": [ { "$ref": "#/components/schemas/nullableShortText" } ] }, "addressPostalCode": { "allOf": [ { "$ref": "#/components/schemas/nullableShortText" } ] }, "addressCountry": { "allOf": [ { "$ref": "#/components/schemas/nullableShortText" } ] }, "currency": { "type": [ "string", "null" ], "pattern": "^[A-Z]{3}$" }, "paymentTermsDays": { "allOf": [ { "$ref": "#/components/schemas/nullableNonNegativeInt" } ] }, "defaultLeadTimeDays": { "allOf": [ { "$ref": "#/components/schemas/nullableNonNegativeInt" } ] }, "defaultInventoryLocationId": { "anyOf": [ { "$ref": "#/components/schemas/inventoryLocationIdSchema" }, { "type": "null" } ] }, "source": { "allOf": [ { "$ref": "#/components/schemas/nullableShortText" } ] }, "externalId": { "allOf": [ { "$ref": "#/components/schemas/nullableShortText" } ] } }, "required": [ "name" ] }, "supplierSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/supplierIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 240 }, "email": { "type": [ "string", "null" ] }, "phone": { "type": [ "string", "null" ] }, "website": { "type": [ "string", "null" ] }, "notes": { "type": [ "string", "null" ] }, "status": { "$ref": "#/components/schemas/supplierStatusSchema" }, "code": { "type": [ "string", "null" ] }, "accountNumber": { "type": [ "string", "null" ] }, "kvk": { "type": [ "string", "null" ] }, "btw": { "type": [ "string", "null" ] }, "orderingEmail": { "type": [ "string", "null" ] }, "billingEmail": { "type": [ "string", "null" ] }, "addressLine1": { "type": [ "string", "null" ] }, "addressLine2": { "type": [ "string", "null" ] }, "addressCity": { "type": [ "string", "null" ] }, "addressState": { "type": [ "string", "null" ] }, "addressPostalCode": { "type": [ "string", "null" ] }, "addressCountry": { "type": [ "string", "null" ] }, "currency": { "type": [ "string", "null" ] }, "paymentTermsDays": { "type": [ "integer", "null" ] }, "defaultLeadTimeDays": { "type": [ "integer", "null" ] }, "defaultInventoryLocationId": { "anyOf": [ { "$ref": "#/components/schemas/inventoryLocationIdSchema" }, { "type": "null" } ] }, "source": { "type": [ "string", "null" ] }, "externalId": { "type": [ "string", "null" ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "name", "email", "phone", "website", "notes", "status", "code", "accountNumber", "kvk", "btw", "orderingEmail", "billingEmail", "addressLine1", "addressLine2", "addressCity", "addressState", "addressPostalCode", "addressCountry", "currency", "paymentTermsDays", "defaultLeadTimeDays", "defaultInventoryLocationId", "source", "externalId", "createdAt", "updatedAt" ] }, "supplierStatusSchema": { "type": "string", "enum": [ "active", "inactive", "archived" ] } } ``` # Get a brand. (/openapi/getBrand) Return a brand for the specified organization. ## GET /v1/organizations/{organizationId}/brands/{brandId} ```json { "operationId": "getBrand", "summary": "Get a brand.", "description": "Return a brand for the specified organization.", "tags": [ "Assets" ], "parameters": [ { "in": "path", "name": "brandId", "required": true, "schema": { "$ref": "#/components/schemas/brandIdSchema" }, "example": "123" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns the requested brand.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/brandSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "brandIdSchema": { "type": "string", "format": "uuid" }, "brandImageIdSchema": { "type": "string", "format": "uuid" }, "brandImageSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/brandImageIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "brandId": { "$ref": "#/components/schemas/brandIdSchema" }, "url": { "type": "string", "format": "uri" }, "altText": { "type": [ "string", "null" ] }, "position": { "type": "integer", "minimum": 0 }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "brandId", "url", "altText", "position", "createdAt", "updatedAt" ] }, "brandSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/brandIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 240 }, "slug": { "type": "string", "minLength": 1, "maxLength": 160, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" }, "description": { "type": [ "string", "null" ] }, "website": { "type": [ "string", "null" ] }, "logoUrl": { "type": [ "string", "null" ], "format": "uri" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" }, "tags": { "type": "array", "items": { "$ref": "#/components/schemas/organizationTagSchema" } }, "images": { "type": "array", "items": { "$ref": "#/components/schemas/brandImageSchema" } } }, "required": [ "id", "organizationId", "name", "slug", "description", "website", "logoUrl", "createdAt", "updatedAt", "tags", "images" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "organizationTagSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/tagIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "kind": { "$ref": "#/components/schemas/tagKindSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 80 }, "slug": { "$ref": "#/components/schemas/tagSlugSchema" }, "color": { "anyOf": [ { "$ref": "#/components/schemas/tagColorSchema" }, { "type": "null" } ] }, "description": { "type": [ "string", "null" ] }, "position": { "type": "integer", "minimum": 0 }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "kind", "name", "slug", "color", "description", "position", "createdAt", "updatedAt" ] }, "tagColorSchema": { "type": "string", "pattern": "^#(?:[0-9a-f]{3}|[0-9a-f]{6})$" }, "tagIdSchema": { "type": "string", "format": "uuid" }, "tagKindSchema": { "type": "string", "enum": [ "task", "customer", "product" ] }, "tagSlugSchema": { "type": "string", "minLength": 1, "maxLength": 160, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" } } ``` # Update a brand. (/openapi/updateBrand) Update a brand for the specified organization. ## PATCH /v1/organizations/{organizationId}/brands/{brandId} ```json { "operationId": "updateBrand", "summary": "Update a brand.", "description": "Update a brand for the specified organization.", "tags": [ "Assets" ], "parameters": [ { "in": "path", "name": "brandId", "required": true, "schema": { "$ref": "#/components/schemas/brandIdSchema" }, "example": "123" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/brandInputSchema" } } }, "description": "The brand payload." }, "responses": { "200": { "description": "Returns the updated brand.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/brandSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "brandIdSchema": { "type": "string", "format": "uuid" }, "brandImageIdSchema": { "type": "string", "format": "uuid" }, "brandImageSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/brandImageIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "brandId": { "$ref": "#/components/schemas/brandIdSchema" }, "url": { "type": "string", "format": "uri" }, "altText": { "type": [ "string", "null" ] }, "position": { "type": "integer", "minimum": 0 }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "brandId", "url", "altText", "position", "createdAt", "updatedAt" ] }, "brandInputSchema": { "type": "object", "properties": { "name": { "type": "string", "minLength": 1, "maxLength": 240 }, "slug": { "type": "string", "minLength": 1, "maxLength": 160, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" }, "description": { "allOf": [ { "$ref": "#/components/schemas/nullableLongText" } ] }, "website": { "allOf": [ { "$ref": "#/components/schemas/nullableUrlInputSchema" } ] } }, "required": [ "name" ] }, "brandSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/brandIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 240 }, "slug": { "type": "string", "minLength": 1, "maxLength": 160, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" }, "description": { "type": [ "string", "null" ] }, "website": { "type": [ "string", "null" ] }, "logoUrl": { "type": [ "string", "null" ], "format": "uri" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" }, "tags": { "type": "array", "items": { "$ref": "#/components/schemas/organizationTagSchema" } }, "images": { "type": "array", "items": { "$ref": "#/components/schemas/brandImageSchema" } } }, "required": [ "id", "organizationId", "name", "slug", "description", "website", "logoUrl", "createdAt", "updatedAt", "tags", "images" ] }, "nullableLongText": { "type": [ "string", "null" ], "maxLength": 4000 }, "nullableUrlInputSchema": { "anyOf": [ { "type": "string", "maxLength": 400, "format": "uri" }, { "type": "null" } ] }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "organizationTagSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/tagIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "kind": { "$ref": "#/components/schemas/tagKindSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 80 }, "slug": { "$ref": "#/components/schemas/tagSlugSchema" }, "color": { "anyOf": [ { "$ref": "#/components/schemas/tagColorSchema" }, { "type": "null" } ] }, "description": { "type": [ "string", "null" ] }, "position": { "type": "integer", "minimum": 0 }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "kind", "name", "slug", "color", "description", "position", "createdAt", "updatedAt" ] }, "tagColorSchema": { "type": "string", "pattern": "^#(?:[0-9a-f]{3}|[0-9a-f]{6})$" }, "tagIdSchema": { "type": "string", "format": "uuid" }, "tagKindSchema": { "type": "string", "enum": [ "task", "customer", "product" ] }, "tagSlugSchema": { "type": "string", "minLength": 1, "maxLength": 160, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" } } ``` # Delete a brand. (/openapi/deleteBrand) Delete a brand for the specified organization. ## DELETE /v1/organizations/{organizationId}/brands/{brandId} ```json { "operationId": "deleteBrand", "summary": "Delete a brand.", "description": "Delete a brand for the specified organization.", "tags": [ "Assets" ], "parameters": [ { "in": "path", "name": "brandId", "required": true, "schema": { "$ref": "#/components/schemas/brandIdSchema" }, "example": "123" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns a success payload when the brand is deleted.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/publicApiSuccessResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "brandIdSchema": { "type": "string", "format": "uuid" }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "publicApiSuccessResponseSchema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [ true ] } }, "required": [ "success" ] } } ``` # Get a customer asset. (/openapi/getCustomerAsset) Return a customer asset for the specified organization. ## GET /v1/organizations/{organizationId}/customer-assets/{customerAssetId} ```json { "operationId": "getCustomerAsset", "summary": "Get a customer asset.", "description": "Return a customer asset for the specified organization.", "tags": [ "Assets" ], "parameters": [ { "in": "path", "name": "customerAssetId", "required": true, "schema": { "$ref": "#/components/schemas/customerAssetIdSchema" }, "example": "123" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns the requested customer asset.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/customerAssetSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "assetCustomAttributeSchema": { "type": "object", "properties": { "valueType": { "$ref": "#/components/schemas/customAttributeValueTypeSchema" }, "textValue": { "type": [ "string", "null" ] }, "numberValue": { "type": [ "number", "null" ] }, "booleanValue": { "type": [ "boolean", "null" ] }, "dateValue": { "$ref": "#/components/schemas/nullableDate" }, "urlValue": { "type": [ "string", "null" ] }, "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "productId": { "anyOf": [ { "$ref": "#/components/schemas/productIdSchema" }, { "type": "null" } ] }, "customerAssetId": { "anyOf": [ { "$ref": "#/components/schemas/customerAssetIdSchema" }, { "type": "null" } ] }, "key": { "type": "string", "minLength": 1, "maxLength": 120 }, "label": { "type": "string", "minLength": 1, "maxLength": 160 }, "position": { "type": "integer", "minimum": 0 }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "valueType", "textValue", "numberValue", "booleanValue", "dateValue", "urlValue", "id", "organizationId", "productId", "customerAssetId", "key", "label", "position", "createdAt", "updatedAt" ] }, "assetImageIdSchema": { "type": "string", "format": "uuid" }, "assetImageSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/assetImageIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "productId": { "anyOf": [ { "$ref": "#/components/schemas/productIdSchema" }, { "type": "null" } ] }, "url": { "type": "string", "format": "uri" }, "altText": { "type": [ "string", "null" ] }, "position": { "type": "integer", "minimum": 0 }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "productId", "url", "altText", "position", "createdAt", "updatedAt" ] }, "brandIdSchema": { "type": "string", "format": "uuid" }, "customAttributeValueTypeSchema": { "type": "string", "enum": [ "text", "number", "boolean", "date", "url" ] }, "customerAddressSchema": { "type": "object", "properties": { "line1": { "$ref": "#/components/schemas/nullableShortText" }, "line2": { "$ref": "#/components/schemas/nullableShortText" }, "city": { "$ref": "#/components/schemas/nullableShortText" }, "state": { "$ref": "#/components/schemas/nullableShortText" }, "postalCode": { "$ref": "#/components/schemas/nullableShortText" }, "country": { "$ref": "#/components/schemas/nullableShortText" } }, "required": [ "line1", "line2", "city", "state", "postalCode", "country" ] }, "customerAssetConditionSchema": { "type": "string", "enum": [ "new", "good", "fair", "poor", "unknown" ] }, "customerAssetFileIdSchema": { "type": "string", "format": "uuid" }, "customerAssetFileSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/customerAssetFileIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "customerAssetId": { "$ref": "#/components/schemas/customerAssetIdSchema" }, "url": { "type": "string", "format": "uri" }, "fileName": { "type": "string", "minLength": 1 }, "contentType": { "type": "string", "minLength": 1 }, "byteSize": { "type": "integer", "exclusiveMinimum": 0 }, "altText": { "type": [ "string", "null" ] }, "position": { "type": "integer", "minimum": 0 }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "customerAssetId", "url", "fileName", "contentType", "byteSize", "altText", "position", "createdBy", "createdAt", "updatedAt" ] }, "customerAssetIdSchema": { "type": "integer", "exclusiveMinimum": 0 }, "customerAssetSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/customerAssetIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "productId": { "$ref": "#/components/schemas/productIdSchema" }, "product": { "anyOf": [ { "$ref": "#/components/schemas/productListItemSchema" }, { "type": "null" } ] }, "customerId": { "$ref": "#/components/schemas/customerIdSchema" }, "customerName": { "type": [ "string", "null" ] }, "customerLocationId": { "type": [ "string", "null" ], "format": "uuid" }, "customerLocation": { "anyOf": [ { "$ref": "#/components/schemas/customerLocationSchema" }, { "type": "null" } ] }, "serialNumber": { "type": [ "string", "null" ] }, "assetTag": { "type": [ "string", "null" ] }, "barcode": { "type": [ "string", "null" ] }, "installedOn": { "$ref": "#/components/schemas/nullableDate" }, "warrantyExpiresOn": { "$ref": "#/components/schemas/nullableDate" }, "condition": { "$ref": "#/components/schemas/customerAssetConditionSchema" }, "status": { "$ref": "#/components/schemas/customerAssetStatusSchema" }, "description": { "type": [ "string", "null" ] }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "updatedBy": { "type": [ "string", "null" ], "format": "uuid" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" }, "tags": { "type": "array", "items": { "$ref": "#/components/schemas/organizationTagSchema" } }, "files": { "type": "array", "items": { "$ref": "#/components/schemas/customerAssetFileSchema" } }, "customAttributes": { "type": "array", "items": { "$ref": "#/components/schemas/assetCustomAttributeSchema" } }, "purchasedOn": { "$ref": "#/components/schemas/nullableDate" }, "purchasePrice": { "type": [ "number", "null" ] }, "currency": { "type": [ "string", "null" ] }, "serviceIntervalDays": { "type": [ "integer", "null" ], "minimum": 1 }, "warrantyProvider": { "type": [ "string", "null" ] }, "warrantyReference": { "type": [ "string", "null" ] }, "quoteId": { "type": [ "string", "null" ], "format": "uuid" }, "invoiceId": { "type": [ "string", "null" ], "format": "uuid" } }, "required": [ "id", "organizationId", "productId", "product", "customerId", "customerName", "customerLocationId", "customerLocation", "serialNumber", "assetTag", "barcode", "installedOn", "warrantyExpiresOn", "condition", "status", "description", "createdBy", "updatedBy", "createdAt", "updatedAt", "tags", "files", "customAttributes", "purchasedOn", "purchasePrice", "currency", "serviceIntervalDays", "warrantyProvider", "warrantyReference", "quoteId", "invoiceId" ] }, "customerAssetStatusSchema": { "type": "string", "enum": [ "active", "inactive", "maintenance", "retired", "archived" ] }, "customerIdSchema": { "type": "integer", "exclusiveMinimum": 0 }, "customerLocationSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "customerId": { "$ref": "#/components/schemas/customerIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 120 }, "address": { "$ref": "#/components/schemas/customerAddressSchema" }, "isPrimary": { "type": "boolean" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "customerId", "organizationId", "name", "address", "isPrimary", "createdAt", "updatedAt" ] }, "iconNameSchema": { "type": "string", "minLength": 1, "maxLength": 120, "pattern": "^[A-Za-z0-9]+Icon$" }, "inventoryLocationIdSchema": { "type": "string", "format": "uuid" }, "nullableDate": { "type": [ "string", "null" ], "format": "date" }, "nullableShortText": { "type": [ "string", "null" ], "maxLength": 240 }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "organizationTagSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/tagIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "kind": { "$ref": "#/components/schemas/tagKindSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 80 }, "slug": { "$ref": "#/components/schemas/tagSlugSchema" }, "color": { "anyOf": [ { "$ref": "#/components/schemas/tagColorSchema" }, { "type": "null" } ] }, "description": { "type": [ "string", "null" ] }, "position": { "type": "integer", "minimum": 0 }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "kind", "name", "slug", "color", "description", "position", "createdAt", "updatedAt" ] }, "productCategorySchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "parentId": { "type": [ "string", "null" ], "format": "uuid" }, "name": { "type": "string", "minLength": 1, "maxLength": 120 }, "slug": { "$ref": "#/components/schemas/slugSchema" }, "icon": { "$ref": "#/components/schemas/iconNameSchema" }, "description": { "type": [ "string", "null" ] }, "position": { "type": "integer", "minimum": 0 }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "parentId", "name", "slug", "icon", "description", "position", "createdAt", "updatedAt" ] }, "productIdSchema": { "type": "string", "format": "uuid" }, "productListItemSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/productIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "categoryIds": { "type": "array", "items": { "type": "string", "format": "uuid" } }, "categories": { "type": "array", "items": { "$ref": "#/components/schemas/productCategorySchema" } }, "name": { "type": "string", "minLength": 1 }, "slug": { "$ref": "#/components/schemas/slugSchema" }, "brand": { "type": [ "object", "null" ], "properties": { "id": { "$ref": "#/components/schemas/brandIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 240 }, "slug": { "type": "string", "minLength": 1, "maxLength": 160, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" }, "description": { "type": [ "string", "null" ] }, "website": { "type": [ "string", "null" ] }, "logoUrl": { "type": [ "string", "null" ], "format": "uri" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "name", "slug", "description", "website", "logoUrl", "createdAt", "updatedAt" ] }, "manufacturer": { "type": [ "string", "null" ] }, "sku": { "type": [ "string", "null" ] }, "ean": { "type": [ "string", "null" ] }, "defaultSellingPrice": { "type": [ "number", "null" ] }, "vatPercentage": { "type": [ "number", "null" ] }, "status": { "$ref": "#/components/schemas/productStatusSchema" }, "updatedAt": { "type": "string", "format": "date-time" }, "tags": { "type": "array", "items": { "$ref": "#/components/schemas/organizationTagSchema" } }, "images": { "type": "array", "items": { "$ref": "#/components/schemas/assetImageSchema" } }, "suppliers": { "type": "array", "items": { "$ref": "#/components/schemas/productSupplierLinkSchema" } }, "manufacturerPartNumber": { "type": [ "string", "null" ] }, "currency": { "type": [ "string", "null" ] }, "unitOfMeasure": { "type": [ "string", "null" ] }, "stockStatus": { "anyOf": [ { "$ref": "#/components/schemas/stockStatusSchema" }, { "type": "null" } ] } }, "required": [ "id", "organizationId", "categoryIds", "categories", "name", "slug", "brand", "manufacturer", "sku", "ean", "defaultSellingPrice", "vatPercentage", "status", "updatedAt", "tags", "images", "suppliers", "manufacturerPartNumber", "currency", "unitOfMeasure" ] }, "productStatusSchema": { "type": "string", "enum": [ "active", "discontinued", "archived" ] }, "productSupplierLinkSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "productId": { "$ref": "#/components/schemas/productIdSchema" }, "supplierId": { "$ref": "#/components/schemas/supplierIdSchema" }, "supplier": { "anyOf": [ { "$ref": "#/components/schemas/supplierSchema" }, { "type": "null" } ] }, "supplierSku": { "type": [ "string", "null" ] }, "orderCode": { "type": [ "string", "null" ] }, "unitCost": { "type": [ "number", "null" ] }, "currency": { "type": [ "string", "null" ] }, "isPreferred": { "type": "boolean" }, "minOrderQuantity": { "type": [ "number", "null" ] }, "orderMultiple": { "type": [ "number", "null" ] }, "leadTimeDays": { "type": [ "integer", "null" ] }, "isActive": { "type": "boolean" }, "externalId": { "type": [ "string", "null" ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "productId", "supplierId", "supplier", "supplierSku", "orderCode", "unitCost", "currency", "isPreferred", "minOrderQuantity", "orderMultiple", "leadTimeDays", "isActive", "externalId", "createdAt", "updatedAt" ] }, "slugSchema": { "type": "string", "minLength": 1, "maxLength": 160, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" }, "stockStatusSchema": { "type": "string", "enum": [ "not_tracked", "discontinued", "out_of_stock", "backordered", "low_stock", "in_stock" ] }, "supplierIdSchema": { "type": "string", "format": "uuid" }, "supplierSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/supplierIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 240 }, "email": { "type": [ "string", "null" ] }, "phone": { "type": [ "string", "null" ] }, "website": { "type": [ "string", "null" ] }, "notes": { "type": [ "string", "null" ] }, "status": { "$ref": "#/components/schemas/supplierStatusSchema" }, "code": { "type": [ "string", "null" ] }, "accountNumber": { "type": [ "string", "null" ] }, "kvk": { "type": [ "string", "null" ] }, "btw": { "type": [ "string", "null" ] }, "orderingEmail": { "type": [ "string", "null" ] }, "billingEmail": { "type": [ "string", "null" ] }, "addressLine1": { "type": [ "string", "null" ] }, "addressLine2": { "type": [ "string", "null" ] }, "addressCity": { "type": [ "string", "null" ] }, "addressState": { "type": [ "string", "null" ] }, "addressPostalCode": { "type": [ "string", "null" ] }, "addressCountry": { "type": [ "string", "null" ] }, "currency": { "type": [ "string", "null" ] }, "paymentTermsDays": { "type": [ "integer", "null" ] }, "defaultLeadTimeDays": { "type": [ "integer", "null" ] }, "defaultInventoryLocationId": { "anyOf": [ { "$ref": "#/components/schemas/inventoryLocationIdSchema" }, { "type": "null" } ] }, "source": { "type": [ "string", "null" ] }, "externalId": { "type": [ "string", "null" ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "name", "email", "phone", "website", "notes", "status", "code", "accountNumber", "kvk", "btw", "orderingEmail", "billingEmail", "addressLine1", "addressLine2", "addressCity", "addressState", "addressPostalCode", "addressCountry", "currency", "paymentTermsDays", "defaultLeadTimeDays", "defaultInventoryLocationId", "source", "externalId", "createdAt", "updatedAt" ] }, "supplierStatusSchema": { "type": "string", "enum": [ "active", "inactive", "archived" ] }, "tagColorSchema": { "type": "string", "pattern": "^#(?:[0-9a-f]{3}|[0-9a-f]{6})$" }, "tagIdSchema": { "type": "string", "format": "uuid" }, "tagKindSchema": { "type": "string", "enum": [ "task", "customer", "product" ] }, "tagSlugSchema": { "type": "string", "minLength": 1, "maxLength": 160, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" } } ``` # Update a customer asset. (/openapi/updateCustomerAsset) Update a customer asset for the specified organization. ## PATCH /v1/organizations/{organizationId}/customer-assets/{customerAssetId} ```json { "operationId": "updateCustomerAsset", "summary": "Update a customer asset.", "description": "Update a customer asset for the specified organization.", "tags": [ "Assets" ], "parameters": [ { "in": "path", "name": "customerAssetId", "required": true, "schema": { "$ref": "#/components/schemas/customerAssetIdSchema" }, "example": "123" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/customerAssetInputSchema" } } }, "description": "The customer asset payload." }, "responses": { "200": { "description": "Returns the updated customer asset.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/customerAssetSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "assetCustomAttributeSchema": { "type": "object", "properties": { "valueType": { "$ref": "#/components/schemas/customAttributeValueTypeSchema" }, "textValue": { "type": [ "string", "null" ] }, "numberValue": { "type": [ "number", "null" ] }, "booleanValue": { "type": [ "boolean", "null" ] }, "dateValue": { "$ref": "#/components/schemas/nullableDate" }, "urlValue": { "type": [ "string", "null" ] }, "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "productId": { "anyOf": [ { "$ref": "#/components/schemas/productIdSchema" }, { "type": "null" } ] }, "customerAssetId": { "anyOf": [ { "$ref": "#/components/schemas/customerAssetIdSchema" }, { "type": "null" } ] }, "key": { "type": "string", "minLength": 1, "maxLength": 120 }, "label": { "type": "string", "minLength": 1, "maxLength": 160 }, "position": { "type": "integer", "minimum": 0 }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "valueType", "textValue", "numberValue", "booleanValue", "dateValue", "urlValue", "id", "organizationId", "productId", "customerAssetId", "key", "label", "position", "createdAt", "updatedAt" ] }, "assetImageIdSchema": { "type": "string", "format": "uuid" }, "assetImageSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/assetImageIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "productId": { "anyOf": [ { "$ref": "#/components/schemas/productIdSchema" }, { "type": "null" } ] }, "url": { "type": "string", "format": "uri" }, "altText": { "type": [ "string", "null" ] }, "position": { "type": "integer", "minimum": 0 }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "productId", "url", "altText", "position", "createdAt", "updatedAt" ] }, "brandIdSchema": { "type": "string", "format": "uuid" }, "currencyCodeSchema": { "type": "string", "pattern": "^[A-Z]{3}$" }, "customAttributeValueTypeSchema": { "type": "string", "enum": [ "text", "number", "boolean", "date", "url" ] }, "customerAddressSchema": { "type": "object", "properties": { "line1": { "$ref": "#/components/schemas/nullableShortText" }, "line2": { "$ref": "#/components/schemas/nullableShortText" }, "city": { "$ref": "#/components/schemas/nullableShortText" }, "state": { "$ref": "#/components/schemas/nullableShortText" }, "postalCode": { "$ref": "#/components/schemas/nullableShortText" }, "country": { "$ref": "#/components/schemas/nullableShortText" } }, "required": [ "line1", "line2", "city", "state", "postalCode", "country" ] }, "customerAssetConditionSchema": { "type": "string", "enum": [ "new", "good", "fair", "poor", "unknown" ] }, "customerAssetFileIdSchema": { "type": "string", "format": "uuid" }, "customerAssetFileSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/customerAssetFileIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "customerAssetId": { "$ref": "#/components/schemas/customerAssetIdSchema" }, "url": { "type": "string", "format": "uri" }, "fileName": { "type": "string", "minLength": 1 }, "contentType": { "type": "string", "minLength": 1 }, "byteSize": { "type": "integer", "exclusiveMinimum": 0 }, "altText": { "type": [ "string", "null" ] }, "position": { "type": "integer", "minimum": 0 }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "customerAssetId", "url", "fileName", "contentType", "byteSize", "altText", "position", "createdBy", "createdAt", "updatedAt" ] }, "customerAssetIdSchema": { "type": "integer", "exclusiveMinimum": 0 }, "customerAssetInputSchema": { "type": "object", "properties": { "productId": { "$ref": "#/components/schemas/productIdSchema" }, "customerId": { "$ref": "#/components/schemas/customerIdSchema" }, "customerLocationId": { "type": [ "string", "null" ], "format": "uuid" }, "serialNumber": { "allOf": [ { "$ref": "#/components/schemas/nullableShortText" } ] }, "assetTag": { "allOf": [ { "$ref": "#/components/schemas/nullableShortText" } ] }, "barcode": { "allOf": [ { "$ref": "#/components/schemas/nullableShortText" } ] }, "installedOn": { "allOf": [ { "$ref": "#/components/schemas/nullableDate" } ] }, "warrantyExpiresOn": { "allOf": [ { "$ref": "#/components/schemas/nullableDate" } ] }, "condition": { "allOf": [ { "$ref": "#/components/schemas/customerAssetConditionSchema" } ] }, "status": { "allOf": [ { "$ref": "#/components/schemas/customerAssetStatusSchema" } ] }, "description": { "allOf": [ { "$ref": "#/components/schemas/nullableLongText" } ] }, "purchasedOn": { "allOf": [ { "$ref": "#/components/schemas/nullableDate" } ] }, "purchasePrice": { "allOf": [ { "$ref": "#/components/schemas/nullableMoney" } ] }, "currency": { "allOf": [ { "$ref": "#/components/schemas/nullableCurrency" } ] }, "serviceIntervalDays": { "type": [ "integer", "null" ], "minimum": 1 }, "warrantyProvider": { "allOf": [ { "$ref": "#/components/schemas/nullableShortText" } ] }, "warrantyReference": { "allOf": [ { "$ref": "#/components/schemas/nullableShortText" } ] }, "quoteId": { "type": [ "string", "null" ], "format": "uuid" }, "invoiceId": { "type": [ "string", "null" ], "format": "uuid" } }, "required": [ "productId", "customerId" ] }, "customerAssetSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/customerAssetIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "productId": { "$ref": "#/components/schemas/productIdSchema" }, "product": { "anyOf": [ { "$ref": "#/components/schemas/productListItemSchema" }, { "type": "null" } ] }, "customerId": { "$ref": "#/components/schemas/customerIdSchema" }, "customerName": { "type": [ "string", "null" ] }, "customerLocationId": { "type": [ "string", "null" ], "format": "uuid" }, "customerLocation": { "anyOf": [ { "$ref": "#/components/schemas/customerLocationSchema" }, { "type": "null" } ] }, "serialNumber": { "type": [ "string", "null" ] }, "assetTag": { "type": [ "string", "null" ] }, "barcode": { "type": [ "string", "null" ] }, "installedOn": { "$ref": "#/components/schemas/nullableDate" }, "warrantyExpiresOn": { "$ref": "#/components/schemas/nullableDate" }, "condition": { "$ref": "#/components/schemas/customerAssetConditionSchema" }, "status": { "$ref": "#/components/schemas/customerAssetStatusSchema" }, "description": { "type": [ "string", "null" ] }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "updatedBy": { "type": [ "string", "null" ], "format": "uuid" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" }, "tags": { "type": "array", "items": { "$ref": "#/components/schemas/organizationTagSchema" } }, "files": { "type": "array", "items": { "$ref": "#/components/schemas/customerAssetFileSchema" } }, "customAttributes": { "type": "array", "items": { "$ref": "#/components/schemas/assetCustomAttributeSchema" } }, "purchasedOn": { "$ref": "#/components/schemas/nullableDate" }, "purchasePrice": { "type": [ "number", "null" ] }, "currency": { "type": [ "string", "null" ] }, "serviceIntervalDays": { "type": [ "integer", "null" ], "minimum": 1 }, "warrantyProvider": { "type": [ "string", "null" ] }, "warrantyReference": { "type": [ "string", "null" ] }, "quoteId": { "type": [ "string", "null" ], "format": "uuid" }, "invoiceId": { "type": [ "string", "null" ], "format": "uuid" } }, "required": [ "id", "organizationId", "productId", "product", "customerId", "customerName", "customerLocationId", "customerLocation", "serialNumber", "assetTag", "barcode", "installedOn", "warrantyExpiresOn", "condition", "status", "description", "createdBy", "updatedBy", "createdAt", "updatedAt", "tags", "files", "customAttributes", "purchasedOn", "purchasePrice", "currency", "serviceIntervalDays", "warrantyProvider", "warrantyReference", "quoteId", "invoiceId" ] }, "customerAssetStatusSchema": { "type": "string", "enum": [ "active", "inactive", "maintenance", "retired", "archived" ] }, "customerIdSchema": { "type": "integer", "exclusiveMinimum": 0 }, "customerLocationSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "customerId": { "$ref": "#/components/schemas/customerIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 120 }, "address": { "$ref": "#/components/schemas/customerAddressSchema" }, "isPrimary": { "type": "boolean" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "customerId", "organizationId", "name", "address", "isPrimary", "createdAt", "updatedAt" ] }, "iconNameSchema": { "type": "string", "minLength": 1, "maxLength": 120, "pattern": "^[A-Za-z0-9]+Icon$" }, "inventoryLocationIdSchema": { "type": "string", "format": "uuid" }, "nullableCurrency": { "anyOf": [ { "$ref": "#/components/schemas/currencyCodeSchema" }, { "type": "null" } ] }, "nullableDate": { "type": [ "string", "null" ], "format": "date" }, "nullableLongText": { "type": [ "string", "null" ], "maxLength": 4000 }, "nullableMoney": { "type": [ "number", "null" ], "minimum": 0 }, "nullableShortText": { "type": [ "string", "null" ], "maxLength": 240 }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "organizationTagSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/tagIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "kind": { "$ref": "#/components/schemas/tagKindSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 80 }, "slug": { "$ref": "#/components/schemas/tagSlugSchema" }, "color": { "anyOf": [ { "$ref": "#/components/schemas/tagColorSchema" }, { "type": "null" } ] }, "description": { "type": [ "string", "null" ] }, "position": { "type": "integer", "minimum": 0 }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "kind", "name", "slug", "color", "description", "position", "createdAt", "updatedAt" ] }, "productCategorySchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "parentId": { "type": [ "string", "null" ], "format": "uuid" }, "name": { "type": "string", "minLength": 1, "maxLength": 120 }, "slug": { "$ref": "#/components/schemas/slugSchema" }, "icon": { "$ref": "#/components/schemas/iconNameSchema" }, "description": { "type": [ "string", "null" ] }, "position": { "type": "integer", "minimum": 0 }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "parentId", "name", "slug", "icon", "description", "position", "createdAt", "updatedAt" ] }, "productIdSchema": { "type": "string", "format": "uuid" }, "productListItemSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/productIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "categoryIds": { "type": "array", "items": { "type": "string", "format": "uuid" } }, "categories": { "type": "array", "items": { "$ref": "#/components/schemas/productCategorySchema" } }, "name": { "type": "string", "minLength": 1 }, "slug": { "$ref": "#/components/schemas/slugSchema" }, "brand": { "type": [ "object", "null" ], "properties": { "id": { "$ref": "#/components/schemas/brandIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 240 }, "slug": { "type": "string", "minLength": 1, "maxLength": 160, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" }, "description": { "type": [ "string", "null" ] }, "website": { "type": [ "string", "null" ] }, "logoUrl": { "type": [ "string", "null" ], "format": "uri" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "name", "slug", "description", "website", "logoUrl", "createdAt", "updatedAt" ] }, "manufacturer": { "type": [ "string", "null" ] }, "sku": { "type": [ "string", "null" ] }, "ean": { "type": [ "string", "null" ] }, "defaultSellingPrice": { "type": [ "number", "null" ] }, "vatPercentage": { "type": [ "number", "null" ] }, "status": { "$ref": "#/components/schemas/productStatusSchema" }, "updatedAt": { "type": "string", "format": "date-time" }, "tags": { "type": "array", "items": { "$ref": "#/components/schemas/organizationTagSchema" } }, "images": { "type": "array", "items": { "$ref": "#/components/schemas/assetImageSchema" } }, "suppliers": { "type": "array", "items": { "$ref": "#/components/schemas/productSupplierLinkSchema" } }, "manufacturerPartNumber": { "type": [ "string", "null" ] }, "currency": { "type": [ "string", "null" ] }, "unitOfMeasure": { "type": [ "string", "null" ] }, "stockStatus": { "anyOf": [ { "$ref": "#/components/schemas/stockStatusSchema" }, { "type": "null" } ] } }, "required": [ "id", "organizationId", "categoryIds", "categories", "name", "slug", "brand", "manufacturer", "sku", "ean", "defaultSellingPrice", "vatPercentage", "status", "updatedAt", "tags", "images", "suppliers", "manufacturerPartNumber", "currency", "unitOfMeasure" ] }, "productStatusSchema": { "type": "string", "enum": [ "active", "discontinued", "archived" ] }, "productSupplierLinkSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "productId": { "$ref": "#/components/schemas/productIdSchema" }, "supplierId": { "$ref": "#/components/schemas/supplierIdSchema" }, "supplier": { "anyOf": [ { "$ref": "#/components/schemas/supplierSchema" }, { "type": "null" } ] }, "supplierSku": { "type": [ "string", "null" ] }, "orderCode": { "type": [ "string", "null" ] }, "unitCost": { "type": [ "number", "null" ] }, "currency": { "type": [ "string", "null" ] }, "isPreferred": { "type": "boolean" }, "minOrderQuantity": { "type": [ "number", "null" ] }, "orderMultiple": { "type": [ "number", "null" ] }, "leadTimeDays": { "type": [ "integer", "null" ] }, "isActive": { "type": "boolean" }, "externalId": { "type": [ "string", "null" ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "productId", "supplierId", "supplier", "supplierSku", "orderCode", "unitCost", "currency", "isPreferred", "minOrderQuantity", "orderMultiple", "leadTimeDays", "isActive", "externalId", "createdAt", "updatedAt" ] }, "slugSchema": { "type": "string", "minLength": 1, "maxLength": 160, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" }, "stockStatusSchema": { "type": "string", "enum": [ "not_tracked", "discontinued", "out_of_stock", "backordered", "low_stock", "in_stock" ] }, "supplierIdSchema": { "type": "string", "format": "uuid" }, "supplierSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/supplierIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 240 }, "email": { "type": [ "string", "null" ] }, "phone": { "type": [ "string", "null" ] }, "website": { "type": [ "string", "null" ] }, "notes": { "type": [ "string", "null" ] }, "status": { "$ref": "#/components/schemas/supplierStatusSchema" }, "code": { "type": [ "string", "null" ] }, "accountNumber": { "type": [ "string", "null" ] }, "kvk": { "type": [ "string", "null" ] }, "btw": { "type": [ "string", "null" ] }, "orderingEmail": { "type": [ "string", "null" ] }, "billingEmail": { "type": [ "string", "null" ] }, "addressLine1": { "type": [ "string", "null" ] }, "addressLine2": { "type": [ "string", "null" ] }, "addressCity": { "type": [ "string", "null" ] }, "addressState": { "type": [ "string", "null" ] }, "addressPostalCode": { "type": [ "string", "null" ] }, "addressCountry": { "type": [ "string", "null" ] }, "currency": { "type": [ "string", "null" ] }, "paymentTermsDays": { "type": [ "integer", "null" ] }, "defaultLeadTimeDays": { "type": [ "integer", "null" ] }, "defaultInventoryLocationId": { "anyOf": [ { "$ref": "#/components/schemas/inventoryLocationIdSchema" }, { "type": "null" } ] }, "source": { "type": [ "string", "null" ] }, "externalId": { "type": [ "string", "null" ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "name", "email", "phone", "website", "notes", "status", "code", "accountNumber", "kvk", "btw", "orderingEmail", "billingEmail", "addressLine1", "addressLine2", "addressCity", "addressState", "addressPostalCode", "addressCountry", "currency", "paymentTermsDays", "defaultLeadTimeDays", "defaultInventoryLocationId", "source", "externalId", "createdAt", "updatedAt" ] }, "supplierStatusSchema": { "type": "string", "enum": [ "active", "inactive", "archived" ] }, "tagColorSchema": { "type": "string", "pattern": "^#(?:[0-9a-f]{3}|[0-9a-f]{6})$" }, "tagIdSchema": { "type": "string", "format": "uuid" }, "tagKindSchema": { "type": "string", "enum": [ "task", "customer", "product" ] }, "tagSlugSchema": { "type": "string", "minLength": 1, "maxLength": 160, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" } } ``` # Delete a customer asset. (/openapi/deleteCustomerAsset) Delete a customer asset for the specified organization. ## DELETE /v1/organizations/{organizationId}/customer-assets/{customerAssetId} ```json { "operationId": "deleteCustomerAsset", "summary": "Delete a customer asset.", "description": "Delete a customer asset for the specified organization.", "tags": [ "Assets" ], "parameters": [ { "in": "path", "name": "customerAssetId", "required": true, "schema": { "$ref": "#/components/schemas/customerAssetIdSchema" }, "example": "123" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns a success payload when the customer asset is deleted.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/publicApiSuccessResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "customerAssetIdSchema": { "type": "integer", "exclusiveMinimum": 0 }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "publicApiSuccessResponseSchema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [ true ] } }, "required": [ "success" ] } } ``` # List inventory allocations. (/openapi/listInventoryAllocations) Return stock reserved or consumed by quotes, tasks, and invoices. ## GET /v1/organizations/{organizationId}/inventory/allocations ```json { "operationId": "listInventoryAllocations", "summary": "List inventory allocations.", "description": "Return stock reserved or consumed by quotes, tasks, and invoices.", "tags": [ "Assets" ], "parameters": [ { "in": "query", "name": "offset", "required": true, "schema": { "type": "string" }, "example": "example" }, { "in": "query", "name": "limit", "required": true, "schema": { "type": "string" }, "example": "example" }, { "in": "query", "name": "q", "required": false, "schema": { "type": "string" }, "example": "example" }, { "in": "query", "name": "invoiceLineId", "required": false, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "query", "name": "productId", "required": false, "schema": { "allOf": [ { "$ref": "#/components/schemas/productIdSchema" } ] }, "example": "123" }, { "in": "query", "name": "quoteVersionLineId", "required": false, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "query", "name": "stockRecordId", "required": false, "schema": { "allOf": [ { "$ref": "#/components/schemas/inventoryStockRecordIdSchema" } ] }, "example": "123" }, { "in": "query", "name": "taskMaterialId", "required": false, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns inventory allocations.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/inventoryAllocationsResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "inventoryAllocationSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "stockRecordId": { "anyOf": [ { "$ref": "#/components/schemas/inventoryStockRecordIdSchema" }, { "type": "null" } ] }, "productId": { "$ref": "#/components/schemas/productIdSchema" }, "quantity": { "type": "number", "exclusiveMinimum": 0 }, "state": { "$ref": "#/components/schemas/inventoryAllocationStateSchema" }, "taskMaterialId": { "type": [ "string", "null" ], "format": "uuid" }, "quoteVersionLineId": { "type": [ "string", "null" ], "format": "uuid" }, "invoiceLineId": { "type": [ "string", "null" ], "format": "uuid" }, "consumedAt": { "type": [ "string", "null" ], "format": "date-time" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "stockRecordId", "productId", "quantity", "state", "taskMaterialId", "quoteVersionLineId", "invoiceLineId", "consumedAt", "createdAt", "updatedAt" ] }, "inventoryAllocationStateSchema": { "type": "string", "enum": [ "reserved", "consumed", "backordered" ] }, "inventoryAllocationsResponseSchema": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/inventoryAllocationSchema" } }, "pagination": { "$ref": "#/components/schemas/listPaginationSchema" } }, "required": [ "data", "pagination" ] }, "inventoryStockRecordIdSchema": { "type": "string", "format": "uuid" }, "listPaginationSchema": { "type": "object", "properties": { "offset": { "type": "integer", "minimum": 0 }, "limit": { "type": "integer", "minimum": 1, "maximum": 100 }, "total": { "type": "integer", "minimum": 0 }, "hasMore": { "type": "boolean" }, "totalIsEstimate": { "type": "boolean" } }, "required": [ "offset", "limit", "total", "hasMore" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "productIdSchema": { "type": "string", "format": "uuid" } } ``` # List inventory locations. (/openapi/listInventoryLocations) Return inventory locations for an organization. ## GET /v1/organizations/{organizationId}/inventory/locations ```json { "operationId": "listInventoryLocations", "summary": "List inventory locations.", "description": "Return inventory locations for an organization.", "tags": [ "Assets" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns inventory locations.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/inventoryLocationsResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "inventoryLocationIdSchema": { "type": "string", "format": "uuid" }, "inventoryLocationSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/inventoryLocationIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 240 }, "description": { "type": [ "string", "null" ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "name", "description", "createdAt", "updatedAt" ] }, "inventoryLocationsResponseSchema": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/inventoryLocationSchema" } }, "pagination": { "$ref": "#/components/schemas/listPaginationSchema" } }, "required": [ "data", "pagination" ] }, "listPaginationSchema": { "type": "object", "properties": { "offset": { "type": "integer", "minimum": 0 }, "limit": { "type": "integer", "minimum": 1, "maximum": 100 }, "total": { "type": "integer", "minimum": 0 }, "hasMore": { "type": "boolean" }, "totalIsEstimate": { "type": "boolean" } }, "required": [ "offset", "limit", "total", "hasMore" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" } } ``` # Create an inventory location. (/openapi/createInventoryLocation) Create an inventory location for an organization. ## POST /v1/organizations/{organizationId}/inventory/locations ```json { "operationId": "createInventoryLocation", "summary": "Create an inventory location.", "description": "Create an inventory location for an organization.", "tags": [ "Assets" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/inventoryLocationInputSchema" } } }, "description": "The inventory location payload." }, "responses": { "201": { "description": "Returns the created inventory location.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/inventoryLocationSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "inventoryLocationIdSchema": { "type": "string", "format": "uuid" }, "inventoryLocationInputSchema": { "type": "object", "properties": { "name": { "type": "string", "minLength": 1, "maxLength": 240 }, "description": { "allOf": [ { "$ref": "#/components/schemas/nullableLongText" } ] } }, "required": [ "name" ] }, "inventoryLocationSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/inventoryLocationIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 240 }, "description": { "type": [ "string", "null" ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "name", "description", "createdAt", "updatedAt" ] }, "nullableLongText": { "type": [ "string", "null" ], "maxLength": 4000 }, "organizationIdSchema": { "type": "string", "format": "uuid" } } ``` # List inventory movements. (/openapi/listInventoryMovements) Return inventory movement history for an organization. ## GET /v1/organizations/{organizationId}/inventory/movements ```json { "operationId": "listInventoryMovements", "summary": "List inventory movements.", "description": "Return inventory movement history for an organization.", "tags": [ "Assets" ], "parameters": [ { "in": "query", "name": "offset", "required": true, "schema": { "type": "string" }, "example": "example" }, { "in": "query", "name": "limit", "required": true, "schema": { "type": "string" }, "example": "example" }, { "in": "query", "name": "q", "required": false, "schema": { "type": "string" }, "example": "example" }, { "in": "query", "name": "productId", "required": false, "schema": { "allOf": [ { "$ref": "#/components/schemas/productIdSchema" } ] }, "example": "123" }, { "in": "query", "name": "stockRecordId", "required": false, "schema": { "allOf": [ { "$ref": "#/components/schemas/inventoryStockRecordIdSchema" } ] }, "example": "123" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns inventory movements.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/inventoryMovementsResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "inventoryLocationIdSchema": { "type": "string", "format": "uuid" }, "inventoryMovementIdSchema": { "type": "string", "format": "uuid" }, "inventoryMovementSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/inventoryMovementIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "stockRecordId": { "$ref": "#/components/schemas/inventoryStockRecordIdSchema" }, "productId": { "$ref": "#/components/schemas/productIdSchema" }, "locationId": { "anyOf": [ { "$ref": "#/components/schemas/inventoryLocationIdSchema" }, { "type": "null" } ] }, "movementType": { "$ref": "#/components/schemas/inventoryMovementTypeSchema" }, "onHandDelta": { "type": "number" }, "allocatedDelta": { "type": "number" }, "quantityAfter": { "type": "number", "minimum": 0 }, "allocatedAfter": { "type": "number", "minimum": 0 }, "reason": { "type": [ "string", "null" ] }, "supplierId": { "anyOf": [ { "$ref": "#/components/schemas/supplierIdSchema" }, { "type": "null" } ] }, "referenceNumber": { "type": [ "string", "null" ] }, "taskMaterialId": { "type": [ "string", "null" ], "format": "uuid" }, "quoteId": { "type": [ "string", "null" ], "format": "uuid" }, "invoiceId": { "type": [ "string", "null" ], "format": "uuid" }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "occurredAt": { "type": "string", "format": "date-time" }, "createdAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "stockRecordId", "productId", "locationId", "movementType", "onHandDelta", "allocatedDelta", "quantityAfter", "allocatedAfter", "reason", "supplierId", "referenceNumber", "taskMaterialId", "quoteId", "invoiceId", "createdBy", "occurredAt", "createdAt" ] }, "inventoryMovementTypeSchema": { "type": "string", "enum": [ "initial_count", "purchase_receipt", "sale", "reservation", "reservation_release", "reservation_transfer", "adjustment", "transfer_out", "transfer_in", "return", "write_off", "cycle_count" ] }, "inventoryMovementsResponseSchema": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/inventoryMovementSchema" } }, "pagination": { "$ref": "#/components/schemas/listPaginationSchema" } }, "required": [ "data", "pagination" ] }, "inventoryStockRecordIdSchema": { "type": "string", "format": "uuid" }, "listPaginationSchema": { "type": "object", "properties": { "offset": { "type": "integer", "minimum": 0 }, "limit": { "type": "integer", "minimum": 1, "maximum": 100 }, "total": { "type": "integer", "minimum": 0 }, "hasMore": { "type": "boolean" }, "totalIsEstimate": { "type": "boolean" } }, "required": [ "offset", "limit", "total", "hasMore" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "productIdSchema": { "type": "string", "format": "uuid" }, "supplierIdSchema": { "type": "string", "format": "uuid" } } ``` # Adjust inventory stock. (/openapi/adjustInventoryStock) Apply a stock adjustment and return the updated stock record. ## POST /v1/organizations/{organizationId}/inventory/stock-adjustments ```json { "operationId": "adjustInventoryStock", "summary": "Adjust inventory stock.", "description": "Apply a stock adjustment and return the updated stock record.", "tags": [ "Assets" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/inventoryStockAdjustmentInputSchema" } } }, "description": "The inventory stock adjustment payload." }, "responses": { "200": { "description": "Returns the updated stock record.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/inventoryStockRecordSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "assetImageIdSchema": { "type": "string", "format": "uuid" }, "assetImageSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/assetImageIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "productId": { "anyOf": [ { "$ref": "#/components/schemas/productIdSchema" }, { "type": "null" } ] }, "url": { "type": "string", "format": "uri" }, "altText": { "type": [ "string", "null" ] }, "position": { "type": "integer", "minimum": 0 }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "productId", "url", "altText", "position", "createdAt", "updatedAt" ] }, "brandIdSchema": { "type": "string", "format": "uuid" }, "iconNameSchema": { "type": "string", "minLength": 1, "maxLength": 120, "pattern": "^[A-Za-z0-9]+Icon$" }, "inventoryLocationIdSchema": { "type": "string", "format": "uuid" }, "inventoryLocationSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/inventoryLocationIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 240 }, "description": { "type": [ "string", "null" ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "name", "description", "createdAt", "updatedAt" ] }, "inventoryStatusSchema": { "type": "string", "enum": [ "available", "quarantined", "expired", "archived" ] }, "inventoryStockAdjustmentInputSchema": { "type": "object", "properties": { "stockRecordId": { "$ref": "#/components/schemas/inventoryStockRecordIdSchema" }, "onHandQuantity": { "type": "number", "minimum": 0 }, "reason": { "type": "string", "minLength": 1, "maxLength": 1000 }, "movementType": { "type": "string", "enum": [ "adjustment", "cycle_count" ] } }, "required": [ "stockRecordId", "onHandQuantity", "reason" ] }, "inventoryStockRecordIdSchema": { "type": "string", "format": "uuid" }, "inventoryStockRecordSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/inventoryStockRecordIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "productId": { "$ref": "#/components/schemas/productIdSchema" }, "product": { "anyOf": [ { "$ref": "#/components/schemas/productListItemSchema" }, { "type": "null" } ] }, "variantId": { "type": "string", "format": "uuid" }, "locationId": { "anyOf": [ { "$ref": "#/components/schemas/inventoryLocationIdSchema" }, { "type": "null" } ] }, "location": { "anyOf": [ { "$ref": "#/components/schemas/inventoryLocationSchema" }, { "type": "null" } ] }, "onHandQuantity": { "type": "number", "minimum": 0 }, "allocatedQuantity": { "type": "number", "minimum": 0 }, "availableQuantity": { "type": "number", "minimum": 0 }, "lotNumber": { "type": [ "string", "null" ] }, "serialNumber": { "type": [ "string", "null" ] }, "expiresOn": { "$ref": "#/components/schemas/nullableDate" }, "status": { "$ref": "#/components/schemas/inventoryStatusSchema" }, "notes": { "type": [ "string", "null" ] }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "updatedBy": { "type": [ "string", "null" ], "format": "uuid" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" }, "binLocation": { "type": [ "string", "null" ] }, "unitCost": { "type": [ "number", "null" ] }, "currency": { "type": [ "string", "null" ] }, "lastCountedOn": { "$ref": "#/components/schemas/nullableDate" } }, "required": [ "id", "organizationId", "productId", "product", "variantId", "locationId", "location", "onHandQuantity", "allocatedQuantity", "availableQuantity", "lotNumber", "serialNumber", "expiresOn", "status", "notes", "createdBy", "updatedBy", "createdAt", "updatedAt", "binLocation", "unitCost", "currency", "lastCountedOn" ] }, "nullableDate": { "type": [ "string", "null" ], "format": "date" }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "organizationTagSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/tagIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "kind": { "$ref": "#/components/schemas/tagKindSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 80 }, "slug": { "$ref": "#/components/schemas/tagSlugSchema" }, "color": { "anyOf": [ { "$ref": "#/components/schemas/tagColorSchema" }, { "type": "null" } ] }, "description": { "type": [ "string", "null" ] }, "position": { "type": "integer", "minimum": 0 }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "kind", "name", "slug", "color", "description", "position", "createdAt", "updatedAt" ] }, "productCategorySchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "parentId": { "type": [ "string", "null" ], "format": "uuid" }, "name": { "type": "string", "minLength": 1, "maxLength": 120 }, "slug": { "$ref": "#/components/schemas/slugSchema" }, "icon": { "$ref": "#/components/schemas/iconNameSchema" }, "description": { "type": [ "string", "null" ] }, "position": { "type": "integer", "minimum": 0 }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "parentId", "name", "slug", "icon", "description", "position", "createdAt", "updatedAt" ] }, "productIdSchema": { "type": "string", "format": "uuid" }, "productListItemSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/productIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "categoryIds": { "type": "array", "items": { "type": "string", "format": "uuid" } }, "categories": { "type": "array", "items": { "$ref": "#/components/schemas/productCategorySchema" } }, "name": { "type": "string", "minLength": 1 }, "slug": { "$ref": "#/components/schemas/slugSchema" }, "brand": { "type": [ "object", "null" ], "properties": { "id": { "$ref": "#/components/schemas/brandIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 240 }, "slug": { "type": "string", "minLength": 1, "maxLength": 160, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" }, "description": { "type": [ "string", "null" ] }, "website": { "type": [ "string", "null" ] }, "logoUrl": { "type": [ "string", "null" ], "format": "uri" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "name", "slug", "description", "website", "logoUrl", "createdAt", "updatedAt" ] }, "manufacturer": { "type": [ "string", "null" ] }, "sku": { "type": [ "string", "null" ] }, "ean": { "type": [ "string", "null" ] }, "defaultSellingPrice": { "type": [ "number", "null" ] }, "vatPercentage": { "type": [ "number", "null" ] }, "status": { "$ref": "#/components/schemas/productStatusSchema" }, "updatedAt": { "type": "string", "format": "date-time" }, "tags": { "type": "array", "items": { "$ref": "#/components/schemas/organizationTagSchema" } }, "images": { "type": "array", "items": { "$ref": "#/components/schemas/assetImageSchema" } }, "suppliers": { "type": "array", "items": { "$ref": "#/components/schemas/productSupplierLinkSchema" } }, "manufacturerPartNumber": { "type": [ "string", "null" ] }, "currency": { "type": [ "string", "null" ] }, "unitOfMeasure": { "type": [ "string", "null" ] }, "stockStatus": { "anyOf": [ { "$ref": "#/components/schemas/stockStatusSchema" }, { "type": "null" } ] } }, "required": [ "id", "organizationId", "categoryIds", "categories", "name", "slug", "brand", "manufacturer", "sku", "ean", "defaultSellingPrice", "vatPercentage", "status", "updatedAt", "tags", "images", "suppliers", "manufacturerPartNumber", "currency", "unitOfMeasure" ] }, "productStatusSchema": { "type": "string", "enum": [ "active", "discontinued", "archived" ] }, "productSupplierLinkSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "productId": { "$ref": "#/components/schemas/productIdSchema" }, "supplierId": { "$ref": "#/components/schemas/supplierIdSchema" }, "supplier": { "anyOf": [ { "$ref": "#/components/schemas/supplierSchema" }, { "type": "null" } ] }, "supplierSku": { "type": [ "string", "null" ] }, "orderCode": { "type": [ "string", "null" ] }, "unitCost": { "type": [ "number", "null" ] }, "currency": { "type": [ "string", "null" ] }, "isPreferred": { "type": "boolean" }, "minOrderQuantity": { "type": [ "number", "null" ] }, "orderMultiple": { "type": [ "number", "null" ] }, "leadTimeDays": { "type": [ "integer", "null" ] }, "isActive": { "type": "boolean" }, "externalId": { "type": [ "string", "null" ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "productId", "supplierId", "supplier", "supplierSku", "orderCode", "unitCost", "currency", "isPreferred", "minOrderQuantity", "orderMultiple", "leadTimeDays", "isActive", "externalId", "createdAt", "updatedAt" ] }, "slugSchema": { "type": "string", "minLength": 1, "maxLength": 160, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" }, "stockStatusSchema": { "type": "string", "enum": [ "not_tracked", "discontinued", "out_of_stock", "backordered", "low_stock", "in_stock" ] }, "supplierIdSchema": { "type": "string", "format": "uuid" }, "supplierSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/supplierIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 240 }, "email": { "type": [ "string", "null" ] }, "phone": { "type": [ "string", "null" ] }, "website": { "type": [ "string", "null" ] }, "notes": { "type": [ "string", "null" ] }, "status": { "$ref": "#/components/schemas/supplierStatusSchema" }, "code": { "type": [ "string", "null" ] }, "accountNumber": { "type": [ "string", "null" ] }, "kvk": { "type": [ "string", "null" ] }, "btw": { "type": [ "string", "null" ] }, "orderingEmail": { "type": [ "string", "null" ] }, "billingEmail": { "type": [ "string", "null" ] }, "addressLine1": { "type": [ "string", "null" ] }, "addressLine2": { "type": [ "string", "null" ] }, "addressCity": { "type": [ "string", "null" ] }, "addressState": { "type": [ "string", "null" ] }, "addressPostalCode": { "type": [ "string", "null" ] }, "addressCountry": { "type": [ "string", "null" ] }, "currency": { "type": [ "string", "null" ] }, "paymentTermsDays": { "type": [ "integer", "null" ] }, "defaultLeadTimeDays": { "type": [ "integer", "null" ] }, "defaultInventoryLocationId": { "anyOf": [ { "$ref": "#/components/schemas/inventoryLocationIdSchema" }, { "type": "null" } ] }, "source": { "type": [ "string", "null" ] }, "externalId": { "type": [ "string", "null" ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "name", "email", "phone", "website", "notes", "status", "code", "accountNumber", "kvk", "btw", "orderingEmail", "billingEmail", "addressLine1", "addressLine2", "addressCity", "addressState", "addressPostalCode", "addressCountry", "currency", "paymentTermsDays", "defaultLeadTimeDays", "defaultInventoryLocationId", "source", "externalId", "createdAt", "updatedAt" ] }, "supplierStatusSchema": { "type": "string", "enum": [ "active", "inactive", "archived" ] }, "tagColorSchema": { "type": "string", "pattern": "^#(?:[0-9a-f]{3}|[0-9a-f]{6})$" }, "tagIdSchema": { "type": "string", "format": "uuid" }, "tagKindSchema": { "type": "string", "enum": [ "task", "customer", "product" ] }, "tagSlugSchema": { "type": "string", "minLength": 1, "maxLength": 160, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" } } ``` # List inventory stock records. (/openapi/listInventoryStockRecords) Return inventory stock records for an organization, optionally filtered by product. ## GET /v1/organizations/{organizationId}/inventory/stock-records ```json { "operationId": "listInventoryStockRecords", "summary": "List inventory stock records.", "description": "Return inventory stock records for an organization, optionally filtered by product.", "tags": [ "Assets" ], "parameters": [ { "in": "query", "name": "offset", "required": true, "schema": { "type": "string" }, "example": "example" }, { "in": "query", "name": "limit", "required": true, "schema": { "type": "string" }, "example": "example" }, { "in": "query", "name": "q", "required": false, "schema": { "type": "string" }, "example": "example" }, { "in": "query", "name": "productId", "required": false, "schema": { "allOf": [ { "$ref": "#/components/schemas/productIdSchema" } ] }, "example": "123" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns inventory stock records.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/inventoryStockRecordsResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "assetImageIdSchema": { "type": "string", "format": "uuid" }, "assetImageSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/assetImageIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "productId": { "anyOf": [ { "$ref": "#/components/schemas/productIdSchema" }, { "type": "null" } ] }, "url": { "type": "string", "format": "uri" }, "altText": { "type": [ "string", "null" ] }, "position": { "type": "integer", "minimum": 0 }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "productId", "url", "altText", "position", "createdAt", "updatedAt" ] }, "brandIdSchema": { "type": "string", "format": "uuid" }, "iconNameSchema": { "type": "string", "minLength": 1, "maxLength": 120, "pattern": "^[A-Za-z0-9]+Icon$" }, "inventoryLocationIdSchema": { "type": "string", "format": "uuid" }, "inventoryLocationSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/inventoryLocationIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 240 }, "description": { "type": [ "string", "null" ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "name", "description", "createdAt", "updatedAt" ] }, "inventoryStatusSchema": { "type": "string", "enum": [ "available", "quarantined", "expired", "archived" ] }, "inventoryStockRecordIdSchema": { "type": "string", "format": "uuid" }, "inventoryStockRecordSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/inventoryStockRecordIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "productId": { "$ref": "#/components/schemas/productIdSchema" }, "product": { "anyOf": [ { "$ref": "#/components/schemas/productListItemSchema" }, { "type": "null" } ] }, "variantId": { "type": "string", "format": "uuid" }, "locationId": { "anyOf": [ { "$ref": "#/components/schemas/inventoryLocationIdSchema" }, { "type": "null" } ] }, "location": { "anyOf": [ { "$ref": "#/components/schemas/inventoryLocationSchema" }, { "type": "null" } ] }, "onHandQuantity": { "type": "number", "minimum": 0 }, "allocatedQuantity": { "type": "number", "minimum": 0 }, "availableQuantity": { "type": "number", "minimum": 0 }, "lotNumber": { "type": [ "string", "null" ] }, "serialNumber": { "type": [ "string", "null" ] }, "expiresOn": { "$ref": "#/components/schemas/nullableDate" }, "status": { "$ref": "#/components/schemas/inventoryStatusSchema" }, "notes": { "type": [ "string", "null" ] }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "updatedBy": { "type": [ "string", "null" ], "format": "uuid" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" }, "binLocation": { "type": [ "string", "null" ] }, "unitCost": { "type": [ "number", "null" ] }, "currency": { "type": [ "string", "null" ] }, "lastCountedOn": { "$ref": "#/components/schemas/nullableDate" } }, "required": [ "id", "organizationId", "productId", "product", "variantId", "locationId", "location", "onHandQuantity", "allocatedQuantity", "availableQuantity", "lotNumber", "serialNumber", "expiresOn", "status", "notes", "createdBy", "updatedBy", "createdAt", "updatedAt", "binLocation", "unitCost", "currency", "lastCountedOn" ] }, "inventoryStockRecordsResponseSchema": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/inventoryStockRecordSchema" } }, "pagination": { "$ref": "#/components/schemas/listPaginationSchema" } }, "required": [ "data", "pagination" ] }, "listPaginationSchema": { "type": "object", "properties": { "offset": { "type": "integer", "minimum": 0 }, "limit": { "type": "integer", "minimum": 1, "maximum": 100 }, "total": { "type": "integer", "minimum": 0 }, "hasMore": { "type": "boolean" }, "totalIsEstimate": { "type": "boolean" } }, "required": [ "offset", "limit", "total", "hasMore" ] }, "nullableDate": { "type": [ "string", "null" ], "format": "date" }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "organizationTagSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/tagIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "kind": { "$ref": "#/components/schemas/tagKindSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 80 }, "slug": { "$ref": "#/components/schemas/tagSlugSchema" }, "color": { "anyOf": [ { "$ref": "#/components/schemas/tagColorSchema" }, { "type": "null" } ] }, "description": { "type": [ "string", "null" ] }, "position": { "type": "integer", "minimum": 0 }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "kind", "name", "slug", "color", "description", "position", "createdAt", "updatedAt" ] }, "productCategorySchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "parentId": { "type": [ "string", "null" ], "format": "uuid" }, "name": { "type": "string", "minLength": 1, "maxLength": 120 }, "slug": { "$ref": "#/components/schemas/slugSchema" }, "icon": { "$ref": "#/components/schemas/iconNameSchema" }, "description": { "type": [ "string", "null" ] }, "position": { "type": "integer", "minimum": 0 }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "parentId", "name", "slug", "icon", "description", "position", "createdAt", "updatedAt" ] }, "productIdSchema": { "type": "string", "format": "uuid" }, "productListItemSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/productIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "categoryIds": { "type": "array", "items": { "type": "string", "format": "uuid" } }, "categories": { "type": "array", "items": { "$ref": "#/components/schemas/productCategorySchema" } }, "name": { "type": "string", "minLength": 1 }, "slug": { "$ref": "#/components/schemas/slugSchema" }, "brand": { "type": [ "object", "null" ], "properties": { "id": { "$ref": "#/components/schemas/brandIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 240 }, "slug": { "type": "string", "minLength": 1, "maxLength": 160, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" }, "description": { "type": [ "string", "null" ] }, "website": { "type": [ "string", "null" ] }, "logoUrl": { "type": [ "string", "null" ], "format": "uri" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "name", "slug", "description", "website", "logoUrl", "createdAt", "updatedAt" ] }, "manufacturer": { "type": [ "string", "null" ] }, "sku": { "type": [ "string", "null" ] }, "ean": { "type": [ "string", "null" ] }, "defaultSellingPrice": { "type": [ "number", "null" ] }, "vatPercentage": { "type": [ "number", "null" ] }, "status": { "$ref": "#/components/schemas/productStatusSchema" }, "updatedAt": { "type": "string", "format": "date-time" }, "tags": { "type": "array", "items": { "$ref": "#/components/schemas/organizationTagSchema" } }, "images": { "type": "array", "items": { "$ref": "#/components/schemas/assetImageSchema" } }, "suppliers": { "type": "array", "items": { "$ref": "#/components/schemas/productSupplierLinkSchema" } }, "manufacturerPartNumber": { "type": [ "string", "null" ] }, "currency": { "type": [ "string", "null" ] }, "unitOfMeasure": { "type": [ "string", "null" ] }, "stockStatus": { "anyOf": [ { "$ref": "#/components/schemas/stockStatusSchema" }, { "type": "null" } ] } }, "required": [ "id", "organizationId", "categoryIds", "categories", "name", "slug", "brand", "manufacturer", "sku", "ean", "defaultSellingPrice", "vatPercentage", "status", "updatedAt", "tags", "images", "suppliers", "manufacturerPartNumber", "currency", "unitOfMeasure" ] }, "productStatusSchema": { "type": "string", "enum": [ "active", "discontinued", "archived" ] }, "productSupplierLinkSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "productId": { "$ref": "#/components/schemas/productIdSchema" }, "supplierId": { "$ref": "#/components/schemas/supplierIdSchema" }, "supplier": { "anyOf": [ { "$ref": "#/components/schemas/supplierSchema" }, { "type": "null" } ] }, "supplierSku": { "type": [ "string", "null" ] }, "orderCode": { "type": [ "string", "null" ] }, "unitCost": { "type": [ "number", "null" ] }, "currency": { "type": [ "string", "null" ] }, "isPreferred": { "type": "boolean" }, "minOrderQuantity": { "type": [ "number", "null" ] }, "orderMultiple": { "type": [ "number", "null" ] }, "leadTimeDays": { "type": [ "integer", "null" ] }, "isActive": { "type": "boolean" }, "externalId": { "type": [ "string", "null" ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "productId", "supplierId", "supplier", "supplierSku", "orderCode", "unitCost", "currency", "isPreferred", "minOrderQuantity", "orderMultiple", "leadTimeDays", "isActive", "externalId", "createdAt", "updatedAt" ] }, "slugSchema": { "type": "string", "minLength": 1, "maxLength": 160, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" }, "stockStatusSchema": { "type": "string", "enum": [ "not_tracked", "discontinued", "out_of_stock", "backordered", "low_stock", "in_stock" ] }, "supplierIdSchema": { "type": "string", "format": "uuid" }, "supplierSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/supplierIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 240 }, "email": { "type": [ "string", "null" ] }, "phone": { "type": [ "string", "null" ] }, "website": { "type": [ "string", "null" ] }, "notes": { "type": [ "string", "null" ] }, "status": { "$ref": "#/components/schemas/supplierStatusSchema" }, "code": { "type": [ "string", "null" ] }, "accountNumber": { "type": [ "string", "null" ] }, "kvk": { "type": [ "string", "null" ] }, "btw": { "type": [ "string", "null" ] }, "orderingEmail": { "type": [ "string", "null" ] }, "billingEmail": { "type": [ "string", "null" ] }, "addressLine1": { "type": [ "string", "null" ] }, "addressLine2": { "type": [ "string", "null" ] }, "addressCity": { "type": [ "string", "null" ] }, "addressState": { "type": [ "string", "null" ] }, "addressPostalCode": { "type": [ "string", "null" ] }, "addressCountry": { "type": [ "string", "null" ] }, "currency": { "type": [ "string", "null" ] }, "paymentTermsDays": { "type": [ "integer", "null" ] }, "defaultLeadTimeDays": { "type": [ "integer", "null" ] }, "defaultInventoryLocationId": { "anyOf": [ { "$ref": "#/components/schemas/inventoryLocationIdSchema" }, { "type": "null" } ] }, "source": { "type": [ "string", "null" ] }, "externalId": { "type": [ "string", "null" ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "name", "email", "phone", "website", "notes", "status", "code", "accountNumber", "kvk", "btw", "orderingEmail", "billingEmail", "addressLine1", "addressLine2", "addressCity", "addressState", "addressPostalCode", "addressCountry", "currency", "paymentTermsDays", "defaultLeadTimeDays", "defaultInventoryLocationId", "source", "externalId", "createdAt", "updatedAt" ] }, "supplierStatusSchema": { "type": "string", "enum": [ "active", "inactive", "archived" ] }, "tagColorSchema": { "type": "string", "pattern": "^#(?:[0-9a-f]{3}|[0-9a-f]{6})$" }, "tagIdSchema": { "type": "string", "format": "uuid" }, "tagKindSchema": { "type": "string", "enum": [ "task", "customer", "product" ] }, "tagSlugSchema": { "type": "string", "minLength": 1, "maxLength": 160, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" } } ``` # Create an inventory stock record. (/openapi/createInventoryStockRecord) Create an inventory stock record for a product. ## POST /v1/organizations/{organizationId}/inventory/stock-records ```json { "operationId": "createInventoryStockRecord", "summary": "Create an inventory stock record.", "description": "Create an inventory stock record for a product.", "tags": [ "Assets" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/inventoryStockRecordInputSchema" } } }, "description": "The inventory stock record payload." }, "responses": { "201": { "description": "Returns the created stock record.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/inventoryStockRecordSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "assetImageIdSchema": { "type": "string", "format": "uuid" }, "assetImageSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/assetImageIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "productId": { "anyOf": [ { "$ref": "#/components/schemas/productIdSchema" }, { "type": "null" } ] }, "url": { "type": "string", "format": "uri" }, "altText": { "type": [ "string", "null" ] }, "position": { "type": "integer", "minimum": 0 }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "productId", "url", "altText", "position", "createdAt", "updatedAt" ] }, "brandIdSchema": { "type": "string", "format": "uuid" }, "currencyCodeSchema": { "type": "string", "pattern": "^[A-Z]{3}$" }, "iconNameSchema": { "type": "string", "minLength": 1, "maxLength": 120, "pattern": "^[A-Za-z0-9]+Icon$" }, "inventoryLocationIdSchema": { "type": "string", "format": "uuid" }, "inventoryLocationSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/inventoryLocationIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 240 }, "description": { "type": [ "string", "null" ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "name", "description", "createdAt", "updatedAt" ] }, "inventoryStatusSchema": { "type": "string", "enum": [ "available", "quarantined", "expired", "archived" ] }, "inventoryStockRecordIdSchema": { "type": "string", "format": "uuid" }, "inventoryStockRecordInputSchema": { "type": "object", "properties": { "productId": { "$ref": "#/components/schemas/productIdSchema" }, "variantId": { "type": "string", "format": "uuid" }, "locationId": { "anyOf": [ { "$ref": "#/components/schemas/inventoryLocationIdSchema" }, { "type": "null" } ] }, "locationName": { "allOf": [ { "$ref": "#/components/schemas/nullableShortText" } ] }, "onHandQuantity": { "type": "number", "minimum": 0 }, "lotNumber": { "allOf": [ { "$ref": "#/components/schemas/nullableShortText" } ] }, "serialNumber": { "allOf": [ { "$ref": "#/components/schemas/nullableShortText" } ] }, "expiresOn": { "allOf": [ { "$ref": "#/components/schemas/nullableDate" } ] }, "status": { "allOf": [ { "$ref": "#/components/schemas/inventoryStatusSchema" } ] }, "notes": { "allOf": [ { "$ref": "#/components/schemas/nullableLongText" } ] }, "binLocation": { "allOf": [ { "$ref": "#/components/schemas/nullableShortText" } ] }, "unitCost": { "allOf": [ { "$ref": "#/components/schemas/nullableMoney" } ] }, "currency": { "allOf": [ { "$ref": "#/components/schemas/nullableCurrency" } ] }, "lastCountedOn": { "allOf": [ { "$ref": "#/components/schemas/nullableDate" } ] } }, "required": [ "productId", "onHandQuantity" ] }, "inventoryStockRecordSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/inventoryStockRecordIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "productId": { "$ref": "#/components/schemas/productIdSchema" }, "product": { "anyOf": [ { "$ref": "#/components/schemas/productListItemSchema" }, { "type": "null" } ] }, "variantId": { "type": "string", "format": "uuid" }, "locationId": { "anyOf": [ { "$ref": "#/components/schemas/inventoryLocationIdSchema" }, { "type": "null" } ] }, "location": { "anyOf": [ { "$ref": "#/components/schemas/inventoryLocationSchema" }, { "type": "null" } ] }, "onHandQuantity": { "type": "number", "minimum": 0 }, "allocatedQuantity": { "type": "number", "minimum": 0 }, "availableQuantity": { "type": "number", "minimum": 0 }, "lotNumber": { "type": [ "string", "null" ] }, "serialNumber": { "type": [ "string", "null" ] }, "expiresOn": { "$ref": "#/components/schemas/nullableDate" }, "status": { "$ref": "#/components/schemas/inventoryStatusSchema" }, "notes": { "type": [ "string", "null" ] }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "updatedBy": { "type": [ "string", "null" ], "format": "uuid" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" }, "binLocation": { "type": [ "string", "null" ] }, "unitCost": { "type": [ "number", "null" ] }, "currency": { "type": [ "string", "null" ] }, "lastCountedOn": { "$ref": "#/components/schemas/nullableDate" } }, "required": [ "id", "organizationId", "productId", "product", "variantId", "locationId", "location", "onHandQuantity", "allocatedQuantity", "availableQuantity", "lotNumber", "serialNumber", "expiresOn", "status", "notes", "createdBy", "updatedBy", "createdAt", "updatedAt", "binLocation", "unitCost", "currency", "lastCountedOn" ] }, "nullableCurrency": { "anyOf": [ { "$ref": "#/components/schemas/currencyCodeSchema" }, { "type": "null" } ] }, "nullableDate": { "type": [ "string", "null" ], "format": "date" }, "nullableLongText": { "type": [ "string", "null" ], "maxLength": 4000 }, "nullableMoney": { "type": [ "number", "null" ], "minimum": 0 }, "nullableShortText": { "type": [ "string", "null" ], "maxLength": 240 }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "organizationTagSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/tagIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "kind": { "$ref": "#/components/schemas/tagKindSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 80 }, "slug": { "$ref": "#/components/schemas/tagSlugSchema" }, "color": { "anyOf": [ { "$ref": "#/components/schemas/tagColorSchema" }, { "type": "null" } ] }, "description": { "type": [ "string", "null" ] }, "position": { "type": "integer", "minimum": 0 }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "kind", "name", "slug", "color", "description", "position", "createdAt", "updatedAt" ] }, "productCategorySchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "parentId": { "type": [ "string", "null" ], "format": "uuid" }, "name": { "type": "string", "minLength": 1, "maxLength": 120 }, "slug": { "$ref": "#/components/schemas/slugSchema" }, "icon": { "$ref": "#/components/schemas/iconNameSchema" }, "description": { "type": [ "string", "null" ] }, "position": { "type": "integer", "minimum": 0 }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "parentId", "name", "slug", "icon", "description", "position", "createdAt", "updatedAt" ] }, "productIdSchema": { "type": "string", "format": "uuid" }, "productListItemSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/productIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "categoryIds": { "type": "array", "items": { "type": "string", "format": "uuid" } }, "categories": { "type": "array", "items": { "$ref": "#/components/schemas/productCategorySchema" } }, "name": { "type": "string", "minLength": 1 }, "slug": { "$ref": "#/components/schemas/slugSchema" }, "brand": { "type": [ "object", "null" ], "properties": { "id": { "$ref": "#/components/schemas/brandIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 240 }, "slug": { "type": "string", "minLength": 1, "maxLength": 160, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" }, "description": { "type": [ "string", "null" ] }, "website": { "type": [ "string", "null" ] }, "logoUrl": { "type": [ "string", "null" ], "format": "uri" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "name", "slug", "description", "website", "logoUrl", "createdAt", "updatedAt" ] }, "manufacturer": { "type": [ "string", "null" ] }, "sku": { "type": [ "string", "null" ] }, "ean": { "type": [ "string", "null" ] }, "defaultSellingPrice": { "type": [ "number", "null" ] }, "vatPercentage": { "type": [ "number", "null" ] }, "status": { "$ref": "#/components/schemas/productStatusSchema" }, "updatedAt": { "type": "string", "format": "date-time" }, "tags": { "type": "array", "items": { "$ref": "#/components/schemas/organizationTagSchema" } }, "images": { "type": "array", "items": { "$ref": "#/components/schemas/assetImageSchema" } }, "suppliers": { "type": "array", "items": { "$ref": "#/components/schemas/productSupplierLinkSchema" } }, "manufacturerPartNumber": { "type": [ "string", "null" ] }, "currency": { "type": [ "string", "null" ] }, "unitOfMeasure": { "type": [ "string", "null" ] }, "stockStatus": { "anyOf": [ { "$ref": "#/components/schemas/stockStatusSchema" }, { "type": "null" } ] } }, "required": [ "id", "organizationId", "categoryIds", "categories", "name", "slug", "brand", "manufacturer", "sku", "ean", "defaultSellingPrice", "vatPercentage", "status", "updatedAt", "tags", "images", "suppliers", "manufacturerPartNumber", "currency", "unitOfMeasure" ] }, "productStatusSchema": { "type": "string", "enum": [ "active", "discontinued", "archived" ] }, "productSupplierLinkSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "productId": { "$ref": "#/components/schemas/productIdSchema" }, "supplierId": { "$ref": "#/components/schemas/supplierIdSchema" }, "supplier": { "anyOf": [ { "$ref": "#/components/schemas/supplierSchema" }, { "type": "null" } ] }, "supplierSku": { "type": [ "string", "null" ] }, "orderCode": { "type": [ "string", "null" ] }, "unitCost": { "type": [ "number", "null" ] }, "currency": { "type": [ "string", "null" ] }, "isPreferred": { "type": "boolean" }, "minOrderQuantity": { "type": [ "number", "null" ] }, "orderMultiple": { "type": [ "number", "null" ] }, "leadTimeDays": { "type": [ "integer", "null" ] }, "isActive": { "type": "boolean" }, "externalId": { "type": [ "string", "null" ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "productId", "supplierId", "supplier", "supplierSku", "orderCode", "unitCost", "currency", "isPreferred", "minOrderQuantity", "orderMultiple", "leadTimeDays", "isActive", "externalId", "createdAt", "updatedAt" ] }, "slugSchema": { "type": "string", "minLength": 1, "maxLength": 160, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" }, "stockStatusSchema": { "type": "string", "enum": [ "not_tracked", "discontinued", "out_of_stock", "backordered", "low_stock", "in_stock" ] }, "supplierIdSchema": { "type": "string", "format": "uuid" }, "supplierSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/supplierIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 240 }, "email": { "type": [ "string", "null" ] }, "phone": { "type": [ "string", "null" ] }, "website": { "type": [ "string", "null" ] }, "notes": { "type": [ "string", "null" ] }, "status": { "$ref": "#/components/schemas/supplierStatusSchema" }, "code": { "type": [ "string", "null" ] }, "accountNumber": { "type": [ "string", "null" ] }, "kvk": { "type": [ "string", "null" ] }, "btw": { "type": [ "string", "null" ] }, "orderingEmail": { "type": [ "string", "null" ] }, "billingEmail": { "type": [ "string", "null" ] }, "addressLine1": { "type": [ "string", "null" ] }, "addressLine2": { "type": [ "string", "null" ] }, "addressCity": { "type": [ "string", "null" ] }, "addressState": { "type": [ "string", "null" ] }, "addressPostalCode": { "type": [ "string", "null" ] }, "addressCountry": { "type": [ "string", "null" ] }, "currency": { "type": [ "string", "null" ] }, "paymentTermsDays": { "type": [ "integer", "null" ] }, "defaultLeadTimeDays": { "type": [ "integer", "null" ] }, "defaultInventoryLocationId": { "anyOf": [ { "$ref": "#/components/schemas/inventoryLocationIdSchema" }, { "type": "null" } ] }, "source": { "type": [ "string", "null" ] }, "externalId": { "type": [ "string", "null" ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "name", "email", "phone", "website", "notes", "status", "code", "accountNumber", "kvk", "btw", "orderingEmail", "billingEmail", "addressLine1", "addressLine2", "addressCity", "addressState", "addressPostalCode", "addressCountry", "currency", "paymentTermsDays", "defaultLeadTimeDays", "defaultInventoryLocationId", "source", "externalId", "createdAt", "updatedAt" ] }, "supplierStatusSchema": { "type": "string", "enum": [ "active", "inactive", "archived" ] }, "tagColorSchema": { "type": "string", "pattern": "^#(?:[0-9a-f]{3}|[0-9a-f]{6})$" }, "tagIdSchema": { "type": "string", "format": "uuid" }, "tagKindSchema": { "type": "string", "enum": [ "task", "customer", "product" ] }, "tagSlugSchema": { "type": "string", "minLength": 1, "maxLength": 160, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" } } ``` # Get a product category. (/openapi/getProductCategory) Return a product category for the specified organization. ## GET /v1/organizations/{organizationId}/product-categories/{categoryId} ```json { "operationId": "getProductCategory", "summary": "Get a product category.", "description": "Return a product category for the specified organization.", "tags": [ "Assets" ], "parameters": [ { "in": "path", "name": "categoryId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns the requested product category.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/productCategorySchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "iconNameSchema": { "type": "string", "minLength": 1, "maxLength": 120, "pattern": "^[A-Za-z0-9]+Icon$" }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "productCategorySchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "parentId": { "type": [ "string", "null" ], "format": "uuid" }, "name": { "type": "string", "minLength": 1, "maxLength": 120 }, "slug": { "$ref": "#/components/schemas/slugSchema" }, "icon": { "$ref": "#/components/schemas/iconNameSchema" }, "description": { "type": [ "string", "null" ] }, "position": { "type": "integer", "minimum": 0 }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "parentId", "name", "slug", "icon", "description", "position", "createdAt", "updatedAt" ] }, "slugSchema": { "type": "string", "minLength": 1, "maxLength": 160, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" } } ``` # Update a product category. (/openapi/updateProductCategory) Update a product category for the specified organization. ## PATCH /v1/organizations/{organizationId}/product-categories/{categoryId} ```json { "operationId": "updateProductCategory", "summary": "Update a product category.", "description": "Update a product category for the specified organization.", "tags": [ "Assets" ], "parameters": [ { "in": "path", "name": "categoryId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/productCategoryInputSchema" } } }, "description": "The product category payload." }, "responses": { "200": { "description": "Returns the updated product category.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/productCategorySchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "iconNameInputSchema": { "type": "string", "minLength": 1, "maxLength": 120, "pattern": "^[A-Za-z0-9]+Icon$" }, "iconNameSchema": { "type": "string", "minLength": 1, "maxLength": 120, "pattern": "^[A-Za-z0-9]+Icon$" }, "nullableLongText": { "type": [ "string", "null" ], "maxLength": 4000 }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "productCategoryInputSchema": { "type": "object", "properties": { "parentId": { "type": [ "string", "null" ], "format": "uuid" }, "name": { "type": "string", "minLength": 1, "maxLength": 120 }, "slug": { "allOf": [ { "$ref": "#/components/schemas/slugInputSchema" } ] }, "icon": { "allOf": [ { "$ref": "#/components/schemas/iconNameInputSchema" } ] }, "description": { "allOf": [ { "$ref": "#/components/schemas/nullableLongText" } ] }, "position": { "type": "integer", "minimum": 0 } }, "required": [ "name" ] }, "productCategorySchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "parentId": { "type": [ "string", "null" ], "format": "uuid" }, "name": { "type": "string", "minLength": 1, "maxLength": 120 }, "slug": { "$ref": "#/components/schemas/slugSchema" }, "icon": { "$ref": "#/components/schemas/iconNameSchema" }, "description": { "type": [ "string", "null" ] }, "position": { "type": "integer", "minimum": 0 }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "parentId", "name", "slug", "icon", "description", "position", "createdAt", "updatedAt" ] }, "slugInputSchema": { "type": "string", "minLength": 1, "maxLength": 160, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" }, "slugSchema": { "type": "string", "minLength": 1, "maxLength": 160, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" } } ``` # Delete a product category. (/openapi/deleteProductCategory) Delete a product category for the specified organization. ## DELETE /v1/organizations/{organizationId}/product-categories/{categoryId} ```json { "operationId": "deleteProductCategory", "summary": "Delete a product category.", "description": "Delete a product category for the specified organization.", "tags": [ "Assets" ], "parameters": [ { "in": "path", "name": "categoryId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns a success payload when the product category is deleted.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/publicApiSuccessResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "organizationIdSchema": { "type": "string", "format": "uuid" }, "publicApiSuccessResponseSchema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [ true ] } }, "required": [ "success" ] } } ``` # Get a product. (/openapi/getProduct) Return a product for the specified organization. ## GET /v1/organizations/{organizationId}/products/{productId} ```json { "operationId": "getProduct", "summary": "Get a product.", "description": "Return a product for the specified organization.", "tags": [ "Assets" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "productId", "required": true, "schema": { "$ref": "#/components/schemas/productIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns the requested product.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/productSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "assetCustomAttributeSchema": { "type": "object", "properties": { "valueType": { "$ref": "#/components/schemas/customAttributeValueTypeSchema" }, "textValue": { "type": [ "string", "null" ] }, "numberValue": { "type": [ "number", "null" ] }, "booleanValue": { "type": [ "boolean", "null" ] }, "dateValue": { "$ref": "#/components/schemas/nullableDate" }, "urlValue": { "type": [ "string", "null" ] }, "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "productId": { "anyOf": [ { "$ref": "#/components/schemas/productIdSchema" }, { "type": "null" } ] }, "customerAssetId": { "anyOf": [ { "$ref": "#/components/schemas/customerAssetIdSchema" }, { "type": "null" } ] }, "key": { "type": "string", "minLength": 1, "maxLength": 120 }, "label": { "type": "string", "minLength": 1, "maxLength": 160 }, "position": { "type": "integer", "minimum": 0 }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "valueType", "textValue", "numberValue", "booleanValue", "dateValue", "urlValue", "id", "organizationId", "productId", "customerAssetId", "key", "label", "position", "createdAt", "updatedAt" ] }, "assetImageIdSchema": { "type": "string", "format": "uuid" }, "assetImageSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/assetImageIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "productId": { "anyOf": [ { "$ref": "#/components/schemas/productIdSchema" }, { "type": "null" } ] }, "url": { "type": "string", "format": "uri" }, "altText": { "type": [ "string", "null" ] }, "position": { "type": "integer", "minimum": 0 }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "productId", "url", "altText", "position", "createdAt", "updatedAt" ] }, "brandIdSchema": { "type": "string", "format": "uuid" }, "customAttributeValueTypeSchema": { "type": "string", "enum": [ "text", "number", "boolean", "date", "url" ] }, "customerAssetIdSchema": { "type": "integer", "exclusiveMinimum": 0 }, "digitalDeliveryKindSchema": { "type": "string", "enum": [ "file", "license_key", "link", "manual" ] }, "iconNameSchema": { "type": "string", "minLength": 1, "maxLength": 120, "pattern": "^[A-Za-z0-9]+Icon$" }, "inventoryLocationIdSchema": { "type": "string", "format": "uuid" }, "nullableDate": { "type": [ "string", "null" ], "format": "date" }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "organizationTagSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/tagIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "kind": { "$ref": "#/components/schemas/tagKindSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 80 }, "slug": { "$ref": "#/components/schemas/tagSlugSchema" }, "color": { "anyOf": [ { "$ref": "#/components/schemas/tagColorSchema" }, { "type": "null" } ] }, "description": { "type": [ "string", "null" ] }, "position": { "type": "integer", "minimum": 0 }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "kind", "name", "slug", "color", "description", "position", "createdAt", "updatedAt" ] }, "productCategorySchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "parentId": { "type": [ "string", "null" ], "format": "uuid" }, "name": { "type": "string", "minLength": 1, "maxLength": 120 }, "slug": { "$ref": "#/components/schemas/slugSchema" }, "icon": { "$ref": "#/components/schemas/iconNameSchema" }, "description": { "type": [ "string", "null" ] }, "position": { "type": "integer", "minimum": 0 }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "parentId", "name", "slug", "icon", "description", "position", "createdAt", "updatedAt" ] }, "productIdSchema": { "type": "string", "format": "uuid" }, "productOptionSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "name": { "type": "string" }, "position": { "type": "integer" }, "values": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "value": { "type": "string" }, "position": { "type": "integer" } }, "required": [ "id", "value", "position" ] } } }, "required": [ "id", "name", "position", "values" ] }, "productSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/productIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "categoryIds": { "type": "array", "items": { "type": "string", "format": "uuid" } }, "categories": { "type": "array", "items": { "$ref": "#/components/schemas/productCategorySchema" } }, "name": { "type": "string", "minLength": 1 }, "slug": { "$ref": "#/components/schemas/slugSchema" }, "description": { "type": [ "string", "null" ] }, "brandId": { "anyOf": [ { "$ref": "#/components/schemas/brandIdSchema" }, { "type": "null" } ] }, "brand": { "type": [ "object", "null" ], "properties": { "id": { "$ref": "#/components/schemas/brandIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 240 }, "slug": { "type": "string", "minLength": 1, "maxLength": 160, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" }, "description": { "type": [ "string", "null" ] }, "website": { "type": [ "string", "null" ] }, "logoUrl": { "type": [ "string", "null" ], "format": "uri" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "name", "slug", "description", "website", "logoUrl", "createdAt", "updatedAt" ] }, "manufacturer": { "type": [ "string", "null" ] }, "model": { "type": [ "string", "null" ] }, "sku": { "type": [ "string", "null" ] }, "ean": { "type": [ "string", "null" ] }, "defaultPurchasePrice": { "type": [ "number", "null" ] }, "defaultSellingPrice": { "type": [ "number", "null" ] }, "vatPercentage": { "type": [ "number", "null" ] }, "status": { "$ref": "#/components/schemas/productStatusSchema" }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "updatedBy": { "type": [ "string", "null" ], "format": "uuid" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" }, "tags": { "type": "array", "items": { "$ref": "#/components/schemas/organizationTagSchema" } }, "images": { "type": "array", "items": { "$ref": "#/components/schemas/assetImageSchema" } }, "suppliers": { "type": "array", "items": { "$ref": "#/components/schemas/productSupplierLinkSchema" } }, "customAttributes": { "type": "array", "items": { "$ref": "#/components/schemas/assetCustomAttributeSchema" } }, "manufacturerPartNumber": { "type": [ "string", "null" ] }, "currency": { "type": [ "string", "null" ] }, "unitOfMeasure": { "type": [ "string", "null" ] }, "productType": { "$ref": "#/components/schemas/productTypeSchema" }, "trackInventory": { "type": "boolean" }, "weightGrams": { "type": [ "integer", "null" ] }, "hsCode": { "type": [ "string", "null" ] }, "countryOfOrigin": { "type": [ "string", "null" ] }, "digitalDelivery": { "anyOf": [ { "$ref": "#/components/schemas/digitalDeliveryKindSchema" }, { "type": "null" } ] }, "options": { "type": "array", "items": { "$ref": "#/components/schemas/productOptionSchema" } }, "variants": { "type": "array", "items": { "$ref": "#/components/schemas/productVariantSchema" } } }, "required": [ "id", "organizationId", "categoryIds", "categories", "name", "slug", "description", "brandId", "brand", "manufacturer", "model", "sku", "ean", "defaultPurchasePrice", "defaultSellingPrice", "vatPercentage", "status", "createdBy", "updatedBy", "createdAt", "updatedAt", "tags", "images", "suppliers", "customAttributes", "manufacturerPartNumber", "currency", "unitOfMeasure", "productType", "trackInventory", "weightGrams", "hsCode", "countryOfOrigin", "digitalDelivery", "options", "variants" ] }, "productStatusSchema": { "type": "string", "enum": [ "active", "discontinued", "archived" ] }, "productSupplierLinkSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "productId": { "$ref": "#/components/schemas/productIdSchema" }, "supplierId": { "$ref": "#/components/schemas/supplierIdSchema" }, "supplier": { "anyOf": [ { "$ref": "#/components/schemas/supplierSchema" }, { "type": "null" } ] }, "supplierSku": { "type": [ "string", "null" ] }, "orderCode": { "type": [ "string", "null" ] }, "unitCost": { "type": [ "number", "null" ] }, "currency": { "type": [ "string", "null" ] }, "isPreferred": { "type": "boolean" }, "minOrderQuantity": { "type": [ "number", "null" ] }, "orderMultiple": { "type": [ "number", "null" ] }, "leadTimeDays": { "type": [ "integer", "null" ] }, "isActive": { "type": "boolean" }, "externalId": { "type": [ "string", "null" ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "productId", "supplierId", "supplier", "supplierSku", "orderCode", "unitCost", "currency", "isPreferred", "minOrderQuantity", "orderMultiple", "leadTimeDays", "isActive", "externalId", "createdAt", "updatedAt" ] }, "productTypeSchema": { "type": "string", "enum": [ "physical", "digital" ] }, "productVariantIdSchema": { "type": "string", "format": "uuid" }, "productVariantSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/productVariantIdSchema" }, "productId": { "$ref": "#/components/schemas/productIdSchema" }, "title": { "type": "string" }, "sku": { "type": [ "string", "null" ] }, "ean": { "type": [ "string", "null" ] }, "manufacturerPartNumber": { "type": [ "string", "null" ] }, "sellingPrice": { "type": [ "number", "null" ] }, "purchasePrice": { "type": [ "number", "null" ] }, "averageCost": { "type": [ "number", "null" ] }, "weightGrams": { "type": [ "integer", "null" ] }, "status": { "$ref": "#/components/schemas/productStatusSchema" }, "allowBackorder": { "type": "boolean" }, "isDefault": { "type": "boolean" }, "position": { "type": "integer" }, "imageId": { "type": [ "string", "null" ], "format": "uuid" }, "optionValueIds": { "type": "array", "items": { "type": "string", "format": "uuid" } } }, "required": [ "id", "productId", "title", "sku", "ean", "manufacturerPartNumber", "sellingPrice", "purchasePrice", "averageCost", "weightGrams", "status", "allowBackorder", "isDefault", "position", "imageId", "optionValueIds" ] }, "slugSchema": { "type": "string", "minLength": 1, "maxLength": 160, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" }, "supplierIdSchema": { "type": "string", "format": "uuid" }, "supplierSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/supplierIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 240 }, "email": { "type": [ "string", "null" ] }, "phone": { "type": [ "string", "null" ] }, "website": { "type": [ "string", "null" ] }, "notes": { "type": [ "string", "null" ] }, "status": { "$ref": "#/components/schemas/supplierStatusSchema" }, "code": { "type": [ "string", "null" ] }, "accountNumber": { "type": [ "string", "null" ] }, "kvk": { "type": [ "string", "null" ] }, "btw": { "type": [ "string", "null" ] }, "orderingEmail": { "type": [ "string", "null" ] }, "billingEmail": { "type": [ "string", "null" ] }, "addressLine1": { "type": [ "string", "null" ] }, "addressLine2": { "type": [ "string", "null" ] }, "addressCity": { "type": [ "string", "null" ] }, "addressState": { "type": [ "string", "null" ] }, "addressPostalCode": { "type": [ "string", "null" ] }, "addressCountry": { "type": [ "string", "null" ] }, "currency": { "type": [ "string", "null" ] }, "paymentTermsDays": { "type": [ "integer", "null" ] }, "defaultLeadTimeDays": { "type": [ "integer", "null" ] }, "defaultInventoryLocationId": { "anyOf": [ { "$ref": "#/components/schemas/inventoryLocationIdSchema" }, { "type": "null" } ] }, "source": { "type": [ "string", "null" ] }, "externalId": { "type": [ "string", "null" ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "name", "email", "phone", "website", "notes", "status", "code", "accountNumber", "kvk", "btw", "orderingEmail", "billingEmail", "addressLine1", "addressLine2", "addressCity", "addressState", "addressPostalCode", "addressCountry", "currency", "paymentTermsDays", "defaultLeadTimeDays", "defaultInventoryLocationId", "source", "externalId", "createdAt", "updatedAt" ] }, "supplierStatusSchema": { "type": "string", "enum": [ "active", "inactive", "archived" ] }, "tagColorSchema": { "type": "string", "pattern": "^#(?:[0-9a-f]{3}|[0-9a-f]{6})$" }, "tagIdSchema": { "type": "string", "format": "uuid" }, "tagKindSchema": { "type": "string", "enum": [ "task", "customer", "product" ] }, "tagSlugSchema": { "type": "string", "minLength": 1, "maxLength": 160, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" } } ``` # Update a product. (/openapi/updateProduct) Update a product for the specified organization. ## PATCH /v1/organizations/{organizationId}/products/{productId} ```json { "operationId": "updateProduct", "summary": "Update a product.", "description": "Update a product for the specified organization.", "tags": [ "Assets" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "productId", "required": true, "schema": { "$ref": "#/components/schemas/productIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/productInputSchema" } } }, "description": "The product payload.", "required": true }, "responses": { "200": { "description": "Returns the updated product.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/productSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "assetCustomAttributeSchema": { "type": "object", "properties": { "valueType": { "$ref": "#/components/schemas/customAttributeValueTypeSchema" }, "textValue": { "type": [ "string", "null" ] }, "numberValue": { "type": [ "number", "null" ] }, "booleanValue": { "type": [ "boolean", "null" ] }, "dateValue": { "$ref": "#/components/schemas/nullableDate" }, "urlValue": { "type": [ "string", "null" ] }, "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "productId": { "anyOf": [ { "$ref": "#/components/schemas/productIdSchema" }, { "type": "null" } ] }, "customerAssetId": { "anyOf": [ { "$ref": "#/components/schemas/customerAssetIdSchema" }, { "type": "null" } ] }, "key": { "type": "string", "minLength": 1, "maxLength": 120 }, "label": { "type": "string", "minLength": 1, "maxLength": 160 }, "position": { "type": "integer", "minimum": 0 }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "valueType", "textValue", "numberValue", "booleanValue", "dateValue", "urlValue", "id", "organizationId", "productId", "customerAssetId", "key", "label", "position", "createdAt", "updatedAt" ] }, "assetImageIdSchema": { "type": "string", "format": "uuid" }, "assetImageSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/assetImageIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "productId": { "anyOf": [ { "$ref": "#/components/schemas/productIdSchema" }, { "type": "null" } ] }, "url": { "type": "string", "format": "uri" }, "altText": { "type": [ "string", "null" ] }, "position": { "type": "integer", "minimum": 0 }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "productId", "url", "altText", "position", "createdAt", "updatedAt" ] }, "brandIdSchema": { "type": "string", "format": "uuid" }, "currencyCodeSchema": { "type": "string", "pattern": "^[A-Z]{3}$" }, "customAttributeValueTypeSchema": { "type": "string", "enum": [ "text", "number", "boolean", "date", "url" ] }, "customerAssetIdSchema": { "type": "integer", "exclusiveMinimum": 0 }, "digitalDeliveryKindSchema": { "type": "string", "enum": [ "file", "license_key", "link", "manual" ] }, "iconNameSchema": { "type": "string", "minLength": 1, "maxLength": 120, "pattern": "^[A-Za-z0-9]+Icon$" }, "inventoryLocationIdSchema": { "type": "string", "format": "uuid" }, "nullableCurrency": { "anyOf": [ { "$ref": "#/components/schemas/currencyCodeSchema" }, { "type": "null" } ] }, "nullableDate": { "type": [ "string", "null" ], "format": "date" }, "nullableLongText": { "type": [ "string", "null" ], "maxLength": 4000 }, "nullableMoney": { "type": [ "number", "null" ], "minimum": 0 }, "nullableShortText": { "type": [ "string", "null" ], "maxLength": 240 }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "organizationTagSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/tagIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "kind": { "$ref": "#/components/schemas/tagKindSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 80 }, "slug": { "$ref": "#/components/schemas/tagSlugSchema" }, "color": { "anyOf": [ { "$ref": "#/components/schemas/tagColorSchema" }, { "type": "null" } ] }, "description": { "type": [ "string", "null" ] }, "position": { "type": "integer", "minimum": 0 }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "kind", "name", "slug", "color", "description", "position", "createdAt", "updatedAt" ] }, "productCategorySchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "parentId": { "type": [ "string", "null" ], "format": "uuid" }, "name": { "type": "string", "minLength": 1, "maxLength": 120 }, "slug": { "$ref": "#/components/schemas/slugSchema" }, "icon": { "$ref": "#/components/schemas/iconNameSchema" }, "description": { "type": [ "string", "null" ] }, "position": { "type": "integer", "minimum": 0 }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "parentId", "name", "slug", "icon", "description", "position", "createdAt", "updatedAt" ] }, "productIdSchema": { "type": "string", "format": "uuid" }, "productInputSchema": { "type": "object", "properties": { "categoryIds": { "type": "array", "items": { "type": "string", "format": "uuid" } }, "tagIds": { "type": "array", "items": { "type": "string", "format": "uuid" }, "maxItems": 100 }, "name": { "type": "string", "minLength": 1, "maxLength": 240 }, "slug": { "allOf": [ { "$ref": "#/components/schemas/slugInputSchema" } ] }, "description": { "allOf": [ { "$ref": "#/components/schemas/nullableLongText" } ] }, "brandId": { "anyOf": [ { "$ref": "#/components/schemas/brandIdSchema" }, { "type": "null" } ] }, "manufacturer": { "allOf": [ { "$ref": "#/components/schemas/nullableShortText" } ] }, "model": { "allOf": [ { "$ref": "#/components/schemas/nullableShortText" } ] }, "sku": { "type": [ "string", "null" ], "maxLength": 120 }, "ean": { "type": [ "string", "null" ], "maxLength": 120 }, "defaultPurchasePrice": { "allOf": [ { "$ref": "#/components/schemas/nullableMoney" } ] }, "defaultSellingPrice": { "allOf": [ { "$ref": "#/components/schemas/nullableMoney" } ] }, "vatPercentage": { "type": [ "number", "null" ], "minimum": 0, "maximum": 100 }, "status": { "allOf": [ { "$ref": "#/components/schemas/productStatusSchema" } ] }, "manufacturerPartNumber": { "allOf": [ { "$ref": "#/components/schemas/nullableShortText" } ] }, "currency": { "allOf": [ { "$ref": "#/components/schemas/nullableCurrency" } ] }, "unitOfMeasure": { "allOf": [ { "$ref": "#/components/schemas/unitOfMeasureInputSchema" } ] }, "productType": { "allOf": [ { "$ref": "#/components/schemas/productTypeSchema" } ] }, "trackInventory": { "type": "boolean" }, "weightGrams": { "type": [ "integer", "null" ], "minimum": 0 }, "hsCode": { "type": [ "string", "null" ], "pattern": "^[0-9]{6,10}$" }, "countryOfOrigin": { "type": [ "string", "null" ], "pattern": "^[A-Z]{2}$" }, "digitalDelivery": { "anyOf": [ { "$ref": "#/components/schemas/digitalDeliveryKindSchema" }, { "type": "null" } ] } }, "required": [ "name" ] }, "productOptionSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "name": { "type": "string" }, "position": { "type": "integer" }, "values": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "value": { "type": "string" }, "position": { "type": "integer" } }, "required": [ "id", "value", "position" ] } } }, "required": [ "id", "name", "position", "values" ] }, "productSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/productIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "categoryIds": { "type": "array", "items": { "type": "string", "format": "uuid" } }, "categories": { "type": "array", "items": { "$ref": "#/components/schemas/productCategorySchema" } }, "name": { "type": "string", "minLength": 1 }, "slug": { "$ref": "#/components/schemas/slugSchema" }, "description": { "type": [ "string", "null" ] }, "brandId": { "anyOf": [ { "$ref": "#/components/schemas/brandIdSchema" }, { "type": "null" } ] }, "brand": { "type": [ "object", "null" ], "properties": { "id": { "$ref": "#/components/schemas/brandIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 240 }, "slug": { "type": "string", "minLength": 1, "maxLength": 160, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" }, "description": { "type": [ "string", "null" ] }, "website": { "type": [ "string", "null" ] }, "logoUrl": { "type": [ "string", "null" ], "format": "uri" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "name", "slug", "description", "website", "logoUrl", "createdAt", "updatedAt" ] }, "manufacturer": { "type": [ "string", "null" ] }, "model": { "type": [ "string", "null" ] }, "sku": { "type": [ "string", "null" ] }, "ean": { "type": [ "string", "null" ] }, "defaultPurchasePrice": { "type": [ "number", "null" ] }, "defaultSellingPrice": { "type": [ "number", "null" ] }, "vatPercentage": { "type": [ "number", "null" ] }, "status": { "$ref": "#/components/schemas/productStatusSchema" }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "updatedBy": { "type": [ "string", "null" ], "format": "uuid" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" }, "tags": { "type": "array", "items": { "$ref": "#/components/schemas/organizationTagSchema" } }, "images": { "type": "array", "items": { "$ref": "#/components/schemas/assetImageSchema" } }, "suppliers": { "type": "array", "items": { "$ref": "#/components/schemas/productSupplierLinkSchema" } }, "customAttributes": { "type": "array", "items": { "$ref": "#/components/schemas/assetCustomAttributeSchema" } }, "manufacturerPartNumber": { "type": [ "string", "null" ] }, "currency": { "type": [ "string", "null" ] }, "unitOfMeasure": { "type": [ "string", "null" ] }, "productType": { "$ref": "#/components/schemas/productTypeSchema" }, "trackInventory": { "type": "boolean" }, "weightGrams": { "type": [ "integer", "null" ] }, "hsCode": { "type": [ "string", "null" ] }, "countryOfOrigin": { "type": [ "string", "null" ] }, "digitalDelivery": { "anyOf": [ { "$ref": "#/components/schemas/digitalDeliveryKindSchema" }, { "type": "null" } ] }, "options": { "type": "array", "items": { "$ref": "#/components/schemas/productOptionSchema" } }, "variants": { "type": "array", "items": { "$ref": "#/components/schemas/productVariantSchema" } } }, "required": [ "id", "organizationId", "categoryIds", "categories", "name", "slug", "description", "brandId", "brand", "manufacturer", "model", "sku", "ean", "defaultPurchasePrice", "defaultSellingPrice", "vatPercentage", "status", "createdBy", "updatedBy", "createdAt", "updatedAt", "tags", "images", "suppliers", "customAttributes", "manufacturerPartNumber", "currency", "unitOfMeasure", "productType", "trackInventory", "weightGrams", "hsCode", "countryOfOrigin", "digitalDelivery", "options", "variants" ] }, "productStatusSchema": { "type": "string", "enum": [ "active", "discontinued", "archived" ] }, "productSupplierLinkSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "productId": { "$ref": "#/components/schemas/productIdSchema" }, "supplierId": { "$ref": "#/components/schemas/supplierIdSchema" }, "supplier": { "anyOf": [ { "$ref": "#/components/schemas/supplierSchema" }, { "type": "null" } ] }, "supplierSku": { "type": [ "string", "null" ] }, "orderCode": { "type": [ "string", "null" ] }, "unitCost": { "type": [ "number", "null" ] }, "currency": { "type": [ "string", "null" ] }, "isPreferred": { "type": "boolean" }, "minOrderQuantity": { "type": [ "number", "null" ] }, "orderMultiple": { "type": [ "number", "null" ] }, "leadTimeDays": { "type": [ "integer", "null" ] }, "isActive": { "type": "boolean" }, "externalId": { "type": [ "string", "null" ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "productId", "supplierId", "supplier", "supplierSku", "orderCode", "unitCost", "currency", "isPreferred", "minOrderQuantity", "orderMultiple", "leadTimeDays", "isActive", "externalId", "createdAt", "updatedAt" ] }, "productTypeSchema": { "type": "string", "enum": [ "physical", "digital" ] }, "productVariantIdSchema": { "type": "string", "format": "uuid" }, "productVariantSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/productVariantIdSchema" }, "productId": { "$ref": "#/components/schemas/productIdSchema" }, "title": { "type": "string" }, "sku": { "type": [ "string", "null" ] }, "ean": { "type": [ "string", "null" ] }, "manufacturerPartNumber": { "type": [ "string", "null" ] }, "sellingPrice": { "type": [ "number", "null" ] }, "purchasePrice": { "type": [ "number", "null" ] }, "averageCost": { "type": [ "number", "null" ] }, "weightGrams": { "type": [ "integer", "null" ] }, "status": { "$ref": "#/components/schemas/productStatusSchema" }, "allowBackorder": { "type": "boolean" }, "isDefault": { "type": "boolean" }, "position": { "type": "integer" }, "imageId": { "type": [ "string", "null" ], "format": "uuid" }, "optionValueIds": { "type": "array", "items": { "type": "string", "format": "uuid" } } }, "required": [ "id", "productId", "title", "sku", "ean", "manufacturerPartNumber", "sellingPrice", "purchasePrice", "averageCost", "weightGrams", "status", "allowBackorder", "isDefault", "position", "imageId", "optionValueIds" ] }, "slugInputSchema": { "type": "string", "minLength": 1, "maxLength": 160, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" }, "slugSchema": { "type": "string", "minLength": 1, "maxLength": 160, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" }, "supplierIdSchema": { "type": "string", "format": "uuid" }, "supplierSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/supplierIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 240 }, "email": { "type": [ "string", "null" ] }, "phone": { "type": [ "string", "null" ] }, "website": { "type": [ "string", "null" ] }, "notes": { "type": [ "string", "null" ] }, "status": { "$ref": "#/components/schemas/supplierStatusSchema" }, "code": { "type": [ "string", "null" ] }, "accountNumber": { "type": [ "string", "null" ] }, "kvk": { "type": [ "string", "null" ] }, "btw": { "type": [ "string", "null" ] }, "orderingEmail": { "type": [ "string", "null" ] }, "billingEmail": { "type": [ "string", "null" ] }, "addressLine1": { "type": [ "string", "null" ] }, "addressLine2": { "type": [ "string", "null" ] }, "addressCity": { "type": [ "string", "null" ] }, "addressState": { "type": [ "string", "null" ] }, "addressPostalCode": { "type": [ "string", "null" ] }, "addressCountry": { "type": [ "string", "null" ] }, "currency": { "type": [ "string", "null" ] }, "paymentTermsDays": { "type": [ "integer", "null" ] }, "defaultLeadTimeDays": { "type": [ "integer", "null" ] }, "defaultInventoryLocationId": { "anyOf": [ { "$ref": "#/components/schemas/inventoryLocationIdSchema" }, { "type": "null" } ] }, "source": { "type": [ "string", "null" ] }, "externalId": { "type": [ "string", "null" ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "name", "email", "phone", "website", "notes", "status", "code", "accountNumber", "kvk", "btw", "orderingEmail", "billingEmail", "addressLine1", "addressLine2", "addressCity", "addressState", "addressPostalCode", "addressCountry", "currency", "paymentTermsDays", "defaultLeadTimeDays", "defaultInventoryLocationId", "source", "externalId", "createdAt", "updatedAt" ] }, "supplierStatusSchema": { "type": "string", "enum": [ "active", "inactive", "archived" ] }, "tagColorSchema": { "type": "string", "pattern": "^#(?:[0-9a-f]{3}|[0-9a-f]{6})$" }, "tagIdSchema": { "type": "string", "format": "uuid" }, "tagKindSchema": { "type": "string", "enum": [ "task", "customer", "product" ] }, "tagSlugSchema": { "type": "string", "minLength": 1, "maxLength": 160, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" }, "unitOfMeasureInputSchema": { "type": [ "string", "null" ], "maxLength": 240 } } ``` # Delete a product. (/openapi/deleteProduct) Delete a product for the specified organization. ## DELETE /v1/organizations/{organizationId}/products/{productId} ```json { "operationId": "deleteProduct", "summary": "Delete a product.", "description": "Delete a product for the specified organization.", "tags": [ "Assets" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "productId", "required": true, "schema": { "$ref": "#/components/schemas/productIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns a success payload when the product is deleted.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/publicApiSuccessResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "organizationIdSchema": { "type": "string", "format": "uuid" }, "productIdSchema": { "type": "string", "format": "uuid" }, "publicApiSuccessResponseSchema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [ true ] } }, "required": [ "success" ] } } ``` # Get a supplier. (/openapi/getSupplier) Return a supplier for the specified organization. ## GET /v1/organizations/{organizationId}/suppliers/{supplierId} ```json { "operationId": "getSupplier", "summary": "Get a supplier.", "description": "Return a supplier for the specified organization.", "tags": [ "Assets" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "supplierId", "required": true, "schema": { "$ref": "#/components/schemas/supplierIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns the requested supplier.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/supplierSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "inventoryLocationIdSchema": { "type": "string", "format": "uuid" }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "supplierIdSchema": { "type": "string", "format": "uuid" }, "supplierSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/supplierIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 240 }, "email": { "type": [ "string", "null" ] }, "phone": { "type": [ "string", "null" ] }, "website": { "type": [ "string", "null" ] }, "notes": { "type": [ "string", "null" ] }, "status": { "$ref": "#/components/schemas/supplierStatusSchema" }, "code": { "type": [ "string", "null" ] }, "accountNumber": { "type": [ "string", "null" ] }, "kvk": { "type": [ "string", "null" ] }, "btw": { "type": [ "string", "null" ] }, "orderingEmail": { "type": [ "string", "null" ] }, "billingEmail": { "type": [ "string", "null" ] }, "addressLine1": { "type": [ "string", "null" ] }, "addressLine2": { "type": [ "string", "null" ] }, "addressCity": { "type": [ "string", "null" ] }, "addressState": { "type": [ "string", "null" ] }, "addressPostalCode": { "type": [ "string", "null" ] }, "addressCountry": { "type": [ "string", "null" ] }, "currency": { "type": [ "string", "null" ] }, "paymentTermsDays": { "type": [ "integer", "null" ] }, "defaultLeadTimeDays": { "type": [ "integer", "null" ] }, "defaultInventoryLocationId": { "anyOf": [ { "$ref": "#/components/schemas/inventoryLocationIdSchema" }, { "type": "null" } ] }, "source": { "type": [ "string", "null" ] }, "externalId": { "type": [ "string", "null" ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "name", "email", "phone", "website", "notes", "status", "code", "accountNumber", "kvk", "btw", "orderingEmail", "billingEmail", "addressLine1", "addressLine2", "addressCity", "addressState", "addressPostalCode", "addressCountry", "currency", "paymentTermsDays", "defaultLeadTimeDays", "defaultInventoryLocationId", "source", "externalId", "createdAt", "updatedAt" ] }, "supplierStatusSchema": { "type": "string", "enum": [ "active", "inactive", "archived" ] } } ``` # Update a supplier. (/openapi/updateSupplier) Update a supplier for the specified organization. ## PATCH /v1/organizations/{organizationId}/suppliers/{supplierId} ```json { "operationId": "updateSupplier", "summary": "Update a supplier.", "description": "Update a supplier for the specified organization.", "tags": [ "Assets" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "supplierId", "required": true, "schema": { "$ref": "#/components/schemas/supplierIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/supplierInputSchema" } } }, "description": "The supplier payload." }, "responses": { "200": { "description": "Returns the updated supplier.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/supplierSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "inventoryLocationIdSchema": { "type": "string", "format": "uuid" }, "nullableEmailInputSchema": { "type": [ "string", "null" ], "format": "email", "maxLength": 320 }, "nullableLongText": { "type": [ "string", "null" ], "maxLength": 4000 }, "nullableNonNegativeInt": { "type": [ "integer", "null" ], "minimum": 0 }, "nullablePhoneInputSchema": { "type": [ "string", "null" ], "maxLength": 20 }, "nullableShortText": { "type": [ "string", "null" ], "maxLength": 240 }, "nullableUrlInputSchema": { "anyOf": [ { "type": "string", "maxLength": 400, "format": "uri" }, { "type": "null" } ] }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "supplierIdSchema": { "type": "string", "format": "uuid" }, "supplierInputSchema": { "type": "object", "properties": { "name": { "type": "string", "minLength": 1, "maxLength": 240 }, "email": { "allOf": [ { "$ref": "#/components/schemas/nullableEmailInputSchema" } ] }, "phone": { "allOf": [ { "$ref": "#/components/schemas/nullablePhoneInputSchema" } ] }, "website": { "allOf": [ { "$ref": "#/components/schemas/nullableUrlInputSchema" } ] }, "notes": { "allOf": [ { "$ref": "#/components/schemas/nullableLongText" } ] }, "status": { "allOf": [ { "$ref": "#/components/schemas/supplierStatusSchema" } ] }, "code": { "allOf": [ { "$ref": "#/components/schemas/nullableShortText" } ] }, "accountNumber": { "allOf": [ { "$ref": "#/components/schemas/nullableShortText" } ] }, "kvk": { "type": [ "string", "null" ], "maxLength": 40 }, "btw": { "type": [ "string", "null" ], "maxLength": 40 }, "orderingEmail": { "allOf": [ { "$ref": "#/components/schemas/nullableEmailInputSchema" } ] }, "billingEmail": { "allOf": [ { "$ref": "#/components/schemas/nullableEmailInputSchema" } ] }, "addressLine1": { "allOf": [ { "$ref": "#/components/schemas/nullableShortText" } ] }, "addressLine2": { "allOf": [ { "$ref": "#/components/schemas/nullableShortText" } ] }, "addressCity": { "allOf": [ { "$ref": "#/components/schemas/nullableShortText" } ] }, "addressState": { "allOf": [ { "$ref": "#/components/schemas/nullableShortText" } ] }, "addressPostalCode": { "allOf": [ { "$ref": "#/components/schemas/nullableShortText" } ] }, "addressCountry": { "allOf": [ { "$ref": "#/components/schemas/nullableShortText" } ] }, "currency": { "type": [ "string", "null" ], "pattern": "^[A-Z]{3}$" }, "paymentTermsDays": { "allOf": [ { "$ref": "#/components/schemas/nullableNonNegativeInt" } ] }, "defaultLeadTimeDays": { "allOf": [ { "$ref": "#/components/schemas/nullableNonNegativeInt" } ] }, "defaultInventoryLocationId": { "anyOf": [ { "$ref": "#/components/schemas/inventoryLocationIdSchema" }, { "type": "null" } ] }, "source": { "allOf": [ { "$ref": "#/components/schemas/nullableShortText" } ] }, "externalId": { "allOf": [ { "$ref": "#/components/schemas/nullableShortText" } ] } }, "required": [ "name" ] }, "supplierSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/supplierIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 240 }, "email": { "type": [ "string", "null" ] }, "phone": { "type": [ "string", "null" ] }, "website": { "type": [ "string", "null" ] }, "notes": { "type": [ "string", "null" ] }, "status": { "$ref": "#/components/schemas/supplierStatusSchema" }, "code": { "type": [ "string", "null" ] }, "accountNumber": { "type": [ "string", "null" ] }, "kvk": { "type": [ "string", "null" ] }, "btw": { "type": [ "string", "null" ] }, "orderingEmail": { "type": [ "string", "null" ] }, "billingEmail": { "type": [ "string", "null" ] }, "addressLine1": { "type": [ "string", "null" ] }, "addressLine2": { "type": [ "string", "null" ] }, "addressCity": { "type": [ "string", "null" ] }, "addressState": { "type": [ "string", "null" ] }, "addressPostalCode": { "type": [ "string", "null" ] }, "addressCountry": { "type": [ "string", "null" ] }, "currency": { "type": [ "string", "null" ] }, "paymentTermsDays": { "type": [ "integer", "null" ] }, "defaultLeadTimeDays": { "type": [ "integer", "null" ] }, "defaultInventoryLocationId": { "anyOf": [ { "$ref": "#/components/schemas/inventoryLocationIdSchema" }, { "type": "null" } ] }, "source": { "type": [ "string", "null" ] }, "externalId": { "type": [ "string", "null" ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "name", "email", "phone", "website", "notes", "status", "code", "accountNumber", "kvk", "btw", "orderingEmail", "billingEmail", "addressLine1", "addressLine2", "addressCity", "addressState", "addressPostalCode", "addressCountry", "currency", "paymentTermsDays", "defaultLeadTimeDays", "defaultInventoryLocationId", "source", "externalId", "createdAt", "updatedAt" ] }, "supplierStatusSchema": { "type": "string", "enum": [ "active", "inactive", "archived" ] } } ``` # Delete a supplier. (/openapi/deleteSupplier) Delete a supplier for the specified organization. ## DELETE /v1/organizations/{organizationId}/suppliers/{supplierId} ```json { "operationId": "deleteSupplier", "summary": "Delete a supplier.", "description": "Delete a supplier for the specified organization.", "tags": [ "Assets" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "supplierId", "required": true, "schema": { "$ref": "#/components/schemas/supplierIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns a success payload when the supplier is deleted.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/publicApiSuccessResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "organizationIdSchema": { "type": "string", "format": "uuid" }, "publicApiSuccessResponseSchema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [ true ] } }, "required": [ "success" ] }, "supplierIdSchema": { "type": "string", "format": "uuid" } } ``` # List customer asset notes. (/openapi/listCustomerAssetNotes) Return internal notes for a customer asset. ## GET /v1/organizations/{organizationId}/customer-assets/{customerAssetId}/notes ```json { "operationId": "listCustomerAssetNotes", "summary": "List customer asset notes.", "description": "Return internal notes for a customer asset.", "tags": [ "Assets" ], "parameters": [ { "in": "path", "name": "customerAssetId", "required": true, "schema": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 }, "example": 123 }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns customer asset notes.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/customerAssetNotesResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "collaborationBlockSchema": { "type": "object", "discriminator": { "propertyName": "type" }, "oneOf": [ { "type": "object", "properties": { "type": { "type": "string", "enum": [ "paragraph" ] }, "content": { "$ref": "#/components/schemas/collaborationInlineContentSchema" } }, "required": [ "type", "content" ], "additionalProperties": false }, { "type": "object", "properties": { "type": { "type": "string", "enum": [ "bullet-list" ] }, "items": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationInlineContentSchema" }, "minItems": 1, "maxItems": 500 } }, "required": [ "type", "items" ], "additionalProperties": false }, { "type": "object", "properties": { "type": { "type": "string", "enum": [ "numbered-list" ] }, "items": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationInlineContentSchema" }, "minItems": 1, "maxItems": 500 } }, "required": [ "type", "items" ], "additionalProperties": false } ] }, "collaborationCommentAuthorSchema": { "type": "object", "properties": { "userId": { "type": "string", "format": "uuid" }, "displayName": { "type": "string", "minLength": 1 }, "avatarUrl": { "type": [ "string", "null" ], "format": "uri" } }, "required": [ "userId", "displayName", "avatarUrl" ] }, "collaborationDocumentSchema": { "type": "object", "properties": { "version": { "type": "integer", "enum": [ 1 ] }, "blocks": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationBlockSchema" }, "minItems": 1, "maxItems": 1000 } }, "required": [ "version", "blocks" ], "additionalProperties": false }, "collaborationInlineContentSchema": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationInlineSchema" }, "minItems": 1, "maxItems": 1000 }, "collaborationInlineSchema": { "type": "object", "discriminator": { "propertyName": "type" }, "oneOf": [ { "type": "object", "properties": { "type": { "type": "string", "enum": [ "text" ] }, "text": { "type": "string", "minLength": 1, "maxLength": 20000 } }, "required": [ "type", "text" ], "additionalProperties": false }, { "type": "object", "properties": { "type": { "type": "string", "enum": [ "reference" ] }, "reference": { "$ref": "#/components/schemas/collaborationReferenceSchema" } }, "required": [ "type", "reference" ], "additionalProperties": false } ] }, "collaborationReferenceSchema": { "type": "object", "discriminator": { "propertyName": "kind" }, "oneOf": [ { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "user" ] }, "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "team" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "task" ] }, "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "customer" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "quote" ] }, "id": { "$ref": "#/components/schemas/quoteIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "invoice" ] }, "id": { "$ref": "#/components/schemas/invoiceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "product" ] }, "id": { "$ref": "#/components/schemas/productIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "customer-asset" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false } ] }, "customerAssetIdSchema": { "type": "integer", "exclusiveMinimum": 0 }, "customerAssetNoteSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "customerAssetId": { "$ref": "#/components/schemas/customerAssetIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "contentText": { "type": "string", "minLength": 1, "maxLength": 20000 }, "contentDoc": { "$ref": "#/components/schemas/collaborationDocumentSchema" }, "authorId": { "type": [ "string", "null" ], "format": "uuid" }, "author": { "anyOf": [ { "$ref": "#/components/schemas/collaborationCommentAuthorSchema" }, { "type": "null" } ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "customerAssetId", "organizationId", "contentText", "contentDoc", "authorId", "author", "createdAt", "updatedAt" ] }, "customerAssetNotesResponseSchema": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/customerAssetNoteSchema" } }, "pagination": { "$ref": "#/components/schemas/listPaginationSchema" } }, "required": [ "data", "pagination" ] }, "invoiceIdSchema": { "type": "string", "format": "uuid" }, "listPaginationSchema": { "type": "object", "properties": { "offset": { "type": "integer", "minimum": 0 }, "limit": { "type": "integer", "minimum": 1, "maximum": 100 }, "total": { "type": "integer", "minimum": 0 }, "hasMore": { "type": "boolean" }, "totalIsEstimate": { "type": "boolean" } }, "required": [ "offset", "limit", "total", "hasMore" ] }, "numericReferenceIdSchema": { "type": "string", "pattern": "^[1-9]\\d*$" }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "productIdSchema": { "type": "string", "format": "uuid" }, "quoteIdSchema": { "type": "string", "format": "uuid" } } ``` # Add a customer asset note. (/openapi/createCustomerAssetNote) Add an internal note to a customer asset. ## POST /v1/organizations/{organizationId}/customer-assets/{customerAssetId}/notes ```json { "operationId": "createCustomerAssetNote", "summary": "Add a customer asset note.", "description": "Add an internal note to a customer asset.", "tags": [ "Assets" ], "parameters": [ { "in": "path", "name": "customerAssetId", "required": true, "schema": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 }, "example": 123 }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/customerAssetNoteInputSchema" } } }, "description": "The customer asset note payload." }, "responses": { "201": { "description": "Returns the created customer asset note.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/customerAssetNoteSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "collaborationBlockSchema": { "type": "object", "discriminator": { "propertyName": "type" }, "oneOf": [ { "type": "object", "properties": { "type": { "type": "string", "enum": [ "paragraph" ] }, "content": { "$ref": "#/components/schemas/collaborationInlineContentSchema" } }, "required": [ "type", "content" ], "additionalProperties": false }, { "type": "object", "properties": { "type": { "type": "string", "enum": [ "bullet-list" ] }, "items": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationInlineContentSchema" }, "minItems": 1, "maxItems": 500 } }, "required": [ "type", "items" ], "additionalProperties": false }, { "type": "object", "properties": { "type": { "type": "string", "enum": [ "numbered-list" ] }, "items": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationInlineContentSchema" }, "minItems": 1, "maxItems": 500 } }, "required": [ "type", "items" ], "additionalProperties": false } ] }, "collaborationCommentAuthorSchema": { "type": "object", "properties": { "userId": { "type": "string", "format": "uuid" }, "displayName": { "type": "string", "minLength": 1 }, "avatarUrl": { "type": [ "string", "null" ], "format": "uri" } }, "required": [ "userId", "displayName", "avatarUrl" ] }, "collaborationComposerInputSchema": { "type": "object", "properties": { "text": { "type": "string", "minLength": 1, "maxLength": 20000 }, "selections": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationComposerSelectionSchema" }, "maxItems": 100 } }, "required": [ "text", "selections" ], "additionalProperties": false }, "collaborationComposerSelectionSchema": { "type": "object", "properties": { "start": { "type": "integer", "minimum": 0, "maximum": 20000 }, "end": { "type": "integer", "exclusiveMinimum": 0, "maximum": 20000 }, "reference": { "$ref": "#/components/schemas/collaborationReferenceIdSchema" } }, "required": [ "start", "end", "reference" ], "additionalProperties": false }, "collaborationDocumentSchema": { "type": "object", "properties": { "version": { "type": "integer", "enum": [ 1 ] }, "blocks": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationBlockSchema" }, "minItems": 1, "maxItems": 1000 } }, "required": [ "version", "blocks" ], "additionalProperties": false }, "collaborationInlineContentSchema": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationInlineSchema" }, "minItems": 1, "maxItems": 1000 }, "collaborationInlineSchema": { "type": "object", "discriminator": { "propertyName": "type" }, "oneOf": [ { "type": "object", "properties": { "type": { "type": "string", "enum": [ "text" ] }, "text": { "type": "string", "minLength": 1, "maxLength": 20000 } }, "required": [ "type", "text" ], "additionalProperties": false }, { "type": "object", "properties": { "type": { "type": "string", "enum": [ "reference" ] }, "reference": { "$ref": "#/components/schemas/collaborationReferenceSchema" } }, "required": [ "type", "reference" ], "additionalProperties": false } ] }, "collaborationReferenceIdSchema": { "type": "object", "discriminator": { "propertyName": "kind" }, "oneOf": [ { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "user" ] }, "id": { "type": "string", "format": "uuid" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "team" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "task" ] }, "id": { "type": "string", "format": "uuid" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "customer" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "quote" ] }, "id": { "$ref": "#/components/schemas/quoteIdSchema" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "invoice" ] }, "id": { "$ref": "#/components/schemas/invoiceIdSchema" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "product" ] }, "id": { "$ref": "#/components/schemas/productIdSchema" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "customer-asset" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" } }, "required": [ "kind", "id" ], "additionalProperties": false } ] }, "collaborationReferenceSchema": { "type": "object", "discriminator": { "propertyName": "kind" }, "oneOf": [ { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "user" ] }, "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "team" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "task" ] }, "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "customer" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "quote" ] }, "id": { "$ref": "#/components/schemas/quoteIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "invoice" ] }, "id": { "$ref": "#/components/schemas/invoiceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "product" ] }, "id": { "$ref": "#/components/schemas/productIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "customer-asset" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false } ] }, "customerAssetIdSchema": { "type": "integer", "exclusiveMinimum": 0 }, "customerAssetNoteInputSchema": { "$ref": "#/components/schemas/collaborationComposerInputSchema" }, "customerAssetNoteSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "customerAssetId": { "$ref": "#/components/schemas/customerAssetIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "contentText": { "type": "string", "minLength": 1, "maxLength": 20000 }, "contentDoc": { "$ref": "#/components/schemas/collaborationDocumentSchema" }, "authorId": { "type": [ "string", "null" ], "format": "uuid" }, "author": { "anyOf": [ { "$ref": "#/components/schemas/collaborationCommentAuthorSchema" }, { "type": "null" } ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "customerAssetId", "organizationId", "contentText", "contentDoc", "authorId", "author", "createdAt", "updatedAt" ] }, "invoiceIdSchema": { "type": "string", "format": "uuid" }, "numericReferenceIdSchema": { "type": "string", "pattern": "^[1-9]\\d*$" }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "productIdSchema": { "type": "string", "format": "uuid" }, "quoteIdSchema": { "type": "string", "format": "uuid" } } ``` # Update an inventory stock record. (/openapi/updateInventoryStockRecord) Update an inventory stock record for a product. ## PATCH /v1/organizations/{organizationId}/inventory/stock-records/{stockRecordId} ```json { "operationId": "updateInventoryStockRecord", "summary": "Update an inventory stock record.", "description": "Update an inventory stock record for a product.", "tags": [ "Assets" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "stockRecordId", "required": true, "schema": { "$ref": "#/components/schemas/inventoryStockRecordIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/inventoryStockRecordInputSchema" } } }, "description": "The inventory stock record payload." }, "responses": { "200": { "description": "Returns the updated stock record.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/inventoryStockRecordSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "assetImageIdSchema": { "type": "string", "format": "uuid" }, "assetImageSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/assetImageIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "productId": { "anyOf": [ { "$ref": "#/components/schemas/productIdSchema" }, { "type": "null" } ] }, "url": { "type": "string", "format": "uri" }, "altText": { "type": [ "string", "null" ] }, "position": { "type": "integer", "minimum": 0 }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "productId", "url", "altText", "position", "createdAt", "updatedAt" ] }, "brandIdSchema": { "type": "string", "format": "uuid" }, "currencyCodeSchema": { "type": "string", "pattern": "^[A-Z]{3}$" }, "iconNameSchema": { "type": "string", "minLength": 1, "maxLength": 120, "pattern": "^[A-Za-z0-9]+Icon$" }, "inventoryLocationIdSchema": { "type": "string", "format": "uuid" }, "inventoryLocationSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/inventoryLocationIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 240 }, "description": { "type": [ "string", "null" ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "name", "description", "createdAt", "updatedAt" ] }, "inventoryStatusSchema": { "type": "string", "enum": [ "available", "quarantined", "expired", "archived" ] }, "inventoryStockRecordIdSchema": { "type": "string", "format": "uuid" }, "inventoryStockRecordInputSchema": { "type": "object", "properties": { "productId": { "$ref": "#/components/schemas/productIdSchema" }, "variantId": { "type": "string", "format": "uuid" }, "locationId": { "anyOf": [ { "$ref": "#/components/schemas/inventoryLocationIdSchema" }, { "type": "null" } ] }, "locationName": { "allOf": [ { "$ref": "#/components/schemas/nullableShortText" } ] }, "onHandQuantity": { "type": "number", "minimum": 0 }, "lotNumber": { "allOf": [ { "$ref": "#/components/schemas/nullableShortText" } ] }, "serialNumber": { "allOf": [ { "$ref": "#/components/schemas/nullableShortText" } ] }, "expiresOn": { "allOf": [ { "$ref": "#/components/schemas/nullableDate" } ] }, "status": { "allOf": [ { "$ref": "#/components/schemas/inventoryStatusSchema" } ] }, "notes": { "allOf": [ { "$ref": "#/components/schemas/nullableLongText" } ] }, "binLocation": { "allOf": [ { "$ref": "#/components/schemas/nullableShortText" } ] }, "unitCost": { "allOf": [ { "$ref": "#/components/schemas/nullableMoney" } ] }, "currency": { "allOf": [ { "$ref": "#/components/schemas/nullableCurrency" } ] }, "lastCountedOn": { "allOf": [ { "$ref": "#/components/schemas/nullableDate" } ] } }, "required": [ "productId", "onHandQuantity" ] }, "inventoryStockRecordSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/inventoryStockRecordIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "productId": { "$ref": "#/components/schemas/productIdSchema" }, "product": { "anyOf": [ { "$ref": "#/components/schemas/productListItemSchema" }, { "type": "null" } ] }, "variantId": { "type": "string", "format": "uuid" }, "locationId": { "anyOf": [ { "$ref": "#/components/schemas/inventoryLocationIdSchema" }, { "type": "null" } ] }, "location": { "anyOf": [ { "$ref": "#/components/schemas/inventoryLocationSchema" }, { "type": "null" } ] }, "onHandQuantity": { "type": "number", "minimum": 0 }, "allocatedQuantity": { "type": "number", "minimum": 0 }, "availableQuantity": { "type": "number", "minimum": 0 }, "lotNumber": { "type": [ "string", "null" ] }, "serialNumber": { "type": [ "string", "null" ] }, "expiresOn": { "$ref": "#/components/schemas/nullableDate" }, "status": { "$ref": "#/components/schemas/inventoryStatusSchema" }, "notes": { "type": [ "string", "null" ] }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "updatedBy": { "type": [ "string", "null" ], "format": "uuid" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" }, "binLocation": { "type": [ "string", "null" ] }, "unitCost": { "type": [ "number", "null" ] }, "currency": { "type": [ "string", "null" ] }, "lastCountedOn": { "$ref": "#/components/schemas/nullableDate" } }, "required": [ "id", "organizationId", "productId", "product", "variantId", "locationId", "location", "onHandQuantity", "allocatedQuantity", "availableQuantity", "lotNumber", "serialNumber", "expiresOn", "status", "notes", "createdBy", "updatedBy", "createdAt", "updatedAt", "binLocation", "unitCost", "currency", "lastCountedOn" ] }, "nullableCurrency": { "anyOf": [ { "$ref": "#/components/schemas/currencyCodeSchema" }, { "type": "null" } ] }, "nullableDate": { "type": [ "string", "null" ], "format": "date" }, "nullableLongText": { "type": [ "string", "null" ], "maxLength": 4000 }, "nullableMoney": { "type": [ "number", "null" ], "minimum": 0 }, "nullableShortText": { "type": [ "string", "null" ], "maxLength": 240 }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "organizationTagSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/tagIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "kind": { "$ref": "#/components/schemas/tagKindSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 80 }, "slug": { "$ref": "#/components/schemas/tagSlugSchema" }, "color": { "anyOf": [ { "$ref": "#/components/schemas/tagColorSchema" }, { "type": "null" } ] }, "description": { "type": [ "string", "null" ] }, "position": { "type": "integer", "minimum": 0 }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "kind", "name", "slug", "color", "description", "position", "createdAt", "updatedAt" ] }, "productCategorySchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "parentId": { "type": [ "string", "null" ], "format": "uuid" }, "name": { "type": "string", "minLength": 1, "maxLength": 120 }, "slug": { "$ref": "#/components/schemas/slugSchema" }, "icon": { "$ref": "#/components/schemas/iconNameSchema" }, "description": { "type": [ "string", "null" ] }, "position": { "type": "integer", "minimum": 0 }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "parentId", "name", "slug", "icon", "description", "position", "createdAt", "updatedAt" ] }, "productIdSchema": { "type": "string", "format": "uuid" }, "productListItemSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/productIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "categoryIds": { "type": "array", "items": { "type": "string", "format": "uuid" } }, "categories": { "type": "array", "items": { "$ref": "#/components/schemas/productCategorySchema" } }, "name": { "type": "string", "minLength": 1 }, "slug": { "$ref": "#/components/schemas/slugSchema" }, "brand": { "type": [ "object", "null" ], "properties": { "id": { "$ref": "#/components/schemas/brandIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 240 }, "slug": { "type": "string", "minLength": 1, "maxLength": 160, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" }, "description": { "type": [ "string", "null" ] }, "website": { "type": [ "string", "null" ] }, "logoUrl": { "type": [ "string", "null" ], "format": "uri" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "name", "slug", "description", "website", "logoUrl", "createdAt", "updatedAt" ] }, "manufacturer": { "type": [ "string", "null" ] }, "sku": { "type": [ "string", "null" ] }, "ean": { "type": [ "string", "null" ] }, "defaultSellingPrice": { "type": [ "number", "null" ] }, "vatPercentage": { "type": [ "number", "null" ] }, "status": { "$ref": "#/components/schemas/productStatusSchema" }, "updatedAt": { "type": "string", "format": "date-time" }, "tags": { "type": "array", "items": { "$ref": "#/components/schemas/organizationTagSchema" } }, "images": { "type": "array", "items": { "$ref": "#/components/schemas/assetImageSchema" } }, "suppliers": { "type": "array", "items": { "$ref": "#/components/schemas/productSupplierLinkSchema" } }, "manufacturerPartNumber": { "type": [ "string", "null" ] }, "currency": { "type": [ "string", "null" ] }, "unitOfMeasure": { "type": [ "string", "null" ] }, "stockStatus": { "anyOf": [ { "$ref": "#/components/schemas/stockStatusSchema" }, { "type": "null" } ] } }, "required": [ "id", "organizationId", "categoryIds", "categories", "name", "slug", "brand", "manufacturer", "sku", "ean", "defaultSellingPrice", "vatPercentage", "status", "updatedAt", "tags", "images", "suppliers", "manufacturerPartNumber", "currency", "unitOfMeasure" ] }, "productStatusSchema": { "type": "string", "enum": [ "active", "discontinued", "archived" ] }, "productSupplierLinkSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "productId": { "$ref": "#/components/schemas/productIdSchema" }, "supplierId": { "$ref": "#/components/schemas/supplierIdSchema" }, "supplier": { "anyOf": [ { "$ref": "#/components/schemas/supplierSchema" }, { "type": "null" } ] }, "supplierSku": { "type": [ "string", "null" ] }, "orderCode": { "type": [ "string", "null" ] }, "unitCost": { "type": [ "number", "null" ] }, "currency": { "type": [ "string", "null" ] }, "isPreferred": { "type": "boolean" }, "minOrderQuantity": { "type": [ "number", "null" ] }, "orderMultiple": { "type": [ "number", "null" ] }, "leadTimeDays": { "type": [ "integer", "null" ] }, "isActive": { "type": "boolean" }, "externalId": { "type": [ "string", "null" ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "productId", "supplierId", "supplier", "supplierSku", "orderCode", "unitCost", "currency", "isPreferred", "minOrderQuantity", "orderMultiple", "leadTimeDays", "isActive", "externalId", "createdAt", "updatedAt" ] }, "slugSchema": { "type": "string", "minLength": 1, "maxLength": 160, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" }, "stockStatusSchema": { "type": "string", "enum": [ "not_tracked", "discontinued", "out_of_stock", "backordered", "low_stock", "in_stock" ] }, "supplierIdSchema": { "type": "string", "format": "uuid" }, "supplierSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/supplierIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 240 }, "email": { "type": [ "string", "null" ] }, "phone": { "type": [ "string", "null" ] }, "website": { "type": [ "string", "null" ] }, "notes": { "type": [ "string", "null" ] }, "status": { "$ref": "#/components/schemas/supplierStatusSchema" }, "code": { "type": [ "string", "null" ] }, "accountNumber": { "type": [ "string", "null" ] }, "kvk": { "type": [ "string", "null" ] }, "btw": { "type": [ "string", "null" ] }, "orderingEmail": { "type": [ "string", "null" ] }, "billingEmail": { "type": [ "string", "null" ] }, "addressLine1": { "type": [ "string", "null" ] }, "addressLine2": { "type": [ "string", "null" ] }, "addressCity": { "type": [ "string", "null" ] }, "addressState": { "type": [ "string", "null" ] }, "addressPostalCode": { "type": [ "string", "null" ] }, "addressCountry": { "type": [ "string", "null" ] }, "currency": { "type": [ "string", "null" ] }, "paymentTermsDays": { "type": [ "integer", "null" ] }, "defaultLeadTimeDays": { "type": [ "integer", "null" ] }, "defaultInventoryLocationId": { "anyOf": [ { "$ref": "#/components/schemas/inventoryLocationIdSchema" }, { "type": "null" } ] }, "source": { "type": [ "string", "null" ] }, "externalId": { "type": [ "string", "null" ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "name", "email", "phone", "website", "notes", "status", "code", "accountNumber", "kvk", "btw", "orderingEmail", "billingEmail", "addressLine1", "addressLine2", "addressCity", "addressState", "addressPostalCode", "addressCountry", "currency", "paymentTermsDays", "defaultLeadTimeDays", "defaultInventoryLocationId", "source", "externalId", "createdAt", "updatedAt" ] }, "supplierStatusSchema": { "type": "string", "enum": [ "active", "inactive", "archived" ] }, "tagColorSchema": { "type": "string", "pattern": "^#(?:[0-9a-f]{3}|[0-9a-f]{6})$" }, "tagIdSchema": { "type": "string", "format": "uuid" }, "tagKindSchema": { "type": "string", "enum": [ "task", "customer", "product" ] }, "tagSlugSchema": { "type": "string", "minLength": 1, "maxLength": 160, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" } } ``` # Archive an inventory stock record. (/openapi/archiveInventoryStockRecord) Archive an inventory stock record. ## DELETE /v1/organizations/{organizationId}/inventory/stock-records/{stockRecordId} ```json { "operationId": "archiveInventoryStockRecord", "summary": "Archive an inventory stock record.", "description": "Archive an inventory stock record.", "tags": [ "Assets" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "stockRecordId", "required": true, "schema": { "$ref": "#/components/schemas/inventoryStockRecordIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns a success payload when the stock record is archived.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/publicApiSuccessResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "inventoryStockRecordIdSchema": { "type": "string", "format": "uuid" }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "publicApiSuccessResponseSchema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [ true ] } }, "required": [ "success" ] } } ``` # Move a product category. (/openapi/moveProductCategory) Move a product category to a new parent and position. ## POST /v1/organizations/{organizationId}/product-categories/{categoryId}/move ```json { "operationId": "moveProductCategory", "summary": "Move a product category.", "description": "Move a product category to a new parent and position.", "tags": [ "Assets" ], "parameters": [ { "in": "path", "name": "categoryId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/productCategoryMoveInputSchema" } } }, "description": "The category move payload." }, "responses": { "200": { "description": "Returns the updated product category tree as a paginated response.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/productCategoriesListResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "organizationIdSchema": { "type": "string", "format": "uuid" }, "productCategoriesListResponseSchema": { "type": "object" }, "productCategoryMoveInputSchema": { "type": "object", "properties": { "parentId": { "type": [ "string", "null" ], "format": "uuid" }, "position": { "type": "integer", "minimum": 0 } }, "required": [ "parentId", "position" ] } } ``` # Add a product supplier link. (/openapi/addProductSupplierLink) Link a supplier to a product. ## POST /v1/organizations/{organizationId}/products/{productId}/suppliers ```json { "operationId": "addProductSupplierLink", "summary": "Add a product supplier link.", "description": "Link a supplier to a product.", "tags": [ "Assets" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "productId", "required": true, "schema": { "$ref": "#/components/schemas/productIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/productSupplierLinkInputSchema" } } }, "description": "The product supplier link payload." }, "responses": { "201": { "description": "Returns the created product supplier link.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/productSupplierLinkSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "inventoryLocationIdSchema": { "type": "string", "format": "uuid" }, "nullableMoney": { "type": [ "number", "null" ], "minimum": 0 }, "nullableNonNegativeInt": { "type": [ "integer", "null" ], "minimum": 0 }, "nullableNonNegativeQuantity": { "type": [ "number", "null" ], "minimum": 0 }, "nullablePositiveQuantity": { "type": [ "number", "null" ], "exclusiveMinimum": 0 }, "nullableShortText": { "type": [ "string", "null" ], "maxLength": 240 }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "productIdSchema": { "type": "string", "format": "uuid" }, "productSupplierLinkInputSchema": { "type": "object", "properties": { "supplierId": { "$ref": "#/components/schemas/supplierIdSchema" }, "supplierSku": { "allOf": [ { "$ref": "#/components/schemas/nullableShortText" } ] }, "orderCode": { "allOf": [ { "$ref": "#/components/schemas/nullableShortText" } ] }, "unitCost": { "allOf": [ { "$ref": "#/components/schemas/nullableMoney" } ] }, "currency": { "type": [ "string", "null" ], "pattern": "^[A-Z]{3}$" }, "isPreferred": { "type": "boolean" }, "minOrderQuantity": { "allOf": [ { "$ref": "#/components/schemas/nullableNonNegativeQuantity" } ] }, "orderMultiple": { "allOf": [ { "$ref": "#/components/schemas/nullablePositiveQuantity" } ] }, "leadTimeDays": { "allOf": [ { "$ref": "#/components/schemas/nullableNonNegativeInt" } ] }, "isActive": { "type": "boolean" }, "externalId": { "allOf": [ { "$ref": "#/components/schemas/nullableShortText" } ] } }, "required": [ "supplierId" ] }, "productSupplierLinkSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "productId": { "$ref": "#/components/schemas/productIdSchema" }, "supplierId": { "$ref": "#/components/schemas/supplierIdSchema" }, "supplier": { "anyOf": [ { "$ref": "#/components/schemas/supplierSchema" }, { "type": "null" } ] }, "supplierSku": { "type": [ "string", "null" ] }, "orderCode": { "type": [ "string", "null" ] }, "unitCost": { "type": [ "number", "null" ] }, "currency": { "type": [ "string", "null" ] }, "isPreferred": { "type": "boolean" }, "minOrderQuantity": { "type": [ "number", "null" ] }, "orderMultiple": { "type": [ "number", "null" ] }, "leadTimeDays": { "type": [ "integer", "null" ] }, "isActive": { "type": "boolean" }, "externalId": { "type": [ "string", "null" ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "productId", "supplierId", "supplier", "supplierSku", "orderCode", "unitCost", "currency", "isPreferred", "minOrderQuantity", "orderMultiple", "leadTimeDays", "isActive", "externalId", "createdAt", "updatedAt" ] }, "supplierIdSchema": { "type": "string", "format": "uuid" }, "supplierSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/supplierIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 240 }, "email": { "type": [ "string", "null" ] }, "phone": { "type": [ "string", "null" ] }, "website": { "type": [ "string", "null" ] }, "notes": { "type": [ "string", "null" ] }, "status": { "$ref": "#/components/schemas/supplierStatusSchema" }, "code": { "type": [ "string", "null" ] }, "accountNumber": { "type": [ "string", "null" ] }, "kvk": { "type": [ "string", "null" ] }, "btw": { "type": [ "string", "null" ] }, "orderingEmail": { "type": [ "string", "null" ] }, "billingEmail": { "type": [ "string", "null" ] }, "addressLine1": { "type": [ "string", "null" ] }, "addressLine2": { "type": [ "string", "null" ] }, "addressCity": { "type": [ "string", "null" ] }, "addressState": { "type": [ "string", "null" ] }, "addressPostalCode": { "type": [ "string", "null" ] }, "addressCountry": { "type": [ "string", "null" ] }, "currency": { "type": [ "string", "null" ] }, "paymentTermsDays": { "type": [ "integer", "null" ] }, "defaultLeadTimeDays": { "type": [ "integer", "null" ] }, "defaultInventoryLocationId": { "anyOf": [ { "$ref": "#/components/schemas/inventoryLocationIdSchema" }, { "type": "null" } ] }, "source": { "type": [ "string", "null" ] }, "externalId": { "type": [ "string", "null" ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "name", "email", "phone", "website", "notes", "status", "code", "accountNumber", "kvk", "btw", "orderingEmail", "billingEmail", "addressLine1", "addressLine2", "addressCity", "addressState", "addressPostalCode", "addressCountry", "currency", "paymentTermsDays", "defaultLeadTimeDays", "defaultInventoryLocationId", "source", "externalId", "createdAt", "updatedAt" ] }, "supplierStatusSchema": { "type": "string", "enum": [ "active", "inactive", "archived" ] } } ``` # Update a customer asset note. (/openapi/updateCustomerAssetNote) Update an internal note on a customer asset. ## PATCH /v1/organizations/{organizationId}/customer-assets/{customerAssetId}/notes/{noteId} ```json { "operationId": "updateCustomerAssetNote", "summary": "Update a customer asset note.", "description": "Update an internal note on a customer asset.", "tags": [ "Assets" ], "parameters": [ { "in": "path", "name": "customerAssetId", "required": true, "schema": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 }, "example": 123 }, { "in": "path", "name": "noteId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/customerAssetNoteInputSchema" } } }, "description": "The customer asset note payload." }, "responses": { "200": { "description": "Returns the updated customer asset note.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/customerAssetNoteSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "collaborationBlockSchema": { "type": "object", "discriminator": { "propertyName": "type" }, "oneOf": [ { "type": "object", "properties": { "type": { "type": "string", "enum": [ "paragraph" ] }, "content": { "$ref": "#/components/schemas/collaborationInlineContentSchema" } }, "required": [ "type", "content" ], "additionalProperties": false }, { "type": "object", "properties": { "type": { "type": "string", "enum": [ "bullet-list" ] }, "items": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationInlineContentSchema" }, "minItems": 1, "maxItems": 500 } }, "required": [ "type", "items" ], "additionalProperties": false }, { "type": "object", "properties": { "type": { "type": "string", "enum": [ "numbered-list" ] }, "items": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationInlineContentSchema" }, "minItems": 1, "maxItems": 500 } }, "required": [ "type", "items" ], "additionalProperties": false } ] }, "collaborationCommentAuthorSchema": { "type": "object", "properties": { "userId": { "type": "string", "format": "uuid" }, "displayName": { "type": "string", "minLength": 1 }, "avatarUrl": { "type": [ "string", "null" ], "format": "uri" } }, "required": [ "userId", "displayName", "avatarUrl" ] }, "collaborationComposerInputSchema": { "type": "object", "properties": { "text": { "type": "string", "minLength": 1, "maxLength": 20000 }, "selections": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationComposerSelectionSchema" }, "maxItems": 100 } }, "required": [ "text", "selections" ], "additionalProperties": false }, "collaborationComposerSelectionSchema": { "type": "object", "properties": { "start": { "type": "integer", "minimum": 0, "maximum": 20000 }, "end": { "type": "integer", "exclusiveMinimum": 0, "maximum": 20000 }, "reference": { "$ref": "#/components/schemas/collaborationReferenceIdSchema" } }, "required": [ "start", "end", "reference" ], "additionalProperties": false }, "collaborationDocumentSchema": { "type": "object", "properties": { "version": { "type": "integer", "enum": [ 1 ] }, "blocks": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationBlockSchema" }, "minItems": 1, "maxItems": 1000 } }, "required": [ "version", "blocks" ], "additionalProperties": false }, "collaborationInlineContentSchema": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationInlineSchema" }, "minItems": 1, "maxItems": 1000 }, "collaborationInlineSchema": { "type": "object", "discriminator": { "propertyName": "type" }, "oneOf": [ { "type": "object", "properties": { "type": { "type": "string", "enum": [ "text" ] }, "text": { "type": "string", "minLength": 1, "maxLength": 20000 } }, "required": [ "type", "text" ], "additionalProperties": false }, { "type": "object", "properties": { "type": { "type": "string", "enum": [ "reference" ] }, "reference": { "$ref": "#/components/schemas/collaborationReferenceSchema" } }, "required": [ "type", "reference" ], "additionalProperties": false } ] }, "collaborationReferenceIdSchema": { "type": "object", "discriminator": { "propertyName": "kind" }, "oneOf": [ { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "user" ] }, "id": { "type": "string", "format": "uuid" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "team" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "task" ] }, "id": { "type": "string", "format": "uuid" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "customer" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "quote" ] }, "id": { "$ref": "#/components/schemas/quoteIdSchema" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "invoice" ] }, "id": { "$ref": "#/components/schemas/invoiceIdSchema" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "product" ] }, "id": { "$ref": "#/components/schemas/productIdSchema" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "customer-asset" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" } }, "required": [ "kind", "id" ], "additionalProperties": false } ] }, "collaborationReferenceSchema": { "type": "object", "discriminator": { "propertyName": "kind" }, "oneOf": [ { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "user" ] }, "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "team" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "task" ] }, "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "customer" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "quote" ] }, "id": { "$ref": "#/components/schemas/quoteIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "invoice" ] }, "id": { "$ref": "#/components/schemas/invoiceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "product" ] }, "id": { "$ref": "#/components/schemas/productIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "customer-asset" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false } ] }, "customerAssetIdSchema": { "type": "integer", "exclusiveMinimum": 0 }, "customerAssetNoteInputSchema": { "$ref": "#/components/schemas/collaborationComposerInputSchema" }, "customerAssetNoteSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "customerAssetId": { "$ref": "#/components/schemas/customerAssetIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "contentText": { "type": "string", "minLength": 1, "maxLength": 20000 }, "contentDoc": { "$ref": "#/components/schemas/collaborationDocumentSchema" }, "authorId": { "type": [ "string", "null" ], "format": "uuid" }, "author": { "anyOf": [ { "$ref": "#/components/schemas/collaborationCommentAuthorSchema" }, { "type": "null" } ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "customerAssetId", "organizationId", "contentText", "contentDoc", "authorId", "author", "createdAt", "updatedAt" ] }, "invoiceIdSchema": { "type": "string", "format": "uuid" }, "numericReferenceIdSchema": { "type": "string", "pattern": "^[1-9]\\d*$" }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "productIdSchema": { "type": "string", "format": "uuid" }, "quoteIdSchema": { "type": "string", "format": "uuid" } } ``` # Delete a customer asset note. (/openapi/deleteCustomerAssetNote) Delete an internal note from a customer asset. ## DELETE /v1/organizations/{organizationId}/customer-assets/{customerAssetId}/notes/{noteId} ```json { "operationId": "deleteCustomerAssetNote", "summary": "Delete a customer asset note.", "description": "Delete an internal note from a customer asset.", "tags": [ "Assets" ], "parameters": [ { "in": "path", "name": "customerAssetId", "required": true, "schema": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 }, "example": 123 }, { "in": "path", "name": "noteId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns a success payload when the note is deleted.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/publicApiSuccessResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "organizationIdSchema": { "type": "string", "format": "uuid" }, "publicApiSuccessResponseSchema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [ true ] } }, "required": [ "success" ] } } ``` # Update a product supplier link. (/openapi/updateProductSupplierLink) Update ordering details for a product supplier link. ## PATCH /v1/organizations/{organizationId}/products/{productId}/suppliers/{linkId} ```json { "operationId": "updateProductSupplierLink", "summary": "Update a product supplier link.", "description": "Update ordering details for a product supplier link.", "tags": [ "Assets" ], "parameters": [ { "in": "path", "name": "linkId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "productId", "required": true, "schema": { "$ref": "#/components/schemas/productIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/productSupplierLinkUpdateSchema" } } }, "description": "The product supplier link fields to update." }, "responses": { "200": { "description": "Returns the updated product supplier link.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/productSupplierLinkSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "inventoryLocationIdSchema": { "type": "string", "format": "uuid" }, "nullableMoney": { "type": [ "number", "null" ], "minimum": 0 }, "nullableNonNegativeInt": { "type": [ "integer", "null" ], "minimum": 0 }, "nullableNonNegativeQuantity": { "type": [ "number", "null" ], "minimum": 0 }, "nullablePositiveQuantity": { "type": [ "number", "null" ], "exclusiveMinimum": 0 }, "nullableShortText": { "type": [ "string", "null" ], "maxLength": 240 }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "productIdSchema": { "type": "string", "format": "uuid" }, "productSupplierLinkSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "productId": { "$ref": "#/components/schemas/productIdSchema" }, "supplierId": { "$ref": "#/components/schemas/supplierIdSchema" }, "supplier": { "anyOf": [ { "$ref": "#/components/schemas/supplierSchema" }, { "type": "null" } ] }, "supplierSku": { "type": [ "string", "null" ] }, "orderCode": { "type": [ "string", "null" ] }, "unitCost": { "type": [ "number", "null" ] }, "currency": { "type": [ "string", "null" ] }, "isPreferred": { "type": "boolean" }, "minOrderQuantity": { "type": [ "number", "null" ] }, "orderMultiple": { "type": [ "number", "null" ] }, "leadTimeDays": { "type": [ "integer", "null" ] }, "isActive": { "type": "boolean" }, "externalId": { "type": [ "string", "null" ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "productId", "supplierId", "supplier", "supplierSku", "orderCode", "unitCost", "currency", "isPreferred", "minOrderQuantity", "orderMultiple", "leadTimeDays", "isActive", "externalId", "createdAt", "updatedAt" ] }, "productSupplierLinkUpdateSchema": { "type": "object", "properties": { "supplierSku": { "allOf": [ { "$ref": "#/components/schemas/nullableShortText" } ] }, "orderCode": { "allOf": [ { "$ref": "#/components/schemas/nullableShortText" } ] }, "unitCost": { "allOf": [ { "$ref": "#/components/schemas/nullableMoney" } ] }, "currency": { "type": [ "string", "null" ], "pattern": "^[A-Z]{3}$" }, "isPreferred": { "type": "boolean" }, "minOrderQuantity": { "allOf": [ { "$ref": "#/components/schemas/nullableNonNegativeQuantity" } ] }, "orderMultiple": { "allOf": [ { "$ref": "#/components/schemas/nullablePositiveQuantity" } ] }, "leadTimeDays": { "allOf": [ { "$ref": "#/components/schemas/nullableNonNegativeInt" } ] }, "isActive": { "type": "boolean" }, "externalId": { "allOf": [ { "$ref": "#/components/schemas/nullableShortText" } ] } }, "required": [] }, "supplierIdSchema": { "type": "string", "format": "uuid" }, "supplierSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/supplierIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 240 }, "email": { "type": [ "string", "null" ] }, "phone": { "type": [ "string", "null" ] }, "website": { "type": [ "string", "null" ] }, "notes": { "type": [ "string", "null" ] }, "status": { "$ref": "#/components/schemas/supplierStatusSchema" }, "code": { "type": [ "string", "null" ] }, "accountNumber": { "type": [ "string", "null" ] }, "kvk": { "type": [ "string", "null" ] }, "btw": { "type": [ "string", "null" ] }, "orderingEmail": { "type": [ "string", "null" ] }, "billingEmail": { "type": [ "string", "null" ] }, "addressLine1": { "type": [ "string", "null" ] }, "addressLine2": { "type": [ "string", "null" ] }, "addressCity": { "type": [ "string", "null" ] }, "addressState": { "type": [ "string", "null" ] }, "addressPostalCode": { "type": [ "string", "null" ] }, "addressCountry": { "type": [ "string", "null" ] }, "currency": { "type": [ "string", "null" ] }, "paymentTermsDays": { "type": [ "integer", "null" ] }, "defaultLeadTimeDays": { "type": [ "integer", "null" ] }, "defaultInventoryLocationId": { "anyOf": [ { "$ref": "#/components/schemas/inventoryLocationIdSchema" }, { "type": "null" } ] }, "source": { "type": [ "string", "null" ] }, "externalId": { "type": [ "string", "null" ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "name", "email", "phone", "website", "notes", "status", "code", "accountNumber", "kvk", "btw", "orderingEmail", "billingEmail", "addressLine1", "addressLine2", "addressCity", "addressState", "addressPostalCode", "addressCountry", "currency", "paymentTermsDays", "defaultLeadTimeDays", "defaultInventoryLocationId", "source", "externalId", "createdAt", "updatedAt" ] }, "supplierStatusSchema": { "type": "string", "enum": [ "active", "inactive", "archived" ] } } ``` # Remove a product supplier link. (/openapi/removeProductSupplierLink) Remove a supplier link from a product. ## DELETE /v1/organizations/{organizationId}/products/{productId}/suppliers/{linkId} ```json { "operationId": "removeProductSupplierLink", "summary": "Remove a product supplier link.", "description": "Remove a supplier link from a product.", "tags": [ "Assets" ], "parameters": [ { "in": "path", "name": "linkId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "productId", "required": true, "schema": { "$ref": "#/components/schemas/productIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns a success payload when the supplier link is removed.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/publicApiSuccessResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "organizationIdSchema": { "type": "string", "format": "uuid" }, "productIdSchema": { "type": "string", "format": "uuid" }, "publicApiSuccessResponseSchema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [ true ] } }, "required": [ "success" ] } } ``` # List organization audit events. (/openapi/listOrganizationAuditEvents) Return a cursor-capable page of redacted organization audit event summaries. Restricted details and sensitive metadata are never returned. ## GET /v1/organizations/{organizationId}/audit-events ```json { "operationId": "listOrganizationAuditEvents", "summary": "List organization audit events.", "description": "Return a cursor-capable page of redacted organization audit event summaries. Restricted details and sensitive metadata are never returned.", "tags": [ "Audit Log" ], "parameters": [ { "in": "query", "name": "actorIds", "required": false, "schema": { "type": "string" }, "example": "example" }, { "in": "query", "name": "actorKinds", "required": false, "schema": { "type": "string" }, "example": "example" }, { "in": "query", "name": "categories", "required": false, "schema": { "type": "string" }, "example": "example" }, { "in": "query", "name": "correlationId", "required": false, "schema": { "type": "string" }, "example": "123" }, { "in": "query", "name": "cursor", "required": false, "schema": { "type": "string" }, "example": "example" }, { "in": "query", "name": "direction", "required": false, "schema": { "type": "string" }, "example": "example" }, { "in": "query", "name": "eventTypes", "required": false, "schema": { "type": "string" }, "example": "example" }, { "in": "query", "name": "from", "required": false, "schema": { "type": "string" }, "example": "example" }, { "in": "query", "name": "offset", "required": false, "schema": { "type": "string" }, "example": "example" }, { "in": "query", "name": "outcomes", "required": false, "schema": { "type": "string" }, "example": "example" }, { "in": "query", "name": "pageSize", "required": false, "schema": { "type": "string" }, "example": "example" }, { "in": "query", "name": "search", "required": false, "schema": { "type": "string" }, "example": "example" }, { "in": "query", "name": "sources", "required": false, "schema": { "type": "string" }, "example": "example" }, { "in": "query", "name": "targetId", "required": false, "schema": { "type": "string" }, "example": "123" }, { "in": "query", "name": "targetType", "required": false, "schema": { "type": "string" }, "example": "example" }, { "in": "query", "name": "to", "required": false, "schema": { "type": "string" }, "example": "example" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns redacted audit event summaries and the next cursor.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/auditLogEventListPageHttpSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "auditHttpActorKindSchema": { "type": "string", "enum": [ "user", "platform_admin", "system", "api_key", "integration", "workflow", "webhook" ] }, "auditHttpCategorySchema": { "type": "string", "enum": [ "authentication", "users", "permissions", "billing", "data", "settings", "integrations", "security", "system" ] }, "auditHttpDateTimeSchema": { "type": "string", "format": "date-time" }, "auditHttpEventTypeSchema": { "type": "string", "minLength": 1, "maxLength": 160, "pattern": "^[a-z0-9]+([._-][a-z0-9]+)*$" }, "auditHttpOutcomeSchema": { "type": "string", "enum": [ "success", "failure", "denied", "attempted" ] }, "auditHttpSourceSchema": { "type": "string", "enum": [ "saas", "api", "mcp", "ai", "workflow", "webhook", "database", "system" ] }, "auditLogEventListPageHttpSchema": { "type": "object", "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/auditLogEventSummaryHttpSchema" } }, "nextCursor": { "type": [ "string", "null" ] }, "total": { "type": "integer", "minimum": 0 } }, "required": [ "items", "nextCursor", "total" ] }, "auditLogEventSummaryHttpSchema": { "type": "object", "properties": { "actor": { "type": "object", "properties": { "displayName": { "type": [ "string", "null" ] }, "id": { "type": [ "string", "null" ], "format": "uuid" }, "kind": { "$ref": "#/components/schemas/auditHttpActorKindSchema" } }, "required": [ "displayName", "id", "kind" ] }, "category": { "$ref": "#/components/schemas/auditHttpCategorySchema" }, "correlationId": { "type": [ "string", "null" ] }, "eventType": { "$ref": "#/components/schemas/auditHttpEventTypeSchema" }, "id": { "type": "string", "format": "uuid" }, "occurredAt": { "$ref": "#/components/schemas/auditHttpDateTimeSchema" }, "outcome": { "$ref": "#/components/schemas/auditHttpOutcomeSchema" }, "source": { "$ref": "#/components/schemas/auditHttpSourceSchema" }, "summary": { "type": [ "string", "null" ] }, "target": { "type": "object", "properties": { "displayName": { "type": [ "string", "null" ] }, "id": { "type": [ "string", "null" ] }, "type": { "type": [ "string", "null" ] } }, "required": [ "displayName", "id", "type" ] } }, "required": [ "actor", "category", "correlationId", "eventType", "id", "occurredAt", "outcome", "source", "summary", "target" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" } } ``` # Create an organization audit export. (/openapi/createOrganizationAuditExport) Queue an asynchronous CSV export of redacted organization audit events. Restricted details cannot be requested through this endpoint. ## POST /v1/organizations/{organizationId}/audit-exports ```json { "operationId": "createOrganizationAuditExport", "summary": "Create an organization audit export.", "description": "Queue an asynchronous CSV export of redacted organization audit events. Restricted details cannot be requested through this endpoint.", "tags": [ "Audit Log" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/organizationAuditExportRequestHttpSchema" } } }, "description": "Audit filters covering at most 90 days. Missing dates default to the latest 30 days.", "required": true }, "responses": { "202": { "description": "Returns the accepted asynchronous export job.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/auditExportJobHttpSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "auditExportJobHttpSchema": { "type": "object", "properties": { "completedAt": { "anyOf": [ { "$ref": "#/components/schemas/auditHttpDateTimeSchema" }, { "type": "null" } ] }, "downloadUrl": { "type": "string", "format": "uri" }, "expiresAt": { "anyOf": [ { "$ref": "#/components/schemas/auditHttpDateTimeSchema" }, { "type": "null" } ] }, "failureReason": { "type": [ "string", "null" ] }, "filters": { "$ref": "#/components/schemas/organizationAuditExportFiltersHttpSchema" }, "id": { "type": "string", "format": "uuid" }, "requestedAt": { "$ref": "#/components/schemas/auditHttpDateTimeSchema" }, "rowCount": { "type": [ "integer", "null" ], "minimum": 0 }, "status": { "$ref": "#/components/schemas/auditHttpExportStatusSchema" }, "updatedAt": { "$ref": "#/components/schemas/auditHttpDateTimeSchema" } }, "required": [ "completedAt", "expiresAt", "failureReason", "filters", "id", "requestedAt", "rowCount", "status", "updatedAt" ] }, "auditHttpActorKindSchema": { "type": "string", "enum": [ "user", "platform_admin", "system", "api_key", "integration", "workflow", "webhook" ] }, "auditHttpCategorySchema": { "type": "string", "enum": [ "authentication", "users", "permissions", "billing", "data", "settings", "integrations", "security", "system" ] }, "auditHttpDateTimeSchema": { "type": "string", "format": "date-time" }, "auditHttpEventTypeSchema": { "type": "string", "minLength": 1, "maxLength": 160, "pattern": "^[a-z0-9]+([._-][a-z0-9]+)*$" }, "auditHttpExportStatusSchema": { "type": "string", "enum": [ "pending", "running", "completed", "failed", "expired" ] }, "auditHttpOutcomeSchema": { "type": "string", "enum": [ "success", "failure", "denied", "attempted" ] }, "auditHttpSourceSchema": { "type": "string", "enum": [ "saas", "api", "mcp", "ai", "workflow", "webhook", "database", "system" ] }, "organizationAuditExportFiltersHttpSchema": { "type": "object", "properties": { "actorIds": { "type": "array", "items": { "type": "string", "format": "uuid" }, "maxItems": 100 }, "actorKinds": { "type": "array", "items": { "$ref": "#/components/schemas/auditHttpActorKindSchema" }, "maxItems": 7 }, "categories": { "type": "array", "items": { "$ref": "#/components/schemas/auditHttpCategorySchema" }, "maxItems": 9 }, "correlationId": { "type": "string", "minLength": 1, "maxLength": 240 }, "direction": { "type": "string", "enum": [ "asc", "desc" ], "default": "desc" }, "eventTypes": { "type": "array", "items": { "$ref": "#/components/schemas/auditHttpEventTypeSchema" }, "maxItems": 100 }, "from": { "allOf": [ { "$ref": "#/components/schemas/auditHttpDateTimeSchema" } ] }, "outcomes": { "type": "array", "items": { "$ref": "#/components/schemas/auditHttpOutcomeSchema" }, "maxItems": 4 }, "search": { "type": "string", "minLength": 1, "maxLength": 200 }, "sources": { "type": "array", "items": { "$ref": "#/components/schemas/auditHttpSourceSchema" }, "maxItems": 8 }, "targetId": { "type": "string", "minLength": 1, "maxLength": 240 }, "targetType": { "type": "string", "minLength": 1, "maxLength": 120 }, "to": { "allOf": [ { "$ref": "#/components/schemas/auditHttpDateTimeSchema" } ] } }, "required": [ "direction" ] }, "organizationAuditExportRequestHttpSchema": { "type": "object", "properties": { "filters": { "type": "object", "properties": { "actorIds": { "type": "array", "items": { "type": "string", "format": "uuid" }, "maxItems": 100 }, "actorKinds": { "type": "array", "items": { "$ref": "#/components/schemas/auditHttpActorKindSchema" }, "maxItems": 7 }, "categories": { "type": "array", "items": { "$ref": "#/components/schemas/auditHttpCategorySchema" }, "maxItems": 9 }, "correlationId": { "type": "string", "minLength": 1, "maxLength": 240 }, "direction": { "type": "string", "enum": [ "asc", "desc" ], "default": "desc" }, "eventTypes": { "type": "array", "items": { "$ref": "#/components/schemas/auditHttpEventTypeSchema" }, "maxItems": 100 }, "from": { "allOf": [ { "$ref": "#/components/schemas/auditHttpDateTimeSchema" } ] }, "outcomes": { "type": "array", "items": { "$ref": "#/components/schemas/auditHttpOutcomeSchema" }, "maxItems": 4 }, "search": { "type": "string", "minLength": 1, "maxLength": 200 }, "sources": { "type": "array", "items": { "$ref": "#/components/schemas/auditHttpSourceSchema" }, "maxItems": 8 }, "targetId": { "type": "string", "minLength": 1, "maxLength": 240 }, "targetType": { "type": "string", "minLength": 1, "maxLength": 120 }, "to": { "allOf": [ { "$ref": "#/components/schemas/auditHttpDateTimeSchema" } ] } }, "required": [ "direction" ], "default": { "direction": "desc" } } }, "required": [ "filters" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" } } ``` # Get an organization audit export. (/openapi/getOrganizationAuditExport) Return one export job owned by the authenticated requester. A five-minute download URL is included only while a completed export remains available. ## GET /v1/organizations/{organizationId}/audit-exports/{exportId} ```json { "operationId": "getOrganizationAuditExport", "summary": "Get an organization audit export.", "description": "Return one export job owned by the authenticated requester. A five-minute download URL is included only while a completed export remains available.", "tags": [ "Audit Log" ], "parameters": [ { "in": "path", "name": "exportId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns safe export status and an eligible short-lived download URL.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/auditExportJobHttpSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "auditExportJobHttpSchema": { "type": "object", "properties": { "completedAt": { "anyOf": [ { "$ref": "#/components/schemas/auditHttpDateTimeSchema" }, { "type": "null" } ] }, "downloadUrl": { "type": "string", "format": "uri" }, "expiresAt": { "anyOf": [ { "$ref": "#/components/schemas/auditHttpDateTimeSchema" }, { "type": "null" } ] }, "failureReason": { "type": [ "string", "null" ] }, "filters": { "$ref": "#/components/schemas/organizationAuditExportFiltersHttpSchema" }, "id": { "type": "string", "format": "uuid" }, "requestedAt": { "$ref": "#/components/schemas/auditHttpDateTimeSchema" }, "rowCount": { "type": [ "integer", "null" ], "minimum": 0 }, "status": { "$ref": "#/components/schemas/auditHttpExportStatusSchema" }, "updatedAt": { "$ref": "#/components/schemas/auditHttpDateTimeSchema" } }, "required": [ "completedAt", "expiresAt", "failureReason", "filters", "id", "requestedAt", "rowCount", "status", "updatedAt" ] }, "auditHttpActorKindSchema": { "type": "string", "enum": [ "user", "platform_admin", "system", "api_key", "integration", "workflow", "webhook" ] }, "auditHttpCategorySchema": { "type": "string", "enum": [ "authentication", "users", "permissions", "billing", "data", "settings", "integrations", "security", "system" ] }, "auditHttpDateTimeSchema": { "type": "string", "format": "date-time" }, "auditHttpEventTypeSchema": { "type": "string", "minLength": 1, "maxLength": 160, "pattern": "^[a-z0-9]+([._-][a-z0-9]+)*$" }, "auditHttpExportStatusSchema": { "type": "string", "enum": [ "pending", "running", "completed", "failed", "expired" ] }, "auditHttpOutcomeSchema": { "type": "string", "enum": [ "success", "failure", "denied", "attempted" ] }, "auditHttpSourceSchema": { "type": "string", "enum": [ "saas", "api", "mcp", "ai", "workflow", "webhook", "database", "system" ] }, "organizationAuditExportFiltersHttpSchema": { "type": "object", "properties": { "actorIds": { "type": "array", "items": { "type": "string", "format": "uuid" }, "maxItems": 100 }, "actorKinds": { "type": "array", "items": { "$ref": "#/components/schemas/auditHttpActorKindSchema" }, "maxItems": 7 }, "categories": { "type": "array", "items": { "$ref": "#/components/schemas/auditHttpCategorySchema" }, "maxItems": 9 }, "correlationId": { "type": "string", "minLength": 1, "maxLength": 240 }, "direction": { "type": "string", "enum": [ "asc", "desc" ], "default": "desc" }, "eventTypes": { "type": "array", "items": { "$ref": "#/components/schemas/auditHttpEventTypeSchema" }, "maxItems": 100 }, "from": { "allOf": [ { "$ref": "#/components/schemas/auditHttpDateTimeSchema" } ] }, "outcomes": { "type": "array", "items": { "$ref": "#/components/schemas/auditHttpOutcomeSchema" }, "maxItems": 4 }, "search": { "type": "string", "minLength": 1, "maxLength": 200 }, "sources": { "type": "array", "items": { "$ref": "#/components/schemas/auditHttpSourceSchema" }, "maxItems": 8 }, "targetId": { "type": "string", "minLength": 1, "maxLength": 240 }, "targetType": { "type": "string", "minLength": 1, "maxLength": 120 }, "to": { "allOf": [ { "$ref": "#/components/schemas/auditHttpDateTimeSchema" } ] } }, "required": [ "direction" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" } } ``` # Log in with email and password. (/openapi/loginWithPassword) Exchange an email and password for an authenticated session. ## POST /v1/auth/login ```json { "operationId": "loginWithPassword", "summary": "Log in with email and password.", "description": "Exchange an email and password for an authenticated session.", "tags": [ "Auth" ], "parameters": [], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/loginRequestSchema" } } }, "description": "Email and password used to create an authenticated session." }, "responses": { "200": { "description": "Returns the authenticated user.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/authUserSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "authUserIdSchema": { "type": "string", "format": "uuid" }, "authUserSchema": { "type": "object", "properties": { "createdAt": { "type": "string", "format": "date-time" }, "email": { "type": [ "string", "null" ], "format": "email" }, "emailVerified": { "type": "boolean" }, "id": { "$ref": "#/components/schemas/authUserIdSchema" }, "lastSignInAt": { "type": [ "string", "null" ], "format": "date-time" } }, "required": [ "createdAt", "email", "emailVerified", "id", "lastSignInAt" ] }, "loginRequestSchema": { "type": "object", "properties": { "email": { "$ref": "#/components/schemas/normalizedEmailSchema" }, "password": { "$ref": "#/components/schemas/passwordSchema" } }, "required": [ "email", "password" ], "examples": [ { "email": "test@example.com", "password": "Password123!" } ] }, "normalizedEmailSchema": { "type": "string", "format": "email" }, "passwordSchema": { "type": "string", "minLength": 8, "maxLength": 72 } } ``` # Log out the current session. (/openapi/logoutCurrentSession) Clear the active authenticated session for the current requester. ## POST /v1/auth/logout ```json { "operationId": "logoutCurrentSession", "summary": "Log out the current session.", "description": "Clear the active authenticated session for the current requester.", "tags": [ "Auth" ], "parameters": [], "responses": { "200": { "description": "Returns a success flag when logout completes.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/logoutResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "logoutResponseSchema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [ true ] } }, "required": [ "success" ] } } ``` # Sign up with email and password. (/openapi/signUpWithPassword) Create a user account and start the standard confirmation flow. ## POST /v1/auth/signup ```json { "operationId": "signUpWithPassword", "summary": "Sign up with email and password.", "description": "Create a user account and start the standard confirmation flow.", "tags": [ "Auth" ], "parameters": [], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/signupRequestSchema" } } }, "description": "Email and password used to create a new user." }, "responses": { "201": { "description": "Returns the created user when the auth provider includes it.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/signupResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "authUserIdSchema": { "type": "string", "format": "uuid" }, "authUserSchema": { "type": "object", "properties": { "createdAt": { "type": "string", "format": "date-time" }, "email": { "type": [ "string", "null" ], "format": "email" }, "emailVerified": { "type": "boolean" }, "id": { "$ref": "#/components/schemas/authUserIdSchema" }, "lastSignInAt": { "type": [ "string", "null" ], "format": "date-time" } }, "required": [ "createdAt", "email", "emailVerified", "id", "lastSignInAt" ] }, "normalizedEmailSchema": { "type": "string", "format": "email" }, "passwordSchema": { "type": "string", "minLength": 8, "maxLength": 72 }, "safeRedirectPathSchema": { "type": "string" }, "signupNameSchema": { "type": "string", "minLength": 1, "maxLength": 128 }, "signupRequestSchema": { "type": "object", "properties": { "email": { "$ref": "#/components/schemas/normalizedEmailSchema" }, "password": { "$ref": "#/components/schemas/passwordSchema" }, "firstName": { "$ref": "#/components/schemas/signupNameSchema" }, "lastName": { "$ref": "#/components/schemas/signupNameSchema" }, "redirectPath": { "allOf": [ { "$ref": "#/components/schemas/safeRedirectPathSchema" } ] } }, "required": [ "email", "password", "firstName", "lastName" ], "examples": [ { "email": "new.user@example.com", "password": "Password123!", "firstName": "New", "lastName": "User", "redirectPath": "/dashboard" } ] }, "signupResponseSchema": { "type": "object", "discriminator": { "propertyName": "status" }, "oneOf": [ { "type": "object", "properties": { "email": { "type": "string", "format": "email" }, "status": { "type": "string", "enum": [ "confirmation_sent" ] }, "user": { "anyOf": [ { "$ref": "#/components/schemas/authUserSchema" }, { "type": "null" } ] } }, "required": [ "email", "status", "user" ] }, { "type": "object", "properties": { "email": { "type": "string", "format": "email" }, "status": { "type": "string", "enum": [ "existing_account_challenge_sent" ] }, "user": { "type": "null" } }, "required": [ "email", "status", "user" ] } ] } } ``` # Start an OAuth provider flow. (/openapi/startOAuthFlow) Create the provider authorization URL for an OAuth sign-in flow. ## GET /v1/auth/oauth/start ```json { "operationId": "startOAuthFlow", "summary": "Start an OAuth provider flow.", "description": "Create the provider authorization URL for an OAuth sign-in flow.", "tags": [ "Auth" ], "parameters": [ { "in": "query", "name": "provider", "required": true, "schema": { "$ref": "#/components/schemas/authProviderSchema" }, "example": "example" }, { "in": "query", "name": "redirectPath", "required": false, "schema": { "type": [ "string", "null" ] }, "example": "example" } ], "responses": { "200": { "description": "Returns the provider redirect URL.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/oauthStartResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "authProviderSchema": { "type": "string", "enum": [ "apple", "azure", "bitbucket", "discord", "facebook", "figma", "github", "gitlab", "google", "kakao", "keycloak", "linkedin_oidc", "notion", "slack", "spotify", "twitch", "twitter", "workos", "zoom" ] }, "oauthStartResponseSchema": { "type": "object", "properties": { "authorizationUrl": { "type": "string", "format": "uri" }, "provider": { "$ref": "#/components/schemas/authProviderSchema" } }, "required": [ "authorizationUrl", "provider" ] } } ``` # Request a password reset email. (/openapi/requestPasswordReset) Start the password recovery flow by sending a recovery email to the provided address. ## POST /v1/auth/password/reset ```json { "operationId": "requestPasswordReset", "summary": "Request a password reset email.", "description": "Start the password recovery flow by sending a recovery email to the provided address.", "tags": [ "Auth" ], "parameters": [], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/passwordResetRequestSchema" } } }, "description": "Email address and optional redirect path used for the recovery flow." }, "responses": { "200": { "description": "Returns the requested email address when the recovery email is accepted.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/passwordResetResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "normalizedEmailSchema": { "type": "string", "format": "email" }, "passwordResetRequestSchema": { "type": "object", "properties": { "email": { "$ref": "#/components/schemas/normalizedEmailSchema" }, "redirectPath": { "allOf": [ { "$ref": "#/components/schemas/safeRedirectPathSchema" } ] } }, "required": [ "email" ], "examples": [ { "email": "test@example.com", "redirectPath": "/login" } ] }, "passwordResetResponseSchema": { "type": "object", "properties": { "email": { "type": "string", "format": "email" }, "success": { "type": "boolean", "enum": [ true ] } }, "required": [ "email", "success" ] }, "safeRedirectPathSchema": { "type": "string" } } ``` # Update the current user's password. (/openapi/updatePassword) Update the password for the active authenticated session, including recovery sessions established through the password reset flow. ## POST /v1/auth/password/update ```json { "operationId": "updatePassword", "summary": "Update the current user's password.", "description": "Update the password for the active authenticated session, including recovery sessions established through the password reset flow.", "tags": [ "Auth" ], "parameters": [], "security": [ { "SessionCookie": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/passwordUpdateRequestSchema" } } }, "description": "The replacement password for the current authenticated user." }, "responses": { "200": { "description": "Returns success when the password update is accepted.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/passwordUpdateResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "passwordSchema": { "type": "string", "minLength": 8, "maxLength": 72 }, "passwordUpdateRequestSchema": { "type": "object", "properties": { "password": { "$ref": "#/components/schemas/passwordSchema" } }, "required": [ "password" ], "examples": [ { "password": "NewPassword123!" } ] }, "passwordUpdateResponseSchema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [ true ] } }, "required": [ "success" ] } } ``` # Download a public calendar feed by token. (/openapi/downloadPublicCalendarFeed) Returns an iCalendar feed through a bearer-token public link. ## GET /v1/public/calendars/{token} ```json { "operationId": "downloadPublicCalendarFeed", "summary": "Download a public calendar feed by token.", "description": "Returns an iCalendar feed through a bearer-token public link.", "tags": [ "Calendars" ], "parameters": [ { "in": "path", "name": "token", "required": true, "schema": { "type": "string", "minLength": 1 }, "example": "example" } ], "responses": { "200": { "description": "Returns the calendar as an iCalendar document.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/publicCalendarFeedResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "publicCalendarFeedResponseSchema": { "type": "string" } } ``` # Read a public shared chat by token. (/openapi/readPublicSharedChat) Returns a paginated read-only chat through a bearer-token public link. ## GET /v1/public/chats/{token} ```json { "operationId": "readPublicSharedChat", "summary": "Read a public shared chat by token.", "description": "Returns a paginated read-only chat through a bearer-token public link.", "tags": [ "Chats" ], "parameters": [ { "in": "query", "name": "limit", "required": true, "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 50 }, "example": 1 }, { "in": "query", "name": "offset", "required": true, "schema": { "default": 0, "type": "integer", "minimum": 0, "maximum": 9007199254740991 }, "example": 1 }, { "in": "path", "name": "token", "required": true, "schema": { "type": "string", "pattern": "^[a-f0-9]{64}$" }, "example": "example" } ], "responses": { "200": { "description": "Returns the shared thread and a chronological message page.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/publicSharedChatResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "jsonValueSchema": { "anyOf": [ { "type": "string" }, { "type": "number" }, { "type": "boolean" }, { "type": "null" }, { "type": "array", "items": { "$ref": "#/components/schemas/jsonValueSchema" } }, { "type": "object", "additionalProperties": { "$ref": "#/components/schemas/jsonValueSchema" }, "propertyNames": { "type": "string" } } ] }, "publicChatMessageSchema": { "type": "object", "properties": { "clientMessageId": { "type": [ "string", "null" ], "minLength": 1, "maxLength": 240 }, "createdAt": { "type": "string", "format": "date-time" }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "id": { "type": "string", "format": "uuid" }, "metadata": { "type": "object", "additionalProperties": { "$ref": "#/components/schemas/jsonValueSchema" }, "propertyNames": { "type": "string" } }, "organizationId": { "type": [ "string", "null" ], "format": "uuid" }, "parts": { "type": "array", "items": {} }, "role": { "type": "string", "enum": [ "system", "user", "assistant", "tool" ] }, "status": { "type": "string", "enum": [ "complete", "partial", "failed" ] }, "threadId": { "type": "string", "format": "uuid" } }, "required": [ "clientMessageId", "createdAt", "createdBy", "id", "metadata", "organizationId", "parts", "role", "status", "threadId" ] }, "publicSharedChatResponseSchema": { "type": "object", "properties": { "messages": { "type": "array", "items": { "$ref": "#/components/schemas/publicChatMessageSchema" } }, "pagination": { "type": "object", "properties": { "hasMore": { "type": "boolean" }, "limit": { "type": "integer", "minimum": 1, "maximum": 100 }, "offset": { "type": "integer", "minimum": 0 }, "total": { "type": "integer", "minimum": 0 } }, "required": [ "hasMore", "limit", "offset", "total" ] }, "thread": { "type": "object", "properties": { "activeStreamId": { "type": [ "string", "null" ], "minLength": 1 }, "activeWorkflowRunId": { "type": [ "string", "null" ], "minLength": 1 }, "archivedAt": { "type": [ "string", "null" ], "format": "date-time" }, "createdAt": { "type": "string", "format": "date-time" }, "id": { "type": "string", "format": "uuid" }, "metadata": { "type": "object", "additionalProperties": { "$ref": "#/components/schemas/jsonValueSchema" }, "propertyNames": { "type": "string" } }, "mode": { "type": "string", "enum": [ "ask", "plan", "agent" ] }, "organizationId": { "type": [ "string", "null" ], "format": "uuid" }, "ownerUserId": { "type": "string", "format": "uuid" }, "pinnedAt": { "type": [ "string", "null" ], "format": "date-time" }, "streamStatus": { "type": "string", "enum": [ "idle", "submitted", "streaming", "interrupted", "stopping", "stopped", "completed", "failed" ] }, "title": { "type": [ "string", "null" ], "minLength": 1, "maxLength": 240 }, "updatedAt": { "type": "string", "format": "date-time" }, "visibility": { "type": "string", "enum": [ "private", "organization", "shared" ] } }, "required": [ "activeStreamId", "activeWorkflowRunId", "archivedAt", "createdAt", "id", "metadata", "mode", "organizationId", "ownerUserId", "pinnedAt", "streamStatus", "title", "updatedAt", "visibility" ] } }, "required": [ "messages", "pagination", "thread" ] } } ``` # Search collaboration references. (/openapi/searchCollaborationReferences) ## GET /v1/organizations/{organizationId}/collaboration/references ```json { "operationId": "searchCollaborationReferences", "summary": "Search collaboration references.", "description": "", "tags": [ "Collaboration" ], "parameters": [ { "in": "query", "name": "trigger", "required": true, "schema": { "$ref": "#/components/schemas/collaborationReferenceTriggerSchema" }, "example": "example" }, { "in": "query", "name": "q", "required": true, "schema": { "type": "string", "maxLength": 200, "default": "" }, "example": "example" }, { "in": "query", "name": "kind", "required": false, "schema": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationReferenceKindSchema" }, "maxItems": 8 }, "example": "example" }, { "in": "query", "name": "limit", "required": true, "schema": { "default": 20, "type": "integer", "minimum": 1, "maximum": 50 }, "example": 1 }, { "in": "path", "name": "organizationId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns authorized collaboration reference candidates.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/collaborationReferenceSearchResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "collaborationReferenceCandidateSchema": { "type": "object", "discriminator": { "propertyName": "kind" }, "oneOf": [ { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "user" ] }, "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 }, "trigger": { "$ref": "#/components/schemas/collaborationReferenceTriggerSchema" }, "description": { "type": [ "string", "null" ], "maxLength": 500 }, "imageUrl": { "type": [ "string", "null" ], "format": "uri" } }, "required": [ "kind", "id", "organizationId", "label", "path", "trigger", "description", "imageUrl" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "team" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 }, "trigger": { "$ref": "#/components/schemas/collaborationReferenceTriggerSchema" }, "description": { "type": [ "string", "null" ], "maxLength": 500 }, "imageUrl": { "type": [ "string", "null" ], "format": "uri" } }, "required": [ "kind", "id", "organizationId", "label", "path", "trigger", "description", "imageUrl" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "task" ] }, "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 }, "trigger": { "$ref": "#/components/schemas/collaborationReferenceTriggerSchema" }, "description": { "type": [ "string", "null" ], "maxLength": 500 }, "imageUrl": { "type": [ "string", "null" ], "format": "uri" } }, "required": [ "kind", "id", "organizationId", "label", "path", "trigger", "description", "imageUrl" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "customer" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 }, "trigger": { "$ref": "#/components/schemas/collaborationReferenceTriggerSchema" }, "description": { "type": [ "string", "null" ], "maxLength": 500 }, "imageUrl": { "type": [ "string", "null" ], "format": "uri" } }, "required": [ "kind", "id", "organizationId", "label", "path", "trigger", "description", "imageUrl" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "quote" ] }, "id": { "$ref": "#/components/schemas/quoteIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 }, "trigger": { "$ref": "#/components/schemas/collaborationReferenceTriggerSchema" }, "description": { "type": [ "string", "null" ], "maxLength": 500 }, "imageUrl": { "type": [ "string", "null" ], "format": "uri" } }, "required": [ "kind", "id", "organizationId", "label", "path", "trigger", "description", "imageUrl" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "invoice" ] }, "id": { "$ref": "#/components/schemas/invoiceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 }, "trigger": { "$ref": "#/components/schemas/collaborationReferenceTriggerSchema" }, "description": { "type": [ "string", "null" ], "maxLength": 500 }, "imageUrl": { "type": [ "string", "null" ], "format": "uri" } }, "required": [ "kind", "id", "organizationId", "label", "path", "trigger", "description", "imageUrl" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "product" ] }, "id": { "$ref": "#/components/schemas/productIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 }, "trigger": { "$ref": "#/components/schemas/collaborationReferenceTriggerSchema" }, "description": { "type": [ "string", "null" ], "maxLength": 500 }, "imageUrl": { "type": [ "string", "null" ], "format": "uri" } }, "required": [ "kind", "id", "organizationId", "label", "path", "trigger", "description", "imageUrl" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "customer-asset" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 }, "trigger": { "$ref": "#/components/schemas/collaborationReferenceTriggerSchema" }, "description": { "type": [ "string", "null" ], "maxLength": 500 }, "imageUrl": { "type": [ "string", "null" ], "format": "uri" } }, "required": [ "kind", "id", "organizationId", "label", "path", "trigger", "description", "imageUrl" ], "additionalProperties": false } ] }, "collaborationReferenceKindSchema": { "type": "string", "enum": [ "user", "team", "task", "customer", "quote", "invoice", "product", "customer-asset" ] }, "collaborationReferenceSearchResponseSchema": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationReferenceCandidateSchema" } }, "collaborationReferenceTriggerSchema": { "type": "string", "enum": [ "@", "#" ] }, "invoiceIdSchema": { "type": "string", "format": "uuid" }, "numericReferenceIdSchema": { "type": "string", "pattern": "^[1-9]\\d*$" }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "productIdSchema": { "type": "string", "format": "uuid" }, "quoteIdSchema": { "type": "string", "format": "uuid" } } ``` # Search records available to the command center. (/openapi/searchCommandCenterRecords) Search authorized organization, customer portal, or platform administration records. ## GET /v1/command-center/search ```json { "operationId": "searchCommandCenterRecords", "summary": "Search records available to the command center.", "description": "Search authorized organization, customer portal, or platform administration records.", "tags": [ "Command Center" ], "parameters": [ { "in": "query", "name": "mode", "required": true, "schema": { "$ref": "#/components/schemas/commandCenterModeSchema" }, "example": "example" }, { "in": "query", "name": "query", "required": true, "schema": { "type": "string", "minLength": 1, "maxLength": 160 }, "example": "example" }, { "in": "query", "name": "orgSlug", "required": false, "schema": { "allOf": [ { "$ref": "#/components/schemas/organizationSlugSchema" } ] }, "example": "example" }, { "in": "query", "name": "limit", "required": true, "schema": { "default": 24, "type": "integer", "minimum": 1, "maximum": 40 }, "example": 1 } ], "responses": { "200": { "description": "Returns matching command center records.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/commandCenterSearchResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "commandCenterModeSchema": { "type": "string", "enum": [ "organization", "customer", "admin" ] }, "commandCenterRecordHitSchema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1 }, "kind": { "$ref": "#/components/schemas/commandCenterRecordKindSchema" }, "label": { "type": "string" }, "description": { "type": [ "string", "null" ] }, "href": { "type": "string", "minLength": 1 }, "organizationId": { "type": [ "string", "null" ], "format": "uuid" } }, "required": [ "id", "kind", "label", "description", "href" ] }, "commandCenterRecordKindSchema": { "type": "string", "enum": [ "customer", "task", "product", "customer-asset", "quote", "invoice", "organization", "user", "admin-invoice", "workflow" ] }, "commandCenterSearchResponseSchema": { "type": "object", "properties": { "results": { "type": "array", "items": { "$ref": "#/components/schemas/commandCenterRecordHitSchema" } } }, "required": [ "results" ] }, "organizationSlugSchema": { "type": "string", "minLength": 2, "maxLength": 64, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" } } ``` # List customers. (/openapi/listCustomers) Return customers for an organization the authenticated user can access. ## GET /v1/organizations/{organizationId}/customers ```json { "operationId": "listCustomers", "summary": "List customers.", "description": "Return customers for an organization the authenticated user can access.", "tags": [ "Customers" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns paginated customers.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/customersListResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "customersListResponseSchema": { "type": "object" }, "organizationIdSchema": { "type": "string", "format": "uuid" } } ``` # Create a customer. (/openapi/createCustomer) Create a customer for the specified organization. ## POST /v1/organizations/{organizationId}/customers ```json { "operationId": "createCustomer", "summary": "Create a customer.", "description": "Create a customer for the specified organization.", "tags": [ "Customers" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/customerCreateInputSchema" } } }, "description": "The customer payload.", "required": true }, "responses": { "201": { "description": "Returns the created customer.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/customerSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "customerAddressSchema": { "type": "object", "properties": { "line1": { "$ref": "#/components/schemas/nullableShortText" }, "line2": { "$ref": "#/components/schemas/nullableShortText" }, "city": { "$ref": "#/components/schemas/nullableShortText" }, "state": { "$ref": "#/components/schemas/nullableShortText" }, "postalCode": { "$ref": "#/components/schemas/nullableShortText" }, "country": { "$ref": "#/components/schemas/nullableShortText" } }, "required": [ "line1", "line2", "city", "state", "postalCode", "country" ] }, "customerAssigneeSchema": { "type": "object", "properties": { "userId": { "type": "string", "format": "uuid" }, "displayName": { "type": "string", "minLength": 1 }, "avatarUrl": { "type": [ "string", "null" ], "format": "uri" }, "disabledAt": { "type": [ "string", "null" ] } }, "required": [ "userId", "displayName", "avatarUrl", "disabledAt" ] }, "customerContactInputSchema": { "type": "object", "properties": { "firstName": { "type": [ "string", "null" ], "maxLength": 120 }, "lastName": { "type": [ "string", "null" ], "maxLength": 120 }, "jobTitle": { "type": [ "string", "null" ], "maxLength": 120 }, "isPrimary": { "type": "boolean" }, "email": { "allOf": [ { "$ref": "#/components/schemas/nullableEmailInputSchema" } ] }, "phone": { "allOf": [ { "$ref": "#/components/schemas/nullablePhoneInputSchema" } ] } } }, "customerCreateInputSchema": { "type": "object", "properties": { "isBusiness": { "type": "boolean" }, "companyName": { "type": [ "string", "null" ], "maxLength": 240 }, "kvk": { "allOf": [ { "$ref": "#/components/schemas/nullableKvkNumberInputSchema" } ] }, "btw": { "allOf": [ { "$ref": "#/components/schemas/nullableVatIdInputSchema" } ] }, "website": { "allOf": [ { "$ref": "#/components/schemas/nullableUrlInputSchema" } ] }, "billingEmail": { "allOf": [ { "$ref": "#/components/schemas/nullableEmailInputSchema" } ] }, "billingName": { "type": [ "string", "null" ], "maxLength": 240 }, "address": { "type": "object", "properties": { "line1": { "$ref": "#/components/schemas/nullableShortText" }, "line2": { "$ref": "#/components/schemas/nullableShortText" }, "city": { "$ref": "#/components/schemas/nullableShortText" }, "state": { "$ref": "#/components/schemas/nullableShortText" }, "postalCode": { "$ref": "#/components/schemas/nullableShortText" }, "country": { "$ref": "#/components/schemas/nullableShortText" } } }, "description": { "allOf": [ { "$ref": "#/components/schemas/nullableLongText" } ] }, "assigneeIds": { "type": "array", "items": { "type": "string", "format": "uuid" }, "maxItems": 50 }, "status": { "allOf": [ { "$ref": "#/components/schemas/customerStatusSchema" } ] }, "primaryContact": { "allOf": [ { "$ref": "#/components/schemas/customerContactInputSchema" } ] }, "primaryLocation": { "allOf": [ { "$ref": "#/components/schemas/customerLocationInputSchema" } ] } } }, "customerIdSchema": { "type": "integer", "exclusiveMinimum": 0 }, "customerLocationInputSchema": { "type": "object", "properties": { "name": { "type": [ "string", "null" ], "maxLength": 120 }, "address": { "type": "object", "properties": { "line1": { "$ref": "#/components/schemas/nullableShortText" }, "line2": { "$ref": "#/components/schemas/nullableShortText" }, "city": { "$ref": "#/components/schemas/nullableShortText" }, "state": { "$ref": "#/components/schemas/nullableShortText" }, "postalCode": { "$ref": "#/components/schemas/nullableShortText" }, "country": { "$ref": "#/components/schemas/nullableShortText" } } }, "isPrimary": { "type": "boolean" } } }, "customerSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/customerIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "companyName": { "type": [ "string", "null" ] }, "isBusiness": { "type": "boolean" }, "kvk": { "type": [ "string", "null" ] }, "btw": { "type": [ "string", "null" ] }, "website": { "type": [ "string", "null" ] }, "billingEmail": { "type": [ "string", "null" ] }, "billingName": { "type": [ "string", "null" ] }, "archivedReason": { "type": [ "string", "null" ] }, "archivedAt": { "type": [ "string", "null" ], "format": "date-time" }, "quoteCount": { "type": "integer", "minimum": 0 }, "taskCount": { "type": "integer", "minimum": 0 }, "address": { "$ref": "#/components/schemas/customerAddressSchema" }, "status": { "$ref": "#/components/schemas/customerStatusSchema" }, "description": { "type": [ "string", "null" ] }, "logoUrl": { "type": [ "string", "null" ], "format": "uri" }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "updatedBy": { "type": [ "string", "null" ], "format": "uuid" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" }, "assignees": { "type": "array", "items": { "$ref": "#/components/schemas/customerAssigneeSchema" } }, "tags": { "type": "array", "items": { "$ref": "#/components/schemas/organizationTagSchema" } } }, "required": [ "id", "organizationId", "companyName", "isBusiness", "kvk", "btw", "website", "billingEmail", "billingName", "archivedReason", "archivedAt", "quoteCount", "taskCount", "address", "status", "description", "logoUrl", "createdBy", "updatedBy", "createdAt", "updatedAt", "assignees", "tags" ] }, "customerStatusSchema": { "type": "string", "enum": [ "prospect", "active", "archived" ] }, "nullableEmailInputSchema": { "type": [ "string", "null" ], "format": "email", "maxLength": 320 }, "nullableKvkNumberInputSchema": { "type": [ "string", "null" ], "maxLength": 40 }, "nullableLongText": { "type": [ "string", "null" ], "maxLength": 4000 }, "nullablePhoneInputSchema": { "type": [ "string", "null" ], "maxLength": 20 }, "nullableShortText": { "type": [ "string", "null" ], "maxLength": 240 }, "nullableUrlInputSchema": { "anyOf": [ { "type": "string", "maxLength": 400, "format": "uri" }, { "type": "null" } ] }, "nullableVatIdInputSchema": { "type": [ "string", "null" ], "maxLength": 40 }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "organizationTagSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/tagIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "kind": { "$ref": "#/components/schemas/tagKindSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 80 }, "slug": { "$ref": "#/components/schemas/tagSlugSchema" }, "color": { "anyOf": [ { "$ref": "#/components/schemas/tagColorSchema" }, { "type": "null" } ] }, "description": { "type": [ "string", "null" ] }, "position": { "type": "integer", "minimum": 0 }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "kind", "name", "slug", "color", "description", "position", "createdAt", "updatedAt" ] }, "tagColorSchema": { "type": "string", "pattern": "^#(?:[0-9a-f]{3}|[0-9a-f]{6})$" }, "tagIdSchema": { "type": "string", "format": "uuid" }, "tagKindSchema": { "type": "string", "enum": [ "task", "customer", "product" ] }, "tagSlugSchema": { "type": "string", "minLength": 1, "maxLength": 160, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" } } ``` # Get a customer. (/openapi/getCustomer) Return a customer detail record for the specified organization. ## GET /v1/organizations/{organizationId}/customers/{customerId} ```json { "operationId": "getCustomer", "summary": "Get a customer.", "description": "Return a customer detail record for the specified organization.", "tags": [ "Customers" ], "parameters": [ { "in": "path", "name": "customerId", "required": true, "schema": { "$ref": "#/components/schemas/customerIdSchema" }, "example": "123" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns the requested customer.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/customerDetailSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "collaborationBlockSchema": { "type": "object", "discriminator": { "propertyName": "type" }, "oneOf": [ { "type": "object", "properties": { "type": { "type": "string", "enum": [ "paragraph" ] }, "content": { "$ref": "#/components/schemas/collaborationInlineContentSchema" } }, "required": [ "type", "content" ], "additionalProperties": false }, { "type": "object", "properties": { "type": { "type": "string", "enum": [ "bullet-list" ] }, "items": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationInlineContentSchema" }, "minItems": 1, "maxItems": 500 } }, "required": [ "type", "items" ], "additionalProperties": false }, { "type": "object", "properties": { "type": { "type": "string", "enum": [ "numbered-list" ] }, "items": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationInlineContentSchema" }, "minItems": 1, "maxItems": 500 } }, "required": [ "type", "items" ], "additionalProperties": false } ] }, "collaborationCommentAuthorSchema": { "type": "object", "properties": { "userId": { "type": "string", "format": "uuid" }, "displayName": { "type": "string", "minLength": 1 }, "avatarUrl": { "type": [ "string", "null" ], "format": "uri" } }, "required": [ "userId", "displayName", "avatarUrl" ] }, "collaborationDocumentSchema": { "type": "object", "properties": { "version": { "type": "integer", "enum": [ 1 ] }, "blocks": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationBlockSchema" }, "minItems": 1, "maxItems": 1000 } }, "required": [ "version", "blocks" ], "additionalProperties": false }, "collaborationInlineContentSchema": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationInlineSchema" }, "minItems": 1, "maxItems": 1000 }, "collaborationInlineSchema": { "type": "object", "discriminator": { "propertyName": "type" }, "oneOf": [ { "type": "object", "properties": { "type": { "type": "string", "enum": [ "text" ] }, "text": { "type": "string", "minLength": 1, "maxLength": 20000 } }, "required": [ "type", "text" ], "additionalProperties": false }, { "type": "object", "properties": { "type": { "type": "string", "enum": [ "reference" ] }, "reference": { "$ref": "#/components/schemas/collaborationReferenceSchema" } }, "required": [ "type", "reference" ], "additionalProperties": false } ] }, "collaborationReferenceSchema": { "type": "object", "discriminator": { "propertyName": "kind" }, "oneOf": [ { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "user" ] }, "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "team" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "task" ] }, "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "customer" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "quote" ] }, "id": { "$ref": "#/components/schemas/quoteIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "invoice" ] }, "id": { "$ref": "#/components/schemas/invoiceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "product" ] }, "id": { "$ref": "#/components/schemas/productIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "customer-asset" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false } ] }, "contactMethodSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "type": { "$ref": "#/components/schemas/contactMethodTypeSchema" }, "label": { "type": [ "string", "null" ], "maxLength": 120 }, "value": { "type": "string", "minLength": 1, "maxLength": 320 }, "isPrimary": { "type": "boolean" }, "position": { "type": "integer", "minimum": 0 }, "verifiedAt": { "type": [ "string", "null" ], "format": "date-time" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "type", "label", "value", "isPrimary", "position", "verifiedAt", "createdAt", "updatedAt" ] }, "contactMethodTypeSchema": { "type": "string", "enum": [ "email", "phone", "whatsapp" ] }, "contactProfileIdSchema": { "type": "string", "format": "uuid" }, "contactProfileSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/contactProfileIdSchema" }, "userId": { "type": [ "string", "null" ], "format": "uuid" }, "firstName": { "type": [ "string", "null" ] }, "lastName": { "type": [ "string", "null" ] }, "displayName": { "type": [ "string", "null" ] }, "avatarUrl": { "type": [ "string", "null" ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" }, "contactMethods": { "type": "array", "items": { "$ref": "#/components/schemas/contactMethodSchema" } } }, "required": [ "id", "userId", "firstName", "lastName", "displayName", "avatarUrl", "createdAt", "updatedAt", "contactMethods" ] }, "customerAddressSchema": { "type": "object", "properties": { "line1": { "$ref": "#/components/schemas/nullableShortText" }, "line2": { "$ref": "#/components/schemas/nullableShortText" }, "city": { "$ref": "#/components/schemas/nullableShortText" }, "state": { "$ref": "#/components/schemas/nullableShortText" }, "postalCode": { "$ref": "#/components/schemas/nullableShortText" }, "country": { "$ref": "#/components/schemas/nullableShortText" } }, "required": [ "line1", "line2", "city", "state", "postalCode", "country" ] }, "customerAssigneeSchema": { "type": "object", "properties": { "userId": { "type": "string", "format": "uuid" }, "displayName": { "type": "string", "minLength": 1 }, "avatarUrl": { "type": [ "string", "null" ], "format": "uri" }, "disabledAt": { "type": [ "string", "null" ] } }, "required": [ "userId", "displayName", "avatarUrl", "disabledAt" ] }, "customerContactSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "customerId": { "$ref": "#/components/schemas/customerIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "contactProfile": { "$ref": "#/components/schemas/contactProfileSchema" }, "jobTitle": { "type": [ "string", "null" ] }, "isPrimary": { "type": "boolean" }, "notes": { "type": [ "string", "null" ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "customerId", "organizationId", "contactProfile", "jobTitle", "isPrimary", "notes", "createdAt", "updatedAt" ] }, "customerDetailSchema": { "type": "object", "properties": { "customer": { "$ref": "#/components/schemas/customerSchema" }, "contacts": { "type": "array", "items": { "$ref": "#/components/schemas/customerContactSchema" } }, "locations": { "type": "array", "items": { "$ref": "#/components/schemas/customerLocationSchema" } }, "notes": { "type": "array", "items": { "$ref": "#/components/schemas/customerNoteSchema" } } }, "required": [ "customer", "contacts", "locations", "notes" ] }, "customerIdSchema": { "type": "integer", "exclusiveMinimum": 0 }, "customerLocationSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "customerId": { "$ref": "#/components/schemas/customerIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 120 }, "address": { "$ref": "#/components/schemas/customerAddressSchema" }, "isPrimary": { "type": "boolean" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "customerId", "organizationId", "name", "address", "isPrimary", "createdAt", "updatedAt" ] }, "customerNoteSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "customerId": { "$ref": "#/components/schemas/customerIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "contentText": { "type": "string" }, "contentDoc": { "$ref": "#/components/schemas/collaborationDocumentSchema" }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "author": { "anyOf": [ { "$ref": "#/components/schemas/collaborationCommentAuthorSchema" }, { "type": "null" } ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "customerId", "organizationId", "contentText", "contentDoc", "createdBy", "author", "createdAt", "updatedAt" ] }, "customerSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/customerIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "companyName": { "type": [ "string", "null" ] }, "isBusiness": { "type": "boolean" }, "kvk": { "type": [ "string", "null" ] }, "btw": { "type": [ "string", "null" ] }, "website": { "type": [ "string", "null" ] }, "billingEmail": { "type": [ "string", "null" ] }, "billingName": { "type": [ "string", "null" ] }, "archivedReason": { "type": [ "string", "null" ] }, "archivedAt": { "type": [ "string", "null" ], "format": "date-time" }, "quoteCount": { "type": "integer", "minimum": 0 }, "taskCount": { "type": "integer", "minimum": 0 }, "address": { "$ref": "#/components/schemas/customerAddressSchema" }, "status": { "$ref": "#/components/schemas/customerStatusSchema" }, "description": { "type": [ "string", "null" ] }, "logoUrl": { "type": [ "string", "null" ], "format": "uri" }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "updatedBy": { "type": [ "string", "null" ], "format": "uuid" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" }, "assignees": { "type": "array", "items": { "$ref": "#/components/schemas/customerAssigneeSchema" } }, "tags": { "type": "array", "items": { "$ref": "#/components/schemas/organizationTagSchema" } } }, "required": [ "id", "organizationId", "companyName", "isBusiness", "kvk", "btw", "website", "billingEmail", "billingName", "archivedReason", "archivedAt", "quoteCount", "taskCount", "address", "status", "description", "logoUrl", "createdBy", "updatedBy", "createdAt", "updatedAt", "assignees", "tags" ] }, "customerStatusSchema": { "type": "string", "enum": [ "prospect", "active", "archived" ] }, "invoiceIdSchema": { "type": "string", "format": "uuid" }, "nullableShortText": { "type": [ "string", "null" ], "maxLength": 240 }, "numericReferenceIdSchema": { "type": "string", "pattern": "^[1-9]\\d*$" }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "organizationTagSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/tagIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "kind": { "$ref": "#/components/schemas/tagKindSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 80 }, "slug": { "$ref": "#/components/schemas/tagSlugSchema" }, "color": { "anyOf": [ { "$ref": "#/components/schemas/tagColorSchema" }, { "type": "null" } ] }, "description": { "type": [ "string", "null" ] }, "position": { "type": "integer", "minimum": 0 }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "kind", "name", "slug", "color", "description", "position", "createdAt", "updatedAt" ] }, "productIdSchema": { "type": "string", "format": "uuid" }, "quoteIdSchema": { "type": "string", "format": "uuid" }, "tagColorSchema": { "type": "string", "pattern": "^#(?:[0-9a-f]{3}|[0-9a-f]{6})$" }, "tagIdSchema": { "type": "string", "format": "uuid" }, "tagKindSchema": { "type": "string", "enum": [ "task", "customer", "product" ] }, "tagSlugSchema": { "type": "string", "minLength": 1, "maxLength": 160, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" } } ``` # Update a customer. (/openapi/updateCustomer) Update a customer for the specified organization. ## PATCH /v1/organizations/{organizationId}/customers/{customerId} ```json { "operationId": "updateCustomer", "summary": "Update a customer.", "description": "Update a customer for the specified organization.", "tags": [ "Customers" ], "parameters": [ { "in": "path", "name": "customerId", "required": true, "schema": { "$ref": "#/components/schemas/customerIdSchema" }, "example": "123" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/customerInputSchema" } } }, "description": "The customer payload.", "required": true }, "responses": { "200": { "description": "Returns the updated customer.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/customerSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "customerAddressSchema": { "type": "object", "properties": { "line1": { "$ref": "#/components/schemas/nullableShortText" }, "line2": { "$ref": "#/components/schemas/nullableShortText" }, "city": { "$ref": "#/components/schemas/nullableShortText" }, "state": { "$ref": "#/components/schemas/nullableShortText" }, "postalCode": { "$ref": "#/components/schemas/nullableShortText" }, "country": { "$ref": "#/components/schemas/nullableShortText" } }, "required": [ "line1", "line2", "city", "state", "postalCode", "country" ] }, "customerAssigneeSchema": { "type": "object", "properties": { "userId": { "type": "string", "format": "uuid" }, "displayName": { "type": "string", "minLength": 1 }, "avatarUrl": { "type": [ "string", "null" ], "format": "uri" }, "disabledAt": { "type": [ "string", "null" ] } }, "required": [ "userId", "displayName", "avatarUrl", "disabledAt" ] }, "customerContactInputSchema": { "type": "object", "properties": { "firstName": { "type": [ "string", "null" ], "maxLength": 120 }, "lastName": { "type": [ "string", "null" ], "maxLength": 120 }, "jobTitle": { "type": [ "string", "null" ], "maxLength": 120 }, "isPrimary": { "type": "boolean" }, "email": { "allOf": [ { "$ref": "#/components/schemas/nullableEmailInputSchema" } ] }, "phone": { "allOf": [ { "$ref": "#/components/schemas/nullablePhoneInputSchema" } ] } } }, "customerIdSchema": { "type": "integer", "exclusiveMinimum": 0 }, "customerInputSchema": { "type": "object", "properties": { "isBusiness": { "type": "boolean" }, "companyName": { "type": [ "string", "null" ], "maxLength": 240 }, "kvk": { "allOf": [ { "$ref": "#/components/schemas/nullableKvkNumberInputSchema" } ] }, "btw": { "allOf": [ { "$ref": "#/components/schemas/nullableVatIdInputSchema" } ] }, "website": { "allOf": [ { "$ref": "#/components/schemas/nullableUrlInputSchema" } ] }, "billingEmail": { "allOf": [ { "$ref": "#/components/schemas/nullableEmailInputSchema" } ] }, "billingName": { "type": [ "string", "null" ], "maxLength": 240 }, "address": { "type": "object", "properties": { "line1": { "$ref": "#/components/schemas/nullableShortText" }, "line2": { "$ref": "#/components/schemas/nullableShortText" }, "city": { "$ref": "#/components/schemas/nullableShortText" }, "state": { "$ref": "#/components/schemas/nullableShortText" }, "postalCode": { "$ref": "#/components/schemas/nullableShortText" }, "country": { "$ref": "#/components/schemas/nullableShortText" } } }, "description": { "allOf": [ { "$ref": "#/components/schemas/nullableLongText" } ] }, "assigneeIds": { "type": "array", "items": { "type": "string", "format": "uuid" }, "maxItems": 50 }, "status": { "allOf": [ { "$ref": "#/components/schemas/customerStatusSchema" } ] }, "primaryContact": { "allOf": [ { "$ref": "#/components/schemas/customerContactInputSchema" } ] }, "primaryLocation": { "allOf": [ { "$ref": "#/components/schemas/customerLocationInputSchema" } ] } } }, "customerLocationInputSchema": { "type": "object", "properties": { "name": { "type": [ "string", "null" ], "maxLength": 120 }, "address": { "type": "object", "properties": { "line1": { "$ref": "#/components/schemas/nullableShortText" }, "line2": { "$ref": "#/components/schemas/nullableShortText" }, "city": { "$ref": "#/components/schemas/nullableShortText" }, "state": { "$ref": "#/components/schemas/nullableShortText" }, "postalCode": { "$ref": "#/components/schemas/nullableShortText" }, "country": { "$ref": "#/components/schemas/nullableShortText" } } }, "isPrimary": { "type": "boolean" } } }, "customerSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/customerIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "companyName": { "type": [ "string", "null" ] }, "isBusiness": { "type": "boolean" }, "kvk": { "type": [ "string", "null" ] }, "btw": { "type": [ "string", "null" ] }, "website": { "type": [ "string", "null" ] }, "billingEmail": { "type": [ "string", "null" ] }, "billingName": { "type": [ "string", "null" ] }, "archivedReason": { "type": [ "string", "null" ] }, "archivedAt": { "type": [ "string", "null" ], "format": "date-time" }, "quoteCount": { "type": "integer", "minimum": 0 }, "taskCount": { "type": "integer", "minimum": 0 }, "address": { "$ref": "#/components/schemas/customerAddressSchema" }, "status": { "$ref": "#/components/schemas/customerStatusSchema" }, "description": { "type": [ "string", "null" ] }, "logoUrl": { "type": [ "string", "null" ], "format": "uri" }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "updatedBy": { "type": [ "string", "null" ], "format": "uuid" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" }, "assignees": { "type": "array", "items": { "$ref": "#/components/schemas/customerAssigneeSchema" } }, "tags": { "type": "array", "items": { "$ref": "#/components/schemas/organizationTagSchema" } } }, "required": [ "id", "organizationId", "companyName", "isBusiness", "kvk", "btw", "website", "billingEmail", "billingName", "archivedReason", "archivedAt", "quoteCount", "taskCount", "address", "status", "description", "logoUrl", "createdBy", "updatedBy", "createdAt", "updatedAt", "assignees", "tags" ] }, "customerStatusSchema": { "type": "string", "enum": [ "prospect", "active", "archived" ] }, "nullableEmailInputSchema": { "type": [ "string", "null" ], "format": "email", "maxLength": 320 }, "nullableKvkNumberInputSchema": { "type": [ "string", "null" ], "maxLength": 40 }, "nullableLongText": { "type": [ "string", "null" ], "maxLength": 4000 }, "nullablePhoneInputSchema": { "type": [ "string", "null" ], "maxLength": 20 }, "nullableShortText": { "type": [ "string", "null" ], "maxLength": 240 }, "nullableUrlInputSchema": { "anyOf": [ { "type": "string", "maxLength": 400, "format": "uri" }, { "type": "null" } ] }, "nullableVatIdInputSchema": { "type": [ "string", "null" ], "maxLength": 40 }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "organizationTagSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/tagIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "kind": { "$ref": "#/components/schemas/tagKindSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 80 }, "slug": { "$ref": "#/components/schemas/tagSlugSchema" }, "color": { "anyOf": [ { "$ref": "#/components/schemas/tagColorSchema" }, { "type": "null" } ] }, "description": { "type": [ "string", "null" ] }, "position": { "type": "integer", "minimum": 0 }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "kind", "name", "slug", "color", "description", "position", "createdAt", "updatedAt" ] }, "tagColorSchema": { "type": "string", "pattern": "^#(?:[0-9a-f]{3}|[0-9a-f]{6})$" }, "tagIdSchema": { "type": "string", "format": "uuid" }, "tagKindSchema": { "type": "string", "enum": [ "task", "customer", "product" ] }, "tagSlugSchema": { "type": "string", "minLength": 1, "maxLength": 160, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" } } ``` # Delete a customer. (/openapi/deleteCustomer) Delete a customer for the specified organization. ## DELETE /v1/organizations/{organizationId}/customers/{customerId} ```json { "operationId": "deleteCustomer", "summary": "Delete a customer.", "description": "Delete a customer for the specified organization.", "tags": [ "Customers" ], "parameters": [ { "in": "path", "name": "customerId", "required": true, "schema": { "$ref": "#/components/schemas/customerIdSchema" }, "example": "123" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns a success payload when the customer is deleted.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/publicApiSuccessResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "customerIdSchema": { "type": "integer", "exclusiveMinimum": 0 }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "publicApiSuccessResponseSchema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [ true ] } }, "required": [ "success" ] } } ``` # Add a customer contact. (/openapi/addCustomerContact) Add a linked contact to a customer. ## POST /v1/organizations/{organizationId}/customers/{customerId}/contacts ```json { "operationId": "addCustomerContact", "summary": "Add a customer contact.", "description": "Add a linked contact to a customer.", "tags": [ "Customers" ], "parameters": [ { "in": "path", "name": "customerId", "required": true, "schema": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 }, "example": 123 }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/customerContactInputSchema" } } }, "description": "The customer contact payload." }, "responses": { "201": { "description": "Returns the created customer contact.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/customerContactSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "contactMethodSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "type": { "$ref": "#/components/schemas/contactMethodTypeSchema" }, "label": { "type": [ "string", "null" ], "maxLength": 120 }, "value": { "type": "string", "minLength": 1, "maxLength": 320 }, "isPrimary": { "type": "boolean" }, "position": { "type": "integer", "minimum": 0 }, "verifiedAt": { "type": [ "string", "null" ], "format": "date-time" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "type", "label", "value", "isPrimary", "position", "verifiedAt", "createdAt", "updatedAt" ] }, "contactMethodTypeSchema": { "type": "string", "enum": [ "email", "phone", "whatsapp" ] }, "contactProfileIdSchema": { "type": "string", "format": "uuid" }, "contactProfileSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/contactProfileIdSchema" }, "userId": { "type": [ "string", "null" ], "format": "uuid" }, "firstName": { "type": [ "string", "null" ] }, "lastName": { "type": [ "string", "null" ] }, "displayName": { "type": [ "string", "null" ] }, "avatarUrl": { "type": [ "string", "null" ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" }, "contactMethods": { "type": "array", "items": { "$ref": "#/components/schemas/contactMethodSchema" } } }, "required": [ "id", "userId", "firstName", "lastName", "displayName", "avatarUrl", "createdAt", "updatedAt", "contactMethods" ] }, "customerContactInputSchema": { "type": "object", "properties": { "firstName": { "type": [ "string", "null" ], "maxLength": 120 }, "lastName": { "type": [ "string", "null" ], "maxLength": 120 }, "jobTitle": { "type": [ "string", "null" ], "maxLength": 120 }, "isPrimary": { "type": "boolean" }, "email": { "allOf": [ { "$ref": "#/components/schemas/nullableEmailInputSchema" } ] }, "phone": { "allOf": [ { "$ref": "#/components/schemas/nullablePhoneInputSchema" } ] } } }, "customerContactSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "customerId": { "$ref": "#/components/schemas/customerIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "contactProfile": { "$ref": "#/components/schemas/contactProfileSchema" }, "jobTitle": { "type": [ "string", "null" ] }, "isPrimary": { "type": "boolean" }, "notes": { "type": [ "string", "null" ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "customerId", "organizationId", "contactProfile", "jobTitle", "isPrimary", "notes", "createdAt", "updatedAt" ] }, "customerIdSchema": { "type": "integer", "exclusiveMinimum": 0 }, "nullableEmailInputSchema": { "type": [ "string", "null" ], "format": "email", "maxLength": 320 }, "nullablePhoneInputSchema": { "type": [ "string", "null" ], "maxLength": 20 }, "organizationIdSchema": { "type": "string", "format": "uuid" } } ``` # Add a customer location. (/openapi/addCustomerLocation) Add a service or billing location to a customer. ## POST /v1/organizations/{organizationId}/customers/{customerId}/locations ```json { "operationId": "addCustomerLocation", "summary": "Add a customer location.", "description": "Add a service or billing location to a customer.", "tags": [ "Customers" ], "parameters": [ { "in": "path", "name": "customerId", "required": true, "schema": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 }, "example": 123 }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/customerLocationInputSchema" } } }, "description": "The customer location payload." }, "responses": { "201": { "description": "Returns the created customer location.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/customerLocationSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "customerAddressSchema": { "type": "object", "properties": { "line1": { "$ref": "#/components/schemas/nullableShortText" }, "line2": { "$ref": "#/components/schemas/nullableShortText" }, "city": { "$ref": "#/components/schemas/nullableShortText" }, "state": { "$ref": "#/components/schemas/nullableShortText" }, "postalCode": { "$ref": "#/components/schemas/nullableShortText" }, "country": { "$ref": "#/components/schemas/nullableShortText" } }, "required": [ "line1", "line2", "city", "state", "postalCode", "country" ] }, "customerIdSchema": { "type": "integer", "exclusiveMinimum": 0 }, "customerLocationInputSchema": { "type": "object", "properties": { "name": { "type": [ "string", "null" ], "maxLength": 120 }, "address": { "type": "object", "properties": { "line1": { "$ref": "#/components/schemas/nullableShortText" }, "line2": { "$ref": "#/components/schemas/nullableShortText" }, "city": { "$ref": "#/components/schemas/nullableShortText" }, "state": { "$ref": "#/components/schemas/nullableShortText" }, "postalCode": { "$ref": "#/components/schemas/nullableShortText" }, "country": { "$ref": "#/components/schemas/nullableShortText" } } }, "isPrimary": { "type": "boolean" } } }, "customerLocationSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "customerId": { "$ref": "#/components/schemas/customerIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 120 }, "address": { "$ref": "#/components/schemas/customerAddressSchema" }, "isPrimary": { "type": "boolean" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "customerId", "organizationId", "name", "address", "isPrimary", "createdAt", "updatedAt" ] }, "nullableShortText": { "type": [ "string", "null" ], "maxLength": 240 }, "organizationIdSchema": { "type": "string", "format": "uuid" } } ``` # Add a customer note. (/openapi/addCustomerNote) Add a note to a customer timeline. ## POST /v1/organizations/{organizationId}/customers/{customerId}/notes ```json { "operationId": "addCustomerNote", "summary": "Add a customer note.", "description": "Add a note to a customer timeline.", "tags": [ "Customers" ], "parameters": [ { "in": "path", "name": "customerId", "required": true, "schema": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 }, "example": 123 }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/customerNoteInputSchema" } } }, "description": "The customer note payload." }, "responses": { "201": { "description": "Returns the created customer note.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/customerNoteSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "collaborationBlockSchema": { "type": "object", "discriminator": { "propertyName": "type" }, "oneOf": [ { "type": "object", "properties": { "type": { "type": "string", "enum": [ "paragraph" ] }, "content": { "$ref": "#/components/schemas/collaborationInlineContentSchema" } }, "required": [ "type", "content" ], "additionalProperties": false }, { "type": "object", "properties": { "type": { "type": "string", "enum": [ "bullet-list" ] }, "items": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationInlineContentSchema" }, "minItems": 1, "maxItems": 500 } }, "required": [ "type", "items" ], "additionalProperties": false }, { "type": "object", "properties": { "type": { "type": "string", "enum": [ "numbered-list" ] }, "items": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationInlineContentSchema" }, "minItems": 1, "maxItems": 500 } }, "required": [ "type", "items" ], "additionalProperties": false } ] }, "collaborationCommentAuthorSchema": { "type": "object", "properties": { "userId": { "type": "string", "format": "uuid" }, "displayName": { "type": "string", "minLength": 1 }, "avatarUrl": { "type": [ "string", "null" ], "format": "uri" } }, "required": [ "userId", "displayName", "avatarUrl" ] }, "collaborationComposerInputSchema": { "type": "object", "properties": { "text": { "type": "string", "minLength": 1, "maxLength": 20000 }, "selections": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationComposerSelectionSchema" }, "maxItems": 100 } }, "required": [ "text", "selections" ], "additionalProperties": false }, "collaborationComposerSelectionSchema": { "type": "object", "properties": { "start": { "type": "integer", "minimum": 0, "maximum": 20000 }, "end": { "type": "integer", "exclusiveMinimum": 0, "maximum": 20000 }, "reference": { "$ref": "#/components/schemas/collaborationReferenceIdSchema" } }, "required": [ "start", "end", "reference" ], "additionalProperties": false }, "collaborationDocumentSchema": { "type": "object", "properties": { "version": { "type": "integer", "enum": [ 1 ] }, "blocks": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationBlockSchema" }, "minItems": 1, "maxItems": 1000 } }, "required": [ "version", "blocks" ], "additionalProperties": false }, "collaborationInlineContentSchema": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationInlineSchema" }, "minItems": 1, "maxItems": 1000 }, "collaborationInlineSchema": { "type": "object", "discriminator": { "propertyName": "type" }, "oneOf": [ { "type": "object", "properties": { "type": { "type": "string", "enum": [ "text" ] }, "text": { "type": "string", "minLength": 1, "maxLength": 20000 } }, "required": [ "type", "text" ], "additionalProperties": false }, { "type": "object", "properties": { "type": { "type": "string", "enum": [ "reference" ] }, "reference": { "$ref": "#/components/schemas/collaborationReferenceSchema" } }, "required": [ "type", "reference" ], "additionalProperties": false } ] }, "collaborationReferenceIdSchema": { "type": "object", "discriminator": { "propertyName": "kind" }, "oneOf": [ { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "user" ] }, "id": { "type": "string", "format": "uuid" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "team" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "task" ] }, "id": { "type": "string", "format": "uuid" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "customer" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "quote" ] }, "id": { "$ref": "#/components/schemas/quoteIdSchema" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "invoice" ] }, "id": { "$ref": "#/components/schemas/invoiceIdSchema" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "product" ] }, "id": { "$ref": "#/components/schemas/productIdSchema" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "customer-asset" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" } }, "required": [ "kind", "id" ], "additionalProperties": false } ] }, "collaborationReferenceSchema": { "type": "object", "discriminator": { "propertyName": "kind" }, "oneOf": [ { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "user" ] }, "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "team" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "task" ] }, "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "customer" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "quote" ] }, "id": { "$ref": "#/components/schemas/quoteIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "invoice" ] }, "id": { "$ref": "#/components/schemas/invoiceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "product" ] }, "id": { "$ref": "#/components/schemas/productIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "customer-asset" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false } ] }, "customerIdSchema": { "type": "integer", "exclusiveMinimum": 0 }, "customerNoteInputSchema": { "$ref": "#/components/schemas/collaborationComposerInputSchema" }, "customerNoteSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "customerId": { "$ref": "#/components/schemas/customerIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "contentText": { "type": "string" }, "contentDoc": { "$ref": "#/components/schemas/collaborationDocumentSchema" }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "author": { "anyOf": [ { "$ref": "#/components/schemas/collaborationCommentAuthorSchema" }, { "type": "null" } ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "customerId", "organizationId", "contentText", "contentDoc", "createdBy", "author", "createdAt", "updatedAt" ] }, "invoiceIdSchema": { "type": "string", "format": "uuid" }, "numericReferenceIdSchema": { "type": "string", "pattern": "^[1-9]\\d*$" }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "productIdSchema": { "type": "string", "format": "uuid" }, "quoteIdSchema": { "type": "string", "format": "uuid" } } ``` # Get a customer contact. (/openapi/getCustomerContact) Return a linked contact for a customer. ## GET /v1/organizations/{organizationId}/customers/{customerId}/contacts/{contactId} ```json { "operationId": "getCustomerContact", "summary": "Get a customer contact.", "description": "Return a linked contact for a customer.", "tags": [ "Customers" ], "parameters": [ { "in": "path", "name": "customerId", "required": true, "schema": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 }, "example": 123 }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "contactId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns the requested customer contact.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/customerContactSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "contactMethodSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "type": { "$ref": "#/components/schemas/contactMethodTypeSchema" }, "label": { "type": [ "string", "null" ], "maxLength": 120 }, "value": { "type": "string", "minLength": 1, "maxLength": 320 }, "isPrimary": { "type": "boolean" }, "position": { "type": "integer", "minimum": 0 }, "verifiedAt": { "type": [ "string", "null" ], "format": "date-time" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "type", "label", "value", "isPrimary", "position", "verifiedAt", "createdAt", "updatedAt" ] }, "contactMethodTypeSchema": { "type": "string", "enum": [ "email", "phone", "whatsapp" ] }, "contactProfileIdSchema": { "type": "string", "format": "uuid" }, "contactProfileSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/contactProfileIdSchema" }, "userId": { "type": [ "string", "null" ], "format": "uuid" }, "firstName": { "type": [ "string", "null" ] }, "lastName": { "type": [ "string", "null" ] }, "displayName": { "type": [ "string", "null" ] }, "avatarUrl": { "type": [ "string", "null" ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" }, "contactMethods": { "type": "array", "items": { "$ref": "#/components/schemas/contactMethodSchema" } } }, "required": [ "id", "userId", "firstName", "lastName", "displayName", "avatarUrl", "createdAt", "updatedAt", "contactMethods" ] }, "customerContactSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "customerId": { "$ref": "#/components/schemas/customerIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "contactProfile": { "$ref": "#/components/schemas/contactProfileSchema" }, "jobTitle": { "type": [ "string", "null" ] }, "isPrimary": { "type": "boolean" }, "notes": { "type": [ "string", "null" ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "customerId", "organizationId", "contactProfile", "jobTitle", "isPrimary", "notes", "createdAt", "updatedAt" ] }, "customerIdSchema": { "type": "integer", "exclusiveMinimum": 0 }, "organizationIdSchema": { "type": "string", "format": "uuid" } } ``` # Update a customer contact. (/openapi/updateCustomerContact) Update a linked contact for a customer. ## PATCH /v1/organizations/{organizationId}/customers/{customerId}/contacts/{contactId} ```json { "operationId": "updateCustomerContact", "summary": "Update a customer contact.", "description": "Update a linked contact for a customer.", "tags": [ "Customers" ], "parameters": [ { "in": "path", "name": "customerId", "required": true, "schema": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 }, "example": 123 }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "contactId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/customerContactInputSchema" } } }, "description": "The customer contact payload." }, "responses": { "200": { "description": "Returns the updated customer contact.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/customerContactSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "contactMethodSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "type": { "$ref": "#/components/schemas/contactMethodTypeSchema" }, "label": { "type": [ "string", "null" ], "maxLength": 120 }, "value": { "type": "string", "minLength": 1, "maxLength": 320 }, "isPrimary": { "type": "boolean" }, "position": { "type": "integer", "minimum": 0 }, "verifiedAt": { "type": [ "string", "null" ], "format": "date-time" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "type", "label", "value", "isPrimary", "position", "verifiedAt", "createdAt", "updatedAt" ] }, "contactMethodTypeSchema": { "type": "string", "enum": [ "email", "phone", "whatsapp" ] }, "contactProfileIdSchema": { "type": "string", "format": "uuid" }, "contactProfileSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/contactProfileIdSchema" }, "userId": { "type": [ "string", "null" ], "format": "uuid" }, "firstName": { "type": [ "string", "null" ] }, "lastName": { "type": [ "string", "null" ] }, "displayName": { "type": [ "string", "null" ] }, "avatarUrl": { "type": [ "string", "null" ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" }, "contactMethods": { "type": "array", "items": { "$ref": "#/components/schemas/contactMethodSchema" } } }, "required": [ "id", "userId", "firstName", "lastName", "displayName", "avatarUrl", "createdAt", "updatedAt", "contactMethods" ] }, "customerContactInputSchema": { "type": "object", "properties": { "firstName": { "type": [ "string", "null" ], "maxLength": 120 }, "lastName": { "type": [ "string", "null" ], "maxLength": 120 }, "jobTitle": { "type": [ "string", "null" ], "maxLength": 120 }, "isPrimary": { "type": "boolean" }, "email": { "allOf": [ { "$ref": "#/components/schemas/nullableEmailInputSchema" } ] }, "phone": { "allOf": [ { "$ref": "#/components/schemas/nullablePhoneInputSchema" } ] } } }, "customerContactSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "customerId": { "$ref": "#/components/schemas/customerIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "contactProfile": { "$ref": "#/components/schemas/contactProfileSchema" }, "jobTitle": { "type": [ "string", "null" ] }, "isPrimary": { "type": "boolean" }, "notes": { "type": [ "string", "null" ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "customerId", "organizationId", "contactProfile", "jobTitle", "isPrimary", "notes", "createdAt", "updatedAt" ] }, "customerIdSchema": { "type": "integer", "exclusiveMinimum": 0 }, "nullableEmailInputSchema": { "type": [ "string", "null" ], "format": "email", "maxLength": 320 }, "nullablePhoneInputSchema": { "type": [ "string", "null" ], "maxLength": 20 }, "organizationIdSchema": { "type": "string", "format": "uuid" } } ``` # Remove a customer contact. (/openapi/removeCustomerContact) Remove a linked contact from a customer. ## DELETE /v1/organizations/{organizationId}/customers/{customerId}/contacts/{contactId} ```json { "operationId": "removeCustomerContact", "summary": "Remove a customer contact.", "description": "Remove a linked contact from a customer.", "tags": [ "Customers" ], "parameters": [ { "in": "path", "name": "customerId", "required": true, "schema": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 }, "example": 123 }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "contactId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns a success payload when the contact is removed.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/publicApiSuccessResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "organizationIdSchema": { "type": "string", "format": "uuid" }, "publicApiSuccessResponseSchema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [ true ] } }, "required": [ "success" ] } } ``` # Get a customer location. (/openapi/getCustomerLocation) Return a customer location. ## GET /v1/organizations/{organizationId}/customers/{customerId}/locations/{locationId} ```json { "operationId": "getCustomerLocation", "summary": "Get a customer location.", "description": "Return a customer location.", "tags": [ "Customers" ], "parameters": [ { "in": "path", "name": "customerId", "required": true, "schema": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 }, "example": 123 }, { "in": "path", "name": "locationId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns the requested customer location.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/customerLocationSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "customerAddressSchema": { "type": "object", "properties": { "line1": { "$ref": "#/components/schemas/nullableShortText" }, "line2": { "$ref": "#/components/schemas/nullableShortText" }, "city": { "$ref": "#/components/schemas/nullableShortText" }, "state": { "$ref": "#/components/schemas/nullableShortText" }, "postalCode": { "$ref": "#/components/schemas/nullableShortText" }, "country": { "$ref": "#/components/schemas/nullableShortText" } }, "required": [ "line1", "line2", "city", "state", "postalCode", "country" ] }, "customerIdSchema": { "type": "integer", "exclusiveMinimum": 0 }, "customerLocationSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "customerId": { "$ref": "#/components/schemas/customerIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 120 }, "address": { "$ref": "#/components/schemas/customerAddressSchema" }, "isPrimary": { "type": "boolean" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "customerId", "organizationId", "name", "address", "isPrimary", "createdAt", "updatedAt" ] }, "nullableShortText": { "type": [ "string", "null" ], "maxLength": 240 }, "organizationIdSchema": { "type": "string", "format": "uuid" } } ``` # Update a customer location. (/openapi/updateCustomerLocation) Update a customer location. ## PATCH /v1/organizations/{organizationId}/customers/{customerId}/locations/{locationId} ```json { "operationId": "updateCustomerLocation", "summary": "Update a customer location.", "description": "Update a customer location.", "tags": [ "Customers" ], "parameters": [ { "in": "path", "name": "customerId", "required": true, "schema": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 }, "example": 123 }, { "in": "path", "name": "locationId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/customerLocationInputSchema" } } }, "description": "The customer location payload." }, "responses": { "200": { "description": "Returns the updated customer location.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/customerLocationSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "customerAddressSchema": { "type": "object", "properties": { "line1": { "$ref": "#/components/schemas/nullableShortText" }, "line2": { "$ref": "#/components/schemas/nullableShortText" }, "city": { "$ref": "#/components/schemas/nullableShortText" }, "state": { "$ref": "#/components/schemas/nullableShortText" }, "postalCode": { "$ref": "#/components/schemas/nullableShortText" }, "country": { "$ref": "#/components/schemas/nullableShortText" } }, "required": [ "line1", "line2", "city", "state", "postalCode", "country" ] }, "customerIdSchema": { "type": "integer", "exclusiveMinimum": 0 }, "customerLocationInputSchema": { "type": "object", "properties": { "name": { "type": [ "string", "null" ], "maxLength": 120 }, "address": { "type": "object", "properties": { "line1": { "$ref": "#/components/schemas/nullableShortText" }, "line2": { "$ref": "#/components/schemas/nullableShortText" }, "city": { "$ref": "#/components/schemas/nullableShortText" }, "state": { "$ref": "#/components/schemas/nullableShortText" }, "postalCode": { "$ref": "#/components/schemas/nullableShortText" }, "country": { "$ref": "#/components/schemas/nullableShortText" } } }, "isPrimary": { "type": "boolean" } } }, "customerLocationSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "customerId": { "$ref": "#/components/schemas/customerIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 120 }, "address": { "$ref": "#/components/schemas/customerAddressSchema" }, "isPrimary": { "type": "boolean" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "customerId", "organizationId", "name", "address", "isPrimary", "createdAt", "updatedAt" ] }, "nullableShortText": { "type": [ "string", "null" ], "maxLength": 240 }, "organizationIdSchema": { "type": "string", "format": "uuid" } } ``` # Remove a customer location. (/openapi/removeCustomerLocation) Remove a customer location. ## DELETE /v1/organizations/{organizationId}/customers/{customerId}/locations/{locationId} ```json { "operationId": "removeCustomerLocation", "summary": "Remove a customer location.", "description": "Remove a customer location.", "tags": [ "Customers" ], "parameters": [ { "in": "path", "name": "customerId", "required": true, "schema": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 }, "example": 123 }, { "in": "path", "name": "locationId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns a success payload when the location is removed.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/publicApiSuccessResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "organizationIdSchema": { "type": "string", "format": "uuid" }, "publicApiSuccessResponseSchema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [ true ] } }, "required": [ "success" ] } } ``` # Update a customer note. (/openapi/updateCustomerNote) Update a note on a customer timeline. ## PATCH /v1/organizations/{organizationId}/customers/{customerId}/notes/{noteId} ```json { "operationId": "updateCustomerNote", "summary": "Update a customer note.", "description": "Update a note on a customer timeline.", "tags": [ "Customers" ], "parameters": [ { "in": "path", "name": "customerId", "required": true, "schema": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 }, "example": 123 }, { "in": "path", "name": "noteId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/customerNoteInputSchema" } } }, "description": "The customer note payload." }, "responses": { "200": { "description": "Returns the updated customer note.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/customerNoteSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "collaborationBlockSchema": { "type": "object", "discriminator": { "propertyName": "type" }, "oneOf": [ { "type": "object", "properties": { "type": { "type": "string", "enum": [ "paragraph" ] }, "content": { "$ref": "#/components/schemas/collaborationInlineContentSchema" } }, "required": [ "type", "content" ], "additionalProperties": false }, { "type": "object", "properties": { "type": { "type": "string", "enum": [ "bullet-list" ] }, "items": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationInlineContentSchema" }, "minItems": 1, "maxItems": 500 } }, "required": [ "type", "items" ], "additionalProperties": false }, { "type": "object", "properties": { "type": { "type": "string", "enum": [ "numbered-list" ] }, "items": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationInlineContentSchema" }, "minItems": 1, "maxItems": 500 } }, "required": [ "type", "items" ], "additionalProperties": false } ] }, "collaborationCommentAuthorSchema": { "type": "object", "properties": { "userId": { "type": "string", "format": "uuid" }, "displayName": { "type": "string", "minLength": 1 }, "avatarUrl": { "type": [ "string", "null" ], "format": "uri" } }, "required": [ "userId", "displayName", "avatarUrl" ] }, "collaborationComposerInputSchema": { "type": "object", "properties": { "text": { "type": "string", "minLength": 1, "maxLength": 20000 }, "selections": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationComposerSelectionSchema" }, "maxItems": 100 } }, "required": [ "text", "selections" ], "additionalProperties": false }, "collaborationComposerSelectionSchema": { "type": "object", "properties": { "start": { "type": "integer", "minimum": 0, "maximum": 20000 }, "end": { "type": "integer", "exclusiveMinimum": 0, "maximum": 20000 }, "reference": { "$ref": "#/components/schemas/collaborationReferenceIdSchema" } }, "required": [ "start", "end", "reference" ], "additionalProperties": false }, "collaborationDocumentSchema": { "type": "object", "properties": { "version": { "type": "integer", "enum": [ 1 ] }, "blocks": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationBlockSchema" }, "minItems": 1, "maxItems": 1000 } }, "required": [ "version", "blocks" ], "additionalProperties": false }, "collaborationInlineContentSchema": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationInlineSchema" }, "minItems": 1, "maxItems": 1000 }, "collaborationInlineSchema": { "type": "object", "discriminator": { "propertyName": "type" }, "oneOf": [ { "type": "object", "properties": { "type": { "type": "string", "enum": [ "text" ] }, "text": { "type": "string", "minLength": 1, "maxLength": 20000 } }, "required": [ "type", "text" ], "additionalProperties": false }, { "type": "object", "properties": { "type": { "type": "string", "enum": [ "reference" ] }, "reference": { "$ref": "#/components/schemas/collaborationReferenceSchema" } }, "required": [ "type", "reference" ], "additionalProperties": false } ] }, "collaborationReferenceIdSchema": { "type": "object", "discriminator": { "propertyName": "kind" }, "oneOf": [ { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "user" ] }, "id": { "type": "string", "format": "uuid" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "team" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "task" ] }, "id": { "type": "string", "format": "uuid" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "customer" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "quote" ] }, "id": { "$ref": "#/components/schemas/quoteIdSchema" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "invoice" ] }, "id": { "$ref": "#/components/schemas/invoiceIdSchema" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "product" ] }, "id": { "$ref": "#/components/schemas/productIdSchema" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "customer-asset" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" } }, "required": [ "kind", "id" ], "additionalProperties": false } ] }, "collaborationReferenceSchema": { "type": "object", "discriminator": { "propertyName": "kind" }, "oneOf": [ { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "user" ] }, "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "team" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "task" ] }, "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "customer" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "quote" ] }, "id": { "$ref": "#/components/schemas/quoteIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "invoice" ] }, "id": { "$ref": "#/components/schemas/invoiceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "product" ] }, "id": { "$ref": "#/components/schemas/productIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "customer-asset" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false } ] }, "customerIdSchema": { "type": "integer", "exclusiveMinimum": 0 }, "customerNoteInputSchema": { "$ref": "#/components/schemas/collaborationComposerInputSchema" }, "customerNoteSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "customerId": { "$ref": "#/components/schemas/customerIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "contentText": { "type": "string" }, "contentDoc": { "$ref": "#/components/schemas/collaborationDocumentSchema" }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "author": { "anyOf": [ { "$ref": "#/components/schemas/collaborationCommentAuthorSchema" }, { "type": "null" } ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "customerId", "organizationId", "contentText", "contentDoc", "createdBy", "author", "createdAt", "updatedAt" ] }, "invoiceIdSchema": { "type": "string", "format": "uuid" }, "numericReferenceIdSchema": { "type": "string", "pattern": "^[1-9]\\d*$" }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "productIdSchema": { "type": "string", "format": "uuid" }, "quoteIdSchema": { "type": "string", "format": "uuid" } } ``` # Delete a customer note. (/openapi/deleteCustomerNote) Delete a note from a customer timeline. ## DELETE /v1/organizations/{organizationId}/customers/{customerId}/notes/{noteId} ```json { "operationId": "deleteCustomerNote", "summary": "Delete a customer note.", "description": "Delete a note from a customer timeline.", "tags": [ "Customers" ], "parameters": [ { "in": "path", "name": "customerId", "required": true, "schema": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 }, "example": 123 }, { "in": "path", "name": "noteId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns a success payload when the note is deleted.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/publicApiSuccessResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "organizationIdSchema": { "type": "string", "format": "uuid" }, "publicApiSuccessResponseSchema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [ true ] } }, "required": [ "success" ] } } ``` # Set primary customer contact. (/openapi/setPrimaryCustomerContact) Mark a linked contact as the customer primary contact. ## POST /v1/organizations/{organizationId}/customers/{customerId}/contacts/{contactId}/primary ```json { "operationId": "setPrimaryCustomerContact", "summary": "Set primary customer contact.", "description": "Mark a linked contact as the customer primary contact.", "tags": [ "Customers" ], "parameters": [ { "in": "path", "name": "customerId", "required": true, "schema": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 }, "example": 123 }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "contactId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns the primary customer contact.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/customerContactSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "contactMethodSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "type": { "$ref": "#/components/schemas/contactMethodTypeSchema" }, "label": { "type": [ "string", "null" ], "maxLength": 120 }, "value": { "type": "string", "minLength": 1, "maxLength": 320 }, "isPrimary": { "type": "boolean" }, "position": { "type": "integer", "minimum": 0 }, "verifiedAt": { "type": [ "string", "null" ], "format": "date-time" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "type", "label", "value", "isPrimary", "position", "verifiedAt", "createdAt", "updatedAt" ] }, "contactMethodTypeSchema": { "type": "string", "enum": [ "email", "phone", "whatsapp" ] }, "contactProfileIdSchema": { "type": "string", "format": "uuid" }, "contactProfileSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/contactProfileIdSchema" }, "userId": { "type": [ "string", "null" ], "format": "uuid" }, "firstName": { "type": [ "string", "null" ] }, "lastName": { "type": [ "string", "null" ] }, "displayName": { "type": [ "string", "null" ] }, "avatarUrl": { "type": [ "string", "null" ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" }, "contactMethods": { "type": "array", "items": { "$ref": "#/components/schemas/contactMethodSchema" } } }, "required": [ "id", "userId", "firstName", "lastName", "displayName", "avatarUrl", "createdAt", "updatedAt", "contactMethods" ] }, "customerContactSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "customerId": { "$ref": "#/components/schemas/customerIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "contactProfile": { "$ref": "#/components/schemas/contactProfileSchema" }, "jobTitle": { "type": [ "string", "null" ] }, "isPrimary": { "type": "boolean" }, "notes": { "type": [ "string", "null" ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "customerId", "organizationId", "contactProfile", "jobTitle", "isPrimary", "notes", "createdAt", "updatedAt" ] }, "customerIdSchema": { "type": "integer", "exclusiveMinimum": 0 }, "organizationIdSchema": { "type": "string", "format": "uuid" } } ``` # Set primary customer location. (/openapi/setPrimaryCustomerLocation) Mark a location as the customer's primary location. ## POST /v1/organizations/{organizationId}/customers/{customerId}/locations/{locationId}/primary ```json { "operationId": "setPrimaryCustomerLocation", "summary": "Set primary customer location.", "description": "Mark a location as the customer's primary location.", "tags": [ "Customers" ], "parameters": [ { "in": "path", "name": "customerId", "required": true, "schema": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 }, "example": 123 }, { "in": "path", "name": "locationId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns the primary customer location.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/customerLocationSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "customerAddressSchema": { "type": "object", "properties": { "line1": { "$ref": "#/components/schemas/nullableShortText" }, "line2": { "$ref": "#/components/schemas/nullableShortText" }, "city": { "$ref": "#/components/schemas/nullableShortText" }, "state": { "$ref": "#/components/schemas/nullableShortText" }, "postalCode": { "$ref": "#/components/schemas/nullableShortText" }, "country": { "$ref": "#/components/schemas/nullableShortText" } }, "required": [ "line1", "line2", "city", "state", "postalCode", "country" ] }, "customerIdSchema": { "type": "integer", "exclusiveMinimum": 0 }, "customerLocationSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "customerId": { "$ref": "#/components/schemas/customerIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 120 }, "address": { "$ref": "#/components/schemas/customerAddressSchema" }, "isPrimary": { "type": "boolean" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "customerId", "organizationId", "name", "address", "isPrimary", "createdAt", "updatedAt" ] }, "nullableShortText": { "type": [ "string", "null" ], "maxLength": 240 }, "organizationIdSchema": { "type": "string", "format": "uuid" } } ``` # List expense claims. (/openapi/listExpenses) Return expense claims for a scope: the caller's own, everything, awaiting the caller's approval, or ready to reimburse. ## GET /v1/organizations/{organizationId}/expenses ```json { "operationId": "listExpenses", "summary": "List expense claims.", "description": "Return expense claims for a scope: the caller's own, everything, awaiting the caller's approval, or ready to reimburse.", "tags": [ "Expenses" ], "parameters": [ { "in": "query", "name": "scope", "required": true, "schema": { "type": "string", "enum": [ "mine", "all", "to-approve", "to-reimburse" ], "default": "mine" }, "example": "mine" }, { "in": "query", "name": "status", "required": false, "schema": { "allOf": [ { "$ref": "#/components/schemas/expenseStatusSchema" } ] }, "example": "example" }, { "in": "query", "name": "employeeId", "required": false, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "query", "name": "from", "required": false, "schema": { "type": "string", "format": "date" }, "example": "example" }, { "in": "query", "name": "to", "required": false, "schema": { "type": "string", "format": "date" }, "example": "example" }, { "in": "query", "name": "q", "required": false, "schema": { "type": "string", "maxLength": 200 }, "example": "example" }, { "in": "query", "name": "offset", "required": true, "schema": { "default": 0, "type": "integer", "minimum": 0, "maximum": 9007199254740991 }, "example": 1 }, { "in": "query", "name": "limit", "required": true, "schema": { "default": 50, "type": "integer", "minimum": 1, "maximum": 100 }, "example": 1 }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns expense claims.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/expensesResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "customerIdSchema": { "type": "integer", "exclusiveMinimum": 0 }, "expenseFlagKindSchema": { "type": "string", "enum": [ "limit_exceeded", "receipt_missing", "duplicate", "per_diem_overlap" ] }, "expenseFlagSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "kind": { "$ref": "#/components/schemas/expenseFlagKindSchema" }, "message": { "type": [ "string", "null" ] }, "relatedExpenseId": { "type": [ "string", "null" ], "format": "uuid" }, "resolvedAt": { "type": [ "string", "null" ] } }, "required": [ "id", "kind", "message", "relatedExpenseId", "resolvedAt" ] }, "expenseKindSchema": { "type": "string", "enum": [ "company", "claim" ] }, "expensePaymentMethodSchema": { "type": "string", "enum": [ "company_card", "out_of_pocket", "mileage", "per_diem" ] }, "expensePersonSchema": { "type": "object", "properties": { "employeeId": { "type": "string", "format": "uuid" }, "userId": { "type": [ "string", "null" ], "format": "uuid" }, "displayName": { "type": "string" } }, "required": [ "employeeId", "userId", "displayName" ] }, "expenseReceiptStatusSchema": { "type": "string", "enum": [ "attached", "missing", "not_required" ] }, "expenseSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "kind": { "$ref": "#/components/schemas/expenseKindSchema" }, "employee": { "anyOf": [ { "$ref": "#/components/schemas/expensePersonSchema" }, { "type": "null" } ] }, "categoryId": { "type": [ "string", "null" ], "format": "uuid" }, "categoryName": { "type": [ "string", "null" ] }, "tripId": { "type": [ "string", "null" ], "format": "uuid" }, "tripName": { "type": [ "string", "null" ] }, "merchant": { "type": [ "string", "null" ] }, "description": { "type": [ "string", "null" ] }, "spentOn": { "$ref": "#/components/schemas/isoDateSchema" }, "amount": { "type": "number" }, "currency": { "type": "string" }, "exchangeRate": { "type": "number" }, "amountBase": { "type": "number" }, "vatAmount": { "type": [ "number", "null" ] }, "vatRate": { "type": [ "number", "null" ] }, "paymentMethod": { "$ref": "#/components/schemas/expensePaymentMethodSchema" }, "distanceKm": { "type": [ "number", "null" ] }, "mileageRate": { "type": [ "number", "null" ] }, "routeFrom": { "type": [ "string", "null" ] }, "routeTo": { "type": [ "string", "null" ] }, "roundTrip": { "type": "boolean" }, "perDiemDays": { "type": [ "number", "null" ] }, "customerId": { "anyOf": [ { "$ref": "#/components/schemas/customerIdSchema" }, { "type": "null" } ] }, "customerName": { "type": [ "string", "null" ] }, "taskId": { "type": [ "string", "null" ], "format": "uuid" }, "taskTitle": { "type": [ "string", "null" ] }, "billable": { "type": "boolean" }, "invoiceId": { "type": [ "string", "null" ], "format": "uuid" }, "status": { "$ref": "#/components/schemas/expenseStatusSchema" }, "receiptStatus": { "$ref": "#/components/schemas/expenseReceiptStatusSchema" }, "receiptFileName": { "type": [ "string", "null" ] }, "source": { "$ref": "#/components/schemas/expenseSourceSchema" }, "extractionConfidence": { "type": [ "number", "null" ] }, "submittedAt": { "type": [ "string", "null" ] }, "decidedAt": { "type": [ "string", "null" ] }, "decisionNote": { "type": [ "string", "null" ] }, "reimbursedAt": { "type": [ "string", "null" ] }, "reimbursementReference": { "type": [ "string", "null" ] }, "flags": { "type": "array", "items": { "$ref": "#/components/schemas/expenseFlagSchema" } }, "canEdit": { "type": "boolean" }, "canDecide": { "type": "boolean" }, "createdAt": { "type": "string" } }, "required": [ "id", "kind", "employee", "categoryId", "categoryName", "tripId", "tripName", "merchant", "description", "spentOn", "amount", "currency", "exchangeRate", "amountBase", "vatAmount", "vatRate", "paymentMethod", "distanceKm", "mileageRate", "routeFrom", "routeTo", "roundTrip", "perDiemDays", "customerId", "customerName", "taskId", "taskTitle", "billable", "invoiceId", "status", "receiptStatus", "receiptFileName", "source", "extractionConfidence", "submittedAt", "decidedAt", "decisionNote", "reimbursedAt", "reimbursementReference", "flags", "canEdit", "canDecide", "createdAt" ] }, "expenseSourceSchema": { "type": "string", "enum": [ "manual", "email", "photo", "card_feed" ] }, "expenseStatusSchema": { "type": "string", "enum": [ "draft", "submitted", "approved", "rejected", "reimbursed" ] }, "expensesResponseSchema": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/expenseSchema" } }, "pagination": { "$ref": "#/components/schemas/listPaginationSchema" } }, "required": [ "data", "pagination" ] }, "isoDateSchema": { "type": "string", "format": "date" }, "listPaginationSchema": { "type": "object", "properties": { "offset": { "type": "integer", "minimum": 0 }, "limit": { "type": "integer", "minimum": 1, "maximum": 100 }, "total": { "type": "integer", "minimum": 0 }, "hasMore": { "type": "boolean" }, "totalIsEstimate": { "type": "boolean" } }, "required": [ "offset", "limit", "total", "hasMore" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" } } ``` # Create an expense claim. (/openapi/createExpense) Create a draft claim, mileage entry, or per diem. Upload receipts in the app; this endpoint accepts JSON only. ## POST /v1/organizations/{organizationId}/expenses ```json { "operationId": "createExpense", "summary": "Create an expense claim.", "description": "Create a draft claim, mileage entry, or per diem. Upload receipts in the app; this endpoint accepts JSON only.", "tags": [ "Expenses" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/expenseInputSchema" } } }, "description": "The expense claim." }, "responses": { "201": { "description": "Returns the created claim.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/expenseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "customerIdSchema": { "type": "integer", "exclusiveMinimum": 0 }, "expenseFlagKindSchema": { "type": "string", "enum": [ "limit_exceeded", "receipt_missing", "duplicate", "per_diem_overlap" ] }, "expenseFlagSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "kind": { "$ref": "#/components/schemas/expenseFlagKindSchema" }, "message": { "type": [ "string", "null" ] }, "relatedExpenseId": { "type": [ "string", "null" ], "format": "uuid" }, "resolvedAt": { "type": [ "string", "null" ] } }, "required": [ "id", "kind", "message", "relatedExpenseId", "resolvedAt" ] }, "expenseInputSchema": { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "company", "claim" ], "default": "claim" }, "employeeId": { "type": [ "string", "null" ], "format": "uuid" }, "categoryId": { "type": [ "string", "null" ], "format": "uuid" }, "tripId": { "type": [ "string", "null" ], "format": "uuid" }, "merchant": { "type": [ "string", "null" ], "maxLength": 200 }, "description": { "type": [ "string", "null" ], "maxLength": 2000 }, "spentOn": { "$ref": "#/components/schemas/isoDateSchema" }, "amount": { "type": "number", "minimum": 0, "maximum": 999999999, "default": 0 }, "currency": { "type": "string", "pattern": "^[A-Z]{3}$", "default": "EUR" }, "exchangeRate": { "type": "number", "maximum": 1000000, "default": 1 }, "vatAmount": { "type": [ "number", "null" ], "minimum": 0 }, "vatRate": { "type": [ "number", "null" ], "minimum": 0, "maximum": 100 }, "paymentMethod": { "type": "string", "enum": [ "company_card", "out_of_pocket", "mileage", "per_diem" ], "default": "out_of_pocket" }, "distanceKm": { "type": [ "number", "null" ], "maximum": 20000 }, "routeFrom": { "type": [ "string", "null" ], "maxLength": 300 }, "routeTo": { "type": [ "string", "null" ], "maxLength": 300 }, "roundTrip": { "type": "boolean", "default": false }, "perDiemDays": { "type": [ "number", "null" ], "maximum": 366 }, "customerId": { "anyOf": [ { "$ref": "#/components/schemas/customerIdSchema" }, { "type": "null" } ] }, "taskId": { "type": [ "string", "null" ], "format": "uuid" }, "billable": { "type": "boolean", "default": false }, "source": { "type": "string", "enum": [ "manual", "email", "photo", "card_feed" ], "default": "manual" }, "extractionConfidence": { "type": [ "number", "null" ], "minimum": 0, "maximum": 1 } }, "required": [ "kind", "merchant", "description", "spentOn", "amount", "currency", "exchangeRate", "paymentMethod", "routeFrom", "routeTo", "roundTrip", "billable", "source" ] }, "expenseKindSchema": { "type": "string", "enum": [ "company", "claim" ] }, "expensePaymentMethodSchema": { "type": "string", "enum": [ "company_card", "out_of_pocket", "mileage", "per_diem" ] }, "expensePersonSchema": { "type": "object", "properties": { "employeeId": { "type": "string", "format": "uuid" }, "userId": { "type": [ "string", "null" ], "format": "uuid" }, "displayName": { "type": "string" } }, "required": [ "employeeId", "userId", "displayName" ] }, "expenseReceiptStatusSchema": { "type": "string", "enum": [ "attached", "missing", "not_required" ] }, "expenseSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "kind": { "$ref": "#/components/schemas/expenseKindSchema" }, "employee": { "anyOf": [ { "$ref": "#/components/schemas/expensePersonSchema" }, { "type": "null" } ] }, "categoryId": { "type": [ "string", "null" ], "format": "uuid" }, "categoryName": { "type": [ "string", "null" ] }, "tripId": { "type": [ "string", "null" ], "format": "uuid" }, "tripName": { "type": [ "string", "null" ] }, "merchant": { "type": [ "string", "null" ] }, "description": { "type": [ "string", "null" ] }, "spentOn": { "$ref": "#/components/schemas/isoDateSchema" }, "amount": { "type": "number" }, "currency": { "type": "string" }, "exchangeRate": { "type": "number" }, "amountBase": { "type": "number" }, "vatAmount": { "type": [ "number", "null" ] }, "vatRate": { "type": [ "number", "null" ] }, "paymentMethod": { "$ref": "#/components/schemas/expensePaymentMethodSchema" }, "distanceKm": { "type": [ "number", "null" ] }, "mileageRate": { "type": [ "number", "null" ] }, "routeFrom": { "type": [ "string", "null" ] }, "routeTo": { "type": [ "string", "null" ] }, "roundTrip": { "type": "boolean" }, "perDiemDays": { "type": [ "number", "null" ] }, "customerId": { "anyOf": [ { "$ref": "#/components/schemas/customerIdSchema" }, { "type": "null" } ] }, "customerName": { "type": [ "string", "null" ] }, "taskId": { "type": [ "string", "null" ], "format": "uuid" }, "taskTitle": { "type": [ "string", "null" ] }, "billable": { "type": "boolean" }, "invoiceId": { "type": [ "string", "null" ], "format": "uuid" }, "status": { "$ref": "#/components/schemas/expenseStatusSchema" }, "receiptStatus": { "$ref": "#/components/schemas/expenseReceiptStatusSchema" }, "receiptFileName": { "type": [ "string", "null" ] }, "source": { "$ref": "#/components/schemas/expenseSourceSchema" }, "extractionConfidence": { "type": [ "number", "null" ] }, "submittedAt": { "type": [ "string", "null" ] }, "decidedAt": { "type": [ "string", "null" ] }, "decisionNote": { "type": [ "string", "null" ] }, "reimbursedAt": { "type": [ "string", "null" ] }, "reimbursementReference": { "type": [ "string", "null" ] }, "flags": { "type": "array", "items": { "$ref": "#/components/schemas/expenseFlagSchema" } }, "canEdit": { "type": "boolean" }, "canDecide": { "type": "boolean" }, "createdAt": { "type": "string" } }, "required": [ "id", "kind", "employee", "categoryId", "categoryName", "tripId", "tripName", "merchant", "description", "spentOn", "amount", "currency", "exchangeRate", "amountBase", "vatAmount", "vatRate", "paymentMethod", "distanceKm", "mileageRate", "routeFrom", "routeTo", "roundTrip", "perDiemDays", "customerId", "customerName", "taskId", "taskTitle", "billable", "invoiceId", "status", "receiptStatus", "receiptFileName", "source", "extractionConfidence", "submittedAt", "decidedAt", "decisionNote", "reimbursedAt", "reimbursementReference", "flags", "canEdit", "canDecide", "createdAt" ] }, "expenseSourceSchema": { "type": "string", "enum": [ "manual", "email", "photo", "card_feed" ] }, "expenseStatusSchema": { "type": "string", "enum": [ "draft", "submitted", "approved", "rejected", "reimbursed" ] }, "isoDateSchema": { "type": "string", "format": "date" }, "organizationIdSchema": { "type": "string", "format": "uuid" } } ``` # Download a public shared file by token. (/openapi/downloadPublicSharedFile) Redirects a valid bearer-token file link to its signed download URL. ## GET /v1/public/files/{token} ```json { "operationId": "downloadPublicSharedFile", "summary": "Download a public shared file by token.", "description": "Redirects a valid bearer-token file link to its signed download URL.", "tags": [ "Files" ], "parameters": [ { "in": "path", "name": "token", "required": true, "schema": { "type": "string", "minLength": 32 }, "example": "example" } ], "responses": { "302": { "description": "Redirects to the signed file download URL.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/publicFileRedirectResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "publicFileRedirectResponseSchema": { "type": "string", "format": "uri" } } ``` # Copy Files nodes recursively. (/openapi/copyFileNodes) Transactionally clone selected folder trees and resume current-version Storage copies through a durable idempotent operation. ## POST /v1/organizations/{organizationId}/files/copy ```json { "operationId": "copyFileNodes", "summary": "Copy Files nodes recursively.", "description": "Transactionally clone selected folder trees and resume current-version Storage copies through a durable idempotent operation.", "tags": [ "Files" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/fileCopyBodySchema" } } } }, "responses": { "200": { "description": "Returns the durable copy operation and progress.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/fileCopyOperationResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "fileCopyBodySchema": { "type": "object", "properties": { "nodeIds": { "type": "array", "items": { "type": "string", "format": "uuid" }, "minItems": 1, "maxItems": 200 }, "destinationDriveId": { "type": "string", "format": "uuid" }, "destinationParentId": { "$ref": "#/components/schemas/nullableUuidSchema" }, "idempotencyKey": { "type": "string", "minLength": 1, "maxLength": 200 }, "conflictStrategy": { "type": "string", "enum": [ "fail", "rename" ], "default": "rename" } }, "required": [ "nodeIds", "destinationDriveId", "destinationParentId", "idempotencyKey", "conflictStrategy" ] }, "fileCopyOperationResponseSchema": { "type": "object", "properties": { "completedAt": { "type": [ "string", "null" ] }, "completedItems": { "type": "integer", "minimum": 0 }, "createdAt": { "type": "string" }, "destinationDriveId": { "type": "string", "format": "uuid" }, "destinationParentId": { "type": [ "string", "null" ], "format": "uuid" }, "errorMessage": { "type": [ "string", "null" ] }, "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "sourceNodeIds": { "type": "array", "items": { "type": "string", "format": "uuid" } }, "status": { "type": "string", "enum": [ "metadata-ready", "copying", "completed", "failed" ] }, "totalItems": { "type": "integer", "minimum": 0 }, "updatedAt": { "type": "string" } }, "required": [ "completedAt", "completedItems", "createdAt", "destinationDriveId", "destinationParentId", "errorMessage", "id", "organizationId", "sourceNodeIds", "status", "totalItems", "updatedAt" ] }, "nullableUuidSchema": { "type": [ "string", "null" ], "format": "uuid" }, "organizationIdSchema": { "type": "string", "format": "uuid" } } ``` # List accessible Files drives. (/openapi/listFileDrives) Returns company, customer, personal, and shared drives visible to the caller through inherited Files access. ## GET /v1/organizations/{organizationId}/files/drives ```json { "operationId": "listFileDrives", "summary": "List accessible Files drives.", "description": "Returns company, customer, personal, and shared drives visible to the caller through inherited Files access.", "tags": [ "Files" ], "parameters": [ { "in": "query", "name": "driveType", "required": false, "schema": { "allOf": [ { "$ref": "#/components/schemas/fileDriveTypeSchema" } ] }, "example": "example" }, { "in": "query", "name": "includeArchived", "required": true, "schema": { "anyOf": [ { "type": "boolean" }, { "type": "boolean" } ], "default": false }, "example": "example" }, { "in": "query", "name": "sortBy", "required": true, "schema": { "type": "string", "enum": [ "driveType", "name", "createdAt", "updatedAt" ], "default": "name" }, "example": "driveType" }, { "in": "query", "name": "sortDirection", "required": true, "schema": { "type": "string", "enum": [ "asc", "desc" ], "default": "asc" }, "example": "asc" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns a bounded page of accessible Files drives.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/fileDrivesHttpResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "fileDriveTypeSchema": { "type": "string", "enum": [ "company", "personal", "shared", "customers" ] }, "fileDrivesHttpResponseSchema": { "type": "object" }, "organizationIdSchema": { "type": "string", "format": "uuid" } } ``` # Create a direct resumable Files upload. (/openapi/createFileUpload) Reserve organization storage quota and return a short-lived signed TUS transport. File bytes are uploaded directly to Storage. ## POST /v1/organizations/{organizationId}/files/uploads ```json { "operationId": "createFileUpload", "summary": "Create a direct resumable Files upload.", "description": "Reserve organization storage quota and return a short-lived signed TUS transport. File bytes are uploaded directly to Storage.", "tags": [ "Files" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/fileUploadCreateBodySchema" } } }, "description": "File metadata and destination for the upload reservation.", "required": true }, "responses": { "201": { "description": "Returns the signed direct Storage upload transport.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/fileUploadTransportResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "fileUploadCreateBodySchema": { "type": "object", "properties": { "driveId": { "type": "string", "format": "uuid" }, "expectedBytes": { "type": "integer", "exclusiveMinimum": 0 }, "fileName": { "type": "string", "minLength": 1, "maxLength": 240 }, "idempotencyKey": { "type": "string", "minLength": 1, "maxLength": 200 }, "mediaType": { "type": "string", "minLength": 1, "maxLength": 240 }, "nodeId": { "type": [ "string", "null" ], "format": "uuid", "default": null }, "parentId": { "type": [ "string", "null" ], "format": "uuid", "default": null } }, "required": [ "driveId", "expectedBytes", "fileName", "idempotencyKey", "mediaType", "nodeId", "parentId" ] }, "fileUploadTransportResponseSchema": { "type": "object", "properties": { "bucket": { "type": "string", "minLength": 1, "maxLength": 80 }, "expiresAt": { "type": "string" }, "path": { "type": "string", "minLength": 1, "maxLength": 1024 }, "reservationId": { "type": "string", "format": "uuid" }, "token": { "type": "string", "minLength": 1 }, "tusEndpoint": { "type": "string", "format": "uri" } }, "required": [ "bucket", "expiresAt", "path", "reservationId", "token", "tusEndpoint" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" } } ``` # Download selected Files as ZIP. (/openapi/createFileZipExport) Streams small authorized selections immediately and queues larger selections as expiring private artifacts. ## POST /v1/organizations/{organizationId}/files/zip-exports ```json { "operationId": "createFileZipExport", "summary": "Download selected Files as ZIP.", "description": "Streams small authorized selections immediately and queues larger selections as expiring private artifacts.", "tags": [ "Files" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/fileZipExportBodySchema" } } } }, "responses": { "202": { "description": "Returns an asynchronous ZIP job when the direct streaming threshold is exceeded. Small selections return application/zip.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/fileZipExportResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "fileZipExportBodySchema": { "type": "object", "properties": { "nodeIds": { "type": "array", "items": { "type": "string", "format": "uuid" }, "minItems": 1, "maxItems": 200 }, "fileName": { "type": "string", "minLength": 1, "maxLength": 240 }, "idempotencyKey": { "type": "string", "minLength": 1, "maxLength": 200 } }, "required": [ "nodeIds", "fileName", "idempotencyKey" ] }, "fileZipExportResponseSchema": { "type": "object", "properties": { "completedItems": { "type": "integer", "minimum": 0 }, "deliveryMode": { "type": "string", "enum": [ "direct", "async" ] }, "downloadUrl": { "type": [ "string", "null" ], "format": "uri" }, "errorMessage": { "type": [ "string", "null" ] }, "expiresAt": { "type": [ "string", "null" ] }, "fileName": { "type": "string" }, "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "status": { "type": "string", "enum": [ "queued", "processing", "ready", "failed", "expired" ] }, "totalBytes": { "type": "integer", "minimum": 0 }, "totalItems": { "type": "integer", "minimum": 0 } }, "required": [ "completedItems", "deliveryMode", "downloadUrl", "errorMessage", "expiresAt", "fileName", "id", "organizationId", "status", "totalBytes", "totalItems" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" } } ``` # Get a Files node. (/openapi/getFileNode) Returns safe file or folder metadata after authoritative inherited node access checks. ## GET /v1/organizations/{organizationId}/files/nodes/{nodeId} ```json { "operationId": "getFileNode", "summary": "Get a Files node.", "description": "Returns safe file or folder metadata after authoritative inherited node access checks.", "tags": [ "Files" ], "parameters": [ { "in": "path", "name": "nodeId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns the accessible Files node without Storage object details.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/fileNodeHttpSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "fileDateTimeSchema": { "type": "string" }, "fileNodeHttpSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "organizationId": { "type": "string", "format": "uuid" }, "driveId": { "type": "string", "format": "uuid" }, "parentId": { "$ref": "#/components/schemas/nullableUuidSchema" }, "kind": { "$ref": "#/components/schemas/fileNodeKindSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 240 }, "source": { "$ref": "#/components/schemas/fileNodeSourceSchema" }, "deletedAt": { "anyOf": [ { "$ref": "#/components/schemas/fileDateTimeSchema" }, { "type": "null" } ] }, "createdBy": { "$ref": "#/components/schemas/nullableUuidSchema" }, "createdAt": { "$ref": "#/components/schemas/fileDateTimeSchema" }, "updatedAt": { "$ref": "#/components/schemas/fileDateTimeSchema" }, "customerId": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 }, "userState": { "anyOf": [ { "$ref": "#/components/schemas/fileNodeUserStateSchema" }, { "type": "null" } ] }, "currentVersion": { "type": [ "object", "null" ], "properties": { "byteSize": { "type": "integer", "minimum": 0 }, "mediaType": { "type": "string", "minLength": 1, "maxLength": 240 } }, "required": [ "byteSize", "mediaType" ] } }, "required": [ "id", "organizationId", "driveId", "parentId", "kind", "name", "source", "deletedAt", "createdBy", "createdAt", "updatedAt" ] }, "fileNodeKindSchema": { "type": "string", "enum": [ "folder", "file" ] }, "fileNodeSourceSchema": { "type": "string", "enum": [ "internal", "google-drive", "onedrive" ] }, "fileNodeUserStateSchema": { "type": "object", "properties": { "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "nodeId": { "type": "string", "format": "uuid" }, "userId": { "type": "string", "format": "uuid" }, "starredAt": { "anyOf": [ { "$ref": "#/components/schemas/fileDateTimeSchema" }, { "type": "null" } ] }, "lastOpenedAt": { "anyOf": [ { "$ref": "#/components/schemas/fileDateTimeSchema" }, { "type": "null" } ] }, "updatedAt": { "$ref": "#/components/schemas/fileDateTimeSchema" } }, "required": [ "organizationId", "nodeId", "userId", "starredAt", "lastOpenedAt", "updatedAt" ] }, "nullableUuidSchema": { "type": [ "string", "null" ], "format": "uuid" }, "organizationIdSchema": { "type": "string", "format": "uuid" } } ``` # Get a Files ZIP export. (/openapi/getFileZipExport) Poll an asynchronous ZIP job. Ready jobs include a short-lived signed URL only for the authorized requester. ## GET /v1/organizations/{organizationId}/files/zip-exports/{exportId} ```json { "operationId": "getFileZipExport", "summary": "Get a Files ZIP export.", "description": "Poll an asynchronous ZIP job. Ready jobs include a short-lived signed URL only for the authorized requester.", "tags": [ "Files" ], "parameters": [ { "in": "path", "name": "exportId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns ZIP job progress and an authorized signed URL when ready.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/fileZipExportStatusResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "fileZipExportStatusResponseSchema": { "type": "object", "properties": { "completedItems": { "type": "integer", "minimum": 0 }, "deliveryMode": { "type": "string", "enum": [ "direct", "async" ] }, "downloadUrl": { "type": [ "string", "null" ], "format": "uri" }, "errorMessage": { "type": [ "string", "null" ] }, "expiresAt": { "type": [ "string", "null" ] }, "fileName": { "type": "string" }, "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "status": { "type": "string", "enum": [ "queued", "processing", "ready", "failed", "expired" ] }, "totalBytes": { "type": "integer", "minimum": 0 }, "totalItems": { "type": "integer", "minimum": 0 } }, "required": [ "completedItems", "deliveryMode", "downloadUrl", "errorMessage", "expiresAt", "fileName", "id", "organizationId", "status", "totalBytes", "totalItems" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" } } ``` # List Files node children. (/openapi/listFileNodeChildren) Returns an authorized page of direct children for a folder, including explicitly requested trash visibility. ## GET /v1/organizations/{organizationId}/files/nodes/{nodeId}/children ```json { "operationId": "listFileNodeChildren", "summary": "List Files node children.", "description": "Returns an authorized page of direct children for a folder, including explicitly requested trash visibility.", "tags": [ "Files" ], "parameters": [ { "in": "query", "name": "kind", "required": false, "schema": { "allOf": [ { "$ref": "#/components/schemas/fileNodeKindSchema" } ] }, "example": "example" }, { "in": "query", "name": "sortBy", "required": true, "schema": { "type": "string", "enum": [ "kind", "name", "createdAt", "updatedAt" ], "default": "name" }, "example": "kind" }, { "in": "query", "name": "sortDirection", "required": true, "schema": { "type": "string", "enum": [ "asc", "desc" ], "default": "asc" }, "example": "asc" }, { "in": "query", "name": "trash", "required": true, "schema": { "type": "string", "enum": [ "exclude", "only" ], "default": "exclude" }, "example": "exclude" }, { "in": "path", "name": "nodeId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns a bounded page of accessible direct child nodes.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/fileNodeChildrenHttpResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "fileNodeChildrenHttpResponseSchema": { "type": "object" }, "fileNodeKindSchema": { "type": "string", "enum": [ "folder", "file" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" } } ``` # Create a Files download URL. (/openapi/createFileDownload) Authorizes the file node and returns a short-lived signed download URL without exposing its Storage object path. ## GET /v1/organizations/{organizationId}/files/nodes/{nodeId}/download ```json { "operationId": "createFileDownload", "summary": "Create a Files download URL.", "description": "Authorizes the file node and returns a short-lived signed download URL without exposing its Storage object path.", "tags": [ "Files" ], "parameters": [ { "in": "path", "name": "nodeId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns a short-lived signed URL and expiry metadata for the file.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/fileDownloadHttpSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "fileDateTimeSchema": { "type": "string" }, "fileDownloadHttpSchema": { "type": "object", "properties": { "url": { "type": "string", "format": "uri" }, "expiresAt": { "$ref": "#/components/schemas/fileDateTimeSchema" }, "fileName": { "type": "string", "minLength": 1 }, "mediaType": { "type": [ "string", "null" ] } }, "required": [ "url", "expiresAt", "fileName", "mediaType" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" } } ``` # Cancel a direct resumable Files upload. (/openapi/cancelFileUpload) Release the upload reservation and delete its Storage object when present. ## POST /v1/organizations/{organizationId}/files/uploads/{reservationId}/cancel ```json { "operationId": "cancelFileUpload", "summary": "Cancel a direct resumable Files upload.", "description": "Release the upload reservation and delete its Storage object when present.", "tags": [ "Files" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "reservationId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Confirms the upload was cancelled.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/publicApiSuccessResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "organizationIdSchema": { "type": "string", "format": "uuid" }, "publicApiSuccessResponseSchema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [ true ] } }, "required": [ "success" ] } } ``` # Finalize a direct resumable Files upload. (/openapi/finalizeFileUpload) Verify the uploaded Storage object, its byte size, and detectable file signature before atomically creating the Files node version. ## POST /v1/organizations/{organizationId}/files/uploads/{reservationId}/finalize ```json { "operationId": "finalizeFileUpload", "summary": "Finalize a direct resumable Files upload.", "description": "Verify the uploaded Storage object, its byte size, and detectable file signature before atomically creating the Files node version.", "tags": [ "Files" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "reservationId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns the finalized upload reservation.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/fileUploadFinalizeResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "fileUploadFinalizeResponseSchema": { "type": "object", "properties": { "actualBytes": { "type": [ "integer", "null" ], "minimum": 0 }, "checksum": { "type": [ "string", "null" ] }, "createdAt": { "type": "string" }, "createdBy": { "type": "string", "format": "uuid" }, "driveId": { "type": "string", "format": "uuid" }, "etag": { "type": [ "string", "null" ] }, "expectedBytes": { "type": "integer", "exclusiveMinimum": 0 }, "expiresAt": { "type": "string" }, "fileName": { "type": "string" }, "finalizedAt": { "type": [ "string", "null" ] }, "finalizedVersionId": { "type": [ "string", "null" ], "format": "uuid" }, "id": { "type": "string", "format": "uuid" }, "idempotencyKey": { "type": "string" }, "mediaType": { "type": "string" }, "nodeId": { "type": [ "string", "null" ], "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "parentId": { "type": [ "string", "null" ], "format": "uuid" }, "status": { "type": "string", "enum": [ "reserved", "uploaded", "finalized", "cancelled", "expired", "failed" ] }, "storageBucket": { "type": "string" }, "storagePath": { "type": "string" }, "updatedAt": { "type": "string" } }, "required": [ "actualBytes", "checksum", "createdAt", "createdBy", "driveId", "etag", "expectedBytes", "expiresAt", "fileName", "finalizedAt", "finalizedVersionId", "id", "idempotencyKey", "mediaType", "nodeId", "organizationId", "parentId", "status", "storageBucket", "storagePath", "updatedAt" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" } } ``` # Versioned health check for the API app. (/openapi/getApiHealthV1) Return the versioned health status for the API app. ## GET /v1/health ```json { "operationId": "getApiHealthV1", "summary": "Versioned health check for the API app.", "description": "Return the versioned health status for the API app.", "tags": [ "Health" ], "parameters": [], "responses": { "200": { "description": "Returns the API service health.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/healthResponseSchema" } } } } } } ``` ## Referenced schemas ```json { "healthResponseSchema": { "type": "object", "properties": { "ok": { "type": "boolean", "enum": [ true ] }, "service": { "type": "string", "enum": [ "api" ] }, "version": { "type": "string", "enum": [ "v1" ] } }, "required": [ "ok", "service", "version" ] } } ``` # List jobs. (/openapi/listHiringJobs) Return hiring jobs (vacancies) for an organization. ## GET /v1/organizations/{organizationId}/hiring/jobs ```json { "operationId": "listHiringJobs", "summary": "List jobs.", "description": "Return hiring jobs (vacancies) for an organization.", "tags": [ "Hiring" ], "parameters": [ { "in": "query", "name": "status", "required": false, "schema": { "allOf": [ { "$ref": "#/components/schemas/hiringJobStatusSchema" } ] }, "example": "example" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns jobs.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/hiringJobsResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "employeeContractTypeSchema": { "type": "string", "enum": [ "permanent", "fixed_term", "on_call", "freelance" ] }, "employeeKindSchema": { "type": "string", "enum": [ "employee", "contractor", "intern" ] }, "hiringJobSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "title": { "type": "string" }, "slug": { "type": "string" }, "description": { "type": [ "string", "null" ] }, "location": { "type": [ "string", "null" ] }, "employmentKind": { "$ref": "#/components/schemas/employeeKindSchema" }, "contractType": { "anyOf": [ { "$ref": "#/components/schemas/employeeContractTypeSchema" }, { "type": "null" } ] }, "hoursPerWeek": { "type": [ "number", "null" ] }, "salaryMin": { "type": [ "number", "null" ] }, "salaryMax": { "type": [ "number", "null" ] }, "currency": { "type": "string" }, "teamId": { "anyOf": [ { "$ref": "#/components/schemas/teamIdSchema" }, { "type": "null" } ] }, "hiringManager": { "anyOf": [ { "$ref": "#/components/schemas/personSummarySchema" }, { "type": "null" } ] }, "status": { "$ref": "#/components/schemas/hiringJobStatusSchema" }, "publishedOnCareersPage": { "type": "boolean" }, "openedAt": { "type": [ "string", "null" ] }, "closedAt": { "type": [ "string", "null" ] }, "activeApplicationCount": { "type": "integer" }, "createdAt": { "type": "string" } }, "required": [ "id", "title", "slug", "description", "location", "employmentKind", "contractType", "hoursPerWeek", "salaryMin", "salaryMax", "currency", "teamId", "hiringManager", "status", "publishedOnCareersPage", "openedAt", "closedAt", "activeApplicationCount", "createdAt" ] }, "hiringJobStatusSchema": { "type": "string", "enum": [ "draft", "open", "closed" ] }, "hiringJobsResponseSchema": { "type": "array", "items": { "$ref": "#/components/schemas/hiringJobSchema" } }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "personSummarySchema": { "type": "object", "properties": { "employeeId": { "type": "string", "format": "uuid" }, "userId": { "type": [ "string", "null" ], "format": "uuid" }, "displayName": { "type": "string" }, "avatarUrl": { "type": [ "string", "null" ] }, "jobTitle": { "type": [ "string", "null" ] } }, "required": [ "employeeId", "userId", "displayName", "avatarUrl", "jobTitle" ] }, "teamIdSchema": { "type": "integer", "exclusiveMinimum": 0 } } ``` # List applications for a job. (/openapi/listHiringApplications) Return candidate applications for a job with their current stage and average rating. ## GET /v1/organizations/{organizationId}/hiring/jobs/{jobId}/applications ```json { "operationId": "listHiringApplications", "summary": "List applications for a job.", "description": "Return candidate applications for a job with their current stage and average rating.", "tags": [ "Hiring" ], "parameters": [ { "in": "query", "name": "status", "required": false, "schema": { "allOf": [ { "$ref": "#/components/schemas/hiringApplicationStatusSchema" } ] }, "example": "example" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "jobId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns applications.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/hiringApplicationsResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "hiringApplicationSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "jobId": { "type": "string", "format": "uuid" }, "jobTitle": { "type": "string" }, "candidate": { "$ref": "#/components/schemas/hiringCandidateSchema" }, "stageId": { "type": "string", "format": "uuid" }, "stageName": { "type": "string" }, "stageSystemKey": { "anyOf": [ { "$ref": "#/components/schemas/hiringStageSystemKeySchema" }, { "type": "null" } ] }, "status": { "$ref": "#/components/schemas/hiringApplicationStatusSchema" }, "source": { "$ref": "#/components/schemas/hiringApplicationSourceSchema" }, "position": { "type": "number" }, "coverLetter": { "type": [ "string", "null" ] }, "rejectionReason": { "type": [ "string", "null" ] }, "stageEnteredAt": { "type": "string" }, "daysInStage": { "type": "integer" }, "isStalled": { "type": "boolean" }, "averageRating": { "type": [ "number", "null" ] }, "scorecardCount": { "type": "integer" }, "nextInterviewAt": { "type": [ "string", "null" ] }, "hiredAt": { "type": [ "string", "null" ] }, "createdAt": { "type": "string" } }, "required": [ "id", "jobId", "jobTitle", "candidate", "stageId", "stageName", "stageSystemKey", "status", "source", "position", "coverLetter", "rejectionReason", "stageEnteredAt", "daysInStage", "isStalled", "averageRating", "scorecardCount", "nextInterviewAt", "hiredAt", "createdAt" ] }, "hiringApplicationSourceSchema": { "type": "string", "enum": [ "careers_page", "manual", "referral", "agency" ] }, "hiringApplicationStatusSchema": { "type": "string", "enum": [ "active", "rejected", "hired", "withdrawn" ] }, "hiringApplicationsResponseSchema": { "type": "array", "items": { "$ref": "#/components/schemas/hiringApplicationSchema" } }, "hiringCandidateSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "firstName": { "type": "string" }, "lastName": { "type": [ "string", "null" ] }, "displayName": { "type": "string" }, "email": { "type": [ "string", "null" ] }, "phone": { "type": [ "string", "null" ] }, "linkedinUrl": { "type": [ "string", "null" ] }, "locale": { "anyOf": [ { "$ref": "#/components/schemas/hiringLocaleSchema" }, { "type": "null" } ] }, "hasCv": { "type": "boolean" }, "cvFileName": { "type": [ "string", "null" ] }, "cvSummary": { "type": [ "string", "null" ] }, "cvSkills": { "type": "array", "items": { "type": "string" } }, "cvExperienceYears": { "type": [ "number", "null" ] }, "consentGivenAt": { "type": [ "string", "null" ] }, "talentPoolConsentUntil": { "type": "object" }, "retentionUntil": { "type": "object" }, "anonymizedAt": { "type": [ "string", "null" ] }, "employeeId": { "type": [ "string", "null" ], "format": "uuid" }, "createdAt": { "type": "string" } }, "required": [ "id", "firstName", "lastName", "displayName", "email", "phone", "linkedinUrl", "locale", "hasCv", "cvFileName", "cvSummary", "cvSkills", "cvExperienceYears", "consentGivenAt", "talentPoolConsentUntil", "retentionUntil", "anonymizedAt", "employeeId", "createdAt" ] }, "hiringLocaleSchema": { "type": "string", "enum": [ "en", "nl" ] }, "hiringStageSystemKeySchema": { "type": "string", "enum": [ "applied", "screening", "interview", "offer", "hired" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" } } ``` # Upload an inbox attachment. (/openapi/uploadInboxAttachment) ## POST /v1/organizations/{organizationId}/inbox/attachments ```json { "operationId": "uploadInboxAttachment", "summary": "Upload an inbox attachment.", "description": "", "tags": [ "Inbox" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "multipart/form-data": { "schema": { "$ref": "#/components/schemas/inboxAttachmentUploadSchema" }, "encoding": { "file": { "contentType": "application/octet-stream" } } } }, "description": "Multipart inbox attachment and target external thread id.", "required": true }, "responses": { "200": { "description": "Returns the recorded attachment metadata.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/inboxAttachmentUploadResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "inboxAttachmentUploadResponseSchema": { "type": "object", "properties": { "attachmentId": { "type": "string", "format": "uuid" }, "mediaType": { "type": "string", "minLength": 1 }, "name": { "type": "string", "minLength": 1 } }, "required": [ "attachmentId", "mediaType", "name" ] }, "inboxAttachmentUploadSchema": { "type": "object", "properties": { "file": { "type": "string", "contentMediaType": "application/octet-stream" }, "threadId": { "type": "string", "format": "uuid" } }, "required": [ "file", "threadId" ] } } ``` # List inbox threads. (/openapi/listInboxThreads) ## GET /v1/organizations/{organizationId}/inbox/threads ```json { "operationId": "listInboxThreads", "summary": "List inbox threads.", "description": "", "tags": [ "Inbox" ], "parameters": [ { "in": "query", "name": "limit", "required": true, "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 30 }, "example": 1 }, { "in": "query", "name": "offset", "required": true, "schema": { "default": 0, "type": "integer", "minimum": 0, "maximum": 9007199254740991 }, "example": 1 }, { "in": "query", "name": "q", "required": false, "schema": { "type": "string", "minLength": 1, "maxLength": 120 }, "example": "example" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns inbox threads and the unread count.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/inboxThreadsResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "channelConnectionStatusSchema": { "type": "string", "enum": [ "draft", "active", "disabled", "error" ] }, "channelDeliveryStatusSchema": { "type": "string", "enum": [ "queued", "sent", "delivered", "failed", "dead_lettered", "canceled" ] }, "channelInboxThreadSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "organizationId": { "type": "string", "format": "uuid" }, "connectionId": { "type": "string", "format": "uuid" }, "chatThreadId": { "type": [ "string", "null" ], "format": "uuid" }, "externalChannelId": { "type": [ "string", "null" ], "minLength": 1, "maxLength": 240 }, "externalThreadId": { "type": "string", "minLength": 1, "maxLength": 240 }, "externalActorId": { "type": [ "string", "null" ], "minLength": 1, "maxLength": 240 }, "customerId": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 }, "assignedUserId": { "type": [ "string", "null" ], "format": "uuid", "default": null }, "assignedTeamId": { "type": [ "integer", "null" ], "exclusiveMinimum": 0, "default": null }, "takeoverStatus": { "type": "string", "enum": [ "ai", "requested", "human", "resolved" ], "default": "ai" }, "takeoverReason": { "type": [ "string", "null" ], "maxLength": 1000, "default": null }, "takeoverAt": { "anyOf": [ { "$ref": "#/components/schemas/dateTimeSchema" }, { "type": "null" } ], "default": null }, "continuationCursor": { "type": [ "string", "null" ], "maxLength": 500, "default": null }, "metadata": { "$ref": "#/components/schemas/metadataSchema" }, "createdAt": { "$ref": "#/components/schemas/dateTimeSchema" }, "updatedAt": { "$ref": "#/components/schemas/dateTimeSchema" }, "archivedAt": { "anyOf": [ { "$ref": "#/components/schemas/dateTimeSchema" }, { "type": "null" } ] }, "chatThreadTitle": { "type": [ "string", "null" ], "minLength": 1, "maxLength": 240 }, "contactAvatarUrl": { "type": [ "string", "null" ], "minLength": 1, "maxLength": 2048 }, "contactDisplayName": { "type": [ "string", "null" ], "minLength": 1, "maxLength": 240 }, "contactEmail": { "type": [ "string", "null" ], "minLength": 1, "maxLength": 320 }, "contactFirstName": { "type": [ "string", "null" ], "minLength": 1, "maxLength": 120 }, "contactLastName": { "type": [ "string", "null" ], "minLength": 1, "maxLength": 120 }, "contactPhone": { "type": [ "string", "null" ], "minLength": 1, "maxLength": 80 }, "connectionKind": { "$ref": "#/components/schemas/channelKindSchema" }, "connectionName": { "type": "string", "minLength": 1, "maxLength": 160 }, "connectionStatus": { "$ref": "#/components/schemas/channelConnectionStatusSchema" }, "lastDeliveryError": { "type": [ "string", "null" ], "maxLength": 2000 }, "lastDeliveryStatus": { "anyOf": [ { "$ref": "#/components/schemas/channelDeliveryStatusSchema" }, { "type": "null" } ] }, "lastMessageAt": { "anyOf": [ { "$ref": "#/components/schemas/dateTimeSchema" }, { "type": "null" } ] }, "lastMessageId": { "type": [ "string", "null" ], "format": "uuid" }, "lastMessageRole": { "anyOf": [ { "$ref": "#/components/schemas/chatMessageRoleSchema" }, { "type": "null" } ] }, "lastMessageText": { "type": [ "string", "null" ] }, "pinnedAt": { "anyOf": [ { "$ref": "#/components/schemas/dateTimeSchema" }, { "type": "null" } ] }, "readAt": { "anyOf": [ { "$ref": "#/components/schemas/dateTimeSchema" }, { "type": "null" } ] }, "streamStatus": { "anyOf": [ { "$ref": "#/components/schemas/chatStreamStatusSchema" }, { "type": "null" } ] }, "unread": { "type": "boolean" } }, "required": [ "id", "organizationId", "connectionId", "chatThreadId", "externalChannelId", "externalThreadId", "externalActorId", "customerId", "assignedUserId", "assignedTeamId", "takeoverStatus", "takeoverReason", "takeoverAt", "continuationCursor", "metadata", "createdAt", "updatedAt", "archivedAt", "chatThreadTitle", "contactAvatarUrl", "contactDisplayName", "contactEmail", "contactFirstName", "contactLastName", "contactPhone", "connectionKind", "connectionName", "connectionStatus", "lastDeliveryError", "lastDeliveryStatus", "lastMessageAt", "lastMessageId", "lastMessageRole", "lastMessageText", "pinnedAt", "readAt", "streamStatus", "unread" ] }, "channelKindSchema": { "type": "string", "enum": [ "slack", "teams", "google-chat", "discord", "telegram", "github", "linear", "whatsapp", "email" ] }, "chatMessageRoleSchema": { "type": "string", "enum": [ "system", "user", "assistant", "tool" ] }, "chatStreamStatusSchema": { "type": "string", "enum": [ "idle", "submitted", "streaming", "interrupted", "stopping", "stopped", "completed", "failed" ] }, "dateTimeSchema": { "type": "string" }, "inboxPaginationSchema": { "type": "object", "properties": { "hasMore": { "type": "boolean" }, "limit": { "type": "integer", "minimum": 1, "maximum": 100 }, "offset": { "type": "integer", "minimum": 0 }, "total": { "type": "integer", "minimum": 0 } }, "required": [ "hasMore", "limit", "offset", "total" ] }, "inboxThreadsResponseSchema": { "type": "object", "properties": { "pagination": { "$ref": "#/components/schemas/inboxPaginationSchema" }, "threads": { "type": "array", "items": { "$ref": "#/components/schemas/channelInboxThreadSchema" } }, "unreadCount": { "type": "integer", "minimum": 0 } }, "required": [ "pagination", "threads", "unreadCount" ] }, "jsonValueSchema": { "anyOf": [ { "type": "string" }, { "type": "number" }, { "type": "boolean" }, { "type": "null" }, { "type": "array", "items": { "$ref": "#/components/schemas/jsonValueSchema" } }, { "type": "object", "additionalProperties": { "$ref": "#/components/schemas/jsonValueSchema" }, "propertyNames": { "type": "string" } } ] }, "metadataSchema": { "type": "object", "additionalProperties": { "$ref": "#/components/schemas/jsonValueSchema" }, "propertyNames": { "type": "string" } } } ``` # Download an inbox attachment. (/openapi/downloadInboxAttachment) ## GET /v1/organizations/{organizationId}/inbox/attachments/{attachmentId} ```json { "operationId": "downloadInboxAttachment", "summary": "Download an inbox attachment.", "description": "", "tags": [ "Inbox" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "path", "name": "attachmentId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "302": { "description": "Redirects to a short-lived signed attachment URL.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/inboxAttachmentRedirectResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "inboxAttachmentRedirectResponseSchema": { "type": "string", "format": "uri" } } ``` # Get inbox delivery status. (/openapi/getInboxDelivery) ## GET /v1/organizations/{organizationId}/inbox/deliveries/{deliveryId} ```json { "operationId": "getInboxDelivery", "summary": "Get inbox delivery status.", "description": "", "tags": [ "Inbox" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "path", "name": "deliveryId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns delivery state and attempt history.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/inboxDeliveryResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "channelDeliveryAttemptSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "organizationId": { "type": "string", "format": "uuid" }, "deliveryId": { "type": "string", "format": "uuid" }, "attempt": { "type": "integer", "exclusiveMinimum": 0 }, "status": { "type": "string", "enum": [ "queued", "running", "sent", "delivered", "failed", "canceled" ] }, "providerStatus": { "type": [ "string", "null" ], "maxLength": 240 }, "errorCode": { "type": [ "string", "null" ], "maxLength": 240 }, "errorMessage": { "type": [ "string", "null" ], "maxLength": 2000 }, "responseMetadata": { "$ref": "#/components/schemas/metadataSchema" }, "startedAt": { "$ref": "#/components/schemas/dateTimeSchema" }, "completedAt": { "anyOf": [ { "$ref": "#/components/schemas/dateTimeSchema" }, { "type": "null" } ] }, "createdAt": { "$ref": "#/components/schemas/dateTimeSchema" } }, "required": [ "id", "organizationId", "deliveryId", "attempt", "status", "providerStatus", "errorCode", "errorMessage", "responseMetadata", "startedAt", "completedAt", "createdAt" ] }, "channelDeliveryStatusSchema": { "type": "string", "enum": [ "queued", "sent", "delivered", "failed", "dead_lettered", "canceled" ] }, "channelMessageDeliverySchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "organizationId": { "type": "string", "format": "uuid" }, "connectionId": { "type": "string", "format": "uuid" }, "chatMessageId": { "type": [ "string", "null" ], "format": "uuid" }, "externalMessageId": { "type": [ "string", "null" ], "minLength": 1, "maxLength": 240 }, "direction": { "type": "string", "enum": [ "inbound", "outbound" ] }, "status": { "$ref": "#/components/schemas/channelDeliveryStatusSchema" }, "idempotencyKey": { "type": [ "string", "null" ], "minLength": 1, "maxLength": 240 }, "errorMessage": { "type": [ "string", "null" ], "maxLength": 2000 }, "nextAttemptAt": { "anyOf": [ { "$ref": "#/components/schemas/dateTimeSchema" }, { "type": "null" } ], "default": null }, "canceledAt": { "anyOf": [ { "$ref": "#/components/schemas/dateTimeSchema" }, { "type": "null" } ], "default": null }, "retryCount": { "type": "integer", "minimum": 0, "default": 0 }, "metadata": { "$ref": "#/components/schemas/metadataSchema" }, "createdAt": { "$ref": "#/components/schemas/dateTimeSchema" }, "updatedAt": { "$ref": "#/components/schemas/dateTimeSchema" } }, "required": [ "id", "organizationId", "connectionId", "chatMessageId", "externalMessageId", "direction", "status", "idempotencyKey", "errorMessage", "nextAttemptAt", "canceledAt", "retryCount", "metadata", "createdAt", "updatedAt" ] }, "dateTimeSchema": { "type": "string" }, "inboxDeliveryResponseSchema": { "type": "object", "properties": { "attempts": { "type": "array", "items": { "$ref": "#/components/schemas/channelDeliveryAttemptSchema" } }, "delivery": { "$ref": "#/components/schemas/channelMessageDeliverySchema" } }, "required": [ "attempts", "delivery" ] }, "jsonValueSchema": { "anyOf": [ { "type": "string" }, { "type": "number" }, { "type": "boolean" }, { "type": "null" }, { "type": "array", "items": { "$ref": "#/components/schemas/jsonValueSchema" } }, { "type": "object", "additionalProperties": { "$ref": "#/components/schemas/jsonValueSchema" }, "propertyNames": { "type": "string" } } ] }, "metadataSchema": { "type": "object", "additionalProperties": { "$ref": "#/components/schemas/jsonValueSchema" }, "propertyNames": { "type": "string" } } } ``` # List inbox thread messages. (/openapi/listInboxThreadMessages) ## GET /v1/organizations/{organizationId}/inbox/threads/{threadId}/messages ```json { "operationId": "listInboxThreadMessages", "summary": "List inbox thread messages.", "description": "", "tags": [ "Inbox" ], "parameters": [ { "in": "query", "name": "limit", "required": true, "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 50 }, "example": 1 }, { "in": "query", "name": "offset", "required": true, "schema": { "default": 0, "type": "integer", "minimum": 0, "maximum": 9007199254740991 }, "example": 1 }, { "in": "path", "name": "organizationId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "path", "name": "threadId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns messages, delivery state, and pagination.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/inboxMessagesResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "channelDeliveryStatusSchema": { "type": "string", "enum": [ "queued", "sent", "delivered", "failed", "dead_lettered", "canceled" ] }, "channelDraftSuggestionSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "organizationId": { "type": "string", "format": "uuid" }, "channelThreadId": { "type": "string", "format": "uuid" }, "sourceMessageId": { "type": [ "string", "null" ], "format": "uuid" }, "status": { "type": "string", "enum": [ "pending", "sent", "discarded", "superseded" ] }, "text": { "type": "string", "minLength": 1, "maxLength": 4000 }, "model": { "type": [ "string", "null" ], "maxLength": 160 }, "confidence": { "type": [ "number", "null" ], "minimum": 0, "maximum": 1 }, "escalationReason": { "type": [ "string", "null" ], "maxLength": 1000 }, "editedBy": { "type": [ "string", "null" ], "format": "uuid" }, "decidedBy": { "type": [ "string", "null" ], "format": "uuid" }, "decision": { "type": [ "string", "null" ], "enum": [ "send", "discard" ] }, "createdAt": { "$ref": "#/components/schemas/dateTimeSchema" }, "updatedAt": { "$ref": "#/components/schemas/dateTimeSchema" }, "decidedAt": { "anyOf": [ { "$ref": "#/components/schemas/dateTimeSchema" }, { "type": "null" } ] } }, "required": [ "id", "organizationId", "channelThreadId", "sourceMessageId", "status", "text", "model", "confidence", "escalationReason", "editedBy", "decidedBy", "decision", "createdAt", "updatedAt", "decidedAt" ] }, "channelMessageDeliverySchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "organizationId": { "type": "string", "format": "uuid" }, "connectionId": { "type": "string", "format": "uuid" }, "chatMessageId": { "type": [ "string", "null" ], "format": "uuid" }, "externalMessageId": { "type": [ "string", "null" ], "minLength": 1, "maxLength": 240 }, "direction": { "type": "string", "enum": [ "inbound", "outbound" ] }, "status": { "$ref": "#/components/schemas/channelDeliveryStatusSchema" }, "idempotencyKey": { "type": [ "string", "null" ], "minLength": 1, "maxLength": 240 }, "errorMessage": { "type": [ "string", "null" ], "maxLength": 2000 }, "nextAttemptAt": { "anyOf": [ { "$ref": "#/components/schemas/dateTimeSchema" }, { "type": "null" } ], "default": null }, "canceledAt": { "anyOf": [ { "$ref": "#/components/schemas/dateTimeSchema" }, { "type": "null" } ], "default": null }, "retryCount": { "type": "integer", "minimum": 0, "default": 0 }, "metadata": { "$ref": "#/components/schemas/metadataSchema" }, "createdAt": { "$ref": "#/components/schemas/dateTimeSchema" }, "updatedAt": { "$ref": "#/components/schemas/dateTimeSchema" } }, "required": [ "id", "organizationId", "connectionId", "chatMessageId", "externalMessageId", "direction", "status", "idempotencyKey", "errorMessage", "nextAttemptAt", "canceledAt", "retryCount", "metadata", "createdAt", "updatedAt" ] }, "chatMentionKindSchema": { "type": "string", "enum": [ "user", "team", "customer", "customer-asset", "contact", "product", "task", "quote", "invoice" ] }, "chatMessageFeedbackRatingSchema": { "type": "string", "enum": [ "positive", "negative" ] }, "chatMessageFeedbackSchema": { "type": "object", "properties": { "messageId": { "type": "string", "format": "uuid" }, "organizationId": { "type": "string", "format": "uuid" }, "rating": { "$ref": "#/components/schemas/chatMessageFeedbackRatingSchema" }, "userId": { "type": "string", "format": "uuid" }, "modelId": { "type": [ "string", "null" ], "minLength": 1, "maxLength": 160 }, "runId": { "type": [ "string", "null" ], "format": "uuid" }, "createdAt": { "$ref": "#/components/schemas/dateTimeSchema" }, "updatedAt": { "$ref": "#/components/schemas/dateTimeSchema" } }, "required": [ "messageId", "organizationId", "rating", "userId", "modelId", "runId", "createdAt", "updatedAt" ] }, "chatMessagePartSchema": { "type": "object", "discriminator": { "propertyName": "type" }, "oneOf": [ { "$ref": "#/components/schemas/contextualAiActionChatPartSchema" }, { "type": "object", "properties": { "type": { "type": "string", "enum": [ "text" ] }, "text": { "type": "string" } }, "required": [ "type", "text" ] }, { "type": "object", "properties": { "type": { "type": "string", "enum": [ "data" ] }, "data": { "$ref": "#/components/schemas/metadataSchema" } }, "required": [ "type", "data" ] }, { "type": "object", "properties": { "type": { "type": "string", "enum": [ "tool" ] }, "toolCallId": { "type": "string", "minLength": 1 }, "toolName": { "type": "string", "minLength": 1 }, "state": { "type": "string", "enum": [ "input-streaming", "input-available", "approval-requested", "approval-responded", "output-available", "output-error", "output-denied" ] }, "input": { "allOf": [ { "$ref": "#/components/schemas/jsonValueSchema" } ] }, "output": { "allOf": [ { "$ref": "#/components/schemas/jsonValueSchema" } ] }, "errorText": { "type": "string", "maxLength": 1000 }, "approval": { "allOf": [ { "$ref": "#/components/schemas/metadataSchema" } ] }, "metadata": { "allOf": [ { "$ref": "#/components/schemas/metadataSchema" } ] } }, "required": [ "type", "toolCallId", "toolName", "state" ] }, { "type": "object", "properties": { "type": { "type": "string", "enum": [ "source-url" ] }, "sourceId": { "type": "string", "minLength": 1, "maxLength": 240 }, "title": { "type": "string", "minLength": 1, "maxLength": 240 }, "url": { "type": "string", "format": "uri" } }, "required": [ "type", "url" ] }, { "type": "object", "properties": { "type": { "type": "string", "enum": [ "file" ] }, "attachmentId": { "type": "string", "format": "uuid" }, "mediaType": { "type": "string", "minLength": 1 }, "name": { "type": "string", "minLength": 1, "maxLength": 240 } }, "required": [ "type", "attachmentId", "mediaType", "name" ] }, { "type": "object", "properties": { "type": { "type": "string", "enum": [ "mention" ] }, "href": { "type": "string", "minLength": 1, "maxLength": 1024 }, "id": { "type": "string", "minLength": 1, "maxLength": 240 }, "kind": { "$ref": "#/components/schemas/chatMentionKindSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 240 }, "metadata": { "allOf": [ { "$ref": "#/components/schemas/metadataSchema" } ] } }, "required": [ "type", "id", "kind", "label" ] }, { "type": "object", "properties": { "type": { "type": "string", "enum": [ "command" ] }, "command": { "type": "string", "minLength": 1, "maxLength": 80 }, "label": { "type": "string", "minLength": 1, "maxLength": 240 }, "workflowId": { "type": "string", "format": "uuid" }, "status": { "type": "string", "enum": [ "selected", "executed", "rejected" ] }, "runId": { "type": "string", "minLength": 1, "maxLength": 240 }, "error": { "type": "string", "minLength": 1, "maxLength": 1000 }, "start": { "type": "integer", "minimum": 0 }, "end": { "type": "integer", "exclusiveMinimum": 0 } }, "required": [ "type", "command", "label", "status" ] } ] }, "chatMessageRoleSchema": { "type": "string", "enum": [ "system", "user", "assistant", "tool" ] }, "chatMessageSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "threadId": { "type": "string", "format": "uuid" }, "organizationId": { "type": [ "string", "null" ], "format": "uuid" }, "clientMessageId": { "type": [ "string", "null" ], "minLength": 1, "maxLength": 240, "default": null }, "role": { "$ref": "#/components/schemas/chatMessageRoleSchema" }, "parts": { "type": "array", "items": { "$ref": "#/components/schemas/chatMessagePartSchema" } }, "metadata": { "$ref": "#/components/schemas/metadataSchema" }, "status": { "type": "string", "enum": [ "complete", "partial", "failed" ] }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "createdAt": { "$ref": "#/components/schemas/dateTimeSchema" }, "viewerFeedback": { "anyOf": [ { "$ref": "#/components/schemas/chatMessageFeedbackSchema" }, { "type": "null" } ] } }, "required": [ "id", "threadId", "organizationId", "clientMessageId", "role", "parts", "metadata", "status", "createdBy", "createdAt" ] }, "contextualAiActionChatPartSchema": { "type": "object", "properties": { "actionId": { "$ref": "#/components/schemas/contextualAiActionIdSchema" }, "actionVersion": { "$ref": "#/components/schemas/contextualAiActionVersionSchema" }, "approvalConfirmed": { "type": "boolean" }, "contextFingerprint": { "$ref": "#/components/schemas/contextualAiActionFingerprintSchema" }, "contextKinds": { "type": "array", "items": { "$ref": "#/components/schemas/contextualAiActionContextKindSchema" }, "minItems": 1, "maxItems": 8 }, "intakeAnswers": { "type": "array", "items": { "$ref": "#/components/schemas/contextualAiActionIntakeAnswerSchema" }, "maxItems": 12 }, "route": { "type": "string", "minLength": 1, "maxLength": 2048, "pattern": "^/" }, "safeReferences": { "type": "array", "items": { "$ref": "#/components/schemas/contextualAiActionSafeReferenceSchema" }, "maxItems": 20 }, "type": { "type": "string", "enum": [ "action" ] } }, "required": [ "actionId", "actionVersion", "approvalConfirmed", "contextFingerprint", "contextKinds", "route", "safeReferences", "type" ] }, "contextualAiActionContextKindSchema": { "type": "string", "enum": [ "page", "organization", "entity", "selection", "form", "thread", "collection", "workflow", "filter", "schedule", "template" ] }, "contextualAiActionFingerprintSchema": { "type": "string", "minLength": 8, "maxLength": 160, "pattern": "^sha256:[A-Za-z0-9_-]+$" }, "contextualAiActionIdSchema": { "type": "string", "minLength": 1, "maxLength": 120, "pattern": "^[a-z0-9]+(?:[.-][a-z0-9]+)*$" }, "contextualAiActionIntakeAnswerSchema": { "type": "object", "properties": { "fieldId": { "$ref": "#/components/schemas/contextualAiActionIdSchema" }, "value": { "$ref": "#/components/schemas/contextualAiActionIntakeValueSchema" } }, "required": [ "fieldId", "value" ], "additionalProperties": false }, "contextualAiActionIntakeValueSchema": { "anyOf": [ { "type": "string", "maxLength": 10000 }, { "type": "array", "items": { "type": "string", "maxLength": 1000 }, "maxItems": 12 } ] }, "contextualAiActionSafeReferenceSchema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "maxLength": 160 }, "kind": { "$ref": "#/components/schemas/contextualAiActionIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 160 }, "organizationId": { "type": "string", "format": "uuid" }, "path": { "type": "string", "minLength": 1, "maxLength": 1024, "pattern": "^/" } }, "required": [ "id", "kind", "label" ], "additionalProperties": false }, "contextualAiActionVersionSchema": { "type": "string", "minLength": 1, "maxLength": 40, "pattern": "^[a-z0-9]+(?:[.-][a-z0-9]+)*$" }, "dateTimeSchema": { "type": "string" }, "inboxMessagesResponseSchema": { "type": "object", "properties": { "deliveries": { "type": "array", "items": { "$ref": "#/components/schemas/channelMessageDeliverySchema" } }, "draftSuggestion": { "anyOf": [ { "$ref": "#/components/schemas/channelDraftSuggestionSchema" }, { "type": "null" } ] }, "messages": { "type": "array", "items": { "$ref": "#/components/schemas/chatMessageSchema" } }, "pagination": { "$ref": "#/components/schemas/inboxPaginationSchema" }, "senderProfiles": { "type": "object", "additionalProperties": { "type": "object", "properties": { "avatarUrl": { "type": [ "string", "null" ], "format": "uri" }, "displayName": { "type": "string" }, "userId": { "type": "string", "format": "uuid" } }, "required": [ "avatarUrl", "displayName", "userId" ] }, "propertyNames": { "type": "string" } }, "streamStatus": { "type": "string" } }, "required": [ "deliveries", "draftSuggestion", "messages", "pagination", "senderProfiles", "streamStatus" ] }, "inboxPaginationSchema": { "type": "object", "properties": { "hasMore": { "type": "boolean" }, "limit": { "type": "integer", "minimum": 1, "maximum": 100 }, "offset": { "type": "integer", "minimum": 0 }, "total": { "type": "integer", "minimum": 0 } }, "required": [ "hasMore", "limit", "offset", "total" ] }, "jsonValueSchema": { "anyOf": [ { "type": "string" }, { "type": "number" }, { "type": "boolean" }, { "type": "null" }, { "type": "array", "items": { "$ref": "#/components/schemas/jsonValueSchema" } }, { "type": "object", "additionalProperties": { "$ref": "#/components/schemas/jsonValueSchema" }, "propertyNames": { "type": "string" } } ] }, "metadataSchema": { "type": "object", "additionalProperties": { "$ref": "#/components/schemas/jsonValueSchema" }, "propertyNames": { "type": "string" } } } ``` # Send a staff reply to an inbox thread. (/openapi/sendInboxThreadReply) ## POST /v1/organizations/{organizationId}/inbox/threads/{threadId}/messages ```json { "operationId": "sendInboxThreadReply", "summary": "Send a staff reply to an inbox thread.", "description": "", "tags": [ "Inbox" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "path", "name": "threadId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/inboxReplyRequestSchema" } } }, "description": "Text and existing inbox attachment references to deliver.", "required": true }, "responses": { "201": { "description": "Returns the persisted message and external delivery state.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/inboxReplyResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "channelDeliveryStatusSchema": { "type": "string", "enum": [ "queued", "sent", "delivered", "failed", "dead_lettered", "canceled" ] }, "channelMessageDeliverySchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "organizationId": { "type": "string", "format": "uuid" }, "connectionId": { "type": "string", "format": "uuid" }, "chatMessageId": { "type": [ "string", "null" ], "format": "uuid" }, "externalMessageId": { "type": [ "string", "null" ], "minLength": 1, "maxLength": 240 }, "direction": { "type": "string", "enum": [ "inbound", "outbound" ] }, "status": { "$ref": "#/components/schemas/channelDeliveryStatusSchema" }, "idempotencyKey": { "type": [ "string", "null" ], "minLength": 1, "maxLength": 240 }, "errorMessage": { "type": [ "string", "null" ], "maxLength": 2000 }, "nextAttemptAt": { "anyOf": [ { "$ref": "#/components/schemas/dateTimeSchema" }, { "type": "null" } ], "default": null }, "canceledAt": { "anyOf": [ { "$ref": "#/components/schemas/dateTimeSchema" }, { "type": "null" } ], "default": null }, "retryCount": { "type": "integer", "minimum": 0, "default": 0 }, "metadata": { "$ref": "#/components/schemas/metadataSchema" }, "createdAt": { "$ref": "#/components/schemas/dateTimeSchema" }, "updatedAt": { "$ref": "#/components/schemas/dateTimeSchema" } }, "required": [ "id", "organizationId", "connectionId", "chatMessageId", "externalMessageId", "direction", "status", "idempotencyKey", "errorMessage", "nextAttemptAt", "canceledAt", "retryCount", "metadata", "createdAt", "updatedAt" ] }, "chatMentionKindSchema": { "type": "string", "enum": [ "user", "team", "customer", "customer-asset", "contact", "product", "task", "quote", "invoice" ] }, "chatMessageFeedbackRatingSchema": { "type": "string", "enum": [ "positive", "negative" ] }, "chatMessageFeedbackSchema": { "type": "object", "properties": { "messageId": { "type": "string", "format": "uuid" }, "organizationId": { "type": "string", "format": "uuid" }, "rating": { "$ref": "#/components/schemas/chatMessageFeedbackRatingSchema" }, "userId": { "type": "string", "format": "uuid" }, "modelId": { "type": [ "string", "null" ], "minLength": 1, "maxLength": 160 }, "runId": { "type": [ "string", "null" ], "format": "uuid" }, "createdAt": { "$ref": "#/components/schemas/dateTimeSchema" }, "updatedAt": { "$ref": "#/components/schemas/dateTimeSchema" } }, "required": [ "messageId", "organizationId", "rating", "userId", "modelId", "runId", "createdAt", "updatedAt" ] }, "chatMessagePartSchema": { "type": "object", "discriminator": { "propertyName": "type" }, "oneOf": [ { "$ref": "#/components/schemas/contextualAiActionChatPartSchema" }, { "type": "object", "properties": { "type": { "type": "string", "enum": [ "text" ] }, "text": { "type": "string" } }, "required": [ "type", "text" ] }, { "type": "object", "properties": { "type": { "type": "string", "enum": [ "data" ] }, "data": { "$ref": "#/components/schemas/metadataSchema" } }, "required": [ "type", "data" ] }, { "type": "object", "properties": { "type": { "type": "string", "enum": [ "tool" ] }, "toolCallId": { "type": "string", "minLength": 1 }, "toolName": { "type": "string", "minLength": 1 }, "state": { "type": "string", "enum": [ "input-streaming", "input-available", "approval-requested", "approval-responded", "output-available", "output-error", "output-denied" ] }, "input": { "allOf": [ { "$ref": "#/components/schemas/jsonValueSchema" } ] }, "output": { "allOf": [ { "$ref": "#/components/schemas/jsonValueSchema" } ] }, "errorText": { "type": "string", "maxLength": 1000 }, "approval": { "allOf": [ { "$ref": "#/components/schemas/metadataSchema" } ] }, "metadata": { "allOf": [ { "$ref": "#/components/schemas/metadataSchema" } ] } }, "required": [ "type", "toolCallId", "toolName", "state" ] }, { "type": "object", "properties": { "type": { "type": "string", "enum": [ "source-url" ] }, "sourceId": { "type": "string", "minLength": 1, "maxLength": 240 }, "title": { "type": "string", "minLength": 1, "maxLength": 240 }, "url": { "type": "string", "format": "uri" } }, "required": [ "type", "url" ] }, { "type": "object", "properties": { "type": { "type": "string", "enum": [ "file" ] }, "attachmentId": { "type": "string", "format": "uuid" }, "mediaType": { "type": "string", "minLength": 1 }, "name": { "type": "string", "minLength": 1, "maxLength": 240 } }, "required": [ "type", "attachmentId", "mediaType", "name" ] }, { "type": "object", "properties": { "type": { "type": "string", "enum": [ "mention" ] }, "href": { "type": "string", "minLength": 1, "maxLength": 1024 }, "id": { "type": "string", "minLength": 1, "maxLength": 240 }, "kind": { "$ref": "#/components/schemas/chatMentionKindSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 240 }, "metadata": { "allOf": [ { "$ref": "#/components/schemas/metadataSchema" } ] } }, "required": [ "type", "id", "kind", "label" ] }, { "type": "object", "properties": { "type": { "type": "string", "enum": [ "command" ] }, "command": { "type": "string", "minLength": 1, "maxLength": 80 }, "label": { "type": "string", "minLength": 1, "maxLength": 240 }, "workflowId": { "type": "string", "format": "uuid" }, "status": { "type": "string", "enum": [ "selected", "executed", "rejected" ] }, "runId": { "type": "string", "minLength": 1, "maxLength": 240 }, "error": { "type": "string", "minLength": 1, "maxLength": 1000 }, "start": { "type": "integer", "minimum": 0 }, "end": { "type": "integer", "exclusiveMinimum": 0 } }, "required": [ "type", "command", "label", "status" ] } ] }, "chatMessageRoleSchema": { "type": "string", "enum": [ "system", "user", "assistant", "tool" ] }, "chatMessageSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "threadId": { "type": "string", "format": "uuid" }, "organizationId": { "type": [ "string", "null" ], "format": "uuid" }, "clientMessageId": { "type": [ "string", "null" ], "minLength": 1, "maxLength": 240, "default": null }, "role": { "$ref": "#/components/schemas/chatMessageRoleSchema" }, "parts": { "type": "array", "items": { "$ref": "#/components/schemas/chatMessagePartSchema" } }, "metadata": { "$ref": "#/components/schemas/metadataSchema" }, "status": { "type": "string", "enum": [ "complete", "partial", "failed" ] }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "createdAt": { "$ref": "#/components/schemas/dateTimeSchema" }, "viewerFeedback": { "anyOf": [ { "$ref": "#/components/schemas/chatMessageFeedbackSchema" }, { "type": "null" } ] } }, "required": [ "id", "threadId", "organizationId", "clientMessageId", "role", "parts", "metadata", "status", "createdBy", "createdAt" ] }, "contextualAiActionChatPartSchema": { "type": "object", "properties": { "actionId": { "$ref": "#/components/schemas/contextualAiActionIdSchema" }, "actionVersion": { "$ref": "#/components/schemas/contextualAiActionVersionSchema" }, "approvalConfirmed": { "type": "boolean" }, "contextFingerprint": { "$ref": "#/components/schemas/contextualAiActionFingerprintSchema" }, "contextKinds": { "type": "array", "items": { "$ref": "#/components/schemas/contextualAiActionContextKindSchema" }, "minItems": 1, "maxItems": 8 }, "intakeAnswers": { "type": "array", "items": { "$ref": "#/components/schemas/contextualAiActionIntakeAnswerSchema" }, "maxItems": 12 }, "route": { "type": "string", "minLength": 1, "maxLength": 2048, "pattern": "^/" }, "safeReferences": { "type": "array", "items": { "$ref": "#/components/schemas/contextualAiActionSafeReferenceSchema" }, "maxItems": 20 }, "type": { "type": "string", "enum": [ "action" ] } }, "required": [ "actionId", "actionVersion", "approvalConfirmed", "contextFingerprint", "contextKinds", "route", "safeReferences", "type" ] }, "contextualAiActionContextKindSchema": { "type": "string", "enum": [ "page", "organization", "entity", "selection", "form", "thread", "collection", "workflow", "filter", "schedule", "template" ] }, "contextualAiActionFingerprintSchema": { "type": "string", "minLength": 8, "maxLength": 160, "pattern": "^sha256:[A-Za-z0-9_-]+$" }, "contextualAiActionIdSchema": { "type": "string", "minLength": 1, "maxLength": 120, "pattern": "^[a-z0-9]+(?:[.-][a-z0-9]+)*$" }, "contextualAiActionIntakeAnswerSchema": { "type": "object", "properties": { "fieldId": { "$ref": "#/components/schemas/contextualAiActionIdSchema" }, "value": { "$ref": "#/components/schemas/contextualAiActionIntakeValueSchema" } }, "required": [ "fieldId", "value" ], "additionalProperties": false }, "contextualAiActionIntakeValueSchema": { "anyOf": [ { "type": "string", "maxLength": 10000 }, { "type": "array", "items": { "type": "string", "maxLength": 1000 }, "maxItems": 12 } ] }, "contextualAiActionSafeReferenceSchema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "maxLength": 160 }, "kind": { "$ref": "#/components/schemas/contextualAiActionIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 160 }, "organizationId": { "type": "string", "format": "uuid" }, "path": { "type": "string", "minLength": 1, "maxLength": 1024, "pattern": "^/" } }, "required": [ "id", "kind", "label" ], "additionalProperties": false }, "contextualAiActionVersionSchema": { "type": "string", "minLength": 1, "maxLength": 40, "pattern": "^[a-z0-9]+(?:[.-][a-z0-9]+)*$" }, "dateTimeSchema": { "type": "string" }, "inboxReplyAttachmentSchema": { "type": "object", "properties": { "attachmentId": { "type": "string", "format": "uuid" }, "mediaType": { "type": "string", "minLength": 1, "maxLength": 240 }, "name": { "type": "string", "minLength": 1, "maxLength": 240 } }, "required": [ "attachmentId", "mediaType", "name" ] }, "inboxReplyRequestSchema": { "type": "object", "properties": { "files": { "type": "array", "items": { "$ref": "#/components/schemas/inboxReplyAttachmentSchema" }, "maxItems": 10 }, "text": { "type": "string", "maxLength": 4000, "default": "" } }, "required": [ "files", "text" ] }, "inboxReplyResponseSchema": { "type": "object", "properties": { "delivery": { "$ref": "#/components/schemas/channelMessageDeliverySchema" }, "message": { "$ref": "#/components/schemas/chatMessageSchema" }, "sent": { "type": "boolean" } }, "required": [ "delivery", "message", "sent" ] }, "jsonValueSchema": { "anyOf": [ { "type": "string" }, { "type": "number" }, { "type": "boolean" }, { "type": "null" }, { "type": "array", "items": { "$ref": "#/components/schemas/jsonValueSchema" } }, { "type": "object", "additionalProperties": { "$ref": "#/components/schemas/jsonValueSchema" }, "propertyNames": { "type": "string" } } ] }, "metadataSchema": { "type": "object", "additionalProperties": { "$ref": "#/components/schemas/jsonValueSchema" }, "propertyNames": { "type": "string" } } } ``` # Generate inbox reply suggestions. (/openapi/generateInboxSuggestions) ## POST /v1/organizations/{organizationId}/inbox/threads/{threadId}/suggestions ```json { "operationId": "generateInboxSuggestions", "summary": "Generate inbox reply suggestions.", "description": "", "tags": [ "Inbox" ], "parameters": [ { "in": "query", "name": "locale", "required": true, "schema": { "type": [ "string", "null" ], "minLength": 2, "maxLength": 12, "default": null }, "example": "example" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "path", "name": "threadId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/inboxSuggestionsBodySchema" } } }, "description": "Previous suggestions to avoid when generating replacements." }, "responses": { "200": { "description": "Returns generated customer reply suggestions.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/inboxSuggestionsResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "inboxSuggestionsBodySchema": { "type": "object", "properties": { "previousSuggestions": { "type": "array", "items": { "type": "string", "minLength": 1, "maxLength": 160 }, "maxItems": 20 } }, "required": [ "previousSuggestions" ] }, "inboxSuggestionsResponseSchema": { "type": "object", "properties": { "suggestions": { "type": "array", "items": { "type": "string", "minLength": 1, "maxLength": 160 } } }, "required": [ "suggestions" ] } } ``` # List stock levels per variant. (/openapi/listInventoryLevels) Return on hand, reserved, available, backordered, and incoming quantities for every stock-tracked variant, for webshop stock sync. Subscribe to the `inventory.level-changed` webhook to hear about changes. ## GET /v1/organizations/{organizationId}/inventory/levels ```json { "operationId": "listInventoryLevels", "summary": "List stock levels per variant.", "description": "Return on hand, reserved, available, backordered, and incoming quantities for every stock-tracked variant, for webshop stock sync. Subscribe to the `inventory.level-changed` webhook to hear about changes.", "tags": [ "Inventory" ], "parameters": [ { "in": "query", "name": "productId", "required": false, "schema": { "allOf": [ { "$ref": "#/components/schemas/productIdSchema" } ] }, "example": "123" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns stock levels per variant.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/inventoryLevelsResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "inventoryLevelsResponseSchema": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/inventoryVariantLevelSchema" } } }, "required": [ "data" ] }, "inventoryVariantLevelSchema": { "type": "object", "properties": { "variantId": { "type": "string", "format": "uuid" }, "productId": { "$ref": "#/components/schemas/productIdSchema" }, "onHand": { "type": "number" }, "allocated": { "type": "number" }, "available": { "type": "number" }, "backordered": { "type": "number" }, "incoming": { "type": "number" }, "reorderPoint": { "type": [ "number", "null" ] }, "stockStatus": { "$ref": "#/components/schemas/stockStatusSchema" }, "variantTitle": { "type": "string" }, "sku": { "type": [ "string", "null" ] }, "isDefault": { "type": "boolean" }, "position": { "type": "integer" } }, "required": [ "variantId", "productId", "onHand", "allocated", "available", "backordered", "incoming", "reorderPoint", "stockStatus", "variantTitle", "sku", "isDefault", "position" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "productIdSchema": { "type": "string", "format": "uuid" }, "stockStatusSchema": { "type": "string", "enum": [ "not_tracked", "discontinued", "out_of_stock", "backordered", "low_stock", "in_stock" ] } } ``` # List invoices. (/openapi/listInvoices) Return invoices for an organization the authenticated user can access. ## GET /v1/organizations/{organizationId}/invoices ```json { "operationId": "listInvoices", "summary": "List invoices.", "description": "Return invoices for an organization the authenticated user can access.", "tags": [ "Invoices" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns paginated invoices.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/invoicesListResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "invoicesListResponseSchema": { "type": "object" }, "organizationIdSchema": { "type": "string", "format": "uuid" } } ``` # Create an invoice. (/openapi/createInvoice) Create an invoice for the specified organization. ## POST /v1/organizations/{organizationId}/invoices ```json { "operationId": "createInvoice", "summary": "Create an invoice.", "description": "Create an invoice for the specified organization.", "tags": [ "Invoices" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/invoiceInputSchema" } } }, "description": "The invoice payload." }, "responses": { "201": { "description": "Returns the created invoice.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/invoiceSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "discountKindSchema": { "type": "string", "enum": [ "none", "percentage", "fixed" ] }, "documentLineStockModeSchema": { "type": "string", "enum": [ "auto", "task", "none" ] }, "documentVatRegimeSchema": { "type": "string", "enum": [ "standard", "reverse_charge", "exempt", "kor", "margin_scheme" ] }, "invoiceCurrencyCodeSchema": { "type": "string", "pattern": "^[A-Z]{3}$" }, "invoiceDisplayNumberSchema": { "type": "string", "minLength": 1, "maxLength": 40 }, "invoiceIdSchema": { "type": "string", "format": "uuid" }, "invoiceInputSchema": { "type": "object", "properties": { "customerId": { "type": "integer", "exclusiveMinimum": 0 }, "customerContactId": { "type": [ "string", "null" ], "format": "uuid" }, "customerReference": { "type": [ "string", "null" ], "maxLength": 240 }, "title": { "type": "string", "minLength": 1, "maxLength": 240 }, "intro": { "type": [ "string", "null" ], "maxLength": 20000 }, "terms": { "type": [ "string", "null" ], "maxLength": 20000 }, "issueDate": { "allOf": [ { "$ref": "#/components/schemas/isoDateOrNull" } ] }, "dueDate": { "allOf": [ { "$ref": "#/components/schemas/isoDateOrNull" } ] }, "currency": { "allOf": [ { "$ref": "#/components/schemas/invoiceCurrencyCodeSchema" } ] }, "vatInclusive": { "type": "boolean" }, "onlinePaymentEnabled": { "type": "boolean" }, "discountKind": { "allOf": [ { "$ref": "#/components/schemas/discountKindSchema" } ] }, "discountValue": { "type": "number", "minimum": 0 }, "discountDescription": { "type": [ "string", "null" ], "maxLength": 500 }, "promotionId": { "type": [ "string", "null" ], "format": "uuid" }, "publicMessage": { "type": [ "string", "null" ], "maxLength": 20000 }, "footerText": { "type": [ "string", "null" ], "maxLength": 20000 }, "dueDays": { "type": [ "integer", "null" ], "minimum": 0, "maximum": 365 }, "invoiceTemplateId": { "type": [ "string", "null" ], "format": "uuid" }, "sourceQuoteId": { "type": [ "string", "null" ], "format": "uuid" }, "deliveryDate": { "allOf": [ { "$ref": "#/components/schemas/isoDateOrNull" } ] }, "kind": { "allOf": [ { "$ref": "#/components/schemas/invoiceKindSchema" } ] }, "originalInvoiceId": { "type": [ "string", "null" ], "format": "uuid" }, "vatRegime": { "allOf": [ { "$ref": "#/components/schemas/documentVatRegimeSchema" } ] }, "termsRichText": { "type": [ "string", "null" ], "maxLength": 100000 }, "termsUrl": { "allOf": [ { "$ref": "#/components/schemas/optionalUrl" } ] }, "termsPdfFileId": { "type": [ "string", "null" ], "format": "uuid" }, "termsPdfVersionId": { "type": [ "string", "null" ], "format": "uuid" }, "lines": { "type": "array", "items": { "$ref": "#/components/schemas/invoiceLineInputSchema" }, "maxItems": 500 }, "linkedTaskIds": { "type": "array", "items": { "type": "string", "format": "uuid" }, "maxItems": 200 } }, "required": [ "customerId", "title" ] }, "invoiceKindSchema": { "type": "string", "enum": [ "invoice", "credit_note", "deposit" ] }, "invoiceLineIdSchema": { "type": "string", "format": "uuid" }, "invoiceLineInputSchema": { "type": "object", "properties": { "id": { "allOf": [ { "$ref": "#/components/schemas/invoiceLineIdSchema" } ] }, "kind": { "allOf": [ { "$ref": "#/components/schemas/invoiceLineKindSchema" } ] }, "lineType": { "anyOf": [ { "$ref": "#/components/schemas/invoiceLineTypeSchema" }, { "type": "null" } ] }, "productId": { "type": [ "string", "null" ], "format": "uuid" }, "variantId": { "type": [ "string", "null" ], "format": "uuid" }, "taskId": { "type": [ "string", "null" ], "format": "uuid" }, "title": { "type": "string", "minLength": 1, "maxLength": 500 }, "description": { "type": [ "string", "null" ], "maxLength": 20000 }, "quantity": { "type": "number", "minimum": 0 }, "unit": { "type": [ "string", "null" ], "maxLength": 40 }, "unitPrice": { "type": "number" }, "vatRate": { "type": "number", "minimum": 0, "maximum": 100 }, "discountKind": { "allOf": [ { "$ref": "#/components/schemas/discountKindSchema" } ] }, "discountValue": { "type": "number", "minimum": 0 }, "promotionId": { "type": [ "string", "null" ], "format": "uuid" }, "pricingMode": { "allOf": [ { "$ref": "#/components/schemas/invoiceLinePricingModeSchema" } ] }, "position": { "type": "number" }, "stockMode": { "allOf": [ { "$ref": "#/components/schemas/documentLineStockModeSchema" } ] }, "expenseId": { "type": "string", "format": "uuid" }, "sourceTimeEntryIds": { "type": "array", "items": { "type": "string", "format": "uuid" }, "maxItems": 2000 }, "sourceTaskMaterialId": { "type": "string", "format": "uuid" } }, "required": [ "title" ] }, "invoiceLineKindSchema": { "type": "string", "enum": [ "line", "section_header" ] }, "invoiceLinePricingModeSchema": { "type": "string", "enum": [ "unit", "hourly", "fixed" ] }, "invoiceLineTypeSchema": { "type": "string", "enum": [ "product", "labor", "custom" ] }, "invoiceNumberPrefixSchema": { "type": "string", "maxLength": 16, "pattern": "^[A-Z0-9-]*$" }, "invoiceNumberSchema": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 }, "invoiceSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/invoiceIdSchema" }, "organizationId": { "type": "string", "format": "uuid" }, "customerId": { "type": "integer", "exclusiveMinimum": 0 }, "customerContactId": { "type": [ "string", "null" ], "format": "uuid" }, "customerReference": { "type": [ "string", "null" ] }, "number": { "anyOf": [ { "$ref": "#/components/schemas/invoiceNumberSchema" }, { "type": "null" } ] }, "numberYear": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 }, "numberPrefix": { "$ref": "#/components/schemas/invoiceNumberPrefixSchema" }, "displayNumber": { "anyOf": [ { "$ref": "#/components/schemas/invoiceDisplayNumberSchema" }, { "type": "null" } ] }, "status": { "$ref": "#/components/schemas/invoiceStatusSchema" }, "dueDays": { "type": [ "integer", "null" ], "minimum": 0, "maximum": 365 }, "invoiceTemplateId": { "type": [ "string", "null" ], "format": "uuid" }, "title": { "type": "string" }, "intro": { "type": [ "string", "null" ] }, "terms": { "type": [ "string", "null" ] }, "currency": { "$ref": "#/components/schemas/invoiceCurrencyCodeSchema" }, "vatInclusive": { "type": "boolean" }, "discountKind": { "$ref": "#/components/schemas/discountKindSchema" }, "discountValue": { "type": "number" }, "discountDescription": { "type": [ "string", "null" ], "maxLength": 500 }, "promotionId": { "type": [ "string", "null" ], "format": "uuid" }, "subtotalExcl": { "type": "number" }, "vatTotal": { "type": "number" }, "totalIncl": { "type": "number" }, "issueDate": { "type": [ "string", "null" ] }, "dueDate": { "type": [ "string", "null" ] }, "publicMessage": { "type": [ "string", "null" ] }, "footerText": { "type": [ "string", "null" ] }, "sourceQuoteId": { "type": [ "string", "null" ], "format": "uuid" }, "deliveryDate": { "type": [ "string", "null" ] }, "kind": { "$ref": "#/components/schemas/invoiceKindSchema" }, "originalInvoiceId": { "type": [ "string", "null" ], "format": "uuid" }, "vatRegime": { "$ref": "#/components/schemas/documentVatRegimeSchema" }, "sellerName": { "type": [ "string", "null" ] }, "sellerAddressLine1": { "type": [ "string", "null" ] }, "sellerAddressLine2": { "type": [ "string", "null" ] }, "sellerAddressPostalCode": { "type": [ "string", "null" ] }, "sellerAddressCity": { "type": [ "string", "null" ] }, "sellerAddressCountry": { "type": [ "string", "null" ] }, "sellerEmail": { "type": [ "string", "null" ] }, "sellerPhone": { "type": [ "string", "null" ] }, "sellerKvk": { "type": [ "string", "null" ] }, "sellerBtw": { "type": [ "string", "null" ] }, "sellerIban": { "type": [ "string", "null" ] }, "buyerName": { "type": [ "string", "null" ] }, "buyerAddressLine1": { "type": [ "string", "null" ] }, "buyerAddressLine2": { "type": [ "string", "null" ] }, "buyerAddressPostalCode": { "type": [ "string", "null" ] }, "buyerAddressCity": { "type": [ "string", "null" ] }, "buyerAddressCountry": { "type": [ "string", "null" ] }, "buyerEmail": { "type": [ "string", "null" ] }, "buyerKvk": { "type": [ "string", "null" ] }, "buyerBtw": { "type": [ "string", "null" ] }, "termsRichText": { "type": [ "string", "null" ], "maxLength": 100000 }, "termsUrl": { "type": [ "string", "null" ] }, "termsPdfFileId": { "type": [ "string", "null" ], "format": "uuid" }, "termsPdfVersionId": { "type": [ "string", "null" ], "format": "uuid" }, "sentAt": { "type": [ "string", "null" ], "format": "date-time" }, "paidAt": { "type": [ "string", "null" ], "format": "date-time" }, "cancelledAt": { "type": [ "string", "null" ], "format": "date-time" }, "archivedAt": { "type": [ "string", "null" ], "format": "date-time" }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "updatedBy": { "type": [ "string", "null" ], "format": "uuid" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" }, "amountPaidCents": { "type": "integer", "minimum": 0 }, "amountDueCents": { "type": "integer", "minimum": 0 }, "uncollectibleAt": { "type": [ "string", "null" ], "format": "date-time" }, "uncollectibleReason": { "type": [ "string", "null" ] }, "onlinePaymentEnabled": { "type": "boolean", "default": true } }, "required": [ "id", "organizationId", "customerId", "customerContactId", "customerReference", "number", "numberYear", "numberPrefix", "displayNumber", "status", "dueDays", "invoiceTemplateId", "title", "intro", "terms", "currency", "vatInclusive", "discountKind", "discountValue", "discountDescription", "promotionId", "subtotalExcl", "vatTotal", "totalIncl", "issueDate", "dueDate", "publicMessage", "footerText", "sourceQuoteId", "deliveryDate", "kind", "originalInvoiceId", "vatRegime", "sellerName", "sellerAddressLine1", "sellerAddressLine2", "sellerAddressPostalCode", "sellerAddressCity", "sellerAddressCountry", "sellerEmail", "sellerPhone", "sellerKvk", "sellerBtw", "sellerIban", "buyerName", "buyerAddressLine1", "buyerAddressLine2", "buyerAddressPostalCode", "buyerAddressCity", "buyerAddressCountry", "buyerEmail", "buyerKvk", "buyerBtw", "termsRichText", "termsUrl", "termsPdfFileId", "termsPdfVersionId", "sentAt", "paidAt", "cancelledAt", "archivedAt", "createdBy", "updatedBy", "createdAt", "updatedAt", "amountPaidCents", "amountDueCents", "uncollectibleAt", "uncollectibleReason", "onlinePaymentEnabled" ] }, "invoiceStatusSchema": { "type": "string", "enum": [ "draft", "sent", "paid", "overdue", "cancelled", "uncollectible" ] }, "isoDateOrNull": { "type": [ "string", "null" ], "pattern": "^\\d{4}-\\d{2}-\\d{2}$" }, "optionalUrl": { "type": [ "string", "null" ], "maxLength": 2000 }, "organizationIdSchema": { "type": "string", "format": "uuid" } } ``` # Get an invoice. (/openapi/getInvoice) Return an invoice detail record for the specified organization. ## GET /v1/organizations/{organizationId}/invoices/{invoiceId} ```json { "operationId": "getInvoice", "summary": "Get an invoice.", "description": "Return an invoice detail record for the specified organization.", "tags": [ "Invoices" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "invoiceId", "required": true, "schema": { "$ref": "#/components/schemas/invoiceIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns the requested invoice.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/invoiceDetailSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "discountKindSchema": { "type": "string", "enum": [ "none", "percentage", "fixed" ] }, "documentLineStockModeSchema": { "type": "string", "enum": [ "auto", "task", "none" ] }, "documentVatRegimeSchema": { "type": "string", "enum": [ "standard", "reverse_charge", "exempt", "kor", "margin_scheme" ] }, "invoiceChargeKindSchema": { "type": "string", "enum": [ "collection_costs", "statutory_interest", "late_fee" ] }, "invoiceChargeSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "organizationId": { "type": "string", "format": "uuid" }, "invoiceId": { "$ref": "#/components/schemas/invoiceIdSchema" }, "kind": { "$ref": "#/components/schemas/invoiceChargeKindSchema" }, "amountCents": { "type": "integer", "exclusiveMinimum": 0 }, "currency": { "$ref": "#/components/schemas/invoiceCurrencyCodeSchema" }, "reason": { "type": [ "string", "null" ] }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "createdByRunStepId": { "type": [ "string", "null" ], "format": "uuid" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "invoiceId", "kind", "amountCents", "currency", "reason", "createdBy", "createdByRunStepId", "createdAt", "updatedAt" ] }, "invoiceCurrencyCodeSchema": { "type": "string", "pattern": "^[A-Z]{3}$" }, "invoiceDetailSchema": { "type": "object", "properties": { "invoice": { "$ref": "#/components/schemas/invoiceSchema" }, "lines": { "type": "array", "items": { "$ref": "#/components/schemas/invoiceLineSchema" } }, "statusHistory": { "type": "array", "items": { "$ref": "#/components/schemas/invoiceStatusHistoryEntrySchema" } }, "linkedTaskIds": { "type": "array", "items": { "type": "string", "format": "uuid" } }, "payment": { "anyOf": [ { "$ref": "#/components/schemas/invoicePaymentSchema" }, { "type": "null" } ] }, "payments": { "type": "array", "items": { "$ref": "#/components/schemas/invoicePaymentSchema" } }, "charges": { "type": "array", "items": { "$ref": "#/components/schemas/invoiceChargeSchema" } }, "originalDisplayNumber": { "type": [ "string", "null" ] }, "stockPreview": { "type": "array", "items": { "$ref": "#/components/schemas/invoiceStockPreviewLineSchema" } } }, "required": [ "invoice", "lines", "statusHistory", "linkedTaskIds", "payment", "payments", "charges", "originalDisplayNumber", "stockPreview" ] }, "invoiceDisplayNumberSchema": { "type": "string", "minLength": 1, "maxLength": 40 }, "invoiceIdSchema": { "type": "string", "format": "uuid" }, "invoiceKindSchema": { "type": "string", "enum": [ "invoice", "credit_note", "deposit" ] }, "invoiceLineIdSchema": { "type": "string", "format": "uuid" }, "invoiceLineKindSchema": { "type": "string", "enum": [ "line", "section_header" ] }, "invoiceLinePricingModeSchema": { "type": "string", "enum": [ "unit", "hourly", "fixed" ] }, "invoiceLineSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/invoiceLineIdSchema" }, "invoiceId": { "$ref": "#/components/schemas/invoiceIdSchema" }, "organizationId": { "type": "string", "format": "uuid" }, "kind": { "$ref": "#/components/schemas/invoiceLineKindSchema" }, "lineType": { "anyOf": [ { "$ref": "#/components/schemas/invoiceLineTypeSchema" }, { "type": "null" } ] }, "productId": { "type": [ "string", "null" ], "format": "uuid" }, "variantId": { "type": [ "string", "null" ], "format": "uuid" }, "taskId": { "type": [ "string", "null" ], "format": "uuid" }, "title": { "type": "string" }, "description": { "type": [ "string", "null" ] }, "quantity": { "type": "number" }, "unit": { "type": [ "string", "null" ] }, "unitPrice": { "type": "number" }, "vatRate": { "type": "number" }, "discountKind": { "$ref": "#/components/schemas/discountKindSchema" }, "discountValue": { "type": "number" }, "promotionId": { "type": [ "string", "null" ], "format": "uuid" }, "pricingMode": { "$ref": "#/components/schemas/invoiceLinePricingModeSchema" }, "position": { "type": "number" }, "stockMode": { "$ref": "#/components/schemas/documentLineStockModeSchema" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "invoiceId", "organizationId", "kind", "lineType", "productId", "variantId", "taskId", "title", "description", "quantity", "unit", "unitPrice", "vatRate", "discountKind", "discountValue", "promotionId", "pricingMode", "position", "stockMode", "createdAt", "updatedAt" ] }, "invoiceLineTypeSchema": { "type": "string", "enum": [ "product", "labor", "custom" ] }, "invoiceNumberPrefixSchema": { "type": "string", "maxLength": 16, "pattern": "^[A-Z0-9-]*$" }, "invoiceNumberSchema": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 }, "invoicePaymentMethodSchema": { "type": "string", "enum": [ "stripe", "bank_transfer", "cash", "other" ] }, "invoicePaymentSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "organizationId": { "type": "string", "format": "uuid" }, "invoiceId": { "$ref": "#/components/schemas/invoiceIdSchema" }, "provider": { "type": "string", "enum": [ "stripe", "manual" ] }, "method": { "$ref": "#/components/schemas/invoicePaymentMethodSchema" }, "stripePaymentIntentId": { "type": [ "string", "null" ], "minLength": 1, "maxLength": 255 }, "stripeChargeId": { "type": [ "string", "null" ], "minLength": 1, "maxLength": 255 }, "stripeDestinationAccountId": { "type": [ "string", "null" ], "minLength": 1, "maxLength": 255 }, "status": { "$ref": "#/components/schemas/invoicePaymentStatusSchema" }, "currency": { "$ref": "#/components/schemas/invoiceCurrencyCodeSchema" }, "amountTotal": { "type": "integer", "minimum": 0 }, "applicationFeeMode": { "type": "string", "enum": [ "percentage", "fixed" ] }, "applicationFeeAmount": { "type": "integer", "minimum": 0 }, "applicationFeeBps": { "type": "integer", "minimum": 0, "maximum": 10000 }, "failureCode": { "type": [ "string", "null" ] }, "failureMessage": { "type": [ "string", "null" ] }, "paidAt": { "type": [ "string", "null" ], "format": "date-time" }, "canceledAt": { "type": [ "string", "null" ], "format": "date-time" }, "refundedAt": { "type": [ "string", "null" ], "format": "date-time" }, "disputedAt": { "type": [ "string", "null" ], "format": "date-time" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" }, "amountCents": { "type": "integer", "minimum": 0 }, "receivedOn": { "type": [ "string", "null" ], "format": "date" }, "reference": { "type": [ "string", "null" ] }, "note": { "type": [ "string", "null" ] }, "recordedBy": { "type": [ "string", "null" ], "format": "uuid" } }, "required": [ "id", "organizationId", "invoiceId", "provider", "method", "stripePaymentIntentId", "stripeChargeId", "stripeDestinationAccountId", "status", "currency", "amountTotal", "applicationFeeMode", "applicationFeeAmount", "applicationFeeBps", "failureCode", "failureMessage", "paidAt", "canceledAt", "refundedAt", "disputedAt", "createdAt", "updatedAt", "amountCents", "receivedOn", "reference", "note", "recordedBy" ] }, "invoicePaymentStatusSchema": { "type": "string", "enum": [ "requires_payment_method", "requires_action", "processing", "succeeded", "canceled", "failed", "refunded", "disputed" ] }, "invoiceSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/invoiceIdSchema" }, "organizationId": { "type": "string", "format": "uuid" }, "customerId": { "type": "integer", "exclusiveMinimum": 0 }, "customerContactId": { "type": [ "string", "null" ], "format": "uuid" }, "customerReference": { "type": [ "string", "null" ] }, "number": { "anyOf": [ { "$ref": "#/components/schemas/invoiceNumberSchema" }, { "type": "null" } ] }, "numberYear": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 }, "numberPrefix": { "$ref": "#/components/schemas/invoiceNumberPrefixSchema" }, "displayNumber": { "anyOf": [ { "$ref": "#/components/schemas/invoiceDisplayNumberSchema" }, { "type": "null" } ] }, "status": { "$ref": "#/components/schemas/invoiceStatusSchema" }, "dueDays": { "type": [ "integer", "null" ], "minimum": 0, "maximum": 365 }, "invoiceTemplateId": { "type": [ "string", "null" ], "format": "uuid" }, "title": { "type": "string" }, "intro": { "type": [ "string", "null" ] }, "terms": { "type": [ "string", "null" ] }, "currency": { "$ref": "#/components/schemas/invoiceCurrencyCodeSchema" }, "vatInclusive": { "type": "boolean" }, "discountKind": { "$ref": "#/components/schemas/discountKindSchema" }, "discountValue": { "type": "number" }, "discountDescription": { "type": [ "string", "null" ], "maxLength": 500 }, "promotionId": { "type": [ "string", "null" ], "format": "uuid" }, "subtotalExcl": { "type": "number" }, "vatTotal": { "type": "number" }, "totalIncl": { "type": "number" }, "issueDate": { "type": [ "string", "null" ] }, "dueDate": { "type": [ "string", "null" ] }, "publicMessage": { "type": [ "string", "null" ] }, "footerText": { "type": [ "string", "null" ] }, "sourceQuoteId": { "type": [ "string", "null" ], "format": "uuid" }, "deliveryDate": { "type": [ "string", "null" ] }, "kind": { "$ref": "#/components/schemas/invoiceKindSchema" }, "originalInvoiceId": { "type": [ "string", "null" ], "format": "uuid" }, "vatRegime": { "$ref": "#/components/schemas/documentVatRegimeSchema" }, "sellerName": { "type": [ "string", "null" ] }, "sellerAddressLine1": { "type": [ "string", "null" ] }, "sellerAddressLine2": { "type": [ "string", "null" ] }, "sellerAddressPostalCode": { "type": [ "string", "null" ] }, "sellerAddressCity": { "type": [ "string", "null" ] }, "sellerAddressCountry": { "type": [ "string", "null" ] }, "sellerEmail": { "type": [ "string", "null" ] }, "sellerPhone": { "type": [ "string", "null" ] }, "sellerKvk": { "type": [ "string", "null" ] }, "sellerBtw": { "type": [ "string", "null" ] }, "sellerIban": { "type": [ "string", "null" ] }, "buyerName": { "type": [ "string", "null" ] }, "buyerAddressLine1": { "type": [ "string", "null" ] }, "buyerAddressLine2": { "type": [ "string", "null" ] }, "buyerAddressPostalCode": { "type": [ "string", "null" ] }, "buyerAddressCity": { "type": [ "string", "null" ] }, "buyerAddressCountry": { "type": [ "string", "null" ] }, "buyerEmail": { "type": [ "string", "null" ] }, "buyerKvk": { "type": [ "string", "null" ] }, "buyerBtw": { "type": [ "string", "null" ] }, "termsRichText": { "type": [ "string", "null" ], "maxLength": 100000 }, "termsUrl": { "type": [ "string", "null" ] }, "termsPdfFileId": { "type": [ "string", "null" ], "format": "uuid" }, "termsPdfVersionId": { "type": [ "string", "null" ], "format": "uuid" }, "sentAt": { "type": [ "string", "null" ], "format": "date-time" }, "paidAt": { "type": [ "string", "null" ], "format": "date-time" }, "cancelledAt": { "type": [ "string", "null" ], "format": "date-time" }, "archivedAt": { "type": [ "string", "null" ], "format": "date-time" }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "updatedBy": { "type": [ "string", "null" ], "format": "uuid" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" }, "amountPaidCents": { "type": "integer", "minimum": 0 }, "amountDueCents": { "type": "integer", "minimum": 0 }, "uncollectibleAt": { "type": [ "string", "null" ], "format": "date-time" }, "uncollectibleReason": { "type": [ "string", "null" ] }, "onlinePaymentEnabled": { "type": "boolean", "default": true } }, "required": [ "id", "organizationId", "customerId", "customerContactId", "customerReference", "number", "numberYear", "numberPrefix", "displayNumber", "status", "dueDays", "invoiceTemplateId", "title", "intro", "terms", "currency", "vatInclusive", "discountKind", "discountValue", "discountDescription", "promotionId", "subtotalExcl", "vatTotal", "totalIncl", "issueDate", "dueDate", "publicMessage", "footerText", "sourceQuoteId", "deliveryDate", "kind", "originalInvoiceId", "vatRegime", "sellerName", "sellerAddressLine1", "sellerAddressLine2", "sellerAddressPostalCode", "sellerAddressCity", "sellerAddressCountry", "sellerEmail", "sellerPhone", "sellerKvk", "sellerBtw", "sellerIban", "buyerName", "buyerAddressLine1", "buyerAddressLine2", "buyerAddressPostalCode", "buyerAddressCity", "buyerAddressCountry", "buyerEmail", "buyerKvk", "buyerBtw", "termsRichText", "termsUrl", "termsPdfFileId", "termsPdfVersionId", "sentAt", "paidAt", "cancelledAt", "archivedAt", "createdBy", "updatedBy", "createdAt", "updatedAt", "amountPaidCents", "amountDueCents", "uncollectibleAt", "uncollectibleReason", "onlinePaymentEnabled" ] }, "invoiceStatusHistoryEntrySchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "invoiceId": { "$ref": "#/components/schemas/invoiceIdSchema" }, "organizationId": { "type": "string", "format": "uuid" }, "fromStatus": { "anyOf": [ { "$ref": "#/components/schemas/invoiceStatusSchema" }, { "type": "null" } ] }, "toStatus": { "$ref": "#/components/schemas/invoiceStatusSchema" }, "actorUserId": { "type": [ "string", "null" ], "format": "uuid" }, "reason": { "type": [ "string", "null" ] }, "createdAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "invoiceId", "organizationId", "fromStatus", "toStatus", "actorUserId", "reason", "createdAt" ] }, "invoiceStatusSchema": { "type": "string", "enum": [ "draft", "sent", "paid", "overdue", "cancelled", "uncollectible" ] }, "invoiceStockPreviewLineSchema": { "type": "object", "properties": { "lineId": { "type": "string", "format": "uuid" }, "productId": { "type": "string", "format": "uuid" }, "title": { "type": "string" }, "requestedQuantity": { "type": "number", "minimum": 0 }, "availableQuantity": { "type": "number", "minimum": 0 }, "shortQuantity": { "type": "number", "exclusiveMinimum": 0 } }, "required": [ "lineId", "productId", "title", "requestedQuantity", "availableQuantity", "shortQuantity" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" } } ``` # Update an invoice. (/openapi/updateInvoice) Update an invoice for the specified organization. ## PATCH /v1/organizations/{organizationId}/invoices/{invoiceId} ```json { "operationId": "updateInvoice", "summary": "Update an invoice.", "description": "Update an invoice for the specified organization.", "tags": [ "Invoices" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "invoiceId", "required": true, "schema": { "$ref": "#/components/schemas/invoiceIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/invoicePatchSchema" } } }, "description": "The invoice patch payload." }, "responses": { "200": { "description": "Returns the updated invoice.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/invoiceSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "discountKindSchema": { "type": "string", "enum": [ "none", "percentage", "fixed" ] }, "documentLineStockModeSchema": { "type": "string", "enum": [ "auto", "task", "none" ] }, "documentVatRegimeSchema": { "type": "string", "enum": [ "standard", "reverse_charge", "exempt", "kor", "margin_scheme" ] }, "invoiceCurrencyCodeSchema": { "type": "string", "pattern": "^[A-Z]{3}$" }, "invoiceDisplayNumberSchema": { "type": "string", "minLength": 1, "maxLength": 40 }, "invoiceIdSchema": { "type": "string", "format": "uuid" }, "invoiceKindSchema": { "type": "string", "enum": [ "invoice", "credit_note", "deposit" ] }, "invoiceLineIdSchema": { "type": "string", "format": "uuid" }, "invoiceLineInputSchema": { "type": "object", "properties": { "id": { "allOf": [ { "$ref": "#/components/schemas/invoiceLineIdSchema" } ] }, "kind": { "allOf": [ { "$ref": "#/components/schemas/invoiceLineKindSchema" } ] }, "lineType": { "anyOf": [ { "$ref": "#/components/schemas/invoiceLineTypeSchema" }, { "type": "null" } ] }, "productId": { "type": [ "string", "null" ], "format": "uuid" }, "variantId": { "type": [ "string", "null" ], "format": "uuid" }, "taskId": { "type": [ "string", "null" ], "format": "uuid" }, "title": { "type": "string", "minLength": 1, "maxLength": 500 }, "description": { "type": [ "string", "null" ], "maxLength": 20000 }, "quantity": { "type": "number", "minimum": 0 }, "unit": { "type": [ "string", "null" ], "maxLength": 40 }, "unitPrice": { "type": "number" }, "vatRate": { "type": "number", "minimum": 0, "maximum": 100 }, "discountKind": { "allOf": [ { "$ref": "#/components/schemas/discountKindSchema" } ] }, "discountValue": { "type": "number", "minimum": 0 }, "promotionId": { "type": [ "string", "null" ], "format": "uuid" }, "pricingMode": { "allOf": [ { "$ref": "#/components/schemas/invoiceLinePricingModeSchema" } ] }, "position": { "type": "number" }, "stockMode": { "allOf": [ { "$ref": "#/components/schemas/documentLineStockModeSchema" } ] }, "expenseId": { "type": "string", "format": "uuid" }, "sourceTimeEntryIds": { "type": "array", "items": { "type": "string", "format": "uuid" }, "maxItems": 2000 }, "sourceTaskMaterialId": { "type": "string", "format": "uuid" } }, "required": [ "title" ] }, "invoiceLineKindSchema": { "type": "string", "enum": [ "line", "section_header" ] }, "invoiceLinePricingModeSchema": { "type": "string", "enum": [ "unit", "hourly", "fixed" ] }, "invoiceLineTypeSchema": { "type": "string", "enum": [ "product", "labor", "custom" ] }, "invoiceNumberPrefixSchema": { "type": "string", "maxLength": 16, "pattern": "^[A-Z0-9-]*$" }, "invoiceNumberSchema": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 }, "invoicePatchSchema": { "type": "object", "properties": { "customerId": { "type": "integer", "exclusiveMinimum": 0 }, "customerContactId": { "type": [ "string", "null" ], "format": "uuid" }, "customerReference": { "type": [ "string", "null" ], "maxLength": 240 }, "title": { "type": "string", "minLength": 1, "maxLength": 240 }, "intro": { "type": [ "string", "null" ], "maxLength": 20000 }, "terms": { "type": [ "string", "null" ], "maxLength": 20000 }, "issueDate": { "allOf": [ { "$ref": "#/components/schemas/isoDateOrNull" } ] }, "dueDate": { "allOf": [ { "$ref": "#/components/schemas/isoDateOrNull" } ] }, "currency": { "allOf": [ { "$ref": "#/components/schemas/invoiceCurrencyCodeSchema" } ] }, "vatInclusive": { "type": "boolean" }, "onlinePaymentEnabled": { "type": "boolean" }, "discountKind": { "allOf": [ { "$ref": "#/components/schemas/discountKindSchema" } ] }, "discountValue": { "type": "number", "minimum": 0 }, "discountDescription": { "type": [ "string", "null" ], "maxLength": 500 }, "promotionId": { "type": [ "string", "null" ], "format": "uuid" }, "publicMessage": { "type": [ "string", "null" ], "maxLength": 20000 }, "footerText": { "type": [ "string", "null" ], "maxLength": 20000 }, "dueDays": { "type": [ "integer", "null" ], "minimum": 0, "maximum": 365 }, "invoiceTemplateId": { "type": [ "string", "null" ], "format": "uuid" }, "sourceQuoteId": { "type": [ "string", "null" ], "format": "uuid" }, "deliveryDate": { "allOf": [ { "$ref": "#/components/schemas/isoDateOrNull" } ] }, "kind": { "allOf": [ { "$ref": "#/components/schemas/invoiceKindSchema" } ] }, "originalInvoiceId": { "type": [ "string", "null" ], "format": "uuid" }, "vatRegime": { "allOf": [ { "$ref": "#/components/schemas/documentVatRegimeSchema" } ] }, "termsRichText": { "type": [ "string", "null" ], "maxLength": 100000 }, "termsUrl": { "allOf": [ { "$ref": "#/components/schemas/optionalUrl" } ] }, "termsPdfFileId": { "type": [ "string", "null" ], "format": "uuid" }, "termsPdfVersionId": { "type": [ "string", "null" ], "format": "uuid" }, "lines": { "type": "array", "items": { "$ref": "#/components/schemas/invoiceLineInputSchema" }, "maxItems": 500 }, "linkedTaskIds": { "type": "array", "items": { "type": "string", "format": "uuid" }, "maxItems": 200 } } }, "invoiceSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/invoiceIdSchema" }, "organizationId": { "type": "string", "format": "uuid" }, "customerId": { "type": "integer", "exclusiveMinimum": 0 }, "customerContactId": { "type": [ "string", "null" ], "format": "uuid" }, "customerReference": { "type": [ "string", "null" ] }, "number": { "anyOf": [ { "$ref": "#/components/schemas/invoiceNumberSchema" }, { "type": "null" } ] }, "numberYear": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 }, "numberPrefix": { "$ref": "#/components/schemas/invoiceNumberPrefixSchema" }, "displayNumber": { "anyOf": [ { "$ref": "#/components/schemas/invoiceDisplayNumberSchema" }, { "type": "null" } ] }, "status": { "$ref": "#/components/schemas/invoiceStatusSchema" }, "dueDays": { "type": [ "integer", "null" ], "minimum": 0, "maximum": 365 }, "invoiceTemplateId": { "type": [ "string", "null" ], "format": "uuid" }, "title": { "type": "string" }, "intro": { "type": [ "string", "null" ] }, "terms": { "type": [ "string", "null" ] }, "currency": { "$ref": "#/components/schemas/invoiceCurrencyCodeSchema" }, "vatInclusive": { "type": "boolean" }, "discountKind": { "$ref": "#/components/schemas/discountKindSchema" }, "discountValue": { "type": "number" }, "discountDescription": { "type": [ "string", "null" ], "maxLength": 500 }, "promotionId": { "type": [ "string", "null" ], "format": "uuid" }, "subtotalExcl": { "type": "number" }, "vatTotal": { "type": "number" }, "totalIncl": { "type": "number" }, "issueDate": { "type": [ "string", "null" ] }, "dueDate": { "type": [ "string", "null" ] }, "publicMessage": { "type": [ "string", "null" ] }, "footerText": { "type": [ "string", "null" ] }, "sourceQuoteId": { "type": [ "string", "null" ], "format": "uuid" }, "deliveryDate": { "type": [ "string", "null" ] }, "kind": { "$ref": "#/components/schemas/invoiceKindSchema" }, "originalInvoiceId": { "type": [ "string", "null" ], "format": "uuid" }, "vatRegime": { "$ref": "#/components/schemas/documentVatRegimeSchema" }, "sellerName": { "type": [ "string", "null" ] }, "sellerAddressLine1": { "type": [ "string", "null" ] }, "sellerAddressLine2": { "type": [ "string", "null" ] }, "sellerAddressPostalCode": { "type": [ "string", "null" ] }, "sellerAddressCity": { "type": [ "string", "null" ] }, "sellerAddressCountry": { "type": [ "string", "null" ] }, "sellerEmail": { "type": [ "string", "null" ] }, "sellerPhone": { "type": [ "string", "null" ] }, "sellerKvk": { "type": [ "string", "null" ] }, "sellerBtw": { "type": [ "string", "null" ] }, "sellerIban": { "type": [ "string", "null" ] }, "buyerName": { "type": [ "string", "null" ] }, "buyerAddressLine1": { "type": [ "string", "null" ] }, "buyerAddressLine2": { "type": [ "string", "null" ] }, "buyerAddressPostalCode": { "type": [ "string", "null" ] }, "buyerAddressCity": { "type": [ "string", "null" ] }, "buyerAddressCountry": { "type": [ "string", "null" ] }, "buyerEmail": { "type": [ "string", "null" ] }, "buyerKvk": { "type": [ "string", "null" ] }, "buyerBtw": { "type": [ "string", "null" ] }, "termsRichText": { "type": [ "string", "null" ], "maxLength": 100000 }, "termsUrl": { "type": [ "string", "null" ] }, "termsPdfFileId": { "type": [ "string", "null" ], "format": "uuid" }, "termsPdfVersionId": { "type": [ "string", "null" ], "format": "uuid" }, "sentAt": { "type": [ "string", "null" ], "format": "date-time" }, "paidAt": { "type": [ "string", "null" ], "format": "date-time" }, "cancelledAt": { "type": [ "string", "null" ], "format": "date-time" }, "archivedAt": { "type": [ "string", "null" ], "format": "date-time" }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "updatedBy": { "type": [ "string", "null" ], "format": "uuid" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" }, "amountPaidCents": { "type": "integer", "minimum": 0 }, "amountDueCents": { "type": "integer", "minimum": 0 }, "uncollectibleAt": { "type": [ "string", "null" ], "format": "date-time" }, "uncollectibleReason": { "type": [ "string", "null" ] }, "onlinePaymentEnabled": { "type": "boolean", "default": true } }, "required": [ "id", "organizationId", "customerId", "customerContactId", "customerReference", "number", "numberYear", "numberPrefix", "displayNumber", "status", "dueDays", "invoiceTemplateId", "title", "intro", "terms", "currency", "vatInclusive", "discountKind", "discountValue", "discountDescription", "promotionId", "subtotalExcl", "vatTotal", "totalIncl", "issueDate", "dueDate", "publicMessage", "footerText", "sourceQuoteId", "deliveryDate", "kind", "originalInvoiceId", "vatRegime", "sellerName", "sellerAddressLine1", "sellerAddressLine2", "sellerAddressPostalCode", "sellerAddressCity", "sellerAddressCountry", "sellerEmail", "sellerPhone", "sellerKvk", "sellerBtw", "sellerIban", "buyerName", "buyerAddressLine1", "buyerAddressLine2", "buyerAddressPostalCode", "buyerAddressCity", "buyerAddressCountry", "buyerEmail", "buyerKvk", "buyerBtw", "termsRichText", "termsUrl", "termsPdfFileId", "termsPdfVersionId", "sentAt", "paidAt", "cancelledAt", "archivedAt", "createdBy", "updatedBy", "createdAt", "updatedAt", "amountPaidCents", "amountDueCents", "uncollectibleAt", "uncollectibleReason", "onlinePaymentEnabled" ] }, "invoiceStatusSchema": { "type": "string", "enum": [ "draft", "sent", "paid", "overdue", "cancelled", "uncollectible" ] }, "isoDateOrNull": { "type": [ "string", "null" ], "pattern": "^\\d{4}-\\d{2}-\\d{2}$" }, "optionalUrl": { "type": [ "string", "null" ], "maxLength": 2000 }, "organizationIdSchema": { "type": "string", "format": "uuid" } } ``` # Delete an invoice. (/openapi/deleteInvoice) Archive an invoice for the specified organization. ## DELETE /v1/organizations/{organizationId}/invoices/{invoiceId} ```json { "operationId": "deleteInvoice", "summary": "Delete an invoice.", "description": "Archive an invoice for the specified organization.", "tags": [ "Invoices" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "invoiceId", "required": true, "schema": { "$ref": "#/components/schemas/invoiceIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns a success payload when the invoice is archived.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/publicApiSuccessResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "invoiceIdSchema": { "type": "string", "format": "uuid" }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "publicApiSuccessResponseSchema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [ true ] } }, "required": [ "success" ] } } ``` # Create an invoice from a quote. (/openapi/createInvoiceFromQuote) Generate a draft invoice by copying the lines of an accepted quote. ## POST /v1/organizations/{organizationId}/invoices/from-quote ```json { "operationId": "createInvoiceFromQuote", "summary": "Create an invoice from a quote.", "description": "Generate a draft invoice by copying the lines of an accepted quote.", "tags": [ "Invoices" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/createInvoiceFromQuoteInputSchema" } } }, "description": "The source quote id." }, "responses": { "201": { "description": "Returns the created draft invoice.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/invoiceSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "createInvoiceFromQuoteInputSchema": { "type": "object", "properties": { "quoteId": { "type": "string", "format": "uuid" }, "depositLineTitle": { "allOf": [ { "$ref": "#/components/schemas/invoiceLineTitleTemplateSchema" } ] } }, "required": [ "quoteId" ] }, "discountKindSchema": { "type": "string", "enum": [ "none", "percentage", "fixed" ] }, "documentVatRegimeSchema": { "type": "string", "enum": [ "standard", "reverse_charge", "exempt", "kor", "margin_scheme" ] }, "invoiceCurrencyCodeSchema": { "type": "string", "pattern": "^[A-Z]{3}$" }, "invoiceDisplayNumberSchema": { "type": "string", "minLength": 1, "maxLength": 40 }, "invoiceIdSchema": { "type": "string", "format": "uuid" }, "invoiceKindSchema": { "type": "string", "enum": [ "invoice", "credit_note", "deposit" ] }, "invoiceLineTitleTemplateSchema": { "type": "string", "minLength": 1, "maxLength": 240 }, "invoiceNumberPrefixSchema": { "type": "string", "maxLength": 16, "pattern": "^[A-Z0-9-]*$" }, "invoiceNumberSchema": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 }, "invoiceSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/invoiceIdSchema" }, "organizationId": { "type": "string", "format": "uuid" }, "customerId": { "type": "integer", "exclusiveMinimum": 0 }, "customerContactId": { "type": [ "string", "null" ], "format": "uuid" }, "customerReference": { "type": [ "string", "null" ] }, "number": { "anyOf": [ { "$ref": "#/components/schemas/invoiceNumberSchema" }, { "type": "null" } ] }, "numberYear": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 }, "numberPrefix": { "$ref": "#/components/schemas/invoiceNumberPrefixSchema" }, "displayNumber": { "anyOf": [ { "$ref": "#/components/schemas/invoiceDisplayNumberSchema" }, { "type": "null" } ] }, "status": { "$ref": "#/components/schemas/invoiceStatusSchema" }, "dueDays": { "type": [ "integer", "null" ], "minimum": 0, "maximum": 365 }, "invoiceTemplateId": { "type": [ "string", "null" ], "format": "uuid" }, "title": { "type": "string" }, "intro": { "type": [ "string", "null" ] }, "terms": { "type": [ "string", "null" ] }, "currency": { "$ref": "#/components/schemas/invoiceCurrencyCodeSchema" }, "vatInclusive": { "type": "boolean" }, "discountKind": { "$ref": "#/components/schemas/discountKindSchema" }, "discountValue": { "type": "number" }, "discountDescription": { "type": [ "string", "null" ], "maxLength": 500 }, "promotionId": { "type": [ "string", "null" ], "format": "uuid" }, "subtotalExcl": { "type": "number" }, "vatTotal": { "type": "number" }, "totalIncl": { "type": "number" }, "issueDate": { "type": [ "string", "null" ] }, "dueDate": { "type": [ "string", "null" ] }, "publicMessage": { "type": [ "string", "null" ] }, "footerText": { "type": [ "string", "null" ] }, "sourceQuoteId": { "type": [ "string", "null" ], "format": "uuid" }, "deliveryDate": { "type": [ "string", "null" ] }, "kind": { "$ref": "#/components/schemas/invoiceKindSchema" }, "originalInvoiceId": { "type": [ "string", "null" ], "format": "uuid" }, "vatRegime": { "$ref": "#/components/schemas/documentVatRegimeSchema" }, "sellerName": { "type": [ "string", "null" ] }, "sellerAddressLine1": { "type": [ "string", "null" ] }, "sellerAddressLine2": { "type": [ "string", "null" ] }, "sellerAddressPostalCode": { "type": [ "string", "null" ] }, "sellerAddressCity": { "type": [ "string", "null" ] }, "sellerAddressCountry": { "type": [ "string", "null" ] }, "sellerEmail": { "type": [ "string", "null" ] }, "sellerPhone": { "type": [ "string", "null" ] }, "sellerKvk": { "type": [ "string", "null" ] }, "sellerBtw": { "type": [ "string", "null" ] }, "sellerIban": { "type": [ "string", "null" ] }, "buyerName": { "type": [ "string", "null" ] }, "buyerAddressLine1": { "type": [ "string", "null" ] }, "buyerAddressLine2": { "type": [ "string", "null" ] }, "buyerAddressPostalCode": { "type": [ "string", "null" ] }, "buyerAddressCity": { "type": [ "string", "null" ] }, "buyerAddressCountry": { "type": [ "string", "null" ] }, "buyerEmail": { "type": [ "string", "null" ] }, "buyerKvk": { "type": [ "string", "null" ] }, "buyerBtw": { "type": [ "string", "null" ] }, "termsRichText": { "type": [ "string", "null" ], "maxLength": 100000 }, "termsUrl": { "type": [ "string", "null" ] }, "termsPdfFileId": { "type": [ "string", "null" ], "format": "uuid" }, "termsPdfVersionId": { "type": [ "string", "null" ], "format": "uuid" }, "sentAt": { "type": [ "string", "null" ], "format": "date-time" }, "paidAt": { "type": [ "string", "null" ], "format": "date-time" }, "cancelledAt": { "type": [ "string", "null" ], "format": "date-time" }, "archivedAt": { "type": [ "string", "null" ], "format": "date-time" }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "updatedBy": { "type": [ "string", "null" ], "format": "uuid" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" }, "amountPaidCents": { "type": "integer", "minimum": 0 }, "amountDueCents": { "type": "integer", "minimum": 0 }, "uncollectibleAt": { "type": [ "string", "null" ], "format": "date-time" }, "uncollectibleReason": { "type": [ "string", "null" ] }, "onlinePaymentEnabled": { "type": "boolean", "default": true } }, "required": [ "id", "organizationId", "customerId", "customerContactId", "customerReference", "number", "numberYear", "numberPrefix", "displayNumber", "status", "dueDays", "invoiceTemplateId", "title", "intro", "terms", "currency", "vatInclusive", "discountKind", "discountValue", "discountDescription", "promotionId", "subtotalExcl", "vatTotal", "totalIncl", "issueDate", "dueDate", "publicMessage", "footerText", "sourceQuoteId", "deliveryDate", "kind", "originalInvoiceId", "vatRegime", "sellerName", "sellerAddressLine1", "sellerAddressLine2", "sellerAddressPostalCode", "sellerAddressCity", "sellerAddressCountry", "sellerEmail", "sellerPhone", "sellerKvk", "sellerBtw", "sellerIban", "buyerName", "buyerAddressLine1", "buyerAddressLine2", "buyerAddressPostalCode", "buyerAddressCity", "buyerAddressCountry", "buyerEmail", "buyerKvk", "buyerBtw", "termsRichText", "termsUrl", "termsPdfFileId", "termsPdfVersionId", "sentAt", "paidAt", "cancelledAt", "archivedAt", "createdBy", "updatedBy", "createdAt", "updatedAt", "amountPaidCents", "amountDueCents", "uncollectibleAt", "uncollectibleReason", "onlinePaymentEnabled" ] }, "invoiceStatusSchema": { "type": "string", "enum": [ "draft", "sent", "paid", "overdue", "cancelled", "uncollectible" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" } } ``` # Cancel an invoice. (/openapi/cancelInvoice) Cancel a draft, sent, or overdue invoice. ## POST /v1/organizations/{organizationId}/invoices/{invoiceId}/cancel ```json { "operationId": "cancelInvoice", "summary": "Cancel an invoice.", "description": "Cancel a draft, sent, or overdue invoice.", "tags": [ "Invoices" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "invoiceId", "required": true, "schema": { "$ref": "#/components/schemas/invoiceIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/invoiceCancelInputSchema" } } }, "description": "Optional cancellation reason.", "required": true }, "responses": { "200": { "description": "Returns the cancelled invoice.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/invoiceSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "discountKindSchema": { "type": "string", "enum": [ "none", "percentage", "fixed" ] }, "documentVatRegimeSchema": { "type": "string", "enum": [ "standard", "reverse_charge", "exempt", "kor", "margin_scheme" ] }, "invoiceCancelInputSchema": { "type": "object", "properties": { "reason": { "type": [ "string", "null" ], "maxLength": 20000 }, "returnToStock": { "type": "boolean" } } }, "invoiceCurrencyCodeSchema": { "type": "string", "pattern": "^[A-Z]{3}$" }, "invoiceDisplayNumberSchema": { "type": "string", "minLength": 1, "maxLength": 40 }, "invoiceIdSchema": { "type": "string", "format": "uuid" }, "invoiceKindSchema": { "type": "string", "enum": [ "invoice", "credit_note", "deposit" ] }, "invoiceNumberPrefixSchema": { "type": "string", "maxLength": 16, "pattern": "^[A-Z0-9-]*$" }, "invoiceNumberSchema": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 }, "invoiceSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/invoiceIdSchema" }, "organizationId": { "type": "string", "format": "uuid" }, "customerId": { "type": "integer", "exclusiveMinimum": 0 }, "customerContactId": { "type": [ "string", "null" ], "format": "uuid" }, "customerReference": { "type": [ "string", "null" ] }, "number": { "anyOf": [ { "$ref": "#/components/schemas/invoiceNumberSchema" }, { "type": "null" } ] }, "numberYear": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 }, "numberPrefix": { "$ref": "#/components/schemas/invoiceNumberPrefixSchema" }, "displayNumber": { "anyOf": [ { "$ref": "#/components/schemas/invoiceDisplayNumberSchema" }, { "type": "null" } ] }, "status": { "$ref": "#/components/schemas/invoiceStatusSchema" }, "dueDays": { "type": [ "integer", "null" ], "minimum": 0, "maximum": 365 }, "invoiceTemplateId": { "type": [ "string", "null" ], "format": "uuid" }, "title": { "type": "string" }, "intro": { "type": [ "string", "null" ] }, "terms": { "type": [ "string", "null" ] }, "currency": { "$ref": "#/components/schemas/invoiceCurrencyCodeSchema" }, "vatInclusive": { "type": "boolean" }, "discountKind": { "$ref": "#/components/schemas/discountKindSchema" }, "discountValue": { "type": "number" }, "discountDescription": { "type": [ "string", "null" ], "maxLength": 500 }, "promotionId": { "type": [ "string", "null" ], "format": "uuid" }, "subtotalExcl": { "type": "number" }, "vatTotal": { "type": "number" }, "totalIncl": { "type": "number" }, "issueDate": { "type": [ "string", "null" ] }, "dueDate": { "type": [ "string", "null" ] }, "publicMessage": { "type": [ "string", "null" ] }, "footerText": { "type": [ "string", "null" ] }, "sourceQuoteId": { "type": [ "string", "null" ], "format": "uuid" }, "deliveryDate": { "type": [ "string", "null" ] }, "kind": { "$ref": "#/components/schemas/invoiceKindSchema" }, "originalInvoiceId": { "type": [ "string", "null" ], "format": "uuid" }, "vatRegime": { "$ref": "#/components/schemas/documentVatRegimeSchema" }, "sellerName": { "type": [ "string", "null" ] }, "sellerAddressLine1": { "type": [ "string", "null" ] }, "sellerAddressLine2": { "type": [ "string", "null" ] }, "sellerAddressPostalCode": { "type": [ "string", "null" ] }, "sellerAddressCity": { "type": [ "string", "null" ] }, "sellerAddressCountry": { "type": [ "string", "null" ] }, "sellerEmail": { "type": [ "string", "null" ] }, "sellerPhone": { "type": [ "string", "null" ] }, "sellerKvk": { "type": [ "string", "null" ] }, "sellerBtw": { "type": [ "string", "null" ] }, "sellerIban": { "type": [ "string", "null" ] }, "buyerName": { "type": [ "string", "null" ] }, "buyerAddressLine1": { "type": [ "string", "null" ] }, "buyerAddressLine2": { "type": [ "string", "null" ] }, "buyerAddressPostalCode": { "type": [ "string", "null" ] }, "buyerAddressCity": { "type": [ "string", "null" ] }, "buyerAddressCountry": { "type": [ "string", "null" ] }, "buyerEmail": { "type": [ "string", "null" ] }, "buyerKvk": { "type": [ "string", "null" ] }, "buyerBtw": { "type": [ "string", "null" ] }, "termsRichText": { "type": [ "string", "null" ], "maxLength": 100000 }, "termsUrl": { "type": [ "string", "null" ] }, "termsPdfFileId": { "type": [ "string", "null" ], "format": "uuid" }, "termsPdfVersionId": { "type": [ "string", "null" ], "format": "uuid" }, "sentAt": { "type": [ "string", "null" ], "format": "date-time" }, "paidAt": { "type": [ "string", "null" ], "format": "date-time" }, "cancelledAt": { "type": [ "string", "null" ], "format": "date-time" }, "archivedAt": { "type": [ "string", "null" ], "format": "date-time" }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "updatedBy": { "type": [ "string", "null" ], "format": "uuid" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" }, "amountPaidCents": { "type": "integer", "minimum": 0 }, "amountDueCents": { "type": "integer", "minimum": 0 }, "uncollectibleAt": { "type": [ "string", "null" ], "format": "date-time" }, "uncollectibleReason": { "type": [ "string", "null" ] }, "onlinePaymentEnabled": { "type": "boolean", "default": true } }, "required": [ "id", "organizationId", "customerId", "customerContactId", "customerReference", "number", "numberYear", "numberPrefix", "displayNumber", "status", "dueDays", "invoiceTemplateId", "title", "intro", "terms", "currency", "vatInclusive", "discountKind", "discountValue", "discountDescription", "promotionId", "subtotalExcl", "vatTotal", "totalIncl", "issueDate", "dueDate", "publicMessage", "footerText", "sourceQuoteId", "deliveryDate", "kind", "originalInvoiceId", "vatRegime", "sellerName", "sellerAddressLine1", "sellerAddressLine2", "sellerAddressPostalCode", "sellerAddressCity", "sellerAddressCountry", "sellerEmail", "sellerPhone", "sellerKvk", "sellerBtw", "sellerIban", "buyerName", "buyerAddressLine1", "buyerAddressLine2", "buyerAddressPostalCode", "buyerAddressCity", "buyerAddressCountry", "buyerEmail", "buyerKvk", "buyerBtw", "termsRichText", "termsUrl", "termsPdfFileId", "termsPdfVersionId", "sentAt", "paidAt", "cancelledAt", "archivedAt", "createdBy", "updatedBy", "createdAt", "updatedAt", "amountPaidCents", "amountDueCents", "uncollectibleAt", "uncollectibleReason", "onlinePaymentEnabled" ] }, "invoiceStatusSchema": { "type": "string", "enum": [ "draft", "sent", "paid", "overdue", "cancelled", "uncollectible" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" } } ``` # Duplicate an invoice. (/openapi/duplicateInvoice) Create a new draft invoice copied from an existing one. ## POST /v1/organizations/{organizationId}/invoices/{invoiceId}/duplicate ```json { "operationId": "duplicateInvoice", "summary": "Duplicate an invoice.", "description": "Create a new draft invoice copied from an existing one.", "tags": [ "Invoices" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "invoiceId", "required": true, "schema": { "$ref": "#/components/schemas/invoiceIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "201": { "description": "Returns the duplicated draft invoice.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/invoiceSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "discountKindSchema": { "type": "string", "enum": [ "none", "percentage", "fixed" ] }, "documentVatRegimeSchema": { "type": "string", "enum": [ "standard", "reverse_charge", "exempt", "kor", "margin_scheme" ] }, "invoiceCurrencyCodeSchema": { "type": "string", "pattern": "^[A-Z]{3}$" }, "invoiceDisplayNumberSchema": { "type": "string", "minLength": 1, "maxLength": 40 }, "invoiceIdSchema": { "type": "string", "format": "uuid" }, "invoiceKindSchema": { "type": "string", "enum": [ "invoice", "credit_note", "deposit" ] }, "invoiceNumberPrefixSchema": { "type": "string", "maxLength": 16, "pattern": "^[A-Z0-9-]*$" }, "invoiceNumberSchema": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 }, "invoiceSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/invoiceIdSchema" }, "organizationId": { "type": "string", "format": "uuid" }, "customerId": { "type": "integer", "exclusiveMinimum": 0 }, "customerContactId": { "type": [ "string", "null" ], "format": "uuid" }, "customerReference": { "type": [ "string", "null" ] }, "number": { "anyOf": [ { "$ref": "#/components/schemas/invoiceNumberSchema" }, { "type": "null" } ] }, "numberYear": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 }, "numberPrefix": { "$ref": "#/components/schemas/invoiceNumberPrefixSchema" }, "displayNumber": { "anyOf": [ { "$ref": "#/components/schemas/invoiceDisplayNumberSchema" }, { "type": "null" } ] }, "status": { "$ref": "#/components/schemas/invoiceStatusSchema" }, "dueDays": { "type": [ "integer", "null" ], "minimum": 0, "maximum": 365 }, "invoiceTemplateId": { "type": [ "string", "null" ], "format": "uuid" }, "title": { "type": "string" }, "intro": { "type": [ "string", "null" ] }, "terms": { "type": [ "string", "null" ] }, "currency": { "$ref": "#/components/schemas/invoiceCurrencyCodeSchema" }, "vatInclusive": { "type": "boolean" }, "discountKind": { "$ref": "#/components/schemas/discountKindSchema" }, "discountValue": { "type": "number" }, "discountDescription": { "type": [ "string", "null" ], "maxLength": 500 }, "promotionId": { "type": [ "string", "null" ], "format": "uuid" }, "subtotalExcl": { "type": "number" }, "vatTotal": { "type": "number" }, "totalIncl": { "type": "number" }, "issueDate": { "type": [ "string", "null" ] }, "dueDate": { "type": [ "string", "null" ] }, "publicMessage": { "type": [ "string", "null" ] }, "footerText": { "type": [ "string", "null" ] }, "sourceQuoteId": { "type": [ "string", "null" ], "format": "uuid" }, "deliveryDate": { "type": [ "string", "null" ] }, "kind": { "$ref": "#/components/schemas/invoiceKindSchema" }, "originalInvoiceId": { "type": [ "string", "null" ], "format": "uuid" }, "vatRegime": { "$ref": "#/components/schemas/documentVatRegimeSchema" }, "sellerName": { "type": [ "string", "null" ] }, "sellerAddressLine1": { "type": [ "string", "null" ] }, "sellerAddressLine2": { "type": [ "string", "null" ] }, "sellerAddressPostalCode": { "type": [ "string", "null" ] }, "sellerAddressCity": { "type": [ "string", "null" ] }, "sellerAddressCountry": { "type": [ "string", "null" ] }, "sellerEmail": { "type": [ "string", "null" ] }, "sellerPhone": { "type": [ "string", "null" ] }, "sellerKvk": { "type": [ "string", "null" ] }, "sellerBtw": { "type": [ "string", "null" ] }, "sellerIban": { "type": [ "string", "null" ] }, "buyerName": { "type": [ "string", "null" ] }, "buyerAddressLine1": { "type": [ "string", "null" ] }, "buyerAddressLine2": { "type": [ "string", "null" ] }, "buyerAddressPostalCode": { "type": [ "string", "null" ] }, "buyerAddressCity": { "type": [ "string", "null" ] }, "buyerAddressCountry": { "type": [ "string", "null" ] }, "buyerEmail": { "type": [ "string", "null" ] }, "buyerKvk": { "type": [ "string", "null" ] }, "buyerBtw": { "type": [ "string", "null" ] }, "termsRichText": { "type": [ "string", "null" ], "maxLength": 100000 }, "termsUrl": { "type": [ "string", "null" ] }, "termsPdfFileId": { "type": [ "string", "null" ], "format": "uuid" }, "termsPdfVersionId": { "type": [ "string", "null" ], "format": "uuid" }, "sentAt": { "type": [ "string", "null" ], "format": "date-time" }, "paidAt": { "type": [ "string", "null" ], "format": "date-time" }, "cancelledAt": { "type": [ "string", "null" ], "format": "date-time" }, "archivedAt": { "type": [ "string", "null" ], "format": "date-time" }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "updatedBy": { "type": [ "string", "null" ], "format": "uuid" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" }, "amountPaidCents": { "type": "integer", "minimum": 0 }, "amountDueCents": { "type": "integer", "minimum": 0 }, "uncollectibleAt": { "type": [ "string", "null" ], "format": "date-time" }, "uncollectibleReason": { "type": [ "string", "null" ] }, "onlinePaymentEnabled": { "type": "boolean", "default": true } }, "required": [ "id", "organizationId", "customerId", "customerContactId", "customerReference", "number", "numberYear", "numberPrefix", "displayNumber", "status", "dueDays", "invoiceTemplateId", "title", "intro", "terms", "currency", "vatInclusive", "discountKind", "discountValue", "discountDescription", "promotionId", "subtotalExcl", "vatTotal", "totalIncl", "issueDate", "dueDate", "publicMessage", "footerText", "sourceQuoteId", "deliveryDate", "kind", "originalInvoiceId", "vatRegime", "sellerName", "sellerAddressLine1", "sellerAddressLine2", "sellerAddressPostalCode", "sellerAddressCity", "sellerAddressCountry", "sellerEmail", "sellerPhone", "sellerKvk", "sellerBtw", "sellerIban", "buyerName", "buyerAddressLine1", "buyerAddressLine2", "buyerAddressPostalCode", "buyerAddressCity", "buyerAddressCountry", "buyerEmail", "buyerKvk", "buyerBtw", "termsRichText", "termsUrl", "termsPdfFileId", "termsPdfVersionId", "sentAt", "paidAt", "cancelledAt", "archivedAt", "createdBy", "updatedBy", "createdAt", "updatedAt", "amountPaidCents", "amountDueCents", "uncollectibleAt", "uncollectibleReason", "onlinePaymentEnabled" ] }, "invoiceStatusSchema": { "type": "string", "enum": [ "draft", "sent", "paid", "overdue", "cancelled", "uncollectible" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" } } ``` # Read a invoice lifecycle. (/openapi/getInvoiceLifecycle) The invoice's lifecycle: the pinned workflow version, the current stage, the stage history, and what the lifecycle waits on now. `lifecycle` is null when no lifecycle run started for the invoice. ## GET /v1/organizations/{organizationId}/invoices/{invoiceId}/lifecycle ```json { "operationId": "getInvoiceLifecycle", "summary": "Read a invoice lifecycle.", "description": "The invoice's lifecycle: the pinned workflow version, the current stage, the stage history, and what the lifecycle waits on now. `lifecycle` is null when no lifecycle run started for the invoice.", "tags": [ "Invoices" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "invoiceId", "required": true, "schema": { "$ref": "#/components/schemas/invoiceIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns the invoice's lifecycle.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/recordLifecycleSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "dateTimeSchema": { "type": "string" }, "invoiceIdSchema": { "type": "string", "format": "uuid" }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "recordLifecycleSchema": { "type": "object", "properties": { "lifecycle": { "anyOf": [ { "$ref": "#/components/schemas/workflowLifecycleSchema" }, { "type": "null" } ] }, "definition": { "type": [ "object", "null" ], "properties": { "id": { "type": "string", "format": "uuid" }, "key": { "type": "string" }, "name": { "type": "string" }, "version": { "type": "integer", "exclusiveMinimum": 0 }, "source": { "type": "string", "enum": [ "organization", "record", "system", "template" ] } }, "required": [ "id", "key", "name", "version", "source" ] }, "events": { "type": "array", "items": { "$ref": "#/components/schemas/workflowLifecycleEventSchema" } }, "waiting": { "type": [ "object", "null" ], "properties": { "kind": { "type": "string", "enum": [ "approval", "delay", "event", "manual" ] }, "at": { "anyOf": [ { "$ref": "#/components/schemas/dateTimeSchema" }, { "type": "null" } ] }, "nextStageKey": { "type": [ "string", "null" ] }, "nextStageLabel": { "type": [ "string", "null" ] }, "events": { "type": "array", "items": { "type": "string" } } }, "required": [ "kind", "at", "nextStageKey", "nextStageLabel", "events" ] } }, "required": [ "lifecycle", "definition", "events", "waiting" ] }, "workflowLifecycleEventSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "organizationId": { "type": "string", "format": "uuid" }, "lifecycleId": { "type": "string", "format": "uuid" }, "stageKey": { "type": "string", "minLength": 1 }, "label": { "type": "string", "minLength": 1 }, "outcome": { "type": "string", "enum": [ "progress", "success", "failure", "neutral" ] }, "display": { "type": "string", "enum": [ "main", "sub", "flag", "hidden" ] }, "reachedAt": { "$ref": "#/components/schemas/dateTimeSchema" }, "actorUserId": { "type": [ "string", "null" ], "format": "uuid" }, "actorCustomerContactId": { "type": [ "string", "null" ], "format": "uuid" }, "runStepId": { "type": [ "string", "null" ], "format": "uuid" }, "sourceRunId": { "type": [ "string", "null" ], "format": "uuid" }, "clearedAt": { "anyOf": [ { "$ref": "#/components/schemas/dateTimeSchema" }, { "type": "null" } ] }, "createdAt": { "$ref": "#/components/schemas/dateTimeSchema" } }, "required": [ "id", "organizationId", "lifecycleId", "stageKey", "label", "outcome", "display", "reachedAt", "actorUserId", "actorCustomerContactId", "runStepId", "sourceRunId", "clearedAt", "createdAt" ] }, "workflowLifecycleSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "organizationId": { "type": "string", "format": "uuid" }, "subjectType": { "$ref": "#/components/schemas/workflowSubjectTypeSchema" }, "subjectId": { "type": "string", "format": "uuid" }, "definitionId": { "type": "string", "format": "uuid" }, "definitionVersion": { "type": "integer", "exclusiveMinimum": 0 }, "runId": { "type": [ "string", "null" ], "format": "uuid" }, "currentStageKey": { "type": [ "string", "null" ] }, "pausedAt": { "anyOf": [ { "$ref": "#/components/schemas/dateTimeSchema" }, { "type": "null" } ] }, "pausedUntil": { "anyOf": [ { "$ref": "#/components/schemas/dateTimeSchema" }, { "type": "null" } ] }, "pauseReason": { "type": [ "string", "null" ] }, "pausedBy": { "type": [ "string", "null" ], "format": "uuid" }, "startedAt": { "$ref": "#/components/schemas/dateTimeSchema" }, "endedAt": { "anyOf": [ { "$ref": "#/components/schemas/dateTimeSchema" }, { "type": "null" } ] }, "createdAt": { "$ref": "#/components/schemas/dateTimeSchema" }, "updatedAt": { "$ref": "#/components/schemas/dateTimeSchema" } }, "required": [ "id", "organizationId", "subjectType", "subjectId", "definitionId", "definitionVersion", "runId", "currentStageKey", "pausedAt", "pausedUntil", "pauseReason", "pausedBy", "startedAt", "endedAt", "createdAt", "updatedAt" ] }, "workflowSubjectTypeSchema": { "type": "string" } } ``` # Mark an invoice paid. (/openapi/markInvoicePaid) Transition a sent or overdue invoice to paid. ## POST /v1/organizations/{organizationId}/invoices/{invoiceId}/mark-paid ```json { "operationId": "markInvoicePaid", "summary": "Mark an invoice paid.", "description": "Transition a sent or overdue invoice to paid.", "tags": [ "Invoices" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "invoiceId", "required": true, "schema": { "$ref": "#/components/schemas/invoiceIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/invoiceMarkPaidInputSchema" } } }, "description": "Optional payment date and note.", "required": true }, "responses": { "200": { "description": "Returns the paid invoice.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/invoiceSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "discountKindSchema": { "type": "string", "enum": [ "none", "percentage", "fixed" ] }, "documentVatRegimeSchema": { "type": "string", "enum": [ "standard", "reverse_charge", "exempt", "kor", "margin_scheme" ] }, "invoiceCurrencyCodeSchema": { "type": "string", "pattern": "^[A-Z]{3}$" }, "invoiceDisplayNumberSchema": { "type": "string", "minLength": 1, "maxLength": 40 }, "invoiceIdSchema": { "type": "string", "format": "uuid" }, "invoiceKindSchema": { "type": "string", "enum": [ "invoice", "credit_note", "deposit" ] }, "invoiceMarkPaidInputSchema": { "type": "object", "properties": { "paidAt": { "allOf": [ { "$ref": "#/components/schemas/isoDateOrNull" } ] }, "reason": { "type": [ "string", "null" ], "maxLength": 2000 } } }, "invoiceNumberPrefixSchema": { "type": "string", "maxLength": 16, "pattern": "^[A-Z0-9-]*$" }, "invoiceNumberSchema": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 }, "invoiceSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/invoiceIdSchema" }, "organizationId": { "type": "string", "format": "uuid" }, "customerId": { "type": "integer", "exclusiveMinimum": 0 }, "customerContactId": { "type": [ "string", "null" ], "format": "uuid" }, "customerReference": { "type": [ "string", "null" ] }, "number": { "anyOf": [ { "$ref": "#/components/schemas/invoiceNumberSchema" }, { "type": "null" } ] }, "numberYear": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 }, "numberPrefix": { "$ref": "#/components/schemas/invoiceNumberPrefixSchema" }, "displayNumber": { "anyOf": [ { "$ref": "#/components/schemas/invoiceDisplayNumberSchema" }, { "type": "null" } ] }, "status": { "$ref": "#/components/schemas/invoiceStatusSchema" }, "dueDays": { "type": [ "integer", "null" ], "minimum": 0, "maximum": 365 }, "invoiceTemplateId": { "type": [ "string", "null" ], "format": "uuid" }, "title": { "type": "string" }, "intro": { "type": [ "string", "null" ] }, "terms": { "type": [ "string", "null" ] }, "currency": { "$ref": "#/components/schemas/invoiceCurrencyCodeSchema" }, "vatInclusive": { "type": "boolean" }, "discountKind": { "$ref": "#/components/schemas/discountKindSchema" }, "discountValue": { "type": "number" }, "discountDescription": { "type": [ "string", "null" ], "maxLength": 500 }, "promotionId": { "type": [ "string", "null" ], "format": "uuid" }, "subtotalExcl": { "type": "number" }, "vatTotal": { "type": "number" }, "totalIncl": { "type": "number" }, "issueDate": { "type": [ "string", "null" ] }, "dueDate": { "type": [ "string", "null" ] }, "publicMessage": { "type": [ "string", "null" ] }, "footerText": { "type": [ "string", "null" ] }, "sourceQuoteId": { "type": [ "string", "null" ], "format": "uuid" }, "deliveryDate": { "type": [ "string", "null" ] }, "kind": { "$ref": "#/components/schemas/invoiceKindSchema" }, "originalInvoiceId": { "type": [ "string", "null" ], "format": "uuid" }, "vatRegime": { "$ref": "#/components/schemas/documentVatRegimeSchema" }, "sellerName": { "type": [ "string", "null" ] }, "sellerAddressLine1": { "type": [ "string", "null" ] }, "sellerAddressLine2": { "type": [ "string", "null" ] }, "sellerAddressPostalCode": { "type": [ "string", "null" ] }, "sellerAddressCity": { "type": [ "string", "null" ] }, "sellerAddressCountry": { "type": [ "string", "null" ] }, "sellerEmail": { "type": [ "string", "null" ] }, "sellerPhone": { "type": [ "string", "null" ] }, "sellerKvk": { "type": [ "string", "null" ] }, "sellerBtw": { "type": [ "string", "null" ] }, "sellerIban": { "type": [ "string", "null" ] }, "buyerName": { "type": [ "string", "null" ] }, "buyerAddressLine1": { "type": [ "string", "null" ] }, "buyerAddressLine2": { "type": [ "string", "null" ] }, "buyerAddressPostalCode": { "type": [ "string", "null" ] }, "buyerAddressCity": { "type": [ "string", "null" ] }, "buyerAddressCountry": { "type": [ "string", "null" ] }, "buyerEmail": { "type": [ "string", "null" ] }, "buyerKvk": { "type": [ "string", "null" ] }, "buyerBtw": { "type": [ "string", "null" ] }, "termsRichText": { "type": [ "string", "null" ], "maxLength": 100000 }, "termsUrl": { "type": [ "string", "null" ] }, "termsPdfFileId": { "type": [ "string", "null" ], "format": "uuid" }, "termsPdfVersionId": { "type": [ "string", "null" ], "format": "uuid" }, "sentAt": { "type": [ "string", "null" ], "format": "date-time" }, "paidAt": { "type": [ "string", "null" ], "format": "date-time" }, "cancelledAt": { "type": [ "string", "null" ], "format": "date-time" }, "archivedAt": { "type": [ "string", "null" ], "format": "date-time" }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "updatedBy": { "type": [ "string", "null" ], "format": "uuid" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" }, "amountPaidCents": { "type": "integer", "minimum": 0 }, "amountDueCents": { "type": "integer", "minimum": 0 }, "uncollectibleAt": { "type": [ "string", "null" ], "format": "date-time" }, "uncollectibleReason": { "type": [ "string", "null" ] }, "onlinePaymentEnabled": { "type": "boolean", "default": true } }, "required": [ "id", "organizationId", "customerId", "customerContactId", "customerReference", "number", "numberYear", "numberPrefix", "displayNumber", "status", "dueDays", "invoiceTemplateId", "title", "intro", "terms", "currency", "vatInclusive", "discountKind", "discountValue", "discountDescription", "promotionId", "subtotalExcl", "vatTotal", "totalIncl", "issueDate", "dueDate", "publicMessage", "footerText", "sourceQuoteId", "deliveryDate", "kind", "originalInvoiceId", "vatRegime", "sellerName", "sellerAddressLine1", "sellerAddressLine2", "sellerAddressPostalCode", "sellerAddressCity", "sellerAddressCountry", "sellerEmail", "sellerPhone", "sellerKvk", "sellerBtw", "sellerIban", "buyerName", "buyerAddressLine1", "buyerAddressLine2", "buyerAddressPostalCode", "buyerAddressCity", "buyerAddressCountry", "buyerEmail", "buyerKvk", "buyerBtw", "termsRichText", "termsUrl", "termsPdfFileId", "termsPdfVersionId", "sentAt", "paidAt", "cancelledAt", "archivedAt", "createdBy", "updatedBy", "createdAt", "updatedAt", "amountPaidCents", "amountDueCents", "uncollectibleAt", "uncollectibleReason", "onlinePaymentEnabled" ] }, "invoiceStatusSchema": { "type": "string", "enum": [ "draft", "sent", "paid", "overdue", "cancelled", "uncollectible" ] }, "isoDateOrNull": { "type": [ "string", "null" ], "pattern": "^\\d{4}-\\d{2}-\\d{2}$" }, "organizationIdSchema": { "type": "string", "format": "uuid" } } ``` # List invoice notes. (/openapi/listInvoiceNotes) Return internal notes for an invoice. ## GET /v1/organizations/{organizationId}/invoices/{invoiceId}/notes ```json { "operationId": "listInvoiceNotes", "summary": "List invoice notes.", "description": "Return internal notes for an invoice.", "tags": [ "Invoices" ], "parameters": [ { "in": "path", "name": "invoiceId", "required": true, "schema": { "$ref": "#/components/schemas/invoiceIdSchema" }, "example": "123" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns invoice notes.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/invoiceNotesResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "collaborationBlockSchema": { "type": "object", "discriminator": { "propertyName": "type" }, "oneOf": [ { "type": "object", "properties": { "type": { "type": "string", "enum": [ "paragraph" ] }, "content": { "$ref": "#/components/schemas/collaborationInlineContentSchema" } }, "required": [ "type", "content" ], "additionalProperties": false }, { "type": "object", "properties": { "type": { "type": "string", "enum": [ "bullet-list" ] }, "items": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationInlineContentSchema" }, "minItems": 1, "maxItems": 500 } }, "required": [ "type", "items" ], "additionalProperties": false }, { "type": "object", "properties": { "type": { "type": "string", "enum": [ "numbered-list" ] }, "items": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationInlineContentSchema" }, "minItems": 1, "maxItems": 500 } }, "required": [ "type", "items" ], "additionalProperties": false } ] }, "collaborationCommentAuthorSchema": { "type": "object", "properties": { "userId": { "type": "string", "format": "uuid" }, "displayName": { "type": "string", "minLength": 1 }, "avatarUrl": { "type": [ "string", "null" ], "format": "uri" } }, "required": [ "userId", "displayName", "avatarUrl" ] }, "collaborationDocumentSchema": { "type": "object", "properties": { "version": { "type": "integer", "enum": [ 1 ] }, "blocks": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationBlockSchema" }, "minItems": 1, "maxItems": 1000 } }, "required": [ "version", "blocks" ], "additionalProperties": false }, "collaborationInlineContentSchema": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationInlineSchema" }, "minItems": 1, "maxItems": 1000 }, "collaborationInlineSchema": { "type": "object", "discriminator": { "propertyName": "type" }, "oneOf": [ { "type": "object", "properties": { "type": { "type": "string", "enum": [ "text" ] }, "text": { "type": "string", "minLength": 1, "maxLength": 20000 } }, "required": [ "type", "text" ], "additionalProperties": false }, { "type": "object", "properties": { "type": { "type": "string", "enum": [ "reference" ] }, "reference": { "$ref": "#/components/schemas/collaborationReferenceSchema" } }, "required": [ "type", "reference" ], "additionalProperties": false } ] }, "collaborationReferenceSchema": { "type": "object", "discriminator": { "propertyName": "kind" }, "oneOf": [ { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "user" ] }, "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "team" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "task" ] }, "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "customer" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "quote" ] }, "id": { "$ref": "#/components/schemas/quoteIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "invoice" ] }, "id": { "$ref": "#/components/schemas/invoiceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "product" ] }, "id": { "$ref": "#/components/schemas/productIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "customer-asset" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false } ] }, "invoiceIdSchema": { "type": "string", "format": "uuid" }, "invoiceNoteSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "invoiceId": { "$ref": "#/components/schemas/invoiceIdSchema" }, "organizationId": { "type": "string", "format": "uuid" }, "contentText": { "type": "string", "minLength": 1, "maxLength": 20000 }, "contentDoc": { "$ref": "#/components/schemas/collaborationDocumentSchema" }, "authorId": { "type": [ "string", "null" ], "format": "uuid" }, "author": { "anyOf": [ { "$ref": "#/components/schemas/collaborationCommentAuthorSchema" }, { "type": "null" } ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "invoiceId", "organizationId", "contentText", "contentDoc", "authorId", "author", "createdAt", "updatedAt" ] }, "invoiceNotesResponseSchema": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/invoiceNoteSchema" } }, "pagination": { "$ref": "#/components/schemas/listPaginationSchema" } }, "required": [ "data", "pagination" ] }, "listPaginationSchema": { "type": "object", "properties": { "offset": { "type": "integer", "minimum": 0 }, "limit": { "type": "integer", "minimum": 1, "maximum": 100 }, "total": { "type": "integer", "minimum": 0 }, "hasMore": { "type": "boolean" }, "totalIsEstimate": { "type": "boolean" } }, "required": [ "offset", "limit", "total", "hasMore" ] }, "numericReferenceIdSchema": { "type": "string", "pattern": "^[1-9]\\d*$" }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "productIdSchema": { "type": "string", "format": "uuid" }, "quoteIdSchema": { "type": "string", "format": "uuid" } } ``` # Add an invoice note. (/openapi/addInvoiceNote) Add an internal note to an invoice. ## POST /v1/organizations/{organizationId}/invoices/{invoiceId}/notes ```json { "operationId": "addInvoiceNote", "summary": "Add an invoice note.", "description": "Add an internal note to an invoice.", "tags": [ "Invoices" ], "parameters": [ { "in": "path", "name": "invoiceId", "required": true, "schema": { "$ref": "#/components/schemas/invoiceIdSchema" }, "example": "123" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/invoiceNoteInputSchema" } } }, "description": "The invoice note payload." }, "responses": { "201": { "description": "Returns the created invoice note.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/invoiceNoteSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "collaborationBlockSchema": { "type": "object", "discriminator": { "propertyName": "type" }, "oneOf": [ { "type": "object", "properties": { "type": { "type": "string", "enum": [ "paragraph" ] }, "content": { "$ref": "#/components/schemas/collaborationInlineContentSchema" } }, "required": [ "type", "content" ], "additionalProperties": false }, { "type": "object", "properties": { "type": { "type": "string", "enum": [ "bullet-list" ] }, "items": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationInlineContentSchema" }, "minItems": 1, "maxItems": 500 } }, "required": [ "type", "items" ], "additionalProperties": false }, { "type": "object", "properties": { "type": { "type": "string", "enum": [ "numbered-list" ] }, "items": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationInlineContentSchema" }, "minItems": 1, "maxItems": 500 } }, "required": [ "type", "items" ], "additionalProperties": false } ] }, "collaborationCommentAuthorSchema": { "type": "object", "properties": { "userId": { "type": "string", "format": "uuid" }, "displayName": { "type": "string", "minLength": 1 }, "avatarUrl": { "type": [ "string", "null" ], "format": "uri" } }, "required": [ "userId", "displayName", "avatarUrl" ] }, "collaborationComposerInputSchema": { "type": "object", "properties": { "text": { "type": "string", "minLength": 1, "maxLength": 20000 }, "selections": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationComposerSelectionSchema" }, "maxItems": 100 } }, "required": [ "text", "selections" ], "additionalProperties": false }, "collaborationComposerSelectionSchema": { "type": "object", "properties": { "start": { "type": "integer", "minimum": 0, "maximum": 20000 }, "end": { "type": "integer", "exclusiveMinimum": 0, "maximum": 20000 }, "reference": { "$ref": "#/components/schemas/collaborationReferenceIdSchema" } }, "required": [ "start", "end", "reference" ], "additionalProperties": false }, "collaborationDocumentSchema": { "type": "object", "properties": { "version": { "type": "integer", "enum": [ 1 ] }, "blocks": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationBlockSchema" }, "minItems": 1, "maxItems": 1000 } }, "required": [ "version", "blocks" ], "additionalProperties": false }, "collaborationInlineContentSchema": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationInlineSchema" }, "minItems": 1, "maxItems": 1000 }, "collaborationInlineSchema": { "type": "object", "discriminator": { "propertyName": "type" }, "oneOf": [ { "type": "object", "properties": { "type": { "type": "string", "enum": [ "text" ] }, "text": { "type": "string", "minLength": 1, "maxLength": 20000 } }, "required": [ "type", "text" ], "additionalProperties": false }, { "type": "object", "properties": { "type": { "type": "string", "enum": [ "reference" ] }, "reference": { "$ref": "#/components/schemas/collaborationReferenceSchema" } }, "required": [ "type", "reference" ], "additionalProperties": false } ] }, "collaborationReferenceIdSchema": { "type": "object", "discriminator": { "propertyName": "kind" }, "oneOf": [ { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "user" ] }, "id": { "type": "string", "format": "uuid" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "team" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "task" ] }, "id": { "type": "string", "format": "uuid" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "customer" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "quote" ] }, "id": { "$ref": "#/components/schemas/quoteIdSchema" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "invoice" ] }, "id": { "$ref": "#/components/schemas/invoiceIdSchema" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "product" ] }, "id": { "$ref": "#/components/schemas/productIdSchema" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "customer-asset" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" } }, "required": [ "kind", "id" ], "additionalProperties": false } ] }, "collaborationReferenceSchema": { "type": "object", "discriminator": { "propertyName": "kind" }, "oneOf": [ { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "user" ] }, "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "team" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "task" ] }, "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "customer" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "quote" ] }, "id": { "$ref": "#/components/schemas/quoteIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "invoice" ] }, "id": { "$ref": "#/components/schemas/invoiceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "product" ] }, "id": { "$ref": "#/components/schemas/productIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "customer-asset" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false } ] }, "invoiceIdSchema": { "type": "string", "format": "uuid" }, "invoiceNoteInputSchema": { "$ref": "#/components/schemas/collaborationComposerInputSchema" }, "invoiceNoteSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "invoiceId": { "$ref": "#/components/schemas/invoiceIdSchema" }, "organizationId": { "type": "string", "format": "uuid" }, "contentText": { "type": "string", "minLength": 1, "maxLength": 20000 }, "contentDoc": { "$ref": "#/components/schemas/collaborationDocumentSchema" }, "authorId": { "type": [ "string", "null" ], "format": "uuid" }, "author": { "anyOf": [ { "$ref": "#/components/schemas/collaborationCommentAuthorSchema" }, { "type": "null" } ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "invoiceId", "organizationId", "contentText", "contentDoc", "authorId", "author", "createdAt", "updatedAt" ] }, "numericReferenceIdSchema": { "type": "string", "pattern": "^[1-9]\\d*$" }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "productIdSchema": { "type": "string", "format": "uuid" }, "quoteIdSchema": { "type": "string", "format": "uuid" } } ``` # Pause invoice reminders. (/openapi/pauseInvoiceReminders) Stops payment reminders on the invoice, for example while a dispute or payment plan runs. With `until`, reminders resume on that date. ## POST /v1/organizations/{organizationId}/invoices/{invoiceId}/pause-reminders ```json { "operationId": "pauseInvoiceReminders", "summary": "Pause invoice reminders.", "description": "Stops payment reminders on the invoice, for example while a dispute or payment plan runs. With `until`, reminders resume on that date.", "tags": [ "Invoices" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "invoiceId", "required": true, "schema": { "$ref": "#/components/schemas/invoiceIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/pauseInvoiceRemindersInputSchema" } } }, "description": "Why reminders pause, and optionally the date they resume.", "required": true }, "responses": { "200": { "description": "Reminders were paused.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/publicApiSuccessResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "invoiceIdSchema": { "type": "string", "format": "uuid" }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "pauseInvoiceRemindersInputSchema": { "type": "object", "properties": { "reason": { "type": "string", "minLength": 1, "maxLength": 500 }, "until": { "type": [ "string", "null" ], "format": "date" } }, "required": [ "reason" ] }, "publicApiSuccessResponseSchema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [ true ] } }, "required": [ "success" ] } } ``` # Record an invoice payment. (/openapi/recordInvoicePayment) Records a manual payment (bank transfer, cash, card, or other) against a sent or overdue invoice. Partial payments keep the invoice open; the payment that settles the balance marks it paid. ## POST /v1/organizations/{organizationId}/invoices/{invoiceId}/payments ```json { "operationId": "recordInvoicePayment", "summary": "Record an invoice payment.", "description": "Records a manual payment (bank transfer, cash, card, or other) against a sent or overdue invoice. Partial payments keep the invoice open; the payment that settles the balance marks it paid.", "tags": [ "Invoices" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "invoiceId", "required": true, "schema": { "$ref": "#/components/schemas/invoiceIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/recordInvoicePaymentInputSchema" } } }, "description": "The amount in major units, the method, the date received, and an optional reference and note.", "required": true }, "responses": { "200": { "description": "Returns the invoice, the recorded payment, and whether this payment settled the invoice.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/invoicePaymentResultSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "discountKindSchema": { "type": "string", "enum": [ "none", "percentage", "fixed" ] }, "documentVatRegimeSchema": { "type": "string", "enum": [ "standard", "reverse_charge", "exempt", "kor", "margin_scheme" ] }, "invoiceCurrencyCodeSchema": { "type": "string", "pattern": "^[A-Z]{3}$" }, "invoiceDisplayNumberSchema": { "type": "string", "minLength": 1, "maxLength": 40 }, "invoiceIdSchema": { "type": "string", "format": "uuid" }, "invoiceKindSchema": { "type": "string", "enum": [ "invoice", "credit_note", "deposit" ] }, "invoiceNumberPrefixSchema": { "type": "string", "maxLength": 16, "pattern": "^[A-Z0-9-]*$" }, "invoiceNumberSchema": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 }, "invoicePaymentMethodSchema": { "type": "string", "enum": [ "stripe", "bank_transfer", "cash", "other" ] }, "invoicePaymentResultSchema": { "type": "object", "properties": { "invoice": { "$ref": "#/components/schemas/invoiceSchema" }, "payment": { "$ref": "#/components/schemas/invoicePaymentSchema" }, "newlyPaid": { "type": "boolean" } }, "required": [ "invoice", "payment", "newlyPaid" ] }, "invoicePaymentSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "organizationId": { "type": "string", "format": "uuid" }, "invoiceId": { "$ref": "#/components/schemas/invoiceIdSchema" }, "provider": { "type": "string", "enum": [ "stripe", "manual" ] }, "method": { "$ref": "#/components/schemas/invoicePaymentMethodSchema" }, "stripePaymentIntentId": { "type": [ "string", "null" ], "minLength": 1, "maxLength": 255 }, "stripeChargeId": { "type": [ "string", "null" ], "minLength": 1, "maxLength": 255 }, "stripeDestinationAccountId": { "type": [ "string", "null" ], "minLength": 1, "maxLength": 255 }, "status": { "$ref": "#/components/schemas/invoicePaymentStatusSchema" }, "currency": { "$ref": "#/components/schemas/invoiceCurrencyCodeSchema" }, "amountTotal": { "type": "integer", "minimum": 0 }, "applicationFeeMode": { "type": "string", "enum": [ "percentage", "fixed" ] }, "applicationFeeAmount": { "type": "integer", "minimum": 0 }, "applicationFeeBps": { "type": "integer", "minimum": 0, "maximum": 10000 }, "failureCode": { "type": [ "string", "null" ] }, "failureMessage": { "type": [ "string", "null" ] }, "paidAt": { "type": [ "string", "null" ], "format": "date-time" }, "canceledAt": { "type": [ "string", "null" ], "format": "date-time" }, "refundedAt": { "type": [ "string", "null" ], "format": "date-time" }, "disputedAt": { "type": [ "string", "null" ], "format": "date-time" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" }, "amountCents": { "type": "integer", "minimum": 0 }, "receivedOn": { "type": [ "string", "null" ], "format": "date" }, "reference": { "type": [ "string", "null" ] }, "note": { "type": [ "string", "null" ] }, "recordedBy": { "type": [ "string", "null" ], "format": "uuid" } }, "required": [ "id", "organizationId", "invoiceId", "provider", "method", "stripePaymentIntentId", "stripeChargeId", "stripeDestinationAccountId", "status", "currency", "amountTotal", "applicationFeeMode", "applicationFeeAmount", "applicationFeeBps", "failureCode", "failureMessage", "paidAt", "canceledAt", "refundedAt", "disputedAt", "createdAt", "updatedAt", "amountCents", "receivedOn", "reference", "note", "recordedBy" ] }, "invoicePaymentStatusSchema": { "type": "string", "enum": [ "requires_payment_method", "requires_action", "processing", "succeeded", "canceled", "failed", "refunded", "disputed" ] }, "invoiceSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/invoiceIdSchema" }, "organizationId": { "type": "string", "format": "uuid" }, "customerId": { "type": "integer", "exclusiveMinimum": 0 }, "customerContactId": { "type": [ "string", "null" ], "format": "uuid" }, "customerReference": { "type": [ "string", "null" ] }, "number": { "anyOf": [ { "$ref": "#/components/schemas/invoiceNumberSchema" }, { "type": "null" } ] }, "numberYear": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 }, "numberPrefix": { "$ref": "#/components/schemas/invoiceNumberPrefixSchema" }, "displayNumber": { "anyOf": [ { "$ref": "#/components/schemas/invoiceDisplayNumberSchema" }, { "type": "null" } ] }, "status": { "$ref": "#/components/schemas/invoiceStatusSchema" }, "dueDays": { "type": [ "integer", "null" ], "minimum": 0, "maximum": 365 }, "invoiceTemplateId": { "type": [ "string", "null" ], "format": "uuid" }, "title": { "type": "string" }, "intro": { "type": [ "string", "null" ] }, "terms": { "type": [ "string", "null" ] }, "currency": { "$ref": "#/components/schemas/invoiceCurrencyCodeSchema" }, "vatInclusive": { "type": "boolean" }, "discountKind": { "$ref": "#/components/schemas/discountKindSchema" }, "discountValue": { "type": "number" }, "discountDescription": { "type": [ "string", "null" ], "maxLength": 500 }, "promotionId": { "type": [ "string", "null" ], "format": "uuid" }, "subtotalExcl": { "type": "number" }, "vatTotal": { "type": "number" }, "totalIncl": { "type": "number" }, "issueDate": { "type": [ "string", "null" ] }, "dueDate": { "type": [ "string", "null" ] }, "publicMessage": { "type": [ "string", "null" ] }, "footerText": { "type": [ "string", "null" ] }, "sourceQuoteId": { "type": [ "string", "null" ], "format": "uuid" }, "deliveryDate": { "type": [ "string", "null" ] }, "kind": { "$ref": "#/components/schemas/invoiceKindSchema" }, "originalInvoiceId": { "type": [ "string", "null" ], "format": "uuid" }, "vatRegime": { "$ref": "#/components/schemas/documentVatRegimeSchema" }, "sellerName": { "type": [ "string", "null" ] }, "sellerAddressLine1": { "type": [ "string", "null" ] }, "sellerAddressLine2": { "type": [ "string", "null" ] }, "sellerAddressPostalCode": { "type": [ "string", "null" ] }, "sellerAddressCity": { "type": [ "string", "null" ] }, "sellerAddressCountry": { "type": [ "string", "null" ] }, "sellerEmail": { "type": [ "string", "null" ] }, "sellerPhone": { "type": [ "string", "null" ] }, "sellerKvk": { "type": [ "string", "null" ] }, "sellerBtw": { "type": [ "string", "null" ] }, "sellerIban": { "type": [ "string", "null" ] }, "buyerName": { "type": [ "string", "null" ] }, "buyerAddressLine1": { "type": [ "string", "null" ] }, "buyerAddressLine2": { "type": [ "string", "null" ] }, "buyerAddressPostalCode": { "type": [ "string", "null" ] }, "buyerAddressCity": { "type": [ "string", "null" ] }, "buyerAddressCountry": { "type": [ "string", "null" ] }, "buyerEmail": { "type": [ "string", "null" ] }, "buyerKvk": { "type": [ "string", "null" ] }, "buyerBtw": { "type": [ "string", "null" ] }, "termsRichText": { "type": [ "string", "null" ], "maxLength": 100000 }, "termsUrl": { "type": [ "string", "null" ] }, "termsPdfFileId": { "type": [ "string", "null" ], "format": "uuid" }, "termsPdfVersionId": { "type": [ "string", "null" ], "format": "uuid" }, "sentAt": { "type": [ "string", "null" ], "format": "date-time" }, "paidAt": { "type": [ "string", "null" ], "format": "date-time" }, "cancelledAt": { "type": [ "string", "null" ], "format": "date-time" }, "archivedAt": { "type": [ "string", "null" ], "format": "date-time" }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "updatedBy": { "type": [ "string", "null" ], "format": "uuid" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" }, "amountPaidCents": { "type": "integer", "minimum": 0 }, "amountDueCents": { "type": "integer", "minimum": 0 }, "uncollectibleAt": { "type": [ "string", "null" ], "format": "date-time" }, "uncollectibleReason": { "type": [ "string", "null" ] }, "onlinePaymentEnabled": { "type": "boolean", "default": true } }, "required": [ "id", "organizationId", "customerId", "customerContactId", "customerReference", "number", "numberYear", "numberPrefix", "displayNumber", "status", "dueDays", "invoiceTemplateId", "title", "intro", "terms", "currency", "vatInclusive", "discountKind", "discountValue", "discountDescription", "promotionId", "subtotalExcl", "vatTotal", "totalIncl", "issueDate", "dueDate", "publicMessage", "footerText", "sourceQuoteId", "deliveryDate", "kind", "originalInvoiceId", "vatRegime", "sellerName", "sellerAddressLine1", "sellerAddressLine2", "sellerAddressPostalCode", "sellerAddressCity", "sellerAddressCountry", "sellerEmail", "sellerPhone", "sellerKvk", "sellerBtw", "sellerIban", "buyerName", "buyerAddressLine1", "buyerAddressLine2", "buyerAddressPostalCode", "buyerAddressCity", "buyerAddressCountry", "buyerEmail", "buyerKvk", "buyerBtw", "termsRichText", "termsUrl", "termsPdfFileId", "termsPdfVersionId", "sentAt", "paidAt", "cancelledAt", "archivedAt", "createdBy", "updatedBy", "createdAt", "updatedAt", "amountPaidCents", "amountDueCents", "uncollectibleAt", "uncollectibleReason", "onlinePaymentEnabled" ] }, "invoiceStatusSchema": { "type": "string", "enum": [ "draft", "sent", "paid", "overdue", "cancelled", "uncollectible" ] }, "manualInvoicePaymentMethodSchema": { "type": "string", "enum": [ "stripe", "bank_transfer", "cash", "other" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "recordInvoicePaymentInputSchema": { "type": "object", "properties": { "amount": { "type": "number", "exclusiveMinimum": 0, "maximum": 100000000 }, "method": { "$ref": "#/components/schemas/manualInvoicePaymentMethodSchema" }, "receivedOn": { "type": "string", "format": "date" }, "reference": { "type": [ "string", "null" ], "maxLength": 240 }, "note": { "type": [ "string", "null" ], "maxLength": 2000 } }, "required": [ "amount", "method", "receivedOn" ] } } ``` # Resume invoice reminders. (/openapi/resumeInvoiceReminders) Resumes payment reminders that were paused on the invoice. ## POST /v1/organizations/{organizationId}/invoices/{invoiceId}/resume-reminders ```json { "operationId": "resumeInvoiceReminders", "summary": "Resume invoice reminders.", "description": "Resumes payment reminders that were paused on the invoice.", "tags": [ "Invoices" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "invoiceId", "required": true, "schema": { "$ref": "#/components/schemas/invoiceIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Reminders were resumed.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/publicApiSuccessResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "invoiceIdSchema": { "type": "string", "format": "uuid" }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "publicApiSuccessResponseSchema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [ true ] } }, "required": [ "success" ] } } ``` # Send an invoice. (/openapi/sendInvoice) Transition a draft invoice to sent, creating its Stripe payment option and public link. ## POST /v1/organizations/{organizationId}/invoices/{invoiceId}/send ```json { "operationId": "sendInvoice", "summary": "Send an invoice.", "description": "Transition a draft invoice to sent, creating its Stripe payment option and public link.", "tags": [ "Invoices" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "invoiceId", "required": true, "schema": { "$ref": "#/components/schemas/invoiceIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/invoiceSendInputSchema" } } }, "description": "Send options for the invoice." }, "responses": { "200": { "description": "Returns the sent invoice.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/invoiceSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "discountKindSchema": { "type": "string", "enum": [ "none", "percentage", "fixed" ] }, "documentVatRegimeSchema": { "type": "string", "enum": [ "standard", "reverse_charge", "exempt", "kor", "margin_scheme" ] }, "invoiceCurrencyCodeSchema": { "type": "string", "pattern": "^[A-Z]{3}$" }, "invoiceDisplayNumberSchema": { "type": "string", "minLength": 1, "maxLength": 40 }, "invoiceIdSchema": { "type": "string", "format": "uuid" }, "invoiceKindSchema": { "type": "string", "enum": [ "invoice", "credit_note", "deposit" ] }, "invoiceNumberPrefixSchema": { "type": "string", "maxLength": 16, "pattern": "^[A-Z0-9-]*$" }, "invoiceNumberSchema": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 }, "invoiceSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/invoiceIdSchema" }, "organizationId": { "type": "string", "format": "uuid" }, "customerId": { "type": "integer", "exclusiveMinimum": 0 }, "customerContactId": { "type": [ "string", "null" ], "format": "uuid" }, "customerReference": { "type": [ "string", "null" ] }, "number": { "anyOf": [ { "$ref": "#/components/schemas/invoiceNumberSchema" }, { "type": "null" } ] }, "numberYear": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 }, "numberPrefix": { "$ref": "#/components/schemas/invoiceNumberPrefixSchema" }, "displayNumber": { "anyOf": [ { "$ref": "#/components/schemas/invoiceDisplayNumberSchema" }, { "type": "null" } ] }, "status": { "$ref": "#/components/schemas/invoiceStatusSchema" }, "dueDays": { "type": [ "integer", "null" ], "minimum": 0, "maximum": 365 }, "invoiceTemplateId": { "type": [ "string", "null" ], "format": "uuid" }, "title": { "type": "string" }, "intro": { "type": [ "string", "null" ] }, "terms": { "type": [ "string", "null" ] }, "currency": { "$ref": "#/components/schemas/invoiceCurrencyCodeSchema" }, "vatInclusive": { "type": "boolean" }, "discountKind": { "$ref": "#/components/schemas/discountKindSchema" }, "discountValue": { "type": "number" }, "discountDescription": { "type": [ "string", "null" ], "maxLength": 500 }, "promotionId": { "type": [ "string", "null" ], "format": "uuid" }, "subtotalExcl": { "type": "number" }, "vatTotal": { "type": "number" }, "totalIncl": { "type": "number" }, "issueDate": { "type": [ "string", "null" ] }, "dueDate": { "type": [ "string", "null" ] }, "publicMessage": { "type": [ "string", "null" ] }, "footerText": { "type": [ "string", "null" ] }, "sourceQuoteId": { "type": [ "string", "null" ], "format": "uuid" }, "deliveryDate": { "type": [ "string", "null" ] }, "kind": { "$ref": "#/components/schemas/invoiceKindSchema" }, "originalInvoiceId": { "type": [ "string", "null" ], "format": "uuid" }, "vatRegime": { "$ref": "#/components/schemas/documentVatRegimeSchema" }, "sellerName": { "type": [ "string", "null" ] }, "sellerAddressLine1": { "type": [ "string", "null" ] }, "sellerAddressLine2": { "type": [ "string", "null" ] }, "sellerAddressPostalCode": { "type": [ "string", "null" ] }, "sellerAddressCity": { "type": [ "string", "null" ] }, "sellerAddressCountry": { "type": [ "string", "null" ] }, "sellerEmail": { "type": [ "string", "null" ] }, "sellerPhone": { "type": [ "string", "null" ] }, "sellerKvk": { "type": [ "string", "null" ] }, "sellerBtw": { "type": [ "string", "null" ] }, "sellerIban": { "type": [ "string", "null" ] }, "buyerName": { "type": [ "string", "null" ] }, "buyerAddressLine1": { "type": [ "string", "null" ] }, "buyerAddressLine2": { "type": [ "string", "null" ] }, "buyerAddressPostalCode": { "type": [ "string", "null" ] }, "buyerAddressCity": { "type": [ "string", "null" ] }, "buyerAddressCountry": { "type": [ "string", "null" ] }, "buyerEmail": { "type": [ "string", "null" ] }, "buyerKvk": { "type": [ "string", "null" ] }, "buyerBtw": { "type": [ "string", "null" ] }, "termsRichText": { "type": [ "string", "null" ], "maxLength": 100000 }, "termsUrl": { "type": [ "string", "null" ] }, "termsPdfFileId": { "type": [ "string", "null" ], "format": "uuid" }, "termsPdfVersionId": { "type": [ "string", "null" ], "format": "uuid" }, "sentAt": { "type": [ "string", "null" ], "format": "date-time" }, "paidAt": { "type": [ "string", "null" ], "format": "date-time" }, "cancelledAt": { "type": [ "string", "null" ], "format": "date-time" }, "archivedAt": { "type": [ "string", "null" ], "format": "date-time" }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "updatedBy": { "type": [ "string", "null" ], "format": "uuid" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" }, "amountPaidCents": { "type": "integer", "minimum": 0 }, "amountDueCents": { "type": "integer", "minimum": 0 }, "uncollectibleAt": { "type": [ "string", "null" ], "format": "date-time" }, "uncollectibleReason": { "type": [ "string", "null" ] }, "onlinePaymentEnabled": { "type": "boolean", "default": true } }, "required": [ "id", "organizationId", "customerId", "customerContactId", "customerReference", "number", "numberYear", "numberPrefix", "displayNumber", "status", "dueDays", "invoiceTemplateId", "title", "intro", "terms", "currency", "vatInclusive", "discountKind", "discountValue", "discountDescription", "promotionId", "subtotalExcl", "vatTotal", "totalIncl", "issueDate", "dueDate", "publicMessage", "footerText", "sourceQuoteId", "deliveryDate", "kind", "originalInvoiceId", "vatRegime", "sellerName", "sellerAddressLine1", "sellerAddressLine2", "sellerAddressPostalCode", "sellerAddressCity", "sellerAddressCountry", "sellerEmail", "sellerPhone", "sellerKvk", "sellerBtw", "sellerIban", "buyerName", "buyerAddressLine1", "buyerAddressLine2", "buyerAddressPostalCode", "buyerAddressCity", "buyerAddressCountry", "buyerEmail", "buyerKvk", "buyerBtw", "termsRichText", "termsUrl", "termsPdfFileId", "termsPdfVersionId", "sentAt", "paidAt", "cancelledAt", "archivedAt", "createdBy", "updatedBy", "createdAt", "updatedAt", "amountPaidCents", "amountDueCents", "uncollectibleAt", "uncollectibleReason", "onlinePaymentEnabled" ] }, "invoiceSendInputSchema": { "type": "object", "properties": { "publicMessage": { "type": [ "string", "null" ], "maxLength": 20000 }, "issueDate": { "allOf": [ { "$ref": "#/components/schemas/isoDateOrNull" } ] }, "dueDate": { "allOf": [ { "$ref": "#/components/schemas/isoDateOrNull" } ] } } }, "invoiceStatusSchema": { "type": "string", "enum": [ "draft", "sent", "paid", "overdue", "cancelled", "uncollectible" ] }, "isoDateOrNull": { "type": [ "string", "null" ], "pattern": "^\\d{4}-\\d{2}-\\d{2}$" }, "organizationIdSchema": { "type": "string", "format": "uuid" } } ``` # Write off an invoice. (/openapi/writeOffInvoice) Marks a sent or overdue invoice uncollectible. Reminders stop and the open balance no longer counts as receivable. ## POST /v1/organizations/{organizationId}/invoices/{invoiceId}/write-off ```json { "operationId": "writeOffInvoice", "summary": "Write off an invoice.", "description": "Marks a sent or overdue invoice uncollectible. Reminders stop and the open balance no longer counts as receivable.", "tags": [ "Invoices" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "invoiceId", "required": true, "schema": { "$ref": "#/components/schemas/invoiceIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/markInvoiceUncollectibleInputSchema" } } }, "description": "Why the invoice is written off.", "required": true }, "responses": { "200": { "description": "Returns the uncollectible invoice.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/invoiceSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "discountKindSchema": { "type": "string", "enum": [ "none", "percentage", "fixed" ] }, "documentVatRegimeSchema": { "type": "string", "enum": [ "standard", "reverse_charge", "exempt", "kor", "margin_scheme" ] }, "invoiceCurrencyCodeSchema": { "type": "string", "pattern": "^[A-Z]{3}$" }, "invoiceDisplayNumberSchema": { "type": "string", "minLength": 1, "maxLength": 40 }, "invoiceIdSchema": { "type": "string", "format": "uuid" }, "invoiceKindSchema": { "type": "string", "enum": [ "invoice", "credit_note", "deposit" ] }, "invoiceNumberPrefixSchema": { "type": "string", "maxLength": 16, "pattern": "^[A-Z0-9-]*$" }, "invoiceNumberSchema": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 }, "invoiceSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/invoiceIdSchema" }, "organizationId": { "type": "string", "format": "uuid" }, "customerId": { "type": "integer", "exclusiveMinimum": 0 }, "customerContactId": { "type": [ "string", "null" ], "format": "uuid" }, "customerReference": { "type": [ "string", "null" ] }, "number": { "anyOf": [ { "$ref": "#/components/schemas/invoiceNumberSchema" }, { "type": "null" } ] }, "numberYear": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 }, "numberPrefix": { "$ref": "#/components/schemas/invoiceNumberPrefixSchema" }, "displayNumber": { "anyOf": [ { "$ref": "#/components/schemas/invoiceDisplayNumberSchema" }, { "type": "null" } ] }, "status": { "$ref": "#/components/schemas/invoiceStatusSchema" }, "dueDays": { "type": [ "integer", "null" ], "minimum": 0, "maximum": 365 }, "invoiceTemplateId": { "type": [ "string", "null" ], "format": "uuid" }, "title": { "type": "string" }, "intro": { "type": [ "string", "null" ] }, "terms": { "type": [ "string", "null" ] }, "currency": { "$ref": "#/components/schemas/invoiceCurrencyCodeSchema" }, "vatInclusive": { "type": "boolean" }, "discountKind": { "$ref": "#/components/schemas/discountKindSchema" }, "discountValue": { "type": "number" }, "discountDescription": { "type": [ "string", "null" ], "maxLength": 500 }, "promotionId": { "type": [ "string", "null" ], "format": "uuid" }, "subtotalExcl": { "type": "number" }, "vatTotal": { "type": "number" }, "totalIncl": { "type": "number" }, "issueDate": { "type": [ "string", "null" ] }, "dueDate": { "type": [ "string", "null" ] }, "publicMessage": { "type": [ "string", "null" ] }, "footerText": { "type": [ "string", "null" ] }, "sourceQuoteId": { "type": [ "string", "null" ], "format": "uuid" }, "deliveryDate": { "type": [ "string", "null" ] }, "kind": { "$ref": "#/components/schemas/invoiceKindSchema" }, "originalInvoiceId": { "type": [ "string", "null" ], "format": "uuid" }, "vatRegime": { "$ref": "#/components/schemas/documentVatRegimeSchema" }, "sellerName": { "type": [ "string", "null" ] }, "sellerAddressLine1": { "type": [ "string", "null" ] }, "sellerAddressLine2": { "type": [ "string", "null" ] }, "sellerAddressPostalCode": { "type": [ "string", "null" ] }, "sellerAddressCity": { "type": [ "string", "null" ] }, "sellerAddressCountry": { "type": [ "string", "null" ] }, "sellerEmail": { "type": [ "string", "null" ] }, "sellerPhone": { "type": [ "string", "null" ] }, "sellerKvk": { "type": [ "string", "null" ] }, "sellerBtw": { "type": [ "string", "null" ] }, "sellerIban": { "type": [ "string", "null" ] }, "buyerName": { "type": [ "string", "null" ] }, "buyerAddressLine1": { "type": [ "string", "null" ] }, "buyerAddressLine2": { "type": [ "string", "null" ] }, "buyerAddressPostalCode": { "type": [ "string", "null" ] }, "buyerAddressCity": { "type": [ "string", "null" ] }, "buyerAddressCountry": { "type": [ "string", "null" ] }, "buyerEmail": { "type": [ "string", "null" ] }, "buyerKvk": { "type": [ "string", "null" ] }, "buyerBtw": { "type": [ "string", "null" ] }, "termsRichText": { "type": [ "string", "null" ], "maxLength": 100000 }, "termsUrl": { "type": [ "string", "null" ] }, "termsPdfFileId": { "type": [ "string", "null" ], "format": "uuid" }, "termsPdfVersionId": { "type": [ "string", "null" ], "format": "uuid" }, "sentAt": { "type": [ "string", "null" ], "format": "date-time" }, "paidAt": { "type": [ "string", "null" ], "format": "date-time" }, "cancelledAt": { "type": [ "string", "null" ], "format": "date-time" }, "archivedAt": { "type": [ "string", "null" ], "format": "date-time" }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "updatedBy": { "type": [ "string", "null" ], "format": "uuid" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" }, "amountPaidCents": { "type": "integer", "minimum": 0 }, "amountDueCents": { "type": "integer", "minimum": 0 }, "uncollectibleAt": { "type": [ "string", "null" ], "format": "date-time" }, "uncollectibleReason": { "type": [ "string", "null" ] }, "onlinePaymentEnabled": { "type": "boolean", "default": true } }, "required": [ "id", "organizationId", "customerId", "customerContactId", "customerReference", "number", "numberYear", "numberPrefix", "displayNumber", "status", "dueDays", "invoiceTemplateId", "title", "intro", "terms", "currency", "vatInclusive", "discountKind", "discountValue", "discountDescription", "promotionId", "subtotalExcl", "vatTotal", "totalIncl", "issueDate", "dueDate", "publicMessage", "footerText", "sourceQuoteId", "deliveryDate", "kind", "originalInvoiceId", "vatRegime", "sellerName", "sellerAddressLine1", "sellerAddressLine2", "sellerAddressPostalCode", "sellerAddressCity", "sellerAddressCountry", "sellerEmail", "sellerPhone", "sellerKvk", "sellerBtw", "sellerIban", "buyerName", "buyerAddressLine1", "buyerAddressLine2", "buyerAddressPostalCode", "buyerAddressCity", "buyerAddressCountry", "buyerEmail", "buyerKvk", "buyerBtw", "termsRichText", "termsUrl", "termsPdfFileId", "termsPdfVersionId", "sentAt", "paidAt", "cancelledAt", "archivedAt", "createdBy", "updatedBy", "createdAt", "updatedAt", "amountPaidCents", "amountDueCents", "uncollectibleAt", "uncollectibleReason", "onlinePaymentEnabled" ] }, "invoiceStatusSchema": { "type": "string", "enum": [ "draft", "sent", "paid", "overdue", "cancelled", "uncollectible" ] }, "markInvoiceUncollectibleInputSchema": { "type": "object", "properties": { "reason": { "type": "string", "minLength": 1, "maxLength": 2000 } }, "required": [ "reason" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" } } ``` # Advance a invoice lifecycle stage. (/openapi/advanceInvoiceLifecycle) Moves the invoice to a manual milestone of its lifecycle. Only stages the lifecycle marks as manually advanceable are accepted. ## POST /v1/organizations/{organizationId}/invoices/{invoiceId}/lifecycle/advance ```json { "operationId": "advanceInvoiceLifecycle", "summary": "Advance a invoice lifecycle stage.", "description": "Moves the invoice to a manual milestone of its lifecycle. Only stages the lifecycle marks as manually advanceable are accepted.", "tags": [ "Invoices" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "invoiceId", "required": true, "schema": { "$ref": "#/components/schemas/invoiceIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/lifecycleAdvanceInputSchema" } } }, "description": "The stage key to move to.", "required": true }, "responses": { "200": { "description": "The stage was recorded.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/publicApiSuccessResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "invoiceIdSchema": { "type": "string", "format": "uuid" }, "lifecycleAdvanceInputSchema": { "type": "object", "properties": { "stageKey": { "type": "string", "minLength": 1, "maxLength": 80 } }, "required": [ "stageKey" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "publicApiSuccessResponseSchema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [ true ] } }, "required": [ "success" ] } } ``` # Update an invoice note. (/openapi/updateInvoiceNote) Update an internal note on an invoice. ## PATCH /v1/organizations/{organizationId}/invoices/{invoiceId}/notes/{noteId} ```json { "operationId": "updateInvoiceNote", "summary": "Update an invoice note.", "description": "Update an internal note on an invoice.", "tags": [ "Invoices" ], "parameters": [ { "in": "path", "name": "invoiceId", "required": true, "schema": { "$ref": "#/components/schemas/invoiceIdSchema" }, "example": "123" }, { "in": "path", "name": "noteId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/invoiceNoteInputSchema" } } }, "description": "The invoice note payload." }, "responses": { "200": { "description": "Returns the updated invoice note.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/invoiceNoteSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "collaborationBlockSchema": { "type": "object", "discriminator": { "propertyName": "type" }, "oneOf": [ { "type": "object", "properties": { "type": { "type": "string", "enum": [ "paragraph" ] }, "content": { "$ref": "#/components/schemas/collaborationInlineContentSchema" } }, "required": [ "type", "content" ], "additionalProperties": false }, { "type": "object", "properties": { "type": { "type": "string", "enum": [ "bullet-list" ] }, "items": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationInlineContentSchema" }, "minItems": 1, "maxItems": 500 } }, "required": [ "type", "items" ], "additionalProperties": false }, { "type": "object", "properties": { "type": { "type": "string", "enum": [ "numbered-list" ] }, "items": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationInlineContentSchema" }, "minItems": 1, "maxItems": 500 } }, "required": [ "type", "items" ], "additionalProperties": false } ] }, "collaborationCommentAuthorSchema": { "type": "object", "properties": { "userId": { "type": "string", "format": "uuid" }, "displayName": { "type": "string", "minLength": 1 }, "avatarUrl": { "type": [ "string", "null" ], "format": "uri" } }, "required": [ "userId", "displayName", "avatarUrl" ] }, "collaborationComposerInputSchema": { "type": "object", "properties": { "text": { "type": "string", "minLength": 1, "maxLength": 20000 }, "selections": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationComposerSelectionSchema" }, "maxItems": 100 } }, "required": [ "text", "selections" ], "additionalProperties": false }, "collaborationComposerSelectionSchema": { "type": "object", "properties": { "start": { "type": "integer", "minimum": 0, "maximum": 20000 }, "end": { "type": "integer", "exclusiveMinimum": 0, "maximum": 20000 }, "reference": { "$ref": "#/components/schemas/collaborationReferenceIdSchema" } }, "required": [ "start", "end", "reference" ], "additionalProperties": false }, "collaborationDocumentSchema": { "type": "object", "properties": { "version": { "type": "integer", "enum": [ 1 ] }, "blocks": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationBlockSchema" }, "minItems": 1, "maxItems": 1000 } }, "required": [ "version", "blocks" ], "additionalProperties": false }, "collaborationInlineContentSchema": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationInlineSchema" }, "minItems": 1, "maxItems": 1000 }, "collaborationInlineSchema": { "type": "object", "discriminator": { "propertyName": "type" }, "oneOf": [ { "type": "object", "properties": { "type": { "type": "string", "enum": [ "text" ] }, "text": { "type": "string", "minLength": 1, "maxLength": 20000 } }, "required": [ "type", "text" ], "additionalProperties": false }, { "type": "object", "properties": { "type": { "type": "string", "enum": [ "reference" ] }, "reference": { "$ref": "#/components/schemas/collaborationReferenceSchema" } }, "required": [ "type", "reference" ], "additionalProperties": false } ] }, "collaborationReferenceIdSchema": { "type": "object", "discriminator": { "propertyName": "kind" }, "oneOf": [ { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "user" ] }, "id": { "type": "string", "format": "uuid" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "team" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "task" ] }, "id": { "type": "string", "format": "uuid" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "customer" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "quote" ] }, "id": { "$ref": "#/components/schemas/quoteIdSchema" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "invoice" ] }, "id": { "$ref": "#/components/schemas/invoiceIdSchema" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "product" ] }, "id": { "$ref": "#/components/schemas/productIdSchema" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "customer-asset" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" } }, "required": [ "kind", "id" ], "additionalProperties": false } ] }, "collaborationReferenceSchema": { "type": "object", "discriminator": { "propertyName": "kind" }, "oneOf": [ { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "user" ] }, "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "team" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "task" ] }, "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "customer" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "quote" ] }, "id": { "$ref": "#/components/schemas/quoteIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "invoice" ] }, "id": { "$ref": "#/components/schemas/invoiceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "product" ] }, "id": { "$ref": "#/components/schemas/productIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "customer-asset" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false } ] }, "invoiceIdSchema": { "type": "string", "format": "uuid" }, "invoiceNoteInputSchema": { "$ref": "#/components/schemas/collaborationComposerInputSchema" }, "invoiceNoteSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "invoiceId": { "$ref": "#/components/schemas/invoiceIdSchema" }, "organizationId": { "type": "string", "format": "uuid" }, "contentText": { "type": "string", "minLength": 1, "maxLength": 20000 }, "contentDoc": { "$ref": "#/components/schemas/collaborationDocumentSchema" }, "authorId": { "type": [ "string", "null" ], "format": "uuid" }, "author": { "anyOf": [ { "$ref": "#/components/schemas/collaborationCommentAuthorSchema" }, { "type": "null" } ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "invoiceId", "organizationId", "contentText", "contentDoc", "authorId", "author", "createdAt", "updatedAt" ] }, "numericReferenceIdSchema": { "type": "string", "pattern": "^[1-9]\\d*$" }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "productIdSchema": { "type": "string", "format": "uuid" }, "quoteIdSchema": { "type": "string", "format": "uuid" } } ``` # Delete an invoice note. (/openapi/deleteInvoiceNote) Delete an internal note from an invoice. ## DELETE /v1/organizations/{organizationId}/invoices/{invoiceId}/notes/{noteId} ```json { "operationId": "deleteInvoiceNote", "summary": "Delete an invoice note.", "description": "Delete an internal note from an invoice.", "tags": [ "Invoices" ], "parameters": [ { "in": "path", "name": "invoiceId", "required": true, "schema": { "$ref": "#/components/schemas/invoiceIdSchema" }, "example": "123" }, { "in": "path", "name": "noteId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns a success payload when the note is deleted.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/publicApiSuccessResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "invoiceIdSchema": { "type": "string", "format": "uuid" }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "publicApiSuccessResponseSchema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [ true ] } }, "required": [ "success" ] } } ``` # Retrieve organization knowledge. (/openapi/retrieveKnowledge) Search authorized organization and system knowledge and return matching text chunks with public provenance. Internal embeddings, vector data, service metadata, and storage paths are never returned. ## POST /v1/organizations/{organizationId}/knowledge/retrieve ```json { "operationId": "retrieveKnowledge", "summary": "Retrieve organization knowledge.", "description": "Search authorized organization and system knowledge and return matching text chunks with public provenance. Internal embeddings, vector data, service metadata, and storage paths are never returned.", "tags": [ "Knowledge" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/knowledgeRetrievalHttpRequestSchema" } } }, "description": "A bounded natural-language query with optional collection and system-knowledge scope.", "required": true }, "responses": { "200": { "description": "Returns ranked knowledge chunks and their public provenance.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/knowledgeRetrievalHttpResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "knowledgeRetrievalHttpChunkSchema": { "type": "object", "properties": { "content": { "type": "string", "minLength": 1, "maxLength": 2000 }, "provenance": { "$ref": "#/components/schemas/knowledgeRetrievalHttpProvenanceSchema" }, "score": { "type": [ "number", "null" ], "minimum": 0, "maximum": 1 } }, "required": [ "content", "provenance", "score" ] }, "knowledgeRetrievalHttpProvenanceSchema": { "type": "object", "properties": { "chunkId": { "type": "string", "format": "uuid" }, "collectionId": { "type": "string", "format": "uuid" }, "fileId": { "type": "string", "format": "uuid" }, "sourceUri": { "type": [ "string", "null" ], "maxLength": 2048 }, "title": { "type": "string", "minLength": 1, "maxLength": 240 } }, "required": [ "chunkId", "collectionId", "fileId", "sourceUri", "title" ] }, "knowledgeRetrievalHttpRequestSchema": { "type": "object", "properties": { "query": { "type": "string", "minLength": 1, "maxLength": 2000 }, "limit": { "type": "integer", "minimum": 1, "maximum": 50 }, "includeSystem": { "type": "boolean" }, "collectionIds": { "type": "array", "items": { "type": "string", "format": "uuid" }, "maxItems": 50 } }, "required": [ "query" ], "additionalProperties": false }, "knowledgeRetrievalHttpResponseSchema": { "type": "object", "properties": { "chunks": { "type": "array", "items": { "$ref": "#/components/schemas/knowledgeRetrievalHttpChunkSchema" } } }, "required": [ "chunks" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" } } ``` # List invitations. (/openapi/listInvitations) Return pending and historical invitations for an organization. ## GET /v1/organizations/{organizationId}/invitations ```json { "operationId": "listInvitations", "summary": "List invitations.", "description": "Return pending and historical invitations for an organization.", "tags": [ "Members" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns paginated invitations.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/invitationsListResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "invitationsListResponseSchema": { "type": "object" }, "organizationIdSchema": { "type": "string", "format": "uuid" } } ``` # List organization members. (/openapi/listMembers) Return members for an organization the authenticated user can access. ## GET /v1/organizations/{organizationId}/members ```json { "operationId": "listMembers", "summary": "List organization members.", "description": "Return members for an organization the authenticated user can access.", "tags": [ "Members" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns paginated organization members.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/membersListResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "membersListResponseSchema": { "type": "object" }, "organizationIdSchema": { "type": "string", "format": "uuid" } } ``` # Invite a user. (/openapi/inviteUser) Create an invitation and dispatch the invite email. ## POST /v1/organizations/{organizationId}/members ```json { "operationId": "inviteUser", "summary": "Invite a user.", "description": "Create an invitation and dispatch the invite email.", "tags": [ "Members" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/inviteMemberInputSchema" } } }, "description": "The invitation payload." }, "responses": { "201": { "description": "Returns the created invitation.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/invitationRowSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "invitationRowSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "organizationId": { "anyOf": [ { "$ref": "#/components/schemas/organizationIdSchema" }, { "type": "null" } ] }, "email": { "type": "string", "format": "email" }, "roleId": { "type": "string", "format": "uuid" }, "roleName": { "type": "string" }, "invitedBy": { "type": [ "string", "null" ], "format": "uuid" }, "expiresAt": { "type": "string" }, "acceptedAt": { "type": [ "string", "null" ] }, "declinedAt": { "type": [ "string", "null" ] }, "createdAt": { "type": "string" } }, "required": [ "id", "organizationId", "email", "roleId", "roleName", "invitedBy", "expiresAt", "acceptedAt", "declinedAt", "createdAt" ] }, "inviteMemberInputSchema": { "type": "object", "properties": { "email": { "type": "string", "format": "email" }, "roleId": { "type": "string", "format": "uuid" }, "prefill": { "type": "object", "properties": { "firstName": { "type": "string", "maxLength": 128 }, "lastName": { "type": "string", "maxLength": 128 } } } }, "required": [ "email", "roleId" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" } } ``` # Get an invitation. (/openapi/getInvitation) Return detail for an organization invitation. ## GET /v1/organizations/{organizationId}/invitations/{invitationId} ```json { "operationId": "getInvitation", "summary": "Get an invitation.", "description": "Return detail for an organization invitation.", "tags": [ "Members" ], "parameters": [ { "in": "path", "name": "invitationId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns the requested invitation.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/invitationDetailSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "invitationDetailSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "organizationId": { "anyOf": [ { "$ref": "#/components/schemas/organizationIdSchema" }, { "type": "null" } ] }, "email": { "type": "string", "format": "email" }, "roleId": { "type": "string", "format": "uuid" }, "roleName": { "type": "string" }, "invitedBy": { "type": [ "string", "null" ], "format": "uuid" }, "expiresAt": { "type": "string" }, "acceptedAt": { "type": [ "string", "null" ] }, "declinedAt": { "type": [ "string", "null" ] }, "createdAt": { "type": "string" }, "status": { "$ref": "#/components/schemas/invitationStatusSchema" }, "prefill": { "$ref": "#/components/schemas/invitationPrefillSchema" } }, "required": [ "id", "organizationId", "email", "roleId", "roleName", "invitedBy", "expiresAt", "acceptedAt", "declinedAt", "createdAt", "status", "prefill" ] }, "invitationPrefillSchema": { "type": "object", "properties": { "firstName": { "type": "string", "maxLength": 128 }, "lastName": { "type": "string", "maxLength": 128 } } }, "invitationStatusSchema": { "type": "string", "enum": [ "pending", "expired", "accepted", "declined" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" } } ``` # Update an invitation. (/openapi/updateInvitation) Update a pending organization invitation. ## PATCH /v1/organizations/{organizationId}/invitations/{invitationId} ```json { "operationId": "updateInvitation", "summary": "Update an invitation.", "description": "Update a pending organization invitation.", "tags": [ "Members" ], "parameters": [ { "in": "path", "name": "invitationId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/updateInvitationInputSchema" } } }, "description": "The invitation patch payload." }, "responses": { "200": { "description": "Returns the updated invitation.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/invitationDetailSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "invitationDetailSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "organizationId": { "anyOf": [ { "$ref": "#/components/schemas/organizationIdSchema" }, { "type": "null" } ] }, "email": { "type": "string", "format": "email" }, "roleId": { "type": "string", "format": "uuid" }, "roleName": { "type": "string" }, "invitedBy": { "type": [ "string", "null" ], "format": "uuid" }, "expiresAt": { "type": "string" }, "acceptedAt": { "type": [ "string", "null" ] }, "declinedAt": { "type": [ "string", "null" ] }, "createdAt": { "type": "string" }, "status": { "$ref": "#/components/schemas/invitationStatusSchema" }, "prefill": { "$ref": "#/components/schemas/invitationPrefillSchema" } }, "required": [ "id", "organizationId", "email", "roleId", "roleName", "invitedBy", "expiresAt", "acceptedAt", "declinedAt", "createdAt", "status", "prefill" ] }, "invitationPrefillSchema": { "type": "object", "properties": { "firstName": { "type": "string", "maxLength": 128 }, "lastName": { "type": "string", "maxLength": 128 } } }, "invitationStatusSchema": { "type": "string", "enum": [ "pending", "expired", "accepted", "declined" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "updateInvitationInputSchema": { "type": "object", "properties": { "email": { "type": "string", "format": "email" }, "roleId": { "type": "string", "format": "uuid" }, "prefill": { "allOf": [ { "$ref": "#/components/schemas/invitationPrefillSchema" } ] } } } } ``` # Revoke an invitation. (/openapi/revokeInvitation) Revoke a pending organization invitation. ## DELETE /v1/organizations/{organizationId}/invitations/{invitationId} ```json { "operationId": "revokeInvitation", "summary": "Revoke an invitation.", "description": "Revoke a pending organization invitation.", "tags": [ "Members" ], "parameters": [ { "in": "path", "name": "invitationId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns a success payload when the invitation is revoked.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/publicApiSuccessResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "organizationIdSchema": { "type": "string", "format": "uuid" }, "publicApiSuccessResponseSchema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [ true ] } }, "required": [ "success" ] } } ``` # Get an organization member. (/openapi/getMember) Return detail for a member in the specified organization. ## GET /v1/organizations/{organizationId}/members/{userId} ```json { "operationId": "getMember", "summary": "Get an organization member.", "description": "Return detail for a member in the specified organization.", "tags": [ "Members" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "userId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns the requested member detail.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/userDetailSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "memberRowSchema": { "type": "object", "properties": { "userId": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "email": { "type": [ "string", "null" ], "format": "email" }, "firstName": { "type": [ "string", "null" ] }, "lastName": { "type": [ "string", "null" ] }, "avatarUrl": { "type": [ "string", "null" ], "format": "uri" }, "roleId": { "type": "string", "format": "uuid" }, "roleKey": { "type": "string" }, "roleName": { "type": "string" }, "disabledAt": { "type": [ "string", "null" ] }, "joinedAt": { "type": "string" }, "lastActiveAt": { "type": [ "string", "null" ] } }, "required": [ "userId", "organizationId", "email", "firstName", "lastName", "avatarUrl", "roleId", "roleKey", "roleName", "disabledAt", "joinedAt", "lastActiveAt" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "systemUserOrganizationSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/organizationIdSchema" }, "name": { "type": "string" }, "slug": { "type": "string" }, "disabledAt": { "type": [ "string", "null" ] } }, "required": [ "id", "name", "slug", "disabledAt" ] }, "userDetailSchema": { "type": "object", "properties": { "userId": { "type": "string", "format": "uuid" }, "email": { "type": [ "string", "null" ], "format": "email" }, "firstName": { "type": [ "string", "null" ] }, "lastName": { "type": [ "string", "null" ] }, "username": { "$ref": "#/components/schemas/usernameSchema" }, "avatarUrl": { "type": [ "string", "null" ], "format": "uri" }, "organizationMembership": { "anyOf": [ { "$ref": "#/components/schemas/memberRowSchema" }, { "type": "null" } ] }, "organizationMemberships": { "type": "array", "items": { "$ref": "#/components/schemas/userOrganizationMembershipSchema" } }, "systemRoleIds": { "type": "array", "items": { "type": "string", "format": "uuid" } }, "systemRoleKeys": { "type": "array", "items": { "type": "string" } }, "organizations": { "type": "array", "items": { "$ref": "#/components/schemas/systemUserOrganizationSchema" } }, "disabledAt": { "type": [ "string", "null" ] }, "bannedUntil": { "type": [ "string", "null" ] }, "createdAt": { "type": [ "string", "null" ] }, "lastActiveAt": { "type": [ "string", "null" ] } }, "required": [ "userId", "email", "firstName", "lastName", "username", "avatarUrl", "organizationMembership", "organizationMemberships", "systemRoleIds", "systemRoleKeys", "organizations", "disabledAt", "bannedUntil", "createdAt", "lastActiveAt" ] }, "userOrganizationMembershipSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/organizationIdSchema" }, "name": { "type": "string" }, "slug": { "type": "string" }, "disabledAt": { "type": [ "string", "null" ] }, "roleId": { "type": "string", "format": "uuid" }, "roleKey": { "type": "string" }, "roleName": { "type": "string" }, "joinedAt": { "type": "string" }, "lastActiveAt": { "type": [ "string", "null" ] } }, "required": [ "id", "name", "slug", "disabledAt", "roleId", "roleKey", "roleName", "joinedAt", "lastActiveAt" ] }, "usernameSchema": { "type": "string", "minLength": 3, "maxLength": 30 } } ``` # Update a member role. (/openapi/updateMemberRole) Update the role assigned to an organization member. ## PATCH /v1/organizations/{organizationId}/members/{userId} ```json { "operationId": "updateMemberRole", "summary": "Update a member role.", "description": "Update the role assigned to an organization member.", "tags": [ "Members" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "userId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/organizationMemberRoleUpdateInputSchema" } } }, "description": "The role assignment payload." }, "responses": { "200": { "description": "Returns a success payload when the member role is updated.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/publicApiSuccessResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "organizationIdSchema": { "type": "string", "format": "uuid" }, "organizationMemberRoleUpdateInputSchema": { "type": "object", "properties": { "roleId": { "type": "string", "format": "uuid" } }, "required": [ "roleId" ] }, "publicApiSuccessResponseSchema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [ true ] } }, "required": [ "success" ] } } ``` # List notification preferences. (/openapi/listNotificationPreferences) Return account and organization channel preferences for the authenticated member. ## GET /v1/organizations/{organizationId}/notification-preferences ```json { "operationId": "listNotificationPreferences", "summary": "List notification preferences.", "description": "Return account and organization channel preferences for the authenticated member.", "tags": [ "Notifications" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns notification channel preferences.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/notificationPreferencesListSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "notificationChannelSchema": { "type": "string", "enum": [ "in_app", "email", "push" ] }, "notificationDateTimeSchema": { "type": "string", "format": "date-time" }, "notificationPreferenceSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "userId": { "type": "string", "format": "uuid" }, "organizationId": { "type": [ "string", "null" ], "format": "uuid" }, "type": { "anyOf": [ { "$ref": "#/components/schemas/notificationTypeSchema" }, { "type": "string", "enum": [ "*" ] } ] }, "channel": { "$ref": "#/components/schemas/notificationChannelSchema" }, "enabled": { "type": "boolean" }, "createdAt": { "$ref": "#/components/schemas/notificationDateTimeSchema" } }, "required": [ "id", "userId", "organizationId", "type", "channel", "enabled", "createdAt" ] }, "notificationPreferencesListSchema": { "type": "array", "items": { "$ref": "#/components/schemas/notificationPreferenceSchema" } }, "notificationTypeSchema": { "type": "string", "enum": [ "task.assigned", "task.comment", "task.updated", "collaboration.mention", "collaboration.note", "task.due-soon", "task.overdue", "agenda.assigned", "agenda.updated", "agent.approval-required", "agent.run-failed", "agent.run-completed", "workflow.approval-required", "workflow.run-failed", "workflow.run-completed", "workflow.notification", "inbox.message", "quote.sent", "quote.viewed", "quote.accepted", "quote.rejected", "quote.changes-requested", "quote.expired", "quote.deposit-paid", "invoice.paid", "invoice.overdue", "invoice.viewed", "invoice.partially-paid", "invoice.payment-failed", "invoice.disputed", "invoice.final-notice-sent", "order.backordered", "order.confirmed", "shipment.exception", "purchase-order.received", "purchase-order.overdue", "inventory.low-stock", "task.stage-changed", "billing.usage-threshold", "organization.invite", "organization.membership-updated", "time-off.requested", "time-off.decided", "time-off.cancelled", "sick-leave.reported", "sick-leave.returned", "sick-leave.follow-up", "timesheet.submitted", "timesheet.decided", "expense.submitted", "expense.decided", "expense.reimbursed", "employee.document-expiring", "employee.contract-ending", "equipment.assigned", "onboarding.item-assigned", "hiring.application-received", "time.timer-forgotten", "time.budget-warning", "approval.requested", "approval.decided", "approval.escalated" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" } } ``` # Upsert a notification preference. (/openapi/putNotificationPreference) Create or update a channel preference for the authenticated member. ## PUT /v1/organizations/{organizationId}/notification-preferences ```json { "operationId": "putNotificationPreference", "summary": "Upsert a notification preference.", "description": "Create or update a channel preference for the authenticated member.", "tags": [ "Notifications" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/notificationPreferencePutInputSchema" } } }, "description": "Channel, type, and enabled state. Omit organizationId to use the path organization." }, "responses": { "200": { "description": "Returns the saved preference.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/notificationPreferenceSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "notificationChannelSchema": { "type": "string", "enum": [ "in_app", "email", "push" ] }, "notificationDateTimeSchema": { "type": "string", "format": "date-time" }, "notificationPreferencePutInputSchema": { "$ref": "#/components/schemas/upsertNotificationPreferenceInputSchema" }, "notificationPreferenceSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "userId": { "type": "string", "format": "uuid" }, "organizationId": { "type": [ "string", "null" ], "format": "uuid" }, "type": { "anyOf": [ { "$ref": "#/components/schemas/notificationTypeSchema" }, { "type": "string", "enum": [ "*" ] } ] }, "channel": { "$ref": "#/components/schemas/notificationChannelSchema" }, "enabled": { "type": "boolean" }, "createdAt": { "$ref": "#/components/schemas/notificationDateTimeSchema" } }, "required": [ "id", "userId", "organizationId", "type", "channel", "enabled", "createdAt" ] }, "notificationTypeSchema": { "type": "string", "enum": [ "task.assigned", "task.comment", "task.updated", "collaboration.mention", "collaboration.note", "task.due-soon", "task.overdue", "agenda.assigned", "agenda.updated", "agent.approval-required", "agent.run-failed", "agent.run-completed", "workflow.approval-required", "workflow.run-failed", "workflow.run-completed", "workflow.notification", "inbox.message", "quote.sent", "quote.viewed", "quote.accepted", "quote.rejected", "quote.changes-requested", "quote.expired", "quote.deposit-paid", "invoice.paid", "invoice.overdue", "invoice.viewed", "invoice.partially-paid", "invoice.payment-failed", "invoice.disputed", "invoice.final-notice-sent", "order.backordered", "order.confirmed", "shipment.exception", "purchase-order.received", "purchase-order.overdue", "inventory.low-stock", "task.stage-changed", "billing.usage-threshold", "organization.invite", "organization.membership-updated", "time-off.requested", "time-off.decided", "time-off.cancelled", "sick-leave.reported", "sick-leave.returned", "sick-leave.follow-up", "timesheet.submitted", "timesheet.decided", "expense.submitted", "expense.decided", "expense.reimbursed", "employee.document-expiring", "employee.contract-ending", "equipment.assigned", "onboarding.item-assigned", "hiring.application-received", "time.timer-forgotten", "time.budget-warning", "approval.requested", "approval.decided", "approval.escalated" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "upsertNotificationPreferenceInputSchema": { "type": "object", "properties": { "organizationId": { "type": [ "string", "null" ], "format": "uuid" }, "type": { "anyOf": [ { "$ref": "#/components/schemas/notificationTypeSchema" }, { "type": "string", "enum": [ "*" ] } ] }, "channel": { "$ref": "#/components/schemas/notificationChannelSchema" }, "enabled": { "type": "boolean" } }, "required": [ "type", "channel", "enabled" ] } } ``` # List notification subscriptions. (/openapi/listNotificationSubscriptions) Return notification levels for the authenticated member. ## GET /v1/organizations/{organizationId}/notification-subscriptions ```json { "operationId": "listNotificationSubscriptions", "summary": "List notification subscriptions.", "description": "Return notification levels for the authenticated member.", "tags": [ "Notifications" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns notification subscriptions.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/notificationSubscriptionsListSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "notificationDateTimeSchema": { "type": "string", "format": "date-time" }, "notificationSubscriptionLevelSchema": { "type": "string", "enum": [ "participating", "all", "ignore" ] }, "notificationSubscriptionSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "organizationId": { "type": "string", "format": "uuid" }, "userId": { "type": "string", "format": "uuid" }, "subjectType": { "$ref": "#/components/schemas/notificationSubscriptionSubjectTypeSchema" }, "subjectId": { "type": "string", "minLength": 1 }, "level": { "$ref": "#/components/schemas/notificationSubscriptionLevelSchema" }, "createdAt": { "$ref": "#/components/schemas/notificationDateTimeSchema" } }, "required": [ "id", "organizationId", "userId", "subjectType", "subjectId", "level", "createdAt" ] }, "notificationSubscriptionSubjectTypeSchema": { "type": "string", "enum": [ "task", "chat-thread", "inbox-thread", "workflow-run", "agenda-item", "customer", "quote", "invoice", "workflow", "hiring-application", "order" ] }, "notificationSubscriptionsListSchema": { "type": "array", "items": { "$ref": "#/components/schemas/notificationSubscriptionSchema" } }, "organizationIdSchema": { "type": "string", "format": "uuid" } } ``` # Create or update a notification subscription. (/openapi/createNotificationSubscription) Set the notification level for a subject for the authenticated member. ## POST /v1/organizations/{organizationId}/notification-subscriptions ```json { "operationId": "createNotificationSubscription", "summary": "Create or update a notification subscription.", "description": "Set the notification level for a subject for the authenticated member.", "tags": [ "Notifications" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/notificationSubscriptionCreateInputSchema" } } }, "description": "Subject type, subject id, and notification level." }, "responses": { "200": { "description": "Returns the saved subscription.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/notificationSubscriptionSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "notificationDateTimeSchema": { "type": "string", "format": "date-time" }, "notificationSubscriptionCreateInputSchema": { "type": "object", "properties": { "subjectType": { "$ref": "#/components/schemas/notificationSubscriptionSubjectTypeSchema" }, "subjectId": { "type": "string", "minLength": 1, "maxLength": 240 }, "level": { "$ref": "#/components/schemas/notificationSubscriptionLevelSchema" } }, "required": [ "subjectType", "subjectId", "level" ] }, "notificationSubscriptionLevelSchema": { "type": "string", "enum": [ "participating", "all", "ignore" ] }, "notificationSubscriptionSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "organizationId": { "type": "string", "format": "uuid" }, "userId": { "type": "string", "format": "uuid" }, "subjectType": { "$ref": "#/components/schemas/notificationSubscriptionSubjectTypeSchema" }, "subjectId": { "type": "string", "minLength": 1 }, "level": { "$ref": "#/components/schemas/notificationSubscriptionLevelSchema" }, "createdAt": { "$ref": "#/components/schemas/notificationDateTimeSchema" } }, "required": [ "id", "organizationId", "userId", "subjectType", "subjectId", "level", "createdAt" ] }, "notificationSubscriptionSubjectTypeSchema": { "type": "string", "enum": [ "task", "chat-thread", "inbox-thread", "workflow-run", "agenda-item", "customer", "quote", "invoice", "workflow", "hiring-application", "order" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" } } ``` # List notifications. (/openapi/listNotifications) Return paginated in-app notifications for the authenticated member of an organization. ## GET /v1/organizations/{organizationId}/notifications ```json { "operationId": "listNotifications", "summary": "List notifications.", "description": "Return paginated in-app notifications for the authenticated member of an organization.", "tags": [ "Notifications" ], "parameters": [ { "in": "query", "name": "offset", "required": true, "schema": { "type": "string" }, "example": "example" }, { "in": "query", "name": "limit", "required": true, "schema": { "type": "string" }, "example": "example" }, { "in": "query", "name": "q", "required": false, "schema": { "type": "string", "maxLength": 200 }, "example": "example" }, { "in": "query", "name": "status", "required": true, "schema": { "type": "string", "enum": [ "all", "unread", "read" ], "default": "all" }, "example": "all" }, { "in": "query", "name": "type", "required": false, "schema": { "allOf": [ { "$ref": "#/components/schemas/notificationTypeSchema" } ] }, "example": "example" }, { "in": "query", "name": "subjectType", "required": false, "schema": { "allOf": [ { "$ref": "#/components/schemas/notificationSubjectTypeSchema" } ] }, "example": "example" }, { "in": "query", "name": "group", "required": true, "schema": { "type": "string", "enum": [ "all", "needs-action", "running", "happened" ], "default": "all" }, "example": "all" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns paginated notifications.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/notificationsListResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "jsonValueSchema": { "anyOf": [ { "type": "string" }, { "type": "number" }, { "type": "boolean" }, { "type": "null" }, { "type": "array", "items": { "$ref": "#/components/schemas/jsonValueSchema" } }, { "type": "object", "additionalProperties": { "$ref": "#/components/schemas/jsonValueSchema" }, "propertyNames": { "type": "string" } } ] }, "listPaginationSchema": { "type": "object", "properties": { "offset": { "type": "integer", "minimum": 0 }, "limit": { "type": "integer", "minimum": 1, "maximum": 100 }, "total": { "type": "integer", "minimum": 0 }, "hasMore": { "type": "boolean" }, "totalIsEstimate": { "type": "boolean" } }, "required": [ "offset", "limit", "total", "hasMore" ] }, "notificationActorSchema": { "type": "object", "properties": { "userId": { "type": "string", "format": "uuid" }, "displayName": { "type": "string", "minLength": 1 }, "avatarUrl": { "type": [ "string", "null" ] } }, "required": [ "userId", "displayName", "avatarUrl" ] }, "notificationDateTimeSchema": { "type": "string", "format": "date-time" }, "notificationMetadataSchema": { "type": "object", "additionalProperties": { "$ref": "#/components/schemas/jsonValueSchema" }, "propertyNames": { "type": "string" } }, "notificationOriginatorKindSchema": { "type": "string", "enum": [ "user", "customer", "system" ] }, "notificationOriginatorSchema": { "type": "object", "properties": { "kind": { "$ref": "#/components/schemas/notificationOriginatorKindSchema" }, "displayName": { "type": "string", "minLength": 1 }, "avatarUrl": { "type": [ "string", "null" ] } }, "required": [ "kind", "displayName", "avatarUrl" ] }, "notificationPrioritySchema": { "type": "string", "enum": [ "low", "normal", "high", "urgent" ] }, "notificationSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "eventId": { "type": "string", "format": "uuid" }, "organizationId": { "type": "string", "format": "uuid" }, "recipientUserId": { "type": "string", "format": "uuid" }, "type": { "$ref": "#/components/schemas/notificationTypeSchema" }, "priority": { "$ref": "#/components/schemas/notificationPrioritySchema" }, "actorUserId": { "type": [ "string", "null" ], "format": "uuid" }, "actor": { "anyOf": [ { "$ref": "#/components/schemas/notificationActorSchema" }, { "type": "null" } ] }, "originator": { "$ref": "#/components/schemas/notificationOriginatorSchema" }, "subjectType": { "$ref": "#/components/schemas/notificationSubjectTypeSchema" }, "subjectId": { "type": "string", "minLength": 1 }, "subjectLabel": { "type": "string", "minLength": 1 }, "summary": { "type": [ "string", "null" ] }, "actionPath": { "type": [ "string", "null" ], "minLength": 1 }, "metadata": { "$ref": "#/components/schemas/notificationMetadataSchema" }, "readAt": { "anyOf": [ { "$ref": "#/components/schemas/notificationDateTimeSchema" }, { "type": "null" } ] }, "dismissedAt": { "anyOf": [ { "$ref": "#/components/schemas/notificationDateTimeSchema" }, { "type": "null" } ] }, "resolvedAt": { "anyOf": [ { "$ref": "#/components/schemas/notificationDateTimeSchema" }, { "type": "null" } ] }, "deliveredAt": { "$ref": "#/components/schemas/notificationDateTimeSchema" }, "createdAt": { "$ref": "#/components/schemas/notificationDateTimeSchema" } }, "required": [ "id", "eventId", "organizationId", "recipientUserId", "type", "priority", "actorUserId", "actor", "originator", "subjectType", "subjectId", "subjectLabel", "summary", "actionPath", "metadata", "readAt", "dismissedAt", "resolvedAt", "deliveredAt", "createdAt" ] }, "notificationSubjectTypeSchema": { "type": "string", "enum": [ "task", "customer", "agenda-item", "agent-run", "billing", "organization", "workflow-run", "inbox-thread", "quote", "invoice", "chat-thread", "artifact", "employee", "time-off-request", "timesheet", "expense", "hiring-application", "employee-journey", "approval", "order", "shipment", "purchase-order", "product" ] }, "notificationTypeSchema": { "type": "string", "enum": [ "task.assigned", "task.comment", "task.updated", "collaboration.mention", "collaboration.note", "task.due-soon", "task.overdue", "agenda.assigned", "agenda.updated", "agent.approval-required", "agent.run-failed", "agent.run-completed", "workflow.approval-required", "workflow.run-failed", "workflow.run-completed", "workflow.notification", "inbox.message", "quote.sent", "quote.viewed", "quote.accepted", "quote.rejected", "quote.changes-requested", "quote.expired", "quote.deposit-paid", "invoice.paid", "invoice.overdue", "invoice.viewed", "invoice.partially-paid", "invoice.payment-failed", "invoice.disputed", "invoice.final-notice-sent", "order.backordered", "order.confirmed", "shipment.exception", "purchase-order.received", "purchase-order.overdue", "inventory.low-stock", "task.stage-changed", "billing.usage-threshold", "organization.invite", "organization.membership-updated", "time-off.requested", "time-off.decided", "time-off.cancelled", "sick-leave.reported", "sick-leave.returned", "sick-leave.follow-up", "timesheet.submitted", "timesheet.decided", "expense.submitted", "expense.decided", "expense.reimbursed", "employee.document-expiring", "employee.contract-ending", "equipment.assigned", "onboarding.item-assigned", "hiring.application-received", "time.timer-forgotten", "time.budget-warning", "approval.requested", "approval.decided", "approval.escalated" ] }, "notificationsListResponseSchema": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/notificationSchema" } }, "pagination": { "$ref": "#/components/schemas/listPaginationSchema" } }, "required": [ "data", "pagination" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" } } ``` # Delete a notification subscription. (/openapi/deleteNotificationSubscription) Stop watching a subject for the authenticated member. ## DELETE /v1/organizations/{organizationId}/notification-subscriptions/{subscriptionId} ```json { "operationId": "deleteNotificationSubscription", "summary": "Delete a notification subscription.", "description": "Stop watching a subject for the authenticated member.", "tags": [ "Notifications" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "subscriptionId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Confirms the subscription was deleted.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/publicApiSuccessResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "organizationIdSchema": { "type": "string", "format": "uuid" }, "publicApiSuccessResponseSchema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [ true ] } }, "required": [ "success" ] } } ``` # Update a notification. (/openapi/patchNotification) Mark a notification read, unread, or dismissed for the authenticated member. ## PATCH /v1/organizations/{organizationId}/notifications/{notificationId} ```json { "operationId": "patchNotification", "summary": "Update a notification.", "description": "Mark a notification read, unread, or dismissed for the authenticated member.", "tags": [ "Notifications" ], "parameters": [ { "in": "path", "name": "notificationId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/notificationPatchInputSchema" } } }, "description": "Read, unread, or dismiss the notification." }, "responses": { "200": { "description": "Returns the updated notification.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/notificationSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "jsonValueSchema": { "anyOf": [ { "type": "string" }, { "type": "number" }, { "type": "boolean" }, { "type": "null" }, { "type": "array", "items": { "$ref": "#/components/schemas/jsonValueSchema" } }, { "type": "object", "additionalProperties": { "$ref": "#/components/schemas/jsonValueSchema" }, "propertyNames": { "type": "string" } } ] }, "notificationActorSchema": { "type": "object", "properties": { "userId": { "type": "string", "format": "uuid" }, "displayName": { "type": "string", "minLength": 1 }, "avatarUrl": { "type": [ "string", "null" ] } }, "required": [ "userId", "displayName", "avatarUrl" ] }, "notificationDateTimeSchema": { "type": "string", "format": "date-time" }, "notificationMetadataSchema": { "type": "object", "additionalProperties": { "$ref": "#/components/schemas/jsonValueSchema" }, "propertyNames": { "type": "string" } }, "notificationOriginatorKindSchema": { "type": "string", "enum": [ "user", "customer", "system" ] }, "notificationOriginatorSchema": { "type": "object", "properties": { "kind": { "$ref": "#/components/schemas/notificationOriginatorKindSchema" }, "displayName": { "type": "string", "minLength": 1 }, "avatarUrl": { "type": [ "string", "null" ] } }, "required": [ "kind", "displayName", "avatarUrl" ] }, "notificationPatchInputSchema": { "type": "object", "properties": { "dismissed": { "type": "boolean" }, "read": { "type": "boolean" } } }, "notificationPrioritySchema": { "type": "string", "enum": [ "low", "normal", "high", "urgent" ] }, "notificationSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "eventId": { "type": "string", "format": "uuid" }, "organizationId": { "type": "string", "format": "uuid" }, "recipientUserId": { "type": "string", "format": "uuid" }, "type": { "$ref": "#/components/schemas/notificationTypeSchema" }, "priority": { "$ref": "#/components/schemas/notificationPrioritySchema" }, "actorUserId": { "type": [ "string", "null" ], "format": "uuid" }, "actor": { "anyOf": [ { "$ref": "#/components/schemas/notificationActorSchema" }, { "type": "null" } ] }, "originator": { "$ref": "#/components/schemas/notificationOriginatorSchema" }, "subjectType": { "$ref": "#/components/schemas/notificationSubjectTypeSchema" }, "subjectId": { "type": "string", "minLength": 1 }, "subjectLabel": { "type": "string", "minLength": 1 }, "summary": { "type": [ "string", "null" ] }, "actionPath": { "type": [ "string", "null" ], "minLength": 1 }, "metadata": { "$ref": "#/components/schemas/notificationMetadataSchema" }, "readAt": { "anyOf": [ { "$ref": "#/components/schemas/notificationDateTimeSchema" }, { "type": "null" } ] }, "dismissedAt": { "anyOf": [ { "$ref": "#/components/schemas/notificationDateTimeSchema" }, { "type": "null" } ] }, "resolvedAt": { "anyOf": [ { "$ref": "#/components/schemas/notificationDateTimeSchema" }, { "type": "null" } ] }, "deliveredAt": { "$ref": "#/components/schemas/notificationDateTimeSchema" }, "createdAt": { "$ref": "#/components/schemas/notificationDateTimeSchema" } }, "required": [ "id", "eventId", "organizationId", "recipientUserId", "type", "priority", "actorUserId", "actor", "originator", "subjectType", "subjectId", "subjectLabel", "summary", "actionPath", "metadata", "readAt", "dismissedAt", "resolvedAt", "deliveredAt", "createdAt" ] }, "notificationSubjectTypeSchema": { "type": "string", "enum": [ "task", "customer", "agenda-item", "agent-run", "billing", "organization", "workflow-run", "inbox-thread", "quote", "invoice", "chat-thread", "artifact", "employee", "time-off-request", "timesheet", "expense", "hiring-application", "employee-journey", "approval", "order", "shipment", "purchase-order", "product" ] }, "notificationTypeSchema": { "type": "string", "enum": [ "task.assigned", "task.comment", "task.updated", "collaboration.mention", "collaboration.note", "task.due-soon", "task.overdue", "agenda.assigned", "agenda.updated", "agent.approval-required", "agent.run-failed", "agent.run-completed", "workflow.approval-required", "workflow.run-failed", "workflow.run-completed", "workflow.notification", "inbox.message", "quote.sent", "quote.viewed", "quote.accepted", "quote.rejected", "quote.changes-requested", "quote.expired", "quote.deposit-paid", "invoice.paid", "invoice.overdue", "invoice.viewed", "invoice.partially-paid", "invoice.payment-failed", "invoice.disputed", "invoice.final-notice-sent", "order.backordered", "order.confirmed", "shipment.exception", "purchase-order.received", "purchase-order.overdue", "inventory.low-stock", "task.stage-changed", "billing.usage-threshold", "organization.invite", "organization.membership-updated", "time-off.requested", "time-off.decided", "time-off.cancelled", "sick-leave.reported", "sick-leave.returned", "sick-leave.follow-up", "timesheet.submitted", "timesheet.decided", "expense.submitted", "expense.decided", "expense.reimbursed", "employee.document-expiring", "employee.contract-ending", "equipment.assigned", "onboarding.item-assigned", "hiring.application-received", "time.timer-forgotten", "time.budget-warning", "approval.requested", "approval.decided", "approval.escalated" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" } } ``` # Get the composed notification inbox. (/openapi/getNotificationInbox) Return the mixed attention inbox for the authenticated member, including live running work and pending invitations. ## GET /v1/organizations/{organizationId}/notifications/inbox ```json { "operationId": "getNotificationInbox", "summary": "Get the composed notification inbox.", "description": "Return the mixed attention inbox for the authenticated member, including live running work and pending invitations.", "tags": [ "Notifications" ], "parameters": [ { "in": "query", "name": "limit", "required": true, "schema": { "type": "integer", "minimum": 1, "maximum": 20, "default": 10 }, "example": 1 }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns the composed notification inbox.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/notificationInboxSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "jsonValueSchema": { "anyOf": [ { "type": "string" }, { "type": "number" }, { "type": "boolean" }, { "type": "null" }, { "type": "array", "items": { "$ref": "#/components/schemas/jsonValueSchema" } }, { "type": "object", "additionalProperties": { "$ref": "#/components/schemas/jsonValueSchema" }, "propertyNames": { "type": "string" } } ] }, "notificationActorSchema": { "type": "object", "properties": { "userId": { "type": "string", "format": "uuid" }, "displayName": { "type": "string", "minLength": 1 }, "avatarUrl": { "type": [ "string", "null" ] } }, "required": [ "userId", "displayName", "avatarUrl" ] }, "notificationApprovalPreviewSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "title": { "type": "string", "minLength": 1 }, "summary": { "type": [ "string", "null" ] }, "actionPath": { "type": [ "string", "null" ] }, "requesterName": { "type": [ "string", "null" ] }, "requestedAt": { "$ref": "#/components/schemas/notificationDateTimeSchema" }, "escalated": { "type": "boolean" }, "agentRunId": { "type": [ "string", "null" ], "format": "uuid" }, "workflowRunId": { "type": [ "string", "null" ], "format": "uuid" } }, "required": [ "id", "title", "summary", "actionPath", "requesterName", "requestedAt", "escalated", "agentRunId", "workflowRunId" ] }, "notificationDateTimeSchema": { "type": "string", "format": "date-time" }, "notificationInboxItemSchema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1 }, "source": { "$ref": "#/components/schemas/notificationInboxSourceSchema" }, "group": { "type": "string", "enum": [ "needs-action", "running", "happened" ] }, "type": { "anyOf": [ { "$ref": "#/components/schemas/notificationTypeSchema" }, { "type": "string", "enum": [ "activity.running", "activity.artifact", "organization.invite" ] } ] }, "createdAt": { "$ref": "#/components/schemas/notificationDateTimeSchema" }, "subjectLabel": { "type": "string", "minLength": 1 }, "summary": { "type": [ "string", "null" ] }, "actionPath": { "type": [ "string", "null" ] }, "originator": { "$ref": "#/components/schemas/notificationOriginatorSchema" }, "readAt": { "anyOf": [ { "$ref": "#/components/schemas/notificationDateTimeSchema" }, { "type": "null" } ] }, "resolvedAt": { "anyOf": [ { "$ref": "#/components/schemas/notificationDateTimeSchema" }, { "type": "null" } ] }, "notification": { "anyOf": [ { "$ref": "#/components/schemas/notificationSchema" }, { "type": "null" } ] }, "activity": { "anyOf": [ { "$ref": "#/components/schemas/notificationLiveActivitySchema" }, { "type": "null" } ] }, "invite": { "anyOf": [ { "$ref": "#/components/schemas/notificationInvitePreviewSchema" }, { "type": "null" } ] }, "approval": { "anyOf": [ { "$ref": "#/components/schemas/notificationApprovalPreviewSchema" }, { "type": "null" } ] } }, "required": [ "id", "source", "group", "type", "createdAt", "subjectLabel", "summary", "actionPath", "originator", "readAt", "resolvedAt", "notification", "activity", "invite", "approval" ] }, "notificationInboxSchema": { "type": "object", "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/notificationInboxItemSchema" } }, "unreadCount": { "type": "integer", "minimum": 0 }, "pendingActionCount": { "type": "integer", "minimum": 0 } }, "required": [ "items", "unreadCount", "pendingActionCount" ] }, "notificationInboxSourceSchema": { "type": "string", "enum": [ "notification", "activity", "invite", "approval" ] }, "notificationInvitePreviewSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "organizationName": { "type": "string", "minLength": 1 }, "token": { "type": "string", "minLength": 1 }, "createdAt": { "type": "string", "minLength": 1 } }, "required": [ "id", "organizationName", "token", "createdAt" ] }, "notificationLiveActivityKindSchema": { "type": "string", "enum": [ "agent-run", "artifact", "workflow-run" ] }, "notificationLiveActivitySchema": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1 }, "kind": { "$ref": "#/components/schemas/notificationLiveActivityKindSchema" }, "group": { "type": "string", "enum": [ "running", "needs-review", "completed", "failed" ] }, "title": { "type": "string", "minLength": 1 }, "summary": { "type": [ "string", "null" ] }, "status": { "type": "string", "minLength": 1 }, "createdAt": { "$ref": "#/components/schemas/notificationDateTimeSchema" }, "updatedAt": { "$ref": "#/components/schemas/notificationDateTimeSchema" }, "actionPath": { "type": [ "string", "null" ] }, "threadId": { "type": [ "string", "null" ] }, "workflowDefinitionId": { "type": [ "string", "null" ] }, "workflowRunId": { "type": [ "string", "null" ] }, "pendingApprovalCount": { "type": "integer", "minimum": 0 }, "pendingApprovalId": { "type": [ "string", "null" ], "minLength": 1 }, "pendingApprovalToolCallId": { "type": [ "string", "null" ], "minLength": 1 }, "artifactContextFingerprint": { "type": [ "string", "null" ], "minLength": 1 }, "activeStreamId": { "type": [ "string", "null" ], "minLength": 1 }, "originator": { "$ref": "#/components/schemas/notificationOriginatorSchema" } }, "required": [ "id", "kind", "group", "title", "summary", "status", "createdAt", "updatedAt", "actionPath", "threadId", "workflowDefinitionId", "workflowRunId", "pendingApprovalCount", "pendingApprovalId", "pendingApprovalToolCallId", "artifactContextFingerprint", "activeStreamId", "originator" ] }, "notificationMetadataSchema": { "type": "object", "additionalProperties": { "$ref": "#/components/schemas/jsonValueSchema" }, "propertyNames": { "type": "string" } }, "notificationOriginatorKindSchema": { "type": "string", "enum": [ "user", "customer", "system" ] }, "notificationOriginatorSchema": { "type": "object", "properties": { "kind": { "$ref": "#/components/schemas/notificationOriginatorKindSchema" }, "displayName": { "type": "string", "minLength": 1 }, "avatarUrl": { "type": [ "string", "null" ] } }, "required": [ "kind", "displayName", "avatarUrl" ] }, "notificationPrioritySchema": { "type": "string", "enum": [ "low", "normal", "high", "urgent" ] }, "notificationSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "eventId": { "type": "string", "format": "uuid" }, "organizationId": { "type": "string", "format": "uuid" }, "recipientUserId": { "type": "string", "format": "uuid" }, "type": { "$ref": "#/components/schemas/notificationTypeSchema" }, "priority": { "$ref": "#/components/schemas/notificationPrioritySchema" }, "actorUserId": { "type": [ "string", "null" ], "format": "uuid" }, "actor": { "anyOf": [ { "$ref": "#/components/schemas/notificationActorSchema" }, { "type": "null" } ] }, "originator": { "$ref": "#/components/schemas/notificationOriginatorSchema" }, "subjectType": { "$ref": "#/components/schemas/notificationSubjectTypeSchema" }, "subjectId": { "type": "string", "minLength": 1 }, "subjectLabel": { "type": "string", "minLength": 1 }, "summary": { "type": [ "string", "null" ] }, "actionPath": { "type": [ "string", "null" ], "minLength": 1 }, "metadata": { "$ref": "#/components/schemas/notificationMetadataSchema" }, "readAt": { "anyOf": [ { "$ref": "#/components/schemas/notificationDateTimeSchema" }, { "type": "null" } ] }, "dismissedAt": { "anyOf": [ { "$ref": "#/components/schemas/notificationDateTimeSchema" }, { "type": "null" } ] }, "resolvedAt": { "anyOf": [ { "$ref": "#/components/schemas/notificationDateTimeSchema" }, { "type": "null" } ] }, "deliveredAt": { "$ref": "#/components/schemas/notificationDateTimeSchema" }, "createdAt": { "$ref": "#/components/schemas/notificationDateTimeSchema" } }, "required": [ "id", "eventId", "organizationId", "recipientUserId", "type", "priority", "actorUserId", "actor", "originator", "subjectType", "subjectId", "subjectLabel", "summary", "actionPath", "metadata", "readAt", "dismissedAt", "resolvedAt", "deliveredAt", "createdAt" ] }, "notificationSubjectTypeSchema": { "type": "string", "enum": [ "task", "customer", "agenda-item", "agent-run", "billing", "organization", "workflow-run", "inbox-thread", "quote", "invoice", "chat-thread", "artifact", "employee", "time-off-request", "timesheet", "expense", "hiring-application", "employee-journey", "approval", "order", "shipment", "purchase-order", "product" ] }, "notificationTypeSchema": { "type": "string", "enum": [ "task.assigned", "task.comment", "task.updated", "collaboration.mention", "collaboration.note", "task.due-soon", "task.overdue", "agenda.assigned", "agenda.updated", "agent.approval-required", "agent.run-failed", "agent.run-completed", "workflow.approval-required", "workflow.run-failed", "workflow.run-completed", "workflow.notification", "inbox.message", "quote.sent", "quote.viewed", "quote.accepted", "quote.rejected", "quote.changes-requested", "quote.expired", "quote.deposit-paid", "invoice.paid", "invoice.overdue", "invoice.viewed", "invoice.partially-paid", "invoice.payment-failed", "invoice.disputed", "invoice.final-notice-sent", "order.backordered", "order.confirmed", "shipment.exception", "purchase-order.received", "purchase-order.overdue", "inventory.low-stock", "task.stage-changed", "billing.usage-threshold", "organization.invite", "organization.membership-updated", "time-off.requested", "time-off.decided", "time-off.cancelled", "sick-leave.reported", "sick-leave.returned", "sick-leave.follow-up", "timesheet.submitted", "timesheet.decided", "expense.submitted", "expense.decided", "expense.reimbursed", "employee.document-expiring", "employee.contract-ending", "equipment.assigned", "onboarding.item-assigned", "hiring.application-received", "time.timer-forgotten", "time.budget-warning", "approval.requested", "approval.decided", "approval.escalated" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" } } ``` # Mark all notifications read. (/openapi/markAllNotificationsRead) Mark every unread notification in the current organization as read for the authenticated member. ## POST /v1/organizations/{organizationId}/notifications/mark-all-read ```json { "operationId": "markAllNotificationsRead", "summary": "Mark all notifications read.", "description": "Mark every unread notification in the current organization as read for the authenticated member.", "tags": [ "Notifications" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns how many notifications were marked read.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/notificationMarkAllReadResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "notificationMarkAllReadResponseSchema": { "type": "object", "properties": { "count": { "type": "integer", "minimum": 0 } }, "required": [ "count" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" } } ``` # Read a new-hire pre-boarding page. (/openapi/getPublicPreboardingPage) Returns the organization, start date, and requested documents for a pre-boarding token link. Expired, revoked, and already-linked links return 404. ## GET /v1/public/onboarding/{token} ```json { "operationId": "getPublicPreboardingPage", "summary": "Read a new-hire pre-boarding page.", "description": "Returns the organization, start date, and requested documents for a pre-boarding token link. Expired, revoked, and already-linked links return 404.", "tags": [ "Onboarding" ], "parameters": [ { "in": "path", "name": "token", "required": true, "schema": { "type": "string", "minLength": 16, "maxLength": 200 }, "example": "example" } ], "responses": { "200": { "description": "Returns the pre-boarding page for the new hire.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/publicPreboardingPageResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "preboardingPageSchema": { "type": "object", "properties": { "organizationName": { "type": "string" }, "firstName": { "type": "string" }, "startDate": { "type": "object" }, "expiresAt": { "type": [ "string", "null" ] }, "requestedDocuments": { "type": "array", "items": { "type": "object", "properties": { "itemId": { "type": "string", "format": "uuid" }, "title": { "type": "string" }, "done": { "type": "boolean" } }, "required": [ "itemId", "title", "done" ] } }, "detailsSubmitted": { "type": "boolean" } }, "required": [ "organizationName", "firstName", "startDate", "expiresAt", "requestedDocuments", "detailsSubmitted" ] }, "publicPreboardingPageResponseSchema": { "$ref": "#/components/schemas/preboardingPageSchema" } } ``` # Submit new-hire private details. (/openapi/submitPublicPreboardingDetails) Saves the new hire's private details (birth date, contact, bank account, emergency contact, address) through a pre-boarding token link and completes the pre-boarding form step. ## POST /v1/public/onboarding/{token} ```json { "operationId": "submitPublicPreboardingDetails", "summary": "Submit new-hire private details.", "description": "Saves the new hire's private details (birth date, contact, bank account, emergency contact, address) through a pre-boarding token link and completes the pre-boarding form step.", "tags": [ "Onboarding" ], "parameters": [ { "in": "path", "name": "token", "required": true, "schema": { "type": "string", "minLength": 16, "maxLength": 200 }, "example": "example" } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/publicPreboardingDetailsBodySchema" } } }, "description": "Private details entered by the new hire." }, "responses": { "200": { "description": "The details were saved.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/publicPreboardingOkResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "employeePrivateDetailsInputSchema": { "type": "object", "properties": { "birthDate": { "type": "object" }, "personalEmail": { "anyOf": [ { "type": [ "string", "null" ], "format": "email", "maxLength": 320 }, { "type": "string", "enum": [ "" ] } ] }, "phone": { "type": [ "string", "null" ], "maxLength": 40 }, "iban": { "anyOf": [ { "type": [ "string", "null" ], "maxLength": 40 }, { "type": "string", "enum": [ "" ] } ] }, "emergencyContactName": { "type": [ "string", "null" ], "maxLength": 160 }, "emergencyContactPhone": { "type": [ "string", "null" ], "maxLength": 40 }, "emergencyContactRelation": { "type": [ "string", "null" ], "maxLength": 80 }, "homeAddressLine1": { "type": [ "string", "null" ], "maxLength": 200 }, "homePostalCode": { "type": [ "string", "null" ], "maxLength": 20 }, "homeCity": { "type": [ "string", "null" ], "maxLength": 120 }, "homeCountry": { "type": [ "string", "null" ], "maxLength": 2 } }, "required": [ "phone", "emergencyContactName", "emergencyContactPhone", "emergencyContactRelation", "homeAddressLine1", "homePostalCode", "homeCity", "homeCountry" ] }, "preboardingSubmitInputSchema": { "$ref": "#/components/schemas/employeePrivateDetailsInputSchema" }, "publicPreboardingDetailsBodySchema": { "$ref": "#/components/schemas/preboardingSubmitInputSchema" }, "publicPreboardingOkResponseSchema": { "type": "object", "properties": { "ok": { "type": "boolean", "enum": [ true ] } }, "required": [ "ok" ] } } ``` # Upload a requested pre-boarding document. (/openapi/uploadPublicPreboardingDocument) Uploads a document the new hire was asked for (multipart form with `itemId` and `file`, at most 20 MB) through a pre-boarding token link and completes that checklist step. ## POST /v1/public/onboarding/{token}/documents ```json { "operationId": "uploadPublicPreboardingDocument", "summary": "Upload a requested pre-boarding document.", "description": "Uploads a document the new hire was asked for (multipart form with `itemId` and `file`, at most 20 MB) through a pre-boarding token link and completes that checklist step.", "tags": [ "Onboarding" ], "parameters": [ { "in": "path", "name": "token", "required": true, "schema": { "type": "string", "minLength": 16, "maxLength": 200 }, "example": "example" } ], "requestBody": { "content": { "multipart/form-data": { "schema": { "$ref": "#/components/schemas/publicPreboardingDocumentBodySchema" }, "encoding": { "file": { "contentType": "application/octet-stream" } } } }, "description": "The checklist item id and the file." }, "responses": { "200": { "description": "The document was stored on the employee record.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/publicPreboardingDocumentResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "publicPreboardingDocumentBodySchema": { "type": "object", "properties": { "itemId": { "type": "string", "format": "uuid" }, "file": { "type": "string", "contentMediaType": "application/octet-stream" } }, "required": [ "itemId", "file" ] }, "publicPreboardingDocumentResponseSchema": { "type": "object", "properties": { "ok": { "type": "boolean", "enum": [ true ] } }, "required": [ "ok" ] } } ``` # List sales orders. (/openapi/listOrders) Return sales orders, newest first. Search matches the order number, external id, and buyer name. ## GET /v1/organizations/{organizationId}/orders ```json { "operationId": "listOrders", "summary": "List sales orders.", "description": "Return sales orders, newest first. Search matches the order number, external id, and buyer name.", "tags": [ "Orders" ], "parameters": [ { "in": "query", "name": "q", "required": false, "schema": { "type": "string", "maxLength": 200 }, "example": "example" }, { "in": "query", "name": "status", "required": false, "schema": { "allOf": [ { "$ref": "#/components/schemas/orderStatusSchema" } ] }, "example": "example" }, { "in": "query", "name": "customerId", "required": false, "schema": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 }, "example": 123 }, { "in": "query", "name": "offset", "required": true, "schema": { "default": 0, "type": "integer", "minimum": 0, "maximum": 9007199254740991 }, "example": 1 }, { "in": "query", "name": "limit", "required": true, "schema": { "default": 50, "type": "integer", "minimum": 1, "maximum": 100 }, "example": 1 }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns sales orders.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ordersResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "isoDateSchema": { "type": "string", "format": "date" }, "listPaginationSchema": { "type": "object", "properties": { "offset": { "type": "integer", "minimum": 0 }, "limit": { "type": "integer", "minimum": 1, "maximum": 100 }, "total": { "type": "integer", "minimum": 0 }, "hasMore": { "type": "boolean" }, "totalIsEstimate": { "type": "boolean" } }, "required": [ "offset", "limit", "total", "hasMore" ] }, "orderChannelSchema": { "type": "string", "enum": [ "manual", "quote", "inbox", "api", "integration" ] }, "orderCustomerSchema": { "type": "object", "properties": { "id": { "type": "integer" }, "name": { "type": "string" } }, "required": [ "id", "name" ] }, "orderDeliveryMethodSchema": { "type": "string", "enum": [ "pickup", "own_delivery", "carrier", "digital", "none" ] }, "orderInvoicingPolicySchema": { "type": "string", "enum": [ "before_fulfillment", "on_fulfillment", "manual" ] }, "orderStatusSchema": { "type": "string", "enum": [ "draft", "confirmed", "on_hold", "completed", "cancelled" ] }, "orderSummarySchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "number": { "type": "integer" }, "formattedNumber": { "type": "string" }, "customer": { "$ref": "#/components/schemas/orderCustomerSchema" }, "status": { "$ref": "#/components/schemas/orderStatusSchema" }, "channel": { "$ref": "#/components/schemas/orderChannelSchema" }, "invoicingPolicy": { "$ref": "#/components/schemas/orderInvoicingPolicySchema" }, "deliveryMethod": { "$ref": "#/components/schemas/orderDeliveryMethodSchema" }, "currency": { "type": "string" }, "subtotalExcl": { "type": "number" }, "vatTotal": { "type": "number" }, "totalIncl": { "type": "number" }, "requestedDeliveryDate": { "anyOf": [ { "$ref": "#/components/schemas/isoDateSchema" }, { "type": "null" } ] }, "quoteId": { "type": [ "string", "null" ], "format": "uuid" }, "confirmedAt": { "type": [ "string", "null" ] }, "completedAt": { "type": [ "string", "null" ] }, "cancelledAt": { "type": [ "string", "null" ] }, "createdAt": { "type": "string" }, "updatedAt": { "type": "string" } }, "required": [ "id", "number", "formattedNumber", "customer", "status", "channel", "invoicingPolicy", "deliveryMethod", "currency", "subtotalExcl", "vatTotal", "totalIncl", "requestedDeliveryDate", "quoteId", "confirmedAt", "completedAt", "cancelledAt", "createdAt", "updatedAt" ] }, "ordersResponseSchema": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/orderSummarySchema" } }, "pagination": { "$ref": "#/components/schemas/listPaginationSchema" } }, "required": [ "data", "pagination" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" } } ``` # Take in a sales order. (/openapi/createOrder) Create an order from a webshop or other channel. The buyer is matched on email or created, lines are matched to variants by id, SKU, or EAN, and a promotion code is applied to the whole order. Posting the same `source` and `externalId` again returns the existing order with status 200 instead of creating a second one. Set `confirm` to reserve stock right away. ## POST /v1/organizations/{organizationId}/orders ```json { "operationId": "createOrder", "summary": "Take in a sales order.", "description": "Create an order from a webshop or other channel. The buyer is matched on email or created, lines are matched to variants by id, SKU, or EAN, and a promotion code is applied to the whole order. Posting the same `source` and `externalId` again returns the existing order with status 200 instead of creating a second one. Set `confirm` to reserve stock right away.", "tags": [ "Orders" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/orderIntakeInputSchema" } } }, "description": "The incoming order." }, "responses": { "201": { "description": "Returns the order and whether it was created.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/orderIntakeResultSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "currencySchema": { "type": "string", "pattern": "^[A-Z]{3}$" }, "isoDateSchema": { "type": "string", "format": "date" }, "orderAddressInputSchema": { "type": "object", "properties": { "name": { "type": [ "string", "null" ], "maxLength": 200 }, "line1": { "type": [ "string", "null" ], "maxLength": 200 }, "line2": { "type": [ "string", "null" ], "maxLength": 200 }, "postalCode": { "type": [ "string", "null" ], "maxLength": 20 }, "city": { "type": [ "string", "null" ], "maxLength": 120 }, "country": { "type": [ "string", "null" ], "pattern": "^[A-Z]{2}$" }, "phone": { "type": [ "string", "null" ], "maxLength": 40 } }, "required": [ "name", "line1", "line2", "postalCode", "city", "phone" ] }, "orderAddressSchema": { "type": "object", "properties": { "name": { "type": [ "string", "null" ] }, "line1": { "type": [ "string", "null" ] }, "line2": { "type": [ "string", "null" ] }, "postalCode": { "type": [ "string", "null" ] }, "city": { "type": [ "string", "null" ] }, "country": { "type": [ "string", "null" ] }, "phone": { "type": [ "string", "null" ] } }, "required": [ "name", "line1", "line2", "postalCode", "city", "country", "phone" ] }, "orderChannelSchema": { "type": "string", "enum": [ "manual", "quote", "inbox", "api", "integration" ] }, "orderCustomerSchema": { "type": "object", "properties": { "id": { "type": "integer" }, "name": { "type": "string" } }, "required": [ "id", "name" ] }, "orderDeliveryMethodSchema": { "type": "string", "enum": [ "pickup", "own_delivery", "carrier", "digital", "none" ] }, "orderDiscountKindSchema": { "type": "string", "enum": [ "none", "percentage", "fixed" ] }, "orderFulfillmentStatusSchema": { "type": "string", "enum": [ "unfulfilled", "partial", "fulfilled" ] }, "orderIntakeCustomerSchema": { "type": "object", "properties": { "name": { "type": "string", "minLength": 1, "maxLength": 240 }, "email": { "type": "string", "format": "email", "maxLength": 320 }, "phone": { "type": [ "string", "null" ], "maxLength": 40 }, "companyName": { "type": [ "string", "null" ], "maxLength": 240 } }, "required": [ "name", "email", "phone", "companyName" ] }, "orderIntakeInputSchema": { "type": "object", "properties": { "source": { "type": "string", "minLength": 1, "maxLength": 80 }, "externalId": { "type": "string", "minLength": 1, "maxLength": 200 }, "customerId": { "type": "integer", "exclusiveMinimum": 0 }, "customer": { "allOf": [ { "$ref": "#/components/schemas/orderIntakeCustomerSchema" } ] }, "deliveryMethod": { "allOf": [ { "$ref": "#/components/schemas/orderDeliveryMethodSchema" } ] }, "shippingAddress": { "allOf": [ { "$ref": "#/components/schemas/orderAddressInputSchema" } ] }, "currency": { "allOf": [ { "$ref": "#/components/schemas/currencySchema" } ] }, "vatInclusive": { "type": "boolean" }, "promotionCode": { "type": "string", "minLength": 1, "maxLength": 40 }, "requestedDeliveryDate": { "allOf": [ { "$ref": "#/components/schemas/isoDateSchema" } ] }, "customerNote": { "type": [ "string", "null" ], "maxLength": 4000 }, "notes": { "type": [ "string", "null" ], "maxLength": 4000 }, "confirm": { "type": "boolean", "default": false }, "lines": { "type": "array", "items": { "$ref": "#/components/schemas/orderIntakeLineSchema" }, "minItems": 1, "maxItems": 500 } }, "required": [ "source", "externalId", "customerNote", "notes", "confirm", "lines" ] }, "orderIntakeLineSchema": { "type": "object", "properties": { "variantId": { "type": "string", "format": "uuid" }, "sku": { "type": "string", "minLength": 1, "maxLength": 120 }, "ean": { "type": "string", "minLength": 1, "maxLength": 40 }, "title": { "type": [ "string", "null" ], "maxLength": 300 }, "quantity": { "type": "number", "exclusiveMinimum": 0, "maximum": 1000000 }, "unitPrice": { "type": "number", "minimum": 0, "maximum": 100000000 }, "vatRate": { "type": "number", "minimum": 0, "maximum": 100 } }, "required": [ "title", "quantity" ] }, "orderIntakeResultSchema": { "type": "object", "properties": { "created": { "type": "boolean" }, "order": { "$ref": "#/components/schemas/orderSchema" } }, "required": [ "created", "order" ] }, "orderInvoiceSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "displayNumber": { "type": [ "string", "null" ] }, "kind": { "type": "string" }, "status": { "type": "string" }, "totalIncl": { "type": "number" }, "amountPaidCents": { "type": "number" }, "amountDueCents": { "type": "number" }, "createdAt": { "type": "string" } }, "required": [ "id", "displayNumber", "kind", "status", "totalIncl", "amountPaidCents", "amountDueCents", "createdAt" ] }, "orderInvoicingPolicySchema": { "type": "string", "enum": [ "before_fulfillment", "on_fulfillment", "manual" ] }, "orderLineSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "lineType": { "$ref": "#/components/schemas/orderLineTypeSchema" }, "productId": { "type": [ "string", "null" ], "format": "uuid" }, "variantId": { "type": [ "string", "null" ], "format": "uuid" }, "productType": { "type": [ "string", "null" ] }, "sku": { "type": [ "string", "null" ] }, "trackInventory": { "type": "boolean" }, "title": { "type": "string" }, "description": { "type": [ "string", "null" ] }, "quantity": { "type": "number" }, "quantityCancelled": { "type": "number" }, "unit": { "type": [ "string", "null" ] }, "unitPrice": { "type": "number" }, "vatRate": { "type": "number" }, "discountKind": { "$ref": "#/components/schemas/orderDiscountKindSchema" }, "discountValue": { "type": "number" }, "promotionId": { "type": [ "string", "null" ], "format": "uuid" }, "position": { "type": "number" }, "allocated": { "type": "number" }, "backordered": { "type": "number" }, "fulfilled": { "type": "number" }, "invoiced": { "type": "number" }, "returned": { "type": "number" } }, "required": [ "id", "lineType", "productId", "variantId", "productType", "sku", "trackInventory", "title", "description", "quantity", "quantityCancelled", "unit", "unitPrice", "vatRate", "discountKind", "discountValue", "promotionId", "position", "allocated", "backordered", "fulfilled", "invoiced", "returned" ] }, "orderLineTypeSchema": { "type": "string", "enum": [ "product", "custom", "shipping" ] }, "orderPaymentStatusSchema": { "type": "string", "enum": [ "none", "unpaid", "partial", "paid" ] }, "orderSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "number": { "type": "integer" }, "formattedNumber": { "type": "string" }, "customer": { "$ref": "#/components/schemas/orderCustomerSchema" }, "status": { "$ref": "#/components/schemas/orderStatusSchema" }, "channel": { "$ref": "#/components/schemas/orderChannelSchema" }, "invoicingPolicy": { "$ref": "#/components/schemas/orderInvoicingPolicySchema" }, "deliveryMethod": { "$ref": "#/components/schemas/orderDeliveryMethodSchema" }, "currency": { "type": "string" }, "subtotalExcl": { "type": "number" }, "vatTotal": { "type": "number" }, "totalIncl": { "type": "number" }, "requestedDeliveryDate": { "anyOf": [ { "$ref": "#/components/schemas/isoDateSchema" }, { "type": "null" } ] }, "quoteId": { "type": [ "string", "null" ], "format": "uuid" }, "confirmedAt": { "type": [ "string", "null" ] }, "completedAt": { "type": [ "string", "null" ] }, "cancelledAt": { "type": [ "string", "null" ] }, "createdAt": { "type": "string" }, "updatedAt": { "type": "string" }, "customerContactId": { "type": [ "string", "null" ], "format": "uuid" }, "source": { "type": [ "string", "null" ] }, "externalId": { "type": [ "string", "null" ] }, "vatInclusive": { "type": "boolean" }, "vatRegime": { "$ref": "#/components/schemas/orderVatRegimeSchema" }, "discountKind": { "$ref": "#/components/schemas/orderDiscountKindSchema" }, "discountValue": { "type": "number" }, "discountDescription": { "type": [ "string", "null" ] }, "promotionId": { "type": [ "string", "null" ], "format": "uuid" }, "templateId": { "type": [ "string", "null" ], "format": "uuid" }, "paymentRequired": { "type": "boolean" }, "shippingAddress": { "$ref": "#/components/schemas/orderAddressSchema" }, "buyerName": { "type": [ "string", "null" ] }, "buyerEmail": { "type": [ "string", "null" ] }, "buyerPhone": { "type": [ "string", "null" ] }, "notes": { "type": [ "string", "null" ] }, "customerNote": { "type": [ "string", "null" ] }, "cancelReason": { "type": [ "string", "null" ] }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "lines": { "type": "array", "items": { "$ref": "#/components/schemas/orderLineSchema" } }, "invoices": { "type": "array", "items": { "$ref": "#/components/schemas/orderInvoiceSchema" } }, "fulfillmentStatus": { "$ref": "#/components/schemas/orderFulfillmentStatusSchema" }, "paymentStatus": { "$ref": "#/components/schemas/orderPaymentStatusSchema" } }, "required": [ "id", "number", "formattedNumber", "customer", "status", "channel", "invoicingPolicy", "deliveryMethod", "currency", "subtotalExcl", "vatTotal", "totalIncl", "requestedDeliveryDate", "quoteId", "confirmedAt", "completedAt", "cancelledAt", "createdAt", "updatedAt", "customerContactId", "source", "externalId", "vatInclusive", "vatRegime", "discountKind", "discountValue", "discountDescription", "promotionId", "templateId", "paymentRequired", "shippingAddress", "buyerName", "buyerEmail", "buyerPhone", "notes", "customerNote", "cancelReason", "createdBy", "lines", "invoices", "fulfillmentStatus", "paymentStatus" ] }, "orderStatusSchema": { "type": "string", "enum": [ "draft", "confirmed", "on_hold", "completed", "cancelled" ] }, "orderVatRegimeSchema": { "type": "string", "enum": [ "standard", "reverse_charge", "exempt", "kor", "margin_scheme" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" } } ``` # Get a sales order. (/openapi/getOrder) Return one sales order with its lines, fulfillment and payment progress. ## GET /v1/organizations/{organizationId}/orders/{orderId} ```json { "operationId": "getOrder", "summary": "Get a sales order.", "description": "Return one sales order with its lines, fulfillment and payment progress.", "tags": [ "Orders" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "orderId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns the order.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/orderSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "isoDateSchema": { "type": "string", "format": "date" }, "orderAddressSchema": { "type": "object", "properties": { "name": { "type": [ "string", "null" ] }, "line1": { "type": [ "string", "null" ] }, "line2": { "type": [ "string", "null" ] }, "postalCode": { "type": [ "string", "null" ] }, "city": { "type": [ "string", "null" ] }, "country": { "type": [ "string", "null" ] }, "phone": { "type": [ "string", "null" ] } }, "required": [ "name", "line1", "line2", "postalCode", "city", "country", "phone" ] }, "orderChannelSchema": { "type": "string", "enum": [ "manual", "quote", "inbox", "api", "integration" ] }, "orderCustomerSchema": { "type": "object", "properties": { "id": { "type": "integer" }, "name": { "type": "string" } }, "required": [ "id", "name" ] }, "orderDeliveryMethodSchema": { "type": "string", "enum": [ "pickup", "own_delivery", "carrier", "digital", "none" ] }, "orderDiscountKindSchema": { "type": "string", "enum": [ "none", "percentage", "fixed" ] }, "orderFulfillmentStatusSchema": { "type": "string", "enum": [ "unfulfilled", "partial", "fulfilled" ] }, "orderInvoiceSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "displayNumber": { "type": [ "string", "null" ] }, "kind": { "type": "string" }, "status": { "type": "string" }, "totalIncl": { "type": "number" }, "amountPaidCents": { "type": "number" }, "amountDueCents": { "type": "number" }, "createdAt": { "type": "string" } }, "required": [ "id", "displayNumber", "kind", "status", "totalIncl", "amountPaidCents", "amountDueCents", "createdAt" ] }, "orderInvoicingPolicySchema": { "type": "string", "enum": [ "before_fulfillment", "on_fulfillment", "manual" ] }, "orderLineSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "lineType": { "$ref": "#/components/schemas/orderLineTypeSchema" }, "productId": { "type": [ "string", "null" ], "format": "uuid" }, "variantId": { "type": [ "string", "null" ], "format": "uuid" }, "productType": { "type": [ "string", "null" ] }, "sku": { "type": [ "string", "null" ] }, "trackInventory": { "type": "boolean" }, "title": { "type": "string" }, "description": { "type": [ "string", "null" ] }, "quantity": { "type": "number" }, "quantityCancelled": { "type": "number" }, "unit": { "type": [ "string", "null" ] }, "unitPrice": { "type": "number" }, "vatRate": { "type": "number" }, "discountKind": { "$ref": "#/components/schemas/orderDiscountKindSchema" }, "discountValue": { "type": "number" }, "promotionId": { "type": [ "string", "null" ], "format": "uuid" }, "position": { "type": "number" }, "allocated": { "type": "number" }, "backordered": { "type": "number" }, "fulfilled": { "type": "number" }, "invoiced": { "type": "number" }, "returned": { "type": "number" } }, "required": [ "id", "lineType", "productId", "variantId", "productType", "sku", "trackInventory", "title", "description", "quantity", "quantityCancelled", "unit", "unitPrice", "vatRate", "discountKind", "discountValue", "promotionId", "position", "allocated", "backordered", "fulfilled", "invoiced", "returned" ] }, "orderLineTypeSchema": { "type": "string", "enum": [ "product", "custom", "shipping" ] }, "orderPaymentStatusSchema": { "type": "string", "enum": [ "none", "unpaid", "partial", "paid" ] }, "orderSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "number": { "type": "integer" }, "formattedNumber": { "type": "string" }, "customer": { "$ref": "#/components/schemas/orderCustomerSchema" }, "status": { "$ref": "#/components/schemas/orderStatusSchema" }, "channel": { "$ref": "#/components/schemas/orderChannelSchema" }, "invoicingPolicy": { "$ref": "#/components/schemas/orderInvoicingPolicySchema" }, "deliveryMethod": { "$ref": "#/components/schemas/orderDeliveryMethodSchema" }, "currency": { "type": "string" }, "subtotalExcl": { "type": "number" }, "vatTotal": { "type": "number" }, "totalIncl": { "type": "number" }, "requestedDeliveryDate": { "anyOf": [ { "$ref": "#/components/schemas/isoDateSchema" }, { "type": "null" } ] }, "quoteId": { "type": [ "string", "null" ], "format": "uuid" }, "confirmedAt": { "type": [ "string", "null" ] }, "completedAt": { "type": [ "string", "null" ] }, "cancelledAt": { "type": [ "string", "null" ] }, "createdAt": { "type": "string" }, "updatedAt": { "type": "string" }, "customerContactId": { "type": [ "string", "null" ], "format": "uuid" }, "source": { "type": [ "string", "null" ] }, "externalId": { "type": [ "string", "null" ] }, "vatInclusive": { "type": "boolean" }, "vatRegime": { "$ref": "#/components/schemas/orderVatRegimeSchema" }, "discountKind": { "$ref": "#/components/schemas/orderDiscountKindSchema" }, "discountValue": { "type": "number" }, "discountDescription": { "type": [ "string", "null" ] }, "promotionId": { "type": [ "string", "null" ], "format": "uuid" }, "templateId": { "type": [ "string", "null" ], "format": "uuid" }, "paymentRequired": { "type": "boolean" }, "shippingAddress": { "$ref": "#/components/schemas/orderAddressSchema" }, "buyerName": { "type": [ "string", "null" ] }, "buyerEmail": { "type": [ "string", "null" ] }, "buyerPhone": { "type": [ "string", "null" ] }, "notes": { "type": [ "string", "null" ] }, "customerNote": { "type": [ "string", "null" ] }, "cancelReason": { "type": [ "string", "null" ] }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "lines": { "type": "array", "items": { "$ref": "#/components/schemas/orderLineSchema" } }, "invoices": { "type": "array", "items": { "$ref": "#/components/schemas/orderInvoiceSchema" } }, "fulfillmentStatus": { "$ref": "#/components/schemas/orderFulfillmentStatusSchema" }, "paymentStatus": { "$ref": "#/components/schemas/orderPaymentStatusSchema" } }, "required": [ "id", "number", "formattedNumber", "customer", "status", "channel", "invoicingPolicy", "deliveryMethod", "currency", "subtotalExcl", "vatTotal", "totalIncl", "requestedDeliveryDate", "quoteId", "confirmedAt", "completedAt", "cancelledAt", "createdAt", "updatedAt", "customerContactId", "source", "externalId", "vatInclusive", "vatRegime", "discountKind", "discountValue", "discountDescription", "promotionId", "templateId", "paymentRequired", "shippingAddress", "buyerName", "buyerEmail", "buyerPhone", "notes", "customerNote", "cancelReason", "createdBy", "lines", "invoices", "fulfillmentStatus", "paymentStatus" ] }, "orderStatusSchema": { "type": "string", "enum": [ "draft", "confirmed", "on_hold", "completed", "cancelled" ] }, "orderVatRegimeSchema": { "type": "string", "enum": [ "standard", "reverse_charge", "exempt", "kor", "margin_scheme" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" } } ``` # Cancel a sales order. (/openapi/cancelOrder) Cancel an order and release its reserved stock. A partly shipped order keeps what shipped and cancels the remainder. Returns 409 `APPROVAL_REQUIRED` with an `approvalId` when an approval policy wants a decision first. ## POST /v1/organizations/{organizationId}/orders/{orderId}/cancel ```json { "operationId": "cancelOrder", "summary": "Cancel a sales order.", "description": "Cancel an order and release its reserved stock. A partly shipped order keeps what shipped and cancels the remainder. Returns 409 `APPROVAL_REQUIRED` with an `approvalId` when an approval policy wants a decision first.", "tags": [ "Orders" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "orderId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/orderCancelInputSchema" } } }, "description": "An optional reason." }, "responses": { "200": { "description": "Returns the order after cancelling.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/orderSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "isoDateSchema": { "type": "string", "format": "date" }, "orderAddressSchema": { "type": "object", "properties": { "name": { "type": [ "string", "null" ] }, "line1": { "type": [ "string", "null" ] }, "line2": { "type": [ "string", "null" ] }, "postalCode": { "type": [ "string", "null" ] }, "city": { "type": [ "string", "null" ] }, "country": { "type": [ "string", "null" ] }, "phone": { "type": [ "string", "null" ] } }, "required": [ "name", "line1", "line2", "postalCode", "city", "country", "phone" ] }, "orderCancelInputSchema": { "type": "object", "properties": { "reason": { "type": [ "string", "null" ], "maxLength": 1000 } }, "required": [ "reason" ] }, "orderChannelSchema": { "type": "string", "enum": [ "manual", "quote", "inbox", "api", "integration" ] }, "orderCustomerSchema": { "type": "object", "properties": { "id": { "type": "integer" }, "name": { "type": "string" } }, "required": [ "id", "name" ] }, "orderDeliveryMethodSchema": { "type": "string", "enum": [ "pickup", "own_delivery", "carrier", "digital", "none" ] }, "orderDiscountKindSchema": { "type": "string", "enum": [ "none", "percentage", "fixed" ] }, "orderFulfillmentStatusSchema": { "type": "string", "enum": [ "unfulfilled", "partial", "fulfilled" ] }, "orderInvoiceSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "displayNumber": { "type": [ "string", "null" ] }, "kind": { "type": "string" }, "status": { "type": "string" }, "totalIncl": { "type": "number" }, "amountPaidCents": { "type": "number" }, "amountDueCents": { "type": "number" }, "createdAt": { "type": "string" } }, "required": [ "id", "displayNumber", "kind", "status", "totalIncl", "amountPaidCents", "amountDueCents", "createdAt" ] }, "orderInvoicingPolicySchema": { "type": "string", "enum": [ "before_fulfillment", "on_fulfillment", "manual" ] }, "orderLineSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "lineType": { "$ref": "#/components/schemas/orderLineTypeSchema" }, "productId": { "type": [ "string", "null" ], "format": "uuid" }, "variantId": { "type": [ "string", "null" ], "format": "uuid" }, "productType": { "type": [ "string", "null" ] }, "sku": { "type": [ "string", "null" ] }, "trackInventory": { "type": "boolean" }, "title": { "type": "string" }, "description": { "type": [ "string", "null" ] }, "quantity": { "type": "number" }, "quantityCancelled": { "type": "number" }, "unit": { "type": [ "string", "null" ] }, "unitPrice": { "type": "number" }, "vatRate": { "type": "number" }, "discountKind": { "$ref": "#/components/schemas/orderDiscountKindSchema" }, "discountValue": { "type": "number" }, "promotionId": { "type": [ "string", "null" ], "format": "uuid" }, "position": { "type": "number" }, "allocated": { "type": "number" }, "backordered": { "type": "number" }, "fulfilled": { "type": "number" }, "invoiced": { "type": "number" }, "returned": { "type": "number" } }, "required": [ "id", "lineType", "productId", "variantId", "productType", "sku", "trackInventory", "title", "description", "quantity", "quantityCancelled", "unit", "unitPrice", "vatRate", "discountKind", "discountValue", "promotionId", "position", "allocated", "backordered", "fulfilled", "invoiced", "returned" ] }, "orderLineTypeSchema": { "type": "string", "enum": [ "product", "custom", "shipping" ] }, "orderPaymentStatusSchema": { "type": "string", "enum": [ "none", "unpaid", "partial", "paid" ] }, "orderSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "number": { "type": "integer" }, "formattedNumber": { "type": "string" }, "customer": { "$ref": "#/components/schemas/orderCustomerSchema" }, "status": { "$ref": "#/components/schemas/orderStatusSchema" }, "channel": { "$ref": "#/components/schemas/orderChannelSchema" }, "invoicingPolicy": { "$ref": "#/components/schemas/orderInvoicingPolicySchema" }, "deliveryMethod": { "$ref": "#/components/schemas/orderDeliveryMethodSchema" }, "currency": { "type": "string" }, "subtotalExcl": { "type": "number" }, "vatTotal": { "type": "number" }, "totalIncl": { "type": "number" }, "requestedDeliveryDate": { "anyOf": [ { "$ref": "#/components/schemas/isoDateSchema" }, { "type": "null" } ] }, "quoteId": { "type": [ "string", "null" ], "format": "uuid" }, "confirmedAt": { "type": [ "string", "null" ] }, "completedAt": { "type": [ "string", "null" ] }, "cancelledAt": { "type": [ "string", "null" ] }, "createdAt": { "type": "string" }, "updatedAt": { "type": "string" }, "customerContactId": { "type": [ "string", "null" ], "format": "uuid" }, "source": { "type": [ "string", "null" ] }, "externalId": { "type": [ "string", "null" ] }, "vatInclusive": { "type": "boolean" }, "vatRegime": { "$ref": "#/components/schemas/orderVatRegimeSchema" }, "discountKind": { "$ref": "#/components/schemas/orderDiscountKindSchema" }, "discountValue": { "type": "number" }, "discountDescription": { "type": [ "string", "null" ] }, "promotionId": { "type": [ "string", "null" ], "format": "uuid" }, "templateId": { "type": [ "string", "null" ], "format": "uuid" }, "paymentRequired": { "type": "boolean" }, "shippingAddress": { "$ref": "#/components/schemas/orderAddressSchema" }, "buyerName": { "type": [ "string", "null" ] }, "buyerEmail": { "type": [ "string", "null" ] }, "buyerPhone": { "type": [ "string", "null" ] }, "notes": { "type": [ "string", "null" ] }, "customerNote": { "type": [ "string", "null" ] }, "cancelReason": { "type": [ "string", "null" ] }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "lines": { "type": "array", "items": { "$ref": "#/components/schemas/orderLineSchema" } }, "invoices": { "type": "array", "items": { "$ref": "#/components/schemas/orderInvoiceSchema" } }, "fulfillmentStatus": { "$ref": "#/components/schemas/orderFulfillmentStatusSchema" }, "paymentStatus": { "$ref": "#/components/schemas/orderPaymentStatusSchema" } }, "required": [ "id", "number", "formattedNumber", "customer", "status", "channel", "invoicingPolicy", "deliveryMethod", "currency", "subtotalExcl", "vatTotal", "totalIncl", "requestedDeliveryDate", "quoteId", "confirmedAt", "completedAt", "cancelledAt", "createdAt", "updatedAt", "customerContactId", "source", "externalId", "vatInclusive", "vatRegime", "discountKind", "discountValue", "discountDescription", "promotionId", "templateId", "paymentRequired", "shippingAddress", "buyerName", "buyerEmail", "buyerPhone", "notes", "customerNote", "cancelReason", "createdBy", "lines", "invoices", "fulfillmentStatus", "paymentStatus" ] }, "orderStatusSchema": { "type": "string", "enum": [ "draft", "confirmed", "on_hold", "completed", "cancelled" ] }, "orderVatRegimeSchema": { "type": "string", "enum": [ "standard", "reverse_charge", "exempt", "kor", "margin_scheme" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" } } ``` # Confirm a sales order. (/openapi/confirmOrder) Confirm a draft order and reserve stock. Lines that cannot be reserved become backorders. Returns 409 `APPROVAL_REQUIRED` with an `approvalId` when an approval policy wants a decision first. ## POST /v1/organizations/{organizationId}/orders/{orderId}/confirm ```json { "operationId": "confirmOrder", "summary": "Confirm a sales order.", "description": "Confirm a draft order and reserve stock. Lines that cannot be reserved become backorders. Returns 409 `APPROVAL_REQUIRED` with an `approvalId` when an approval policy wants a decision first.", "tags": [ "Orders" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "orderId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns the confirmed order and what could be reserved.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/orderConfirmResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "isoDateSchema": { "type": "string", "format": "date" }, "orderAddressSchema": { "type": "object", "properties": { "name": { "type": [ "string", "null" ] }, "line1": { "type": [ "string", "null" ] }, "line2": { "type": [ "string", "null" ] }, "postalCode": { "type": [ "string", "null" ] }, "city": { "type": [ "string", "null" ] }, "country": { "type": [ "string", "null" ] }, "phone": { "type": [ "string", "null" ] } }, "required": [ "name", "line1", "line2", "postalCode", "city", "country", "phone" ] }, "orderChannelSchema": { "type": "string", "enum": [ "manual", "quote", "inbox", "api", "integration" ] }, "orderConfirmResponseSchema": { "type": "object", "properties": { "order": { "$ref": "#/components/schemas/orderSchema" }, "allocation": { "$ref": "#/components/schemas/orderConfirmResultSchema" } }, "required": [ "order", "allocation" ] }, "orderConfirmResultSchema": { "type": "object", "properties": { "backorderedLines": { "type": "integer" }, "shortLines": { "type": "integer" } }, "required": [ "backorderedLines", "shortLines" ] }, "orderCustomerSchema": { "type": "object", "properties": { "id": { "type": "integer" }, "name": { "type": "string" } }, "required": [ "id", "name" ] }, "orderDeliveryMethodSchema": { "type": "string", "enum": [ "pickup", "own_delivery", "carrier", "digital", "none" ] }, "orderDiscountKindSchema": { "type": "string", "enum": [ "none", "percentage", "fixed" ] }, "orderFulfillmentStatusSchema": { "type": "string", "enum": [ "unfulfilled", "partial", "fulfilled" ] }, "orderInvoiceSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "displayNumber": { "type": [ "string", "null" ] }, "kind": { "type": "string" }, "status": { "type": "string" }, "totalIncl": { "type": "number" }, "amountPaidCents": { "type": "number" }, "amountDueCents": { "type": "number" }, "createdAt": { "type": "string" } }, "required": [ "id", "displayNumber", "kind", "status", "totalIncl", "amountPaidCents", "amountDueCents", "createdAt" ] }, "orderInvoicingPolicySchema": { "type": "string", "enum": [ "before_fulfillment", "on_fulfillment", "manual" ] }, "orderLineSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "lineType": { "$ref": "#/components/schemas/orderLineTypeSchema" }, "productId": { "type": [ "string", "null" ], "format": "uuid" }, "variantId": { "type": [ "string", "null" ], "format": "uuid" }, "productType": { "type": [ "string", "null" ] }, "sku": { "type": [ "string", "null" ] }, "trackInventory": { "type": "boolean" }, "title": { "type": "string" }, "description": { "type": [ "string", "null" ] }, "quantity": { "type": "number" }, "quantityCancelled": { "type": "number" }, "unit": { "type": [ "string", "null" ] }, "unitPrice": { "type": "number" }, "vatRate": { "type": "number" }, "discountKind": { "$ref": "#/components/schemas/orderDiscountKindSchema" }, "discountValue": { "type": "number" }, "promotionId": { "type": [ "string", "null" ], "format": "uuid" }, "position": { "type": "number" }, "allocated": { "type": "number" }, "backordered": { "type": "number" }, "fulfilled": { "type": "number" }, "invoiced": { "type": "number" }, "returned": { "type": "number" } }, "required": [ "id", "lineType", "productId", "variantId", "productType", "sku", "trackInventory", "title", "description", "quantity", "quantityCancelled", "unit", "unitPrice", "vatRate", "discountKind", "discountValue", "promotionId", "position", "allocated", "backordered", "fulfilled", "invoiced", "returned" ] }, "orderLineTypeSchema": { "type": "string", "enum": [ "product", "custom", "shipping" ] }, "orderPaymentStatusSchema": { "type": "string", "enum": [ "none", "unpaid", "partial", "paid" ] }, "orderSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "number": { "type": "integer" }, "formattedNumber": { "type": "string" }, "customer": { "$ref": "#/components/schemas/orderCustomerSchema" }, "status": { "$ref": "#/components/schemas/orderStatusSchema" }, "channel": { "$ref": "#/components/schemas/orderChannelSchema" }, "invoicingPolicy": { "$ref": "#/components/schemas/orderInvoicingPolicySchema" }, "deliveryMethod": { "$ref": "#/components/schemas/orderDeliveryMethodSchema" }, "currency": { "type": "string" }, "subtotalExcl": { "type": "number" }, "vatTotal": { "type": "number" }, "totalIncl": { "type": "number" }, "requestedDeliveryDate": { "anyOf": [ { "$ref": "#/components/schemas/isoDateSchema" }, { "type": "null" } ] }, "quoteId": { "type": [ "string", "null" ], "format": "uuid" }, "confirmedAt": { "type": [ "string", "null" ] }, "completedAt": { "type": [ "string", "null" ] }, "cancelledAt": { "type": [ "string", "null" ] }, "createdAt": { "type": "string" }, "updatedAt": { "type": "string" }, "customerContactId": { "type": [ "string", "null" ], "format": "uuid" }, "source": { "type": [ "string", "null" ] }, "externalId": { "type": [ "string", "null" ] }, "vatInclusive": { "type": "boolean" }, "vatRegime": { "$ref": "#/components/schemas/orderVatRegimeSchema" }, "discountKind": { "$ref": "#/components/schemas/orderDiscountKindSchema" }, "discountValue": { "type": "number" }, "discountDescription": { "type": [ "string", "null" ] }, "promotionId": { "type": [ "string", "null" ], "format": "uuid" }, "templateId": { "type": [ "string", "null" ], "format": "uuid" }, "paymentRequired": { "type": "boolean" }, "shippingAddress": { "$ref": "#/components/schemas/orderAddressSchema" }, "buyerName": { "type": [ "string", "null" ] }, "buyerEmail": { "type": [ "string", "null" ] }, "buyerPhone": { "type": [ "string", "null" ] }, "notes": { "type": [ "string", "null" ] }, "customerNote": { "type": [ "string", "null" ] }, "cancelReason": { "type": [ "string", "null" ] }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "lines": { "type": "array", "items": { "$ref": "#/components/schemas/orderLineSchema" } }, "invoices": { "type": "array", "items": { "$ref": "#/components/schemas/orderInvoiceSchema" } }, "fulfillmentStatus": { "$ref": "#/components/schemas/orderFulfillmentStatusSchema" }, "paymentStatus": { "$ref": "#/components/schemas/orderPaymentStatusSchema" } }, "required": [ "id", "number", "formattedNumber", "customer", "status", "channel", "invoicingPolicy", "deliveryMethod", "currency", "subtotalExcl", "vatTotal", "totalIncl", "requestedDeliveryDate", "quoteId", "confirmedAt", "completedAt", "cancelledAt", "createdAt", "updatedAt", "customerContactId", "source", "externalId", "vatInclusive", "vatRegime", "discountKind", "discountValue", "discountDescription", "promotionId", "templateId", "paymentRequired", "shippingAddress", "buyerName", "buyerEmail", "buyerPhone", "notes", "customerNote", "cancelReason", "createdBy", "lines", "invoices", "fulfillmentStatus", "paymentStatus" ] }, "orderStatusSchema": { "type": "string", "enum": [ "draft", "confirmed", "on_hold", "completed", "cancelled" ] }, "orderVatRegimeSchema": { "type": "string", "enum": [ "standard", "reverse_charge", "exempt", "kor", "margin_scheme" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" } } ``` # Read an order lifecycle. (/openapi/getOrderLifecycle) The order's lifecycle: the pinned workflow version, the current stage, the stage history, and what the lifecycle waits on now. `lifecycle` is null when no lifecycle run started for the order. ## GET /v1/organizations/{organizationId}/orders/{orderId}/lifecycle ```json { "operationId": "getOrderLifecycle", "summary": "Read an order lifecycle.", "description": "The order's lifecycle: the pinned workflow version, the current stage, the stage history, and what the lifecycle waits on now. `lifecycle` is null when no lifecycle run started for the order.", "tags": [ "Orders" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "orderId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns the order's lifecycle.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/recordLifecycleSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "dateTimeSchema": { "type": "string" }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "recordLifecycleSchema": { "type": "object", "properties": { "lifecycle": { "anyOf": [ { "$ref": "#/components/schemas/workflowLifecycleSchema" }, { "type": "null" } ] }, "definition": { "type": [ "object", "null" ], "properties": { "id": { "type": "string", "format": "uuid" }, "key": { "type": "string" }, "name": { "type": "string" }, "version": { "type": "integer", "exclusiveMinimum": 0 }, "source": { "type": "string", "enum": [ "organization", "record", "system", "template" ] } }, "required": [ "id", "key", "name", "version", "source" ] }, "events": { "type": "array", "items": { "$ref": "#/components/schemas/workflowLifecycleEventSchema" } }, "waiting": { "type": [ "object", "null" ], "properties": { "kind": { "type": "string", "enum": [ "approval", "delay", "event", "manual" ] }, "at": { "anyOf": [ { "$ref": "#/components/schemas/dateTimeSchema" }, { "type": "null" } ] }, "nextStageKey": { "type": [ "string", "null" ] }, "nextStageLabel": { "type": [ "string", "null" ] }, "events": { "type": "array", "items": { "type": "string" } } }, "required": [ "kind", "at", "nextStageKey", "nextStageLabel", "events" ] } }, "required": [ "lifecycle", "definition", "events", "waiting" ] }, "workflowLifecycleEventSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "organizationId": { "type": "string", "format": "uuid" }, "lifecycleId": { "type": "string", "format": "uuid" }, "stageKey": { "type": "string", "minLength": 1 }, "label": { "type": "string", "minLength": 1 }, "outcome": { "type": "string", "enum": [ "progress", "success", "failure", "neutral" ] }, "display": { "type": "string", "enum": [ "main", "sub", "flag", "hidden" ] }, "reachedAt": { "$ref": "#/components/schemas/dateTimeSchema" }, "actorUserId": { "type": [ "string", "null" ], "format": "uuid" }, "actorCustomerContactId": { "type": [ "string", "null" ], "format": "uuid" }, "runStepId": { "type": [ "string", "null" ], "format": "uuid" }, "sourceRunId": { "type": [ "string", "null" ], "format": "uuid" }, "clearedAt": { "anyOf": [ { "$ref": "#/components/schemas/dateTimeSchema" }, { "type": "null" } ] }, "createdAt": { "$ref": "#/components/schemas/dateTimeSchema" } }, "required": [ "id", "organizationId", "lifecycleId", "stageKey", "label", "outcome", "display", "reachedAt", "actorUserId", "actorCustomerContactId", "runStepId", "sourceRunId", "clearedAt", "createdAt" ] }, "workflowLifecycleSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "organizationId": { "type": "string", "format": "uuid" }, "subjectType": { "$ref": "#/components/schemas/workflowSubjectTypeSchema" }, "subjectId": { "type": "string", "format": "uuid" }, "definitionId": { "type": "string", "format": "uuid" }, "definitionVersion": { "type": "integer", "exclusiveMinimum": 0 }, "runId": { "type": [ "string", "null" ], "format": "uuid" }, "currentStageKey": { "type": [ "string", "null" ] }, "pausedAt": { "anyOf": [ { "$ref": "#/components/schemas/dateTimeSchema" }, { "type": "null" } ] }, "pausedUntil": { "anyOf": [ { "$ref": "#/components/schemas/dateTimeSchema" }, { "type": "null" } ] }, "pauseReason": { "type": [ "string", "null" ] }, "pausedBy": { "type": [ "string", "null" ], "format": "uuid" }, "startedAt": { "$ref": "#/components/schemas/dateTimeSchema" }, "endedAt": { "anyOf": [ { "$ref": "#/components/schemas/dateTimeSchema" }, { "type": "null" } ] }, "createdAt": { "$ref": "#/components/schemas/dateTimeSchema" }, "updatedAt": { "$ref": "#/components/schemas/dateTimeSchema" } }, "required": [ "id", "organizationId", "subjectType", "subjectId", "definitionId", "definitionVersion", "runId", "currentStageKey", "pausedAt", "pausedUntil", "pauseReason", "pausedBy", "startedAt", "endedAt", "createdAt", "updatedAt" ] }, "workflowSubjectTypeSchema": { "type": "string" } } ``` # Advance an order lifecycle stage. (/openapi/advanceOrderLifecycle) Moves the order to a manual milestone of its lifecycle. Only stages the lifecycle marks as manually advanceable are accepted. ## POST /v1/organizations/{organizationId}/orders/{orderId}/lifecycle/advance ```json { "operationId": "advanceOrderLifecycle", "summary": "Advance an order lifecycle stage.", "description": "Moves the order to a manual milestone of its lifecycle. Only stages the lifecycle marks as manually advanceable are accepted.", "tags": [ "Orders" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "orderId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/lifecycleAdvanceInputSchema" } } }, "description": "The stage key to move to.", "required": true }, "responses": { "200": { "description": "The stage was recorded.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/publicApiSuccessResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "lifecycleAdvanceInputSchema": { "type": "object", "properties": { "stageKey": { "type": "string", "minLength": 1, "maxLength": 80 } }, "required": [ "stageKey" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "publicApiSuccessResponseSchema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [ true ] } }, "required": [ "success" ] } } ``` # List the organizations available to the authenticated user. (/openapi/listOrganizations) Return the organizations the authenticated user can access, ordered by recent usage. ## GET /v1/organizations ```json { "operationId": "listOrganizations", "summary": "List the organizations available to the authenticated user.", "description": "Return the organizations the authenticated user can access, ordered by recent usage.", "tags": [ "Organizations" ], "parameters": [], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns the organizations available to the authenticated user.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/organizationsListResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "brandingColorSchema": { "type": [ "string", "null" ], "pattern": "^#(?:[0-9a-f]{3}|[0-9a-f]{6})$" }, "lifecycleKeySchema": { "type": [ "string", "null" ], "minLength": 1, "maxLength": 120, "pattern": "^[a-z0-9][a-z0-9-]*$" }, "listPaginationSchema": { "type": "object", "properties": { "offset": { "type": "integer", "minimum": 0 }, "limit": { "type": "integer", "minimum": 1, "maximum": 100 }, "total": { "type": "integer", "minimum": 0 }, "hasMore": { "type": "boolean" }, "totalIsEstimate": { "type": "boolean" } }, "required": [ "offset", "limit", "total", "hasMore" ] }, "nullablePhoneInputSchema": { "type": [ "string", "null" ], "maxLength": 20 }, "nullableShortTextSchema": { "type": [ "string", "null" ], "maxLength": 200 }, "organizationAiInferenceRegionSchema": { "type": "string", "enum": [ "eu", "us", "global" ] }, "organizationBrandImageIdSchema": { "type": "string", "format": "uuid" }, "organizationBrandImageSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/organizationBrandImageIdSchema" }, "url": { "type": "string", "format": "uri" }, "width": { "type": "integer", "exclusiveMinimum": 0 }, "height": { "type": "integer", "exclusiveMinimum": 0 } }, "required": [ "id", "url", "width", "height" ] }, "organizationBrandingColorsSchema": { "type": "object", "properties": { "primary": { "$ref": "#/components/schemas/brandingColorSchema" }, "secondary": { "$ref": "#/components/schemas/brandingColorSchema" } }, "required": [ "primary", "secondary" ] }, "organizationBrandingSchema": { "type": "object", "properties": { "logoUrl": { "type": [ "string", "null" ], "format": "uri" }, "colors": { "$ref": "#/components/schemas/organizationBrandingColorsSchema" }, "images": { "type": "array", "items": { "$ref": "#/components/schemas/organizationBrandImageSchema" }, "maxItems": 12 } }, "required": [ "logoUrl", "colors", "images" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "organizationLifecycleDefaultsSchema": { "type": "object", "properties": { "invoice": { "$ref": "#/components/schemas/lifecycleKeySchema" }, "quote": { "$ref": "#/components/schemas/lifecycleKeySchema" }, "task": { "$ref": "#/components/schemas/lifecycleKeySchema" }, "order": { "$ref": "#/components/schemas/lifecycleKeySchema" }, "purchase_order": { "$ref": "#/components/schemas/lifecycleKeySchema" }, "shipment": { "$ref": "#/components/schemas/lifecycleKeySchema" } }, "required": [ "invoice", "quote", "task", "order", "purchase_order", "shipment" ] }, "organizationOperatingDefaultsSchema": { "type": "object" }, "organizationSchema": { "type": "object", "properties": { "addressCity": { "$ref": "#/components/schemas/nullableShortTextSchema" }, "addressCountry": { "$ref": "#/components/schemas/nullableShortTextSchema" }, "addressLine1": { "$ref": "#/components/schemas/nullableShortTextSchema" }, "addressLine2": { "$ref": "#/components/schemas/nullableShortTextSchema" }, "addressPostalCode": { "$ref": "#/components/schemas/nullableShortTextSchema" }, "addressState": { "$ref": "#/components/schemas/nullableShortTextSchema" }, "aiInferenceRegion": { "$ref": "#/components/schemas/organizationAiInferenceRegionSchema" }, "btw": { "$ref": "#/components/schemas/nullableShortTextSchema" }, "iban": { "$ref": "#/components/schemas/nullableShortTextSchema" }, "id": { "$ref": "#/components/schemas/organizationIdSchema" }, "kvk": { "$ref": "#/components/schemas/nullableShortTextSchema" }, "name": { "type": "string", "minLength": 1 }, "phone": { "$ref": "#/components/schemas/nullablePhoneInputSchema" }, "slug": { "$ref": "#/components/schemas/organizationSlugSchema" }, "website": { "type": [ "string", "null" ], "format": "uri" }, "socials": { "type": "array", "items": { "$ref": "#/components/schemas/organizationSocialLinkSchema" } }, "branding": { "$ref": "#/components/schemas/organizationBrandingSchema" }, "operatingDefaults": { "$ref": "#/components/schemas/organizationOperatingDefaultsSchema" }, "lifecycleDefaults": { "$ref": "#/components/schemas/organizationLifecycleDefaultsSchema" }, "createdAt": { "type": "string", "format": "date-time" }, "disabledAt": { "type": [ "string", "null" ], "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "addressCity", "addressCountry", "addressLine1", "addressLine2", "addressPostalCode", "addressState", "aiInferenceRegion", "btw", "iban", "id", "kvk", "name", "phone", "slug", "website", "socials", "branding", "operatingDefaults", "lifecycleDefaults", "createdAt", "disabledAt", "updatedAt" ] }, "organizationSlugSchema": { "type": "string", "minLength": 2, "maxLength": 64, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" }, "organizationSocialLinkSchema": { "type": "object", "properties": { "label": { "type": "string", "minLength": 1, "maxLength": 50 }, "url": { "type": "string", "format": "uri" } }, "required": [ "label", "url" ] }, "organizationsListResponseSchema": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/userOrganizationSchema" } }, "pagination": { "$ref": "#/components/schemas/listPaginationSchema" } }, "required": [ "data", "pagination" ] }, "userOrganizationSchema": { "type": "object", "properties": { "organization": { "$ref": "#/components/schemas/organizationSchema" }, "hasTransferOwnership": { "type": "boolean" }, "lastUsedAt": { "type": "string", "format": "date-time" }, "roleKey": { "type": "string", "minLength": 1 }, "roleName": { "type": "string", "minLength": 1 } }, "required": [ "organization", "hasTransferOwnership", "lastUsedAt", "roleKey", "roleName" ] } } ``` # Create a new organization for the authenticated user. (/openapi/createOrganization) Create an organization, assign the current user as owner, and return the created organization. ## POST /v1/organizations ```json { "operationId": "createOrganization", "summary": "Create a new organization for the authenticated user.", "description": "Create an organization, assign the current user as owner, and return the created organization.", "tags": [ "Organizations" ], "parameters": [], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/organizationCreateSchema" } } }, "description": "The organization settings used to create the new organization." }, "responses": { "201": { "description": "Returns the created organization.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/organizationResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "brandingColorSchema": { "type": [ "string", "null" ], "pattern": "^#(?:[0-9a-f]{3}|[0-9a-f]{6})$" }, "lifecycleKeySchema": { "type": [ "string", "null" ], "minLength": 1, "maxLength": 120, "pattern": "^[a-z0-9][a-z0-9-]*$" }, "nullableKvkNumberInputSchema": { "type": [ "string", "null" ], "maxLength": 40 }, "nullablePhoneInputSchema": { "type": [ "string", "null" ], "maxLength": 20 }, "nullableShortTextSchema": { "type": [ "string", "null" ], "maxLength": 200 }, "nullableVatIdInputSchema": { "type": [ "string", "null" ], "maxLength": 40 }, "organizationAiInferenceRegionSchema": { "type": "string", "enum": [ "eu", "us", "global" ] }, "organizationBrandImageIdSchema": { "type": "string", "format": "uuid" }, "organizationBrandImageSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/organizationBrandImageIdSchema" }, "url": { "type": "string", "format": "uri" }, "width": { "type": "integer", "exclusiveMinimum": 0 }, "height": { "type": "integer", "exclusiveMinimum": 0 } }, "required": [ "id", "url", "width", "height" ] }, "organizationBrandingColorsSchema": { "type": "object", "properties": { "primary": { "$ref": "#/components/schemas/brandingColorSchema" }, "secondary": { "$ref": "#/components/schemas/brandingColorSchema" } }, "required": [ "primary", "secondary" ] }, "organizationBrandingSchema": { "type": "object", "properties": { "logoUrl": { "type": [ "string", "null" ], "format": "uri" }, "colors": { "$ref": "#/components/schemas/organizationBrandingColorsSchema" }, "images": { "type": "array", "items": { "$ref": "#/components/schemas/organizationBrandImageSchema" }, "maxItems": 12 } }, "required": [ "logoUrl", "colors", "images" ] }, "organizationCreateSchema": { "$ref": "#/components/schemas/organizationSettingsInputSchema" }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "organizationLifecycleDefaultsSchema": { "type": "object", "properties": { "invoice": { "$ref": "#/components/schemas/lifecycleKeySchema" }, "quote": { "$ref": "#/components/schemas/lifecycleKeySchema" }, "task": { "$ref": "#/components/schemas/lifecycleKeySchema" }, "order": { "$ref": "#/components/schemas/lifecycleKeySchema" }, "purchase_order": { "$ref": "#/components/schemas/lifecycleKeySchema" }, "shipment": { "$ref": "#/components/schemas/lifecycleKeySchema" } }, "required": [ "invoice", "quote", "task", "order", "purchase_order", "shipment" ] }, "organizationOperatingDefaultsSchema": { "type": "object" }, "organizationResponseSchema": { "$ref": "#/components/schemas/organizationSchema" }, "organizationSchema": { "type": "object", "properties": { "addressCity": { "$ref": "#/components/schemas/nullableShortTextSchema" }, "addressCountry": { "$ref": "#/components/schemas/nullableShortTextSchema" }, "addressLine1": { "$ref": "#/components/schemas/nullableShortTextSchema" }, "addressLine2": { "$ref": "#/components/schemas/nullableShortTextSchema" }, "addressPostalCode": { "$ref": "#/components/schemas/nullableShortTextSchema" }, "addressState": { "$ref": "#/components/schemas/nullableShortTextSchema" }, "aiInferenceRegion": { "$ref": "#/components/schemas/organizationAiInferenceRegionSchema" }, "btw": { "$ref": "#/components/schemas/nullableShortTextSchema" }, "iban": { "$ref": "#/components/schemas/nullableShortTextSchema" }, "id": { "$ref": "#/components/schemas/organizationIdSchema" }, "kvk": { "$ref": "#/components/schemas/nullableShortTextSchema" }, "name": { "type": "string", "minLength": 1 }, "phone": { "$ref": "#/components/schemas/nullablePhoneInputSchema" }, "slug": { "$ref": "#/components/schemas/organizationSlugSchema" }, "website": { "type": [ "string", "null" ], "format": "uri" }, "socials": { "type": "array", "items": { "$ref": "#/components/schemas/organizationSocialLinkSchema" } }, "branding": { "$ref": "#/components/schemas/organizationBrandingSchema" }, "operatingDefaults": { "$ref": "#/components/schemas/organizationOperatingDefaultsSchema" }, "lifecycleDefaults": { "$ref": "#/components/schemas/organizationLifecycleDefaultsSchema" }, "createdAt": { "type": "string", "format": "date-time" }, "disabledAt": { "type": [ "string", "null" ], "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "addressCity", "addressCountry", "addressLine1", "addressLine2", "addressPostalCode", "addressState", "aiInferenceRegion", "btw", "iban", "id", "kvk", "name", "phone", "slug", "website", "socials", "branding", "operatingDefaults", "lifecycleDefaults", "createdAt", "disabledAt", "updatedAt" ] }, "organizationSettingsInputSchema": { "type": "object", "properties": { "addressCity": { "$ref": "#/components/schemas/nullableShortTextSchema" }, "addressCountry": { "$ref": "#/components/schemas/nullableShortTextSchema" }, "addressLine1": { "$ref": "#/components/schemas/nullableShortTextSchema" }, "addressLine2": { "$ref": "#/components/schemas/nullableShortTextSchema" }, "addressPostalCode": { "$ref": "#/components/schemas/nullableShortTextSchema" }, "addressState": { "$ref": "#/components/schemas/nullableShortTextSchema" }, "btw": { "$ref": "#/components/schemas/nullableVatIdInputSchema" }, "iban": { "$ref": "#/components/schemas/nullableShortTextSchema" }, "kvk": { "$ref": "#/components/schemas/nullableKvkNumberInputSchema" }, "name": { "type": "string", "minLength": 2, "maxLength": 120 }, "phone": { "$ref": "#/components/schemas/nullablePhoneInputSchema" }, "slug": { "$ref": "#/components/schemas/organizationSlugSchema" }, "website": { "type": [ "string", "null" ], "format": "uri" }, "socials": { "type": "array", "items": { "$ref": "#/components/schemas/organizationSocialLinkSchema" }, "maxItems": 12 }, "colors": { "$ref": "#/components/schemas/organizationBrandingColorsSchema" } }, "required": [ "addressCity", "addressCountry", "addressLine1", "addressLine2", "addressPostalCode", "addressState", "btw", "iban", "kvk", "name", "phone", "slug", "website", "socials", "colors" ] }, "organizationSlugSchema": { "type": "string", "minLength": 2, "maxLength": 64, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" }, "organizationSocialLinkSchema": { "type": "object", "properties": { "label": { "type": "string", "minLength": 1, "maxLength": 50 }, "url": { "type": "string", "format": "uri" } }, "required": [ "label", "url" ] } } ``` # Get a single organization available to the authenticated user. (/openapi/getOrganizationById) Return the organization identified by the provided ID when the authenticated user can access it. ## GET /v1/organizations/{organizationId} ```json { "operationId": "getOrganizationById", "summary": "Get a single organization available to the authenticated user.", "description": "Return the organization identified by the provided ID when the authenticated user can access it.", "tags": [ "Organizations" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns the requested organization.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/organizationResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "brandingColorSchema": { "type": [ "string", "null" ], "pattern": "^#(?:[0-9a-f]{3}|[0-9a-f]{6})$" }, "lifecycleKeySchema": { "type": [ "string", "null" ], "minLength": 1, "maxLength": 120, "pattern": "^[a-z0-9][a-z0-9-]*$" }, "nullablePhoneInputSchema": { "type": [ "string", "null" ], "maxLength": 20 }, "nullableShortTextSchema": { "type": [ "string", "null" ], "maxLength": 200 }, "organizationAiInferenceRegionSchema": { "type": "string", "enum": [ "eu", "us", "global" ] }, "organizationBrandImageIdSchema": { "type": "string", "format": "uuid" }, "organizationBrandImageSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/organizationBrandImageIdSchema" }, "url": { "type": "string", "format": "uri" }, "width": { "type": "integer", "exclusiveMinimum": 0 }, "height": { "type": "integer", "exclusiveMinimum": 0 } }, "required": [ "id", "url", "width", "height" ] }, "organizationBrandingColorsSchema": { "type": "object", "properties": { "primary": { "$ref": "#/components/schemas/brandingColorSchema" }, "secondary": { "$ref": "#/components/schemas/brandingColorSchema" } }, "required": [ "primary", "secondary" ] }, "organizationBrandingSchema": { "type": "object", "properties": { "logoUrl": { "type": [ "string", "null" ], "format": "uri" }, "colors": { "$ref": "#/components/schemas/organizationBrandingColorsSchema" }, "images": { "type": "array", "items": { "$ref": "#/components/schemas/organizationBrandImageSchema" }, "maxItems": 12 } }, "required": [ "logoUrl", "colors", "images" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "organizationLifecycleDefaultsSchema": { "type": "object", "properties": { "invoice": { "$ref": "#/components/schemas/lifecycleKeySchema" }, "quote": { "$ref": "#/components/schemas/lifecycleKeySchema" }, "task": { "$ref": "#/components/schemas/lifecycleKeySchema" }, "order": { "$ref": "#/components/schemas/lifecycleKeySchema" }, "purchase_order": { "$ref": "#/components/schemas/lifecycleKeySchema" }, "shipment": { "$ref": "#/components/schemas/lifecycleKeySchema" } }, "required": [ "invoice", "quote", "task", "order", "purchase_order", "shipment" ] }, "organizationOperatingDefaultsSchema": { "type": "object" }, "organizationResponseSchema": { "$ref": "#/components/schemas/organizationSchema" }, "organizationSchema": { "type": "object", "properties": { "addressCity": { "$ref": "#/components/schemas/nullableShortTextSchema" }, "addressCountry": { "$ref": "#/components/schemas/nullableShortTextSchema" }, "addressLine1": { "$ref": "#/components/schemas/nullableShortTextSchema" }, "addressLine2": { "$ref": "#/components/schemas/nullableShortTextSchema" }, "addressPostalCode": { "$ref": "#/components/schemas/nullableShortTextSchema" }, "addressState": { "$ref": "#/components/schemas/nullableShortTextSchema" }, "aiInferenceRegion": { "$ref": "#/components/schemas/organizationAiInferenceRegionSchema" }, "btw": { "$ref": "#/components/schemas/nullableShortTextSchema" }, "iban": { "$ref": "#/components/schemas/nullableShortTextSchema" }, "id": { "$ref": "#/components/schemas/organizationIdSchema" }, "kvk": { "$ref": "#/components/schemas/nullableShortTextSchema" }, "name": { "type": "string", "minLength": 1 }, "phone": { "$ref": "#/components/schemas/nullablePhoneInputSchema" }, "slug": { "$ref": "#/components/schemas/organizationSlugSchema" }, "website": { "type": [ "string", "null" ], "format": "uri" }, "socials": { "type": "array", "items": { "$ref": "#/components/schemas/organizationSocialLinkSchema" } }, "branding": { "$ref": "#/components/schemas/organizationBrandingSchema" }, "operatingDefaults": { "$ref": "#/components/schemas/organizationOperatingDefaultsSchema" }, "lifecycleDefaults": { "$ref": "#/components/schemas/organizationLifecycleDefaultsSchema" }, "createdAt": { "type": "string", "format": "date-time" }, "disabledAt": { "type": [ "string", "null" ], "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "addressCity", "addressCountry", "addressLine1", "addressLine2", "addressPostalCode", "addressState", "aiInferenceRegion", "btw", "iban", "id", "kvk", "name", "phone", "slug", "website", "socials", "branding", "operatingDefaults", "lifecycleDefaults", "createdAt", "disabledAt", "updatedAt" ] }, "organizationSlugSchema": { "type": "string", "minLength": 2, "maxLength": 64, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" }, "organizationSocialLinkSchema": { "type": "object", "properties": { "label": { "type": "string", "minLength": 1, "maxLength": 50 }, "url": { "type": "string", "format": "uri" } }, "required": [ "label", "url" ] } } ``` # Update a single organization. (/openapi/updateOrganization) Update the settings for the specified organization when the authenticated user has permission. ## PATCH /v1/organizations/{organizationId} ```json { "operationId": "updateOrganization", "summary": "Update a single organization.", "description": "Update the settings for the specified organization when the authenticated user has permission.", "tags": [ "Organizations" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/organizationUpdateSchema" } } }, "description": "The updated organization settings." }, "responses": { "200": { "description": "Returns the updated organization.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/organizationResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "brandingColorSchema": { "type": [ "string", "null" ], "pattern": "^#(?:[0-9a-f]{3}|[0-9a-f]{6})$" }, "lifecycleKeySchema": { "type": [ "string", "null" ], "minLength": 1, "maxLength": 120, "pattern": "^[a-z0-9][a-z0-9-]*$" }, "nullableKvkNumberInputSchema": { "type": [ "string", "null" ], "maxLength": 40 }, "nullablePhoneInputSchema": { "type": [ "string", "null" ], "maxLength": 20 }, "nullableShortTextSchema": { "type": [ "string", "null" ], "maxLength": 200 }, "nullableVatIdInputSchema": { "type": [ "string", "null" ], "maxLength": 40 }, "organizationAiInferenceRegionSchema": { "type": "string", "enum": [ "eu", "us", "global" ] }, "organizationBrandImageIdSchema": { "type": "string", "format": "uuid" }, "organizationBrandImageSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/organizationBrandImageIdSchema" }, "url": { "type": "string", "format": "uri" }, "width": { "type": "integer", "exclusiveMinimum": 0 }, "height": { "type": "integer", "exclusiveMinimum": 0 } }, "required": [ "id", "url", "width", "height" ] }, "organizationBrandingColorsSchema": { "type": "object", "properties": { "primary": { "$ref": "#/components/schemas/brandingColorSchema" }, "secondary": { "$ref": "#/components/schemas/brandingColorSchema" } }, "required": [ "primary", "secondary" ] }, "organizationBrandingSchema": { "type": "object", "properties": { "logoUrl": { "type": [ "string", "null" ], "format": "uri" }, "colors": { "$ref": "#/components/schemas/organizationBrandingColorsSchema" }, "images": { "type": "array", "items": { "$ref": "#/components/schemas/organizationBrandImageSchema" }, "maxItems": 12 } }, "required": [ "logoUrl", "colors", "images" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "organizationLifecycleDefaultsSchema": { "type": "object", "properties": { "invoice": { "$ref": "#/components/schemas/lifecycleKeySchema" }, "quote": { "$ref": "#/components/schemas/lifecycleKeySchema" }, "task": { "$ref": "#/components/schemas/lifecycleKeySchema" }, "order": { "$ref": "#/components/schemas/lifecycleKeySchema" }, "purchase_order": { "$ref": "#/components/schemas/lifecycleKeySchema" }, "shipment": { "$ref": "#/components/schemas/lifecycleKeySchema" } }, "required": [ "invoice", "quote", "task", "order", "purchase_order", "shipment" ] }, "organizationOperatingDefaultsSchema": { "type": "object" }, "organizationResponseSchema": { "$ref": "#/components/schemas/organizationSchema" }, "organizationSchema": { "type": "object", "properties": { "addressCity": { "$ref": "#/components/schemas/nullableShortTextSchema" }, "addressCountry": { "$ref": "#/components/schemas/nullableShortTextSchema" }, "addressLine1": { "$ref": "#/components/schemas/nullableShortTextSchema" }, "addressLine2": { "$ref": "#/components/schemas/nullableShortTextSchema" }, "addressPostalCode": { "$ref": "#/components/schemas/nullableShortTextSchema" }, "addressState": { "$ref": "#/components/schemas/nullableShortTextSchema" }, "aiInferenceRegion": { "$ref": "#/components/schemas/organizationAiInferenceRegionSchema" }, "btw": { "$ref": "#/components/schemas/nullableShortTextSchema" }, "iban": { "$ref": "#/components/schemas/nullableShortTextSchema" }, "id": { "$ref": "#/components/schemas/organizationIdSchema" }, "kvk": { "$ref": "#/components/schemas/nullableShortTextSchema" }, "name": { "type": "string", "minLength": 1 }, "phone": { "$ref": "#/components/schemas/nullablePhoneInputSchema" }, "slug": { "$ref": "#/components/schemas/organizationSlugSchema" }, "website": { "type": [ "string", "null" ], "format": "uri" }, "socials": { "type": "array", "items": { "$ref": "#/components/schemas/organizationSocialLinkSchema" } }, "branding": { "$ref": "#/components/schemas/organizationBrandingSchema" }, "operatingDefaults": { "$ref": "#/components/schemas/organizationOperatingDefaultsSchema" }, "lifecycleDefaults": { "$ref": "#/components/schemas/organizationLifecycleDefaultsSchema" }, "createdAt": { "type": "string", "format": "date-time" }, "disabledAt": { "type": [ "string", "null" ], "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "addressCity", "addressCountry", "addressLine1", "addressLine2", "addressPostalCode", "addressState", "aiInferenceRegion", "btw", "iban", "id", "kvk", "name", "phone", "slug", "website", "socials", "branding", "operatingDefaults", "lifecycleDefaults", "createdAt", "disabledAt", "updatedAt" ] }, "organizationSettingsInputSchema": { "type": "object", "properties": { "addressCity": { "$ref": "#/components/schemas/nullableShortTextSchema" }, "addressCountry": { "$ref": "#/components/schemas/nullableShortTextSchema" }, "addressLine1": { "$ref": "#/components/schemas/nullableShortTextSchema" }, "addressLine2": { "$ref": "#/components/schemas/nullableShortTextSchema" }, "addressPostalCode": { "$ref": "#/components/schemas/nullableShortTextSchema" }, "addressState": { "$ref": "#/components/schemas/nullableShortTextSchema" }, "btw": { "$ref": "#/components/schemas/nullableVatIdInputSchema" }, "iban": { "$ref": "#/components/schemas/nullableShortTextSchema" }, "kvk": { "$ref": "#/components/schemas/nullableKvkNumberInputSchema" }, "name": { "type": "string", "minLength": 2, "maxLength": 120 }, "phone": { "$ref": "#/components/schemas/nullablePhoneInputSchema" }, "slug": { "$ref": "#/components/schemas/organizationSlugSchema" }, "website": { "type": [ "string", "null" ], "format": "uri" }, "socials": { "type": "array", "items": { "$ref": "#/components/schemas/organizationSocialLinkSchema" }, "maxItems": 12 }, "colors": { "$ref": "#/components/schemas/organizationBrandingColorsSchema" } }, "required": [ "addressCity", "addressCountry", "addressLine1", "addressLine2", "addressPostalCode", "addressState", "btw", "iban", "kvk", "name", "phone", "slug", "website", "socials", "colors" ] }, "organizationSlugSchema": { "type": "string", "minLength": 2, "maxLength": 64, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" }, "organizationSocialLinkSchema": { "type": "object", "properties": { "label": { "type": "string", "minLength": 1, "maxLength": 50 }, "url": { "type": "string", "format": "uri" } }, "required": [ "label", "url" ] }, "organizationUpdateSchema": { "$ref": "#/components/schemas/organizationSettingsInputSchema" } } ``` # Delete a single organization. (/openapi/deleteOrganization) Delete the specified organization when the authenticated user is allowed to remove it. ## DELETE /v1/organizations/{organizationId} ```json { "operationId": "deleteOrganization", "summary": "Delete a single organization.", "description": "Delete the specified organization when the authenticated user is allowed to remove it.", "tags": [ "Organizations" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns a success payload when the organization is deleted.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/organizationDeleteResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "organizationDeleteResponseSchema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [ true ] } }, "required": [ "success" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" } } ``` # Set the active organization preference for the authenticated user. (/openapi/setActiveOrganization) Store the preferred active organization on the current user's profile. This helps clients choose a default organization when no explicit organization id is supplied on a request, but it does not replace per-request membership checks or authorization. ## PATCH /v1/users/me/active-organization ```json { "operationId": "setActiveOrganization", "summary": "Set the active organization preference for the authenticated user.", "description": "Store the preferred active organization on the current user's profile. This helps clients choose a default organization when no explicit organization id is supplied on a request, but it does not replace per-request membership checks or authorization.", "tags": [ "Organizations" ], "parameters": [], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/organizationSelectionSchema" } } }, "description": "The organization to mark as the preferred active organization." }, "responses": { "200": { "description": "Returns a success payload when the active organization preference is updated.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/activeOrganizationResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "activeOrganizationResponseSchema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [ true ] } }, "required": [ "success" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "organizationSelectionSchema": { "type": "object", "properties": { "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" } }, "required": [ "organizationId" ] } } ``` # Upload a brand image to the organization gallery. (/openapi/uploadOrganizationBrandImage) Upload a PNG, JPEG, or WebP brand image. The server normalizes the image, strips EXIF metadata, and stores one standardized WebP image for rendering. Organizations are capped at 12 brand images. ## POST /v1/organizations/{organizationId}/branding/brand-images ```json { "operationId": "uploadOrganizationBrandImage", "summary": "Upload a brand image to the organization gallery.", "description": "Upload a PNG, JPEG, or WebP brand image. The server normalizes the image, strips EXIF metadata, and stores one standardized WebP image for rendering. Organizations are capped at 12 brand images.", "tags": [ "Organizations" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "multipart/form-data": { "schema": { "$ref": "#/components/schemas/brandingFileUploadBodySchema" }, "encoding": { "file": { "contentType": "application/octet-stream" } } } }, "description": "Multipart form data with a single `file` field containing the brand image." }, "responses": { "200": { "description": "Returns the metadata and public URL for the stored brand image.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/organizationBrandImageResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "brandingFileUploadBodySchema": { "type": "object", "properties": { "file": { "type": "string", "contentMediaType": "application/octet-stream", "description": "PNG, JPEG, or WebP image file" } }, "required": [ "file" ] }, "organizationBrandImageIdSchema": { "type": "string", "format": "uuid" }, "organizationBrandImageResponseSchema": { "$ref": "#/components/schemas/organizationBrandImageSchema" }, "organizationBrandImageSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/organizationBrandImageIdSchema" }, "url": { "type": "string", "format": "uri" }, "width": { "type": "integer", "exclusiveMinimum": 0 }, "height": { "type": "integer", "exclusiveMinimum": 0 } }, "required": [ "id", "url", "width", "height" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" } } ``` # Upload the organization logo. (/openapi/uploadOrganizationLogo) Upload a PNG, JPEG, or WebP file to replace the organization logo. The server normalizes the image, strips EXIF metadata, and stores one standardized WebP image for rendering. ## POST /v1/organizations/{organizationId}/branding/logo ```json { "operationId": "uploadOrganizationLogo", "summary": "Upload the organization logo.", "description": "Upload a PNG, JPEG, or WebP file to replace the organization logo. The server normalizes the image, strips EXIF metadata, and stores one standardized WebP image for rendering.", "tags": [ "Organizations" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "multipart/form-data": { "schema": { "$ref": "#/components/schemas/brandingFileUploadBodySchema" }, "encoding": { "file": { "contentType": "application/octet-stream" } } } }, "description": "Multipart form data with a single `file` field containing the logo image." }, "responses": { "200": { "description": "Returns the public URL for the generated logo.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/uploadBrandingLogoResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "brandingFileUploadBodySchema": { "type": "object", "properties": { "file": { "type": "string", "contentMediaType": "application/octet-stream", "description": "PNG, JPEG, or WebP image file" } }, "required": [ "file" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "uploadBrandingLogoResponseSchema": { "type": "object", "properties": { "logoUrl": { "type": "string", "format": "uri" } }, "required": [ "logoUrl" ] } } ``` # Remove the organization logo. (/openapi/removeOrganizationLogo) Remove the organization logo from storage. ## DELETE /v1/organizations/{organizationId}/branding/logo ```json { "operationId": "removeOrganizationLogo", "summary": "Remove the organization logo.", "description": "Remove the organization logo from storage.", "tags": [ "Organizations" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns a success payload when the logo is removed.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/organizationDeleteResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "organizationDeleteResponseSchema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [ true ] } }, "required": [ "success" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" } } ``` # Remove a brand image from the organization gallery. (/openapi/removeOrganizationBrandImage) Delete a stored brand image. ## DELETE /v1/organizations/{organizationId}/branding/brand-images/{brandImageId} ```json { "operationId": "removeOrganizationBrandImage", "summary": "Remove a brand image from the organization gallery.", "description": "Delete a stored brand image.", "tags": [ "Organizations" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "brandImageId", "required": true, "schema": { "$ref": "#/components/schemas/organizationBrandImageIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns a success payload when the brand image is removed.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/organizationDeleteResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "organizationBrandImageIdSchema": { "type": "string", "format": "uuid" }, "organizationDeleteResponseSchema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [ true ] } }, "required": [ "success" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" } } ``` # List my pending approvals. (/openapi/listPendingApprovals) Return time-off requests, timesheets, and expense claims waiting for the caller's decision, including delegated ones. ## GET /v1/organizations/{organizationId}/approvals ```json { "operationId": "listPendingApprovals", "summary": "List my pending approvals.", "description": "Return time-off requests, timesheets, and expense claims waiting for the caller's decision, including delegated ones.", "tags": [ "People" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns pending approvals.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/approvalsResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "approvalsResponseSchema": { "type": "array", "items": { "$ref": "#/components/schemas/hrApprovalSchema" } }, "hrApprovalDecisionSchema": { "type": "string", "enum": [ "pending", "approved", "rejected" ] }, "hrApprovalSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "subjectType": { "$ref": "#/components/schemas/hrApprovalSubjectSchema" }, "subjectId": { "type": "string", "format": "uuid" }, "employee": { "$ref": "#/components/schemas/personSummarySchema" }, "stepNumber": { "type": "integer" }, "approverEmployeeId": { "type": [ "string", "null" ], "format": "uuid" }, "decision": { "$ref": "#/components/schemas/hrApprovalDecisionSchema" }, "decidedBy": { "type": [ "string", "null" ], "format": "uuid" }, "decidedAt": { "type": [ "string", "null" ] }, "note": { "type": [ "string", "null" ] }, "summary": { "type": "string" }, "weekStart": { "anyOf": [ { "$ref": "#/components/schemas/isoDateSchema" }, { "type": "null" } ] }, "createdAt": { "type": "string" } }, "required": [ "id", "subjectType", "subjectId", "employee", "stepNumber", "approverEmployeeId", "decision", "decidedBy", "decidedAt", "note", "summary", "weekStart", "createdAt" ] }, "hrApprovalSubjectSchema": { "type": "string", "enum": [ "time_off_request", "timesheet", "expense" ] }, "isoDateSchema": { "type": "string", "format": "date" }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "personSummarySchema": { "type": "object", "properties": { "employeeId": { "type": "string", "format": "uuid" }, "userId": { "type": [ "string", "null" ], "format": "uuid" }, "displayName": { "type": "string" }, "avatarUrl": { "type": [ "string", "null" ] }, "jobTitle": { "type": [ "string", "null" ] } }, "required": [ "employeeId", "userId", "displayName", "avatarUrl", "jobTitle" ] } } ``` # Decide approvals. (/openapi/decideApprovals) Approve or reject up to 100 pending approvals at once. Each approval succeeds or fails on its own. ## POST /v1/organizations/{organizationId}/approvals ```json { "operationId": "decideApprovals", "summary": "Decide approvals.", "description": "Approve or reject up to 100 pending approvals at once. Each approval succeeds or fails on its own.", "tags": [ "People" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/hrApprovalDecideInputSchema" } } }, "description": "The approvals and the decision." }, "responses": { "200": { "description": "Returns which approvals were decided and which failed.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/hrApprovalDecideResultSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "hrApprovalDecideInputSchema": { "type": "object", "properties": { "approvalIds": { "type": "array", "items": { "type": "string", "format": "uuid" }, "minItems": 1, "maxItems": 100 }, "decision": { "type": "string", "enum": [ "approved", "rejected" ] }, "note": { "type": [ "string", "null" ], "maxLength": 2000 } }, "required": [ "approvalIds", "decision", "note" ] }, "hrApprovalDecideResultSchema": { "type": "object", "properties": { "decided": { "type": "array", "items": { "type": "string", "format": "uuid" } }, "failed": { "type": "array", "items": { "type": "object", "properties": { "approvalId": { "type": "string", "format": "uuid" }, "message": { "type": "string" } }, "required": [ "approvalId", "message" ] } } }, "required": [ "decided", "failed" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" } } ``` # List employees. (/openapi/listEmployees) Return employees, contractors, and interns for an organization. Private details are never included. ## GET /v1/organizations/{organizationId}/employees ```json { "operationId": "listEmployees", "summary": "List employees.", "description": "Return employees, contractors, and interns for an organization. Private details are never included.", "tags": [ "People" ], "parameters": [ { "in": "query", "name": "q", "required": false, "schema": { "type": "string", "maxLength": 200 }, "example": "example" }, { "in": "query", "name": "status", "required": false, "schema": { "allOf": [ { "$ref": "#/components/schemas/employeeStatusSchema" } ] }, "example": "example" }, { "in": "query", "name": "kind", "required": false, "schema": { "allOf": [ { "$ref": "#/components/schemas/employeeKindSchema" } ] }, "example": "example" }, { "in": "query", "name": "teamId", "required": false, "schema": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 }, "example": 123 }, { "in": "query", "name": "managerEmployeeId", "required": false, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "query", "name": "includeArchived", "required": false, "schema": { "type": "string", "enum": [ "true", "false" ] }, "example": "true" }, { "in": "query", "name": "offset", "required": true, "schema": { "default": 0, "type": "integer", "minimum": 0, "maximum": 9007199254740991 }, "example": 1 }, { "in": "query", "name": "limit", "required": true, "schema": { "default": 50, "type": "integer", "minimum": 1, "maximum": 100 }, "example": 1 }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns employees.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/employeesResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "employeeKindSchema": { "type": "string", "enum": [ "employee", "contractor", "intern" ] }, "employeeSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "organizationId": { "type": "string", "format": "uuid" }, "userId": { "type": [ "string", "null" ], "format": "uuid" }, "kind": { "$ref": "#/components/schemas/employeeKindSchema" }, "status": { "$ref": "#/components/schemas/employeeStatusSchema" }, "firstName": { "type": "string" }, "lastName": { "type": [ "string", "null" ] }, "displayName": { "type": "string" }, "workEmail": { "type": [ "string", "null" ] }, "jobTitle": { "type": [ "string", "null" ] }, "departmentTeamId": { "anyOf": [ { "$ref": "#/components/schemas/teamIdSchema" }, { "type": "null" } ] }, "departmentTeamName": { "type": [ "string", "null" ] }, "reportsToEmployeeId": { "type": [ "string", "null" ], "format": "uuid" }, "manager": { "anyOf": [ { "$ref": "#/components/schemas/personSummarySchema" }, { "type": "null" } ] }, "startDate": { "type": "object" }, "endDate": { "type": "object" }, "timezone": { "type": [ "string", "null" ] }, "companyName": { "type": [ "string", "null" ] }, "kvk": { "type": [ "string", "null" ] }, "vatId": { "type": [ "string", "null" ] }, "showBirthday": { "type": "boolean" }, "showWorkAnniversary": { "type": "boolean" }, "scheduleCycleAnchor": { "type": "object" }, "avatarUrl": { "type": [ "string", "null" ] }, "hasAppAccess": { "type": "boolean" }, "accountDisabled": { "type": "boolean" }, "archivedAt": { "type": [ "string", "null" ] }, "createdAt": { "type": "string" }, "updatedAt": { "type": "string" } }, "required": [ "id", "organizationId", "userId", "kind", "status", "firstName", "lastName", "displayName", "workEmail", "jobTitle", "departmentTeamId", "departmentTeamName", "reportsToEmployeeId", "manager", "startDate", "endDate", "timezone", "companyName", "kvk", "vatId", "showBirthday", "showWorkAnniversary", "scheduleCycleAnchor", "avatarUrl", "hasAppAccess", "accountDisabled", "archivedAt", "createdAt", "updatedAt" ] }, "employeeStatusSchema": { "type": "string", "enum": [ "onboarding", "active", "offboarding", "ended" ] }, "employeesResponseSchema": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/employeeSchema" } }, "pagination": { "$ref": "#/components/schemas/listPaginationSchema" } }, "required": [ "data", "pagination" ] }, "listPaginationSchema": { "type": "object", "properties": { "offset": { "type": "integer", "minimum": 0 }, "limit": { "type": "integer", "minimum": 1, "maximum": 100 }, "total": { "type": "integer", "minimum": 0 }, "hasMore": { "type": "boolean" }, "totalIsEstimate": { "type": "boolean" } }, "required": [ "offset", "limit", "total", "hasMore" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "personSummarySchema": { "type": "object", "properties": { "employeeId": { "type": "string", "format": "uuid" }, "userId": { "type": [ "string", "null" ], "format": "uuid" }, "displayName": { "type": "string" }, "avatarUrl": { "type": [ "string", "null" ] }, "jobTitle": { "type": [ "string", "null" ] } }, "required": [ "employeeId", "userId", "displayName", "avatarUrl", "jobTitle" ] }, "teamIdSchema": { "type": "integer", "exclusiveMinimum": 0 } } ``` # Create an employee. (/openapi/createEmployee) Create an employee record without a user account. Every organization member already has one. ## POST /v1/organizations/{organizationId}/employees ```json { "operationId": "createEmployee", "summary": "Create an employee.", "description": "Create an employee record without a user account. Every organization member already has one.", "tags": [ "People" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/employeeInputSchema" } } }, "description": "The employee payload." }, "responses": { "201": { "description": "Returns the created employee.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/employeeSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "employeeInputSchema": { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "employee", "contractor", "intern" ], "default": "employee" }, "status": { "allOf": [ { "$ref": "#/components/schemas/employeeStatusSchema" } ] }, "firstName": { "type": "string", "minLength": 1, "maxLength": 120 }, "lastName": { "type": [ "string", "null" ], "maxLength": 120 }, "workEmail": { "anyOf": [ { "type": [ "string", "null" ], "format": "email", "maxLength": 320 }, { "type": "string", "enum": [ "" ] } ] }, "jobTitle": { "type": [ "string", "null" ], "maxLength": 160 }, "departmentTeamId": { "anyOf": [ { "$ref": "#/components/schemas/teamIdSchema" }, { "type": "null" } ] }, "reportsToEmployeeId": { "type": [ "string", "null" ], "format": "uuid" }, "startDate": { "type": "object" }, "endDate": { "type": "object" }, "timezone": { "type": [ "string", "null" ], "maxLength": 64 }, "companyName": { "type": [ "string", "null" ], "maxLength": 200 }, "kvk": { "type": [ "string", "null" ], "maxLength": 32 }, "vatId": { "type": [ "string", "null" ], "maxLength": 32 }, "showBirthday": { "type": "boolean" }, "showWorkAnniversary": { "type": "boolean" } }, "required": [ "kind", "firstName", "lastName", "jobTitle", "timezone", "companyName", "kvk", "vatId" ] }, "employeeKindSchema": { "type": "string", "enum": [ "employee", "contractor", "intern" ] }, "employeeSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "organizationId": { "type": "string", "format": "uuid" }, "userId": { "type": [ "string", "null" ], "format": "uuid" }, "kind": { "$ref": "#/components/schemas/employeeKindSchema" }, "status": { "$ref": "#/components/schemas/employeeStatusSchema" }, "firstName": { "type": "string" }, "lastName": { "type": [ "string", "null" ] }, "displayName": { "type": "string" }, "workEmail": { "type": [ "string", "null" ] }, "jobTitle": { "type": [ "string", "null" ] }, "departmentTeamId": { "anyOf": [ { "$ref": "#/components/schemas/teamIdSchema" }, { "type": "null" } ] }, "departmentTeamName": { "type": [ "string", "null" ] }, "reportsToEmployeeId": { "type": [ "string", "null" ], "format": "uuid" }, "manager": { "anyOf": [ { "$ref": "#/components/schemas/personSummarySchema" }, { "type": "null" } ] }, "startDate": { "type": "object" }, "endDate": { "type": "object" }, "timezone": { "type": [ "string", "null" ] }, "companyName": { "type": [ "string", "null" ] }, "kvk": { "type": [ "string", "null" ] }, "vatId": { "type": [ "string", "null" ] }, "showBirthday": { "type": "boolean" }, "showWorkAnniversary": { "type": "boolean" }, "scheduleCycleAnchor": { "type": "object" }, "avatarUrl": { "type": [ "string", "null" ] }, "hasAppAccess": { "type": "boolean" }, "accountDisabled": { "type": "boolean" }, "archivedAt": { "type": [ "string", "null" ] }, "createdAt": { "type": "string" }, "updatedAt": { "type": "string" } }, "required": [ "id", "organizationId", "userId", "kind", "status", "firstName", "lastName", "displayName", "workEmail", "jobTitle", "departmentTeamId", "departmentTeamName", "reportsToEmployeeId", "manager", "startDate", "endDate", "timezone", "companyName", "kvk", "vatId", "showBirthday", "showWorkAnniversary", "scheduleCycleAnchor", "avatarUrl", "hasAppAccess", "accountDisabled", "archivedAt", "createdAt", "updatedAt" ] }, "employeeStatusSchema": { "type": "string", "enum": [ "onboarding", "active", "offboarding", "ended" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "personSummarySchema": { "type": "object", "properties": { "employeeId": { "type": "string", "format": "uuid" }, "userId": { "type": [ "string", "null" ], "format": "uuid" }, "displayName": { "type": "string" }, "avatarUrl": { "type": [ "string", "null" ] }, "jobTitle": { "type": [ "string", "null" ] } }, "required": [ "employeeId", "userId", "displayName", "avatarUrl", "jobTitle" ] }, "teamIdSchema": { "type": "integer", "exclusiveMinimum": 0 } } ``` # List time entries. (/openapi/listTimeEntries) Return logged time for tasks, customers, and internal work. ## GET /v1/organizations/{organizationId}/time-entries ```json { "operationId": "listTimeEntries", "summary": "List time entries.", "description": "Return logged time for tasks, customers, and internal work.", "tags": [ "People" ], "parameters": [ { "in": "query", "name": "employeeId", "required": false, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "query", "name": "taskId", "required": false, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "query", "name": "customerId", "required": false, "schema": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 }, "example": 123 }, { "in": "query", "name": "from", "required": false, "schema": { "type": "string", "format": "date-time" }, "example": "example" }, { "in": "query", "name": "to", "required": false, "schema": { "type": "string", "format": "date-time" }, "example": "example" }, { "in": "query", "name": "offset", "required": true, "schema": { "default": 0, "type": "integer", "minimum": 0, "maximum": 9007199254740991 }, "example": 1 }, { "in": "query", "name": "limit", "required": true, "schema": { "default": 50, "type": "integer", "minimum": 1, "maximum": 100 }, "example": 1 }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns time entries.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/timeEntriesResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "customerIdSchema": { "type": "integer", "exclusiveMinimum": 0 }, "listPaginationSchema": { "type": "object", "properties": { "offset": { "type": "integer", "minimum": 0 }, "limit": { "type": "integer", "minimum": 1, "maximum": 100 }, "total": { "type": "integer", "minimum": 0 }, "hasMore": { "type": "boolean" }, "totalIsEstimate": { "type": "boolean" } }, "required": [ "offset", "limit", "total", "hasMore" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "timeEntriesResponseSchema": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/timeEntrySchema" } }, "pagination": { "$ref": "#/components/schemas/listPaginationSchema" } }, "required": [ "data", "pagination" ] }, "timeEntrySchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "employeeId": { "type": "string", "format": "uuid" }, "userId": { "type": [ "string", "null" ], "format": "uuid" }, "taskId": { "type": [ "string", "null" ], "format": "uuid" }, "taskTitle": { "type": [ "string", "null" ] }, "agendaItemId": { "type": [ "string", "null" ], "format": "uuid" }, "agendaItemTitle": { "type": [ "string", "null" ] }, "customerId": { "anyOf": [ { "$ref": "#/components/schemas/customerIdSchema" }, { "type": "null" } ] }, "customerName": { "type": [ "string", "null" ] }, "startedAt": { "type": "string" }, "durationSeconds": { "type": [ "integer", "null" ] }, "note": { "type": [ "string", "null" ] }, "billable": { "type": "boolean" }, "source": { "$ref": "#/components/schemas/timeEntrySourceSchema" }, "timesheetId": { "type": [ "string", "null" ], "format": "uuid" }, "invoiceLineId": { "type": [ "string", "null" ], "format": "uuid" }, "locked": { "type": "boolean" }, "createdAt": { "type": "string" } }, "required": [ "id", "employeeId", "userId", "taskId", "taskTitle", "agendaItemId", "agendaItemTitle", "customerId", "customerName", "startedAt", "durationSeconds", "note", "billable", "source", "timesheetId", "invoiceLineId", "locked", "createdAt" ] }, "timeEntrySourceSchema": { "type": "string", "enum": [ "manual", "timer", "agenda", "planning", "suggestion" ] } } ``` # Log time. (/openapi/createTimeEntry) Create a time entry. Entries in a submitted or approved timesheet week are rejected. ## POST /v1/organizations/{organizationId}/time-entries ```json { "operationId": "createTimeEntry", "summary": "Log time.", "description": "Create a time entry. Entries in a submitted or approved timesheet week are rejected.", "tags": [ "People" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/timeEntryInputSchema" } } }, "description": "The time entry." }, "responses": { "201": { "description": "Returns the created time entry.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/timeEntrySchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "customerIdSchema": { "type": "integer", "exclusiveMinimum": 0 }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "timeEntryInputSchema": { "type": "object", "properties": { "employeeId": { "type": "string", "format": "uuid" }, "taskId": { "type": [ "string", "null" ], "format": "uuid" }, "agendaItemId": { "type": [ "string", "null" ], "format": "uuid" }, "customerId": { "anyOf": [ { "$ref": "#/components/schemas/customerIdSchema" }, { "type": "null" } ] }, "startedAt": { "type": "string", "format": "date-time" }, "durationSeconds": { "type": "integer", "exclusiveMinimum": 0, "maximum": 2678400 }, "note": { "type": [ "string", "null" ], "maxLength": 2000 }, "billable": { "type": "boolean" }, "source": { "allOf": [ { "$ref": "#/components/schemas/timeEntrySourceSchema" } ] } }, "required": [ "startedAt", "durationSeconds", "note" ] }, "timeEntrySchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "employeeId": { "type": "string", "format": "uuid" }, "userId": { "type": [ "string", "null" ], "format": "uuid" }, "taskId": { "type": [ "string", "null" ], "format": "uuid" }, "taskTitle": { "type": [ "string", "null" ] }, "agendaItemId": { "type": [ "string", "null" ], "format": "uuid" }, "agendaItemTitle": { "type": [ "string", "null" ] }, "customerId": { "anyOf": [ { "$ref": "#/components/schemas/customerIdSchema" }, { "type": "null" } ] }, "customerName": { "type": [ "string", "null" ] }, "startedAt": { "type": "string" }, "durationSeconds": { "type": [ "integer", "null" ] }, "note": { "type": [ "string", "null" ] }, "billable": { "type": "boolean" }, "source": { "$ref": "#/components/schemas/timeEntrySourceSchema" }, "timesheetId": { "type": [ "string", "null" ], "format": "uuid" }, "invoiceLineId": { "type": [ "string", "null" ], "format": "uuid" }, "locked": { "type": "boolean" }, "createdAt": { "type": "string" } }, "required": [ "id", "employeeId", "userId", "taskId", "taskTitle", "agendaItemId", "agendaItemTitle", "customerId", "customerName", "startedAt", "durationSeconds", "note", "billable", "source", "timesheetId", "invoiceLineId", "locked", "createdAt" ] }, "timeEntrySourceSchema": { "type": "string", "enum": [ "manual", "timer", "agenda", "planning", "suggestion" ] } } ``` # List time-off requests. (/openapi/listTimeOffRequests) Return time-off requests visible to the caller: their own, their reports', or everyone's for HR. ## GET /v1/organizations/{organizationId}/time-off-requests ```json { "operationId": "listTimeOffRequests", "summary": "List time-off requests.", "description": "Return time-off requests visible to the caller: their own, their reports', or everyone's for HR.", "tags": [ "People" ], "parameters": [ { "in": "query", "name": "employeeId", "required": false, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "query", "name": "status", "required": false, "schema": { "allOf": [ { "$ref": "#/components/schemas/timeOffRequestStatusSchema" } ] }, "example": "example" }, { "in": "query", "name": "from", "required": false, "schema": { "type": "object" }, "style": "deepObject", "explode": true, "example": "example" }, { "in": "query", "name": "to", "required": false, "schema": { "type": "object" }, "style": "deepObject", "explode": true, "example": "example" }, { "in": "query", "name": "offset", "required": true, "schema": { "default": 0, "type": "integer", "minimum": 0, "maximum": 9007199254740991 }, "example": 1 }, { "in": "query", "name": "limit", "required": true, "schema": { "default": 50, "type": "integer", "minimum": 1, "maximum": 100 }, "example": 1 }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns time-off requests.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/timeOffRequestsResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "isoDateSchema": { "type": "string", "format": "date" }, "listPaginationSchema": { "type": "object", "properties": { "offset": { "type": "integer", "minimum": 0 }, "limit": { "type": "integer", "minimum": 1, "maximum": 100 }, "total": { "type": "integer", "minimum": 0 }, "hasMore": { "type": "boolean" }, "totalIsEstimate": { "type": "boolean" } }, "required": [ "offset", "limit", "total", "hasMore" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "personSummarySchema": { "type": "object", "properties": { "employeeId": { "type": "string", "format": "uuid" }, "userId": { "type": [ "string", "null" ], "format": "uuid" }, "displayName": { "type": "string" }, "avatarUrl": { "type": [ "string", "null" ] }, "jobTitle": { "type": [ "string", "null" ] } }, "required": [ "employeeId", "userId", "displayName", "avatarUrl", "jobTitle" ] }, "timeOffCategorySchema": { "type": "string", "enum": [ "vacation", "sick", "special", "unpaid", "parental", "compensatory" ] }, "timeOffPartialDaySchema": { "type": "string", "enum": [ "full", "morning", "afternoon", "hours" ] }, "timeOffRequestSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "employeeId": { "type": "string", "format": "uuid" }, "employee": { "$ref": "#/components/schemas/personSummarySchema" }, "timeOffTypeId": { "type": [ "string", "null" ], "format": "uuid" }, "typeName": { "type": "string" }, "typeCategory": { "anyOf": [ { "$ref": "#/components/schemas/timeOffCategorySchema" }, { "type": "null" } ] }, "typeColor": { "type": [ "string", "null" ] }, "startsOn": { "$ref": "#/components/schemas/isoDateSchema" }, "endsOn": { "$ref": "#/components/schemas/isoDateSchema" }, "startPart": { "$ref": "#/components/schemas/timeOffPartialDaySchema" }, "endPart": { "$ref": "#/components/schemas/timeOffPartialDaySchema" }, "startTime": { "type": [ "string", "null" ] }, "endTime": { "type": [ "string", "null" ] }, "hours": { "type": "number" }, "status": { "$ref": "#/components/schemas/timeOffRequestStatusSchema" }, "note": { "type": [ "string", "null" ] }, "hasAttachment": { "type": "boolean" }, "decidedBy": { "type": [ "string", "null" ], "format": "uuid" }, "decidedAt": { "type": [ "string", "null" ] }, "decisionNote": { "type": [ "string", "null" ] }, "cancelledAt": { "type": [ "string", "null" ] }, "createdAt": { "type": "string" }, "needsRecalculation": { "type": "boolean" }, "canDecide": { "type": "boolean" }, "masked": { "type": "boolean" }, "deductsBalance": { "type": "boolean" }, "balanceAfterApproval": { "type": [ "number", "null" ] }, "allowedNegativeHours": { "type": [ "number", "null" ] } }, "required": [ "id", "employeeId", "employee", "timeOffTypeId", "typeName", "typeCategory", "typeColor", "startsOn", "endsOn", "startPart", "endPart", "startTime", "endTime", "hours", "status", "note", "hasAttachment", "decidedBy", "decidedAt", "decisionNote", "cancelledAt", "createdAt", "needsRecalculation", "canDecide", "masked", "deductsBalance", "balanceAfterApproval", "allowedNegativeHours" ] }, "timeOffRequestStatusSchema": { "type": "string", "enum": [ "pending", "approved", "rejected", "cancelled" ] }, "timeOffRequestsResponseSchema": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/timeOffRequestSchema" } }, "pagination": { "$ref": "#/components/schemas/listPaginationSchema" } }, "required": [ "data", "pagination" ] } } ``` # Request time off. (/openapi/createTimeOffRequest) Create a time-off request in hours. It is routed to the employee's manager for approval. ## POST /v1/organizations/{organizationId}/time-off-requests ```json { "operationId": "createTimeOffRequest", "summary": "Request time off.", "description": "Create a time-off request in hours. It is routed to the employee's manager for approval.", "tags": [ "People" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/timeOffRequestInputSchema" } } }, "description": "The time-off request." }, "responses": { "201": { "description": "Returns the created request.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/timeOffRequestSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "clockTimeSchema": { "type": "string", "pattern": "^([01]\\d|2[0-3]):[0-5]\\d(:[0-5]\\d)?$" }, "isoDateSchema": { "type": "string", "format": "date" }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "personSummarySchema": { "type": "object", "properties": { "employeeId": { "type": "string", "format": "uuid" }, "userId": { "type": [ "string", "null" ], "format": "uuid" }, "displayName": { "type": "string" }, "avatarUrl": { "type": [ "string", "null" ] }, "jobTitle": { "type": [ "string", "null" ] } }, "required": [ "employeeId", "userId", "displayName", "avatarUrl", "jobTitle" ] }, "timeOffCategorySchema": { "type": "string", "enum": [ "vacation", "sick", "special", "unpaid", "parental", "compensatory" ] }, "timeOffPartialDaySchema": { "type": "string", "enum": [ "full", "morning", "afternoon", "hours" ] }, "timeOffRequestInputSchema": { "type": "object", "properties": { "employeeId": { "type": "string", "format": "uuid" }, "timeOffTypeId": { "type": "string", "format": "uuid" }, "startsOn": { "$ref": "#/components/schemas/isoDateSchema" }, "endsOn": { "$ref": "#/components/schemas/isoDateSchema" }, "startPart": { "type": "string", "enum": [ "full", "morning", "afternoon", "hours" ], "default": "full" }, "endPart": { "type": "string", "enum": [ "full", "morning", "afternoon", "hours" ], "default": "full" }, "startTime": { "anyOf": [ { "$ref": "#/components/schemas/clockTimeSchema" }, { "type": "null" } ] }, "endTime": { "anyOf": [ { "$ref": "#/components/schemas/clockTimeSchema" }, { "type": "null" } ] }, "note": { "type": [ "string", "null" ], "maxLength": 2000 }, "acknowledgeWarnings": { "type": "boolean" } }, "required": [ "timeOffTypeId", "startsOn", "endsOn", "startPart", "endPart", "note" ] }, "timeOffRequestSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "employeeId": { "type": "string", "format": "uuid" }, "employee": { "$ref": "#/components/schemas/personSummarySchema" }, "timeOffTypeId": { "type": [ "string", "null" ], "format": "uuid" }, "typeName": { "type": "string" }, "typeCategory": { "anyOf": [ { "$ref": "#/components/schemas/timeOffCategorySchema" }, { "type": "null" } ] }, "typeColor": { "type": [ "string", "null" ] }, "startsOn": { "$ref": "#/components/schemas/isoDateSchema" }, "endsOn": { "$ref": "#/components/schemas/isoDateSchema" }, "startPart": { "$ref": "#/components/schemas/timeOffPartialDaySchema" }, "endPart": { "$ref": "#/components/schemas/timeOffPartialDaySchema" }, "startTime": { "type": [ "string", "null" ] }, "endTime": { "type": [ "string", "null" ] }, "hours": { "type": "number" }, "status": { "$ref": "#/components/schemas/timeOffRequestStatusSchema" }, "note": { "type": [ "string", "null" ] }, "hasAttachment": { "type": "boolean" }, "decidedBy": { "type": [ "string", "null" ], "format": "uuid" }, "decidedAt": { "type": [ "string", "null" ] }, "decisionNote": { "type": [ "string", "null" ] }, "cancelledAt": { "type": [ "string", "null" ] }, "createdAt": { "type": "string" }, "needsRecalculation": { "type": "boolean" }, "canDecide": { "type": "boolean" }, "masked": { "type": "boolean" }, "deductsBalance": { "type": "boolean" }, "balanceAfterApproval": { "type": [ "number", "null" ] }, "allowedNegativeHours": { "type": [ "number", "null" ] } }, "required": [ "id", "employeeId", "employee", "timeOffTypeId", "typeName", "typeCategory", "typeColor", "startsOn", "endsOn", "startPart", "endPart", "startTime", "endTime", "hours", "status", "note", "hasAttachment", "decidedBy", "decidedAt", "decisionNote", "cancelledAt", "createdAt", "needsRecalculation", "canDecide", "masked", "deductsBalance", "balanceAfterApproval", "allowedNegativeHours" ] }, "timeOffRequestStatusSchema": { "type": "string", "enum": [ "pending", "approved", "rejected", "cancelled" ] } } ``` # Get an employee. (/openapi/getEmployee) Return one employee. Private details are never included. ## GET /v1/organizations/{organizationId}/employees/{employeeId} ```json { "operationId": "getEmployee", "summary": "Get an employee.", "description": "Return one employee. Private details are never included.", "tags": [ "People" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "employeeId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns the employee.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/employeeSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "employeeKindSchema": { "type": "string", "enum": [ "employee", "contractor", "intern" ] }, "employeeSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "organizationId": { "type": "string", "format": "uuid" }, "userId": { "type": [ "string", "null" ], "format": "uuid" }, "kind": { "$ref": "#/components/schemas/employeeKindSchema" }, "status": { "$ref": "#/components/schemas/employeeStatusSchema" }, "firstName": { "type": "string" }, "lastName": { "type": [ "string", "null" ] }, "displayName": { "type": "string" }, "workEmail": { "type": [ "string", "null" ] }, "jobTitle": { "type": [ "string", "null" ] }, "departmentTeamId": { "anyOf": [ { "$ref": "#/components/schemas/teamIdSchema" }, { "type": "null" } ] }, "departmentTeamName": { "type": [ "string", "null" ] }, "reportsToEmployeeId": { "type": [ "string", "null" ], "format": "uuid" }, "manager": { "anyOf": [ { "$ref": "#/components/schemas/personSummarySchema" }, { "type": "null" } ] }, "startDate": { "type": "object" }, "endDate": { "type": "object" }, "timezone": { "type": [ "string", "null" ] }, "companyName": { "type": [ "string", "null" ] }, "kvk": { "type": [ "string", "null" ] }, "vatId": { "type": [ "string", "null" ] }, "showBirthday": { "type": "boolean" }, "showWorkAnniversary": { "type": "boolean" }, "scheduleCycleAnchor": { "type": "object" }, "avatarUrl": { "type": [ "string", "null" ] }, "hasAppAccess": { "type": "boolean" }, "accountDisabled": { "type": "boolean" }, "archivedAt": { "type": [ "string", "null" ] }, "createdAt": { "type": "string" }, "updatedAt": { "type": "string" } }, "required": [ "id", "organizationId", "userId", "kind", "status", "firstName", "lastName", "displayName", "workEmail", "jobTitle", "departmentTeamId", "departmentTeamName", "reportsToEmployeeId", "manager", "startDate", "endDate", "timezone", "companyName", "kvk", "vatId", "showBirthday", "showWorkAnniversary", "scheduleCycleAnchor", "avatarUrl", "hasAppAccess", "accountDisabled", "archivedAt", "createdAt", "updatedAt" ] }, "employeeStatusSchema": { "type": "string", "enum": [ "onboarding", "active", "offboarding", "ended" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "personSummarySchema": { "type": "object", "properties": { "employeeId": { "type": "string", "format": "uuid" }, "userId": { "type": [ "string", "null" ], "format": "uuid" }, "displayName": { "type": "string" }, "avatarUrl": { "type": [ "string", "null" ] }, "jobTitle": { "type": [ "string", "null" ] } }, "required": [ "employeeId", "userId", "displayName", "avatarUrl", "jobTitle" ] }, "teamIdSchema": { "type": "integer", "exclusiveMinimum": 0 } } ``` # Update an employee. (/openapi/updateEmployee) Update employment fields such as job title, department, manager, and status. ## PATCH /v1/organizations/{organizationId}/employees/{employeeId} ```json { "operationId": "updateEmployee", "summary": "Update an employee.", "description": "Update employment fields such as job title, department, manager, and status.", "tags": [ "People" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "employeeId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/employeeUpdateSchema" } } }, "description": "The fields to change." }, "responses": { "200": { "description": "Returns the updated employee.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/employeeSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "employeeKindSchema": { "type": "string", "enum": [ "employee", "contractor", "intern" ] }, "employeeSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "organizationId": { "type": "string", "format": "uuid" }, "userId": { "type": [ "string", "null" ], "format": "uuid" }, "kind": { "$ref": "#/components/schemas/employeeKindSchema" }, "status": { "$ref": "#/components/schemas/employeeStatusSchema" }, "firstName": { "type": "string" }, "lastName": { "type": [ "string", "null" ] }, "displayName": { "type": "string" }, "workEmail": { "type": [ "string", "null" ] }, "jobTitle": { "type": [ "string", "null" ] }, "departmentTeamId": { "anyOf": [ { "$ref": "#/components/schemas/teamIdSchema" }, { "type": "null" } ] }, "departmentTeamName": { "type": [ "string", "null" ] }, "reportsToEmployeeId": { "type": [ "string", "null" ], "format": "uuid" }, "manager": { "anyOf": [ { "$ref": "#/components/schemas/personSummarySchema" }, { "type": "null" } ] }, "startDate": { "type": "object" }, "endDate": { "type": "object" }, "timezone": { "type": [ "string", "null" ] }, "companyName": { "type": [ "string", "null" ] }, "kvk": { "type": [ "string", "null" ] }, "vatId": { "type": [ "string", "null" ] }, "showBirthday": { "type": "boolean" }, "showWorkAnniversary": { "type": "boolean" }, "scheduleCycleAnchor": { "type": "object" }, "avatarUrl": { "type": [ "string", "null" ] }, "hasAppAccess": { "type": "boolean" }, "accountDisabled": { "type": "boolean" }, "archivedAt": { "type": [ "string", "null" ] }, "createdAt": { "type": "string" }, "updatedAt": { "type": "string" } }, "required": [ "id", "organizationId", "userId", "kind", "status", "firstName", "lastName", "displayName", "workEmail", "jobTitle", "departmentTeamId", "departmentTeamName", "reportsToEmployeeId", "manager", "startDate", "endDate", "timezone", "companyName", "kvk", "vatId", "showBirthday", "showWorkAnniversary", "scheduleCycleAnchor", "avatarUrl", "hasAppAccess", "accountDisabled", "archivedAt", "createdAt", "updatedAt" ] }, "employeeStatusSchema": { "type": "string", "enum": [ "onboarding", "active", "offboarding", "ended" ] }, "employeeUpdateSchema": { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "employee", "contractor", "intern" ], "default": "employee" }, "status": { "allOf": [ { "$ref": "#/components/schemas/employeeStatusSchema" } ] }, "firstName": { "type": "string", "minLength": 1, "maxLength": 120 }, "lastName": { "type": [ "string", "null" ], "maxLength": 120 }, "workEmail": { "anyOf": [ { "type": [ "string", "null" ], "format": "email", "maxLength": 320 }, { "type": "string", "enum": [ "" ] } ] }, "jobTitle": { "type": [ "string", "null" ], "maxLength": 160 }, "departmentTeamId": { "anyOf": [ { "$ref": "#/components/schemas/teamIdSchema" }, { "type": "null" } ] }, "reportsToEmployeeId": { "type": [ "string", "null" ], "format": "uuid" }, "startDate": { "type": "object" }, "endDate": { "type": "object" }, "timezone": { "type": [ "string", "null" ], "maxLength": 64 }, "companyName": { "type": [ "string", "null" ], "maxLength": 200 }, "kvk": { "type": [ "string", "null" ], "maxLength": 32 }, "vatId": { "type": [ "string", "null" ], "maxLength": 32 }, "showBirthday": { "type": "boolean" }, "showWorkAnniversary": { "type": "boolean" }, "archived": { "type": "boolean" } }, "required": [] }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "personSummarySchema": { "type": "object", "properties": { "employeeId": { "type": "string", "format": "uuid" }, "userId": { "type": [ "string", "null" ], "format": "uuid" }, "displayName": { "type": "string" }, "avatarUrl": { "type": [ "string", "null" ] }, "jobTitle": { "type": [ "string", "null" ] } }, "required": [ "employeeId", "userId", "displayName", "avatarUrl", "jobTitle" ] }, "teamIdSchema": { "type": "integer", "exclusiveMinimum": 0 } } ``` # Validate a promotion. (/openapi/validatePromotion) Check whether a promotion or code can be used by a customer on a quote, order, or invoice, without redeeming it. Pass the subtotal and quantity to check minimums. ## POST /v1/organizations/{organizationId}/promotions/validate ```json { "operationId": "validatePromotion", "summary": "Validate a promotion.", "description": "Check whether a promotion or code can be used by a customer on a quote, order, or invoice, without redeeming it. Pass the subtotal and quantity to check minimums.", "tags": [ "Promotions" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/promotionEvaluationInputSchema" } } }, "description": "The promotion or code and the customer." }, "responses": { "200": { "description": "Returns whether the promotion applies, the reason when not, and the promotion.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/promotionEvaluationSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "numeric": { "type": "number" }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "promotionAppliesToSchema": { "type": "string", "enum": [ "document", "lines" ] }, "promotionEvaluationInputSchema": { "type": "object", "properties": { "subjectType": { "$ref": "#/components/schemas/promotionSubjectTypeSchema" }, "customerId": { "type": "integer", "exclusiveMinimum": 0 }, "promotionId": { "type": [ "string", "null" ], "format": "uuid" }, "code": { "type": [ "string", "null" ], "minLength": 1, "maxLength": 40 }, "subtotal": { "type": [ "number", "null" ] }, "quantity": { "type": [ "number", "null" ] }, "subjectId": { "type": [ "string", "null" ], "format": "uuid" } }, "required": [ "subjectType", "customerId" ] }, "promotionEvaluationSchema": { "type": "object", "properties": { "valid": { "type": "boolean" }, "reason": { "anyOf": [ { "$ref": "#/components/schemas/promotionRejectionSchema" }, { "type": "null" } ] }, "promotion": { "anyOf": [ { "$ref": "#/components/schemas/promotionSummarySchema" }, { "type": "null" } ] } }, "required": [ "valid", "reason", "promotion" ] }, "promotionKindSchema": { "type": "string", "enum": [ "percentage", "fixed_amount", "free_shipping" ] }, "promotionRejectionSchema": { "type": "string", "enum": [ "not_found", "inactive", "not_started", "expired", "surface", "ineligible", "min_subtotal", "min_quantity", "limit_total", "limit_customer" ] }, "promotionSubjectTypeSchema": { "type": "string", "enum": [ "quote", "order", "invoice" ] }, "promotionSummarySchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "name": { "type": "string" }, "code": { "type": [ "string", "null" ] }, "kind": { "$ref": "#/components/schemas/promotionKindSchema" }, "value": { "$ref": "#/components/schemas/numeric" }, "currency": { "type": "string" }, "appliesTo": { "$ref": "#/components/schemas/promotionAppliesToSchema" } }, "required": [ "id", "name", "code", "kind", "value", "currency", "appliesTo" ] } } ``` # List purchase orders. (/openapi/listPurchaseOrders) Return purchase orders to suppliers, newest first. ## GET /v1/organizations/{organizationId}/purchase-orders ```json { "operationId": "listPurchaseOrders", "summary": "List purchase orders.", "description": "Return purchase orders to suppliers, newest first.", "tags": [ "Purchase orders" ], "parameters": [ { "in": "query", "name": "q", "required": false, "schema": { "type": "string", "maxLength": 200 }, "example": "example" }, { "in": "query", "name": "status", "required": false, "schema": { "allOf": [ { "$ref": "#/components/schemas/purchaseOrderStatusSchema" } ] }, "example": "example" }, { "in": "query", "name": "supplierId", "required": false, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "query", "name": "offset", "required": true, "schema": { "default": 0, "type": "integer", "minimum": 0, "maximum": 9007199254740991 }, "example": 1 }, { "in": "query", "name": "limit", "required": true, "schema": { "default": 50, "type": "integer", "minimum": 1, "maximum": 100 }, "example": 1 }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns purchase orders.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/purchaseOrdersResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "isoDateSchema": { "type": "string", "format": "date" }, "listPaginationSchema": { "type": "object", "properties": { "offset": { "type": "integer", "minimum": 0 }, "limit": { "type": "integer", "minimum": 1, "maximum": 100 }, "total": { "type": "integer", "minimum": 0 }, "hasMore": { "type": "boolean" }, "totalIsEstimate": { "type": "boolean" } }, "required": [ "offset", "limit", "total", "hasMore" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "purchaseOrderStatusSchema": { "type": "string", "enum": [ "draft", "sent", "partially_received", "received", "closed", "cancelled" ] }, "purchaseOrderSummarySchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "number": { "type": "integer" }, "formattedNumber": { "type": "string" }, "supplier": { "$ref": "#/components/schemas/purchaseOrderSupplierSchema" }, "status": { "$ref": "#/components/schemas/purchaseOrderStatusSchema" }, "currency": { "type": "string" }, "exchangeRate": { "type": "number" }, "expectedAt": { "anyOf": [ { "$ref": "#/components/schemas/isoDateSchema" }, { "type": "null" } ] }, "shipToLocationId": { "type": [ "string", "null" ], "format": "uuid" }, "shipToLocationName": { "type": [ "string", "null" ] }, "supplierReference": { "type": [ "string", "null" ] }, "templateId": { "type": [ "string", "null" ], "format": "uuid" }, "subtotalExcl": { "type": "number" }, "vatTotal": { "type": "number" }, "totalIncl": { "type": "number" }, "lineCount": { "type": "integer" }, "receivedRatio": { "type": "number" }, "sentAt": { "type": [ "string", "null" ] }, "receivedAt": { "type": [ "string", "null" ] }, "closedAt": { "type": [ "string", "null" ] }, "cancelledAt": { "type": [ "string", "null" ] }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "createdAt": { "type": "string" }, "updatedAt": { "type": "string" } }, "required": [ "id", "number", "formattedNumber", "supplier", "status", "currency", "exchangeRate", "expectedAt", "shipToLocationId", "shipToLocationName", "supplierReference", "templateId", "subtotalExcl", "vatTotal", "totalIncl", "lineCount", "receivedRatio", "sentAt", "receivedAt", "closedAt", "cancelledAt", "createdBy", "createdAt", "updatedAt" ] }, "purchaseOrderSupplierSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "name": { "type": "string" }, "orderingEmail": { "type": [ "string", "null" ] }, "email": { "type": [ "string", "null" ] } }, "required": [ "id", "name", "orderingEmail", "email" ] }, "purchaseOrdersResponseSchema": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/purchaseOrderSummarySchema" } }, "pagination": { "$ref": "#/components/schemas/listPaginationSchema" } }, "required": [ "data", "pagination" ] } } ``` # Create a purchase order. (/openapi/createPurchaseOrder) Create a draft purchase order for a supplier. Sending and emailing it happens in the app. ## POST /v1/organizations/{organizationId}/purchase-orders ```json { "operationId": "createPurchaseOrder", "summary": "Create a purchase order.", "description": "Create a draft purchase order for a supplier. Sending and emailing it happens in the app.", "tags": [ "Purchase orders" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/purchaseOrderInputSchema" } } }, "description": "The supplier and variant lines." }, "responses": { "201": { "description": "Returns the draft purchase order.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/purchaseOrderSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "currencySchema": { "type": "string", "pattern": "^[A-Z]{3}$" }, "isoDateSchema": { "type": "string", "format": "date" }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "purchaseOrderInputSchema": { "type": "object", "properties": { "supplierId": { "type": "string", "format": "uuid" }, "currency": { "allOf": [ { "$ref": "#/components/schemas/currencySchema" } ] }, "exchangeRate": { "type": "number", "exclusiveMinimum": 0, "maximum": 1000000 }, "expectedAt": { "anyOf": [ { "$ref": "#/components/schemas/isoDateSchema" }, { "type": "null" } ] }, "shipToLocationId": { "type": [ "string", "null" ], "format": "uuid" }, "supplierReference": { "type": [ "string", "null" ], "maxLength": 120 }, "templateId": { "type": [ "string", "null" ], "format": "uuid" }, "notes": { "type": [ "string", "null" ], "maxLength": 4000 }, "supplierNotes": { "type": [ "string", "null" ], "maxLength": 4000 }, "lines": { "type": "array", "items": { "$ref": "#/components/schemas/purchaseOrderLineInputSchema" }, "maxItems": 500 } }, "required": [ "supplierId", "supplierReference", "notes", "supplierNotes", "lines" ] }, "purchaseOrderLineInputSchema": { "type": "object", "properties": { "variantId": { "type": "string", "format": "uuid" }, "productSupplierLinkId": { "type": [ "string", "null" ], "format": "uuid" }, "supplierSku": { "type": [ "string", "null" ], "maxLength": 120 }, "title": { "type": [ "string", "null" ], "maxLength": 300 }, "quantityOrdered": { "type": "number", "exclusiveMinimum": 0, "maximum": 1000000 }, "unitCost": { "type": "number", "minimum": 0, "maximum": 100000000 }, "vatRate": { "type": "number", "minimum": 0, "maximum": 100 }, "expectedAt": { "anyOf": [ { "$ref": "#/components/schemas/isoDateSchema" }, { "type": "null" } ] } }, "required": [ "variantId", "supplierSku", "title", "quantityOrdered", "unitCost", "vatRate" ] }, "purchaseOrderLineSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "productId": { "type": "string", "format": "uuid" }, "variantId": { "type": "string", "format": "uuid" }, "productName": { "type": "string" }, "variantTitle": { "type": [ "string", "null" ] }, "sku": { "type": [ "string", "null" ] }, "productSupplierLinkId": { "type": [ "string", "null" ], "format": "uuid" }, "supplierSku": { "type": [ "string", "null" ] }, "title": { "type": "string" }, "quantityOrdered": { "type": "number" }, "quantityReceived": { "type": "number" }, "quantityCancelled": { "type": "number" }, "quantityOpen": { "type": "number" }, "unitCost": { "type": "number" }, "vatRate": { "type": "number" }, "expectedAt": { "anyOf": [ { "$ref": "#/components/schemas/isoDateSchema" }, { "type": "null" } ] }, "position": { "type": "integer" } }, "required": [ "id", "productId", "variantId", "productName", "variantTitle", "sku", "productSupplierLinkId", "supplierSku", "title", "quantityOrdered", "quantityReceived", "quantityCancelled", "quantityOpen", "unitCost", "vatRate", "expectedAt", "position" ] }, "purchaseOrderSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "number": { "type": "integer" }, "formattedNumber": { "type": "string" }, "supplier": { "$ref": "#/components/schemas/purchaseOrderSupplierSchema" }, "status": { "$ref": "#/components/schemas/purchaseOrderStatusSchema" }, "currency": { "type": "string" }, "exchangeRate": { "type": "number" }, "expectedAt": { "anyOf": [ { "$ref": "#/components/schemas/isoDateSchema" }, { "type": "null" } ] }, "shipToLocationId": { "type": [ "string", "null" ], "format": "uuid" }, "shipToLocationName": { "type": [ "string", "null" ] }, "supplierReference": { "type": [ "string", "null" ] }, "templateId": { "type": [ "string", "null" ], "format": "uuid" }, "subtotalExcl": { "type": "number" }, "vatTotal": { "type": "number" }, "totalIncl": { "type": "number" }, "lineCount": { "type": "integer" }, "receivedRatio": { "type": "number" }, "sentAt": { "type": [ "string", "null" ] }, "receivedAt": { "type": [ "string", "null" ] }, "closedAt": { "type": [ "string", "null" ] }, "cancelledAt": { "type": [ "string", "null" ] }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "createdAt": { "type": "string" }, "updatedAt": { "type": "string" }, "notes": { "type": [ "string", "null" ] }, "supplierNotes": { "type": [ "string", "null" ] }, "lines": { "type": "array", "items": { "$ref": "#/components/schemas/purchaseOrderLineSchema" } } }, "required": [ "id", "number", "formattedNumber", "supplier", "status", "currency", "exchangeRate", "expectedAt", "shipToLocationId", "shipToLocationName", "supplierReference", "templateId", "subtotalExcl", "vatTotal", "totalIncl", "lineCount", "receivedRatio", "sentAt", "receivedAt", "closedAt", "cancelledAt", "createdBy", "createdAt", "updatedAt", "notes", "supplierNotes", "lines" ] }, "purchaseOrderStatusSchema": { "type": "string", "enum": [ "draft", "sent", "partially_received", "received", "closed", "cancelled" ] }, "purchaseOrderSupplierSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "name": { "type": "string" }, "orderingEmail": { "type": [ "string", "null" ] }, "email": { "type": [ "string", "null" ] } }, "required": [ "id", "name", "orderingEmail", "email" ] } } ``` # Get a purchase order. (/openapi/getPurchaseOrder) Return one purchase order with its lines and what was received so far. ## GET /v1/organizations/{organizationId}/purchase-orders/{purchaseOrderId} ```json { "operationId": "getPurchaseOrder", "summary": "Get a purchase order.", "description": "Return one purchase order with its lines and what was received so far.", "tags": [ "Purchase orders" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "purchaseOrderId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns the purchase order.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/purchaseOrderSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "isoDateSchema": { "type": "string", "format": "date" }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "purchaseOrderLineSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "productId": { "type": "string", "format": "uuid" }, "variantId": { "type": "string", "format": "uuid" }, "productName": { "type": "string" }, "variantTitle": { "type": [ "string", "null" ] }, "sku": { "type": [ "string", "null" ] }, "productSupplierLinkId": { "type": [ "string", "null" ], "format": "uuid" }, "supplierSku": { "type": [ "string", "null" ] }, "title": { "type": "string" }, "quantityOrdered": { "type": "number" }, "quantityReceived": { "type": "number" }, "quantityCancelled": { "type": "number" }, "quantityOpen": { "type": "number" }, "unitCost": { "type": "number" }, "vatRate": { "type": "number" }, "expectedAt": { "anyOf": [ { "$ref": "#/components/schemas/isoDateSchema" }, { "type": "null" } ] }, "position": { "type": "integer" } }, "required": [ "id", "productId", "variantId", "productName", "variantTitle", "sku", "productSupplierLinkId", "supplierSku", "title", "quantityOrdered", "quantityReceived", "quantityCancelled", "quantityOpen", "unitCost", "vatRate", "expectedAt", "position" ] }, "purchaseOrderSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "number": { "type": "integer" }, "formattedNumber": { "type": "string" }, "supplier": { "$ref": "#/components/schemas/purchaseOrderSupplierSchema" }, "status": { "$ref": "#/components/schemas/purchaseOrderStatusSchema" }, "currency": { "type": "string" }, "exchangeRate": { "type": "number" }, "expectedAt": { "anyOf": [ { "$ref": "#/components/schemas/isoDateSchema" }, { "type": "null" } ] }, "shipToLocationId": { "type": [ "string", "null" ], "format": "uuid" }, "shipToLocationName": { "type": [ "string", "null" ] }, "supplierReference": { "type": [ "string", "null" ] }, "templateId": { "type": [ "string", "null" ], "format": "uuid" }, "subtotalExcl": { "type": "number" }, "vatTotal": { "type": "number" }, "totalIncl": { "type": "number" }, "lineCount": { "type": "integer" }, "receivedRatio": { "type": "number" }, "sentAt": { "type": [ "string", "null" ] }, "receivedAt": { "type": [ "string", "null" ] }, "closedAt": { "type": [ "string", "null" ] }, "cancelledAt": { "type": [ "string", "null" ] }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "createdAt": { "type": "string" }, "updatedAt": { "type": "string" }, "notes": { "type": [ "string", "null" ] }, "supplierNotes": { "type": [ "string", "null" ] }, "lines": { "type": "array", "items": { "$ref": "#/components/schemas/purchaseOrderLineSchema" } } }, "required": [ "id", "number", "formattedNumber", "supplier", "status", "currency", "exchangeRate", "expectedAt", "shipToLocationId", "shipToLocationName", "supplierReference", "templateId", "subtotalExcl", "vatTotal", "totalIncl", "lineCount", "receivedRatio", "sentAt", "receivedAt", "closedAt", "cancelledAt", "createdBy", "createdAt", "updatedAt", "notes", "supplierNotes", "lines" ] }, "purchaseOrderStatusSchema": { "type": "string", "enum": [ "draft", "sent", "partially_received", "received", "closed", "cancelled" ] }, "purchaseOrderSupplierSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "name": { "type": "string" }, "orderingEmail": { "type": [ "string", "null" ] }, "email": { "type": [ "string", "null" ] } }, "required": [ "id", "name", "orderingEmail", "email" ] } } ``` # Receive purchase order lines. (/openapi/receivePurchaseOrder) Book received quantities into stock. Average cost updates and waiting backorders are filled. Returns 409 `APPROVAL_REQUIRED` with an `approvalId` when an approval policy wants a decision first. ## POST /v1/organizations/{organizationId}/purchase-orders/{purchaseOrderId}/receive ```json { "operationId": "receivePurchaseOrder", "summary": "Receive purchase order lines.", "description": "Book received quantities into stock. Average cost updates and waiting backorders are filled. Returns 409 `APPROVAL_REQUIRED` with an `approvalId` when an approval policy wants a decision first.", "tags": [ "Purchase orders" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "purchaseOrderId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/purchaseOrderReceiptInputSchema" } } }, "description": "The received quantity per line." }, "responses": { "200": { "description": "Returns the new status and what was received.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/purchaseOrderReceiptResultSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "isoDateSchema": { "type": "string", "format": "date" }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "purchaseOrderReceiptInputSchema": { "type": "object", "properties": { "lines": { "type": "array", "items": { "$ref": "#/components/schemas/purchaseOrderReceiptLineSchema" }, "minItems": 1, "maxItems": 500 }, "locationId": { "type": [ "string", "null" ], "format": "uuid" }, "allowOverReceipt": { "type": "boolean" } }, "required": [ "lines" ] }, "purchaseOrderReceiptLineSchema": { "type": "object", "properties": { "lineId": { "type": "string", "format": "uuid" }, "quantity": { "type": "number", "exclusiveMinimum": 0, "maximum": 1000000 }, "lotNumber": { "type": [ "string", "null" ], "maxLength": 120 }, "serialNumber": { "type": [ "string", "null" ], "maxLength": 120 }, "expiresOn": { "anyOf": [ { "$ref": "#/components/schemas/isoDateSchema" }, { "type": "null" } ] }, "binLocation": { "type": [ "string", "null" ], "maxLength": 120 }, "locationId": { "type": [ "string", "null" ], "format": "uuid" } }, "required": [ "lineId", "quantity", "lotNumber", "serialNumber", "binLocation" ] }, "purchaseOrderReceiptResultSchema": { "type": "object", "properties": { "status": { "$ref": "#/components/schemas/purchaseOrderStatusSchema" }, "received": { "type": "number" }, "variantIds": { "type": "array", "items": { "type": "string", "format": "uuid" } } }, "required": [ "status", "received", "variantIds" ] }, "purchaseOrderStatusSchema": { "type": "string", "enum": [ "draft", "sent", "partially_received", "received", "closed", "cancelled" ] } } ``` # List quotes. (/openapi/listQuotes) Return quotes for an organization the authenticated user can access. ## GET /v1/organizations/{organizationId}/quotes ```json { "operationId": "listQuotes", "summary": "List quotes.", "description": "Return quotes for an organization the authenticated user can access.", "tags": [ "Quotes" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns paginated quotes.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/quotesListResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "organizationIdSchema": { "type": "string", "format": "uuid" }, "quotesListResponseSchema": { "type": "object" } } ``` # Create a quote. (/openapi/createQuote) Create a quote for the specified organization. ## POST /v1/organizations/{organizationId}/quotes ```json { "operationId": "createQuote", "summary": "Create a quote.", "description": "Create a quote for the specified organization.", "tags": [ "Quotes" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/quoteInputSchema" } } }, "description": "The quote payload.", "required": true }, "responses": { "201": { "description": "Returns the created quote.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/quoteSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "discountDescriptionSchema": { "type": [ "string", "null" ], "maxLength": 500 }, "discountKindSchema": { "type": "string", "enum": [ "none", "percentage", "fixed" ] }, "documentVatRegimeSchema": { "type": "string", "enum": [ "standard", "reverse_charge", "exempt", "kor", "margin_scheme" ] }, "isoDateOrNull": { "type": [ "string", "null" ], "pattern": "^\\d{4}-\\d{2}-\\d{2}$" }, "optionalUrl": { "type": [ "string", "null" ], "maxLength": 2000 }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "quoteCurrencyCodeSchema": { "type": "string", "pattern": "^[A-Z]{3}$" }, "quoteDepositKindSchema": { "type": "string", "enum": [ "none", "percent", "fixed" ] }, "quoteDisplayNumberSchema": { "type": "string", "minLength": 1, "maxLength": 40 }, "quoteIdSchema": { "type": "string", "format": "uuid" }, "quoteInputSchema": { "type": "object", "properties": { "customerId": { "type": "integer", "exclusiveMinimum": 0 }, "title": { "type": "string", "minLength": 1, "maxLength": 240 }, "quoteTemplateId": { "type": [ "string", "null" ], "format": "uuid" }, "intro": { "type": [ "string", "null" ], "maxLength": 20000 }, "terms": { "type": [ "string", "null" ], "maxLength": 20000 }, "validUntil": { "allOf": [ { "$ref": "#/components/schemas/isoDateOrNull" } ] }, "currency": { "allOf": [ { "$ref": "#/components/schemas/quoteCurrencyCodeSchema" } ] }, "vatInclusive": { "type": "boolean" }, "discountKind": { "allOf": [ { "$ref": "#/components/schemas/discountKindSchema" } ] }, "discountValue": { "type": "number", "minimum": 0 }, "discountDescription": { "allOf": [ { "$ref": "#/components/schemas/discountDescriptionSchema" } ] }, "promotionId": { "type": [ "string", "null" ], "format": "uuid" }, "publicMessage": { "type": [ "string", "null" ], "maxLength": 20000 }, "issueDate": { "allOf": [ { "$ref": "#/components/schemas/isoDateOrNull" } ] }, "vatRegime": { "allOf": [ { "$ref": "#/components/schemas/documentVatRegimeSchema" } ] }, "termsRichText": { "type": [ "string", "null" ], "maxLength": 100000 }, "termsUrl": { "allOf": [ { "$ref": "#/components/schemas/optionalUrl" } ] }, "termsPdfFileId": { "type": [ "string", "null" ], "format": "uuid" }, "termsPdfVersionId": { "type": [ "string", "null" ], "format": "uuid" }, "depositKind": { "allOf": [ { "$ref": "#/components/schemas/quoteDepositKindSchema" } ] }, "depositValue": { "type": "number", "minimum": 0 }, "depositRequiredToAccept": { "type": "boolean" }, "lines": { "type": "array", "items": { "$ref": "#/components/schemas/quoteLineInputSchema" }, "maxItems": 500 }, "linkedTaskIds": { "type": "array", "items": { "type": "string", "format": "uuid" }, "maxItems": 200 } }, "required": [ "customerId", "title" ] }, "quoteLineIdSchema": { "type": "string", "format": "uuid" }, "quoteLineInputSchema": { "type": "object", "properties": { "id": { "allOf": [ { "$ref": "#/components/schemas/quoteLineIdSchema" } ] }, "kind": { "allOf": [ { "$ref": "#/components/schemas/quoteLineKindSchema" } ] }, "lineType": { "anyOf": [ { "$ref": "#/components/schemas/quoteLineTypeSchema" }, { "type": "null" } ] }, "productId": { "type": [ "string", "null" ], "format": "uuid" }, "variantId": { "type": [ "string", "null" ], "format": "uuid" }, "taskId": { "type": [ "string", "null" ], "format": "uuid" }, "title": { "type": "string", "minLength": 1, "maxLength": 500 }, "description": { "type": [ "string", "null" ], "maxLength": 20000 }, "quantity": { "type": "number", "minimum": 0 }, "unit": { "type": [ "string", "null" ], "maxLength": 40 }, "unitPrice": { "type": "number" }, "vatRate": { "type": "number", "minimum": 0, "maximum": 100 }, "discountKind": { "allOf": [ { "$ref": "#/components/schemas/discountKindSchema" } ] }, "discountValue": { "type": "number", "minimum": 0 }, "promotionId": { "type": [ "string", "null" ], "format": "uuid" }, "isOptional": { "type": "boolean" }, "pricingMode": { "allOf": [ { "$ref": "#/components/schemas/quoteLinePricingModeSchema" } ] }, "subscriptionInterval": { "anyOf": [ { "$ref": "#/components/schemas/quoteSubscriptionIntervalSchema" }, { "type": "null" } ] }, "subscriptionPeriods": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 }, "position": { "type": "number" }, "stockMode": { "allOf": [ { "$ref": "#/components/schemas/quoteLineStockModeSchema" } ] } }, "required": [ "title" ] }, "quoteLineKindSchema": { "type": "string", "enum": [ "line", "section_header" ] }, "quoteLinePricingModeSchema": { "type": "string", "enum": [ "unit", "hourly", "subscription", "fixed" ] }, "quoteLineStockModeSchema": { "type": "string", "enum": [ "auto", "none" ] }, "quoteLineTypeSchema": { "type": "string", "enum": [ "product", "labor", "custom" ] }, "quoteNumberPrefixSchema": { "type": "string", "maxLength": 16, "pattern": "^[A-Z0-9-]*$" }, "quoteNumberSchema": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 }, "quoteSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/quoteIdSchema" }, "organizationId": { "type": "string", "format": "uuid" }, "customerId": { "type": "integer", "exclusiveMinimum": 0 }, "number": { "$ref": "#/components/schemas/quoteNumberSchema" }, "numberPrefix": { "$ref": "#/components/schemas/quoteNumberPrefixSchema" }, "displayNumber": { "$ref": "#/components/schemas/quoteDisplayNumberSchema" }, "version": { "type": "integer", "minimum": 1 }, "supersedesQuoteId": { "type": [ "string", "null" ], "format": "uuid" }, "status": { "$ref": "#/components/schemas/quoteStatusSchema" }, "quoteTemplateId": { "type": [ "string", "null" ], "format": "uuid" }, "title": { "type": "string" }, "intro": { "type": [ "string", "null" ] }, "terms": { "type": [ "string", "null" ] }, "validUntil": { "type": [ "string", "null" ] }, "currency": { "$ref": "#/components/schemas/quoteCurrencyCodeSchema" }, "vatInclusive": { "type": "boolean" }, "discountKind": { "$ref": "#/components/schemas/discountKindSchema" }, "discountValue": { "type": "number" }, "discountDescription": { "$ref": "#/components/schemas/discountDescriptionSchema" }, "promotionId": { "type": [ "string", "null" ], "format": "uuid" }, "subtotalExcl": { "type": "number" }, "vatTotal": { "type": "number" }, "totalIncl": { "type": "number" }, "publicMessage": { "type": [ "string", "null" ] }, "sentAt": { "type": [ "string", "null" ], "format": "date-time" }, "acceptedAt": { "type": [ "string", "null" ], "format": "date-time" }, "rejectedAt": { "type": [ "string", "null" ], "format": "date-time" }, "expiredAt": { "type": [ "string", "null" ], "format": "date-time" }, "withdrawnAt": { "type": [ "string", "null" ], "format": "date-time" }, "hasUnpublishedChanges": { "type": "boolean" }, "acceptedByName": { "type": [ "string", "null" ] }, "acceptedByEmail": { "type": [ "string", "null" ] }, "acceptedIp": { "type": [ "string", "null" ] }, "acceptedUserAgent": { "type": [ "string", "null" ] }, "acceptedOptionalLineIds": { "type": "array", "items": { "type": "string", "format": "uuid" } }, "archivedAt": { "type": [ "string", "null" ], "format": "date-time" }, "signatureProvider": { "type": [ "string", "null" ] }, "signatureEnvelopeId": { "type": [ "string", "null" ] }, "signedPdfUrl": { "type": [ "string", "null" ] }, "invoiceId": { "type": [ "string", "null" ], "format": "uuid" }, "issueDate": { "type": [ "string", "null" ] }, "vatRegime": { "$ref": "#/components/schemas/documentVatRegimeSchema" }, "sellerName": { "type": [ "string", "null" ] }, "sellerAddressLine1": { "type": [ "string", "null" ] }, "sellerAddressLine2": { "type": [ "string", "null" ] }, "sellerAddressPostalCode": { "type": [ "string", "null" ] }, "sellerAddressCity": { "type": [ "string", "null" ] }, "sellerAddressCountry": { "type": [ "string", "null" ] }, "sellerEmail": { "type": [ "string", "null" ] }, "sellerPhone": { "type": [ "string", "null" ] }, "sellerKvk": { "type": [ "string", "null" ] }, "sellerBtw": { "type": [ "string", "null" ] }, "sellerIban": { "type": [ "string", "null" ] }, "buyerName": { "type": [ "string", "null" ] }, "buyerAddressLine1": { "type": [ "string", "null" ] }, "buyerAddressLine2": { "type": [ "string", "null" ] }, "buyerAddressPostalCode": { "type": [ "string", "null" ] }, "buyerAddressCity": { "type": [ "string", "null" ] }, "buyerAddressCountry": { "type": [ "string", "null" ] }, "buyerEmail": { "type": [ "string", "null" ] }, "buyerKvk": { "type": [ "string", "null" ] }, "buyerBtw": { "type": [ "string", "null" ] }, "termsRichText": { "type": [ "string", "null" ], "maxLength": 100000 }, "termsUrl": { "type": [ "string", "null" ] }, "termsPdfFileId": { "type": [ "string", "null" ], "format": "uuid" }, "termsPdfVersionId": { "type": [ "string", "null" ], "format": "uuid" }, "depositKind": { "$ref": "#/components/schemas/quoteDepositKindSchema" }, "depositValue": { "type": "number", "minimum": 0 }, "depositRequiredToAccept": { "type": "boolean" }, "changesRequestedAt": { "type": [ "string", "null" ], "format": "date-time" }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "updatedBy": { "type": [ "string", "null" ], "format": "uuid" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "customerId", "number", "numberPrefix", "displayNumber", "version", "supersedesQuoteId", "status", "quoteTemplateId", "title", "intro", "terms", "validUntil", "currency", "vatInclusive", "discountKind", "discountValue", "discountDescription", "promotionId", "subtotalExcl", "vatTotal", "totalIncl", "publicMessage", "sentAt", "acceptedAt", "rejectedAt", "expiredAt", "withdrawnAt", "hasUnpublishedChanges", "acceptedByName", "acceptedByEmail", "acceptedIp", "acceptedUserAgent", "acceptedOptionalLineIds", "archivedAt", "signatureProvider", "signatureEnvelopeId", "signedPdfUrl", "invoiceId", "issueDate", "vatRegime", "sellerName", "sellerAddressLine1", "sellerAddressLine2", "sellerAddressPostalCode", "sellerAddressCity", "sellerAddressCountry", "sellerEmail", "sellerPhone", "sellerKvk", "sellerBtw", "sellerIban", "buyerName", "buyerAddressLine1", "buyerAddressLine2", "buyerAddressPostalCode", "buyerAddressCity", "buyerAddressCountry", "buyerEmail", "buyerKvk", "buyerBtw", "termsRichText", "termsUrl", "termsPdfFileId", "termsPdfVersionId", "depositKind", "depositValue", "depositRequiredToAccept", "changesRequestedAt", "createdBy", "updatedBy", "createdAt", "updatedAt" ] }, "quoteStatusSchema": { "type": "string", "enum": [ "draft", "sent", "accepted", "rejected", "expired", "withdrawn", "changes_requested" ] }, "quoteSubscriptionIntervalSchema": { "type": "string", "enum": [ "weekly", "monthly", "quarterly", "yearly" ] } } ``` # Get a quote. (/openapi/getQuote) Return a quote detail record for the specified organization. ## GET /v1/organizations/{organizationId}/quotes/{quoteId} ```json { "operationId": "getQuote", "summary": "Get a quote.", "description": "Return a quote detail record for the specified organization.", "tags": [ "Quotes" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "quoteId", "required": true, "schema": { "$ref": "#/components/schemas/quoteIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns the requested quote.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/quoteDetailSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "discountDescriptionSchema": { "type": [ "string", "null" ], "maxLength": 500 }, "discountKindSchema": { "type": "string", "enum": [ "none", "percentage", "fixed" ] }, "documentVatRegimeSchema": { "type": "string", "enum": [ "standard", "reverse_charge", "exempt", "kor", "margin_scheme" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "quoteChangeRequestSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "version": { "type": "integer", "minimum": 1 }, "comment": { "type": "string" }, "requestedByName": { "type": "string" }, "requestedByEmail": { "type": "string" }, "createdAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "version", "comment", "requestedByName", "requestedByEmail", "createdAt" ] }, "quoteCurrencyCodeSchema": { "type": "string", "pattern": "^[A-Z]{3}$" }, "quoteDepositKindSchema": { "type": "string", "enum": [ "none", "percent", "fixed" ] }, "quoteDetailSchema": { "type": "object", "properties": { "quote": { "$ref": "#/components/schemas/quoteSchema" }, "changeRequests": { "type": "array", "items": { "$ref": "#/components/schemas/quoteChangeRequestSchema" } }, "lines": { "type": "array", "items": { "$ref": "#/components/schemas/quoteLineSchema" } }, "statusHistory": { "type": "array", "items": { "$ref": "#/components/schemas/quoteStatusHistoryEntrySchema" } }, "versions": { "type": "array", "items": { "$ref": "#/components/schemas/quoteVersionSummarySchema" } }, "linkedAssetIds": { "type": "array", "items": { "type": "integer", "exclusiveMinimum": 0 } }, "linkedTaskIds": { "type": "array", "items": { "type": "string", "format": "uuid" } }, "stockSummary": { "type": "array", "items": { "$ref": "#/components/schemas/quoteStockLineSummarySchema" } } }, "required": [ "quote", "changeRequests", "lines", "statusHistory", "versions", "linkedAssetIds", "linkedTaskIds", "stockSummary" ] }, "quoteDisplayNumberSchema": { "type": "string", "minLength": 1, "maxLength": 40 }, "quoteIdSchema": { "type": "string", "format": "uuid" }, "quoteLineIdSchema": { "type": "string", "format": "uuid" }, "quoteLineKindSchema": { "type": "string", "enum": [ "line", "section_header" ] }, "quoteLinePricingModeSchema": { "type": "string", "enum": [ "unit", "hourly", "subscription", "fixed" ] }, "quoteLineSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/quoteLineIdSchema" }, "quoteId": { "$ref": "#/components/schemas/quoteIdSchema" }, "organizationId": { "type": "string", "format": "uuid" }, "kind": { "$ref": "#/components/schemas/quoteLineKindSchema" }, "lineType": { "anyOf": [ { "$ref": "#/components/schemas/quoteLineTypeSchema" }, { "type": "null" } ] }, "productId": { "type": [ "string", "null" ], "format": "uuid" }, "variantId": { "type": [ "string", "null" ], "format": "uuid" }, "taskId": { "type": [ "string", "null" ], "format": "uuid" }, "title": { "type": "string" }, "description": { "type": [ "string", "null" ] }, "quantity": { "type": "number" }, "unit": { "type": [ "string", "null" ] }, "unitPrice": { "type": "number" }, "vatRate": { "type": "number" }, "discountKind": { "$ref": "#/components/schemas/discountKindSchema" }, "discountValue": { "type": "number" }, "promotionId": { "type": [ "string", "null" ], "format": "uuid" }, "isOptional": { "type": "boolean" }, "pricingMode": { "$ref": "#/components/schemas/quoteLinePricingModeSchema" }, "subscriptionInterval": { "anyOf": [ { "$ref": "#/components/schemas/quoteSubscriptionIntervalSchema" }, { "type": "null" } ] }, "subscriptionPeriods": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 }, "position": { "type": "number" }, "stockMode": { "$ref": "#/components/schemas/quoteLineStockModeSchema" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "quoteId", "organizationId", "kind", "lineType", "productId", "variantId", "taskId", "title", "description", "quantity", "unit", "unitPrice", "vatRate", "discountKind", "discountValue", "promotionId", "isOptional", "pricingMode", "subscriptionInterval", "subscriptionPeriods", "position", "stockMode", "createdAt", "updatedAt" ] }, "quoteLineStockModeSchema": { "type": "string", "enum": [ "auto", "none" ] }, "quoteLineTypeSchema": { "type": "string", "enum": [ "product", "labor", "custom" ] }, "quoteNumberPrefixSchema": { "type": "string", "maxLength": 16, "pattern": "^[A-Z0-9-]*$" }, "quoteNumberSchema": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 }, "quoteSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/quoteIdSchema" }, "organizationId": { "type": "string", "format": "uuid" }, "customerId": { "type": "integer", "exclusiveMinimum": 0 }, "number": { "$ref": "#/components/schemas/quoteNumberSchema" }, "numberPrefix": { "$ref": "#/components/schemas/quoteNumberPrefixSchema" }, "displayNumber": { "$ref": "#/components/schemas/quoteDisplayNumberSchema" }, "version": { "type": "integer", "minimum": 1 }, "supersedesQuoteId": { "type": [ "string", "null" ], "format": "uuid" }, "status": { "$ref": "#/components/schemas/quoteStatusSchema" }, "quoteTemplateId": { "type": [ "string", "null" ], "format": "uuid" }, "title": { "type": "string" }, "intro": { "type": [ "string", "null" ] }, "terms": { "type": [ "string", "null" ] }, "validUntil": { "type": [ "string", "null" ] }, "currency": { "$ref": "#/components/schemas/quoteCurrencyCodeSchema" }, "vatInclusive": { "type": "boolean" }, "discountKind": { "$ref": "#/components/schemas/discountKindSchema" }, "discountValue": { "type": "number" }, "discountDescription": { "$ref": "#/components/schemas/discountDescriptionSchema" }, "promotionId": { "type": [ "string", "null" ], "format": "uuid" }, "subtotalExcl": { "type": "number" }, "vatTotal": { "type": "number" }, "totalIncl": { "type": "number" }, "publicMessage": { "type": [ "string", "null" ] }, "sentAt": { "type": [ "string", "null" ], "format": "date-time" }, "acceptedAt": { "type": [ "string", "null" ], "format": "date-time" }, "rejectedAt": { "type": [ "string", "null" ], "format": "date-time" }, "expiredAt": { "type": [ "string", "null" ], "format": "date-time" }, "withdrawnAt": { "type": [ "string", "null" ], "format": "date-time" }, "hasUnpublishedChanges": { "type": "boolean" }, "acceptedByName": { "type": [ "string", "null" ] }, "acceptedByEmail": { "type": [ "string", "null" ] }, "acceptedIp": { "type": [ "string", "null" ] }, "acceptedUserAgent": { "type": [ "string", "null" ] }, "acceptedOptionalLineIds": { "type": "array", "items": { "type": "string", "format": "uuid" } }, "archivedAt": { "type": [ "string", "null" ], "format": "date-time" }, "signatureProvider": { "type": [ "string", "null" ] }, "signatureEnvelopeId": { "type": [ "string", "null" ] }, "signedPdfUrl": { "type": [ "string", "null" ] }, "invoiceId": { "type": [ "string", "null" ], "format": "uuid" }, "issueDate": { "type": [ "string", "null" ] }, "vatRegime": { "$ref": "#/components/schemas/documentVatRegimeSchema" }, "sellerName": { "type": [ "string", "null" ] }, "sellerAddressLine1": { "type": [ "string", "null" ] }, "sellerAddressLine2": { "type": [ "string", "null" ] }, "sellerAddressPostalCode": { "type": [ "string", "null" ] }, "sellerAddressCity": { "type": [ "string", "null" ] }, "sellerAddressCountry": { "type": [ "string", "null" ] }, "sellerEmail": { "type": [ "string", "null" ] }, "sellerPhone": { "type": [ "string", "null" ] }, "sellerKvk": { "type": [ "string", "null" ] }, "sellerBtw": { "type": [ "string", "null" ] }, "sellerIban": { "type": [ "string", "null" ] }, "buyerName": { "type": [ "string", "null" ] }, "buyerAddressLine1": { "type": [ "string", "null" ] }, "buyerAddressLine2": { "type": [ "string", "null" ] }, "buyerAddressPostalCode": { "type": [ "string", "null" ] }, "buyerAddressCity": { "type": [ "string", "null" ] }, "buyerAddressCountry": { "type": [ "string", "null" ] }, "buyerEmail": { "type": [ "string", "null" ] }, "buyerKvk": { "type": [ "string", "null" ] }, "buyerBtw": { "type": [ "string", "null" ] }, "termsRichText": { "type": [ "string", "null" ], "maxLength": 100000 }, "termsUrl": { "type": [ "string", "null" ] }, "termsPdfFileId": { "type": [ "string", "null" ], "format": "uuid" }, "termsPdfVersionId": { "type": [ "string", "null" ], "format": "uuid" }, "depositKind": { "$ref": "#/components/schemas/quoteDepositKindSchema" }, "depositValue": { "type": "number", "minimum": 0 }, "depositRequiredToAccept": { "type": "boolean" }, "changesRequestedAt": { "type": [ "string", "null" ], "format": "date-time" }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "updatedBy": { "type": [ "string", "null" ], "format": "uuid" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "customerId", "number", "numberPrefix", "displayNumber", "version", "supersedesQuoteId", "status", "quoteTemplateId", "title", "intro", "terms", "validUntil", "currency", "vatInclusive", "discountKind", "discountValue", "discountDescription", "promotionId", "subtotalExcl", "vatTotal", "totalIncl", "publicMessage", "sentAt", "acceptedAt", "rejectedAt", "expiredAt", "withdrawnAt", "hasUnpublishedChanges", "acceptedByName", "acceptedByEmail", "acceptedIp", "acceptedUserAgent", "acceptedOptionalLineIds", "archivedAt", "signatureProvider", "signatureEnvelopeId", "signedPdfUrl", "invoiceId", "issueDate", "vatRegime", "sellerName", "sellerAddressLine1", "sellerAddressLine2", "sellerAddressPostalCode", "sellerAddressCity", "sellerAddressCountry", "sellerEmail", "sellerPhone", "sellerKvk", "sellerBtw", "sellerIban", "buyerName", "buyerAddressLine1", "buyerAddressLine2", "buyerAddressPostalCode", "buyerAddressCity", "buyerAddressCountry", "buyerEmail", "buyerKvk", "buyerBtw", "termsRichText", "termsUrl", "termsPdfFileId", "termsPdfVersionId", "depositKind", "depositValue", "depositRequiredToAccept", "changesRequestedAt", "createdBy", "updatedBy", "createdAt", "updatedAt" ] }, "quoteStatusHistoryEntrySchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "quoteId": { "$ref": "#/components/schemas/quoteIdSchema" }, "organizationId": { "type": "string", "format": "uuid" }, "fromStatus": { "anyOf": [ { "$ref": "#/components/schemas/quoteStatusSchema" }, { "type": "null" } ] }, "toStatus": { "$ref": "#/components/schemas/quoteStatusSchema" }, "actorUserId": { "type": [ "string", "null" ], "format": "uuid" }, "actorCustomerName": { "type": [ "string", "null" ] }, "actorCustomerEmail": { "type": [ "string", "null" ] }, "reason": { "type": [ "string", "null" ] }, "ip": { "type": [ "string", "null" ] }, "userAgent": { "type": [ "string", "null" ] }, "createdAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "quoteId", "organizationId", "fromStatus", "toStatus", "actorUserId", "actorCustomerName", "actorCustomerEmail", "reason", "ip", "userAgent", "createdAt" ] }, "quoteStatusSchema": { "type": "string", "enum": [ "draft", "sent", "accepted", "rejected", "expired", "withdrawn", "changes_requested" ] }, "quoteStockLineSummarySchema": { "type": "object", "properties": { "lineId": { "type": "string", "format": "uuid" }, "productId": { "type": [ "string", "null" ], "format": "uuid" }, "heldQuantity": { "type": "number", "minimum": 0 }, "shortQuantity": { "type": "number", "minimum": 0 }, "availableQuantity": { "type": "number", "minimum": 0 }, "reserves": { "type": "boolean" }, "tracked": { "type": "boolean" }, "handledByTask": { "type": [ "object", "null" ], "properties": { "taskId": { "type": "string", "format": "uuid" }, "taskNumber": { "type": "integer", "exclusiveMinimum": 0 } }, "required": [ "taskId", "taskNumber" ] } }, "required": [ "lineId", "productId", "heldQuantity", "shortQuantity", "availableQuantity", "reserves", "tracked", "handledByTask" ] }, "quoteSubscriptionIntervalSchema": { "type": "string", "enum": [ "weekly", "monthly", "quarterly", "yearly" ] }, "quoteVersionSummarySchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "version": { "type": "integer", "minimum": 1 }, "status": { "$ref": "#/components/schemas/quoteStatusSchema" }, "reason": { "type": [ "string", "null" ] }, "publishedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "version", "status", "reason", "publishedAt" ] } } ``` # Update a quote. (/openapi/updateQuote) Update a quote for the specified organization. ## PATCH /v1/organizations/{organizationId}/quotes/{quoteId} ```json { "operationId": "updateQuote", "summary": "Update a quote.", "description": "Update a quote for the specified organization.", "tags": [ "Quotes" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "quoteId", "required": true, "schema": { "$ref": "#/components/schemas/quoteIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/quotePatchSchema" } } }, "description": "The quote patch payload.", "required": true }, "responses": { "200": { "description": "Returns the updated quote.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/quoteSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "discountDescriptionSchema": { "type": [ "string", "null" ], "maxLength": 500 }, "discountKindSchema": { "type": "string", "enum": [ "none", "percentage", "fixed" ] }, "documentVatRegimeSchema": { "type": "string", "enum": [ "standard", "reverse_charge", "exempt", "kor", "margin_scheme" ] }, "isoDateOrNull": { "type": [ "string", "null" ], "pattern": "^\\d{4}-\\d{2}-\\d{2}$" }, "optionalUrl": { "type": [ "string", "null" ], "maxLength": 2000 }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "quoteCurrencyCodeSchema": { "type": "string", "pattern": "^[A-Z]{3}$" }, "quoteDepositKindSchema": { "type": "string", "enum": [ "none", "percent", "fixed" ] }, "quoteDisplayNumberSchema": { "type": "string", "minLength": 1, "maxLength": 40 }, "quoteIdSchema": { "type": "string", "format": "uuid" }, "quoteLineIdSchema": { "type": "string", "format": "uuid" }, "quoteLineInputSchema": { "type": "object", "properties": { "id": { "allOf": [ { "$ref": "#/components/schemas/quoteLineIdSchema" } ] }, "kind": { "allOf": [ { "$ref": "#/components/schemas/quoteLineKindSchema" } ] }, "lineType": { "anyOf": [ { "$ref": "#/components/schemas/quoteLineTypeSchema" }, { "type": "null" } ] }, "productId": { "type": [ "string", "null" ], "format": "uuid" }, "variantId": { "type": [ "string", "null" ], "format": "uuid" }, "taskId": { "type": [ "string", "null" ], "format": "uuid" }, "title": { "type": "string", "minLength": 1, "maxLength": 500 }, "description": { "type": [ "string", "null" ], "maxLength": 20000 }, "quantity": { "type": "number", "minimum": 0 }, "unit": { "type": [ "string", "null" ], "maxLength": 40 }, "unitPrice": { "type": "number" }, "vatRate": { "type": "number", "minimum": 0, "maximum": 100 }, "discountKind": { "allOf": [ { "$ref": "#/components/schemas/discountKindSchema" } ] }, "discountValue": { "type": "number", "minimum": 0 }, "promotionId": { "type": [ "string", "null" ], "format": "uuid" }, "isOptional": { "type": "boolean" }, "pricingMode": { "allOf": [ { "$ref": "#/components/schemas/quoteLinePricingModeSchema" } ] }, "subscriptionInterval": { "anyOf": [ { "$ref": "#/components/schemas/quoteSubscriptionIntervalSchema" }, { "type": "null" } ] }, "subscriptionPeriods": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 }, "position": { "type": "number" }, "stockMode": { "allOf": [ { "$ref": "#/components/schemas/quoteLineStockModeSchema" } ] } }, "required": [ "title" ] }, "quoteLineKindSchema": { "type": "string", "enum": [ "line", "section_header" ] }, "quoteLinePricingModeSchema": { "type": "string", "enum": [ "unit", "hourly", "subscription", "fixed" ] }, "quoteLineStockModeSchema": { "type": "string", "enum": [ "auto", "none" ] }, "quoteLineTypeSchema": { "type": "string", "enum": [ "product", "labor", "custom" ] }, "quoteNumberPrefixSchema": { "type": "string", "maxLength": 16, "pattern": "^[A-Z0-9-]*$" }, "quoteNumberSchema": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 }, "quotePatchSchema": { "type": "object", "properties": { "customerId": { "type": "integer", "exclusiveMinimum": 0 }, "title": { "type": "string", "minLength": 1, "maxLength": 240 }, "quoteTemplateId": { "type": [ "string", "null" ], "format": "uuid" }, "intro": { "type": [ "string", "null" ], "maxLength": 20000 }, "terms": { "type": [ "string", "null" ], "maxLength": 20000 }, "validUntil": { "allOf": [ { "$ref": "#/components/schemas/isoDateOrNull" } ] }, "currency": { "allOf": [ { "$ref": "#/components/schemas/quoteCurrencyCodeSchema" } ] }, "vatInclusive": { "type": "boolean" }, "discountKind": { "allOf": [ { "$ref": "#/components/schemas/discountKindSchema" } ] }, "discountValue": { "type": "number", "minimum": 0 }, "discountDescription": { "allOf": [ { "$ref": "#/components/schemas/discountDescriptionSchema" } ] }, "promotionId": { "type": [ "string", "null" ], "format": "uuid" }, "publicMessage": { "type": [ "string", "null" ], "maxLength": 20000 }, "issueDate": { "allOf": [ { "$ref": "#/components/schemas/isoDateOrNull" } ] }, "vatRegime": { "allOf": [ { "$ref": "#/components/schemas/documentVatRegimeSchema" } ] }, "termsRichText": { "type": [ "string", "null" ], "maxLength": 100000 }, "termsUrl": { "allOf": [ { "$ref": "#/components/schemas/optionalUrl" } ] }, "termsPdfFileId": { "type": [ "string", "null" ], "format": "uuid" }, "termsPdfVersionId": { "type": [ "string", "null" ], "format": "uuid" }, "depositKind": { "allOf": [ { "$ref": "#/components/schemas/quoteDepositKindSchema" } ] }, "depositValue": { "type": "number", "minimum": 0 }, "depositRequiredToAccept": { "type": "boolean" }, "lines": { "type": "array", "items": { "$ref": "#/components/schemas/quoteLineInputSchema" }, "maxItems": 500 }, "linkedTaskIds": { "type": "array", "items": { "type": "string", "format": "uuid" }, "maxItems": 200 } } }, "quoteSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/quoteIdSchema" }, "organizationId": { "type": "string", "format": "uuid" }, "customerId": { "type": "integer", "exclusiveMinimum": 0 }, "number": { "$ref": "#/components/schemas/quoteNumberSchema" }, "numberPrefix": { "$ref": "#/components/schemas/quoteNumberPrefixSchema" }, "displayNumber": { "$ref": "#/components/schemas/quoteDisplayNumberSchema" }, "version": { "type": "integer", "minimum": 1 }, "supersedesQuoteId": { "type": [ "string", "null" ], "format": "uuid" }, "status": { "$ref": "#/components/schemas/quoteStatusSchema" }, "quoteTemplateId": { "type": [ "string", "null" ], "format": "uuid" }, "title": { "type": "string" }, "intro": { "type": [ "string", "null" ] }, "terms": { "type": [ "string", "null" ] }, "validUntil": { "type": [ "string", "null" ] }, "currency": { "$ref": "#/components/schemas/quoteCurrencyCodeSchema" }, "vatInclusive": { "type": "boolean" }, "discountKind": { "$ref": "#/components/schemas/discountKindSchema" }, "discountValue": { "type": "number" }, "discountDescription": { "$ref": "#/components/schemas/discountDescriptionSchema" }, "promotionId": { "type": [ "string", "null" ], "format": "uuid" }, "subtotalExcl": { "type": "number" }, "vatTotal": { "type": "number" }, "totalIncl": { "type": "number" }, "publicMessage": { "type": [ "string", "null" ] }, "sentAt": { "type": [ "string", "null" ], "format": "date-time" }, "acceptedAt": { "type": [ "string", "null" ], "format": "date-time" }, "rejectedAt": { "type": [ "string", "null" ], "format": "date-time" }, "expiredAt": { "type": [ "string", "null" ], "format": "date-time" }, "withdrawnAt": { "type": [ "string", "null" ], "format": "date-time" }, "hasUnpublishedChanges": { "type": "boolean" }, "acceptedByName": { "type": [ "string", "null" ] }, "acceptedByEmail": { "type": [ "string", "null" ] }, "acceptedIp": { "type": [ "string", "null" ] }, "acceptedUserAgent": { "type": [ "string", "null" ] }, "acceptedOptionalLineIds": { "type": "array", "items": { "type": "string", "format": "uuid" } }, "archivedAt": { "type": [ "string", "null" ], "format": "date-time" }, "signatureProvider": { "type": [ "string", "null" ] }, "signatureEnvelopeId": { "type": [ "string", "null" ] }, "signedPdfUrl": { "type": [ "string", "null" ] }, "invoiceId": { "type": [ "string", "null" ], "format": "uuid" }, "issueDate": { "type": [ "string", "null" ] }, "vatRegime": { "$ref": "#/components/schemas/documentVatRegimeSchema" }, "sellerName": { "type": [ "string", "null" ] }, "sellerAddressLine1": { "type": [ "string", "null" ] }, "sellerAddressLine2": { "type": [ "string", "null" ] }, "sellerAddressPostalCode": { "type": [ "string", "null" ] }, "sellerAddressCity": { "type": [ "string", "null" ] }, "sellerAddressCountry": { "type": [ "string", "null" ] }, "sellerEmail": { "type": [ "string", "null" ] }, "sellerPhone": { "type": [ "string", "null" ] }, "sellerKvk": { "type": [ "string", "null" ] }, "sellerBtw": { "type": [ "string", "null" ] }, "sellerIban": { "type": [ "string", "null" ] }, "buyerName": { "type": [ "string", "null" ] }, "buyerAddressLine1": { "type": [ "string", "null" ] }, "buyerAddressLine2": { "type": [ "string", "null" ] }, "buyerAddressPostalCode": { "type": [ "string", "null" ] }, "buyerAddressCity": { "type": [ "string", "null" ] }, "buyerAddressCountry": { "type": [ "string", "null" ] }, "buyerEmail": { "type": [ "string", "null" ] }, "buyerKvk": { "type": [ "string", "null" ] }, "buyerBtw": { "type": [ "string", "null" ] }, "termsRichText": { "type": [ "string", "null" ], "maxLength": 100000 }, "termsUrl": { "type": [ "string", "null" ] }, "termsPdfFileId": { "type": [ "string", "null" ], "format": "uuid" }, "termsPdfVersionId": { "type": [ "string", "null" ], "format": "uuid" }, "depositKind": { "$ref": "#/components/schemas/quoteDepositKindSchema" }, "depositValue": { "type": "number", "minimum": 0 }, "depositRequiredToAccept": { "type": "boolean" }, "changesRequestedAt": { "type": [ "string", "null" ], "format": "date-time" }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "updatedBy": { "type": [ "string", "null" ], "format": "uuid" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "customerId", "number", "numberPrefix", "displayNumber", "version", "supersedesQuoteId", "status", "quoteTemplateId", "title", "intro", "terms", "validUntil", "currency", "vatInclusive", "discountKind", "discountValue", "discountDescription", "promotionId", "subtotalExcl", "vatTotal", "totalIncl", "publicMessage", "sentAt", "acceptedAt", "rejectedAt", "expiredAt", "withdrawnAt", "hasUnpublishedChanges", "acceptedByName", "acceptedByEmail", "acceptedIp", "acceptedUserAgent", "acceptedOptionalLineIds", "archivedAt", "signatureProvider", "signatureEnvelopeId", "signedPdfUrl", "invoiceId", "issueDate", "vatRegime", "sellerName", "sellerAddressLine1", "sellerAddressLine2", "sellerAddressPostalCode", "sellerAddressCity", "sellerAddressCountry", "sellerEmail", "sellerPhone", "sellerKvk", "sellerBtw", "sellerIban", "buyerName", "buyerAddressLine1", "buyerAddressLine2", "buyerAddressPostalCode", "buyerAddressCity", "buyerAddressCountry", "buyerEmail", "buyerKvk", "buyerBtw", "termsRichText", "termsUrl", "termsPdfFileId", "termsPdfVersionId", "depositKind", "depositValue", "depositRequiredToAccept", "changesRequestedAt", "createdBy", "updatedBy", "createdAt", "updatedAt" ] }, "quoteStatusSchema": { "type": "string", "enum": [ "draft", "sent", "accepted", "rejected", "expired", "withdrawn", "changes_requested" ] }, "quoteSubscriptionIntervalSchema": { "type": "string", "enum": [ "weekly", "monthly", "quarterly", "yearly" ] } } ``` # Delete a quote. (/openapi/deleteQuote) Archive a quote for the specified organization. ## DELETE /v1/organizations/{organizationId}/quotes/{quoteId} ```json { "operationId": "deleteQuote", "summary": "Delete a quote.", "description": "Archive a quote for the specified organization.", "tags": [ "Quotes" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "quoteId", "required": true, "schema": { "$ref": "#/components/schemas/quoteIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns a success payload when the quote is archived.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/publicApiSuccessResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "organizationIdSchema": { "type": "string", "format": "uuid" }, "publicApiSuccessResponseSchema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [ true ] } }, "required": [ "success" ] }, "quoteIdSchema": { "type": "string", "format": "uuid" } } ``` # Accept a quote. (/openapi/acceptQuote) Mark a quote accepted from the authenticated organization context. ## POST /v1/organizations/{organizationId}/quotes/{quoteId}/accept ```json { "operationId": "acceptQuote", "summary": "Accept a quote.", "description": "Mark a quote accepted from the authenticated organization context.", "tags": [ "Quotes" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "quoteId", "required": true, "schema": { "$ref": "#/components/schemas/quoteIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/quoteMarkAcceptedInputSchema" } } }, "description": "Acceptance metadata for the quote.", "required": true }, "responses": { "200": { "description": "Returns the accepted quote.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/quoteSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "discountDescriptionSchema": { "type": [ "string", "null" ], "maxLength": 500 }, "discountKindSchema": { "type": "string", "enum": [ "none", "percentage", "fixed" ] }, "documentVatRegimeSchema": { "type": "string", "enum": [ "standard", "reverse_charge", "exempt", "kor", "margin_scheme" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "quoteCurrencyCodeSchema": { "type": "string", "pattern": "^[A-Z]{3}$" }, "quoteDepositKindSchema": { "type": "string", "enum": [ "none", "percent", "fixed" ] }, "quoteDisplayNumberSchema": { "type": "string", "minLength": 1, "maxLength": 40 }, "quoteIdSchema": { "type": "string", "format": "uuid" }, "quoteMarkAcceptedInputSchema": { "type": "object", "properties": { "acceptedByName": { "type": [ "string", "null" ], "minLength": 1, "maxLength": 240 }, "acceptedByEmail": { "type": [ "string", "null" ], "format": "email", "maxLength": 320 }, "reason": { "type": [ "string", "null" ], "maxLength": 2000 }, "selectedOptionalLineIds": { "type": "array", "items": { "type": "string", "format": "uuid" }, "maxItems": 500 } } }, "quoteNumberPrefixSchema": { "type": "string", "maxLength": 16, "pattern": "^[A-Z0-9-]*$" }, "quoteNumberSchema": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 }, "quoteSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/quoteIdSchema" }, "organizationId": { "type": "string", "format": "uuid" }, "customerId": { "type": "integer", "exclusiveMinimum": 0 }, "number": { "$ref": "#/components/schemas/quoteNumberSchema" }, "numberPrefix": { "$ref": "#/components/schemas/quoteNumberPrefixSchema" }, "displayNumber": { "$ref": "#/components/schemas/quoteDisplayNumberSchema" }, "version": { "type": "integer", "minimum": 1 }, "supersedesQuoteId": { "type": [ "string", "null" ], "format": "uuid" }, "status": { "$ref": "#/components/schemas/quoteStatusSchema" }, "quoteTemplateId": { "type": [ "string", "null" ], "format": "uuid" }, "title": { "type": "string" }, "intro": { "type": [ "string", "null" ] }, "terms": { "type": [ "string", "null" ] }, "validUntil": { "type": [ "string", "null" ] }, "currency": { "$ref": "#/components/schemas/quoteCurrencyCodeSchema" }, "vatInclusive": { "type": "boolean" }, "discountKind": { "$ref": "#/components/schemas/discountKindSchema" }, "discountValue": { "type": "number" }, "discountDescription": { "$ref": "#/components/schemas/discountDescriptionSchema" }, "promotionId": { "type": [ "string", "null" ], "format": "uuid" }, "subtotalExcl": { "type": "number" }, "vatTotal": { "type": "number" }, "totalIncl": { "type": "number" }, "publicMessage": { "type": [ "string", "null" ] }, "sentAt": { "type": [ "string", "null" ], "format": "date-time" }, "acceptedAt": { "type": [ "string", "null" ], "format": "date-time" }, "rejectedAt": { "type": [ "string", "null" ], "format": "date-time" }, "expiredAt": { "type": [ "string", "null" ], "format": "date-time" }, "withdrawnAt": { "type": [ "string", "null" ], "format": "date-time" }, "hasUnpublishedChanges": { "type": "boolean" }, "acceptedByName": { "type": [ "string", "null" ] }, "acceptedByEmail": { "type": [ "string", "null" ] }, "acceptedIp": { "type": [ "string", "null" ] }, "acceptedUserAgent": { "type": [ "string", "null" ] }, "acceptedOptionalLineIds": { "type": "array", "items": { "type": "string", "format": "uuid" } }, "archivedAt": { "type": [ "string", "null" ], "format": "date-time" }, "signatureProvider": { "type": [ "string", "null" ] }, "signatureEnvelopeId": { "type": [ "string", "null" ] }, "signedPdfUrl": { "type": [ "string", "null" ] }, "invoiceId": { "type": [ "string", "null" ], "format": "uuid" }, "issueDate": { "type": [ "string", "null" ] }, "vatRegime": { "$ref": "#/components/schemas/documentVatRegimeSchema" }, "sellerName": { "type": [ "string", "null" ] }, "sellerAddressLine1": { "type": [ "string", "null" ] }, "sellerAddressLine2": { "type": [ "string", "null" ] }, "sellerAddressPostalCode": { "type": [ "string", "null" ] }, "sellerAddressCity": { "type": [ "string", "null" ] }, "sellerAddressCountry": { "type": [ "string", "null" ] }, "sellerEmail": { "type": [ "string", "null" ] }, "sellerPhone": { "type": [ "string", "null" ] }, "sellerKvk": { "type": [ "string", "null" ] }, "sellerBtw": { "type": [ "string", "null" ] }, "sellerIban": { "type": [ "string", "null" ] }, "buyerName": { "type": [ "string", "null" ] }, "buyerAddressLine1": { "type": [ "string", "null" ] }, "buyerAddressLine2": { "type": [ "string", "null" ] }, "buyerAddressPostalCode": { "type": [ "string", "null" ] }, "buyerAddressCity": { "type": [ "string", "null" ] }, "buyerAddressCountry": { "type": [ "string", "null" ] }, "buyerEmail": { "type": [ "string", "null" ] }, "buyerKvk": { "type": [ "string", "null" ] }, "buyerBtw": { "type": [ "string", "null" ] }, "termsRichText": { "type": [ "string", "null" ], "maxLength": 100000 }, "termsUrl": { "type": [ "string", "null" ] }, "termsPdfFileId": { "type": [ "string", "null" ], "format": "uuid" }, "termsPdfVersionId": { "type": [ "string", "null" ], "format": "uuid" }, "depositKind": { "$ref": "#/components/schemas/quoteDepositKindSchema" }, "depositValue": { "type": "number", "minimum": 0 }, "depositRequiredToAccept": { "type": "boolean" }, "changesRequestedAt": { "type": [ "string", "null" ], "format": "date-time" }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "updatedBy": { "type": [ "string", "null" ], "format": "uuid" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "customerId", "number", "numberPrefix", "displayNumber", "version", "supersedesQuoteId", "status", "quoteTemplateId", "title", "intro", "terms", "validUntil", "currency", "vatInclusive", "discountKind", "discountValue", "discountDescription", "promotionId", "subtotalExcl", "vatTotal", "totalIncl", "publicMessage", "sentAt", "acceptedAt", "rejectedAt", "expiredAt", "withdrawnAt", "hasUnpublishedChanges", "acceptedByName", "acceptedByEmail", "acceptedIp", "acceptedUserAgent", "acceptedOptionalLineIds", "archivedAt", "signatureProvider", "signatureEnvelopeId", "signedPdfUrl", "invoiceId", "issueDate", "vatRegime", "sellerName", "sellerAddressLine1", "sellerAddressLine2", "sellerAddressPostalCode", "sellerAddressCity", "sellerAddressCountry", "sellerEmail", "sellerPhone", "sellerKvk", "sellerBtw", "sellerIban", "buyerName", "buyerAddressLine1", "buyerAddressLine2", "buyerAddressPostalCode", "buyerAddressCity", "buyerAddressCountry", "buyerEmail", "buyerKvk", "buyerBtw", "termsRichText", "termsUrl", "termsPdfFileId", "termsPdfVersionId", "depositKind", "depositValue", "depositRequiredToAccept", "changesRequestedAt", "createdBy", "updatedBy", "createdAt", "updatedAt" ] }, "quoteStatusSchema": { "type": "string", "enum": [ "draft", "sent", "accepted", "rejected", "expired", "withdrawn", "changes_requested" ] } } ``` # Set quote asset links. (/openapi/setQuoteAssets) Replace the customer assets linked to a quote. ## PUT /v1/organizations/{organizationId}/quotes/{quoteId}/assets ```json { "operationId": "setQuoteAssets", "summary": "Set quote asset links.", "description": "Replace the customer assets linked to a quote.", "tags": [ "Quotes" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "quoteId", "required": true, "schema": { "$ref": "#/components/schemas/quoteIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/quoteAssetLinksInputSchema" } } }, "description": "Customer asset ids to link to the quote." }, "responses": { "200": { "description": "Returns the linked customer asset ids.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/quoteAssetLinksResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "organizationIdSchema": { "type": "string", "format": "uuid" }, "quoteAssetLinksInputSchema": { "type": "object", "properties": { "customerAssetIds": { "type": "array", "items": { "type": "integer", "exclusiveMinimum": 0 }, "maxItems": 200 } }, "required": [ "customerAssetIds" ] }, "quoteAssetLinksResponseSchema": { "type": "object", "properties": { "customerAssetIds": { "type": "array", "items": { "type": "integer", "exclusiveMinimum": 0 } } }, "required": [ "customerAssetIds" ] }, "quoteIdSchema": { "type": "string", "format": "uuid" } } ``` # Duplicate a quote. (/openapi/duplicateQuote) Duplicate a quote as a new editable version. ## POST /v1/organizations/{organizationId}/quotes/{quoteId}/duplicate ```json { "operationId": "duplicateQuote", "summary": "Duplicate a quote.", "description": "Duplicate a quote as a new editable version.", "tags": [ "Quotes" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "quoteId", "required": true, "schema": { "$ref": "#/components/schemas/quoteIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "201": { "description": "Returns the duplicated quote.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/quoteSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "discountDescriptionSchema": { "type": [ "string", "null" ], "maxLength": 500 }, "discountKindSchema": { "type": "string", "enum": [ "none", "percentage", "fixed" ] }, "documentVatRegimeSchema": { "type": "string", "enum": [ "standard", "reverse_charge", "exempt", "kor", "margin_scheme" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "quoteCurrencyCodeSchema": { "type": "string", "pattern": "^[A-Z]{3}$" }, "quoteDepositKindSchema": { "type": "string", "enum": [ "none", "percent", "fixed" ] }, "quoteDisplayNumberSchema": { "type": "string", "minLength": 1, "maxLength": 40 }, "quoteIdSchema": { "type": "string", "format": "uuid" }, "quoteNumberPrefixSchema": { "type": "string", "maxLength": 16, "pattern": "^[A-Z0-9-]*$" }, "quoteNumberSchema": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 }, "quoteSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/quoteIdSchema" }, "organizationId": { "type": "string", "format": "uuid" }, "customerId": { "type": "integer", "exclusiveMinimum": 0 }, "number": { "$ref": "#/components/schemas/quoteNumberSchema" }, "numberPrefix": { "$ref": "#/components/schemas/quoteNumberPrefixSchema" }, "displayNumber": { "$ref": "#/components/schemas/quoteDisplayNumberSchema" }, "version": { "type": "integer", "minimum": 1 }, "supersedesQuoteId": { "type": [ "string", "null" ], "format": "uuid" }, "status": { "$ref": "#/components/schemas/quoteStatusSchema" }, "quoteTemplateId": { "type": [ "string", "null" ], "format": "uuid" }, "title": { "type": "string" }, "intro": { "type": [ "string", "null" ] }, "terms": { "type": [ "string", "null" ] }, "validUntil": { "type": [ "string", "null" ] }, "currency": { "$ref": "#/components/schemas/quoteCurrencyCodeSchema" }, "vatInclusive": { "type": "boolean" }, "discountKind": { "$ref": "#/components/schemas/discountKindSchema" }, "discountValue": { "type": "number" }, "discountDescription": { "$ref": "#/components/schemas/discountDescriptionSchema" }, "promotionId": { "type": [ "string", "null" ], "format": "uuid" }, "subtotalExcl": { "type": "number" }, "vatTotal": { "type": "number" }, "totalIncl": { "type": "number" }, "publicMessage": { "type": [ "string", "null" ] }, "sentAt": { "type": [ "string", "null" ], "format": "date-time" }, "acceptedAt": { "type": [ "string", "null" ], "format": "date-time" }, "rejectedAt": { "type": [ "string", "null" ], "format": "date-time" }, "expiredAt": { "type": [ "string", "null" ], "format": "date-time" }, "withdrawnAt": { "type": [ "string", "null" ], "format": "date-time" }, "hasUnpublishedChanges": { "type": "boolean" }, "acceptedByName": { "type": [ "string", "null" ] }, "acceptedByEmail": { "type": [ "string", "null" ] }, "acceptedIp": { "type": [ "string", "null" ] }, "acceptedUserAgent": { "type": [ "string", "null" ] }, "acceptedOptionalLineIds": { "type": "array", "items": { "type": "string", "format": "uuid" } }, "archivedAt": { "type": [ "string", "null" ], "format": "date-time" }, "signatureProvider": { "type": [ "string", "null" ] }, "signatureEnvelopeId": { "type": [ "string", "null" ] }, "signedPdfUrl": { "type": [ "string", "null" ] }, "invoiceId": { "type": [ "string", "null" ], "format": "uuid" }, "issueDate": { "type": [ "string", "null" ] }, "vatRegime": { "$ref": "#/components/schemas/documentVatRegimeSchema" }, "sellerName": { "type": [ "string", "null" ] }, "sellerAddressLine1": { "type": [ "string", "null" ] }, "sellerAddressLine2": { "type": [ "string", "null" ] }, "sellerAddressPostalCode": { "type": [ "string", "null" ] }, "sellerAddressCity": { "type": [ "string", "null" ] }, "sellerAddressCountry": { "type": [ "string", "null" ] }, "sellerEmail": { "type": [ "string", "null" ] }, "sellerPhone": { "type": [ "string", "null" ] }, "sellerKvk": { "type": [ "string", "null" ] }, "sellerBtw": { "type": [ "string", "null" ] }, "sellerIban": { "type": [ "string", "null" ] }, "buyerName": { "type": [ "string", "null" ] }, "buyerAddressLine1": { "type": [ "string", "null" ] }, "buyerAddressLine2": { "type": [ "string", "null" ] }, "buyerAddressPostalCode": { "type": [ "string", "null" ] }, "buyerAddressCity": { "type": [ "string", "null" ] }, "buyerAddressCountry": { "type": [ "string", "null" ] }, "buyerEmail": { "type": [ "string", "null" ] }, "buyerKvk": { "type": [ "string", "null" ] }, "buyerBtw": { "type": [ "string", "null" ] }, "termsRichText": { "type": [ "string", "null" ], "maxLength": 100000 }, "termsUrl": { "type": [ "string", "null" ] }, "termsPdfFileId": { "type": [ "string", "null" ], "format": "uuid" }, "termsPdfVersionId": { "type": [ "string", "null" ], "format": "uuid" }, "depositKind": { "$ref": "#/components/schemas/quoteDepositKindSchema" }, "depositValue": { "type": "number", "minimum": 0 }, "depositRequiredToAccept": { "type": "boolean" }, "changesRequestedAt": { "type": [ "string", "null" ], "format": "date-time" }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "updatedBy": { "type": [ "string", "null" ], "format": "uuid" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "customerId", "number", "numberPrefix", "displayNumber", "version", "supersedesQuoteId", "status", "quoteTemplateId", "title", "intro", "terms", "validUntil", "currency", "vatInclusive", "discountKind", "discountValue", "discountDescription", "promotionId", "subtotalExcl", "vatTotal", "totalIncl", "publicMessage", "sentAt", "acceptedAt", "rejectedAt", "expiredAt", "withdrawnAt", "hasUnpublishedChanges", "acceptedByName", "acceptedByEmail", "acceptedIp", "acceptedUserAgent", "acceptedOptionalLineIds", "archivedAt", "signatureProvider", "signatureEnvelopeId", "signedPdfUrl", "invoiceId", "issueDate", "vatRegime", "sellerName", "sellerAddressLine1", "sellerAddressLine2", "sellerAddressPostalCode", "sellerAddressCity", "sellerAddressCountry", "sellerEmail", "sellerPhone", "sellerKvk", "sellerBtw", "sellerIban", "buyerName", "buyerAddressLine1", "buyerAddressLine2", "buyerAddressPostalCode", "buyerAddressCity", "buyerAddressCountry", "buyerEmail", "buyerKvk", "buyerBtw", "termsRichText", "termsUrl", "termsPdfFileId", "termsPdfVersionId", "depositKind", "depositValue", "depositRequiredToAccept", "changesRequestedAt", "createdBy", "updatedBy", "createdAt", "updatedAt" ] }, "quoteStatusSchema": { "type": "string", "enum": [ "draft", "sent", "accepted", "rejected", "expired", "withdrawn", "changes_requested" ] } } ``` # Read a quote lifecycle. (/openapi/getQuoteLifecycle) The quote's lifecycle: the pinned workflow version, the current stage, the stage history, and what the lifecycle waits on now. `lifecycle` is null when no lifecycle run started for the quote. ## GET /v1/organizations/{organizationId}/quotes/{quoteId}/lifecycle ```json { "operationId": "getQuoteLifecycle", "summary": "Read a quote lifecycle.", "description": "The quote's lifecycle: the pinned workflow version, the current stage, the stage history, and what the lifecycle waits on now. `lifecycle` is null when no lifecycle run started for the quote.", "tags": [ "Quotes" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "quoteId", "required": true, "schema": { "$ref": "#/components/schemas/quoteIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns the quote's lifecycle.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/recordLifecycleSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "dateTimeSchema": { "type": "string" }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "quoteIdSchema": { "type": "string", "format": "uuid" }, "recordLifecycleSchema": { "type": "object", "properties": { "lifecycle": { "anyOf": [ { "$ref": "#/components/schemas/workflowLifecycleSchema" }, { "type": "null" } ] }, "definition": { "type": [ "object", "null" ], "properties": { "id": { "type": "string", "format": "uuid" }, "key": { "type": "string" }, "name": { "type": "string" }, "version": { "type": "integer", "exclusiveMinimum": 0 }, "source": { "type": "string", "enum": [ "organization", "record", "system", "template" ] } }, "required": [ "id", "key", "name", "version", "source" ] }, "events": { "type": "array", "items": { "$ref": "#/components/schemas/workflowLifecycleEventSchema" } }, "waiting": { "type": [ "object", "null" ], "properties": { "kind": { "type": "string", "enum": [ "approval", "delay", "event", "manual" ] }, "at": { "anyOf": [ { "$ref": "#/components/schemas/dateTimeSchema" }, { "type": "null" } ] }, "nextStageKey": { "type": [ "string", "null" ] }, "nextStageLabel": { "type": [ "string", "null" ] }, "events": { "type": "array", "items": { "type": "string" } } }, "required": [ "kind", "at", "nextStageKey", "nextStageLabel", "events" ] } }, "required": [ "lifecycle", "definition", "events", "waiting" ] }, "workflowLifecycleEventSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "organizationId": { "type": "string", "format": "uuid" }, "lifecycleId": { "type": "string", "format": "uuid" }, "stageKey": { "type": "string", "minLength": 1 }, "label": { "type": "string", "minLength": 1 }, "outcome": { "type": "string", "enum": [ "progress", "success", "failure", "neutral" ] }, "display": { "type": "string", "enum": [ "main", "sub", "flag", "hidden" ] }, "reachedAt": { "$ref": "#/components/schemas/dateTimeSchema" }, "actorUserId": { "type": [ "string", "null" ], "format": "uuid" }, "actorCustomerContactId": { "type": [ "string", "null" ], "format": "uuid" }, "runStepId": { "type": [ "string", "null" ], "format": "uuid" }, "sourceRunId": { "type": [ "string", "null" ], "format": "uuid" }, "clearedAt": { "anyOf": [ { "$ref": "#/components/schemas/dateTimeSchema" }, { "type": "null" } ] }, "createdAt": { "$ref": "#/components/schemas/dateTimeSchema" } }, "required": [ "id", "organizationId", "lifecycleId", "stageKey", "label", "outcome", "display", "reachedAt", "actorUserId", "actorCustomerContactId", "runStepId", "sourceRunId", "clearedAt", "createdAt" ] }, "workflowLifecycleSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "organizationId": { "type": "string", "format": "uuid" }, "subjectType": { "$ref": "#/components/schemas/workflowSubjectTypeSchema" }, "subjectId": { "type": "string", "format": "uuid" }, "definitionId": { "type": "string", "format": "uuid" }, "definitionVersion": { "type": "integer", "exclusiveMinimum": 0 }, "runId": { "type": [ "string", "null" ], "format": "uuid" }, "currentStageKey": { "type": [ "string", "null" ] }, "pausedAt": { "anyOf": [ { "$ref": "#/components/schemas/dateTimeSchema" }, { "type": "null" } ] }, "pausedUntil": { "anyOf": [ { "$ref": "#/components/schemas/dateTimeSchema" }, { "type": "null" } ] }, "pauseReason": { "type": [ "string", "null" ] }, "pausedBy": { "type": [ "string", "null" ], "format": "uuid" }, "startedAt": { "$ref": "#/components/schemas/dateTimeSchema" }, "endedAt": { "anyOf": [ { "$ref": "#/components/schemas/dateTimeSchema" }, { "type": "null" } ] }, "createdAt": { "$ref": "#/components/schemas/dateTimeSchema" }, "updatedAt": { "$ref": "#/components/schemas/dateTimeSchema" } }, "required": [ "id", "organizationId", "subjectType", "subjectId", "definitionId", "definitionVersion", "runId", "currentStageKey", "pausedAt", "pausedUntil", "pauseReason", "pausedBy", "startedAt", "endedAt", "createdAt", "updatedAt" ] }, "workflowSubjectTypeSchema": { "type": "string" } } ``` # List quote notes. (/openapi/listQuoteNotes) Return internal notes for a quote. ## GET /v1/organizations/{organizationId}/quotes/{quoteId}/notes ```json { "operationId": "listQuoteNotes", "summary": "List quote notes.", "description": "Return internal notes for a quote.", "tags": [ "Quotes" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "quoteId", "required": true, "schema": { "$ref": "#/components/schemas/quoteIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns quote notes.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/quoteNotesResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "collaborationBlockSchema": { "type": "object", "discriminator": { "propertyName": "type" }, "oneOf": [ { "type": "object", "properties": { "type": { "type": "string", "enum": [ "paragraph" ] }, "content": { "$ref": "#/components/schemas/collaborationInlineContentSchema" } }, "required": [ "type", "content" ], "additionalProperties": false }, { "type": "object", "properties": { "type": { "type": "string", "enum": [ "bullet-list" ] }, "items": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationInlineContentSchema" }, "minItems": 1, "maxItems": 500 } }, "required": [ "type", "items" ], "additionalProperties": false }, { "type": "object", "properties": { "type": { "type": "string", "enum": [ "numbered-list" ] }, "items": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationInlineContentSchema" }, "minItems": 1, "maxItems": 500 } }, "required": [ "type", "items" ], "additionalProperties": false } ] }, "collaborationCommentAuthorSchema": { "type": "object", "properties": { "userId": { "type": "string", "format": "uuid" }, "displayName": { "type": "string", "minLength": 1 }, "avatarUrl": { "type": [ "string", "null" ], "format": "uri" } }, "required": [ "userId", "displayName", "avatarUrl" ] }, "collaborationDocumentSchema": { "type": "object", "properties": { "version": { "type": "integer", "enum": [ 1 ] }, "blocks": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationBlockSchema" }, "minItems": 1, "maxItems": 1000 } }, "required": [ "version", "blocks" ], "additionalProperties": false }, "collaborationInlineContentSchema": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationInlineSchema" }, "minItems": 1, "maxItems": 1000 }, "collaborationInlineSchema": { "type": "object", "discriminator": { "propertyName": "type" }, "oneOf": [ { "type": "object", "properties": { "type": { "type": "string", "enum": [ "text" ] }, "text": { "type": "string", "minLength": 1, "maxLength": 20000 } }, "required": [ "type", "text" ], "additionalProperties": false }, { "type": "object", "properties": { "type": { "type": "string", "enum": [ "reference" ] }, "reference": { "$ref": "#/components/schemas/collaborationReferenceSchema" } }, "required": [ "type", "reference" ], "additionalProperties": false } ] }, "collaborationReferenceSchema": { "type": "object", "discriminator": { "propertyName": "kind" }, "oneOf": [ { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "user" ] }, "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "team" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "task" ] }, "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "customer" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "quote" ] }, "id": { "$ref": "#/components/schemas/quoteIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "invoice" ] }, "id": { "$ref": "#/components/schemas/invoiceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "product" ] }, "id": { "$ref": "#/components/schemas/productIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "customer-asset" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false } ] }, "invoiceIdSchema": { "type": "string", "format": "uuid" }, "listPaginationSchema": { "type": "object", "properties": { "offset": { "type": "integer", "minimum": 0 }, "limit": { "type": "integer", "minimum": 1, "maximum": 100 }, "total": { "type": "integer", "minimum": 0 }, "hasMore": { "type": "boolean" }, "totalIsEstimate": { "type": "boolean" } }, "required": [ "offset", "limit", "total", "hasMore" ] }, "numericReferenceIdSchema": { "type": "string", "pattern": "^[1-9]\\d*$" }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "productIdSchema": { "type": "string", "format": "uuid" }, "quoteIdSchema": { "type": "string", "format": "uuid" }, "quoteNoteSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "quoteId": { "$ref": "#/components/schemas/quoteIdSchema" }, "organizationId": { "type": "string", "format": "uuid" }, "contentText": { "type": "string", "minLength": 1, "maxLength": 20000 }, "contentDoc": { "$ref": "#/components/schemas/collaborationDocumentSchema" }, "authorId": { "type": [ "string", "null" ], "format": "uuid" }, "author": { "anyOf": [ { "$ref": "#/components/schemas/collaborationCommentAuthorSchema" }, { "type": "null" } ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "quoteId", "organizationId", "contentText", "contentDoc", "authorId", "author", "createdAt", "updatedAt" ] }, "quoteNotesResponseSchema": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/quoteNoteSchema" } }, "pagination": { "$ref": "#/components/schemas/listPaginationSchema" } }, "required": [ "data", "pagination" ] } } ``` # Add a quote note. (/openapi/addQuoteNote) Add an internal note to a quote. ## POST /v1/organizations/{organizationId}/quotes/{quoteId}/notes ```json { "operationId": "addQuoteNote", "summary": "Add a quote note.", "description": "Add an internal note to a quote.", "tags": [ "Quotes" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "quoteId", "required": true, "schema": { "$ref": "#/components/schemas/quoteIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/quoteNoteInputSchema" } } }, "description": "The quote note payload." }, "responses": { "201": { "description": "Returns the created quote note.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/quoteNoteSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "collaborationBlockSchema": { "type": "object", "discriminator": { "propertyName": "type" }, "oneOf": [ { "type": "object", "properties": { "type": { "type": "string", "enum": [ "paragraph" ] }, "content": { "$ref": "#/components/schemas/collaborationInlineContentSchema" } }, "required": [ "type", "content" ], "additionalProperties": false }, { "type": "object", "properties": { "type": { "type": "string", "enum": [ "bullet-list" ] }, "items": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationInlineContentSchema" }, "minItems": 1, "maxItems": 500 } }, "required": [ "type", "items" ], "additionalProperties": false }, { "type": "object", "properties": { "type": { "type": "string", "enum": [ "numbered-list" ] }, "items": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationInlineContentSchema" }, "minItems": 1, "maxItems": 500 } }, "required": [ "type", "items" ], "additionalProperties": false } ] }, "collaborationCommentAuthorSchema": { "type": "object", "properties": { "userId": { "type": "string", "format": "uuid" }, "displayName": { "type": "string", "minLength": 1 }, "avatarUrl": { "type": [ "string", "null" ], "format": "uri" } }, "required": [ "userId", "displayName", "avatarUrl" ] }, "collaborationComposerInputSchema": { "type": "object", "properties": { "text": { "type": "string", "minLength": 1, "maxLength": 20000 }, "selections": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationComposerSelectionSchema" }, "maxItems": 100 } }, "required": [ "text", "selections" ], "additionalProperties": false }, "collaborationComposerSelectionSchema": { "type": "object", "properties": { "start": { "type": "integer", "minimum": 0, "maximum": 20000 }, "end": { "type": "integer", "exclusiveMinimum": 0, "maximum": 20000 }, "reference": { "$ref": "#/components/schemas/collaborationReferenceIdSchema" } }, "required": [ "start", "end", "reference" ], "additionalProperties": false }, "collaborationDocumentSchema": { "type": "object", "properties": { "version": { "type": "integer", "enum": [ 1 ] }, "blocks": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationBlockSchema" }, "minItems": 1, "maxItems": 1000 } }, "required": [ "version", "blocks" ], "additionalProperties": false }, "collaborationInlineContentSchema": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationInlineSchema" }, "minItems": 1, "maxItems": 1000 }, "collaborationInlineSchema": { "type": "object", "discriminator": { "propertyName": "type" }, "oneOf": [ { "type": "object", "properties": { "type": { "type": "string", "enum": [ "text" ] }, "text": { "type": "string", "minLength": 1, "maxLength": 20000 } }, "required": [ "type", "text" ], "additionalProperties": false }, { "type": "object", "properties": { "type": { "type": "string", "enum": [ "reference" ] }, "reference": { "$ref": "#/components/schemas/collaborationReferenceSchema" } }, "required": [ "type", "reference" ], "additionalProperties": false } ] }, "collaborationReferenceIdSchema": { "type": "object", "discriminator": { "propertyName": "kind" }, "oneOf": [ { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "user" ] }, "id": { "type": "string", "format": "uuid" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "team" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "task" ] }, "id": { "type": "string", "format": "uuid" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "customer" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "quote" ] }, "id": { "$ref": "#/components/schemas/quoteIdSchema" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "invoice" ] }, "id": { "$ref": "#/components/schemas/invoiceIdSchema" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "product" ] }, "id": { "$ref": "#/components/schemas/productIdSchema" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "customer-asset" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" } }, "required": [ "kind", "id" ], "additionalProperties": false } ] }, "collaborationReferenceSchema": { "type": "object", "discriminator": { "propertyName": "kind" }, "oneOf": [ { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "user" ] }, "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "team" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "task" ] }, "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "customer" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "quote" ] }, "id": { "$ref": "#/components/schemas/quoteIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "invoice" ] }, "id": { "$ref": "#/components/schemas/invoiceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "product" ] }, "id": { "$ref": "#/components/schemas/productIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "customer-asset" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false } ] }, "invoiceIdSchema": { "type": "string", "format": "uuid" }, "numericReferenceIdSchema": { "type": "string", "pattern": "^[1-9]\\d*$" }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "productIdSchema": { "type": "string", "format": "uuid" }, "quoteIdSchema": { "type": "string", "format": "uuid" }, "quoteNoteInputSchema": { "$ref": "#/components/schemas/collaborationComposerInputSchema" }, "quoteNoteSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "quoteId": { "$ref": "#/components/schemas/quoteIdSchema" }, "organizationId": { "type": "string", "format": "uuid" }, "contentText": { "type": "string", "minLength": 1, "maxLength": 20000 }, "contentDoc": { "$ref": "#/components/schemas/collaborationDocumentSchema" }, "authorId": { "type": [ "string", "null" ], "format": "uuid" }, "author": { "anyOf": [ { "$ref": "#/components/schemas/collaborationCommentAuthorSchema" }, { "type": "null" } ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "quoteId", "organizationId", "contentText", "contentDoc", "authorId", "author", "createdAt", "updatedAt" ] } } ``` # Reject a quote. (/openapi/rejectQuote) Mark a quote rejected from the authenticated organization context. ## POST /v1/organizations/{organizationId}/quotes/{quoteId}/reject ```json { "operationId": "rejectQuote", "summary": "Reject a quote.", "description": "Mark a quote rejected from the authenticated organization context.", "tags": [ "Quotes" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "quoteId", "required": true, "schema": { "$ref": "#/components/schemas/quoteIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/quoteMarkRejectedInputSchema" } } }, "description": "Rejection metadata for the quote.", "required": true }, "responses": { "200": { "description": "Returns the rejected quote.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/quoteSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "discountDescriptionSchema": { "type": [ "string", "null" ], "maxLength": 500 }, "discountKindSchema": { "type": "string", "enum": [ "none", "percentage", "fixed" ] }, "documentVatRegimeSchema": { "type": "string", "enum": [ "standard", "reverse_charge", "exempt", "kor", "margin_scheme" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "quoteCurrencyCodeSchema": { "type": "string", "pattern": "^[A-Z]{3}$" }, "quoteDepositKindSchema": { "type": "string", "enum": [ "none", "percent", "fixed" ] }, "quoteDisplayNumberSchema": { "type": "string", "minLength": 1, "maxLength": 40 }, "quoteIdSchema": { "type": "string", "format": "uuid" }, "quoteMarkRejectedInputSchema": { "type": "object", "properties": { "reason": { "type": [ "string", "null" ], "maxLength": 20000 } } }, "quoteNumberPrefixSchema": { "type": "string", "maxLength": 16, "pattern": "^[A-Z0-9-]*$" }, "quoteNumberSchema": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 }, "quoteSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/quoteIdSchema" }, "organizationId": { "type": "string", "format": "uuid" }, "customerId": { "type": "integer", "exclusiveMinimum": 0 }, "number": { "$ref": "#/components/schemas/quoteNumberSchema" }, "numberPrefix": { "$ref": "#/components/schemas/quoteNumberPrefixSchema" }, "displayNumber": { "$ref": "#/components/schemas/quoteDisplayNumberSchema" }, "version": { "type": "integer", "minimum": 1 }, "supersedesQuoteId": { "type": [ "string", "null" ], "format": "uuid" }, "status": { "$ref": "#/components/schemas/quoteStatusSchema" }, "quoteTemplateId": { "type": [ "string", "null" ], "format": "uuid" }, "title": { "type": "string" }, "intro": { "type": [ "string", "null" ] }, "terms": { "type": [ "string", "null" ] }, "validUntil": { "type": [ "string", "null" ] }, "currency": { "$ref": "#/components/schemas/quoteCurrencyCodeSchema" }, "vatInclusive": { "type": "boolean" }, "discountKind": { "$ref": "#/components/schemas/discountKindSchema" }, "discountValue": { "type": "number" }, "discountDescription": { "$ref": "#/components/schemas/discountDescriptionSchema" }, "promotionId": { "type": [ "string", "null" ], "format": "uuid" }, "subtotalExcl": { "type": "number" }, "vatTotal": { "type": "number" }, "totalIncl": { "type": "number" }, "publicMessage": { "type": [ "string", "null" ] }, "sentAt": { "type": [ "string", "null" ], "format": "date-time" }, "acceptedAt": { "type": [ "string", "null" ], "format": "date-time" }, "rejectedAt": { "type": [ "string", "null" ], "format": "date-time" }, "expiredAt": { "type": [ "string", "null" ], "format": "date-time" }, "withdrawnAt": { "type": [ "string", "null" ], "format": "date-time" }, "hasUnpublishedChanges": { "type": "boolean" }, "acceptedByName": { "type": [ "string", "null" ] }, "acceptedByEmail": { "type": [ "string", "null" ] }, "acceptedIp": { "type": [ "string", "null" ] }, "acceptedUserAgent": { "type": [ "string", "null" ] }, "acceptedOptionalLineIds": { "type": "array", "items": { "type": "string", "format": "uuid" } }, "archivedAt": { "type": [ "string", "null" ], "format": "date-time" }, "signatureProvider": { "type": [ "string", "null" ] }, "signatureEnvelopeId": { "type": [ "string", "null" ] }, "signedPdfUrl": { "type": [ "string", "null" ] }, "invoiceId": { "type": [ "string", "null" ], "format": "uuid" }, "issueDate": { "type": [ "string", "null" ] }, "vatRegime": { "$ref": "#/components/schemas/documentVatRegimeSchema" }, "sellerName": { "type": [ "string", "null" ] }, "sellerAddressLine1": { "type": [ "string", "null" ] }, "sellerAddressLine2": { "type": [ "string", "null" ] }, "sellerAddressPostalCode": { "type": [ "string", "null" ] }, "sellerAddressCity": { "type": [ "string", "null" ] }, "sellerAddressCountry": { "type": [ "string", "null" ] }, "sellerEmail": { "type": [ "string", "null" ] }, "sellerPhone": { "type": [ "string", "null" ] }, "sellerKvk": { "type": [ "string", "null" ] }, "sellerBtw": { "type": [ "string", "null" ] }, "sellerIban": { "type": [ "string", "null" ] }, "buyerName": { "type": [ "string", "null" ] }, "buyerAddressLine1": { "type": [ "string", "null" ] }, "buyerAddressLine2": { "type": [ "string", "null" ] }, "buyerAddressPostalCode": { "type": [ "string", "null" ] }, "buyerAddressCity": { "type": [ "string", "null" ] }, "buyerAddressCountry": { "type": [ "string", "null" ] }, "buyerEmail": { "type": [ "string", "null" ] }, "buyerKvk": { "type": [ "string", "null" ] }, "buyerBtw": { "type": [ "string", "null" ] }, "termsRichText": { "type": [ "string", "null" ], "maxLength": 100000 }, "termsUrl": { "type": [ "string", "null" ] }, "termsPdfFileId": { "type": [ "string", "null" ], "format": "uuid" }, "termsPdfVersionId": { "type": [ "string", "null" ], "format": "uuid" }, "depositKind": { "$ref": "#/components/schemas/quoteDepositKindSchema" }, "depositValue": { "type": "number", "minimum": 0 }, "depositRequiredToAccept": { "type": "boolean" }, "changesRequestedAt": { "type": [ "string", "null" ], "format": "date-time" }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "updatedBy": { "type": [ "string", "null" ], "format": "uuid" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "customerId", "number", "numberPrefix", "displayNumber", "version", "supersedesQuoteId", "status", "quoteTemplateId", "title", "intro", "terms", "validUntil", "currency", "vatInclusive", "discountKind", "discountValue", "discountDescription", "promotionId", "subtotalExcl", "vatTotal", "totalIncl", "publicMessage", "sentAt", "acceptedAt", "rejectedAt", "expiredAt", "withdrawnAt", "hasUnpublishedChanges", "acceptedByName", "acceptedByEmail", "acceptedIp", "acceptedUserAgent", "acceptedOptionalLineIds", "archivedAt", "signatureProvider", "signatureEnvelopeId", "signedPdfUrl", "invoiceId", "issueDate", "vatRegime", "sellerName", "sellerAddressLine1", "sellerAddressLine2", "sellerAddressPostalCode", "sellerAddressCity", "sellerAddressCountry", "sellerEmail", "sellerPhone", "sellerKvk", "sellerBtw", "sellerIban", "buyerName", "buyerAddressLine1", "buyerAddressLine2", "buyerAddressPostalCode", "buyerAddressCity", "buyerAddressCountry", "buyerEmail", "buyerKvk", "buyerBtw", "termsRichText", "termsUrl", "termsPdfFileId", "termsPdfVersionId", "depositKind", "depositValue", "depositRequiredToAccept", "changesRequestedAt", "createdBy", "updatedBy", "createdAt", "updatedAt" ] }, "quoteStatusSchema": { "type": "string", "enum": [ "draft", "sent", "accepted", "rejected", "expired", "withdrawn", "changes_requested" ] } } ``` # Send a quote. (/openapi/sendQuote) Transition a draft quote to sent. ## POST /v1/organizations/{organizationId}/quotes/{quoteId}/send ```json { "operationId": "sendQuote", "summary": "Send a quote.", "description": "Transition a draft quote to sent.", "tags": [ "Quotes" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "quoteId", "required": true, "schema": { "$ref": "#/components/schemas/quoteIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/quoteSendInputSchema" } } }, "description": "Send options for the quote.", "required": true }, "responses": { "200": { "description": "Returns the sent quote.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/quoteSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "discountDescriptionSchema": { "type": [ "string", "null" ], "maxLength": 500 }, "discountKindSchema": { "type": "string", "enum": [ "none", "percentage", "fixed" ] }, "documentVatRegimeSchema": { "type": "string", "enum": [ "standard", "reverse_charge", "exempt", "kor", "margin_scheme" ] }, "isoDateOrNull": { "type": [ "string", "null" ], "pattern": "^\\d{4}-\\d{2}-\\d{2}$" }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "quoteCurrencyCodeSchema": { "type": "string", "pattern": "^[A-Z]{3}$" }, "quoteDepositKindSchema": { "type": "string", "enum": [ "none", "percent", "fixed" ] }, "quoteDisplayNumberSchema": { "type": "string", "minLength": 1, "maxLength": 40 }, "quoteIdSchema": { "type": "string", "format": "uuid" }, "quoteNumberPrefixSchema": { "type": "string", "maxLength": 16, "pattern": "^[A-Z0-9-]*$" }, "quoteNumberSchema": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 }, "quoteSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/quoteIdSchema" }, "organizationId": { "type": "string", "format": "uuid" }, "customerId": { "type": "integer", "exclusiveMinimum": 0 }, "number": { "$ref": "#/components/schemas/quoteNumberSchema" }, "numberPrefix": { "$ref": "#/components/schemas/quoteNumberPrefixSchema" }, "displayNumber": { "$ref": "#/components/schemas/quoteDisplayNumberSchema" }, "version": { "type": "integer", "minimum": 1 }, "supersedesQuoteId": { "type": [ "string", "null" ], "format": "uuid" }, "status": { "$ref": "#/components/schemas/quoteStatusSchema" }, "quoteTemplateId": { "type": [ "string", "null" ], "format": "uuid" }, "title": { "type": "string" }, "intro": { "type": [ "string", "null" ] }, "terms": { "type": [ "string", "null" ] }, "validUntil": { "type": [ "string", "null" ] }, "currency": { "$ref": "#/components/schemas/quoteCurrencyCodeSchema" }, "vatInclusive": { "type": "boolean" }, "discountKind": { "$ref": "#/components/schemas/discountKindSchema" }, "discountValue": { "type": "number" }, "discountDescription": { "$ref": "#/components/schemas/discountDescriptionSchema" }, "promotionId": { "type": [ "string", "null" ], "format": "uuid" }, "subtotalExcl": { "type": "number" }, "vatTotal": { "type": "number" }, "totalIncl": { "type": "number" }, "publicMessage": { "type": [ "string", "null" ] }, "sentAt": { "type": [ "string", "null" ], "format": "date-time" }, "acceptedAt": { "type": [ "string", "null" ], "format": "date-time" }, "rejectedAt": { "type": [ "string", "null" ], "format": "date-time" }, "expiredAt": { "type": [ "string", "null" ], "format": "date-time" }, "withdrawnAt": { "type": [ "string", "null" ], "format": "date-time" }, "hasUnpublishedChanges": { "type": "boolean" }, "acceptedByName": { "type": [ "string", "null" ] }, "acceptedByEmail": { "type": [ "string", "null" ] }, "acceptedIp": { "type": [ "string", "null" ] }, "acceptedUserAgent": { "type": [ "string", "null" ] }, "acceptedOptionalLineIds": { "type": "array", "items": { "type": "string", "format": "uuid" } }, "archivedAt": { "type": [ "string", "null" ], "format": "date-time" }, "signatureProvider": { "type": [ "string", "null" ] }, "signatureEnvelopeId": { "type": [ "string", "null" ] }, "signedPdfUrl": { "type": [ "string", "null" ] }, "invoiceId": { "type": [ "string", "null" ], "format": "uuid" }, "issueDate": { "type": [ "string", "null" ] }, "vatRegime": { "$ref": "#/components/schemas/documentVatRegimeSchema" }, "sellerName": { "type": [ "string", "null" ] }, "sellerAddressLine1": { "type": [ "string", "null" ] }, "sellerAddressLine2": { "type": [ "string", "null" ] }, "sellerAddressPostalCode": { "type": [ "string", "null" ] }, "sellerAddressCity": { "type": [ "string", "null" ] }, "sellerAddressCountry": { "type": [ "string", "null" ] }, "sellerEmail": { "type": [ "string", "null" ] }, "sellerPhone": { "type": [ "string", "null" ] }, "sellerKvk": { "type": [ "string", "null" ] }, "sellerBtw": { "type": [ "string", "null" ] }, "sellerIban": { "type": [ "string", "null" ] }, "buyerName": { "type": [ "string", "null" ] }, "buyerAddressLine1": { "type": [ "string", "null" ] }, "buyerAddressLine2": { "type": [ "string", "null" ] }, "buyerAddressPostalCode": { "type": [ "string", "null" ] }, "buyerAddressCity": { "type": [ "string", "null" ] }, "buyerAddressCountry": { "type": [ "string", "null" ] }, "buyerEmail": { "type": [ "string", "null" ] }, "buyerKvk": { "type": [ "string", "null" ] }, "buyerBtw": { "type": [ "string", "null" ] }, "termsRichText": { "type": [ "string", "null" ], "maxLength": 100000 }, "termsUrl": { "type": [ "string", "null" ] }, "termsPdfFileId": { "type": [ "string", "null" ], "format": "uuid" }, "termsPdfVersionId": { "type": [ "string", "null" ], "format": "uuid" }, "depositKind": { "$ref": "#/components/schemas/quoteDepositKindSchema" }, "depositValue": { "type": "number", "minimum": 0 }, "depositRequiredToAccept": { "type": "boolean" }, "changesRequestedAt": { "type": [ "string", "null" ], "format": "date-time" }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "updatedBy": { "type": [ "string", "null" ], "format": "uuid" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "customerId", "number", "numberPrefix", "displayNumber", "version", "supersedesQuoteId", "status", "quoteTemplateId", "title", "intro", "terms", "validUntil", "currency", "vatInclusive", "discountKind", "discountValue", "discountDescription", "promotionId", "subtotalExcl", "vatTotal", "totalIncl", "publicMessage", "sentAt", "acceptedAt", "rejectedAt", "expiredAt", "withdrawnAt", "hasUnpublishedChanges", "acceptedByName", "acceptedByEmail", "acceptedIp", "acceptedUserAgent", "acceptedOptionalLineIds", "archivedAt", "signatureProvider", "signatureEnvelopeId", "signedPdfUrl", "invoiceId", "issueDate", "vatRegime", "sellerName", "sellerAddressLine1", "sellerAddressLine2", "sellerAddressPostalCode", "sellerAddressCity", "sellerAddressCountry", "sellerEmail", "sellerPhone", "sellerKvk", "sellerBtw", "sellerIban", "buyerName", "buyerAddressLine1", "buyerAddressLine2", "buyerAddressPostalCode", "buyerAddressCity", "buyerAddressCountry", "buyerEmail", "buyerKvk", "buyerBtw", "termsRichText", "termsUrl", "termsPdfFileId", "termsPdfVersionId", "depositKind", "depositValue", "depositRequiredToAccept", "changesRequestedAt", "createdBy", "updatedBy", "createdAt", "updatedAt" ] }, "quoteSendInputSchema": { "type": "object", "properties": { "publicMessage": { "type": [ "string", "null" ], "maxLength": 20000 }, "validUntil": { "allOf": [ { "$ref": "#/components/schemas/isoDateOrNull" } ] } } }, "quoteStatusSchema": { "type": "string", "enum": [ "draft", "sent", "accepted", "rejected", "expired", "withdrawn", "changes_requested" ] } } ``` # Set quote task links. (/openapi/setQuoteTasks) Replace the tasks linked to a quote. ## PUT /v1/organizations/{organizationId}/quotes/{quoteId}/tasks ```json { "operationId": "setQuoteTasks", "summary": "Set quote task links.", "description": "Replace the tasks linked to a quote.", "tags": [ "Quotes" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "quoteId", "required": true, "schema": { "$ref": "#/components/schemas/quoteIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/quoteTaskLinksInputSchema" } } }, "description": "Task ids to link to the quote." }, "responses": { "200": { "description": "Returns the linked task ids.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/quoteTaskLinksResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "organizationIdSchema": { "type": "string", "format": "uuid" }, "quoteIdSchema": { "type": "string", "format": "uuid" }, "quoteTaskLinksInputSchema": { "type": "object", "properties": { "taskIds": { "type": "array", "items": { "type": "string", "format": "uuid" }, "maxItems": 200 } }, "required": [ "taskIds" ] }, "quoteTaskLinksResponseSchema": { "type": "object", "properties": { "taskIds": { "type": "array", "items": { "type": "string", "format": "uuid" } } }, "required": [ "taskIds" ] } } ``` # Advance a quote lifecycle stage. (/openapi/advanceQuoteLifecycle) Moves the quote to a manual milestone of its lifecycle. Only stages the lifecycle marks as manually advanceable are accepted. ## POST /v1/organizations/{organizationId}/quotes/{quoteId}/lifecycle/advance ```json { "operationId": "advanceQuoteLifecycle", "summary": "Advance a quote lifecycle stage.", "description": "Moves the quote to a manual milestone of its lifecycle. Only stages the lifecycle marks as manually advanceable are accepted.", "tags": [ "Quotes" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "quoteId", "required": true, "schema": { "$ref": "#/components/schemas/quoteIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/lifecycleAdvanceInputSchema" } } }, "description": "The stage key to move to.", "required": true }, "responses": { "200": { "description": "The stage was recorded.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/publicApiSuccessResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "lifecycleAdvanceInputSchema": { "type": "object", "properties": { "stageKey": { "type": "string", "minLength": 1, "maxLength": 80 } }, "required": [ "stageKey" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "publicApiSuccessResponseSchema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [ true ] } }, "required": [ "success" ] }, "quoteIdSchema": { "type": "string", "format": "uuid" } } ``` # Update a quote note. (/openapi/updateQuoteNote) Update an internal note on a quote. ## PATCH /v1/organizations/{organizationId}/quotes/{quoteId}/notes/{noteId} ```json { "operationId": "updateQuoteNote", "summary": "Update a quote note.", "description": "Update an internal note on a quote.", "tags": [ "Quotes" ], "parameters": [ { "in": "path", "name": "noteId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "quoteId", "required": true, "schema": { "$ref": "#/components/schemas/quoteIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/quoteNoteInputSchema" } } }, "description": "The quote note payload." }, "responses": { "200": { "description": "Returns the updated quote note.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/quoteNoteSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "collaborationBlockSchema": { "type": "object", "discriminator": { "propertyName": "type" }, "oneOf": [ { "type": "object", "properties": { "type": { "type": "string", "enum": [ "paragraph" ] }, "content": { "$ref": "#/components/schemas/collaborationInlineContentSchema" } }, "required": [ "type", "content" ], "additionalProperties": false }, { "type": "object", "properties": { "type": { "type": "string", "enum": [ "bullet-list" ] }, "items": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationInlineContentSchema" }, "minItems": 1, "maxItems": 500 } }, "required": [ "type", "items" ], "additionalProperties": false }, { "type": "object", "properties": { "type": { "type": "string", "enum": [ "numbered-list" ] }, "items": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationInlineContentSchema" }, "minItems": 1, "maxItems": 500 } }, "required": [ "type", "items" ], "additionalProperties": false } ] }, "collaborationCommentAuthorSchema": { "type": "object", "properties": { "userId": { "type": "string", "format": "uuid" }, "displayName": { "type": "string", "minLength": 1 }, "avatarUrl": { "type": [ "string", "null" ], "format": "uri" } }, "required": [ "userId", "displayName", "avatarUrl" ] }, "collaborationComposerInputSchema": { "type": "object", "properties": { "text": { "type": "string", "minLength": 1, "maxLength": 20000 }, "selections": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationComposerSelectionSchema" }, "maxItems": 100 } }, "required": [ "text", "selections" ], "additionalProperties": false }, "collaborationComposerSelectionSchema": { "type": "object", "properties": { "start": { "type": "integer", "minimum": 0, "maximum": 20000 }, "end": { "type": "integer", "exclusiveMinimum": 0, "maximum": 20000 }, "reference": { "$ref": "#/components/schemas/collaborationReferenceIdSchema" } }, "required": [ "start", "end", "reference" ], "additionalProperties": false }, "collaborationDocumentSchema": { "type": "object", "properties": { "version": { "type": "integer", "enum": [ 1 ] }, "blocks": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationBlockSchema" }, "minItems": 1, "maxItems": 1000 } }, "required": [ "version", "blocks" ], "additionalProperties": false }, "collaborationInlineContentSchema": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationInlineSchema" }, "minItems": 1, "maxItems": 1000 }, "collaborationInlineSchema": { "type": "object", "discriminator": { "propertyName": "type" }, "oneOf": [ { "type": "object", "properties": { "type": { "type": "string", "enum": [ "text" ] }, "text": { "type": "string", "minLength": 1, "maxLength": 20000 } }, "required": [ "type", "text" ], "additionalProperties": false }, { "type": "object", "properties": { "type": { "type": "string", "enum": [ "reference" ] }, "reference": { "$ref": "#/components/schemas/collaborationReferenceSchema" } }, "required": [ "type", "reference" ], "additionalProperties": false } ] }, "collaborationReferenceIdSchema": { "type": "object", "discriminator": { "propertyName": "kind" }, "oneOf": [ { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "user" ] }, "id": { "type": "string", "format": "uuid" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "team" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "task" ] }, "id": { "type": "string", "format": "uuid" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "customer" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "quote" ] }, "id": { "$ref": "#/components/schemas/quoteIdSchema" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "invoice" ] }, "id": { "$ref": "#/components/schemas/invoiceIdSchema" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "product" ] }, "id": { "$ref": "#/components/schemas/productIdSchema" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "customer-asset" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" } }, "required": [ "kind", "id" ], "additionalProperties": false } ] }, "collaborationReferenceSchema": { "type": "object", "discriminator": { "propertyName": "kind" }, "oneOf": [ { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "user" ] }, "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "team" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "task" ] }, "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "customer" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "quote" ] }, "id": { "$ref": "#/components/schemas/quoteIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "invoice" ] }, "id": { "$ref": "#/components/schemas/invoiceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "product" ] }, "id": { "$ref": "#/components/schemas/productIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "customer-asset" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false } ] }, "invoiceIdSchema": { "type": "string", "format": "uuid" }, "numericReferenceIdSchema": { "type": "string", "pattern": "^[1-9]\\d*$" }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "productIdSchema": { "type": "string", "format": "uuid" }, "quoteIdSchema": { "type": "string", "format": "uuid" }, "quoteNoteInputSchema": { "$ref": "#/components/schemas/collaborationComposerInputSchema" }, "quoteNoteSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "quoteId": { "$ref": "#/components/schemas/quoteIdSchema" }, "organizationId": { "type": "string", "format": "uuid" }, "contentText": { "type": "string", "minLength": 1, "maxLength": 20000 }, "contentDoc": { "$ref": "#/components/schemas/collaborationDocumentSchema" }, "authorId": { "type": [ "string", "null" ], "format": "uuid" }, "author": { "anyOf": [ { "$ref": "#/components/schemas/collaborationCommentAuthorSchema" }, { "type": "null" } ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "quoteId", "organizationId", "contentText", "contentDoc", "authorId", "author", "createdAt", "updatedAt" ] } } ``` # Delete a quote note. (/openapi/deleteQuoteNote) Delete an internal note from a quote. ## DELETE /v1/organizations/{organizationId}/quotes/{quoteId}/notes/{noteId} ```json { "operationId": "deleteQuoteNote", "summary": "Delete a quote note.", "description": "Delete an internal note from a quote.", "tags": [ "Quotes" ], "parameters": [ { "in": "path", "name": "noteId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "quoteId", "required": true, "schema": { "$ref": "#/components/schemas/quoteIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns a success payload when the note is deleted.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/publicApiSuccessResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "organizationIdSchema": { "type": "string", "format": "uuid" }, "publicApiSuccessResponseSchema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [ true ] } }, "required": [ "success" ] }, "quoteIdSchema": { "type": "string", "format": "uuid" } } ``` # Request changes to a quote. (/openapi/requestPortalQuoteChanges) The customer asks for changes to the sent quote from the portal page. The quote moves to Changes requested, its reminders stop, and the team is notified. Guests can call this; the quote must be published and still open. ## POST /v1/public/portal/{orgSlug}/quotes/{quoteId}/request-changes ```json { "operationId": "requestPortalQuoteChanges", "summary": "Request changes to a quote.", "description": "The customer asks for changes to the sent quote from the portal page. The quote moves to Changes requested, its reminders stop, and the team is notified. Guests can call this; the quote must be published and still open.", "tags": [ "Quotes" ], "parameters": [ { "in": "path", "name": "orgSlug", "required": true, "schema": { "$ref": "#/components/schemas/organizationSlugSchema" }, "example": "example" }, { "in": "path", "name": "quoteId", "required": true, "schema": { "$ref": "#/components/schemas/quoteIdSchema" }, "example": "123" } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/quoteRequestChangesInputSchema" } } }, "description": "What the customer wants changed.", "required": true }, "responses": { "200": { "description": "The change request was recorded.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/portalQuoteRequestChangesResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "organizationSlugSchema": { "type": "string", "minLength": 2, "maxLength": 64, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" }, "portalQuoteRequestChangesResponseSchema": { "type": "object", "properties": { "quoteId": { "type": "string", "format": "uuid" } }, "required": [ "quoteId" ] }, "quoteIdSchema": { "type": "string", "format": "uuid" }, "quoteRequestChangesInputSchema": { "type": "object", "properties": { "name": { "type": "string", "minLength": 1, "maxLength": 240 }, "email": { "type": "string", "format": "email", "maxLength": 320 }, "comment": { "type": "string", "minLength": 1, "maxLength": 20000 } }, "required": [ "name", "email", "comment" ], "examples": [ { "name": "Alex Customer", "email": "alex@example.com", "comment": "Could you split the installation into two visits?" } ] } } ``` # List shipments. (/openapi/listShipments) Return outbound deliveries, inbound receipts, and returns, filtered by order or purchase order. ## GET /v1/organizations/{organizationId}/shipments ```json { "operationId": "listShipments", "summary": "List shipments.", "description": "Return outbound deliveries, inbound receipts, and returns, filtered by order or purchase order.", "tags": [ "Shipments" ], "parameters": [ { "in": "query", "name": "q", "required": false, "schema": { "type": "string", "maxLength": 200 }, "example": "example" }, { "in": "query", "name": "direction", "required": false, "schema": { "allOf": [ { "$ref": "#/components/schemas/shipmentDirectionSchema" } ] }, "example": "example" }, { "in": "query", "name": "status", "required": false, "schema": { "allOf": [ { "$ref": "#/components/schemas/shipmentStatusSchema" } ] }, "example": "example" }, { "in": "query", "name": "orderId", "required": false, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "query", "name": "purchaseOrderId", "required": false, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "query", "name": "offset", "required": true, "schema": { "default": 0, "type": "integer", "minimum": 0, "maximum": 9007199254740991 }, "example": 1 }, { "in": "query", "name": "limit", "required": true, "schema": { "default": 50, "type": "integer", "minimum": 1, "maximum": 100 }, "example": 1 }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns shipments.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/shipmentsResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "listPaginationSchema": { "type": "object", "properties": { "offset": { "type": "integer", "minimum": 0 }, "limit": { "type": "integer", "minimum": 1, "maximum": 100 }, "total": { "type": "integer", "minimum": 0 }, "hasMore": { "type": "boolean" }, "totalIsEstimate": { "type": "boolean" } }, "required": [ "offset", "limit", "total", "hasMore" ] }, "orderDeliveryMethodSchema": { "type": "string", "enum": [ "pickup", "own_delivery", "carrier", "digital", "none" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "shipmentDirectionSchema": { "type": "string", "enum": [ "outbound", "inbound", "return" ] }, "shipmentStatusSchema": { "type": "string", "enum": [ "draft", "packed", "ready_for_pickup", "in_transit", "delivered", "picked_up", "exception", "cancelled" ] }, "shipmentSummarySchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "number": { "type": "integer" }, "formattedNumber": { "type": "string" }, "direction": { "$ref": "#/components/schemas/shipmentDirectionSchema" }, "status": { "$ref": "#/components/schemas/shipmentStatusSchema" }, "deliveryMethod": { "$ref": "#/components/schemas/orderDeliveryMethodSchema" }, "orderId": { "type": [ "string", "null" ], "format": "uuid" }, "orderNumber": { "type": [ "string", "null" ] }, "purchaseOrderId": { "type": [ "string", "null" ], "format": "uuid" }, "purchaseOrderNumber": { "type": [ "string", "null" ] }, "partyName": { "type": [ "string", "null" ] }, "carrier": { "type": [ "string", "null" ] }, "trackingNumber": { "type": [ "string", "null" ] }, "trackingUrl": { "type": [ "string", "null" ] }, "packageCount": { "type": "integer" }, "shippedAt": { "type": [ "string", "null" ] }, "deliveredAt": { "type": [ "string", "null" ] }, "createdAt": { "type": "string" }, "updatedAt": { "type": "string" } }, "required": [ "id", "number", "formattedNumber", "direction", "status", "deliveryMethod", "orderId", "orderNumber", "purchaseOrderId", "purchaseOrderNumber", "partyName", "carrier", "trackingNumber", "trackingUrl", "packageCount", "shippedAt", "deliveredAt", "createdAt", "updatedAt" ] }, "shipmentsResponseSchema": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/shipmentSummarySchema" } }, "pagination": { "$ref": "#/components/schemas/listPaginationSchema" } }, "required": [ "data", "pagination" ] } } ``` # Get a shipment. (/openapi/getShipment) Return one shipment with its lines, carrier, and tracking details. ## GET /v1/organizations/{organizationId}/shipments/{shipmentId} ```json { "operationId": "getShipment", "summary": "Get a shipment.", "description": "Return one shipment with its lines, carrier, and tracking details.", "tags": [ "Shipments" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "shipmentId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns the shipment.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/shipmentSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "orderDeliveryMethodSchema": { "type": "string", "enum": [ "pickup", "own_delivery", "carrier", "digital", "none" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "shipmentDirectionSchema": { "type": "string", "enum": [ "outbound", "inbound", "return" ] }, "shipmentLineSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "orderLineId": { "type": [ "string", "null" ], "format": "uuid" }, "purchaseOrderLineId": { "type": [ "string", "null" ], "format": "uuid" }, "productId": { "type": [ "string", "null" ], "format": "uuid" }, "variantId": { "type": [ "string", "null" ], "format": "uuid" }, "title": { "type": "string" }, "sku": { "type": [ "string", "null" ] }, "quantity": { "type": "number" }, "lotNumber": { "type": [ "string", "null" ] }, "serialNumber": { "type": [ "string", "null" ] }, "expiresOn": { "type": [ "string", "null" ] }, "returnDisposition": { "anyOf": [ { "$ref": "#/components/schemas/shipmentReturnDispositionSchema" }, { "type": "null" } ] }, "position": { "type": "number" } }, "required": [ "id", "orderLineId", "purchaseOrderLineId", "productId", "variantId", "title", "sku", "quantity", "lotNumber", "serialNumber", "expiresOn", "returnDisposition", "position" ] }, "shipmentReturnDispositionSchema": { "type": "string", "enum": [ "restock", "quarantine", "write_off" ] }, "shipmentSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "number": { "type": "integer" }, "formattedNumber": { "type": "string" }, "direction": { "$ref": "#/components/schemas/shipmentDirectionSchema" }, "status": { "$ref": "#/components/schemas/shipmentStatusSchema" }, "deliveryMethod": { "$ref": "#/components/schemas/orderDeliveryMethodSchema" }, "orderId": { "type": [ "string", "null" ], "format": "uuid" }, "orderNumber": { "type": [ "string", "null" ] }, "purchaseOrderId": { "type": [ "string", "null" ], "format": "uuid" }, "purchaseOrderNumber": { "type": [ "string", "null" ] }, "partyName": { "type": [ "string", "null" ] }, "carrier": { "type": [ "string", "null" ] }, "trackingNumber": { "type": [ "string", "null" ] }, "trackingUrl": { "type": [ "string", "null" ] }, "packageCount": { "type": "integer" }, "shippedAt": { "type": [ "string", "null" ] }, "deliveredAt": { "type": [ "string", "null" ] }, "createdAt": { "type": "string" }, "updatedAt": { "type": "string" }, "service": { "type": [ "string", "null" ] }, "weightGrams": { "type": [ "integer", "null" ] }, "shipTo": { "type": "object", "properties": { "name": { "type": [ "string", "null" ] }, "line1": { "type": [ "string", "null" ] }, "line2": { "type": [ "string", "null" ] }, "postalCode": { "type": [ "string", "null" ] }, "city": { "type": [ "string", "null" ] }, "country": { "type": [ "string", "null" ] } }, "required": [ "name", "line1", "line2", "postalCode", "city", "country" ] }, "locationId": { "type": [ "string", "null" ], "format": "uuid" }, "locationName": { "type": [ "string", "null" ] }, "agendaItemId": { "type": [ "string", "null" ], "format": "uuid" }, "notes": { "type": [ "string", "null" ] }, "statusOccurredAt": { "type": [ "string", "null" ] }, "lines": { "type": "array", "items": { "$ref": "#/components/schemas/shipmentLineSchema" } } }, "required": [ "id", "number", "formattedNumber", "direction", "status", "deliveryMethod", "orderId", "orderNumber", "purchaseOrderId", "purchaseOrderNumber", "partyName", "carrier", "trackingNumber", "trackingUrl", "packageCount", "shippedAt", "deliveredAt", "createdAt", "updatedAt", "service", "weightGrams", "shipTo", "locationId", "locationName", "agendaItemId", "notes", "statusOccurredAt", "lines" ] }, "shipmentStatusSchema": { "type": "string", "enum": [ "draft", "packed", "ready_for_pickup", "in_transit", "delivered", "picked_up", "exception", "cancelled" ] } } ``` # Ship a shipment. (/openapi/shipShipment) Mark an outbound shipment as shipped: stock leaves the warehouse and the order's fulfillment progresses. Returns 409 `APPROVAL_REQUIRED` with an `approvalId` when an approval policy wants a decision first. ## POST /v1/organizations/{organizationId}/shipments/{shipmentId}/ship ```json { "operationId": "shipShipment", "summary": "Ship a shipment.", "description": "Mark an outbound shipment as shipped: stock leaves the warehouse and the order's fulfillment progresses. Returns 409 `APPROVAL_REQUIRED` with an `approvalId` when an approval policy wants a decision first.", "tags": [ "Shipments" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "shipmentId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns the shipped shipment.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/shipmentSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "orderDeliveryMethodSchema": { "type": "string", "enum": [ "pickup", "own_delivery", "carrier", "digital", "none" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "shipmentDirectionSchema": { "type": "string", "enum": [ "outbound", "inbound", "return" ] }, "shipmentLineSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "orderLineId": { "type": [ "string", "null" ], "format": "uuid" }, "purchaseOrderLineId": { "type": [ "string", "null" ], "format": "uuid" }, "productId": { "type": [ "string", "null" ], "format": "uuid" }, "variantId": { "type": [ "string", "null" ], "format": "uuid" }, "title": { "type": "string" }, "sku": { "type": [ "string", "null" ] }, "quantity": { "type": "number" }, "lotNumber": { "type": [ "string", "null" ] }, "serialNumber": { "type": [ "string", "null" ] }, "expiresOn": { "type": [ "string", "null" ] }, "returnDisposition": { "anyOf": [ { "$ref": "#/components/schemas/shipmentReturnDispositionSchema" }, { "type": "null" } ] }, "position": { "type": "number" } }, "required": [ "id", "orderLineId", "purchaseOrderLineId", "productId", "variantId", "title", "sku", "quantity", "lotNumber", "serialNumber", "expiresOn", "returnDisposition", "position" ] }, "shipmentReturnDispositionSchema": { "type": "string", "enum": [ "restock", "quarantine", "write_off" ] }, "shipmentSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "number": { "type": "integer" }, "formattedNumber": { "type": "string" }, "direction": { "$ref": "#/components/schemas/shipmentDirectionSchema" }, "status": { "$ref": "#/components/schemas/shipmentStatusSchema" }, "deliveryMethod": { "$ref": "#/components/schemas/orderDeliveryMethodSchema" }, "orderId": { "type": [ "string", "null" ], "format": "uuid" }, "orderNumber": { "type": [ "string", "null" ] }, "purchaseOrderId": { "type": [ "string", "null" ], "format": "uuid" }, "purchaseOrderNumber": { "type": [ "string", "null" ] }, "partyName": { "type": [ "string", "null" ] }, "carrier": { "type": [ "string", "null" ] }, "trackingNumber": { "type": [ "string", "null" ] }, "trackingUrl": { "type": [ "string", "null" ] }, "packageCount": { "type": "integer" }, "shippedAt": { "type": [ "string", "null" ] }, "deliveredAt": { "type": [ "string", "null" ] }, "createdAt": { "type": "string" }, "updatedAt": { "type": "string" }, "service": { "type": [ "string", "null" ] }, "weightGrams": { "type": [ "integer", "null" ] }, "shipTo": { "type": "object", "properties": { "name": { "type": [ "string", "null" ] }, "line1": { "type": [ "string", "null" ] }, "line2": { "type": [ "string", "null" ] }, "postalCode": { "type": [ "string", "null" ] }, "city": { "type": [ "string", "null" ] }, "country": { "type": [ "string", "null" ] } }, "required": [ "name", "line1", "line2", "postalCode", "city", "country" ] }, "locationId": { "type": [ "string", "null" ], "format": "uuid" }, "locationName": { "type": [ "string", "null" ] }, "agendaItemId": { "type": [ "string", "null" ], "format": "uuid" }, "notes": { "type": [ "string", "null" ] }, "statusOccurredAt": { "type": [ "string", "null" ] }, "lines": { "type": "array", "items": { "$ref": "#/components/schemas/shipmentLineSchema" } } }, "required": [ "id", "number", "formattedNumber", "direction", "status", "deliveryMethod", "orderId", "orderNumber", "purchaseOrderId", "purchaseOrderNumber", "partyName", "carrier", "trackingNumber", "trackingUrl", "packageCount", "shippedAt", "deliveredAt", "createdAt", "updatedAt", "service", "weightGrams", "shipTo", "locationId", "locationName", "agendaItemId", "notes", "statusOccurredAt", "lines" ] }, "shipmentStatusSchema": { "type": "string", "enum": [ "draft", "packed", "ready_for_pickup", "in_transit", "delivered", "picked_up", "exception", "cancelled" ] } } ``` # List tags. (/openapi/listTags) Return tags for an organization, optionally filtered by kind. ## GET /v1/organizations/{organizationId}/tags ```json { "operationId": "listTags", "summary": "List tags.", "description": "Return tags for an organization, optionally filtered by kind.", "tags": [ "Tags" ], "parameters": [ { "in": "query", "name": "kind", "required": false, "schema": { "allOf": [ { "$ref": "#/components/schemas/tagKindSchema" } ] }, "example": "example" }, { "in": "query", "name": "offset", "required": false, "schema": { "type": "string" }, "example": "example" }, { "in": "query", "name": "limit", "required": false, "schema": { "type": "string" }, "example": "example" }, { "in": "query", "name": "q", "required": false, "schema": { "type": "string" }, "example": "example" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns paginated tags.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/tagsListResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "organizationIdSchema": { "type": "string", "format": "uuid" }, "tagKindSchema": { "type": "string", "enum": [ "task", "customer", "product" ] }, "tagsListResponseSchema": { "type": "object" } } ``` # Create a tag. (/openapi/createTag) Create a tag for the specified organization. ## POST /v1/organizations/{organizationId}/tags ```json { "operationId": "createTag", "summary": "Create a tag.", "description": "Create a tag for the specified organization.", "tags": [ "Tags" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/tagCreateInputSchema" } } }, "description": "The tag payload.", "required": true }, "responses": { "201": { "description": "Returns the created tag.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/organizationTagSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "organizationIdSchema": { "type": "string", "format": "uuid" }, "organizationTagSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/tagIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "kind": { "$ref": "#/components/schemas/tagKindSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 80 }, "slug": { "$ref": "#/components/schemas/tagSlugSchema" }, "color": { "anyOf": [ { "$ref": "#/components/schemas/tagColorSchema" }, { "type": "null" } ] }, "description": { "type": [ "string", "null" ] }, "position": { "type": "integer", "minimum": 0 }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "kind", "name", "slug", "color", "description", "position", "createdAt", "updatedAt" ] }, "tagColorSchema": { "type": "string", "pattern": "^#(?:[0-9a-f]{3}|[0-9a-f]{6})$" }, "tagCreateInputSchema": { "type": "object", "properties": { "kind": { "$ref": "#/components/schemas/tagKindSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 80 }, "slug": { "allOf": [ { "$ref": "#/components/schemas/tagSlugSchema" } ] }, "color": { "type": [ "string", "null" ], "pattern": "^#(?:[0-9a-f]{3}|[0-9a-f]{6})$" }, "description": { "type": [ "string", "null" ], "maxLength": 4000 } }, "required": [ "kind", "name" ] }, "tagIdSchema": { "type": "string", "format": "uuid" }, "tagKindSchema": { "type": "string", "enum": [ "task", "customer", "product" ] }, "tagSlugSchema": { "type": "string", "minLength": 1, "maxLength": 160, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" } } ``` # Get a tag. (/openapi/getTag) Return a tag for the specified organization. ## GET /v1/organizations/{organizationId}/tags/{tagId} ```json { "operationId": "getTag", "summary": "Get a tag.", "description": "Return a tag for the specified organization.", "tags": [ "Tags" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "tagId", "required": true, "schema": { "$ref": "#/components/schemas/tagIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns the requested tag.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/organizationTagSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "organizationIdSchema": { "type": "string", "format": "uuid" }, "organizationTagSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/tagIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "kind": { "$ref": "#/components/schemas/tagKindSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 80 }, "slug": { "$ref": "#/components/schemas/tagSlugSchema" }, "color": { "anyOf": [ { "$ref": "#/components/schemas/tagColorSchema" }, { "type": "null" } ] }, "description": { "type": [ "string", "null" ] }, "position": { "type": "integer", "minimum": 0 }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "kind", "name", "slug", "color", "description", "position", "createdAt", "updatedAt" ] }, "tagColorSchema": { "type": "string", "pattern": "^#(?:[0-9a-f]{3}|[0-9a-f]{6})$" }, "tagIdSchema": { "type": "string", "format": "uuid" }, "tagKindSchema": { "type": "string", "enum": [ "task", "customer", "product" ] }, "tagSlugSchema": { "type": "string", "minLength": 1, "maxLength": 160, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" } } ``` # Update a tag. (/openapi/updateTag) Update a tag for the specified organization. ## PATCH /v1/organizations/{organizationId}/tags/{tagId} ```json { "operationId": "updateTag", "summary": "Update a tag.", "description": "Update a tag for the specified organization.", "tags": [ "Tags" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "tagId", "required": true, "schema": { "$ref": "#/components/schemas/tagIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/tagUpdateInputSchema" } } }, "description": "The tag patch payload.", "required": true }, "responses": { "200": { "description": "Returns the updated tag.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/organizationTagSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "organizationIdSchema": { "type": "string", "format": "uuid" }, "organizationTagSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/tagIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "kind": { "$ref": "#/components/schemas/tagKindSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 80 }, "slug": { "$ref": "#/components/schemas/tagSlugSchema" }, "color": { "anyOf": [ { "$ref": "#/components/schemas/tagColorSchema" }, { "type": "null" } ] }, "description": { "type": [ "string", "null" ] }, "position": { "type": "integer", "minimum": 0 }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "kind", "name", "slug", "color", "description", "position", "createdAt", "updatedAt" ] }, "tagColorSchema": { "type": "string", "pattern": "^#(?:[0-9a-f]{3}|[0-9a-f]{6})$" }, "tagIdSchema": { "type": "string", "format": "uuid" }, "tagKindSchema": { "type": "string", "enum": [ "task", "customer", "product" ] }, "tagSlugSchema": { "type": "string", "minLength": 1, "maxLength": 160, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" }, "tagUpdateInputSchema": { "type": "object", "properties": { "kind": { "allOf": [ { "$ref": "#/components/schemas/tagKindSchema" } ] }, "name": { "type": "string", "minLength": 1, "maxLength": 80 }, "slug": { "allOf": [ { "$ref": "#/components/schemas/tagSlugSchema" } ] }, "color": { "type": [ "string", "null" ], "pattern": "^#(?:[0-9a-f]{3}|[0-9a-f]{6})$" }, "description": { "type": [ "string", "null" ], "maxLength": 4000 } } } } ``` # Delete a tag. (/openapi/deleteTag) Delete a tag for the specified organization. ## DELETE /v1/organizations/{organizationId}/tags/{tagId} ```json { "operationId": "deleteTag", "summary": "Delete a tag.", "description": "Delete a tag for the specified organization.", "tags": [ "Tags" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "tagId", "required": true, "schema": { "$ref": "#/components/schemas/tagIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns a success payload when the tag is deleted.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/publicApiSuccessResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "organizationIdSchema": { "type": "string", "format": "uuid" }, "publicApiSuccessResponseSchema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [ true ] } }, "required": [ "success" ] }, "tagIdSchema": { "type": "string", "format": "uuid" } } ``` # List tasks. (/openapi/listTasks) Return tasks for an organization the authenticated user can access. ## GET /v1/organizations/{organizationId}/tasks ```json { "operationId": "listTasks", "summary": "List tasks.", "description": "Return tasks for an organization the authenticated user can access.", "tags": [ "Tasks" ], "parameters": [ { "in": "query", "name": "status", "required": false, "schema": { "allOf": [ { "$ref": "#/components/schemas/taskStatusSchema" } ] }, "example": "example" }, { "in": "query", "name": "statuses", "required": false, "schema": { "type": "array", "items": { "$ref": "#/components/schemas/taskStatusSchema" }, "maxItems": 20 }, "example": "example" }, { "in": "query", "name": "include", "required": false, "schema": { "type": "array", "items": { "$ref": "#/components/schemas/taskIncludeFilterSchema" }, "maxItems": 2 }, "example": "example" }, { "in": "query", "name": "priority", "required": false, "schema": { "allOf": [ { "$ref": "#/components/schemas/taskPrioritySchema" } ] }, "example": "example" }, { "in": "query", "name": "priorities", "required": false, "schema": { "type": "array", "items": { "$ref": "#/components/schemas/taskPrioritySchema" }, "maxItems": 20 }, "example": "example" }, { "in": "query", "name": "dueDateBuckets", "required": false, "schema": { "type": "array", "items": { "$ref": "#/components/schemas/taskDueDateBucketSchema" }, "maxItems": 5 }, "example": "example" }, { "in": "query", "name": "assigneeId", "required": false, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "query", "name": "assigneeIds", "required": false, "schema": { "type": "array", "items": { "type": "string", "format": "uuid" }, "maxItems": 100 }, "example": "example" }, { "in": "query", "name": "unassigned", "required": false, "schema": { "allOf": [ { "$ref": "#/components/schemas/taskListBooleanSchema" } ] }, "example": "example" }, { "in": "query", "name": "customerId", "required": false, "schema": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 }, "example": 123 }, { "in": "query", "name": "customerIds", "required": false, "schema": { "type": "array", "items": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 }, "maxItems": 100 }, "example": "example" }, { "in": "query", "name": "noCustomer", "required": false, "schema": { "allOf": [ { "$ref": "#/components/schemas/taskListBooleanSchema" } ] }, "example": "example" }, { "in": "query", "name": "tagId", "required": false, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "query", "name": "tagIds", "required": false, "schema": { "type": "array", "items": { "type": "string", "format": "uuid" }, "maxItems": 100 }, "example": "example" }, { "in": "query", "name": "sortBy", "required": false, "schema": { "allOf": [ { "$ref": "#/components/schemas/taskListSortSchema" } ] }, "example": "example" }, { "in": "query", "name": "sortDirection", "required": false, "schema": { "type": "string", "enum": [ "asc", "desc" ] }, "example": "asc" }, { "in": "query", "name": "limit", "required": true, "schema": { "default": 50, "type": "integer", "minimum": 1, "maximum": 100 }, "example": 1 }, { "in": "query", "name": "offset", "required": true, "schema": { "default": 0, "type": "integer", "minimum": 0, "maximum": 9007199254740991 }, "example": 1 }, { "in": "query", "name": "q", "required": false, "schema": { "type": "string", "maxLength": 200 }, "example": "example" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns paginated tasks.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/tasksListResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "dateFilterBucketSchema": { "type": "string", "enum": [ "overdue", "today", "this_week", "later", "none" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "taskDueDateBucketSchema": { "$ref": "#/components/schemas/dateFilterBucketSchema" }, "taskIncludeFilterSchema": { "type": "string", "enum": [ "done", "archived" ] }, "taskListBooleanSchema": { "anyOf": [ { "type": "boolean" }, { "type": "boolean" } ] }, "taskListSortSchema": { "type": "string", "enum": [ "title", "status", "priority", "dueAt" ] }, "taskPrioritySchema": { "type": "string", "enum": [ "low", "medium", "high", "urgent" ] }, "taskStatusSchema": { "type": "string", "enum": [ "todo", "in_progress", "on_hold", "done" ] }, "tasksListResponseSchema": { "type": "object" } } ``` # Create a task. (/openapi/createTask) Create a task for the specified organization. ## POST /v1/organizations/{organizationId}/tasks ```json { "operationId": "createTask", "summary": "Create a task.", "description": "Create a task for the specified organization.", "tags": [ "Tasks" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/taskInputSchema" } } }, "description": "The task payload.", "required": true }, "responses": { "201": { "description": "Returns the created task.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/taskSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "isoDatetimeOrNull": { "type": [ "string", "null" ], "format": "date-time" }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "organizationTagSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/tagIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "kind": { "$ref": "#/components/schemas/tagKindSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 80 }, "slug": { "$ref": "#/components/schemas/tagSlugSchema" }, "color": { "anyOf": [ { "$ref": "#/components/schemas/tagColorSchema" }, { "type": "null" } ] }, "description": { "type": [ "string", "null" ] }, "position": { "type": "integer", "minimum": 0 }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "kind", "name", "slug", "color", "description", "position", "createdAt", "updatedAt" ] }, "tagColorSchema": { "type": "string", "pattern": "^#(?:[0-9a-f]{3}|[0-9a-f]{6})$" }, "tagIdSchema": { "type": "string", "format": "uuid" }, "tagKindSchema": { "type": "string", "enum": [ "task", "customer", "product" ] }, "tagSlugSchema": { "type": "string", "minLength": 1, "maxLength": 160, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" }, "taskAssigneeSchema": { "type": "object", "properties": { "userId": { "type": "string", "format": "uuid" }, "displayName": { "type": "string", "minLength": 1 }, "avatarUrl": { "type": [ "string", "null" ], "format": "uri" }, "disabledAt": { "type": [ "string", "null" ] } }, "required": [ "userId", "displayName", "avatarUrl", "disabledAt" ] }, "taskDisplayNumberSchema": { "type": "string", "minLength": 1, "maxLength": 40 }, "taskIdSchema": { "type": "string", "format": "uuid" }, "taskInputSchema": { "type": "object", "properties": { "title": { "type": "string", "minLength": 1, "maxLength": 240 }, "description": { "type": [ "string", "null" ], "maxLength": 20000 }, "status": { "allOf": [ { "$ref": "#/components/schemas/taskStatusSchema" } ] }, "priority": { "allOf": [ { "$ref": "#/components/schemas/taskPrioritySchema" } ] }, "startAt": { "allOf": [ { "$ref": "#/components/schemas/isoDatetimeOrNull" } ] }, "endAt": { "allOf": [ { "$ref": "#/components/schemas/isoDatetimeOrNull" } ] }, "dueAt": { "allOf": [ { "$ref": "#/components/schemas/isoDatetimeOrNull" } ] }, "customerId": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 }, "customerAssetId": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 }, "assigneeIds": { "type": "array", "items": { "type": "string", "format": "uuid" }, "maxItems": 50 }, "tagIds": { "type": "array", "items": { "type": "string", "format": "uuid" }, "maxItems": 50 }, "parentTaskId": { "anyOf": [ { "$ref": "#/components/schemas/taskIdSchema" }, { "type": "null" } ] }, "taskTemplateId": { "type": [ "string", "null" ], "format": "uuid" } }, "required": [ "title" ] }, "taskNumberSchema": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 }, "taskPrioritySchema": { "type": "string", "enum": [ "low", "medium", "high", "urgent" ] }, "taskSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/taskIdSchema" }, "organizationId": { "type": "string", "format": "uuid" }, "number": { "$ref": "#/components/schemas/taskNumberSchema" }, "displayNumber": { "$ref": "#/components/schemas/taskDisplayNumberSchema" }, "customerId": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 }, "customerAssetId": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 }, "title": { "type": "string" }, "description": { "type": [ "string", "null" ] }, "status": { "$ref": "#/components/schemas/taskStatusSchema" }, "priority": { "$ref": "#/components/schemas/taskPrioritySchema" }, "startAt": { "type": [ "string", "null" ], "format": "date-time" }, "endAt": { "type": [ "string", "null" ], "format": "date-time" }, "dueAt": { "type": [ "string", "null" ], "format": "date-time" }, "position": { "type": "number" }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "updatedBy": { "type": [ "string", "null" ], "format": "uuid" }, "archivedAt": { "type": [ "string", "null" ], "format": "date-time" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" }, "parentTaskId": { "anyOf": [ { "$ref": "#/components/schemas/taskIdSchema" }, { "type": "null" } ] }, "taskTemplateId": { "type": [ "string", "null" ], "format": "uuid" }, "assignees": { "type": "array", "items": { "$ref": "#/components/schemas/taskAssigneeSchema" } }, "tags": { "type": "array", "items": { "$ref": "#/components/schemas/organizationTagSchema" } } }, "required": [ "id", "organizationId", "number", "displayNumber", "customerId", "customerAssetId", "title", "description", "status", "priority", "startAt", "endAt", "dueAt", "position", "createdBy", "updatedBy", "archivedAt", "createdAt", "updatedAt", "parentTaskId", "taskTemplateId", "assignees", "tags" ] }, "taskStatusSchema": { "type": "string", "enum": [ "todo", "in_progress", "on_hold", "done" ] } } ``` # Get a task. (/openapi/getTask) Return task detail for the specified organization. ## GET /v1/organizations/{organizationId}/tasks/{taskId} ```json { "operationId": "getTask", "summary": "Get a task.", "description": "Return task detail for the specified organization.", "tags": [ "Tasks" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "taskId", "required": true, "schema": { "$ref": "#/components/schemas/taskIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns the requested task.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/taskDetailSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "collaborationBlockSchema": { "type": "object", "discriminator": { "propertyName": "type" }, "oneOf": [ { "type": "object", "properties": { "type": { "type": "string", "enum": [ "paragraph" ] }, "content": { "$ref": "#/components/schemas/collaborationInlineContentSchema" } }, "required": [ "type", "content" ], "additionalProperties": false }, { "type": "object", "properties": { "type": { "type": "string", "enum": [ "bullet-list" ] }, "items": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationInlineContentSchema" }, "minItems": 1, "maxItems": 500 } }, "required": [ "type", "items" ], "additionalProperties": false }, { "type": "object", "properties": { "type": { "type": "string", "enum": [ "numbered-list" ] }, "items": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationInlineContentSchema" }, "minItems": 1, "maxItems": 500 } }, "required": [ "type", "items" ], "additionalProperties": false } ] }, "collaborationCommentAuthorSchema": { "type": "object", "properties": { "userId": { "type": "string", "format": "uuid" }, "displayName": { "type": "string", "minLength": 1 }, "avatarUrl": { "type": [ "string", "null" ], "format": "uri" } }, "required": [ "userId", "displayName", "avatarUrl" ] }, "collaborationDocumentSchema": { "type": "object", "properties": { "version": { "type": "integer", "enum": [ 1 ] }, "blocks": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationBlockSchema" }, "minItems": 1, "maxItems": 1000 } }, "required": [ "version", "blocks" ], "additionalProperties": false }, "collaborationInlineContentSchema": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationInlineSchema" }, "minItems": 1, "maxItems": 1000 }, "collaborationInlineSchema": { "type": "object", "discriminator": { "propertyName": "type" }, "oneOf": [ { "type": "object", "properties": { "type": { "type": "string", "enum": [ "text" ] }, "text": { "type": "string", "minLength": 1, "maxLength": 20000 } }, "required": [ "type", "text" ], "additionalProperties": false }, { "type": "object", "properties": { "type": { "type": "string", "enum": [ "reference" ] }, "reference": { "$ref": "#/components/schemas/collaborationReferenceSchema" } }, "required": [ "type", "reference" ], "additionalProperties": false } ] }, "collaborationReferenceSchema": { "type": "object", "discriminator": { "propertyName": "kind" }, "oneOf": [ { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "user" ] }, "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "team" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "task" ] }, "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "customer" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "quote" ] }, "id": { "$ref": "#/components/schemas/quoteIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "invoice" ] }, "id": { "$ref": "#/components/schemas/invoiceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "product" ] }, "id": { "$ref": "#/components/schemas/productIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "customer-asset" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false } ] }, "invoiceIdSchema": { "type": "string", "format": "uuid" }, "numericReferenceIdSchema": { "type": "string", "pattern": "^[1-9]\\d*$" }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "organizationTagSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/tagIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "kind": { "$ref": "#/components/schemas/tagKindSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 80 }, "slug": { "$ref": "#/components/schemas/tagSlugSchema" }, "color": { "anyOf": [ { "$ref": "#/components/schemas/tagColorSchema" }, { "type": "null" } ] }, "description": { "type": [ "string", "null" ] }, "position": { "type": "integer", "minimum": 0 }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "kind", "name", "slug", "color", "description", "position", "createdAt", "updatedAt" ] }, "productIdSchema": { "type": "string", "format": "uuid" }, "quoteIdSchema": { "type": "string", "format": "uuid" }, "tagColorSchema": { "type": "string", "pattern": "^#(?:[0-9a-f]{3}|[0-9a-f]{6})$" }, "tagIdSchema": { "type": "string", "format": "uuid" }, "tagKindSchema": { "type": "string", "enum": [ "task", "customer", "product" ] }, "tagSlugSchema": { "type": "string", "minLength": 1, "maxLength": 160, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" }, "taskAssigneeSchema": { "type": "object", "properties": { "userId": { "type": "string", "format": "uuid" }, "displayName": { "type": "string", "minLength": 1 }, "avatarUrl": { "type": [ "string", "null" ], "format": "uri" }, "disabledAt": { "type": [ "string", "null" ] } }, "required": [ "userId", "displayName", "avatarUrl", "disabledAt" ] }, "taskBilledInvoiceSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "displayNumber": { "type": [ "string", "null" ], "minLength": 1 }, "status": { "type": "string", "minLength": 1 } }, "required": [ "id", "displayNumber", "status" ] }, "taskCommentAuthorSchema": { "$ref": "#/components/schemas/collaborationCommentAuthorSchema" }, "taskCommentSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "taskId": { "type": "string", "format": "uuid" }, "organizationId": { "type": "string", "format": "uuid" }, "contentText": { "type": "string", "minLength": 1, "maxLength": 20000 }, "contentDoc": { "$ref": "#/components/schemas/collaborationDocumentSchema" }, "authorId": { "type": [ "string", "null" ], "format": "uuid" }, "author": { "anyOf": [ { "$ref": "#/components/schemas/taskCommentAuthorSchema" }, { "type": "null" } ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "taskId", "organizationId", "contentText", "contentDoc", "authorId", "author", "createdAt", "updatedAt" ] }, "taskDetailSchema": { "type": "object", "properties": { "task": { "$ref": "#/components/schemas/taskSchema" }, "subtasks": { "type": "array", "items": { "$ref": "#/components/schemas/taskListItemSchema" } }, "comments": { "type": "array", "items": { "$ref": "#/components/schemas/taskCommentSchema" } }, "materials": { "type": "array", "items": { "$ref": "#/components/schemas/taskMaterialSchema" } }, "linkedQuoteIds": { "type": "array", "items": { "type": "string", "format": "uuid" } }, "linkedInvoiceIds": { "type": "array", "items": { "type": "string", "format": "uuid" } } }, "required": [ "task", "subtasks", "comments", "materials", "linkedQuoteIds", "linkedInvoiceIds" ] }, "taskDisplayNumberSchema": { "type": "string", "minLength": 1, "maxLength": 40 }, "taskIdSchema": { "type": "string", "format": "uuid" }, "taskListItemSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/taskIdSchema" }, "organizationId": { "type": "string", "format": "uuid" }, "number": { "$ref": "#/components/schemas/taskNumberSchema" }, "displayNumber": { "$ref": "#/components/schemas/taskDisplayNumberSchema" }, "customerId": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 }, "customerAssetId": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 }, "title": { "type": "string" }, "status": { "$ref": "#/components/schemas/taskStatusSchema" }, "priority": { "$ref": "#/components/schemas/taskPrioritySchema" }, "startAt": { "type": [ "string", "null" ], "format": "date-time" }, "endAt": { "type": [ "string", "null" ], "format": "date-time" }, "dueAt": { "type": [ "string", "null" ], "format": "date-time" }, "position": { "type": "number" }, "updatedAt": { "type": "string", "format": "date-time" }, "parentTaskId": { "anyOf": [ { "$ref": "#/components/schemas/taskIdSchema" }, { "type": "null" } ] }, "assignees": { "type": "array", "items": { "$ref": "#/components/schemas/taskAssigneeSchema" } }, "tags": { "type": "array", "items": { "$ref": "#/components/schemas/organizationTagSchema" } }, "subtaskTotal": { "type": "integer", "minimum": 0 }, "subtaskDone": { "type": "integer", "minimum": 0 }, "commentCount": { "type": "integer", "minimum": 0 } }, "required": [ "id", "organizationId", "number", "displayNumber", "customerId", "customerAssetId", "title", "status", "priority", "startAt", "endAt", "dueAt", "position", "updatedAt", "parentTaskId", "assignees", "tags", "subtaskTotal", "subtaskDone", "commentCount" ] }, "taskMaterialSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "organizationId": { "type": "string", "format": "uuid" }, "taskId": { "$ref": "#/components/schemas/taskIdSchema" }, "productId": { "type": "string", "format": "uuid" }, "variantId": { "type": "string", "format": "uuid" }, "variantTitle": { "type": [ "string", "null" ] }, "productName": { "type": "string", "minLength": 1 }, "productSku": { "type": [ "string", "null" ] }, "productSlug": { "type": [ "string", "null" ], "minLength": 1 }, "productUnitOfMeasure": { "type": [ "string", "null" ] }, "quantity": { "type": "number", "exclusiveMinimum": 0 }, "unit": { "type": [ "string", "null" ] }, "unitPrice": { "type": "number", "minimum": 0 }, "vatRate": { "type": "number", "minimum": 0, "maximum": 100 }, "currency": { "type": [ "string", "null" ], "pattern": "^[A-Z]{3}$" }, "stockState": { "$ref": "#/components/schemas/taskMaterialStockStateSchema" }, "quoteId": { "type": [ "string", "null" ], "format": "uuid" }, "consumedAt": { "type": [ "string", "null" ], "format": "date-time" }, "notes": { "type": [ "string", "null" ] }, "position": { "type": "number", "minimum": 0 }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "updatedBy": { "type": [ "string", "null" ], "format": "uuid" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" }, "billable": { "type": "boolean" }, "billedInvoice": { "anyOf": [ { "$ref": "#/components/schemas/taskBilledInvoiceSchema" }, { "type": "null" } ] } }, "required": [ "id", "organizationId", "taskId", "productId", "variantId", "variantTitle", "productName", "productSku", "productSlug", "productUnitOfMeasure", "quantity", "unit", "unitPrice", "vatRate", "currency", "stockState", "quoteId", "consumedAt", "notes", "position", "createdBy", "updatedBy", "createdAt", "updatedAt", "billable", "billedInvoice" ] }, "taskMaterialStockStateSchema": { "type": "string", "enum": [ "untracked", "reserved", "consumed" ] }, "taskNumberSchema": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 }, "taskPrioritySchema": { "type": "string", "enum": [ "low", "medium", "high", "urgent" ] }, "taskSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/taskIdSchema" }, "organizationId": { "type": "string", "format": "uuid" }, "number": { "$ref": "#/components/schemas/taskNumberSchema" }, "displayNumber": { "$ref": "#/components/schemas/taskDisplayNumberSchema" }, "customerId": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 }, "customerAssetId": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 }, "title": { "type": "string" }, "description": { "type": [ "string", "null" ] }, "status": { "$ref": "#/components/schemas/taskStatusSchema" }, "priority": { "$ref": "#/components/schemas/taskPrioritySchema" }, "startAt": { "type": [ "string", "null" ], "format": "date-time" }, "endAt": { "type": [ "string", "null" ], "format": "date-time" }, "dueAt": { "type": [ "string", "null" ], "format": "date-time" }, "position": { "type": "number" }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "updatedBy": { "type": [ "string", "null" ], "format": "uuid" }, "archivedAt": { "type": [ "string", "null" ], "format": "date-time" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" }, "parentTaskId": { "anyOf": [ { "$ref": "#/components/schemas/taskIdSchema" }, { "type": "null" } ] }, "taskTemplateId": { "type": [ "string", "null" ], "format": "uuid" }, "assignees": { "type": "array", "items": { "$ref": "#/components/schemas/taskAssigneeSchema" } }, "tags": { "type": "array", "items": { "$ref": "#/components/schemas/organizationTagSchema" } } }, "required": [ "id", "organizationId", "number", "displayNumber", "customerId", "customerAssetId", "title", "description", "status", "priority", "startAt", "endAt", "dueAt", "position", "createdBy", "updatedBy", "archivedAt", "createdAt", "updatedAt", "parentTaskId", "taskTemplateId", "assignees", "tags" ] }, "taskStatusSchema": { "type": "string", "enum": [ "todo", "in_progress", "on_hold", "done" ] } } ``` # Update a task. (/openapi/updateTask) Update a task for the specified organization. ## PATCH /v1/organizations/{organizationId}/tasks/{taskId} ```json { "operationId": "updateTask", "summary": "Update a task.", "description": "Update a task for the specified organization.", "tags": [ "Tasks" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "taskId", "required": true, "schema": { "$ref": "#/components/schemas/taskIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/taskInputSchema" } } }, "description": "The task payload.", "required": true }, "responses": { "200": { "description": "Returns the updated task.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/taskSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "isoDatetimeOrNull": { "type": [ "string", "null" ], "format": "date-time" }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "organizationTagSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/tagIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "kind": { "$ref": "#/components/schemas/tagKindSchema" }, "name": { "type": "string", "minLength": 1, "maxLength": 80 }, "slug": { "$ref": "#/components/schemas/tagSlugSchema" }, "color": { "anyOf": [ { "$ref": "#/components/schemas/tagColorSchema" }, { "type": "null" } ] }, "description": { "type": [ "string", "null" ] }, "position": { "type": "integer", "minimum": 0 }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "kind", "name", "slug", "color", "description", "position", "createdAt", "updatedAt" ] }, "tagColorSchema": { "type": "string", "pattern": "^#(?:[0-9a-f]{3}|[0-9a-f]{6})$" }, "tagIdSchema": { "type": "string", "format": "uuid" }, "tagKindSchema": { "type": "string", "enum": [ "task", "customer", "product" ] }, "tagSlugSchema": { "type": "string", "minLength": 1, "maxLength": 160, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" }, "taskAssigneeSchema": { "type": "object", "properties": { "userId": { "type": "string", "format": "uuid" }, "displayName": { "type": "string", "minLength": 1 }, "avatarUrl": { "type": [ "string", "null" ], "format": "uri" }, "disabledAt": { "type": [ "string", "null" ] } }, "required": [ "userId", "displayName", "avatarUrl", "disabledAt" ] }, "taskDisplayNumberSchema": { "type": "string", "minLength": 1, "maxLength": 40 }, "taskIdSchema": { "type": "string", "format": "uuid" }, "taskInputSchema": { "type": "object", "properties": { "title": { "type": "string", "minLength": 1, "maxLength": 240 }, "description": { "type": [ "string", "null" ], "maxLength": 20000 }, "status": { "allOf": [ { "$ref": "#/components/schemas/taskStatusSchema" } ] }, "priority": { "allOf": [ { "$ref": "#/components/schemas/taskPrioritySchema" } ] }, "startAt": { "allOf": [ { "$ref": "#/components/schemas/isoDatetimeOrNull" } ] }, "endAt": { "allOf": [ { "$ref": "#/components/schemas/isoDatetimeOrNull" } ] }, "dueAt": { "allOf": [ { "$ref": "#/components/schemas/isoDatetimeOrNull" } ] }, "customerId": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 }, "customerAssetId": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 }, "assigneeIds": { "type": "array", "items": { "type": "string", "format": "uuid" }, "maxItems": 50 }, "tagIds": { "type": "array", "items": { "type": "string", "format": "uuid" }, "maxItems": 50 }, "parentTaskId": { "anyOf": [ { "$ref": "#/components/schemas/taskIdSchema" }, { "type": "null" } ] }, "taskTemplateId": { "type": [ "string", "null" ], "format": "uuid" } }, "required": [ "title" ] }, "taskNumberSchema": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 }, "taskPrioritySchema": { "type": "string", "enum": [ "low", "medium", "high", "urgent" ] }, "taskSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/taskIdSchema" }, "organizationId": { "type": "string", "format": "uuid" }, "number": { "$ref": "#/components/schemas/taskNumberSchema" }, "displayNumber": { "$ref": "#/components/schemas/taskDisplayNumberSchema" }, "customerId": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 }, "customerAssetId": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 }, "title": { "type": "string" }, "description": { "type": [ "string", "null" ] }, "status": { "$ref": "#/components/schemas/taskStatusSchema" }, "priority": { "$ref": "#/components/schemas/taskPrioritySchema" }, "startAt": { "type": [ "string", "null" ], "format": "date-time" }, "endAt": { "type": [ "string", "null" ], "format": "date-time" }, "dueAt": { "type": [ "string", "null" ], "format": "date-time" }, "position": { "type": "number" }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "updatedBy": { "type": [ "string", "null" ], "format": "uuid" }, "archivedAt": { "type": [ "string", "null" ], "format": "date-time" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" }, "parentTaskId": { "anyOf": [ { "$ref": "#/components/schemas/taskIdSchema" }, { "type": "null" } ] }, "taskTemplateId": { "type": [ "string", "null" ], "format": "uuid" }, "assignees": { "type": "array", "items": { "$ref": "#/components/schemas/taskAssigneeSchema" } }, "tags": { "type": "array", "items": { "$ref": "#/components/schemas/organizationTagSchema" } } }, "required": [ "id", "organizationId", "number", "displayNumber", "customerId", "customerAssetId", "title", "description", "status", "priority", "startAt", "endAt", "dueAt", "position", "createdBy", "updatedBy", "archivedAt", "createdAt", "updatedAt", "parentTaskId", "taskTemplateId", "assignees", "tags" ] }, "taskStatusSchema": { "type": "string", "enum": [ "todo", "in_progress", "on_hold", "done" ] } } ``` # Delete a task. (/openapi/deleteTask) Delete a task for the specified organization. ## DELETE /v1/organizations/{organizationId}/tasks/{taskId} ```json { "operationId": "deleteTask", "summary": "Delete a task.", "description": "Delete a task for the specified organization.", "tags": [ "Tasks" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "taskId", "required": true, "schema": { "$ref": "#/components/schemas/taskIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns a success payload when the task is deleted.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/publicApiSuccessResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "organizationIdSchema": { "type": "string", "format": "uuid" }, "publicApiSuccessResponseSchema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [ true ] } }, "required": [ "success" ] }, "taskIdSchema": { "type": "string", "format": "uuid" } } ``` # Reorder tasks. (/openapi/reorderTasks) Reorder task board items for the specified organization. ## POST /v1/organizations/{organizationId}/tasks/reorder ```json { "operationId": "reorderTasks", "summary": "Reorder tasks.", "description": "Reorder task board items for the specified organization.", "tags": [ "Tasks" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/taskReorderInputSchema" } } }, "description": "The task ordering payload." }, "responses": { "200": { "description": "Returns a success payload when tasks are reordered.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/publicApiSuccessResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "organizationIdSchema": { "type": "string", "format": "uuid" }, "publicApiSuccessResponseSchema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [ true ] } }, "required": [ "success" ] }, "taskReorderInputSchema": { "type": "object", "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/taskReorderItemSchema" }, "minItems": 1, "maxItems": 500 } }, "required": [ "items" ] }, "taskReorderItemSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "status": { "$ref": "#/components/schemas/taskStatusSchema" }, "position": { "type": "number" } }, "required": [ "id", "status", "position" ] }, "taskStatusSchema": { "type": "string", "enum": [ "todo", "in_progress", "on_hold", "done" ] } } ``` # Set task assignees. (/openapi/setTaskAssignees) Replace the users assigned to a task. ## PUT /v1/organizations/{organizationId}/tasks/{taskId}/assignees ```json { "operationId": "setTaskAssignees", "summary": "Set task assignees.", "description": "Replace the users assigned to a task.", "tags": [ "Tasks" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "taskId", "required": true, "schema": { "$ref": "#/components/schemas/taskIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/taskAssigneesInputSchema" } } }, "description": "The assignee payload.", "required": true }, "responses": { "200": { "description": "Returns a success payload when assignees are updated.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/publicApiSuccessResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "organizationIdSchema": { "type": "string", "format": "uuid" }, "publicApiSuccessResponseSchema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [ true ] } }, "required": [ "success" ] }, "taskAssigneesInputSchema": { "type": "object", "properties": { "userIds": { "type": "array", "items": { "type": "string", "format": "uuid" }, "maxItems": 50 } }, "required": [ "userIds" ] }, "taskIdSchema": { "type": "string", "format": "uuid" } } ``` # List task comments. (/openapi/listTaskComments) Return comments for a task. ## GET /v1/organizations/{organizationId}/tasks/{taskId}/comments ```json { "operationId": "listTaskComments", "summary": "List task comments.", "description": "Return comments for a task.", "tags": [ "Tasks" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "taskId", "required": true, "schema": { "$ref": "#/components/schemas/taskIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns task comments.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/taskCommentsResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "collaborationBlockSchema": { "type": "object", "discriminator": { "propertyName": "type" }, "oneOf": [ { "type": "object", "properties": { "type": { "type": "string", "enum": [ "paragraph" ] }, "content": { "$ref": "#/components/schemas/collaborationInlineContentSchema" } }, "required": [ "type", "content" ], "additionalProperties": false }, { "type": "object", "properties": { "type": { "type": "string", "enum": [ "bullet-list" ] }, "items": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationInlineContentSchema" }, "minItems": 1, "maxItems": 500 } }, "required": [ "type", "items" ], "additionalProperties": false }, { "type": "object", "properties": { "type": { "type": "string", "enum": [ "numbered-list" ] }, "items": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationInlineContentSchema" }, "minItems": 1, "maxItems": 500 } }, "required": [ "type", "items" ], "additionalProperties": false } ] }, "collaborationCommentAuthorSchema": { "type": "object", "properties": { "userId": { "type": "string", "format": "uuid" }, "displayName": { "type": "string", "minLength": 1 }, "avatarUrl": { "type": [ "string", "null" ], "format": "uri" } }, "required": [ "userId", "displayName", "avatarUrl" ] }, "collaborationDocumentSchema": { "type": "object", "properties": { "version": { "type": "integer", "enum": [ 1 ] }, "blocks": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationBlockSchema" }, "minItems": 1, "maxItems": 1000 } }, "required": [ "version", "blocks" ], "additionalProperties": false }, "collaborationInlineContentSchema": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationInlineSchema" }, "minItems": 1, "maxItems": 1000 }, "collaborationInlineSchema": { "type": "object", "discriminator": { "propertyName": "type" }, "oneOf": [ { "type": "object", "properties": { "type": { "type": "string", "enum": [ "text" ] }, "text": { "type": "string", "minLength": 1, "maxLength": 20000 } }, "required": [ "type", "text" ], "additionalProperties": false }, { "type": "object", "properties": { "type": { "type": "string", "enum": [ "reference" ] }, "reference": { "$ref": "#/components/schemas/collaborationReferenceSchema" } }, "required": [ "type", "reference" ], "additionalProperties": false } ] }, "collaborationReferenceSchema": { "type": "object", "discriminator": { "propertyName": "kind" }, "oneOf": [ { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "user" ] }, "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "team" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "task" ] }, "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "customer" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "quote" ] }, "id": { "$ref": "#/components/schemas/quoteIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "invoice" ] }, "id": { "$ref": "#/components/schemas/invoiceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "product" ] }, "id": { "$ref": "#/components/schemas/productIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "customer-asset" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false } ] }, "invoiceIdSchema": { "type": "string", "format": "uuid" }, "listPaginationSchema": { "type": "object", "properties": { "offset": { "type": "integer", "minimum": 0 }, "limit": { "type": "integer", "minimum": 1, "maximum": 100 }, "total": { "type": "integer", "minimum": 0 }, "hasMore": { "type": "boolean" }, "totalIsEstimate": { "type": "boolean" } }, "required": [ "offset", "limit", "total", "hasMore" ] }, "numericReferenceIdSchema": { "type": "string", "pattern": "^[1-9]\\d*$" }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "productIdSchema": { "type": "string", "format": "uuid" }, "quoteIdSchema": { "type": "string", "format": "uuid" }, "taskCommentAuthorSchema": { "$ref": "#/components/schemas/collaborationCommentAuthorSchema" }, "taskCommentSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "taskId": { "type": "string", "format": "uuid" }, "organizationId": { "type": "string", "format": "uuid" }, "contentText": { "type": "string", "minLength": 1, "maxLength": 20000 }, "contentDoc": { "$ref": "#/components/schemas/collaborationDocumentSchema" }, "authorId": { "type": [ "string", "null" ], "format": "uuid" }, "author": { "anyOf": [ { "$ref": "#/components/schemas/taskCommentAuthorSchema" }, { "type": "null" } ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "taskId", "organizationId", "contentText", "contentDoc", "authorId", "author", "createdAt", "updatedAt" ] }, "taskCommentsResponseSchema": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/taskCommentSchema" } }, "pagination": { "$ref": "#/components/schemas/listPaginationSchema" } }, "required": [ "data", "pagination" ] }, "taskIdSchema": { "type": "string", "format": "uuid" } } ``` # Add a task comment. (/openapi/addTaskComment) Add a comment to a task. ## POST /v1/organizations/{organizationId}/tasks/{taskId}/comments ```json { "operationId": "addTaskComment", "summary": "Add a task comment.", "description": "Add a comment to a task.", "tags": [ "Tasks" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "taskId", "required": true, "schema": { "$ref": "#/components/schemas/taskIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/taskCommentInputSchema" } } }, "description": "The task comment payload." }, "responses": { "201": { "description": "Returns the created task comment.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/taskCommentSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "collaborationBlockSchema": { "type": "object", "discriminator": { "propertyName": "type" }, "oneOf": [ { "type": "object", "properties": { "type": { "type": "string", "enum": [ "paragraph" ] }, "content": { "$ref": "#/components/schemas/collaborationInlineContentSchema" } }, "required": [ "type", "content" ], "additionalProperties": false }, { "type": "object", "properties": { "type": { "type": "string", "enum": [ "bullet-list" ] }, "items": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationInlineContentSchema" }, "minItems": 1, "maxItems": 500 } }, "required": [ "type", "items" ], "additionalProperties": false }, { "type": "object", "properties": { "type": { "type": "string", "enum": [ "numbered-list" ] }, "items": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationInlineContentSchema" }, "minItems": 1, "maxItems": 500 } }, "required": [ "type", "items" ], "additionalProperties": false } ] }, "collaborationCommentAuthorSchema": { "type": "object", "properties": { "userId": { "type": "string", "format": "uuid" }, "displayName": { "type": "string", "minLength": 1 }, "avatarUrl": { "type": [ "string", "null" ], "format": "uri" } }, "required": [ "userId", "displayName", "avatarUrl" ] }, "collaborationComposerInputSchema": { "type": "object", "properties": { "text": { "type": "string", "minLength": 1, "maxLength": 20000 }, "selections": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationComposerSelectionSchema" }, "maxItems": 100 } }, "required": [ "text", "selections" ], "additionalProperties": false }, "collaborationComposerSelectionSchema": { "type": "object", "properties": { "start": { "type": "integer", "minimum": 0, "maximum": 20000 }, "end": { "type": "integer", "exclusiveMinimum": 0, "maximum": 20000 }, "reference": { "$ref": "#/components/schemas/collaborationReferenceIdSchema" } }, "required": [ "start", "end", "reference" ], "additionalProperties": false }, "collaborationDocumentSchema": { "type": "object", "properties": { "version": { "type": "integer", "enum": [ 1 ] }, "blocks": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationBlockSchema" }, "minItems": 1, "maxItems": 1000 } }, "required": [ "version", "blocks" ], "additionalProperties": false }, "collaborationInlineContentSchema": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationInlineSchema" }, "minItems": 1, "maxItems": 1000 }, "collaborationInlineSchema": { "type": "object", "discriminator": { "propertyName": "type" }, "oneOf": [ { "type": "object", "properties": { "type": { "type": "string", "enum": [ "text" ] }, "text": { "type": "string", "minLength": 1, "maxLength": 20000 } }, "required": [ "type", "text" ], "additionalProperties": false }, { "type": "object", "properties": { "type": { "type": "string", "enum": [ "reference" ] }, "reference": { "$ref": "#/components/schemas/collaborationReferenceSchema" } }, "required": [ "type", "reference" ], "additionalProperties": false } ] }, "collaborationReferenceIdSchema": { "type": "object", "discriminator": { "propertyName": "kind" }, "oneOf": [ { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "user" ] }, "id": { "type": "string", "format": "uuid" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "team" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "task" ] }, "id": { "type": "string", "format": "uuid" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "customer" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "quote" ] }, "id": { "$ref": "#/components/schemas/quoteIdSchema" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "invoice" ] }, "id": { "$ref": "#/components/schemas/invoiceIdSchema" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "product" ] }, "id": { "$ref": "#/components/schemas/productIdSchema" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "customer-asset" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" } }, "required": [ "kind", "id" ], "additionalProperties": false } ] }, "collaborationReferenceSchema": { "type": "object", "discriminator": { "propertyName": "kind" }, "oneOf": [ { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "user" ] }, "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "team" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "task" ] }, "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "customer" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "quote" ] }, "id": { "$ref": "#/components/schemas/quoteIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "invoice" ] }, "id": { "$ref": "#/components/schemas/invoiceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "product" ] }, "id": { "$ref": "#/components/schemas/productIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "customer-asset" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false } ] }, "invoiceIdSchema": { "type": "string", "format": "uuid" }, "numericReferenceIdSchema": { "type": "string", "pattern": "^[1-9]\\d*$" }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "productIdSchema": { "type": "string", "format": "uuid" }, "quoteIdSchema": { "type": "string", "format": "uuid" }, "taskCommentAuthorSchema": { "$ref": "#/components/schemas/collaborationCommentAuthorSchema" }, "taskCommentInputSchema": { "$ref": "#/components/schemas/collaborationComposerInputSchema" }, "taskCommentSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "taskId": { "type": "string", "format": "uuid" }, "organizationId": { "type": "string", "format": "uuid" }, "contentText": { "type": "string", "minLength": 1, "maxLength": 20000 }, "contentDoc": { "$ref": "#/components/schemas/collaborationDocumentSchema" }, "authorId": { "type": [ "string", "null" ], "format": "uuid" }, "author": { "anyOf": [ { "$ref": "#/components/schemas/taskCommentAuthorSchema" }, { "type": "null" } ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "taskId", "organizationId", "contentText", "contentDoc", "authorId", "author", "createdAt", "updatedAt" ] }, "taskIdSchema": { "type": "string", "format": "uuid" } } ``` # Read a task lifecycle. (/openapi/getTaskLifecycle) The task's lifecycle: the pinned workflow version, the current stage, the stage history, and what the lifecycle waits on now. `lifecycle` is null when no lifecycle run started for the task. ## GET /v1/organizations/{organizationId}/tasks/{taskId}/lifecycle ```json { "operationId": "getTaskLifecycle", "summary": "Read a task lifecycle.", "description": "The task's lifecycle: the pinned workflow version, the current stage, the stage history, and what the lifecycle waits on now. `lifecycle` is null when no lifecycle run started for the task.", "tags": [ "Tasks" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "taskId", "required": true, "schema": { "$ref": "#/components/schemas/taskIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns the task's lifecycle.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/recordLifecycleSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "dateTimeSchema": { "type": "string" }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "recordLifecycleSchema": { "type": "object", "properties": { "lifecycle": { "anyOf": [ { "$ref": "#/components/schemas/workflowLifecycleSchema" }, { "type": "null" } ] }, "definition": { "type": [ "object", "null" ], "properties": { "id": { "type": "string", "format": "uuid" }, "key": { "type": "string" }, "name": { "type": "string" }, "version": { "type": "integer", "exclusiveMinimum": 0 }, "source": { "type": "string", "enum": [ "organization", "record", "system", "template" ] } }, "required": [ "id", "key", "name", "version", "source" ] }, "events": { "type": "array", "items": { "$ref": "#/components/schemas/workflowLifecycleEventSchema" } }, "waiting": { "type": [ "object", "null" ], "properties": { "kind": { "type": "string", "enum": [ "approval", "delay", "event", "manual" ] }, "at": { "anyOf": [ { "$ref": "#/components/schemas/dateTimeSchema" }, { "type": "null" } ] }, "nextStageKey": { "type": [ "string", "null" ] }, "nextStageLabel": { "type": [ "string", "null" ] }, "events": { "type": "array", "items": { "type": "string" } } }, "required": [ "kind", "at", "nextStageKey", "nextStageLabel", "events" ] } }, "required": [ "lifecycle", "definition", "events", "waiting" ] }, "taskIdSchema": { "type": "string", "format": "uuid" }, "workflowLifecycleEventSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "organizationId": { "type": "string", "format": "uuid" }, "lifecycleId": { "type": "string", "format": "uuid" }, "stageKey": { "type": "string", "minLength": 1 }, "label": { "type": "string", "minLength": 1 }, "outcome": { "type": "string", "enum": [ "progress", "success", "failure", "neutral" ] }, "display": { "type": "string", "enum": [ "main", "sub", "flag", "hidden" ] }, "reachedAt": { "$ref": "#/components/schemas/dateTimeSchema" }, "actorUserId": { "type": [ "string", "null" ], "format": "uuid" }, "actorCustomerContactId": { "type": [ "string", "null" ], "format": "uuid" }, "runStepId": { "type": [ "string", "null" ], "format": "uuid" }, "sourceRunId": { "type": [ "string", "null" ], "format": "uuid" }, "clearedAt": { "anyOf": [ { "$ref": "#/components/schemas/dateTimeSchema" }, { "type": "null" } ] }, "createdAt": { "$ref": "#/components/schemas/dateTimeSchema" } }, "required": [ "id", "organizationId", "lifecycleId", "stageKey", "label", "outcome", "display", "reachedAt", "actorUserId", "actorCustomerContactId", "runStepId", "sourceRunId", "clearedAt", "createdAt" ] }, "workflowLifecycleSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "organizationId": { "type": "string", "format": "uuid" }, "subjectType": { "$ref": "#/components/schemas/workflowSubjectTypeSchema" }, "subjectId": { "type": "string", "format": "uuid" }, "definitionId": { "type": "string", "format": "uuid" }, "definitionVersion": { "type": "integer", "exclusiveMinimum": 0 }, "runId": { "type": [ "string", "null" ], "format": "uuid" }, "currentStageKey": { "type": [ "string", "null" ] }, "pausedAt": { "anyOf": [ { "$ref": "#/components/schemas/dateTimeSchema" }, { "type": "null" } ] }, "pausedUntil": { "anyOf": [ { "$ref": "#/components/schemas/dateTimeSchema" }, { "type": "null" } ] }, "pauseReason": { "type": [ "string", "null" ] }, "pausedBy": { "type": [ "string", "null" ], "format": "uuid" }, "startedAt": { "$ref": "#/components/schemas/dateTimeSchema" }, "endedAt": { "anyOf": [ { "$ref": "#/components/schemas/dateTimeSchema" }, { "type": "null" } ] }, "createdAt": { "$ref": "#/components/schemas/dateTimeSchema" }, "updatedAt": { "$ref": "#/components/schemas/dateTimeSchema" } }, "required": [ "id", "organizationId", "subjectType", "subjectId", "definitionId", "definitionVersion", "runId", "currentStageKey", "pausedAt", "pausedUntil", "pauseReason", "pausedBy", "startedAt", "endedAt", "createdAt", "updatedAt" ] }, "workflowSubjectTypeSchema": { "type": "string" } } ``` # Set task status. (/openapi/setTaskStatus) Move a task to a status and board position. ## PATCH /v1/organizations/{organizationId}/tasks/{taskId}/status ```json { "operationId": "setTaskStatus", "summary": "Set task status.", "description": "Move a task to a status and board position.", "tags": [ "Tasks" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "taskId", "required": true, "schema": { "$ref": "#/components/schemas/taskIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/taskStatusUpdateInputSchema" } } }, "description": "The status and position payload.", "required": true }, "responses": { "200": { "description": "Returns a success payload when the status is updated.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/publicApiSuccessResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "organizationIdSchema": { "type": "string", "format": "uuid" }, "publicApiSuccessResponseSchema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [ true ] } }, "required": [ "success" ] }, "taskIdSchema": { "type": "string", "format": "uuid" }, "taskStatusSchema": { "type": "string", "enum": [ "todo", "in_progress", "on_hold", "done" ] }, "taskStatusUpdateInputSchema": { "type": "object", "properties": { "position": { "type": "number" }, "status": { "$ref": "#/components/schemas/taskStatusSchema" } }, "required": [ "position", "status" ] } } ``` # List task time entries. (/openapi/listTaskTimeEntries) Return time entries for a task. ## GET /v1/organizations/{organizationId}/tasks/{taskId}/time-entries ```json { "operationId": "listTaskTimeEntries", "summary": "List task time entries.", "description": "Return time entries for a task.", "tags": [ "Tasks" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "taskId", "required": true, "schema": { "$ref": "#/components/schemas/taskIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns task time entries.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/taskTimeEntriesResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "listPaginationSchema": { "type": "object", "properties": { "offset": { "type": "integer", "minimum": 0 }, "limit": { "type": "integer", "minimum": 1, "maximum": 100 }, "total": { "type": "integer", "minimum": 0 }, "hasMore": { "type": "boolean" }, "totalIsEstimate": { "type": "boolean" } }, "required": [ "offset", "limit", "total", "hasMore" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "taskBilledInvoiceSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "displayNumber": { "type": [ "string", "null" ], "minLength": 1 }, "status": { "type": "string", "minLength": 1 } }, "required": [ "id", "displayNumber", "status" ] }, "taskIdSchema": { "type": "string", "format": "uuid" }, "taskTimeEntriesResponseSchema": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/taskTimeEntrySchema" } }, "pagination": { "$ref": "#/components/schemas/listPaginationSchema" } }, "required": [ "data", "pagination" ] }, "taskTimeEntryAuthorSchema": { "type": "object", "properties": { "userId": { "type": "string", "format": "uuid" }, "displayName": { "type": "string", "minLength": 1 }, "avatarUrl": { "type": [ "string", "null" ], "format": "uri" } }, "required": [ "userId", "displayName", "avatarUrl" ] }, "taskTimeEntrySchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "taskId": { "type": "string", "format": "uuid" }, "organizationId": { "type": "string", "format": "uuid" }, "userId": { "type": [ "string", "null" ], "format": "uuid" }, "user": { "anyOf": [ { "$ref": "#/components/schemas/taskTimeEntryAuthorSchema" }, { "type": "null" } ] }, "startedAt": { "type": "string", "format": "date-time" }, "durationSeconds": { "type": "integer", "exclusiveMinimum": 0 }, "note": { "type": [ "string", "null" ] }, "billable": { "type": "boolean" }, "billedInvoice": { "anyOf": [ { "$ref": "#/components/schemas/taskBilledInvoiceSchema" }, { "type": "null" } ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "taskId", "organizationId", "userId", "user", "startedAt", "durationSeconds", "note", "billable", "billedInvoice", "createdAt", "updatedAt" ] } } ``` # Add a task time entry. (/openapi/addTaskTimeEntry) Add a time entry to a task. ## POST /v1/organizations/{organizationId}/tasks/{taskId}/time-entries ```json { "operationId": "addTaskTimeEntry", "summary": "Add a task time entry.", "description": "Add a time entry to a task.", "tags": [ "Tasks" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "taskId", "required": true, "schema": { "$ref": "#/components/schemas/taskIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/taskTimeEntryInputSchema" } } }, "description": "The task time entry payload." }, "responses": { "201": { "description": "Returns the created task time entry.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/taskTimeEntrySchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "organizationIdSchema": { "type": "string", "format": "uuid" }, "taskBilledInvoiceSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "displayNumber": { "type": [ "string", "null" ], "minLength": 1 }, "status": { "type": "string", "minLength": 1 } }, "required": [ "id", "displayNumber", "status" ] }, "taskIdSchema": { "type": "string", "format": "uuid" }, "taskTimeEntryAuthorSchema": { "type": "object", "properties": { "userId": { "type": "string", "format": "uuid" }, "displayName": { "type": "string", "minLength": 1 }, "avatarUrl": { "type": [ "string", "null" ], "format": "uri" } }, "required": [ "userId", "displayName", "avatarUrl" ] }, "taskTimeEntryInputSchema": { "type": "object", "properties": { "startedAt": { "type": "string", "format": "date-time" }, "durationSeconds": { "type": "integer", "exclusiveMinimum": 0, "maximum": 2678400 }, "note": { "type": [ "string", "null" ], "maxLength": 2000 }, "userId": { "type": "string", "format": "uuid" }, "billable": { "type": "boolean" } }, "required": [ "durationSeconds" ] }, "taskTimeEntrySchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "taskId": { "type": "string", "format": "uuid" }, "organizationId": { "type": "string", "format": "uuid" }, "userId": { "type": [ "string", "null" ], "format": "uuid" }, "user": { "anyOf": [ { "$ref": "#/components/schemas/taskTimeEntryAuthorSchema" }, { "type": "null" } ] }, "startedAt": { "type": "string", "format": "date-time" }, "durationSeconds": { "type": "integer", "exclusiveMinimum": 0 }, "note": { "type": [ "string", "null" ] }, "billable": { "type": "boolean" }, "billedInvoice": { "anyOf": [ { "$ref": "#/components/schemas/taskBilledInvoiceSchema" }, { "type": "null" } ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "taskId", "organizationId", "userId", "user", "startedAt", "durationSeconds", "note", "billable", "billedInvoice", "createdAt", "updatedAt" ] } } ``` # Update a task comment. (/openapi/updateTaskComment) Update a comment on a task. ## PATCH /v1/organizations/{organizationId}/tasks/{taskId}/comments/{commentId} ```json { "operationId": "updateTaskComment", "summary": "Update a task comment.", "description": "Update a comment on a task.", "tags": [ "Tasks" ], "parameters": [ { "in": "path", "name": "commentId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "taskId", "required": true, "schema": { "$ref": "#/components/schemas/taskIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/taskCommentInputSchema" } } }, "description": "The task comment payload." }, "responses": { "200": { "description": "Returns the updated task comment.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/taskCommentSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "collaborationBlockSchema": { "type": "object", "discriminator": { "propertyName": "type" }, "oneOf": [ { "type": "object", "properties": { "type": { "type": "string", "enum": [ "paragraph" ] }, "content": { "$ref": "#/components/schemas/collaborationInlineContentSchema" } }, "required": [ "type", "content" ], "additionalProperties": false }, { "type": "object", "properties": { "type": { "type": "string", "enum": [ "bullet-list" ] }, "items": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationInlineContentSchema" }, "minItems": 1, "maxItems": 500 } }, "required": [ "type", "items" ], "additionalProperties": false }, { "type": "object", "properties": { "type": { "type": "string", "enum": [ "numbered-list" ] }, "items": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationInlineContentSchema" }, "minItems": 1, "maxItems": 500 } }, "required": [ "type", "items" ], "additionalProperties": false } ] }, "collaborationCommentAuthorSchema": { "type": "object", "properties": { "userId": { "type": "string", "format": "uuid" }, "displayName": { "type": "string", "minLength": 1 }, "avatarUrl": { "type": [ "string", "null" ], "format": "uri" } }, "required": [ "userId", "displayName", "avatarUrl" ] }, "collaborationComposerInputSchema": { "type": "object", "properties": { "text": { "type": "string", "minLength": 1, "maxLength": 20000 }, "selections": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationComposerSelectionSchema" }, "maxItems": 100 } }, "required": [ "text", "selections" ], "additionalProperties": false }, "collaborationComposerSelectionSchema": { "type": "object", "properties": { "start": { "type": "integer", "minimum": 0, "maximum": 20000 }, "end": { "type": "integer", "exclusiveMinimum": 0, "maximum": 20000 }, "reference": { "$ref": "#/components/schemas/collaborationReferenceIdSchema" } }, "required": [ "start", "end", "reference" ], "additionalProperties": false }, "collaborationDocumentSchema": { "type": "object", "properties": { "version": { "type": "integer", "enum": [ 1 ] }, "blocks": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationBlockSchema" }, "minItems": 1, "maxItems": 1000 } }, "required": [ "version", "blocks" ], "additionalProperties": false }, "collaborationInlineContentSchema": { "type": "array", "items": { "$ref": "#/components/schemas/collaborationInlineSchema" }, "minItems": 1, "maxItems": 1000 }, "collaborationInlineSchema": { "type": "object", "discriminator": { "propertyName": "type" }, "oneOf": [ { "type": "object", "properties": { "type": { "type": "string", "enum": [ "text" ] }, "text": { "type": "string", "minLength": 1, "maxLength": 20000 } }, "required": [ "type", "text" ], "additionalProperties": false }, { "type": "object", "properties": { "type": { "type": "string", "enum": [ "reference" ] }, "reference": { "$ref": "#/components/schemas/collaborationReferenceSchema" } }, "required": [ "type", "reference" ], "additionalProperties": false } ] }, "collaborationReferenceIdSchema": { "type": "object", "discriminator": { "propertyName": "kind" }, "oneOf": [ { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "user" ] }, "id": { "type": "string", "format": "uuid" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "team" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "task" ] }, "id": { "type": "string", "format": "uuid" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "customer" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "quote" ] }, "id": { "$ref": "#/components/schemas/quoteIdSchema" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "invoice" ] }, "id": { "$ref": "#/components/schemas/invoiceIdSchema" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "product" ] }, "id": { "$ref": "#/components/schemas/productIdSchema" } }, "required": [ "kind", "id" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "customer-asset" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" } }, "required": [ "kind", "id" ], "additionalProperties": false } ] }, "collaborationReferenceSchema": { "type": "object", "discriminator": { "propertyName": "kind" }, "oneOf": [ { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "user" ] }, "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "team" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "task" ] }, "id": { "type": "string", "format": "uuid" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "customer" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "quote" ] }, "id": { "$ref": "#/components/schemas/quoteIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "invoice" ] }, "id": { "$ref": "#/components/schemas/invoiceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "product" ] }, "id": { "$ref": "#/components/schemas/productIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "customer-asset" ] }, "id": { "$ref": "#/components/schemas/numericReferenceIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "label": { "type": "string", "minLength": 1, "maxLength": 500 }, "path": { "type": "string", "pattern": "^/", "maxLength": 2000 } }, "required": [ "kind", "id", "organizationId", "label", "path" ], "additionalProperties": false } ] }, "invoiceIdSchema": { "type": "string", "format": "uuid" }, "numericReferenceIdSchema": { "type": "string", "pattern": "^[1-9]\\d*$" }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "productIdSchema": { "type": "string", "format": "uuid" }, "quoteIdSchema": { "type": "string", "format": "uuid" }, "taskCommentAuthorSchema": { "$ref": "#/components/schemas/collaborationCommentAuthorSchema" }, "taskCommentInputSchema": { "$ref": "#/components/schemas/collaborationComposerInputSchema" }, "taskCommentSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "taskId": { "type": "string", "format": "uuid" }, "organizationId": { "type": "string", "format": "uuid" }, "contentText": { "type": "string", "minLength": 1, "maxLength": 20000 }, "contentDoc": { "$ref": "#/components/schemas/collaborationDocumentSchema" }, "authorId": { "type": [ "string", "null" ], "format": "uuid" }, "author": { "anyOf": [ { "$ref": "#/components/schemas/taskCommentAuthorSchema" }, { "type": "null" } ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "taskId", "organizationId", "contentText", "contentDoc", "authorId", "author", "createdAt", "updatedAt" ] }, "taskIdSchema": { "type": "string", "format": "uuid" } } ``` # Delete a task comment. (/openapi/deleteTaskComment) Delete a comment from a task. ## DELETE /v1/organizations/{organizationId}/tasks/{taskId}/comments/{commentId} ```json { "operationId": "deleteTaskComment", "summary": "Delete a task comment.", "description": "Delete a comment from a task.", "tags": [ "Tasks" ], "parameters": [ { "in": "path", "name": "commentId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "taskId", "required": true, "schema": { "$ref": "#/components/schemas/taskIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns a success payload when the comment is deleted.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/publicApiSuccessResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "organizationIdSchema": { "type": "string", "format": "uuid" }, "publicApiSuccessResponseSchema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [ true ] } }, "required": [ "success" ] }, "taskIdSchema": { "type": "string", "format": "uuid" } } ``` # Advance a task lifecycle stage. (/openapi/advanceTaskLifecycle) Moves the task to a manual milestone of its lifecycle. Only stages the lifecycle marks as manually advanceable are accepted. ## POST /v1/organizations/{organizationId}/tasks/{taskId}/lifecycle/advance ```json { "operationId": "advanceTaskLifecycle", "summary": "Advance a task lifecycle stage.", "description": "Moves the task to a manual milestone of its lifecycle. Only stages the lifecycle marks as manually advanceable are accepted.", "tags": [ "Tasks" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "taskId", "required": true, "schema": { "$ref": "#/components/schemas/taskIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/lifecycleAdvanceInputSchema" } } }, "description": "The stage key to move to.", "required": true }, "responses": { "200": { "description": "The stage was recorded.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/publicApiSuccessResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "lifecycleAdvanceInputSchema": { "type": "object", "properties": { "stageKey": { "type": "string", "minLength": 1, "maxLength": 80 } }, "required": [ "stageKey" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "publicApiSuccessResponseSchema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [ true ] } }, "required": [ "success" ] }, "taskIdSchema": { "type": "string", "format": "uuid" } } ``` # Delete a task time entry. (/openapi/deleteTaskTimeEntry) Delete a time entry from a task. ## DELETE /v1/organizations/{organizationId}/tasks/{taskId}/time-entries/{entryId} ```json { "operationId": "deleteTaskTimeEntry", "summary": "Delete a task time entry.", "description": "Delete a time entry from a task.", "tags": [ "Tasks" ], "parameters": [ { "in": "path", "name": "entryId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "taskId", "required": true, "schema": { "$ref": "#/components/schemas/taskIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns a success payload when the time entry is deleted.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/publicApiSuccessResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "organizationIdSchema": { "type": "string", "format": "uuid" }, "publicApiSuccessResponseSchema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [ true ] } }, "required": [ "success" ] }, "taskIdSchema": { "type": "string", "format": "uuid" } } ``` # List teams. (/openapi/listTeams) Return teams for an organization the authenticated user can access. ## GET /v1/organizations/{organizationId}/teams ```json { "operationId": "listTeams", "summary": "List teams.", "description": "Return teams for an organization the authenticated user can access.", "tags": [ "Teams" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns paginated teams.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/teamsListResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "organizationIdSchema": { "type": "string", "format": "uuid" }, "teamsListResponseSchema": { "type": "object" } } ``` # Create a team. (/openapi/createTeam) Create a team for the specified organization. ## POST /v1/organizations/{organizationId}/teams ```json { "operationId": "createTeam", "summary": "Create a team.", "description": "Create a team for the specified organization.", "tags": [ "Teams" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/teamInputSchema" } } }, "description": "The team payload." }, "responses": { "201": { "description": "Returns the created team.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/teamSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "organizationIdSchema": { "type": "string", "format": "uuid" }, "teamDescriptionSchema": { "type": "string", "maxLength": 500 }, "teamIconNameSchema": { "type": "string", "minLength": 1, "maxLength": 120, "pattern": "^[A-Za-z0-9]+Icon$" }, "teamIdSchema": { "type": "integer", "exclusiveMinimum": 0 }, "teamInputSchema": { "type": "object", "properties": { "name": { "type": "string", "minLength": 1, "maxLength": 120 }, "slug": { "$ref": "#/components/schemas/teamSlugSchema" }, "iconName": { "$ref": "#/components/schemas/teamIconNameSchema" }, "description": { "anyOf": [ { "$ref": "#/components/schemas/teamDescriptionSchema" }, { "type": "null" } ] } }, "required": [ "name", "slug", "iconName" ] }, "teamSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/teamIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "name": { "type": "string", "minLength": 1 }, "slug": { "$ref": "#/components/schemas/teamSlugSchema" }, "iconName": { "$ref": "#/components/schemas/teamIconNameSchema" }, "description": { "anyOf": [ { "$ref": "#/components/schemas/teamDescriptionSchema" }, { "type": "null" } ] }, "status": { "$ref": "#/components/schemas/teamStatusSchema" }, "memberCount": { "type": "integer", "minimum": 0 }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "updatedBy": { "type": [ "string", "null" ], "format": "uuid" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "name", "slug", "iconName", "description", "status", "memberCount", "createdBy", "updatedBy", "createdAt", "updatedAt" ] }, "teamSlugSchema": { "type": "string", "minLength": 2, "maxLength": 64, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" }, "teamStatusSchema": { "type": "string", "enum": [ "active", "archived" ] } } ``` # Get a team. (/openapi/getTeam) Return a team detail record for the specified organization. ## GET /v1/organizations/{organizationId}/teams/{teamId} ```json { "operationId": "getTeam", "summary": "Get a team.", "description": "Return a team detail record for the specified organization.", "tags": [ "Teams" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "teamId", "required": true, "schema": { "$ref": "#/components/schemas/teamIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns the requested team.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/teamDetailSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "organizationIdSchema": { "type": "string", "format": "uuid" }, "teamDescriptionSchema": { "type": "string", "maxLength": 500 }, "teamDetailSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/teamIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "name": { "type": "string", "minLength": 1 }, "slug": { "$ref": "#/components/schemas/teamSlugSchema" }, "iconName": { "$ref": "#/components/schemas/teamIconNameSchema" }, "description": { "anyOf": [ { "$ref": "#/components/schemas/teamDescriptionSchema" }, { "type": "null" } ] }, "status": { "$ref": "#/components/schemas/teamStatusSchema" }, "memberCount": { "type": "integer", "minimum": 0 }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "updatedBy": { "type": [ "string", "null" ], "format": "uuid" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" }, "members": { "type": "array", "items": { "$ref": "#/components/schemas/teamMemberSchema" } } }, "required": [ "id", "organizationId", "name", "slug", "iconName", "description", "status", "memberCount", "createdBy", "updatedBy", "createdAt", "updatedAt", "members" ] }, "teamIconNameSchema": { "type": "string", "minLength": 1, "maxLength": 120, "pattern": "^[A-Za-z0-9]+Icon$" }, "teamIdSchema": { "type": "integer", "exclusiveMinimum": 0 }, "teamMemberSchema": { "type": "object", "properties": { "userId": { "type": "string", "format": "uuid" }, "email": { "type": [ "string", "null" ], "format": "email" }, "firstName": { "type": [ "string", "null" ] }, "lastName": { "type": [ "string", "null" ] }, "avatarUrl": { "type": [ "string", "null" ], "format": "uri" }, "roleName": { "type": [ "string", "null" ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "userId", "email", "firstName", "lastName", "avatarUrl", "roleName", "createdAt", "updatedAt" ] }, "teamSlugSchema": { "type": "string", "minLength": 2, "maxLength": 64, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" }, "teamStatusSchema": { "type": "string", "enum": [ "active", "archived" ] } } ``` # Update a team. (/openapi/updateTeam) Update a team for the specified organization. ## PATCH /v1/organizations/{organizationId}/teams/{teamId} ```json { "operationId": "updateTeam", "summary": "Update a team.", "description": "Update a team for the specified organization.", "tags": [ "Teams" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "teamId", "required": true, "schema": { "$ref": "#/components/schemas/teamIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/teamInputSchema" } } }, "description": "The team payload." }, "responses": { "200": { "description": "Returns the updated team.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/teamSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "organizationIdSchema": { "type": "string", "format": "uuid" }, "teamDescriptionSchema": { "type": "string", "maxLength": 500 }, "teamIconNameSchema": { "type": "string", "minLength": 1, "maxLength": 120, "pattern": "^[A-Za-z0-9]+Icon$" }, "teamIdSchema": { "type": "integer", "exclusiveMinimum": 0 }, "teamInputSchema": { "type": "object", "properties": { "name": { "type": "string", "minLength": 1, "maxLength": 120 }, "slug": { "$ref": "#/components/schemas/teamSlugSchema" }, "iconName": { "$ref": "#/components/schemas/teamIconNameSchema" }, "description": { "anyOf": [ { "$ref": "#/components/schemas/teamDescriptionSchema" }, { "type": "null" } ] } }, "required": [ "name", "slug", "iconName" ] }, "teamSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/teamIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "name": { "type": "string", "minLength": 1 }, "slug": { "$ref": "#/components/schemas/teamSlugSchema" }, "iconName": { "$ref": "#/components/schemas/teamIconNameSchema" }, "description": { "anyOf": [ { "$ref": "#/components/schemas/teamDescriptionSchema" }, { "type": "null" } ] }, "status": { "$ref": "#/components/schemas/teamStatusSchema" }, "memberCount": { "type": "integer", "minimum": 0 }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "updatedBy": { "type": [ "string", "null" ], "format": "uuid" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "name", "slug", "iconName", "description", "status", "memberCount", "createdBy", "updatedBy", "createdAt", "updatedAt" ] }, "teamSlugSchema": { "type": "string", "minLength": 2, "maxLength": 64, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" }, "teamStatusSchema": { "type": "string", "enum": [ "active", "archived" ] } } ``` # Delete a team. (/openapi/deleteTeam) Delete a team for the specified organization. ## DELETE /v1/organizations/{organizationId}/teams/{teamId} ```json { "operationId": "deleteTeam", "summary": "Delete a team.", "description": "Delete a team for the specified organization.", "tags": [ "Teams" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "teamId", "required": true, "schema": { "$ref": "#/components/schemas/teamIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns a success payload when the team is deleted.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/publicApiSuccessResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "organizationIdSchema": { "type": "string", "format": "uuid" }, "publicApiSuccessResponseSchema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [ true ] } }, "required": [ "success" ] }, "teamIdSchema": { "type": "integer", "exclusiveMinimum": 0 } } ``` # Replace team members. (/openapi/replaceTeamMembers) Replace all members assigned to a team. ## PUT /v1/organizations/{organizationId}/teams/{teamId}/members ```json { "operationId": "replaceTeamMembers", "summary": "Replace team members.", "description": "Replace all members assigned to a team.", "tags": [ "Teams" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "teamId", "required": true, "schema": { "$ref": "#/components/schemas/teamIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/teamMembersInputSchema" } } }, "description": "The complete member assignment for the team." }, "responses": { "200": { "description": "Returns the updated team detail.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/teamDetailSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "organizationIdSchema": { "type": "string", "format": "uuid" }, "teamDescriptionSchema": { "type": "string", "maxLength": 500 }, "teamDetailSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/teamIdSchema" }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "name": { "type": "string", "minLength": 1 }, "slug": { "$ref": "#/components/schemas/teamSlugSchema" }, "iconName": { "$ref": "#/components/schemas/teamIconNameSchema" }, "description": { "anyOf": [ { "$ref": "#/components/schemas/teamDescriptionSchema" }, { "type": "null" } ] }, "status": { "$ref": "#/components/schemas/teamStatusSchema" }, "memberCount": { "type": "integer", "minimum": 0 }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "updatedBy": { "type": [ "string", "null" ], "format": "uuid" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" }, "members": { "type": "array", "items": { "$ref": "#/components/schemas/teamMemberSchema" } } }, "required": [ "id", "organizationId", "name", "slug", "iconName", "description", "status", "memberCount", "createdBy", "updatedBy", "createdAt", "updatedAt", "members" ] }, "teamIconNameSchema": { "type": "string", "minLength": 1, "maxLength": 120, "pattern": "^[A-Za-z0-9]+Icon$" }, "teamIdSchema": { "type": "integer", "exclusiveMinimum": 0 }, "teamMemberSchema": { "type": "object", "properties": { "userId": { "type": "string", "format": "uuid" }, "email": { "type": [ "string", "null" ], "format": "email" }, "firstName": { "type": [ "string", "null" ] }, "lastName": { "type": [ "string", "null" ] }, "avatarUrl": { "type": [ "string", "null" ], "format": "uri" }, "roleName": { "type": [ "string", "null" ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "userId", "email", "firstName", "lastName", "avatarUrl", "roleName", "createdAt", "updatedAt" ] }, "teamMembersInputSchema": { "type": "object", "properties": { "userIds": { "type": "array", "items": { "type": "string", "format": "uuid" }, "maxItems": 200 } }, "required": [ "userIds" ] }, "teamSlugSchema": { "type": "string", "minLength": 2, "maxLength": 64, "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" }, "teamStatusSchema": { "type": "string", "enum": [ "active", "archived" ] } } ``` # List templates. (/openapi/listTemplates) Return quote, invoice, and task templates for an organization the authenticated user can access. ## GET /v1/organizations/{organizationId}/templates ```json { "operationId": "listTemplates", "summary": "List templates.", "description": "Return quote, invoice, and task templates for an organization the authenticated user can access.", "tags": [ "Templates" ], "parameters": [ { "in": "query", "name": "type", "required": false, "schema": { "allOf": [ { "$ref": "#/components/schemas/templateTypeSchema" } ] }, "example": "example" }, { "in": "query", "name": "language", "required": false, "schema": { "allOf": [ { "$ref": "#/components/schemas/templateLanguageSchema" } ] }, "example": "example" }, { "in": "query", "name": "limit", "required": false, "schema": { "type": "string" }, "example": "example" }, { "in": "query", "name": "offset", "required": false, "schema": { "type": "string" }, "example": "example" }, { "in": "query", "name": "q", "required": false, "schema": { "type": "string" }, "example": "example" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns paginated templates.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/templatesListResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "organizationIdSchema": { "type": "string", "format": "uuid" }, "templateLanguageSchema": { "type": "string", "enum": [ "en", "nl" ] }, "templateTypeSchema": { "type": "string", "enum": [ "invoice", "quote", "task", "order", "purchase_order" ] }, "templatesListResponseSchema": { "type": "object" } } ``` # Create a template. (/openapi/createTemplate) Create a reusable quote, invoice, or task template for the specified organization. ## POST /v1/organizations/{organizationId}/templates ```json { "operationId": "createTemplate", "summary": "Create a template.", "description": "Create a reusable quote, invoice, or task template for the specified organization.", "tags": [ "Templates" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/templateInputSchema" } } }, "description": "The template payload." }, "responses": { "201": { "description": "Returns the created template.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/templateSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "currencyCode": { "type": "string", "pattern": "^[A-Z]{3}$" }, "discountKindSchema": { "type": "string", "enum": [ "none", "percentage", "fixed" ] }, "documentVatRegimeSchema": { "type": "string", "enum": [ "standard", "reverse_charge", "exempt", "kor", "margin_scheme" ] }, "invoiceTemplateInputSchema": { "type": "object", "properties": { "language": { "allOf": [ { "$ref": "#/components/schemas/templateLanguageSchema" } ] }, "isDefault": { "type": "boolean" }, "name": { "type": "string", "minLength": 1, "maxLength": 240 }, "description": { "type": [ "string", "null" ], "maxLength": 20000 }, "title": { "type": "string", "minLength": 1, "maxLength": 240 }, "intro": { "type": [ "string", "null" ], "maxLength": 20000 }, "terms": { "type": [ "string", "null" ], "maxLength": 20000 }, "internalNotes": { "type": [ "string", "null" ], "maxLength": 20000 }, "publicMessage": { "type": [ "string", "null" ], "maxLength": 20000 }, "footerText": { "type": [ "string", "null" ], "maxLength": 20000 }, "currency": { "allOf": [ { "$ref": "#/components/schemas/currencyCode" } ] }, "vatInclusive": { "type": "boolean" }, "discountKind": { "allOf": [ { "$ref": "#/components/schemas/discountKindSchema" } ] }, "discountValue": { "type": "number", "minimum": 0 }, "defaultDueDays": { "type": [ "integer", "null" ], "minimum": 0 }, "defaultValidityDays": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 }, "vatRegime": { "allOf": [ { "$ref": "#/components/schemas/documentVatRegimeSchema" } ] }, "lifecycleWorkflowKey": { "allOf": [ { "$ref": "#/components/schemas/templateLifecycleWorkflowKeySchema" } ] }, "termsRichText": { "type": [ "string", "null" ], "maxLength": 100000 }, "termsUrl": { "allOf": [ { "$ref": "#/components/schemas/optionalUrl" } ] }, "termsPdfFileId": { "type": [ "string", "null" ], "format": "uuid" }, "termsPdfVersionId": { "type": [ "string", "null" ], "format": "uuid" }, "type": { "type": "string", "enum": [ "invoice" ] }, "lines": { "type": "array", "items": { "$ref": "#/components/schemas/invoiceTemplateLineInputSchema" }, "maxItems": 500 } }, "required": [ "name", "title", "type" ] }, "invoiceTemplateLineInputSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "kind": { "allOf": [ { "$ref": "#/components/schemas/templateLineKindSchema" } ] }, "lineType": { "anyOf": [ { "$ref": "#/components/schemas/templateLineTypeSchema" }, { "type": "null" } ] }, "productId": { "type": [ "string", "null" ], "format": "uuid" }, "variantId": { "type": [ "string", "null" ], "format": "uuid" }, "title": { "type": "string", "minLength": 1, "maxLength": 500 }, "description": { "type": [ "string", "null" ], "maxLength": 20000 }, "quantity": { "type": "number", "minimum": 0 }, "unit": { "type": [ "string", "null" ], "maxLength": 40 }, "unitPrice": { "type": "number" }, "vatRate": { "type": "number", "minimum": 0, "maximum": 100 }, "discountKind": { "allOf": [ { "$ref": "#/components/schemas/discountKindSchema" } ] }, "discountValue": { "type": "number", "minimum": 0 }, "pricingMode": { "allOf": [ { "$ref": "#/components/schemas/invoiceTemplateLinePricingModeSchema" } ] }, "position": { "type": "number" } }, "required": [ "title" ], "additionalProperties": false }, "invoiceTemplateLinePricingModeSchema": { "type": "string", "enum": [ "unit", "hourly", "fixed" ] }, "optionalUrl": { "type": [ "string", "null" ], "maxLength": 2000 }, "orderDeliveryMethodSchema": { "type": "string", "enum": [ "pickup", "own_delivery", "carrier", "digital", "none" ] }, "orderInvoicingPolicySchema": { "type": "string", "enum": [ "before_fulfillment", "on_fulfillment", "manual" ] }, "orderTemplateInputSchema": { "type": "object", "properties": { "language": { "allOf": [ { "$ref": "#/components/schemas/templateLanguageSchema" } ] }, "isDefault": { "type": "boolean" }, "name": { "type": "string", "minLength": 1, "maxLength": 240 }, "description": { "type": [ "string", "null" ], "maxLength": 20000 }, "title": { "type": "string", "minLength": 1, "maxLength": 240 }, "intro": { "type": [ "string", "null" ], "maxLength": 20000 }, "terms": { "type": [ "string", "null" ], "maxLength": 20000 }, "internalNotes": { "type": [ "string", "null" ], "maxLength": 20000 }, "publicMessage": { "type": [ "string", "null" ], "maxLength": 20000 }, "footerText": { "type": [ "string", "null" ], "maxLength": 20000 }, "currency": { "allOf": [ { "$ref": "#/components/schemas/currencyCode" } ] }, "vatInclusive": { "type": "boolean" }, "discountKind": { "allOf": [ { "$ref": "#/components/schemas/discountKindSchema" } ] }, "discountValue": { "type": "number", "minimum": 0 }, "defaultDueDays": { "type": [ "integer", "null" ], "minimum": 0 }, "defaultValidityDays": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 }, "vatRegime": { "allOf": [ { "$ref": "#/components/schemas/documentVatRegimeSchema" } ] }, "lifecycleWorkflowKey": { "allOf": [ { "$ref": "#/components/schemas/templateLifecycleWorkflowKeySchema" } ] }, "termsRichText": { "type": [ "string", "null" ], "maxLength": 100000 }, "termsUrl": { "allOf": [ { "$ref": "#/components/schemas/optionalUrl" } ] }, "termsPdfFileId": { "type": [ "string", "null" ], "format": "uuid" }, "termsPdfVersionId": { "type": [ "string", "null" ], "format": "uuid" }, "type": { "type": "string", "enum": [ "order" ] }, "orderDeliveryMethod": { "anyOf": [ { "$ref": "#/components/schemas/orderDeliveryMethodSchema" }, { "type": "null" } ] }, "orderInvoicingPolicy": { "anyOf": [ { "$ref": "#/components/schemas/orderInvoicingPolicySchema" }, { "type": "null" } ] }, "orderPaymentRequired": { "type": "boolean" } }, "required": [ "name", "title", "type" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "purchaseOrderTemplateInputSchema": { "type": "object", "properties": { "language": { "allOf": [ { "$ref": "#/components/schemas/templateLanguageSchema" } ] }, "isDefault": { "type": "boolean" }, "name": { "type": "string", "minLength": 1, "maxLength": 240 }, "description": { "type": [ "string", "null" ], "maxLength": 20000 }, "title": { "type": "string", "minLength": 1, "maxLength": 240 }, "intro": { "type": [ "string", "null" ], "maxLength": 20000 }, "terms": { "type": [ "string", "null" ], "maxLength": 20000 }, "internalNotes": { "type": [ "string", "null" ], "maxLength": 20000 }, "publicMessage": { "type": [ "string", "null" ], "maxLength": 20000 }, "footerText": { "type": [ "string", "null" ], "maxLength": 20000 }, "currency": { "allOf": [ { "$ref": "#/components/schemas/currencyCode" } ] }, "vatInclusive": { "type": "boolean" }, "discountKind": { "allOf": [ { "$ref": "#/components/schemas/discountKindSchema" } ] }, "discountValue": { "type": "number", "minimum": 0 }, "defaultDueDays": { "type": [ "integer", "null" ], "minimum": 0 }, "defaultValidityDays": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 }, "vatRegime": { "allOf": [ { "$ref": "#/components/schemas/documentVatRegimeSchema" } ] }, "lifecycleWorkflowKey": { "allOf": [ { "$ref": "#/components/schemas/templateLifecycleWorkflowKeySchema" } ] }, "termsRichText": { "type": [ "string", "null" ], "maxLength": 100000 }, "termsUrl": { "allOf": [ { "$ref": "#/components/schemas/optionalUrl" } ] }, "termsPdfFileId": { "type": [ "string", "null" ], "format": "uuid" }, "termsPdfVersionId": { "type": [ "string", "null" ], "format": "uuid" }, "type": { "type": "string", "enum": [ "purchase_order" ] }, "shipToLocationId": { "type": [ "string", "null" ], "format": "uuid" } }, "required": [ "name", "title", "type" ] }, "quoteDepositKindSchema": { "type": "string", "enum": [ "none", "percent", "fixed" ] }, "quoteTemplateInputSchema": { "type": "object", "properties": { "language": { "allOf": [ { "$ref": "#/components/schemas/templateLanguageSchema" } ] }, "isDefault": { "type": "boolean" }, "name": { "type": "string", "minLength": 1, "maxLength": 240 }, "description": { "type": [ "string", "null" ], "maxLength": 20000 }, "title": { "type": "string", "minLength": 1, "maxLength": 240 }, "intro": { "type": [ "string", "null" ], "maxLength": 20000 }, "terms": { "type": [ "string", "null" ], "maxLength": 20000 }, "internalNotes": { "type": [ "string", "null" ], "maxLength": 20000 }, "publicMessage": { "type": [ "string", "null" ], "maxLength": 20000 }, "footerText": { "type": [ "string", "null" ], "maxLength": 20000 }, "currency": { "allOf": [ { "$ref": "#/components/schemas/currencyCode" } ] }, "vatInclusive": { "type": "boolean" }, "discountKind": { "allOf": [ { "$ref": "#/components/schemas/discountKindSchema" } ] }, "discountValue": { "type": "number", "minimum": 0 }, "defaultDueDays": { "type": [ "integer", "null" ], "minimum": 0 }, "defaultValidityDays": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 }, "vatRegime": { "allOf": [ { "$ref": "#/components/schemas/documentVatRegimeSchema" } ] }, "lifecycleWorkflowKey": { "allOf": [ { "$ref": "#/components/schemas/templateLifecycleWorkflowKeySchema" } ] }, "termsRichText": { "type": [ "string", "null" ], "maxLength": 100000 }, "termsUrl": { "allOf": [ { "$ref": "#/components/schemas/optionalUrl" } ] }, "termsPdfFileId": { "type": [ "string", "null" ], "format": "uuid" }, "termsPdfVersionId": { "type": [ "string", "null" ], "format": "uuid" }, "type": { "type": "string", "enum": [ "quote" ] }, "depositKind": { "allOf": [ { "$ref": "#/components/schemas/quoteDepositKindSchema" } ] }, "depositValue": { "type": "number", "minimum": 0 }, "depositRequiredToAccept": { "type": "boolean" }, "createOrderOnAccept": { "type": "boolean" }, "lines": { "type": "array", "items": { "$ref": "#/components/schemas/quoteTemplateLineInputSchema" }, "maxItems": 500 } }, "required": [ "name", "title", "type" ] }, "quoteTemplateLineInputSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "kind": { "allOf": [ { "$ref": "#/components/schemas/templateLineKindSchema" } ] }, "lineType": { "anyOf": [ { "$ref": "#/components/schemas/templateLineTypeSchema" }, { "type": "null" } ] }, "productId": { "type": [ "string", "null" ], "format": "uuid" }, "variantId": { "type": [ "string", "null" ], "format": "uuid" }, "title": { "type": "string", "minLength": 1, "maxLength": 500 }, "description": { "type": [ "string", "null" ], "maxLength": 20000 }, "quantity": { "type": "number", "minimum": 0 }, "unit": { "type": [ "string", "null" ], "maxLength": 40 }, "unitPrice": { "type": "number" }, "vatRate": { "type": "number", "minimum": 0, "maximum": 100 }, "discountKind": { "allOf": [ { "$ref": "#/components/schemas/discountKindSchema" } ] }, "discountValue": { "type": "number", "minimum": 0 }, "isOptional": { "type": "boolean" }, "pricingMode": { "allOf": [ { "$ref": "#/components/schemas/quoteTemplateLinePricingModeSchema" } ] }, "subscriptionInterval": { "anyOf": [ { "$ref": "#/components/schemas/quoteTemplateSubscriptionIntervalSchema" }, { "type": "null" } ] }, "subscriptionPeriods": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 }, "position": { "type": "number" } }, "required": [ "title" ], "additionalProperties": false }, "quoteTemplateLinePricingModeSchema": { "type": "string", "enum": [ "unit", "hourly", "subscription", "fixed" ] }, "quoteTemplateSubscriptionIntervalSchema": { "type": "string", "enum": [ "weekly", "monthly", "quarterly", "yearly" ] }, "taskPrioritySchema": { "type": "string", "enum": [ "low", "medium", "high", "urgent" ] }, "taskStatusSchema": { "type": "string", "enum": [ "todo", "in_progress", "on_hold", "done" ] }, "taskTemplateInputSchema": { "type": "object", "properties": { "language": { "allOf": [ { "$ref": "#/components/schemas/templateLanguageSchema" } ] }, "isDefault": { "type": "boolean" }, "name": { "type": "string", "minLength": 1, "maxLength": 240 }, "description": { "type": [ "string", "null" ], "maxLength": 20000 }, "title": { "type": "string", "minLength": 1, "maxLength": 240 }, "intro": { "type": [ "string", "null" ], "maxLength": 20000 }, "terms": { "type": [ "string", "null" ], "maxLength": 20000 }, "internalNotes": { "type": [ "string", "null" ], "maxLength": 20000 }, "publicMessage": { "type": [ "string", "null" ], "maxLength": 20000 }, "footerText": { "type": [ "string", "null" ], "maxLength": 20000 }, "currency": { "allOf": [ { "$ref": "#/components/schemas/currencyCode" } ] }, "vatInclusive": { "type": "boolean" }, "discountKind": { "allOf": [ { "$ref": "#/components/schemas/discountKindSchema" } ] }, "discountValue": { "type": "number", "minimum": 0 }, "defaultDueDays": { "type": [ "integer", "null" ], "minimum": 0 }, "defaultValidityDays": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 }, "vatRegime": { "allOf": [ { "$ref": "#/components/schemas/documentVatRegimeSchema" } ] }, "lifecycleWorkflowKey": { "allOf": [ { "$ref": "#/components/schemas/templateLifecycleWorkflowKeySchema" } ] }, "termsRichText": { "type": [ "string", "null" ], "maxLength": 100000 }, "termsUrl": { "allOf": [ { "$ref": "#/components/schemas/optionalUrl" } ] }, "termsPdfFileId": { "type": [ "string", "null" ], "format": "uuid" }, "termsPdfVersionId": { "type": [ "string", "null" ], "format": "uuid" }, "type": { "type": "string", "enum": [ "task" ] }, "task": { "$ref": "#/components/schemas/templateTaskSettingsInputSchema" }, "subtasks": { "type": "array", "items": { "$ref": "#/components/schemas/templateTaskSubtaskInputSchema" }, "maxItems": 100 }, "assigneeIds": { "type": "array", "items": { "type": "string", "format": "uuid" }, "maxItems": 50 }, "tagIds": { "type": "array", "items": { "type": "string", "format": "uuid" }, "maxItems": 50 } }, "required": [ "name", "title", "type", "task" ] }, "templateIdSchema": { "type": "string", "format": "uuid" }, "templateInputSchema": { "type": "object", "discriminator": { "propertyName": "type" }, "oneOf": [ { "$ref": "#/components/schemas/invoiceTemplateInputSchema" }, { "$ref": "#/components/schemas/quoteTemplateInputSchema" }, { "$ref": "#/components/schemas/taskTemplateInputSchema" }, { "$ref": "#/components/schemas/orderTemplateInputSchema" }, { "$ref": "#/components/schemas/purchaseOrderTemplateInputSchema" } ] }, "templateLanguageSchema": { "type": "string", "enum": [ "en", "nl" ] }, "templateLifecycleWorkflowKeySchema": { "type": [ "string", "null" ], "minLength": 1, "maxLength": 120, "pattern": "^[a-z0-9][a-z0-9-]*$" }, "templateLineKindSchema": { "type": "string", "enum": [ "line", "section_header" ] }, "templateLineTypeSchema": { "type": "string", "enum": [ "product", "labor", "custom" ] }, "templateSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/templateIdSchema" }, "organizationId": { "type": "string", "format": "uuid" }, "type": { "$ref": "#/components/schemas/templateTypeSchema" }, "language": { "$ref": "#/components/schemas/templateLanguageSchema" }, "isDefault": { "type": "boolean" }, "name": { "type": "string" }, "description": { "type": [ "string", "null" ] }, "title": { "type": "string" }, "intro": { "type": [ "string", "null" ] }, "terms": { "type": [ "string", "null" ] }, "internalNotes": { "type": [ "string", "null" ] }, "publicMessage": { "type": [ "string", "null" ] }, "footerText": { "type": [ "string", "null" ] }, "currency": { "$ref": "#/components/schemas/currencyCode" }, "vatInclusive": { "type": "boolean" }, "discountKind": { "$ref": "#/components/schemas/discountKindSchema" }, "discountValue": { "type": "number" }, "defaultDueDays": { "type": [ "integer", "null" ], "minimum": 0 }, "defaultValidityDays": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 }, "vatRegime": { "$ref": "#/components/schemas/documentVatRegimeSchema" }, "termsRichText": { "type": [ "string", "null" ], "maxLength": 100000 }, "termsUrl": { "type": [ "string", "null" ] }, "termsPdfFileId": { "type": [ "string", "null" ], "format": "uuid" }, "termsPdfVersionId": { "type": [ "string", "null" ], "format": "uuid" }, "depositKind": { "$ref": "#/components/schemas/quoteDepositKindSchema" }, "depositValue": { "type": "number", "minimum": 0 }, "depositRequiredToAccept": { "type": "boolean" }, "lifecycleWorkflowKey": { "type": [ "string", "null" ] }, "orderDeliveryMethod": { "anyOf": [ { "$ref": "#/components/schemas/orderDeliveryMethodSchema" }, { "type": "null" } ] }, "orderInvoicingPolicy": { "anyOf": [ { "$ref": "#/components/schemas/orderInvoicingPolicySchema" }, { "type": "null" } ] }, "orderPaymentRequired": { "type": "boolean" }, "shipToLocationId": { "type": [ "string", "null" ], "format": "uuid" }, "createOrderOnAccept": { "type": "boolean" }, "archivedAt": { "type": [ "string", "null" ], "format": "date-time" }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "updatedBy": { "type": [ "string", "null" ], "format": "uuid" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "type", "language", "isDefault", "name", "description", "title", "intro", "terms", "internalNotes", "publicMessage", "footerText", "currency", "vatInclusive", "discountKind", "discountValue", "defaultDueDays", "defaultValidityDays", "vatRegime", "termsRichText", "termsUrl", "termsPdfFileId", "termsPdfVersionId", "depositKind", "depositValue", "depositRequiredToAccept", "lifecycleWorkflowKey", "orderDeliveryMethod", "orderInvoicingPolicy", "orderPaymentRequired", "shipToLocationId", "createOrderOnAccept", "archivedAt", "createdBy", "updatedBy", "createdAt", "updatedAt" ] }, "templateTaskSettingsInputSchema": { "type": "object", "properties": { "title": { "type": "string", "minLength": 1, "maxLength": 240 }, "description": { "type": [ "string", "null" ], "maxLength": 20000 }, "status": { "allOf": [ { "$ref": "#/components/schemas/taskStatusSchema" } ] }, "priority": { "allOf": [ { "$ref": "#/components/schemas/taskPrioritySchema" } ] }, "customerId": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 }, "customerAssetId": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 } }, "required": [ "title" ] }, "templateTaskSubtaskInputSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "title": { "type": "string", "minLength": 1, "maxLength": 240 }, "status": { "allOf": [ { "$ref": "#/components/schemas/taskStatusSchema" } ] }, "priority": { "allOf": [ { "$ref": "#/components/schemas/taskPrioritySchema" } ] }, "position": { "type": "number" } }, "required": [ "title" ] }, "templateTypeSchema": { "type": "string", "enum": [ "invoice", "quote", "task", "order", "purchase_order" ] } } ``` # Get a template. (/openapi/getTemplate) Return a template detail record for the specified organization. ## GET /v1/organizations/{organizationId}/templates/{templateId} ```json { "operationId": "getTemplate", "summary": "Get a template.", "description": "Return a template detail record for the specified organization.", "tags": [ "Templates" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "templateId", "required": true, "schema": { "$ref": "#/components/schemas/templateIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns the requested template.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/templateDetailSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "currencyCode": { "type": "string", "pattern": "^[A-Z]{3}$" }, "discountKindSchema": { "type": "string", "enum": [ "none", "percentage", "fixed" ] }, "documentVatRegimeSchema": { "type": "string", "enum": [ "standard", "reverse_charge", "exempt", "kor", "margin_scheme" ] }, "invoiceTemplateDetailSchema": { "type": "object", "properties": { "template": { "$ref": "#/components/schemas/invoiceTemplateSchema" }, "lines": { "type": "array", "items": { "$ref": "#/components/schemas/invoiceTemplateLineSchema" } } }, "required": [ "template", "lines" ] }, "invoiceTemplateLinePricingModeSchema": { "type": "string", "enum": [ "unit", "hourly", "fixed" ] }, "invoiceTemplateLineSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "templateId": { "$ref": "#/components/schemas/templateIdSchema" }, "organizationId": { "type": "string", "format": "uuid" }, "kind": { "$ref": "#/components/schemas/templateLineKindSchema" }, "lineType": { "anyOf": [ { "$ref": "#/components/schemas/templateLineTypeSchema" }, { "type": "null" } ] }, "productId": { "type": [ "string", "null" ], "format": "uuid" }, "variantId": { "type": [ "string", "null" ], "format": "uuid" }, "title": { "type": "string" }, "description": { "type": [ "string", "null" ] }, "quantity": { "type": "number" }, "unit": { "type": [ "string", "null" ] }, "unitPrice": { "type": "number" }, "vatRate": { "type": "number" }, "discountKind": { "$ref": "#/components/schemas/discountKindSchema" }, "discountValue": { "type": "number" }, "pricingMode": { "$ref": "#/components/schemas/invoiceTemplateLinePricingModeSchema" }, "position": { "type": "number" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "templateId", "organizationId", "kind", "lineType", "productId", "variantId", "title", "description", "quantity", "unit", "unitPrice", "vatRate", "discountKind", "discountValue", "pricingMode", "position", "createdAt", "updatedAt" ] }, "invoiceTemplateSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/templateIdSchema" }, "organizationId": { "type": "string", "format": "uuid" }, "type": { "type": "string", "enum": [ "invoice" ] }, "language": { "$ref": "#/components/schemas/templateLanguageSchema" }, "isDefault": { "type": "boolean" }, "name": { "type": "string" }, "description": { "type": [ "string", "null" ] }, "title": { "type": "string" }, "intro": { "type": [ "string", "null" ] }, "terms": { "type": [ "string", "null" ] }, "internalNotes": { "type": [ "string", "null" ] }, "publicMessage": { "type": [ "string", "null" ] }, "footerText": { "type": [ "string", "null" ] }, "currency": { "$ref": "#/components/schemas/currencyCode" }, "vatInclusive": { "type": "boolean" }, "discountKind": { "$ref": "#/components/schemas/discountKindSchema" }, "discountValue": { "type": "number" }, "defaultDueDays": { "type": [ "integer", "null" ], "minimum": 0 }, "defaultValidityDays": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 }, "vatRegime": { "$ref": "#/components/schemas/documentVatRegimeSchema" }, "termsRichText": { "type": [ "string", "null" ], "maxLength": 100000 }, "termsUrl": { "type": [ "string", "null" ] }, "termsPdfFileId": { "type": [ "string", "null" ], "format": "uuid" }, "termsPdfVersionId": { "type": [ "string", "null" ], "format": "uuid" }, "depositKind": { "$ref": "#/components/schemas/quoteDepositKindSchema" }, "depositValue": { "type": "number", "minimum": 0 }, "depositRequiredToAccept": { "type": "boolean" }, "lifecycleWorkflowKey": { "type": [ "string", "null" ] }, "orderDeliveryMethod": { "anyOf": [ { "$ref": "#/components/schemas/orderDeliveryMethodSchema" }, { "type": "null" } ] }, "orderInvoicingPolicy": { "anyOf": [ { "$ref": "#/components/schemas/orderInvoicingPolicySchema" }, { "type": "null" } ] }, "orderPaymentRequired": { "type": "boolean" }, "shipToLocationId": { "type": [ "string", "null" ], "format": "uuid" }, "createOrderOnAccept": { "type": "boolean" }, "archivedAt": { "type": [ "string", "null" ], "format": "date-time" }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "updatedBy": { "type": [ "string", "null" ], "format": "uuid" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "type", "language", "isDefault", "name", "description", "title", "intro", "terms", "internalNotes", "publicMessage", "footerText", "currency", "vatInclusive", "discountKind", "discountValue", "defaultDueDays", "defaultValidityDays", "vatRegime", "termsRichText", "termsUrl", "termsPdfFileId", "termsPdfVersionId", "depositKind", "depositValue", "depositRequiredToAccept", "lifecycleWorkflowKey", "orderDeliveryMethod", "orderInvoicingPolicy", "orderPaymentRequired", "shipToLocationId", "createOrderOnAccept", "archivedAt", "createdBy", "updatedBy", "createdAt", "updatedAt" ] }, "orderDeliveryMethodSchema": { "type": "string", "enum": [ "pickup", "own_delivery", "carrier", "digital", "none" ] }, "orderInvoicingPolicySchema": { "type": "string", "enum": [ "before_fulfillment", "on_fulfillment", "manual" ] }, "orderTemplateDetailSchema": { "type": "object", "properties": { "template": { "$ref": "#/components/schemas/orderTemplateSchema" }, "lines": { "type": "array", "prefixItems": [], "items": false } }, "required": [ "template", "lines" ] }, "orderTemplateSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/templateIdSchema" }, "organizationId": { "type": "string", "format": "uuid" }, "type": { "type": "string", "enum": [ "order" ] }, "language": { "$ref": "#/components/schemas/templateLanguageSchema" }, "isDefault": { "type": "boolean" }, "name": { "type": "string" }, "description": { "type": [ "string", "null" ] }, "title": { "type": "string" }, "intro": { "type": [ "string", "null" ] }, "terms": { "type": [ "string", "null" ] }, "internalNotes": { "type": [ "string", "null" ] }, "publicMessage": { "type": [ "string", "null" ] }, "footerText": { "type": [ "string", "null" ] }, "currency": { "$ref": "#/components/schemas/currencyCode" }, "vatInclusive": { "type": "boolean" }, "discountKind": { "$ref": "#/components/schemas/discountKindSchema" }, "discountValue": { "type": "number" }, "defaultDueDays": { "type": [ "integer", "null" ], "minimum": 0 }, "defaultValidityDays": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 }, "vatRegime": { "$ref": "#/components/schemas/documentVatRegimeSchema" }, "termsRichText": { "type": [ "string", "null" ], "maxLength": 100000 }, "termsUrl": { "type": [ "string", "null" ] }, "termsPdfFileId": { "type": [ "string", "null" ], "format": "uuid" }, "termsPdfVersionId": { "type": [ "string", "null" ], "format": "uuid" }, "depositKind": { "$ref": "#/components/schemas/quoteDepositKindSchema" }, "depositValue": { "type": "number", "minimum": 0 }, "depositRequiredToAccept": { "type": "boolean" }, "lifecycleWorkflowKey": { "type": [ "string", "null" ] }, "orderDeliveryMethod": { "anyOf": [ { "$ref": "#/components/schemas/orderDeliveryMethodSchema" }, { "type": "null" } ] }, "orderInvoicingPolicy": { "anyOf": [ { "$ref": "#/components/schemas/orderInvoicingPolicySchema" }, { "type": "null" } ] }, "orderPaymentRequired": { "type": "boolean" }, "shipToLocationId": { "type": [ "string", "null" ], "format": "uuid" }, "createOrderOnAccept": { "type": "boolean" }, "archivedAt": { "type": [ "string", "null" ], "format": "date-time" }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "updatedBy": { "type": [ "string", "null" ], "format": "uuid" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "type", "language", "isDefault", "name", "description", "title", "intro", "terms", "internalNotes", "publicMessage", "footerText", "currency", "vatInclusive", "discountKind", "discountValue", "defaultDueDays", "defaultValidityDays", "vatRegime", "termsRichText", "termsUrl", "termsPdfFileId", "termsPdfVersionId", "depositKind", "depositValue", "depositRequiredToAccept", "lifecycleWorkflowKey", "orderDeliveryMethod", "orderInvoicingPolicy", "orderPaymentRequired", "shipToLocationId", "createOrderOnAccept", "archivedAt", "createdBy", "updatedBy", "createdAt", "updatedAt" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "purchaseOrderTemplateDetailSchema": { "type": "object", "properties": { "template": { "$ref": "#/components/schemas/purchaseOrderTemplateSchema" }, "lines": { "type": "array", "prefixItems": [], "items": false } }, "required": [ "template", "lines" ] }, "purchaseOrderTemplateSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/templateIdSchema" }, "organizationId": { "type": "string", "format": "uuid" }, "type": { "type": "string", "enum": [ "purchase_order" ] }, "language": { "$ref": "#/components/schemas/templateLanguageSchema" }, "isDefault": { "type": "boolean" }, "name": { "type": "string" }, "description": { "type": [ "string", "null" ] }, "title": { "type": "string" }, "intro": { "type": [ "string", "null" ] }, "terms": { "type": [ "string", "null" ] }, "internalNotes": { "type": [ "string", "null" ] }, "publicMessage": { "type": [ "string", "null" ] }, "footerText": { "type": [ "string", "null" ] }, "currency": { "$ref": "#/components/schemas/currencyCode" }, "vatInclusive": { "type": "boolean" }, "discountKind": { "$ref": "#/components/schemas/discountKindSchema" }, "discountValue": { "type": "number" }, "defaultDueDays": { "type": [ "integer", "null" ], "minimum": 0 }, "defaultValidityDays": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 }, "vatRegime": { "$ref": "#/components/schemas/documentVatRegimeSchema" }, "termsRichText": { "type": [ "string", "null" ], "maxLength": 100000 }, "termsUrl": { "type": [ "string", "null" ] }, "termsPdfFileId": { "type": [ "string", "null" ], "format": "uuid" }, "termsPdfVersionId": { "type": [ "string", "null" ], "format": "uuid" }, "depositKind": { "$ref": "#/components/schemas/quoteDepositKindSchema" }, "depositValue": { "type": "number", "minimum": 0 }, "depositRequiredToAccept": { "type": "boolean" }, "lifecycleWorkflowKey": { "type": [ "string", "null" ] }, "orderDeliveryMethod": { "anyOf": [ { "$ref": "#/components/schemas/orderDeliveryMethodSchema" }, { "type": "null" } ] }, "orderInvoicingPolicy": { "anyOf": [ { "$ref": "#/components/schemas/orderInvoicingPolicySchema" }, { "type": "null" } ] }, "orderPaymentRequired": { "type": "boolean" }, "shipToLocationId": { "type": [ "string", "null" ], "format": "uuid" }, "createOrderOnAccept": { "type": "boolean" }, "archivedAt": { "type": [ "string", "null" ], "format": "date-time" }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "updatedBy": { "type": [ "string", "null" ], "format": "uuid" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "type", "language", "isDefault", "name", "description", "title", "intro", "terms", "internalNotes", "publicMessage", "footerText", "currency", "vatInclusive", "discountKind", "discountValue", "defaultDueDays", "defaultValidityDays", "vatRegime", "termsRichText", "termsUrl", "termsPdfFileId", "termsPdfVersionId", "depositKind", "depositValue", "depositRequiredToAccept", "lifecycleWorkflowKey", "orderDeliveryMethod", "orderInvoicingPolicy", "orderPaymentRequired", "shipToLocationId", "createOrderOnAccept", "archivedAt", "createdBy", "updatedBy", "createdAt", "updatedAt" ] }, "quoteDepositKindSchema": { "type": "string", "enum": [ "none", "percent", "fixed" ] }, "quoteTemplateDetailSchema": { "type": "object", "properties": { "template": { "$ref": "#/components/schemas/quoteTemplateSchema" }, "lines": { "type": "array", "items": { "$ref": "#/components/schemas/quoteTemplateLineSchema" } } }, "required": [ "template", "lines" ] }, "quoteTemplateLinePricingModeSchema": { "type": "string", "enum": [ "unit", "hourly", "subscription", "fixed" ] }, "quoteTemplateLineSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "templateId": { "$ref": "#/components/schemas/templateIdSchema" }, "organizationId": { "type": "string", "format": "uuid" }, "kind": { "$ref": "#/components/schemas/templateLineKindSchema" }, "lineType": { "anyOf": [ { "$ref": "#/components/schemas/templateLineTypeSchema" }, { "type": "null" } ] }, "productId": { "type": [ "string", "null" ], "format": "uuid" }, "variantId": { "type": [ "string", "null" ], "format": "uuid" }, "title": { "type": "string" }, "description": { "type": [ "string", "null" ] }, "quantity": { "type": "number" }, "unit": { "type": [ "string", "null" ] }, "unitPrice": { "type": "number" }, "vatRate": { "type": "number" }, "discountKind": { "$ref": "#/components/schemas/discountKindSchema" }, "discountValue": { "type": "number" }, "isOptional": { "type": "boolean" }, "pricingMode": { "$ref": "#/components/schemas/quoteTemplateLinePricingModeSchema" }, "subscriptionInterval": { "anyOf": [ { "$ref": "#/components/schemas/quoteTemplateSubscriptionIntervalSchema" }, { "type": "null" } ] }, "subscriptionPeriods": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 }, "position": { "type": "number" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "templateId", "organizationId", "kind", "lineType", "productId", "variantId", "title", "description", "quantity", "unit", "unitPrice", "vatRate", "discountKind", "discountValue", "isOptional", "pricingMode", "subscriptionInterval", "subscriptionPeriods", "position", "createdAt", "updatedAt" ] }, "quoteTemplateSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/templateIdSchema" }, "organizationId": { "type": "string", "format": "uuid" }, "type": { "type": "string", "enum": [ "quote" ] }, "language": { "$ref": "#/components/schemas/templateLanguageSchema" }, "isDefault": { "type": "boolean" }, "name": { "type": "string" }, "description": { "type": [ "string", "null" ] }, "title": { "type": "string" }, "intro": { "type": [ "string", "null" ] }, "terms": { "type": [ "string", "null" ] }, "internalNotes": { "type": [ "string", "null" ] }, "publicMessage": { "type": [ "string", "null" ] }, "footerText": { "type": [ "string", "null" ] }, "currency": { "$ref": "#/components/schemas/currencyCode" }, "vatInclusive": { "type": "boolean" }, "discountKind": { "$ref": "#/components/schemas/discountKindSchema" }, "discountValue": { "type": "number" }, "defaultDueDays": { "type": [ "integer", "null" ], "minimum": 0 }, "defaultValidityDays": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 }, "vatRegime": { "$ref": "#/components/schemas/documentVatRegimeSchema" }, "termsRichText": { "type": [ "string", "null" ], "maxLength": 100000 }, "termsUrl": { "type": [ "string", "null" ] }, "termsPdfFileId": { "type": [ "string", "null" ], "format": "uuid" }, "termsPdfVersionId": { "type": [ "string", "null" ], "format": "uuid" }, "depositKind": { "$ref": "#/components/schemas/quoteDepositKindSchema" }, "depositValue": { "type": "number", "minimum": 0 }, "depositRequiredToAccept": { "type": "boolean" }, "lifecycleWorkflowKey": { "type": [ "string", "null" ] }, "orderDeliveryMethod": { "anyOf": [ { "$ref": "#/components/schemas/orderDeliveryMethodSchema" }, { "type": "null" } ] }, "orderInvoicingPolicy": { "anyOf": [ { "$ref": "#/components/schemas/orderInvoicingPolicySchema" }, { "type": "null" } ] }, "orderPaymentRequired": { "type": "boolean" }, "shipToLocationId": { "type": [ "string", "null" ], "format": "uuid" }, "createOrderOnAccept": { "type": "boolean" }, "archivedAt": { "type": [ "string", "null" ], "format": "date-time" }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "updatedBy": { "type": [ "string", "null" ], "format": "uuid" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "type", "language", "isDefault", "name", "description", "title", "intro", "terms", "internalNotes", "publicMessage", "footerText", "currency", "vatInclusive", "discountKind", "discountValue", "defaultDueDays", "defaultValidityDays", "vatRegime", "termsRichText", "termsUrl", "termsPdfFileId", "termsPdfVersionId", "depositKind", "depositValue", "depositRequiredToAccept", "lifecycleWorkflowKey", "orderDeliveryMethod", "orderInvoicingPolicy", "orderPaymentRequired", "shipToLocationId", "createOrderOnAccept", "archivedAt", "createdBy", "updatedBy", "createdAt", "updatedAt" ] }, "quoteTemplateSubscriptionIntervalSchema": { "type": "string", "enum": [ "weekly", "monthly", "quarterly", "yearly" ] }, "taskPrioritySchema": { "type": "string", "enum": [ "low", "medium", "high", "urgent" ] }, "taskStatusSchema": { "type": "string", "enum": [ "todo", "in_progress", "on_hold", "done" ] }, "taskTemplateDetailSchema": { "type": "object", "properties": { "template": { "$ref": "#/components/schemas/taskTemplateSchema" }, "lines": { "type": "array", "prefixItems": [], "items": false }, "task": { "$ref": "#/components/schemas/templateTaskSettingsSchema" }, "subtasks": { "type": "array", "items": { "$ref": "#/components/schemas/templateTaskSubtaskSchema" } }, "assigneeIds": { "type": "array", "items": { "type": "string", "format": "uuid" } }, "tagIds": { "type": "array", "items": { "type": "string", "format": "uuid" } } }, "required": [ "template", "lines", "task", "subtasks", "assigneeIds", "tagIds" ] }, "taskTemplateSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/templateIdSchema" }, "organizationId": { "type": "string", "format": "uuid" }, "type": { "type": "string", "enum": [ "task" ] }, "language": { "$ref": "#/components/schemas/templateLanguageSchema" }, "isDefault": { "type": "boolean" }, "name": { "type": "string" }, "description": { "type": [ "string", "null" ] }, "title": { "type": "string" }, "intro": { "type": [ "string", "null" ] }, "terms": { "type": [ "string", "null" ] }, "internalNotes": { "type": [ "string", "null" ] }, "publicMessage": { "type": [ "string", "null" ] }, "footerText": { "type": [ "string", "null" ] }, "currency": { "$ref": "#/components/schemas/currencyCode" }, "vatInclusive": { "type": "boolean" }, "discountKind": { "$ref": "#/components/schemas/discountKindSchema" }, "discountValue": { "type": "number" }, "defaultDueDays": { "type": [ "integer", "null" ], "minimum": 0 }, "defaultValidityDays": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 }, "vatRegime": { "$ref": "#/components/schemas/documentVatRegimeSchema" }, "termsRichText": { "type": [ "string", "null" ], "maxLength": 100000 }, "termsUrl": { "type": [ "string", "null" ] }, "termsPdfFileId": { "type": [ "string", "null" ], "format": "uuid" }, "termsPdfVersionId": { "type": [ "string", "null" ], "format": "uuid" }, "depositKind": { "$ref": "#/components/schemas/quoteDepositKindSchema" }, "depositValue": { "type": "number", "minimum": 0 }, "depositRequiredToAccept": { "type": "boolean" }, "lifecycleWorkflowKey": { "type": [ "string", "null" ] }, "orderDeliveryMethod": { "anyOf": [ { "$ref": "#/components/schemas/orderDeliveryMethodSchema" }, { "type": "null" } ] }, "orderInvoicingPolicy": { "anyOf": [ { "$ref": "#/components/schemas/orderInvoicingPolicySchema" }, { "type": "null" } ] }, "orderPaymentRequired": { "type": "boolean" }, "shipToLocationId": { "type": [ "string", "null" ], "format": "uuid" }, "createOrderOnAccept": { "type": "boolean" }, "archivedAt": { "type": [ "string", "null" ], "format": "date-time" }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "updatedBy": { "type": [ "string", "null" ], "format": "uuid" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "type", "language", "isDefault", "name", "description", "title", "intro", "terms", "internalNotes", "publicMessage", "footerText", "currency", "vatInclusive", "discountKind", "discountValue", "defaultDueDays", "defaultValidityDays", "vatRegime", "termsRichText", "termsUrl", "termsPdfFileId", "termsPdfVersionId", "depositKind", "depositValue", "depositRequiredToAccept", "lifecycleWorkflowKey", "orderDeliveryMethod", "orderInvoicingPolicy", "orderPaymentRequired", "shipToLocationId", "createOrderOnAccept", "archivedAt", "createdBy", "updatedBy", "createdAt", "updatedAt" ] }, "templateDetailSchema": { "anyOf": [ { "$ref": "#/components/schemas/invoiceTemplateDetailSchema" }, { "$ref": "#/components/schemas/quoteTemplateDetailSchema" }, { "$ref": "#/components/schemas/taskTemplateDetailSchema" }, { "$ref": "#/components/schemas/orderTemplateDetailSchema" }, { "$ref": "#/components/schemas/purchaseOrderTemplateDetailSchema" } ] }, "templateIdSchema": { "type": "string", "format": "uuid" }, "templateLanguageSchema": { "type": "string", "enum": [ "en", "nl" ] }, "templateLineKindSchema": { "type": "string", "enum": [ "line", "section_header" ] }, "templateLineTypeSchema": { "type": "string", "enum": [ "product", "labor", "custom" ] }, "templateTaskSettingsSchema": { "type": "object", "properties": { "title": { "type": "string" }, "description": { "type": [ "string", "null" ] }, "status": { "$ref": "#/components/schemas/taskStatusSchema" }, "priority": { "$ref": "#/components/schemas/taskPrioritySchema" }, "customerId": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 }, "customerAssetId": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 } }, "required": [ "title", "description", "status", "priority", "customerId", "customerAssetId" ] }, "templateTaskSubtaskSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "templateId": { "$ref": "#/components/schemas/templateIdSchema" }, "organizationId": { "type": "string", "format": "uuid" }, "title": { "type": "string" }, "status": { "$ref": "#/components/schemas/taskStatusSchema" }, "priority": { "$ref": "#/components/schemas/taskPrioritySchema" }, "position": { "type": "number" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "templateId", "organizationId", "title", "status", "priority", "position", "createdAt", "updatedAt" ] } } ``` # Update a template. (/openapi/updateTemplate) Update a reusable quote or invoice template for the specified organization. ## PATCH /v1/organizations/{organizationId}/templates/{templateId} ```json { "operationId": "updateTemplate", "summary": "Update a template.", "description": "Update a reusable quote or invoice template for the specified organization.", "tags": [ "Templates" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "templateId", "required": true, "schema": { "$ref": "#/components/schemas/templateIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/templatePatchSchema" } } }, "description": "The template patch payload." }, "responses": { "200": { "description": "Returns the updated template.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/templateSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "currencyCode": { "type": "string", "pattern": "^[A-Z]{3}$" }, "discountKindSchema": { "type": "string", "enum": [ "none", "percentage", "fixed" ] }, "documentVatRegimeSchema": { "type": "string", "enum": [ "standard", "reverse_charge", "exempt", "kor", "margin_scheme" ] }, "invoiceTemplateLineInputSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "kind": { "allOf": [ { "$ref": "#/components/schemas/templateLineKindSchema" } ] }, "lineType": { "anyOf": [ { "$ref": "#/components/schemas/templateLineTypeSchema" }, { "type": "null" } ] }, "productId": { "type": [ "string", "null" ], "format": "uuid" }, "variantId": { "type": [ "string", "null" ], "format": "uuid" }, "title": { "type": "string", "minLength": 1, "maxLength": 500 }, "description": { "type": [ "string", "null" ], "maxLength": 20000 }, "quantity": { "type": "number", "minimum": 0 }, "unit": { "type": [ "string", "null" ], "maxLength": 40 }, "unitPrice": { "type": "number" }, "vatRate": { "type": "number", "minimum": 0, "maximum": 100 }, "discountKind": { "allOf": [ { "$ref": "#/components/schemas/discountKindSchema" } ] }, "discountValue": { "type": "number", "minimum": 0 }, "pricingMode": { "allOf": [ { "$ref": "#/components/schemas/invoiceTemplateLinePricingModeSchema" } ] }, "position": { "type": "number" } }, "required": [ "title" ], "additionalProperties": false }, "invoiceTemplateLinePricingModeSchema": { "type": "string", "enum": [ "unit", "hourly", "fixed" ] }, "invoiceTemplatePatchSchema": { "type": "object", "properties": { "language": { "allOf": [ { "$ref": "#/components/schemas/templateLanguageSchema" } ] }, "isDefault": { "type": "boolean" }, "name": { "type": "string", "minLength": 1, "maxLength": 240 }, "description": { "type": [ "string", "null" ], "maxLength": 20000 }, "title": { "type": "string", "minLength": 1, "maxLength": 240 }, "intro": { "type": [ "string", "null" ], "maxLength": 20000 }, "terms": { "type": [ "string", "null" ], "maxLength": 20000 }, "internalNotes": { "type": [ "string", "null" ], "maxLength": 20000 }, "publicMessage": { "type": [ "string", "null" ], "maxLength": 20000 }, "footerText": { "type": [ "string", "null" ], "maxLength": 20000 }, "currency": { "allOf": [ { "$ref": "#/components/schemas/currencyCode" } ] }, "vatInclusive": { "type": "boolean" }, "discountKind": { "allOf": [ { "$ref": "#/components/schemas/discountKindSchema" } ] }, "discountValue": { "type": "number", "minimum": 0 }, "defaultDueDays": { "type": [ "integer", "null" ], "minimum": 0 }, "defaultValidityDays": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 }, "vatRegime": { "allOf": [ { "$ref": "#/components/schemas/documentVatRegimeSchema" } ] }, "lifecycleWorkflowKey": { "allOf": [ { "$ref": "#/components/schemas/templateLifecycleWorkflowKeySchema" } ] }, "termsRichText": { "type": [ "string", "null" ], "maxLength": 100000 }, "termsUrl": { "allOf": [ { "$ref": "#/components/schemas/optionalUrl" } ] }, "termsPdfFileId": { "type": [ "string", "null" ], "format": "uuid" }, "termsPdfVersionId": { "type": [ "string", "null" ], "format": "uuid" }, "type": { "type": "string", "enum": [ "invoice" ] }, "lines": { "type": "array", "items": { "$ref": "#/components/schemas/invoiceTemplateLineInputSchema" }, "maxItems": 500 } } }, "optionalUrl": { "type": [ "string", "null" ], "maxLength": 2000 }, "orderDeliveryMethodSchema": { "type": "string", "enum": [ "pickup", "own_delivery", "carrier", "digital", "none" ] }, "orderInvoicingPolicySchema": { "type": "string", "enum": [ "before_fulfillment", "on_fulfillment", "manual" ] }, "orderTemplatePatchSchema": { "type": "object", "properties": { "language": { "allOf": [ { "$ref": "#/components/schemas/templateLanguageSchema" } ] }, "isDefault": { "type": "boolean" }, "name": { "type": "string", "minLength": 1, "maxLength": 240 }, "description": { "type": [ "string", "null" ], "maxLength": 20000 }, "title": { "type": "string", "minLength": 1, "maxLength": 240 }, "intro": { "type": [ "string", "null" ], "maxLength": 20000 }, "terms": { "type": [ "string", "null" ], "maxLength": 20000 }, "internalNotes": { "type": [ "string", "null" ], "maxLength": 20000 }, "publicMessage": { "type": [ "string", "null" ], "maxLength": 20000 }, "footerText": { "type": [ "string", "null" ], "maxLength": 20000 }, "currency": { "allOf": [ { "$ref": "#/components/schemas/currencyCode" } ] }, "vatInclusive": { "type": "boolean" }, "discountKind": { "allOf": [ { "$ref": "#/components/schemas/discountKindSchema" } ] }, "discountValue": { "type": "number", "minimum": 0 }, "defaultDueDays": { "type": [ "integer", "null" ], "minimum": 0 }, "defaultValidityDays": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 }, "vatRegime": { "allOf": [ { "$ref": "#/components/schemas/documentVatRegimeSchema" } ] }, "lifecycleWorkflowKey": { "allOf": [ { "$ref": "#/components/schemas/templateLifecycleWorkflowKeySchema" } ] }, "termsRichText": { "type": [ "string", "null" ], "maxLength": 100000 }, "termsUrl": { "allOf": [ { "$ref": "#/components/schemas/optionalUrl" } ] }, "termsPdfFileId": { "type": [ "string", "null" ], "format": "uuid" }, "termsPdfVersionId": { "type": [ "string", "null" ], "format": "uuid" }, "type": { "type": "string", "enum": [ "order" ] }, "orderDeliveryMethod": { "anyOf": [ { "$ref": "#/components/schemas/orderDeliveryMethodSchema" }, { "type": "null" } ] }, "orderInvoicingPolicy": { "anyOf": [ { "$ref": "#/components/schemas/orderInvoicingPolicySchema" }, { "type": "null" } ] }, "orderPaymentRequired": { "type": "boolean" } } }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "purchaseOrderTemplatePatchSchema": { "type": "object", "properties": { "language": { "allOf": [ { "$ref": "#/components/schemas/templateLanguageSchema" } ] }, "isDefault": { "type": "boolean" }, "name": { "type": "string", "minLength": 1, "maxLength": 240 }, "description": { "type": [ "string", "null" ], "maxLength": 20000 }, "title": { "type": "string", "minLength": 1, "maxLength": 240 }, "intro": { "type": [ "string", "null" ], "maxLength": 20000 }, "terms": { "type": [ "string", "null" ], "maxLength": 20000 }, "internalNotes": { "type": [ "string", "null" ], "maxLength": 20000 }, "publicMessage": { "type": [ "string", "null" ], "maxLength": 20000 }, "footerText": { "type": [ "string", "null" ], "maxLength": 20000 }, "currency": { "allOf": [ { "$ref": "#/components/schemas/currencyCode" } ] }, "vatInclusive": { "type": "boolean" }, "discountKind": { "allOf": [ { "$ref": "#/components/schemas/discountKindSchema" } ] }, "discountValue": { "type": "number", "minimum": 0 }, "defaultDueDays": { "type": [ "integer", "null" ], "minimum": 0 }, "defaultValidityDays": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 }, "vatRegime": { "allOf": [ { "$ref": "#/components/schemas/documentVatRegimeSchema" } ] }, "lifecycleWorkflowKey": { "allOf": [ { "$ref": "#/components/schemas/templateLifecycleWorkflowKeySchema" } ] }, "termsRichText": { "type": [ "string", "null" ], "maxLength": 100000 }, "termsUrl": { "allOf": [ { "$ref": "#/components/schemas/optionalUrl" } ] }, "termsPdfFileId": { "type": [ "string", "null" ], "format": "uuid" }, "termsPdfVersionId": { "type": [ "string", "null" ], "format": "uuid" }, "type": { "type": "string", "enum": [ "purchase_order" ] }, "shipToLocationId": { "type": [ "string", "null" ], "format": "uuid" } } }, "quoteDepositKindSchema": { "type": "string", "enum": [ "none", "percent", "fixed" ] }, "quoteTemplateLineInputSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "kind": { "allOf": [ { "$ref": "#/components/schemas/templateLineKindSchema" } ] }, "lineType": { "anyOf": [ { "$ref": "#/components/schemas/templateLineTypeSchema" }, { "type": "null" } ] }, "productId": { "type": [ "string", "null" ], "format": "uuid" }, "variantId": { "type": [ "string", "null" ], "format": "uuid" }, "title": { "type": "string", "minLength": 1, "maxLength": 500 }, "description": { "type": [ "string", "null" ], "maxLength": 20000 }, "quantity": { "type": "number", "minimum": 0 }, "unit": { "type": [ "string", "null" ], "maxLength": 40 }, "unitPrice": { "type": "number" }, "vatRate": { "type": "number", "minimum": 0, "maximum": 100 }, "discountKind": { "allOf": [ { "$ref": "#/components/schemas/discountKindSchema" } ] }, "discountValue": { "type": "number", "minimum": 0 }, "isOptional": { "type": "boolean" }, "pricingMode": { "allOf": [ { "$ref": "#/components/schemas/quoteTemplateLinePricingModeSchema" } ] }, "subscriptionInterval": { "anyOf": [ { "$ref": "#/components/schemas/quoteTemplateSubscriptionIntervalSchema" }, { "type": "null" } ] }, "subscriptionPeriods": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 }, "position": { "type": "number" } }, "required": [ "title" ], "additionalProperties": false }, "quoteTemplateLinePricingModeSchema": { "type": "string", "enum": [ "unit", "hourly", "subscription", "fixed" ] }, "quoteTemplatePatchSchema": { "type": "object", "properties": { "language": { "allOf": [ { "$ref": "#/components/schemas/templateLanguageSchema" } ] }, "isDefault": { "type": "boolean" }, "name": { "type": "string", "minLength": 1, "maxLength": 240 }, "description": { "type": [ "string", "null" ], "maxLength": 20000 }, "title": { "type": "string", "minLength": 1, "maxLength": 240 }, "intro": { "type": [ "string", "null" ], "maxLength": 20000 }, "terms": { "type": [ "string", "null" ], "maxLength": 20000 }, "internalNotes": { "type": [ "string", "null" ], "maxLength": 20000 }, "publicMessage": { "type": [ "string", "null" ], "maxLength": 20000 }, "footerText": { "type": [ "string", "null" ], "maxLength": 20000 }, "currency": { "allOf": [ { "$ref": "#/components/schemas/currencyCode" } ] }, "vatInclusive": { "type": "boolean" }, "discountKind": { "allOf": [ { "$ref": "#/components/schemas/discountKindSchema" } ] }, "discountValue": { "type": "number", "minimum": 0 }, "defaultDueDays": { "type": [ "integer", "null" ], "minimum": 0 }, "defaultValidityDays": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 }, "vatRegime": { "allOf": [ { "$ref": "#/components/schemas/documentVatRegimeSchema" } ] }, "lifecycleWorkflowKey": { "allOf": [ { "$ref": "#/components/schemas/templateLifecycleWorkflowKeySchema" } ] }, "termsRichText": { "type": [ "string", "null" ], "maxLength": 100000 }, "termsUrl": { "allOf": [ { "$ref": "#/components/schemas/optionalUrl" } ] }, "termsPdfFileId": { "type": [ "string", "null" ], "format": "uuid" }, "termsPdfVersionId": { "type": [ "string", "null" ], "format": "uuid" }, "type": { "type": "string", "enum": [ "quote" ] }, "depositKind": { "allOf": [ { "$ref": "#/components/schemas/quoteDepositKindSchema" } ] }, "depositValue": { "type": "number", "minimum": 0 }, "depositRequiredToAccept": { "type": "boolean" }, "createOrderOnAccept": { "type": "boolean" }, "lines": { "type": "array", "items": { "$ref": "#/components/schemas/quoteTemplateLineInputSchema" }, "maxItems": 500 } } }, "quoteTemplateSubscriptionIntervalSchema": { "type": "string", "enum": [ "weekly", "monthly", "quarterly", "yearly" ] }, "taskPrioritySchema": { "type": "string", "enum": [ "low", "medium", "high", "urgent" ] }, "taskStatusSchema": { "type": "string", "enum": [ "todo", "in_progress", "on_hold", "done" ] }, "taskTemplatePatchSchema": { "type": "object", "properties": { "language": { "allOf": [ { "$ref": "#/components/schemas/templateLanguageSchema" } ] }, "isDefault": { "type": "boolean" }, "name": { "type": "string", "minLength": 1, "maxLength": 240 }, "description": { "type": [ "string", "null" ], "maxLength": 20000 }, "title": { "type": "string", "minLength": 1, "maxLength": 240 }, "intro": { "type": [ "string", "null" ], "maxLength": 20000 }, "terms": { "type": [ "string", "null" ], "maxLength": 20000 }, "internalNotes": { "type": [ "string", "null" ], "maxLength": 20000 }, "publicMessage": { "type": [ "string", "null" ], "maxLength": 20000 }, "footerText": { "type": [ "string", "null" ], "maxLength": 20000 }, "currency": { "allOf": [ { "$ref": "#/components/schemas/currencyCode" } ] }, "vatInclusive": { "type": "boolean" }, "discountKind": { "allOf": [ { "$ref": "#/components/schemas/discountKindSchema" } ] }, "discountValue": { "type": "number", "minimum": 0 }, "defaultDueDays": { "type": [ "integer", "null" ], "minimum": 0 }, "defaultValidityDays": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 }, "vatRegime": { "allOf": [ { "$ref": "#/components/schemas/documentVatRegimeSchema" } ] }, "lifecycleWorkflowKey": { "allOf": [ { "$ref": "#/components/schemas/templateLifecycleWorkflowKeySchema" } ] }, "termsRichText": { "type": [ "string", "null" ], "maxLength": 100000 }, "termsUrl": { "allOf": [ { "$ref": "#/components/schemas/optionalUrl" } ] }, "termsPdfFileId": { "type": [ "string", "null" ], "format": "uuid" }, "termsPdfVersionId": { "type": [ "string", "null" ], "format": "uuid" }, "type": { "type": "string", "enum": [ "task" ] }, "task": { "$ref": "#/components/schemas/templateTaskSettingsInputSchema" }, "subtasks": { "type": "array", "items": { "$ref": "#/components/schemas/templateTaskSubtaskInputSchema" }, "maxItems": 100 }, "assigneeIds": { "type": "array", "items": { "type": "string", "format": "uuid" }, "maxItems": 50 }, "tagIds": { "type": "array", "items": { "type": "string", "format": "uuid" }, "maxItems": 50 } } }, "templateIdSchema": { "type": "string", "format": "uuid" }, "templateLanguageSchema": { "type": "string", "enum": [ "en", "nl" ] }, "templateLifecycleWorkflowKeySchema": { "type": [ "string", "null" ], "minLength": 1, "maxLength": 120, "pattern": "^[a-z0-9][a-z0-9-]*$" }, "templateLineKindSchema": { "type": "string", "enum": [ "line", "section_header" ] }, "templateLineTypeSchema": { "type": "string", "enum": [ "product", "labor", "custom" ] }, "templatePatchSchema": { "anyOf": [ { "$ref": "#/components/schemas/invoiceTemplatePatchSchema" }, { "$ref": "#/components/schemas/quoteTemplatePatchSchema" }, { "$ref": "#/components/schemas/taskTemplatePatchSchema" }, { "$ref": "#/components/schemas/orderTemplatePatchSchema" }, { "$ref": "#/components/schemas/purchaseOrderTemplatePatchSchema" } ] }, "templateSchema": { "type": "object", "properties": { "id": { "$ref": "#/components/schemas/templateIdSchema" }, "organizationId": { "type": "string", "format": "uuid" }, "type": { "$ref": "#/components/schemas/templateTypeSchema" }, "language": { "$ref": "#/components/schemas/templateLanguageSchema" }, "isDefault": { "type": "boolean" }, "name": { "type": "string" }, "description": { "type": [ "string", "null" ] }, "title": { "type": "string" }, "intro": { "type": [ "string", "null" ] }, "terms": { "type": [ "string", "null" ] }, "internalNotes": { "type": [ "string", "null" ] }, "publicMessage": { "type": [ "string", "null" ] }, "footerText": { "type": [ "string", "null" ] }, "currency": { "$ref": "#/components/schemas/currencyCode" }, "vatInclusive": { "type": "boolean" }, "discountKind": { "$ref": "#/components/schemas/discountKindSchema" }, "discountValue": { "type": "number" }, "defaultDueDays": { "type": [ "integer", "null" ], "minimum": 0 }, "defaultValidityDays": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 }, "vatRegime": { "$ref": "#/components/schemas/documentVatRegimeSchema" }, "termsRichText": { "type": [ "string", "null" ], "maxLength": 100000 }, "termsUrl": { "type": [ "string", "null" ] }, "termsPdfFileId": { "type": [ "string", "null" ], "format": "uuid" }, "termsPdfVersionId": { "type": [ "string", "null" ], "format": "uuid" }, "depositKind": { "$ref": "#/components/schemas/quoteDepositKindSchema" }, "depositValue": { "type": "number", "minimum": 0 }, "depositRequiredToAccept": { "type": "boolean" }, "lifecycleWorkflowKey": { "type": [ "string", "null" ] }, "orderDeliveryMethod": { "anyOf": [ { "$ref": "#/components/schemas/orderDeliveryMethodSchema" }, { "type": "null" } ] }, "orderInvoicingPolicy": { "anyOf": [ { "$ref": "#/components/schemas/orderInvoicingPolicySchema" }, { "type": "null" } ] }, "orderPaymentRequired": { "type": "boolean" }, "shipToLocationId": { "type": [ "string", "null" ], "format": "uuid" }, "createOrderOnAccept": { "type": "boolean" }, "archivedAt": { "type": [ "string", "null" ], "format": "date-time" }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "updatedBy": { "type": [ "string", "null" ], "format": "uuid" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "organizationId", "type", "language", "isDefault", "name", "description", "title", "intro", "terms", "internalNotes", "publicMessage", "footerText", "currency", "vatInclusive", "discountKind", "discountValue", "defaultDueDays", "defaultValidityDays", "vatRegime", "termsRichText", "termsUrl", "termsPdfFileId", "termsPdfVersionId", "depositKind", "depositValue", "depositRequiredToAccept", "lifecycleWorkflowKey", "orderDeliveryMethod", "orderInvoicingPolicy", "orderPaymentRequired", "shipToLocationId", "createOrderOnAccept", "archivedAt", "createdBy", "updatedBy", "createdAt", "updatedAt" ] }, "templateTaskSettingsInputSchema": { "type": "object", "properties": { "title": { "type": "string", "minLength": 1, "maxLength": 240 }, "description": { "type": [ "string", "null" ], "maxLength": 20000 }, "status": { "allOf": [ { "$ref": "#/components/schemas/taskStatusSchema" } ] }, "priority": { "allOf": [ { "$ref": "#/components/schemas/taskPrioritySchema" } ] }, "customerId": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 }, "customerAssetId": { "type": [ "integer", "null" ], "exclusiveMinimum": 0 } }, "required": [ "title" ] }, "templateTaskSubtaskInputSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "title": { "type": "string", "minLength": 1, "maxLength": 240 }, "status": { "allOf": [ { "$ref": "#/components/schemas/taskStatusSchema" } ] }, "priority": { "allOf": [ { "$ref": "#/components/schemas/taskPrioritySchema" } ] }, "position": { "type": "number" } }, "required": [ "title" ] }, "templateTypeSchema": { "type": "string", "enum": [ "invoice", "quote", "task", "order", "purchase_order" ] } } ``` # Delete a template. (/openapi/deleteTemplate) Delete a reusable quote or invoice template for the specified organization. ## DELETE /v1/organizations/{organizationId}/templates/{templateId} ```json { "operationId": "deleteTemplate", "summary": "Delete a template.", "description": "Delete a reusable quote or invoice template for the specified organization.", "tags": [ "Templates" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "templateId", "required": true, "schema": { "$ref": "#/components/schemas/templateIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns a success payload when the template is deleted.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/publicApiSuccessResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "organizationIdSchema": { "type": "string", "format": "uuid" }, "publicApiSuccessResponseSchema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [ true ] } }, "required": [ "success" ] }, "templateIdSchema": { "type": "string", "format": "uuid" } } ``` # Download a team leave feed by token. (/openapi/downloadPublicTimeOffFeed) Returns approved team leave as an iCalendar feed through a private token link. Access is revalidated on every fetch and sick-leave reasons are hidden. ## GET /v1/public/time-off/{token} ```json { "operationId": "downloadPublicTimeOffFeed", "summary": "Download a team leave feed by token.", "description": "Returns approved team leave as an iCalendar feed through a private token link. Access is revalidated on every fetch and sick-leave reasons are hidden.", "tags": [ "Time off" ], "parameters": [ { "in": "path", "name": "token", "required": true, "schema": { "type": "string", "minLength": 1 }, "example": "example" } ], "responses": { "200": { "description": "Returns team leave as an iCalendar document.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/publicTimeOffFeedResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "publicTimeOffFeedResponseSchema": { "type": "string" } } ``` # Get the current authenticated user. (/openapi/getCurrentUser) Return the currently authenticated user for the active request. Accepts a bearer token for API clients while continuing to support the current browser session. ## GET /v1/users/me ```json { "operationId": "getCurrentUser", "summary": "Get the current authenticated user.", "description": "Return the currently authenticated user for the active request. Accepts a bearer token for API clients while continuing to support the current browser session.", "tags": [ "Users" ], "parameters": [], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns the current authenticated user.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/authUserSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "authUserIdSchema": { "type": "string", "format": "uuid" }, "authUserSchema": { "type": "object", "properties": { "createdAt": { "type": "string", "format": "date-time" }, "email": { "type": [ "string", "null" ], "format": "email" }, "emailVerified": { "type": "boolean" }, "id": { "$ref": "#/components/schemas/authUserIdSchema" }, "lastSignInAt": { "type": [ "string", "null" ], "format": "date-time" } }, "required": [ "createdAt", "email", "emailVerified", "id", "lastSignInAt" ] } } ``` # Receive an incoming organization webhook. (/openapi/receivePublicWebhook) Accepts a public token URL, verifies the HMAC-SHA256 signature (`x-centrakit-signature` over `{timestamp}.{body}` within five minutes) or the shared secret header, and starts matching workflows. Bodies over 256 KB are rejected. ## POST /v1/public/webhooks/{token} ```json { "operationId": "receivePublicWebhook", "summary": "Receive an incoming organization webhook.", "description": "Accepts a public token URL, verifies the HMAC-SHA256 signature (`x-centrakit-signature` over `{timestamp}.{body}` within five minutes) or the shared secret header, and starts matching workflows. Bodies over 256 KB are rejected.", "tags": [ "Webhooks" ], "parameters": [ { "in": "path", "name": "token", "required": true, "schema": { "type": "string", "minLength": 16, "maxLength": 80 }, "example": "example" } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/publicWebhookBodySchema" } } }, "description": "JSON payload forwarded to matching workflow triggers." }, "responses": { "202": { "description": "Returns the published event id and optional workflow run id.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/publicWebhookResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "jsonValueSchema": { "anyOf": [ { "type": "string" }, { "type": "number" }, { "type": "boolean" }, { "type": "null" }, { "type": "array", "items": { "$ref": "#/components/schemas/jsonValueSchema" } }, { "type": "object", "additionalProperties": { "$ref": "#/components/schemas/jsonValueSchema" }, "propertyNames": { "type": "string" } } ] }, "publicWebhookBodySchema": { "type": "object", "additionalProperties": { "$ref": "#/components/schemas/jsonValueSchema" }, "propertyNames": { "type": "string" }, "examples": [ { "orderId": "ord_123" } ] }, "publicWebhookResponseSchema": { "type": "object", "properties": { "eventId": { "type": "string", "format": "uuid" }, "ok": { "type": "boolean", "enum": [ true ] }, "runId": { "type": [ "string", "null" ], "format": "uuid" } }, "required": [ "eventId", "ok", "runId" ], "examples": [ { "eventId": "5b657e4b-6f4b-4e5f-8c03-c0bc0d3f5f4a", "ok": true, "runId": "6c768f5c-7f5c-4f60-9d14-d1cd1e40665b" } ] } } ``` # List webhook deliveries. (/openapi/listWebhookDeliveries) Return paginated delivery attempts, optionally filtered by destination. ## GET /v1/organizations/{organizationId}/webhooks/deliveries ```json { "operationId": "listWebhookDeliveries", "summary": "List webhook deliveries.", "description": "Return paginated delivery attempts, optionally filtered by destination.", "tags": [ "Webhooks" ], "parameters": [ { "in": "query", "name": "destinationId", "required": false, "schema": { "type": "string" }, "example": "123" }, { "in": "query", "name": "limit", "required": false, "schema": { "type": "string" }, "example": "example" }, { "in": "query", "name": "offset", "required": false, "schema": { "type": "string" }, "example": "example" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns paginated outgoing webhook deliveries.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/webhookDeliveriesListResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "dateTimeSchema": { "type": "string" }, "jsonObjectSchema": { "type": "object", "additionalProperties": { "$ref": "#/components/schemas/jsonValueSchema" }, "propertyNames": { "type": "string" } }, "jsonValueSchema": { "anyOf": [ { "type": "string" }, { "type": "number" }, { "type": "boolean" }, { "type": "null" }, { "type": "array", "items": { "$ref": "#/components/schemas/jsonValueSchema" } }, { "type": "object", "additionalProperties": { "$ref": "#/components/schemas/jsonValueSchema" }, "propertyNames": { "type": "string" } } ] }, "listPaginationSchema": { "type": "object", "properties": { "offset": { "type": "integer", "minimum": 0 }, "limit": { "type": "integer", "minimum": 1, "maximum": 100 }, "total": { "type": "integer", "minimum": 0 }, "hasMore": { "type": "boolean" }, "totalIsEstimate": { "type": "boolean" } }, "required": [ "offset", "limit", "total", "hasMore" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "webhookDeliveriesListResponseSchema": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/webhookDeliverySchema" } }, "pagination": { "$ref": "#/components/schemas/listPaginationSchema" } }, "required": [ "data", "pagination" ] }, "webhookDeliverySchema": { "type": "object", "properties": { "attempt": { "type": "integer", "minimum": 0 }, "availableAt": { "$ref": "#/components/schemas/dateTimeSchema" }, "createdAt": { "$ref": "#/components/schemas/dateTimeSchema" }, "destinationId": { "type": "string", "format": "uuid" }, "durationMs": { "type": [ "integer", "null" ] }, "eventId": { "type": [ "string", "null" ], "format": "uuid" }, "eventKind": { "type": "string", "minLength": 1, "maxLength": 160 }, "id": { "type": "string", "format": "uuid" }, "lastError": { "type": [ "string", "null" ] }, "leasedUntil": { "anyOf": [ { "$ref": "#/components/schemas/dateTimeSchema" }, { "type": "null" } ] }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "payload": { "$ref": "#/components/schemas/jsonObjectSchema" }, "processedAt": { "anyOf": [ { "$ref": "#/components/schemas/dateTimeSchema" }, { "type": "null" } ] }, "responseBody": { "type": [ "string", "null" ] }, "responseStatus": { "type": [ "integer", "null" ] }, "status": { "$ref": "#/components/schemas/webhookDeliveryStatusSchema" }, "updatedAt": { "$ref": "#/components/schemas/dateTimeSchema" }, "workflowRunId": { "type": [ "string", "null" ], "format": "uuid" } }, "required": [ "attempt", "availableAt", "createdAt", "destinationId", "durationMs", "eventId", "eventKind", "id", "lastError", "leasedUntil", "organizationId", "payload", "processedAt", "responseBody", "responseStatus", "status", "updatedAt", "workflowRunId" ] }, "webhookDeliveryStatusSchema": { "type": "string", "enum": [ "pending", "processing", "completed", "failed", "dead_lettered", "canceled" ] } } ``` # List webhook destinations. (/openapi/listWebhookDestinations) Return outgoing webhook destinations without their signing secrets. ## GET /v1/organizations/{organizationId}/webhooks/destinations ```json { "operationId": "listWebhookDestinations", "summary": "List webhook destinations.", "description": "Return outgoing webhook destinations without their signing secrets.", "tags": [ "Webhooks" ], "parameters": [ { "in": "query", "name": "limit", "required": false, "schema": { "type": "string" }, "example": "example" }, { "in": "query", "name": "offset", "required": false, "schema": { "type": "string" }, "example": "example" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns paginated outgoing webhook destinations.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/webhookDestinationsListResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "dateTimeSchema": { "type": "string" }, "listPaginationSchema": { "type": "object", "properties": { "offset": { "type": "integer", "minimum": 0 }, "limit": { "type": "integer", "minimum": 1, "maximum": 100 }, "total": { "type": "integer", "minimum": 0 }, "hasMore": { "type": "boolean" }, "totalIsEstimate": { "type": "boolean" } }, "required": [ "offset", "limit", "total", "hasMore" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "webhookDestinationSchema": { "type": "object", "properties": { "createdAt": { "$ref": "#/components/schemas/dateTimeSchema" }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "enabled": { "type": "boolean" }, "eventKinds": { "type": "array", "items": { "type": "string", "minLength": 1, "maxLength": 160 } }, "id": { "type": "string", "format": "uuid" }, "name": { "type": "string", "minLength": 1, "maxLength": 160 }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "updatedAt": { "$ref": "#/components/schemas/dateTimeSchema" }, "url": { "type": "string", "format": "uri" } }, "required": [ "createdAt", "createdBy", "enabled", "eventKinds", "id", "name", "organizationId", "updatedAt", "url" ] }, "webhookDestinationsListResponseSchema": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/webhookDestinationSchema" } }, "pagination": { "$ref": "#/components/schemas/listPaginationSchema" } }, "required": [ "data", "pagination" ] } } ``` # Create a webhook destination. (/openapi/createWebhookDestination) Create an HTTPS destination for outgoing webhook events. The signing secret is returned only by this operation or an explicit rotation. ## POST /v1/organizations/{organizationId}/webhooks/destinations ```json { "operationId": "createWebhookDestination", "summary": "Create a webhook destination.", "description": "Create an HTTPS destination for outgoing webhook events. The signing secret is returned only by this operation or an explicit rotation.", "tags": [ "Webhooks" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/webhookDestinationCreateRequestSchema" } } }, "description": "Outgoing webhook destination settings and subscribed event kinds.", "required": true }, "responses": { "201": { "description": "Returns the created destination and its one-time signing secret.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/webhookDestinationSecretResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "dateTimeSchema": { "type": "string" }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "webhookDestinationCreateRequestSchema": { "type": "object", "properties": { "eventKinds": { "type": "array", "items": { "type": "string", "minLength": 1, "maxLength": 160 } }, "name": { "type": "string", "minLength": 1, "maxLength": 160 }, "url": { "type": "string", "format": "uri" } }, "required": [ "eventKinds", "name", "url" ] }, "webhookDestinationSecretResponseSchema": { "type": "object", "properties": { "createdAt": { "$ref": "#/components/schemas/dateTimeSchema" }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "enabled": { "type": "boolean" }, "eventKinds": { "type": "array", "items": { "type": "string", "minLength": 1, "maxLength": 160 } }, "id": { "type": "string", "format": "uuid" }, "name": { "type": "string", "minLength": 1, "maxLength": 160 }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "updatedAt": { "$ref": "#/components/schemas/dateTimeSchema" }, "url": { "type": "string", "format": "uri" }, "secret": { "type": "string", "minLength": 16, "maxLength": 200 } }, "required": [ "createdAt", "createdBy", "enabled", "eventKinds", "id", "name", "organizationId", "updatedAt", "url", "secret" ] } } ``` # List webhook endpoints. (/openapi/listWebhookEndpoints) Return incoming webhook endpoints without their signing secrets. ## GET /v1/organizations/{organizationId}/webhooks/endpoints ```json { "operationId": "listWebhookEndpoints", "summary": "List webhook endpoints.", "description": "Return incoming webhook endpoints without their signing secrets.", "tags": [ "Webhooks" ], "parameters": [ { "in": "query", "name": "limit", "required": false, "schema": { "type": "string" }, "example": "example" }, { "in": "query", "name": "offset", "required": false, "schema": { "type": "string" }, "example": "example" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns paginated incoming webhook endpoints.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/webhookEndpointsListResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "dateTimeSchema": { "type": "string" }, "listPaginationSchema": { "type": "object", "properties": { "offset": { "type": "integer", "minimum": 0 }, "limit": { "type": "integer", "minimum": 1, "maximum": 100 }, "total": { "type": "integer", "minimum": 0 }, "hasMore": { "type": "boolean" }, "totalIsEstimate": { "type": "boolean" } }, "required": [ "offset", "limit", "total", "hasMore" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "webhookEndpointHttpSchema": { "type": "object", "properties": { "createdAt": { "$ref": "#/components/schemas/dateTimeSchema" }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "enabled": { "type": "boolean" }, "id": { "type": "string", "format": "uuid" }, "lastReceivedAt": { "anyOf": [ { "$ref": "#/components/schemas/dateTimeSchema" }, { "type": "null" } ] }, "lastStatus": { "anyOf": [ { "$ref": "#/components/schemas/webhookEndpointStatusSchema" }, { "type": "null" } ] }, "name": { "type": "string", "minLength": 1, "maxLength": 160 }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "receiveCount": { "type": "integer", "minimum": 0 }, "updatedAt": { "$ref": "#/components/schemas/dateTimeSchema" }, "workflowDefinitionId": { "type": [ "string", "null" ], "format": "uuid" } }, "required": [ "createdAt", "createdBy", "enabled", "id", "lastReceivedAt", "lastStatus", "name", "organizationId", "receiveCount", "updatedAt", "workflowDefinitionId" ] }, "webhookEndpointStatusSchema": { "type": "string", "enum": [ "accepted", "unauthorized", "disabled", "failed" ] }, "webhookEndpointsListResponseSchema": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/webhookEndpointHttpSchema" } }, "pagination": { "$ref": "#/components/schemas/listPaginationSchema" } }, "required": [ "data", "pagination" ] } } ``` # Create a webhook endpoint. (/openapi/createWebhookEndpoint) Create an incoming webhook endpoint. The signing secret is returned only by this operation or an explicit rotation. ## POST /v1/organizations/{organizationId}/webhooks/endpoints ```json { "operationId": "createWebhookEndpoint", "summary": "Create a webhook endpoint.", "description": "Create an incoming webhook endpoint. The signing secret is returned only by this operation or an explicit rotation.", "tags": [ "Webhooks" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/webhookEndpointCreateRequestSchema" } } }, "description": "Incoming webhook endpoint settings.", "required": true }, "responses": { "201": { "description": "Returns the created endpoint and its one-time signing secret.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/webhookEndpointCreateResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "dateTimeSchema": { "type": "string" }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "webhookEndpointCreateRequestSchema": { "type": "object", "properties": { "name": { "type": "string", "minLength": 1, "maxLength": 160 }, "workflowDefinitionId": { "type": [ "string", "null" ], "format": "uuid" } }, "required": [ "name" ] }, "webhookEndpointCreateResponseSchema": { "type": "object", "properties": { "createdAt": { "$ref": "#/components/schemas/dateTimeSchema" }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "enabled": { "type": "boolean" }, "id": { "type": "string", "format": "uuid" }, "lastReceivedAt": { "anyOf": [ { "$ref": "#/components/schemas/dateTimeSchema" }, { "type": "null" } ] }, "lastStatus": { "anyOf": [ { "$ref": "#/components/schemas/webhookEndpointStatusSchema" }, { "type": "null" } ] }, "name": { "type": "string", "minLength": 1, "maxLength": 160 }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "receiveCount": { "type": "integer", "minimum": 0 }, "updatedAt": { "$ref": "#/components/schemas/dateTimeSchema" }, "workflowDefinitionId": { "type": [ "string", "null" ], "format": "uuid" }, "secret": { "type": [ "string", "null" ], "minLength": 16, "maxLength": 200 }, "token": { "$ref": "#/components/schemas/webhookEndpointTokenSchema" } }, "required": [ "createdAt", "createdBy", "enabled", "id", "lastReceivedAt", "lastStatus", "name", "organizationId", "receiveCount", "updatedAt", "workflowDefinitionId", "secret", "token" ] }, "webhookEndpointStatusSchema": { "type": "string", "enum": [ "accepted", "unauthorized", "disabled", "failed" ] }, "webhookEndpointTokenSchema": { "type": "object" } } ``` # Get a webhook delivery. (/openapi/getWebhookDelivery) Return one outgoing webhook delivery attempt and its observed response. ## GET /v1/organizations/{organizationId}/webhooks/deliveries/{deliveryId} ```json { "operationId": "getWebhookDelivery", "summary": "Get a webhook delivery.", "description": "Return one outgoing webhook delivery attempt and its observed response.", "tags": [ "Webhooks" ], "parameters": [ { "in": "path", "name": "deliveryId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns the requested webhook delivery.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/webhookDeliverySchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "dateTimeSchema": { "type": "string" }, "jsonObjectSchema": { "type": "object", "additionalProperties": { "$ref": "#/components/schemas/jsonValueSchema" }, "propertyNames": { "type": "string" } }, "jsonValueSchema": { "anyOf": [ { "type": "string" }, { "type": "number" }, { "type": "boolean" }, { "type": "null" }, { "type": "array", "items": { "$ref": "#/components/schemas/jsonValueSchema" } }, { "type": "object", "additionalProperties": { "$ref": "#/components/schemas/jsonValueSchema" }, "propertyNames": { "type": "string" } } ] }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "webhookDeliverySchema": { "type": "object", "properties": { "attempt": { "type": "integer", "minimum": 0 }, "availableAt": { "$ref": "#/components/schemas/dateTimeSchema" }, "createdAt": { "$ref": "#/components/schemas/dateTimeSchema" }, "destinationId": { "type": "string", "format": "uuid" }, "durationMs": { "type": [ "integer", "null" ] }, "eventId": { "type": [ "string", "null" ], "format": "uuid" }, "eventKind": { "type": "string", "minLength": 1, "maxLength": 160 }, "id": { "type": "string", "format": "uuid" }, "lastError": { "type": [ "string", "null" ] }, "leasedUntil": { "anyOf": [ { "$ref": "#/components/schemas/dateTimeSchema" }, { "type": "null" } ] }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "payload": { "$ref": "#/components/schemas/jsonObjectSchema" }, "processedAt": { "anyOf": [ { "$ref": "#/components/schemas/dateTimeSchema" }, { "type": "null" } ] }, "responseBody": { "type": [ "string", "null" ] }, "responseStatus": { "type": [ "integer", "null" ] }, "status": { "$ref": "#/components/schemas/webhookDeliveryStatusSchema" }, "updatedAt": { "$ref": "#/components/schemas/dateTimeSchema" }, "workflowRunId": { "type": [ "string", "null" ], "format": "uuid" } }, "required": [ "attempt", "availableAt", "createdAt", "destinationId", "durationMs", "eventId", "eventKind", "id", "lastError", "leasedUntil", "organizationId", "payload", "processedAt", "responseBody", "responseStatus", "status", "updatedAt", "workflowRunId" ] }, "webhookDeliveryStatusSchema": { "type": "string", "enum": [ "pending", "processing", "completed", "failed", "dead_lettered", "canceled" ] } } ``` # Get a webhook destination. (/openapi/getWebhookDestination) Return an outgoing webhook destination without its signing secret. ## GET /v1/organizations/{organizationId}/webhooks/destinations/{destinationId} ```json { "operationId": "getWebhookDestination", "summary": "Get a webhook destination.", "description": "Return an outgoing webhook destination without its signing secret.", "tags": [ "Webhooks" ], "parameters": [ { "in": "path", "name": "destinationId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns the requested webhook destination.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/webhookDestinationSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "dateTimeSchema": { "type": "string" }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "webhookDestinationSchema": { "type": "object", "properties": { "createdAt": { "$ref": "#/components/schemas/dateTimeSchema" }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "enabled": { "type": "boolean" }, "eventKinds": { "type": "array", "items": { "type": "string", "minLength": 1, "maxLength": 160 } }, "id": { "type": "string", "format": "uuid" }, "name": { "type": "string", "minLength": 1, "maxLength": 160 }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "updatedAt": { "$ref": "#/components/schemas/dateTimeSchema" }, "url": { "type": "string", "format": "uri" } }, "required": [ "createdAt", "createdBy", "enabled", "eventKinds", "id", "name", "organizationId", "updatedAt", "url" ] } } ``` # Update a webhook destination. (/openapi/updateWebhookDestination) Update the name, URL, event subscriptions, or enabled state of an outgoing webhook destination. ## PATCH /v1/organizations/{organizationId}/webhooks/destinations/{destinationId} ```json { "operationId": "updateWebhookDestination", "summary": "Update a webhook destination.", "description": "Update the name, URL, event subscriptions, or enabled state of an outgoing webhook destination.", "tags": [ "Webhooks" ], "parameters": [ { "in": "path", "name": "destinationId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/updateWebhookDestinationInputSchema" } } }, "description": "Webhook destination fields to update.", "required": true }, "responses": { "200": { "description": "Returns the updated webhook destination without its signing secret.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/webhookDestinationSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "dateTimeSchema": { "type": "string" }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "updateWebhookDestinationInputSchema": { "type": "object", "properties": { "enabled": { "type": "boolean" }, "eventKinds": { "type": "array", "items": { "type": "string", "minLength": 1, "maxLength": 160 } }, "name": { "type": "string", "minLength": 1, "maxLength": 160 }, "url": { "type": "string", "format": "uri" } } }, "webhookDestinationSchema": { "type": "object", "properties": { "createdAt": { "$ref": "#/components/schemas/dateTimeSchema" }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "enabled": { "type": "boolean" }, "eventKinds": { "type": "array", "items": { "type": "string", "minLength": 1, "maxLength": 160 } }, "id": { "type": "string", "format": "uuid" }, "name": { "type": "string", "minLength": 1, "maxLength": 160 }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "updatedAt": { "$ref": "#/components/schemas/dateTimeSchema" }, "url": { "type": "string", "format": "uri" } }, "required": [ "createdAt", "createdBy", "enabled", "eventKinds", "id", "name", "organizationId", "updatedAt", "url" ] } } ``` # Delete a webhook destination. (/openapi/deleteWebhookDestination) Permanently delete an outgoing webhook destination. ## DELETE /v1/organizations/{organizationId}/webhooks/destinations/{destinationId} ```json { "operationId": "deleteWebhookDestination", "summary": "Delete a webhook destination.", "description": "Permanently delete an outgoing webhook destination.", "tags": [ "Webhooks" ], "parameters": [ { "in": "path", "name": "destinationId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Confirms the webhook destination was deleted.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/publicApiSuccessResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "organizationIdSchema": { "type": "string", "format": "uuid" }, "publicApiSuccessResponseSchema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [ true ] } }, "required": [ "success" ] } } ``` # Get a webhook endpoint. (/openapi/getWebhookEndpoint) Return an incoming webhook endpoint without its signing secret. ## GET /v1/organizations/{organizationId}/webhooks/endpoints/{endpointId} ```json { "operationId": "getWebhookEndpoint", "summary": "Get a webhook endpoint.", "description": "Return an incoming webhook endpoint without its signing secret.", "tags": [ "Webhooks" ], "parameters": [ { "in": "path", "name": "endpointId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns the requested webhook endpoint.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/webhookEndpointHttpSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "dateTimeSchema": { "type": "string" }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "webhookEndpointHttpSchema": { "type": "object", "properties": { "createdAt": { "$ref": "#/components/schemas/dateTimeSchema" }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "enabled": { "type": "boolean" }, "id": { "type": "string", "format": "uuid" }, "lastReceivedAt": { "anyOf": [ { "$ref": "#/components/schemas/dateTimeSchema" }, { "type": "null" } ] }, "lastStatus": { "anyOf": [ { "$ref": "#/components/schemas/webhookEndpointStatusSchema" }, { "type": "null" } ] }, "name": { "type": "string", "minLength": 1, "maxLength": 160 }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "receiveCount": { "type": "integer", "minimum": 0 }, "updatedAt": { "$ref": "#/components/schemas/dateTimeSchema" }, "workflowDefinitionId": { "type": [ "string", "null" ], "format": "uuid" } }, "required": [ "createdAt", "createdBy", "enabled", "id", "lastReceivedAt", "lastStatus", "name", "organizationId", "receiveCount", "updatedAt", "workflowDefinitionId" ] }, "webhookEndpointStatusSchema": { "type": "string", "enum": [ "accepted", "unauthorized", "disabled", "failed" ] } } ``` # Update a webhook endpoint. (/openapi/updateWebhookEndpoint) Update the name, workflow binding, or enabled state of an incoming webhook endpoint. ## PATCH /v1/organizations/{organizationId}/webhooks/endpoints/{endpointId} ```json { "operationId": "updateWebhookEndpoint", "summary": "Update a webhook endpoint.", "description": "Update the name, workflow binding, or enabled state of an incoming webhook endpoint.", "tags": [ "Webhooks" ], "parameters": [ { "in": "path", "name": "endpointId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/updateWebhookEndpointInputSchema" } } }, "description": "Webhook endpoint fields to update.", "required": true }, "responses": { "200": { "description": "Returns the updated webhook endpoint without its signing secret.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/webhookEndpointHttpSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "dateTimeSchema": { "type": "string" }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "updateWebhookEndpointInputSchema": { "type": "object", "properties": { "enabled": { "type": "boolean" }, "name": { "type": "string", "minLength": 1, "maxLength": 160 }, "workflowDefinitionId": { "type": [ "string", "null" ], "format": "uuid" } } }, "webhookEndpointHttpSchema": { "type": "object", "properties": { "createdAt": { "$ref": "#/components/schemas/dateTimeSchema" }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "enabled": { "type": "boolean" }, "id": { "type": "string", "format": "uuid" }, "lastReceivedAt": { "anyOf": [ { "$ref": "#/components/schemas/dateTimeSchema" }, { "type": "null" } ] }, "lastStatus": { "anyOf": [ { "$ref": "#/components/schemas/webhookEndpointStatusSchema" }, { "type": "null" } ] }, "name": { "type": "string", "minLength": 1, "maxLength": 160 }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "receiveCount": { "type": "integer", "minimum": 0 }, "updatedAt": { "$ref": "#/components/schemas/dateTimeSchema" }, "workflowDefinitionId": { "type": [ "string", "null" ], "format": "uuid" } }, "required": [ "createdAt", "createdBy", "enabled", "id", "lastReceivedAt", "lastStatus", "name", "organizationId", "receiveCount", "updatedAt", "workflowDefinitionId" ] }, "webhookEndpointStatusSchema": { "type": "string", "enum": [ "accepted", "unauthorized", "disabled", "failed" ] } } ``` # Delete a webhook endpoint. (/openapi/deleteWebhookEndpoint) Permanently delete an incoming webhook endpoint. ## DELETE /v1/organizations/{organizationId}/webhooks/endpoints/{endpointId} ```json { "operationId": "deleteWebhookEndpoint", "summary": "Delete a webhook endpoint.", "description": "Permanently delete an incoming webhook endpoint.", "tags": [ "Webhooks" ], "parameters": [ { "in": "path", "name": "endpointId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Confirms the webhook endpoint was deleted.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/publicApiSuccessResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "organizationIdSchema": { "type": "string", "format": "uuid" }, "publicApiSuccessResponseSchema": { "type": "object", "properties": { "success": { "type": "boolean", "enum": [ true ] } }, "required": [ "success" ] } } ``` # Rotate a webhook destination signing secret. (/openapi/rotateWebhookDestinationSecret) Replace the signing secret used for outgoing webhook deliveries. The new secret is returned only once. ## POST /v1/organizations/{organizationId}/webhooks/destinations/{destinationId}/rotate-secret ```json { "operationId": "rotateWebhookDestinationSecret", "summary": "Rotate a webhook destination signing secret.", "description": "Replace the signing secret used for outgoing webhook deliveries. The new secret is returned only once.", "tags": [ "Webhooks" ], "parameters": [ { "in": "path", "name": "destinationId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns the destination and its new one-time signing secret.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/webhookDestinationSecretResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "dateTimeSchema": { "type": "string" }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "webhookDestinationSecretResponseSchema": { "type": "object", "properties": { "createdAt": { "$ref": "#/components/schemas/dateTimeSchema" }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "enabled": { "type": "boolean" }, "eventKinds": { "type": "array", "items": { "type": "string", "minLength": 1, "maxLength": 160 } }, "id": { "type": "string", "format": "uuid" }, "name": { "type": "string", "minLength": 1, "maxLength": 160 }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "updatedAt": { "$ref": "#/components/schemas/dateTimeSchema" }, "url": { "type": "string", "format": "uri" }, "secret": { "type": "string", "minLength": 16, "maxLength": 200 } }, "required": [ "createdAt", "createdBy", "enabled", "eventKinds", "id", "name", "organizationId", "updatedAt", "url", "secret" ] } } ``` # Rotate a webhook endpoint signing secret. (/openapi/rotateWebhookEndpointSecret) Replace the signing secret for an incoming webhook endpoint. The new secret is returned only once. ## POST /v1/organizations/{organizationId}/webhooks/endpoints/{endpointId}/rotate-secret ```json { "operationId": "rotateWebhookEndpointSecret", "summary": "Rotate a webhook endpoint signing secret.", "description": "Replace the signing secret for an incoming webhook endpoint. The new secret is returned only once.", "tags": [ "Webhooks" ], "parameters": [ { "in": "path", "name": "endpointId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns the endpoint and its new one-time signing secret.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/webhookEndpointSecretResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "dateTimeSchema": { "type": "string" }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "webhookEndpointSecretResponseSchema": { "type": "object", "properties": { "createdAt": { "$ref": "#/components/schemas/dateTimeSchema" }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "enabled": { "type": "boolean" }, "id": { "type": "string", "format": "uuid" }, "lastReceivedAt": { "anyOf": [ { "$ref": "#/components/schemas/dateTimeSchema" }, { "type": "null" } ] }, "lastStatus": { "anyOf": [ { "$ref": "#/components/schemas/webhookEndpointStatusSchema" }, { "type": "null" } ] }, "name": { "type": "string", "minLength": 1, "maxLength": 160 }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "receiveCount": { "type": "integer", "minimum": 0 }, "updatedAt": { "$ref": "#/components/schemas/dateTimeSchema" }, "workflowDefinitionId": { "type": [ "string", "null" ], "format": "uuid" }, "secret": { "type": [ "string", "null" ], "minLength": 16, "maxLength": 200 } }, "required": [ "createdAt", "createdBy", "enabled", "id", "lastReceivedAt", "lastStatus", "name", "organizationId", "receiveCount", "updatedAt", "workflowDefinitionId", "secret" ] }, "webhookEndpointStatusSchema": { "type": "string", "enum": [ "accepted", "unauthorized", "disabled", "failed" ] } } ``` # Rotate a webhook endpoint token. (/openapi/rotateWebhookEndpointToken) Replace the public receiver token for an incoming webhook endpoint, invalidating the previous URL. ## POST /v1/organizations/{organizationId}/webhooks/endpoints/{endpointId}/rotate-token ```json { "operationId": "rotateWebhookEndpointToken", "summary": "Rotate a webhook endpoint token.", "description": "Replace the public receiver token for an incoming webhook endpoint, invalidating the previous URL.", "tags": [ "Webhooks" ], "parameters": [ { "in": "path", "name": "endpointId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns the endpoint with its replacement receiver token.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/webhookEndpointTokenResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "dateTimeSchema": { "type": "string" }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "webhookEndpointStatusSchema": { "type": "string", "enum": [ "accepted", "unauthorized", "disabled", "failed" ] }, "webhookEndpointTokenResponseSchema": { "type": "object", "properties": { "createdAt": { "$ref": "#/components/schemas/dateTimeSchema" }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "enabled": { "type": "boolean" }, "id": { "type": "string", "format": "uuid" }, "lastReceivedAt": { "anyOf": [ { "$ref": "#/components/schemas/dateTimeSchema" }, { "type": "null" } ] }, "lastStatus": { "anyOf": [ { "$ref": "#/components/schemas/webhookEndpointStatusSchema" }, { "type": "null" } ] }, "name": { "type": "string", "minLength": 1, "maxLength": 160 }, "organizationId": { "$ref": "#/components/schemas/organizationIdSchema" }, "receiveCount": { "type": "integer", "minimum": 0 }, "updatedAt": { "$ref": "#/components/schemas/dateTimeSchema" }, "workflowDefinitionId": { "type": [ "string", "null" ], "format": "uuid" }, "token": { "$ref": "#/components/schemas/webhookEndpointTokenSchema" } }, "required": [ "createdAt", "createdBy", "enabled", "id", "lastReceivedAt", "lastStatus", "name", "organizationId", "receiveCount", "updatedAt", "workflowDefinitionId", "token" ] }, "webhookEndpointTokenSchema": { "type": "object" } } ``` # List workflow definitions. (/openapi/listWorkflowDefinitions) Return paginated summaries of published workflow definitions available to the authenticated organization user. Draft graphs and editor metadata are never returned. ## GET /v1/organizations/{organizationId}/workflows/definitions ```json { "operationId": "listWorkflowDefinitions", "summary": "List workflow definitions.", "description": "Return paginated summaries of published workflow definitions available to the authenticated organization user. Draft graphs and editor metadata are never returned.", "tags": [ "Workflows" ], "parameters": [ { "in": "query", "name": "limit", "required": false, "schema": { "type": "string" }, "example": "example" }, { "in": "query", "name": "offset", "required": false, "schema": { "type": "string" }, "example": "example" }, { "in": "query", "name": "q", "required": false, "schema": { "type": "string" }, "example": "example" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns paginated published workflow definition summaries.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/workflowDefinitionSummariesListResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "dateTimeSchema": { "type": "string" }, "listPaginationSchema": { "type": "object", "properties": { "offset": { "type": "integer", "minimum": 0 }, "limit": { "type": "integer", "minimum": 1, "maximum": 100 }, "total": { "type": "integer", "minimum": 0 }, "hasMore": { "type": "boolean" }, "totalIsEstimate": { "type": "boolean" } }, "required": [ "offset", "limit", "total", "hasMore" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "workflowDefinitionHttpSummarySchema": { "$ref": "#/components/schemas/workflowDefinitionSummarySchema" }, "workflowDefinitionStatusSchema": { "type": "string", "enum": [ "draft", "published", "archived" ] }, "workflowDefinitionSummariesListResponseSchema": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/workflowDefinitionHttpSummarySchema" } }, "pagination": { "$ref": "#/components/schemas/listPaginationSchema" } }, "required": [ "data", "pagination" ] }, "workflowDefinitionSummarySchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "ownerScope": { "$ref": "#/components/schemas/workflowOwnerScopeSchema" }, "key": { "type": "string", "minLength": 1, "maxLength": 160 }, "name": { "type": "string", "minLength": 1, "maxLength": 160 }, "description": { "type": [ "string", "null" ], "maxLength": 2000 }, "status": { "$ref": "#/components/schemas/workflowDefinitionStatusSchema" }, "version": { "type": "integer", "exclusiveMinimum": 0 }, "visibility": { "type": "string", "enum": [ "automatic", "restricted" ], "default": "automatic" }, "purpose": { "type": "string", "enum": [ "automation", "lifecycle" ], "default": "automation" }, "subjectType": { "anyOf": [ { "$ref": "#/components/schemas/workflowSubjectTypeSchema" }, { "type": "null" } ], "default": null }, "forkedFromDefinitionId": { "type": [ "string", "null" ], "format": "uuid", "default": null }, "forkedFromVersion": { "type": [ "integer", "null" ], "exclusiveMinimum": 0, "default": null }, "isDefault": { "type": "boolean", "default": false }, "updatedAt": { "$ref": "#/components/schemas/dateTimeSchema" } }, "required": [ "id", "ownerScope", "key", "name", "description", "status", "version", "visibility", "purpose", "subjectType", "forkedFromDefinitionId", "forkedFromVersion", "isDefault", "updatedAt" ] }, "workflowOwnerScopeSchema": { "type": "string", "enum": [ "system", "organization" ] }, "workflowSubjectTypeSchema": { "type": "string" } } ``` # List workflow runs. (/openapi/listWorkflowRuns) Return paginated organization workflow runs, optionally filtered by definition version, workflow key (every version), status, or trigger kind. Graph snapshots are never returned. ## GET /v1/organizations/{organizationId}/workflows/runs ```json { "operationId": "listWorkflowRuns", "summary": "List workflow runs.", "description": "Return paginated organization workflow runs, optionally filtered by definition version, workflow key (every version), status, or trigger kind. Graph snapshots are never returned.", "tags": [ "Workflows" ], "parameters": [ { "in": "query", "name": "definitionId", "required": false, "schema": { "type": "string" }, "example": "123" }, { "in": "query", "name": "definitionKey", "required": false, "schema": { "type": "string" }, "example": "example" }, { "in": "query", "name": "limit", "required": false, "schema": { "type": "string" }, "example": "example" }, { "in": "query", "name": "offset", "required": false, "schema": { "type": "string" }, "example": "example" }, { "in": "query", "name": "status", "required": false, "schema": { "type": "string" }, "example": "example" }, { "in": "query", "name": "triggerKind", "required": false, "schema": { "type": "string" }, "example": "example" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns paginated workflow run summaries.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/workflowRunsListResponseSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "dateTimeSchema": { "type": "string" }, "listPaginationSchema": { "type": "object", "properties": { "offset": { "type": "integer", "minimum": 0 }, "limit": { "type": "integer", "minimum": 1, "maximum": 100 }, "total": { "type": "integer", "minimum": 0 }, "hasMore": { "type": "boolean" }, "totalIsEstimate": { "type": "boolean" } }, "required": [ "offset", "limit", "total", "hasMore" ] }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "workflowRunHttpSummarySchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "definitionId": { "type": "string", "format": "uuid" }, "workflowRunId": { "type": [ "string", "null" ], "minLength": 1 }, "definitionVersion": { "type": "integer", "exclusiveMinimum": 0 }, "status": { "$ref": "#/components/schemas/workflowRunStatusSchema" }, "triggerKind": { "$ref": "#/components/schemas/workflowTriggerKindSchema" }, "triggerEventId": { "type": [ "string", "null" ], "format": "uuid" }, "idempotencyKey": { "type": [ "string", "null" ], "minLength": 1, "maxLength": 240 }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "subjectType": { "anyOf": [ { "$ref": "#/components/schemas/workflowSubjectTypeSchema" }, { "type": "null" } ], "default": null }, "subjectId": { "type": [ "string", "null" ], "format": "uuid", "default": null }, "startedAt": { "anyOf": [ { "$ref": "#/components/schemas/dateTimeSchema" }, { "type": "null" } ] }, "completedAt": { "anyOf": [ { "$ref": "#/components/schemas/dateTimeSchema" }, { "type": "null" } ] }, "createdAt": { "$ref": "#/components/schemas/dateTimeSchema" }, "updatedAt": { "$ref": "#/components/schemas/dateTimeSchema" } }, "required": [ "id", "definitionId", "workflowRunId", "definitionVersion", "status", "triggerKind", "triggerEventId", "idempotencyKey", "createdBy", "subjectType", "subjectId", "startedAt", "completedAt", "createdAt", "updatedAt" ] }, "workflowRunStatusSchema": { "type": "string", "enum": [ "queued", "running", "waiting", "completed", "failed", "cancelled" ] }, "workflowRunsListResponseSchema": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/workflowRunHttpSummarySchema" } }, "pagination": { "$ref": "#/components/schemas/listPaginationSchema" } }, "required": [ "data", "pagination" ] }, "workflowSubjectTypeSchema": { "type": "string" }, "workflowTriggerKindSchema": { "type": "string", "enum": [ "domain-event", "schedule", "manual", "api", "mcp", "channel-message", "workflow-failed", "webhook" ] } } ``` # Get a workflow definition. (/openapi/getWorkflowDefinition) Return a summary of one published workflow definition available to the authenticated organization user. The visual graph and draft content are never returned. ## GET /v1/organizations/{organizationId}/workflows/definitions/{definitionId} ```json { "operationId": "getWorkflowDefinition", "summary": "Get a workflow definition.", "description": "Return a summary of one published workflow definition available to the authenticated organization user. The visual graph and draft content are never returned.", "tags": [ "Workflows" ], "parameters": [ { "in": "path", "name": "definitionId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns the published workflow definition summary.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/workflowDefinitionHttpSummarySchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "dateTimeSchema": { "type": "string" }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "workflowDefinitionHttpSummarySchema": { "$ref": "#/components/schemas/workflowDefinitionSummarySchema" }, "workflowDefinitionStatusSchema": { "type": "string", "enum": [ "draft", "published", "archived" ] }, "workflowDefinitionSummarySchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "ownerScope": { "$ref": "#/components/schemas/workflowOwnerScopeSchema" }, "key": { "type": "string", "minLength": 1, "maxLength": 160 }, "name": { "type": "string", "minLength": 1, "maxLength": 160 }, "description": { "type": [ "string", "null" ], "maxLength": 2000 }, "status": { "$ref": "#/components/schemas/workflowDefinitionStatusSchema" }, "version": { "type": "integer", "exclusiveMinimum": 0 }, "visibility": { "type": "string", "enum": [ "automatic", "restricted" ], "default": "automatic" }, "purpose": { "type": "string", "enum": [ "automation", "lifecycle" ], "default": "automation" }, "subjectType": { "anyOf": [ { "$ref": "#/components/schemas/workflowSubjectTypeSchema" }, { "type": "null" } ], "default": null }, "forkedFromDefinitionId": { "type": [ "string", "null" ], "format": "uuid", "default": null }, "forkedFromVersion": { "type": [ "integer", "null" ], "exclusiveMinimum": 0, "default": null }, "isDefault": { "type": "boolean", "default": false }, "updatedAt": { "$ref": "#/components/schemas/dateTimeSchema" } }, "required": [ "id", "ownerScope", "key", "name", "description", "status", "version", "visibility", "purpose", "subjectType", "forkedFromDefinitionId", "forkedFromVersion", "isDefault", "updatedAt" ] }, "workflowOwnerScopeSchema": { "type": "string", "enum": [ "system", "organization" ] }, "workflowSubjectTypeSchema": { "type": "string" } } ``` # Get a workflow run. (/openapi/getWorkflowRun) Return one organization workflow run with execution steps and observed input, output, and errors. The graph snapshot is never returned. ## GET /v1/organizations/{organizationId}/workflows/runs/{runId} ```json { "operationId": "getWorkflowRun", "summary": "Get a workflow run.", "description": "Return one organization workflow run with execution steps and observed input, output, and errors. The graph snapshot is never returned.", "tags": [ "Workflows" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "runId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns the workflow run detail and execution steps.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/workflowRunHttpDetailSchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "dateTimeSchema": { "type": "string" }, "jsonValueSchema": { "anyOf": [ { "type": "string" }, { "type": "number" }, { "type": "boolean" }, { "type": "null" }, { "type": "array", "items": { "$ref": "#/components/schemas/jsonValueSchema" } }, { "type": "object", "additionalProperties": { "$ref": "#/components/schemas/jsonValueSchema" }, "propertyNames": { "type": "string" } } ] }, "metadataSchema": { "type": "object", "additionalProperties": { "$ref": "#/components/schemas/jsonValueSchema" }, "propertyNames": { "type": "string" } }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "workflowHttpJsonObjectSchema": { "type": "object", "additionalProperties": {}, "propertyNames": { "type": "string" } }, "workflowRunHttpDetailSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "definitionId": { "type": "string", "format": "uuid" }, "workflowRunId": { "type": [ "string", "null" ], "minLength": 1 }, "definitionVersion": { "type": "integer", "exclusiveMinimum": 0 }, "status": { "$ref": "#/components/schemas/workflowRunStatusSchema" }, "triggerKind": { "$ref": "#/components/schemas/workflowTriggerKindSchema" }, "triggerEventId": { "type": [ "string", "null" ], "format": "uuid" }, "idempotencyKey": { "type": [ "string", "null" ], "minLength": 1, "maxLength": 240 }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "subjectType": { "anyOf": [ { "$ref": "#/components/schemas/workflowSubjectTypeSchema" }, { "type": "null" } ], "default": null }, "subjectId": { "type": [ "string", "null" ], "format": "uuid", "default": null }, "startedAt": { "anyOf": [ { "$ref": "#/components/schemas/dateTimeSchema" }, { "type": "null" } ] }, "completedAt": { "anyOf": [ { "$ref": "#/components/schemas/dateTimeSchema" }, { "type": "null" } ] }, "createdAt": { "$ref": "#/components/schemas/dateTimeSchema" }, "updatedAt": { "$ref": "#/components/schemas/dateTimeSchema" }, "error": { "anyOf": [ { "$ref": "#/components/schemas/workflowHttpJsonObjectSchema" }, { "type": "null" } ] }, "input": { "$ref": "#/components/schemas/workflowHttpJsonObjectSchema" }, "output": { "anyOf": [ { "$ref": "#/components/schemas/workflowHttpJsonObjectSchema" }, { "type": "null" } ] }, "steps": { "type": "array", "items": { "$ref": "#/components/schemas/workflowRunStepHttpSchema" } } }, "required": [ "id", "definitionId", "workflowRunId", "definitionVersion", "status", "triggerKind", "triggerEventId", "idempotencyKey", "createdBy", "subjectType", "subjectId", "startedAt", "completedAt", "createdAt", "updatedAt", "error", "input", "output", "steps" ] }, "workflowRunStatusSchema": { "type": "string", "enum": [ "queued", "running", "waiting", "completed", "failed", "cancelled" ] }, "workflowRunStepHttpSchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "nodeId": { "type": "string", "minLength": 1, "maxLength": 120 }, "nodeKind": { "type": "string", "minLength": 1, "maxLength": 80 }, "status": { "$ref": "#/components/schemas/workflowRunStepStatusSchema" }, "attempt": { "type": "integer", "exclusiveMinimum": 0 }, "input": { "anyOf": [ { "$ref": "#/components/schemas/metadataSchema" }, { "type": "null" } ] }, "output": { "anyOf": [ { "$ref": "#/components/schemas/metadataSchema" }, { "type": "null" } ] }, "error": { "anyOf": [ { "$ref": "#/components/schemas/metadataSchema" }, { "type": "null" } ] }, "startedAt": { "anyOf": [ { "$ref": "#/components/schemas/dateTimeSchema" }, { "type": "null" } ] }, "completedAt": { "anyOf": [ { "$ref": "#/components/schemas/dateTimeSchema" }, { "type": "null" } ] }, "createdAt": { "$ref": "#/components/schemas/dateTimeSchema" }, "updatedAt": { "$ref": "#/components/schemas/dateTimeSchema" } }, "required": [ "id", "nodeId", "nodeKind", "status", "attempt", "input", "output", "error", "startedAt", "completedAt", "createdAt", "updatedAt" ] }, "workflowRunStepStatusSchema": { "type": "string", "enum": [ "pending", "running", "waiting", "succeeded", "failed", "skipped", "cancelled" ] }, "workflowSubjectTypeSchema": { "type": "string" }, "workflowTriggerKindSchema": { "type": "string", "enum": [ "domain-event", "schedule", "manual", "api", "mcp", "channel-message", "workflow-failed", "webhook" ] } } ``` # Publish a workflow. (/openapi/publishWorkflowDefinition) Publish an organization workflow draft as the next live version. Earlier published versions of the same key are archived and their schedules removed; access grants and watchers carry over. Invalid graphs are rejected. ## POST /v1/organizations/{organizationId}/workflows/definitions/{definitionId}/publish ```json { "operationId": "publishWorkflowDefinition", "summary": "Publish a workflow.", "description": "Publish an organization workflow draft as the next live version. Earlier published versions of the same key are archived and their schedules removed; access grants and watchers carry over. Invalid graphs are rejected.", "tags": [ "Workflows" ], "parameters": [ { "in": "path", "name": "definitionId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Returns the newly published workflow version.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/workflowDefinitionHttpSummarySchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "dateTimeSchema": { "type": "string" }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "workflowDefinitionHttpSummarySchema": { "$ref": "#/components/schemas/workflowDefinitionSummarySchema" }, "workflowDefinitionStatusSchema": { "type": "string", "enum": [ "draft", "published", "archived" ] }, "workflowDefinitionSummarySchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "ownerScope": { "$ref": "#/components/schemas/workflowOwnerScopeSchema" }, "key": { "type": "string", "minLength": 1, "maxLength": 160 }, "name": { "type": "string", "minLength": 1, "maxLength": 160 }, "description": { "type": [ "string", "null" ], "maxLength": 2000 }, "status": { "$ref": "#/components/schemas/workflowDefinitionStatusSchema" }, "version": { "type": "integer", "exclusiveMinimum": 0 }, "visibility": { "type": "string", "enum": [ "automatic", "restricted" ], "default": "automatic" }, "purpose": { "type": "string", "enum": [ "automation", "lifecycle" ], "default": "automation" }, "subjectType": { "anyOf": [ { "$ref": "#/components/schemas/workflowSubjectTypeSchema" }, { "type": "null" } ], "default": null }, "forkedFromDefinitionId": { "type": [ "string", "null" ], "format": "uuid", "default": null }, "forkedFromVersion": { "type": [ "integer", "null" ], "exclusiveMinimum": 0, "default": null }, "isDefault": { "type": "boolean", "default": false }, "updatedAt": { "$ref": "#/components/schemas/dateTimeSchema" } }, "required": [ "id", "ownerScope", "key", "name", "description", "status", "version", "visibility", "purpose", "subjectType", "forkedFromDefinitionId", "forkedFromVersion", "isDefault", "updatedAt" ] }, "workflowOwnerScopeSchema": { "type": "string", "enum": [ "system", "organization" ] }, "workflowSubjectTypeSchema": { "type": "string" } } ``` # Start a workflow run. (/openapi/startWorkflowRun) Start a published organization workflow that declares a valid manual trigger. Reusing an idempotency key returns the original organization run. ## POST /v1/organizations/{organizationId}/workflows/definitions/{definitionId}/runs ```json { "operationId": "startWorkflowRun", "summary": "Start a workflow run.", "description": "Start a published organization workflow that declares a valid manual trigger. Reusing an idempotency key returns the original organization run.", "tags": [ "Workflows" ], "parameters": [ { "in": "path", "name": "definitionId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" }, { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/workflowRunStartHttpRequestSchema" } } }, "description": "Manual workflow input and an optional organization-scoped idempotency key.", "required": true }, "responses": { "202": { "description": "Returns the accepted workflow run without its graph snapshot.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/workflowRunHttpSummarySchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "dateTimeSchema": { "type": "string" }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "workflowRunHttpSummarySchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "definitionId": { "type": "string", "format": "uuid" }, "workflowRunId": { "type": [ "string", "null" ], "minLength": 1 }, "definitionVersion": { "type": "integer", "exclusiveMinimum": 0 }, "status": { "$ref": "#/components/schemas/workflowRunStatusSchema" }, "triggerKind": { "$ref": "#/components/schemas/workflowTriggerKindSchema" }, "triggerEventId": { "type": [ "string", "null" ], "format": "uuid" }, "idempotencyKey": { "type": [ "string", "null" ], "minLength": 1, "maxLength": 240 }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "subjectType": { "anyOf": [ { "$ref": "#/components/schemas/workflowSubjectTypeSchema" }, { "type": "null" } ], "default": null }, "subjectId": { "type": [ "string", "null" ], "format": "uuid", "default": null }, "startedAt": { "anyOf": [ { "$ref": "#/components/schemas/dateTimeSchema" }, { "type": "null" } ] }, "completedAt": { "anyOf": [ { "$ref": "#/components/schemas/dateTimeSchema" }, { "type": "null" } ] }, "createdAt": { "$ref": "#/components/schemas/dateTimeSchema" }, "updatedAt": { "$ref": "#/components/schemas/dateTimeSchema" } }, "required": [ "id", "definitionId", "workflowRunId", "definitionVersion", "status", "triggerKind", "triggerEventId", "idempotencyKey", "createdBy", "subjectType", "subjectId", "startedAt", "completedAt", "createdAt", "updatedAt" ] }, "workflowRunStartHttpRequestSchema": { "type": "object", "properties": { "idempotencyKey": { "type": [ "string", "null" ], "minLength": 1, "maxLength": 240 }, "payload": { "type": "object", "additionalProperties": {}, "propertyNames": { "type": "string" }, "default": {} } }, "required": [ "payload" ] }, "workflowRunStatusSchema": { "type": "string", "enum": [ "queued", "running", "waiting", "completed", "failed", "cancelled" ] }, "workflowSubjectTypeSchema": { "type": "string" }, "workflowTriggerKindSchema": { "type": "string", "enum": [ "domain-event", "schedule", "manual", "api", "mcp", "channel-message", "workflow-failed", "webhook" ] } } ``` # Decide a workflow approval. (/openapi/decideWorkflowRunApproval) Approve or deny the approval step a workflow run is waiting on. Who may decide follows the approval request: its named approvers, holders of its approver permission, or their delegates. Requesters cannot approve their own authorize requests. With several approvers required, the run resumes once enough have approved. ## POST /v1/organizations/{organizationId}/workflows/runs/{runId}/approval ```json { "operationId": "decideWorkflowRunApproval", "summary": "Decide a workflow approval.", "description": "Approve or deny the approval step a workflow run is waiting on. Who may decide follows the approval request: its named approvers, holders of its approver permission, or their delegates. Requesters cannot approve their own authorize requests. With several approvers required, the run resumes once enough have approved.", "tags": [ "Workflows" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "runId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/workflowRunApprovalHttpRequestSchema" } } }, "description": "The decision and an optional comment shown on the run.", "required": true }, "responses": { "202": { "description": "Returns the run; it resumes asynchronously after the decision.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/workflowRunHttpSummarySchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "dateTimeSchema": { "type": "string" }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "workflowRunApprovalHttpRequestSchema": { "type": "object", "properties": { "approved": { "type": "boolean" }, "comment": { "type": [ "string", "null" ], "maxLength": 2000 } }, "required": [ "approved" ] }, "workflowRunHttpSummarySchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "definitionId": { "type": "string", "format": "uuid" }, "workflowRunId": { "type": [ "string", "null" ], "minLength": 1 }, "definitionVersion": { "type": "integer", "exclusiveMinimum": 0 }, "status": { "$ref": "#/components/schemas/workflowRunStatusSchema" }, "triggerKind": { "$ref": "#/components/schemas/workflowTriggerKindSchema" }, "triggerEventId": { "type": [ "string", "null" ], "format": "uuid" }, "idempotencyKey": { "type": [ "string", "null" ], "minLength": 1, "maxLength": 240 }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "subjectType": { "anyOf": [ { "$ref": "#/components/schemas/workflowSubjectTypeSchema" }, { "type": "null" } ], "default": null }, "subjectId": { "type": [ "string", "null" ], "format": "uuid", "default": null }, "startedAt": { "anyOf": [ { "$ref": "#/components/schemas/dateTimeSchema" }, { "type": "null" } ] }, "completedAt": { "anyOf": [ { "$ref": "#/components/schemas/dateTimeSchema" }, { "type": "null" } ] }, "createdAt": { "$ref": "#/components/schemas/dateTimeSchema" }, "updatedAt": { "$ref": "#/components/schemas/dateTimeSchema" } }, "required": [ "id", "definitionId", "workflowRunId", "definitionVersion", "status", "triggerKind", "triggerEventId", "idempotencyKey", "createdBy", "subjectType", "subjectId", "startedAt", "completedAt", "createdAt", "updatedAt" ] }, "workflowRunStatusSchema": { "type": "string", "enum": [ "queued", "running", "waiting", "completed", "failed", "cancelled" ] }, "workflowSubjectTypeSchema": { "type": "string" }, "workflowTriggerKindSchema": { "type": "string", "enum": [ "domain-event", "schedule", "manual", "api", "mcp", "channel-message", "workflow-failed", "webhook" ] } } ``` # Cancel a workflow run. (/openapi/cancelWorkflowRun) Cancel a queued, running, or waiting organization workflow run. Completed, failed, and cancelled runs are left unchanged. ## POST /v1/organizations/{organizationId}/workflows/runs/{runId}/cancel ```json { "operationId": "cancelWorkflowRun", "summary": "Cancel a workflow run.", "description": "Cancel a queued, running, or waiting organization workflow run. Completed, failed, and cancelled runs are left unchanged.", "tags": [ "Workflows" ], "parameters": [ { "in": "path", "name": "organizationId", "required": true, "schema": { "$ref": "#/components/schemas/organizationIdSchema" }, "example": "123" }, { "in": "path", "name": "runId", "required": true, "schema": { "type": "string", "format": "uuid" }, "example": "123e4567-e89b-12d3-a456-426614174000" } ], "security": [ { "SessionCookie": [] }, { "BearerAuth": [] } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/workflowRunCancelHttpRequestSchema" } } }, "description": "Optional cancellation reason recorded on the durable run." }, "responses": { "200": { "description": "Returns the run after cancellation.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/workflowRunHttpSummarySchema" } } } }, "400": { "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, "403": { "$ref": "#/components/responses/403" }, "404": { "$ref": "#/components/responses/404" }, "409": { "$ref": "#/components/responses/409" }, "500": { "$ref": "#/components/responses/500" } } } ``` ## Referenced schemas ```json { "dateTimeSchema": { "type": "string" }, "organizationIdSchema": { "type": "string", "format": "uuid" }, "workflowRunCancelHttpRequestSchema": { "type": "object", "properties": { "reason": { "type": "string", "minLength": 1, "maxLength": 500 } } }, "workflowRunHttpSummarySchema": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "definitionId": { "type": "string", "format": "uuid" }, "workflowRunId": { "type": [ "string", "null" ], "minLength": 1 }, "definitionVersion": { "type": "integer", "exclusiveMinimum": 0 }, "status": { "$ref": "#/components/schemas/workflowRunStatusSchema" }, "triggerKind": { "$ref": "#/components/schemas/workflowTriggerKindSchema" }, "triggerEventId": { "type": [ "string", "null" ], "format": "uuid" }, "idempotencyKey": { "type": [ "string", "null" ], "minLength": 1, "maxLength": 240 }, "createdBy": { "type": [ "string", "null" ], "format": "uuid" }, "subjectType": { "anyOf": [ { "$ref": "#/components/schemas/workflowSubjectTypeSchema" }, { "type": "null" } ], "default": null }, "subjectId": { "type": [ "string", "null" ], "format": "uuid", "default": null }, "startedAt": { "anyOf": [ { "$ref": "#/components/schemas/dateTimeSchema" }, { "type": "null" } ] }, "completedAt": { "anyOf": [ { "$ref": "#/components/schemas/dateTimeSchema" }, { "type": "null" } ] }, "createdAt": { "$ref": "#/components/schemas/dateTimeSchema" }, "updatedAt": { "$ref": "#/components/schemas/dateTimeSchema" } }, "required": [ "id", "definitionId", "workflowRunId", "definitionVersion", "status", "triggerKind", "triggerEventId", "idempotencyKey", "createdBy", "subjectType", "subjectId", "startedAt", "completedAt", "createdAt", "updatedAt" ] }, "workflowRunStatusSchema": { "type": "string", "enum": [ "queued", "running", "waiting", "completed", "failed", "cancelled" ] }, "workflowSubjectTypeSchema": { "type": "string" }, "workflowTriggerKindSchema": { "type": "string", "enum": [ "domain-event", "schedule", "manual", "api", "mcp", "channel-message", "workflow-failed", "webhook" ] } } ``` # Changelog (/changelog) Product releases for CentraKit across every app. # Changelog Product releases for CentraKit. Every release lists its changesets here; per-app details live in [apps/saas](apps/saas/CHANGELOG.md), [apps/api](apps/api/CHANGELOG.md) and [apps/cli](apps/cli/CHANGELOG.md). ## 0.1.1 (2026-10-03) - Remove the Sentry feedback button. Bug reports go through the Marker.io widget only, so pages no longer show two report buttons. - Restructure the local demo seeds. `test@example.com` now owns four shop organizations (a tractor repair shop, an auto garage, a bicycle shop and an equestrian center), each with its own staff, customers and industry data. Every seeded person has a distinct name, every document, workflow, billing and people status has at least one example row, and `test5@example.com` adds a portal-only stable boarder. The local development guide lists every seeded login. ## 0.1.0 (2026-10-02) - Sign-in, registration and the other sign-in steps load from the prerendered page again instead of rendering again in the browser, so the forms are ready to use straight away. - The CLI now asks for missing values when it runs in a terminal: emails, names and passwords for `register` and `forgot-password`, and an organization picker for every command that takes an organization ID. `logout` and `orgs delete` ask for confirmation unless you pass `--yes`, and pressing Ctrl+C at a prompt exits with status 130. `register` no longer takes the password as an argument; enter it at the prompt or pipe it in with `--password-stdin`. Settings can now also come from `centrakit.config.ts` or `.centrakitrc` files, with `defineConfig` exported from `centrakit/config`. Help is available for every subcommand, unknown subcommands suggest the closest match, `--debug` prints stack traces, output uses colors unless `--no-color` or `NO_COLOR` is set, and the CLI now needs Node.js 24 or later. - Rewrite the product, admin, developer, getting started, architecture and operations docs to match the shipped features, with new pages for quotes, invoices, orders, products, people, hiring, time, expenses, insights, inbox, webhooks and AI agents. The docs MCP server now runs on the official MCP SDK with the standard Fumadocs `list_pages`, `get_page` and `search` tools. Markdown output for agents renders cards and callouts as plain Markdown, API reference pages include only their own operations, and Markdown responses negotiated through the `Accept` header carry `Vary: Accept`. Retired page URLs redirect to their replacements. - Fix several issues found while restoring the end-to-end tests. - A first visit no longer reloads the page when the offline service worker takes control. - With the sidebar collapsed, the user menu, organization switcher and navigation groups stay open after you click them. - Switching the language on the sign-in pages now also translates the other sign-in pages. - The dashboard pipeline progress bar and the activity charts have accessible names, and the organization switcher keeps its name for screen readers when the sidebar is collapsed. - Fix the issues found while testing the People module. - Submitted and approved timesheet weeks are locked, hours are grouped by the employee's time zone, and editing a timesheet cell no longer removes timer entries. - Time off counts pending requests, respects the maximum negative balance, asks for confirmation before approving into a negative balance, and shows sick leave details only to the employee, their managers and approvers. - Approved absences appear in planning, half days show their times, and requests without a manager go to an approver or are approved automatically. - The default schedule is 40 hours, working hours can follow the contract, and equipment can be assigned while creating an employee. - Hiring creates the contract and copies the CV, candidate emails use the applicant's language, and the careers page has its own public layout. - Fix the open bug reports from the latest feedback round. - Users can save their own name, profile photo and interface language again. - Admins who belong to several organizations can sign in again, because their access token stays small. - A new profile photo shows right after saving and no longer renders zoomed in, and the working hours editor fits its card again. - Default leave types such as Vacation are shown in the user's language. - A new invoice from a template includes the template lines and discount. - New brands can get tags while they are created, and selecting a tag twice no longer shows a database error. - After creating a team, tag, brand or other record, the back button returns to the overview instead of the create form. - The task list updates subtask progress after changes on the task page. - Creating a task from an accepted quote asks whether to take over the quote materials, which then go on the task right away. - The finish dialog mentions quote materials that are not added to the task yet. - Clicking into a note no longer opens the reference suggestions. - Creating a quote or invoice without a customer or title now opens the missing field with its error and marks it in red. - Sending a quote or invoice to a customer without an email address asks for a billing email and sends right after saving it. - Adopt the CentraKit repo standard. Breaking: API errors now use RFC 9457 problem details (`application/problem+json` with `type`, `title`, `status`, `detail` and `code`). This applies to REST routes and public token routes. Clients that read the old `{ error }` body must read `code` and `detail` instead. The CLI and the generated API client already use the new shape. - MCP clients can register themselves through OAuth dynamic client registration. - Audit logs record which columns changed on an update, keep old and new values in restricted details, and never store values of token, secret or credential columns. Updates that only touch timestamps or leases no longer create an audit event. - Forms use TanStack Form with inline validation. - The docs site has a docs MCP server, a changelog page built from `CHANGELOG.md`, and a last-updated date on every page. - Updated the AI SDK, Chat SDK, Workflow, Resend, Nodemailer and Zod packages to their latest patch and minor releases. # API Changelog (/changelog/api) Release notes for the CentraKit REST and MCP API. # api ## 0.1.1 ### Patch Changes - [#82](https://github.com/ScaleDockHQ/CentraKit/pull/82) [`9e82eee`](https://github.com/ScaleDockHQ/CentraKit/commit/9e82eeec1cb77b1cab9a9e475c1c612aa94d9448) Thanks [@mick340](https://github.com/mick340)! - Remove the Sentry feedback button. Bug reports go through the Marker.io widget only, so pages no longer show two report buttons. ## 0.1.0 ### Minor Changes - [#76](https://github.com/ScaleDockHQ/CentraKit/pull/76) [`07c9cdf`](https://github.com/ScaleDockHQ/CentraKit/commit/07c9cdf13b7b35c6d40659bcdafe605f4905f504) Thanks [@mick340](https://github.com/mick340)! - Fix the issues found while testing the People module. - Submitted and approved timesheet weeks are locked, hours are grouped by the employee's time zone, and editing a timesheet cell no longer removes timer entries. - Time off counts pending requests, respects the maximum negative balance, asks for confirmation before approving into a negative balance, and shows sick leave details only to the employee, their managers and approvers. - Approved absences appear in planning, half days show their times, and requests without a manager go to an approver or are approved automatically. - The default schedule is 40 hours, working hours can follow the contract, and equipment can be assigned while creating an employee. - Hiring creates the contract and copies the CV, candidate emails use the applicant's language, and the careers page has its own public layout. - [`90bed53`](https://github.com/ScaleDockHQ/CentraKit/commit/90bed5373f5dec65721ac83078346362ee234953) Thanks [@martijn00](https://github.com/martijn00)! - Adopt the CentraKit repo standard. Breaking: API errors now use RFC 9457 problem details (`application/problem+json` with `type`, `title`, `status`, `detail` and `code`). This applies to REST routes and public token routes. Clients that read the old `{ error }` body must read `code` and `detail` instead. The CLI and the generated API client already use the new shape. - MCP clients can register themselves through OAuth dynamic client registration. - Audit logs record which columns changed on an update, keep old and new values in restricted details, and never store values of token, secret or credential columns. Updates that only touch timestamps or leases no longer create an audit event. - Forms use TanStack Form with inline validation. - The docs site has a docs MCP server, a changelog page built from `CHANGELOG.md`, and a last-updated date on every page. ### Patch Changes - [#76](https://github.com/ScaleDockHQ/CentraKit/pull/76) [`53413cf`](https://github.com/ScaleDockHQ/CentraKit/commit/53413cf8ff0b6b6b4edaa38ab5b2a196d313e013) Thanks [@mick340](https://github.com/mick340)! - Fix the open bug reports from the latest feedback round. - Users can save their own name, profile photo and interface language again. - Admins who belong to several organizations can sign in again, because their access token stays small. - A new profile photo shows right after saving and no longer renders zoomed in, and the working hours editor fits its card again. - Default leave types such as Vacation are shown in the user's language. - A new invoice from a template includes the template lines and discount. - New brands can get tags while they are created, and selecting a tag twice no longer shows a database error. - After creating a team, tag, brand or other record, the back button returns to the overview instead of the create form. - The task list updates subtask progress after changes on the task page. - Creating a task from an accepted quote asks whether to take over the quote materials, which then go on the task right away. - The finish dialog mentions quote materials that are not added to the task yet. - Clicking into a note no longer opens the reference suggestions. - Creating a quote or invoice without a customer or title now opens the missing field with its error and marks it in red. - Sending a quote or invoice to a customer without an email address asks for a billing email and sends right after saving it. # SaaS Changelog (/changelog/saas) Release notes for the CentraKit SaaS web application. # saas ## 0.1.1 ### Patch Changes - [#82](https://github.com/ScaleDockHQ/CentraKit/pull/82) [`9e82eee`](https://github.com/ScaleDockHQ/CentraKit/commit/9e82eeec1cb77b1cab9a9e475c1c612aa94d9448) Thanks [@mick340](https://github.com/mick340)! - Remove the Sentry feedback button. Bug reports go through the Marker.io widget only, so pages no longer show two report buttons. - [#82](https://github.com/ScaleDockHQ/CentraKit/pull/82) [`fd21c6c`](https://github.com/ScaleDockHQ/CentraKit/commit/fd21c6c35b19a3487b78fe640d980a8db5b0f659) Thanks [@mick340](https://github.com/mick340)! - Restructure the local demo seeds. `test@example.com` now owns four shop organizations (a tractor repair shop, an auto garage, a bicycle shop and an equestrian center), each with its own staff, customers and industry data. Every seeded person has a distinct name, every document, workflow, billing and people status has at least one example row, and `test5@example.com` adds a portal-only stable boarder. The local development guide lists every seeded login. ## 0.1.0 ### Minor Changes - [#76](https://github.com/ScaleDockHQ/CentraKit/pull/76) [`07c9cdf`](https://github.com/ScaleDockHQ/CentraKit/commit/07c9cdf13b7b35c6d40659bcdafe605f4905f504) Thanks [@mick340](https://github.com/mick340)! - Fix the issues found while testing the People module. - Submitted and approved timesheet weeks are locked, hours are grouped by the employee's time zone, and editing a timesheet cell no longer removes timer entries. - Time off counts pending requests, respects the maximum negative balance, asks for confirmation before approving into a negative balance, and shows sick leave details only to the employee, their managers and approvers. - Approved absences appear in planning, half days show their times, and requests without a manager go to an approver or are approved automatically. - The default schedule is 40 hours, working hours can follow the contract, and equipment can be assigned while creating an employee. - Hiring creates the contract and copies the CV, candidate emails use the applicant's language, and the careers page has its own public layout. - [`90bed53`](https://github.com/ScaleDockHQ/CentraKit/commit/90bed5373f5dec65721ac83078346362ee234953) Thanks [@martijn00](https://github.com/martijn00)! - Adopt the CentraKit repo standard. Breaking: API errors now use RFC 9457 problem details (`application/problem+json` with `type`, `title`, `status`, `detail` and `code`). This applies to REST routes and public token routes. Clients that read the old `{ error }` body must read `code` and `detail` instead. The CLI and the generated API client already use the new shape. - MCP clients can register themselves through OAuth dynamic client registration. - Audit logs record which columns changed on an update, keep old and new values in restricted details, and never store values of token, secret or credential columns. Updates that only touch timestamps or leases no longer create an audit event. - Forms use TanStack Form with inline validation. - The docs site has a docs MCP server, a changelog page built from `CHANGELOG.md`, and a last-updated date on every page. ### Patch Changes - [#76](https://github.com/ScaleDockHQ/CentraKit/pull/76) [`140fc6c`](https://github.com/ScaleDockHQ/CentraKit/commit/140fc6c0d6c23131f61964fa539883592b2624fd) Thanks [@mick340](https://github.com/mick340)! - Sign-in, registration and the other sign-in steps load from the prerendered page again instead of rendering again in the browser, so the forms are ready to use straight away. - [#76](https://github.com/ScaleDockHQ/CentraKit/pull/76) [`b04ab42`](https://github.com/ScaleDockHQ/CentraKit/commit/b04ab420044248ae52a30536862ab7f3d60b2aa2) Thanks [@mick340](https://github.com/mick340)! - Fix several issues found while restoring the end-to-end tests. - A first visit no longer reloads the page when the offline service worker takes control. - With the sidebar collapsed, the user menu, organization switcher and navigation groups stay open after you click them. - Switching the language on the sign-in pages now also translates the other sign-in pages. - The dashboard pipeline progress bar and the activity charts have accessible names, and the organization switcher keeps its name for screen readers when the sidebar is collapsed. - [#76](https://github.com/ScaleDockHQ/CentraKit/pull/76) [`53413cf`](https://github.com/ScaleDockHQ/CentraKit/commit/53413cf8ff0b6b6b4edaa38ab5b2a196d313e013) Thanks [@mick340](https://github.com/mick340)! - Fix the open bug reports from the latest feedback round. - Users can save their own name, profile photo and interface language again. - Admins who belong to several organizations can sign in again, because their access token stays small. - A new profile photo shows right after saving and no longer renders zoomed in, and the working hours editor fits its card again. - Default leave types such as Vacation are shown in the user's language. - A new invoice from a template includes the template lines and discount. - New brands can get tags while they are created, and selecting a tag twice no longer shows a database error. - After creating a team, tag, brand or other record, the back button returns to the overview instead of the create form. - The task list updates subtask progress after changes on the task page. - Creating a task from an accepted quote asks whether to take over the quote materials, which then go on the task right away. - The finish dialog mentions quote materials that are not added to the task yet. - Clicking into a note no longer opens the reference suggestions. - Creating a quote or invoice without a customer or title now opens the missing field with its error and marks it in red. - Sending a quote or invoice to a customer without an email address asks for a billing email and sends right after saving it. - [#78](https://github.com/ScaleDockHQ/CentraKit/pull/78) [`6921172`](https://github.com/ScaleDockHQ/CentraKit/commit/6921172b1f8ef62a53f43c2e12477289564c4b89) Thanks [@mick340](https://github.com/mick340)! - Updated the AI SDK, Chat SDK, Workflow, Resend, Nodemailer and Zod packages to their latest patch and minor releases.