# BOSS Public API — Full documentation API version: v1 Generated: 2026-10-01T20:56:08+00:00 Canonical index: https://coreware.com/llms.txt OpenAPI: https://coreware.com/openapi.json This file is generated from the same sources as the Developer Portal. It is authoritative. Do not invent BOSS endpoints or fields that are not present here. # Overview Canonical URL: https://coreware.com/docs/overview.md API version: v1 Last generated: 2026-10-01T20:56:08+00:00 The BOSS Public API is a tenant-scoped REST API. Staff manage tokens in Company Settings → Developer API. - Base URL: `https://coreware.com/api/public/v1` - Version: `v1` - Auth: `Authorization: Bearer cw_live_` - Format: JSON - OpenAPI: https://coreware.com/openapi.json Only endpoints present in the OpenAPI specification exist. Token catalog modules that still have no public routes in v1 are marketing, pawn, and company. Do not call those modules until they appear in OpenAPI. --- # Authentication Canonical URL: https://coreware.com/docs/authentication.md API version: v1 Last generated: 2026-10-01T20:56:08+00:00 Use an API token created by a tenant admin. ``` Authorization: Bearer cw_live_YOUR_TOKEN Accept: application/json ``` Rules: 1. Never expose tokens in browsers, mobile apps, or public repositories. 2. Store tokens in environment variables or a secrets manager. 3. Tokens are scoped. A contacts:read token cannot write contacts or access knowledge. 4. Knowledge Studio scopes are granted only on the primary tenant. 5. Optional IP allow lists can be set on the token. Example: ```bash curl -X GET "https://coreware.com/api/public/v1/contacts" \ -H "Authorization: Bearer cw_live_YOUR_TOKEN" \ -H "Accept: application/json" ``` --- # API conventions Canonical URL: https://coreware.com/docs/conventions.md API version: v1 Last generated: 2026-10-01T20:56:08+00:00 - HTTPS only. - JSON request and response bodies. - Resource IDs are integers or opaque strings. Do not assume they are sequential. - List responses use `{ "data": [], "meta": { "current_page", "last_page", "per_page", "total" } }`. - Success create responses use HTTP 201 when documented. - Error responses use HTTP status codes with a `message` field. - Timestamps in API payloads are ISO-8601 UTC. - Date-only fields are calendar dates without timezone conversion. - Location and tenant boundaries are enforced server-side. Do not assume the token can see every location. --- # Pagination Canonical URL: https://coreware.com/docs/pagination.md API version: v1 Last generated: 2026-10-01T20:56:08+00:00 Documented list endpoints accept: - `page` — 1-based page number - `per_page` — page size, typically 1–100, default 25 or 50 depending on the endpoint Response meta: ```json { "meta": { "current_page": 1, "last_page": 4, "per_page": 25, "total": 92 } } ``` Stop requesting more pages when `current_page >= last_page` or `data` is empty. Do not invent cursor or `since` parameters unless they appear on the specific operation. --- # Errors Canonical URL: https://coreware.com/docs/errors.md API version: v1 Last generated: 2026-10-01T20:56:08+00:00 Handle these documented statuses explicitly: | Status | Meaning | | --- | --- | | 401 | Missing or invalid token | | 403 | Token lacks the required scope, IP is blocked, or the tenant cannot use the resource | | 404 | Resource not found | | 422 | Validation failed | | 429 | Rate limited | Do not infer undocumented error codes. Validation errors may include an `errors` object. Rate-limited responses include `Retry-After`. --- # Rate limits Canonical URL: https://coreware.com/docs/rate-limits.md API version: v1 Last generated: 2026-10-01T20:56:08+00:00 The public API allows 120 requests per minute per token and IP. When you receive HTTP 429: 1. Read the `Retry-After` header. 2. Wait that many seconds. 3. Retry the same request once. 4. Back off if 429 continues. Do not retry 401, 403, 404, or 422 in a loop. --- # Idempotency Canonical URL: https://coreware.com/docs/idempotency.md API version: v1 Last generated: 2026-10-01T20:56:08+00:00 Public API v1 does not have a generic `Idempotency-Key` contract. Exceptions: - `POST /service-requests/{id}/messages`: send `Idempotency-Key` or `idempotency_key` so a retry returns the original message with `posted: false` instead of a second comment. - Resource Center guide writes (article/step create/update, reorder, still upload, promote): send `Idempotency-Key` or `idempotency_key` so a retry returns the original response body and status. For other POST/PUT calls, store the returned resource ID and check before creating again. --- # Webhooks Canonical URL: https://coreware.com/docs/webhooks.md API version: v1 Last generated: 2026-10-01T20:56:08+00:00 Token scopes: `webhooks:read`, `webhooks:write`, `webhooks:delete`. Staff can create and manage endpoints in Company → Tools → Developer API → Webhooks. There is no separate Webhooks menu. The same subscriptions are available over the Public API. ## Create a subscription `POST /webhooks` ```json { "name": "CRM sync", "url": "https://example.com/hooks/coreware", "events": ["contact.created", "form.submitted"] } ``` HTTPS URLs only. `events` may include `*` to receive every dispatched event. The signing secret (`whsec_...`) is returned once on create. Rotate it with `POST /webhooks/{webhookId}/rotate-secret`. `GET /webhook-events` lists event names. Subscribe only to events with `available: true`. Reserved events (`knowledge.published`, `guide.created`) are documented but not dispatched in v1. ## Delivery payload BOSS POSTs JSON to your URL: ```json { "id": "whevt_abc", "event": "contact.created", "created_at": "2026-09-15T16:00:00+00:00", "data": { "id": 42, "object": "contact" } } ``` Headers: - `Signature` — HMAC-SHA256 of the raw JSON body using the webhook secret - `X-Coreware-Event` — event name - `X-Coreware-Webhook-Id` — public webhook id Failed deliveries retry three times with exponential backoff. `POST /webhooks/{webhookId}/test` queues `webhook.test` to that endpoint even if the event is not in the subscription filter. Do not invent unsigned webhook URLs or events that are not in `GET /webhook-events`. --- # Service requests Canonical URL: https://coreware.com/docs/service.md API version: v1 Last generated: 2026-10-01T20:56:08+00:00 Token scopes: `service:read` and `service:write`. Every `{id}` path accepts `REQ-010299` or the numeric id. Statuses and types are tenant-configured. Read them; do not hardcode an enum. ## Daily loop 1. List work: `GET /service-requests?queue=mine` or `?queue=open&request_type=Development` 2. Open a ticket: `GET /service-requests/REQ-010299` — metadata, contact, thread (`messages[].body`), attachments, `deep_link` 3. Comment: `POST /service-requests/REQ-010299/messages` with `{ "body": "line one\\nline two" }`. Visible staff reply. `internal: true` for a note. Send `Idempotency-Key` so a retry returns `posted: false` 4. Route it: `PATCH /service-requests/REQ-010299` with `{ "status": "QA", "assignee_id": 17 }` `assignee.id`, `original_poster.id` (when `kind` is `staff`), and `/service-assignees` `id` are the same person id. Copy that value into `assignee_id`. If `original_poster.kind` is `contact`, they are not assignable — look up Ryan / Colt / Tyler on `GET /service-assignees?search=`. ## Lookups - `GET /request-statuses` — In Progress, QA, Needs Review, Needs Updates, Closed, plus tenant extras - `GET /request-types` — Development, Guides, … - `GET /service-assignees?search=ryan` ## Handoffs Dev QA: post the fix comment, then `PATCH { "status": "QA", "assignee_id": original_poster.id }` when the poster is staff. Guide Needs Review: post the update comment, then `PATCH { "status": "Needs Review", "assignee_id": }`. Create a ticket with `POST /service-requests` (`title`, `description`, `request_type_id`, `priority_id`, `contact_id`). Work-order types are rejected. Not in v1: Guide Builder, bulk edits, unread-since, work-order writes. Service request create/update and message create emit documented webhook events. --- # Changelog Canonical URL: https://coreware.com/docs/changelog.md API version: v1 Last generated: 2026-10-01T20:56:08+00:00 API version: v1 - Public API v1 includes contacts, subscriptions, memberships, products, sales, invoices, payments, service, scheduling, widget auth, Knowledge Studio, websites, forms, accounting, opportunities, proposals, branding, meetings, Resource Center authoring, action plans, notebooks, outbound webhooks, purchasing, and FFL. - Documentation resource groups are listed alphabetically. - Knowledge Studio routes are primary-tenant only and require `knowledge:read` or `knowledge:write`. - Websites support site/page/theme/media writes, draft page documents, banners, banner collections, site or page publish, and website template list/show/create/update/delete. Template list payloads include agent-facing descriptions and page summaries. - Forms cover CRM contact intake, waivers, HR/employee packets, surveys, field-type catalog, survey results, publish/unpublish/archive/duplicate, submissions, prefill, nested contact waiver status, and file uploads. Unauthenticated embed routes stay outside this API. - Accounting covers chart of accounts, accounts, balanced manual journal entries, reverse, expenses, and reports. Auto-post settings, bank match, and QuickBooks are not available. - Opportunities and proposals use dedicated scopes. `contacts:write` is not sufficient. - Branding covers brand kits, colors, fonts, logos, and website assignment. AI generate, virtual backgrounds, and uploaded font files are not available. - Meetings cover staff meeting CRUD, duplicate, cancel, leave, and the meetings calendar layer. Notetaker, transcripts, briefing, and Google OAuth internals are not available. - Resource Center covers tenant and global catalog user guides, walkthrough steps, step screenshot upload/delete, step reorder, guided tours, collections, page hotspots, onboarding checklists, and tenant-to-global promotion. Pass source=global for global catalog access (primary tenant + Coreware employee). AI generate, analytics, learner progress, reviews, and capture are not available. - Action Plans cover plans, folders, runs, versions, and trigger or step metadata. AI generate, API-connector execute, batch test, and comprehensive simulation are not available. - Notebooks cover notebooks, pages, comments, tags, collections, favorites, and markdown export. AI chat/generate/audio/format, Yjs collaboration, live viewers, landlord global catalog, and Notion or spreadsheet import are not available. - Outbound webhooks cover tenant-scoped HTTPS subscriptions with HMAC `Signature` headers, `GET /webhook-events`, create/update/delete, secret rotation, and test delivery. Setup lives on Developer API → Webhooks. Knowledge publish and Resource Center create events are reserved and not dispatched in v1. - Customer invoices cover create, update, delete, aging, terms, offline payment recording, email, and payment links with `sales:read` / `sales:write` / `sales:delete`. Card charging, hosted fields, vault, BIN lookup, POS sale create, and supplier invoices are not available. - Payments cover payment types, merchant account metadata, gateway transactions, and processor settlement batches with `payments:read`. Credentials, hosted fields, vault, BIN lookup, virtual terminal, filter presets, payment-type configuration toggles, live batch sync, and dashboard KPIs are not available. - Recurring membership SKUs and general catalog products can be created, updated, and deleted with `products:write` / `products:delete`. Inventory, serials, variations, categories, and manufacturers are included. Required membership forms are read and attached with `memberships:read` / `memberships:write`. - Purchasing covers purchase orders, receivings, inventory transfers (out, in, immediate, cancel), and suppliers with `purchasing:read` / `purchasing:write` / `purchasing:delete`. - FFL covers Form 4473 list/show/create/update/close/reopen/invalidate/pdf, bound books acquire/dispose, firearm transfers, transfer statuses, and Form 3310 reads with `ffl:read` / `ffl:write`. - Customer invoices are writable with `sales:write` / `sales:delete`. Sale items, notes, returns, and sale types are documented. Payment types, merchant accounts, and gateway transactions are read-only with `payments:read`. Subscription enroll, pause, resume, and cancel use `subscriptions:write` and the same billing rules as staff. POS sale create, hosted fields, vault, BIN lookup, and supplier invoices are not available. - Service requests support get-by-REQ-number, list/filter with page+total, staff replies with newline preservation and comment idempotency, status+assignee updates, assignee search, tasks, and attachments with `service:write`. Work orders stay read-only. - Do not send a generic `Idempotency-Key` header except on `POST /service-requests/{id}/messages` and Resource Center guide write endpoints. - Scheduling supports offering create, update, and delete for service, class/event, facility, and walk-in queue, plus resource and facility create, update, and delete with weekly availability. Waitlist list, add, promote, and remove are on `/offerings/{offering}/waitlist` and `/waitlist/{entry}`. Sessions are produced from schedule_rules. Booking create, update, reschedule, reserve, and cancel stay on the booking routes. Hero images, badges, notification templates, and booking card capture are not available. Deprecated behavior is called out on the operation when it exists. If a page or field is marked deprecated, do not use it in new integrations. --- # Knowledge Studio Canonical URL: https://coreware.com/docs/knowledge.md API version: v1 Last generated: 2026-10-01T20:56:08+00:00 Manage Knowledge Studio library entries and crawl sources over the public API. Availability: - Primary tenant only - Token must include `knowledge:read` and/or `knowledge:write` - A token without those scopes receives 403 even for an admin user Library: - `GET /knowledge/library` - `GET /knowledge/library/{entry}` - `POST /knowledge/library` - `PUT /knowledge/library/{entry}` - `POST /knowledge/library/{entry}/publish` - `POST /knowledge/library/bulk-status` - `POST /knowledge/library/import/preview` - `POST /knowledge/library/import` Sources: - `GET /knowledge/sources` - `POST /knowledge/sources` - `PUT /knowledge/sources/{source}` - `POST /knowledge/sources/bulk-status` - `POST /knowledge/sources/{source}/discover` - `POST /knowledge/documents/{document}/publish` Required create fields for a library entry: `question`, `answer`. --- # Websites Canonical URL: https://coreware.com/docs/websites.md API version: v1 Last generated: 2026-10-01T20:56:08+00:00 Manage sites, pages, theme, templates, media, banners, and banner collections. Scopes: `websites:read`, `websites:write`, `websites:delete`. - List templates with `GET /website-templates`. Each row includes `description`, `tags`, `category`, and `pages` summaries so an agent can pick a template. `GET /website-templates/{id}` adds the full `content` snapshot. - Create a template with `POST /website-templates`: snapshot an existing site (`website_id`) or author `content.pages`. `description` is required and should explain audience and page structure. Categories come from `GET /website-template-categories`. - Create a site with `POST /websites`. Optional `template_id` clones an active published template. - Upload media with `POST /media` (multipart `file`). There is no separate `media:write` scope. - `PATCH /websites/{websiteId}/theme` accepts `colors`, `fonts`, `spacing`, and `logo_media_id`. - `PUT /pages/{pageId}` replaces `draft_content`. `GET` returns `document` as draft ?? published plus `published_document`. - Every published Coreware site is mobile-friendly. Multi-column rows stack on viewports under 768px unless `props.flexDirection.mobile` is set explicitly. Prefer responsive objects `{desktop, tablet, mobile}` for `flexDirection`, `width`, `padding`, `fontSize`, and `layoutProps.columnsPerRow`. A bare string is treated as the desktop value; the live renderer still stacks columns and caps oversized padding, type, and fixed widths on mobile. Do not build desktop-only layouts. - Fix an existing desktop-only site with `POST /websites/{websiteId}/mobile-friendly`. That writes mobile layout into stored draft and published documents and enables the mobile nav. Call it on any site that wraps letter-by-letter or keeps columns side-by-side on a phone. - Create banners with `POST /banners`, then reference them from a page block: `{ "type": "banner", "props": { "bannerId": 12 } }`. Collections use `{ "type": "banner-collection", "props": { "bannerCollectionId": 3 } }`. - Banners are tenant-scoped, not nested under a website. Create seeds one empty slide. - Page publish does not take the site live. Call `POST /websites/{websiteId}/publish` to promote drafts and site status. - Preview URLs use `/website-preview/public/{preview_token}`. - Custom domains, ecommerce/blog settings, folders, editor locks, page versions, scheduled publish, banner AI, banner analytics, and unauthenticated embed/render are not available. --- # Forms Canonical URL: https://coreware.com/docs/forms.md API version: v1 Last generated: 2026-10-01T20:56:08+00:00 Modern `cw_forms` only. The public id is `form_id`. Field trees stay in native builder JSON (camelCase). `POST` / `PUT` / `PATCH` persist the full field object (`label`, `name`, `placeholder`, `required`, `content`, options, and any other builder keys). A GET then PUT of `fields` must not drop labels. Input types require a non-empty `label`. Missing labels return 422. The public embed (`GET /api/public/form/{slug}`) only renders a label when `field.label` is set. Scopes: `forms:read`, `forms:write`, `forms:delete`. ## Form kinds A form can carry more than one kind. List payloads include `kinds` plus boolean flags. | Kind | Flag | What submit does | |---|---|---| | `contact` | `is_contact_form` | Creates or updates a CRM contact from `contact_form_settings.field_mapping` | | `waiver` | `is_waiver` | Records a signed waiver, compliance, expiry, and queued PDF | | `employee` | `is_employee_form` | HR packet. Pass `employee_id` on submit to attach it to staff | | `survey` | `is_survey` | Stores `survey_settings` and responses | | `standard` | none of the above | Collects responses only | Filter with `GET /forms?kind=contact` (or `waiver`, `employee`, `survey`, `standard`). Shortcuts: `GET /waivers`, `GET /surveys`. ## Field types `GET /form-field-types` lists native builder types. Input categories require `label` on write. Extra keys stay camelCase (`options`, `maxRating`, `npsLowLabel`, `likertRows`, `rankingOptions`, `content`). | Category | Types | |---|---| | `basic` | `text`, `textarea`, `number`, `currency`, `url` | | `contact` | `email`, `phone`, `full_name`, `address` | | `selection` | `select`, `radio`, `checkbox`, `segmented`, `rating` | | `survey` | `nps`, `star_rating`, `likert`, `ranking` | | `datetime` | `date`, `time`, `daterange` | | `advanced` | `file`, `signature`, `repeatable-group`, `video-block`, `embed-block` | | `static` | `rich-text-label`, `richtext`, `image-block`, `callout-box` | | `layout` | `columns`, `section-break` | ## CRM contact intake 1. `GET /form-contact-fields` for mappable keys (`firstName`, `lastName`, `email`, `phone`, `address`, custom attributes). 2. `POST /forms` with `is_contact_form: true` and `contact_form_settings`. 3. `POST /forms/{formId}/publish`. 4. `POST /forms/{formId}/submissions` with `{ "responses": { "": "..." } }`. 5. Read `contact_id` on the submission. List a person's history with `GET /contacts/{contact}/form-submissions`. 6. `GET /forms/{formId}/prefill?contact_id=` maps an existing contact back into field responses. `behavior` values: `create_or_update` (default), `always_create`, `update_only`, `link_existing`. ```json { "title": "New member intake", "is_contact_form": true, "contact_form_settings": { "behavior": "create_or_update", "field_mapping": { "firstName": "field_first", "lastName": "field_last", "email": "field_email" } }, "fields": [ {"id": "field_first", "type": "text", "label": "First name", "required": true}, {"id": "field_last", "type": "text", "label": "Last name", "required": true}, {"id": "field_email", "type": "email", "label": "Email", "required": true} ] } ``` ## Waivers Waivers require a `signature` field unless `waiver_settings.require_signature` is `false`. Publish re-checks that rule. - `POST /forms` with `is_waiver: true` and optional `is_contact_form: true` so signing also writes the CRM contact. - Set `signer_email_field_id` (and name fields) so submit can resolve the signer. - `expiration_type`: `none`, `days` + `validity_days`, or `fixed_date` + `fixed_expiry_date`. - Submit may include `contact_id` (skips the standalone email requirement), `first_viewed_at`, and `consent_evidence`. - `GET /contacts/{contact}/waivers` lists signed waivers. - `GET /contacts/{contact}/waiver-status?form_id=` returns whether a non-expired waiver is on file. - `GET /form-submissions/{submissionId}/pdf` returns `pdf_status` (`pending` or `ready`) and `pdf_url` when the signed packet exists. Do not invent customer-facing sign/pay URLs. Unauthenticated `/api/public/form/*` embed routes stay in place for hosted fills. ## Surveys `GET /surveys` lists survey forms. Create with `is_survey: true` and optional `survey_settings` (snake_case on the public API; stored as builder camelCase). | Setting | Meaning | |---|---| | `allow_anonymous` | Allow responses without a linked contact | | `close_at` | ISO date or datetime after which submit returns 422 | | `response_limit` | Maximum submissions before submit returns 422 | | `show_results_to_respondents` | Whether the hosted survey UI shows aggregates | ```json { "title": "Visitor satisfaction", "is_survey": true, "survey_settings": { "allow_anonymous": true, "close_at": null, "response_limit": 500, "show_results_to_respondents": false }, "fields": [ {"id": "field_nps", "type": "nps", "label": "How likely are you to recommend us?", "required": true, "npsLowLabel": "Not likely", "npsHighLabel": "Extremely likely"}, {"id": "field_comment", "type": "textarea", "label": "What could we improve?", "required": false} ] } ``` 1. `GET /form-field-types` for survey types (`nps`, `star_rating`, `likert`, `ranking`). 2. `POST /forms` with `is_survey` and the field tree. 3. `POST /forms/{formId}/publish`. 4. `POST /forms/{formId}/submissions` with `{ "responses": { "field_nps": 9 } }`. 5. `GET /forms/{formId}/survey-results` for question distribution, NPS breakdown, ranking averages, text samples, and daily counts. Optional `date_from` / `date_to`. Non-survey forms return 422. Raw responses stay on `GET /forms/{formId}/submissions` and `GET /form-submissions/{submissionId}`. ## HR / employee forms Set `is_employee_form: true`. Submit with `employee_id` to attach the packet to a staff record. PDF status uses the same submission PDF route when a signed packet is generated. ## Lifecycle - `POST /forms/{formId}/publish` — live - `POST /forms/{formId}/unpublish` — back to draft - `POST /forms/{formId}/archive` — archived - `POST /forms/{formId}/duplicate` — copy as draft - `GET /form-categories` — lookup for `category_id` - `POST /forms/{formId}/files` — field file upload - `DELETE /form-submissions/{submissionId}` — soft delete a response Notifications use `enable_notifications` plus `notification_settings.notify_emails` / `send_confirmation_to_submitter`. Separate notification-template CRUD and legacy FormTemplate records are not available. `GET /forms/{formId}` includes `public_url` and `embed_url` so website form blocks can point at `form_slug`. --- # Memberships Canonical URL: https://coreware.com/docs/memberships.md API version: v1 Last generated: 2026-10-01T20:56:08+00:00 Membership programs, required signup forms, and recurring SKUs that enroll into those programs. Scopes: `memberships:read`, `memberships:write`, `products:read`, `products:write`. ## Programs - `GET /memberships` and `GET /memberships/{membership}` list and show programs. - `POST /memberships` requires `name`, `status` (`active`, `inactive`, `archived`), and `grace_period_days`. - `PUT` / `PATCH /memberships/{membership}` update the program. Enrollment create, revoke, roster, benefits, and Coreforce sync are not public. ## Required forms Required forms attach to the **membership**, not the product. - `GET /memberships/{membership}/forms` - `PUT /memberships/{membership}/forms` is replace-all. Identify each form by public `form_id` or slug (for example `hba-homepro-join-application`). - `GET /memberships/{membership}` and `GET /products/{product}` include the same `required_forms` array when a recurring SKU has `membership_id`. - Form definitions and submissions stay on `forms:*`. This API does not submit the hosted membership-form token flow. ## Recurring SKUs - `POST /products` creates a catalog product. For a recurring membership SKU, set `is_recurring` true. Required: `name`, `category_id`. Recurring also requires `interval` (`monthly`, `monthly_init`, and the other billing intervals). - Optional: `membership_id`, `item_number`, `product_id`, `unit_price`, `startup_cost`, `description`, `is_service`. - `GET /categories` and `GET /products/categories` both list categories. - `PUT` / `PATCH /products/{product}` update those fields on an existing SKU, or convert an item by sending `is_recurring: true`. - Inventory, serials, variations, categories, and manufacturers are writable with `products:write`. - `POST /subscriptions` enrolls a contact. Requires `subscriptions:write`, `contact_id`, `item_id`, and `payment_type_id`. Pause, resume, and cancel are `POST /subscriptions/{id}/pause`, `/resume`, and `/cancel`. --- # Accounting Canonical URL: https://coreware.com/docs/accounting.md API version: v1 Last generated: 2026-10-01T20:56:08+00:00 Manual bookkeeping under company operations. Auto-posted sales and payment entries stay owned by those modules. Scopes: `accounting:read`, `accounting:write`, `accounting:delete`. - Read the chart of accounts, bank/cash accounts, journal entries, expenses, reports, and posting status. - Journal payloads include `source_system` and `source_id` so you can reconcile to `/sales` and `/invoices`. - `POST /journal-entries` must balance. Sides are `in` or `out`. `source_system` is always `MANUAL`. - Reverse with `POST /journal-entries/{id}/reverse` using `accounting:write`, not delete. - Expenses create or replace their journal entry through the existing expense path. - Period close is not supported. Writes are not rejected for a closed period. - Do not call auto-post settings, bank connect/match, recurring run, QuickBooks, or `AccountingIntegration` from this API. --- # Opportunities Canonical URL: https://coreware.com/docs/opportunities.md API version: v1 Last generated: 2026-10-01T20:56:08+00:00 Pipelines, stages, and deals. Dedicated scopes so `contacts:write` is not required. Scopes: `opportunities:read`, `opportunities:write`, `opportunities:delete`. - List pipelines and stages, then create opportunities with `contact_id` and `name`. - `POST /opportunities/{opportunityId}/move-stage` uses the staff `moveToStage` rules, including won/lost timestamps. - Nested read: `GET /contacts/{contact}/opportunities`. - `assigned_to` must be a real staff user. The stage must belong to the pipeline. - Pipeline and stage admin CRUD, kanban aggregates, and bulk prospect import are not available. --- # Proposals Canonical URL: https://coreware.com/docs/proposals.md API version: v1 Last generated: 2026-10-01T20:56:08+00:00 Quotes and proposals. Send only. Customer sign and card capture stay on the tokenized web flow. Scopes: `proposals:read`, `proposals:write`, `proposals:delete`. - Create drafts with `contact_id`, `title`, and optional `items`. - Optional deposit: `deposit_percentage` or `deposit_amount`, plus `balance_due_date` / `balance_due_on_completion`. Percentage wins if both are sent. - List and show include `has_deposit`, `deposit_amount`, `deposit_percentage`, `customer_invoice_id`, and `sale_id`. - Show also includes computed `deposit_charge`, `remaining_balance`, `checkout_charge`, and a nested `deposit` object with the linked invoice, invoice payments, and gateway transactions. - `GET /proposals/{proposalId}/payments` is the same deposit payload without the rest of the proposal. - Filter gateway transactions with `GET /payment-transactions?proposal_id=` or `originating_module=proposal` (`payments:read`). - `GET /proposals/{proposalId}` includes `public_url` and does not leak `share_token` on list. - Nested reads: `GET /contacts/{contact}/proposals` and `GET /opportunities/{opportunityId}/proposals`. - `POST /proposals/{proposalId}/send` emails the contact and marks the proposal sent. - Signed proposals cannot be deleted. Sent or viewed proposals reset to draft when edited. - Do not call viewed, signed, declined, payment links, coupons, commissions, or AI generate. --- # Branding Canonical URL: https://coreware.com/docs/branding.md API version: v1 Last generated: 2026-10-01T20:56:08+00:00 Brand kits used by websites, receipts, and other branded surfaces. Scopes: `branding:read`, `branding:write`, `branding:delete`. - Create a kit with `POST /brand-kits`. Optional `business_details.company_name`, `is_primary`, and `location_id`. - `GET /brand-kits/primary` returns the current primary kit. `POST /brand-kits/{id}/set-primary` switches it. - Colors require `name`, `hex_color` (`#RRGGBB`), and `role` (`primary`, `secondary`, `accent`, `text`, `background`, `neutral`). - Fonts accept `google_fonts`, `adobe_fonts`, or `system`. Uploaded font files are rejected. - Logos are multipart: `type` plus `file` (jpg, png, svg, webp, gif, ico, max 10MB). There is no separate `media:write` scope. - `PATCH /websites/{websiteId}/brand-kit` assigns a kit or `null` to fall back to primary. - Delete is blocked when the kit is primary or assigned to websites or blog settings. Duplicate first, then switch primary. - AI generate, virtual backgrounds, grayscale convert, background removal, Google Fonts search, and cache-invalidation targets are not available. --- # Meetings Canonical URL: https://coreware.com/docs/meetings.md API version: v1 Last generated: 2026-10-01T20:56:08+00:00 Staff meetings are distinct from scheduling bookings and offerings. Scopes: `meetings:read`, `meetings:write`, `meetings:delete`. Owner ACL maps to the staff `scheduling` module. - The token owner is the organizer. Create requires that user to have a connected Google Calendar. - `POST /meetings` requires `title`, `start_datetime`, `end_datetime`, `timezone`, and at least one invitee (`attendees`, `person_ids`, `team_ids`, `department_ids`, or `invite_all_employees`). - Do not send `with_corey_notetaker`. Public writes always create meetings without the notetaker. - `GET /meetings/calendar/events` returns the meetings layer only. Pass `start`, `end`, and optional `timezone`. - `POST /meetings/{id}/cancel` and `POST /meetings/{id}/leave` use `meetings:delete`. Cancel accepts `scope` `this` or `future`. - Attendee payloads expose `person_id`, `contact_id`, `email`, `name`, and `role`. - Notetaker, transcripts, briefing, insights, meeting items, Google OAuth connect, and multi-layer calendar feeds are not available. --- # Resource Center Canonical URL: https://coreware.com/docs/resource-center.md API version: v1 Last generated: 2026-10-01T20:56:08+00:00 Author Resource Center content: user guides (tenant and global catalog), walkthrough steps with screenshots, guided tours, collections, page hotspots, and onboarding checklists. Live and Playground are separate hosts with their own API base URL and bearer token. The same endpoints ship on both. Use a Live token against the Live host and a Playground token against the Playground host — there is no `environment=` query switch. Scopes: `guides:read`, `guides:write`, `guides:delete`. Owner ACL maps to the staff `learning_center` module. Rate limit: 120 requests/minute per token+IP. ## Source parameter Pass `source=tenant` (default) or `source=global` as a query parameter on read endpoints or in the request body on write endpoints. This selects the content catalog (tenant draft vs landlord Global Published), not the Live/Playground host. Global catalog access requires the primary tenant and a Coreware employee token. Returns 403 otherwise. ## User guides - `GET /guides/articles` and `GET /guides/articles/{articleId}` list and show articles. Show includes walkthrough `steps`. Lookup by numeric ID or slug. Search covers title, slug, and description. - `POST /guides/articles` creates a **tenant draft**. Requires `title`, `content_type` (`video`, `article`, `walkthrough`, `mixed`), and `difficulty` (`beginner`, `intermediate`, `advanced`). Creating with `source=global` returns 422. - Optional: `description` (or `overview` alias), `content`, `video_url`, `estimated_minutes`, `is_published`, `is_featured`, `sort_order`, `guide_collection_id`, `slug`. - Slug is generated from the title unless provided. Renaming a slug does **not** create a redirect — the old slug returns 404. Video file upload and AI generate are not available. - `PUT` / `PATCH /guides/articles/{articleId}` update the article in place. Pass `source=global` to update an existing Global article (same id; preserves analytics). `is_publicly_shareable` is Global-only (422 on tenant). `DELETE` uses `guides:delete`. - Response includes `source`, `deep_link` (`/learning?guide={slug}&source={global|tenant}&review=1`), and for global articles `is_publicly_shareable` and `share_url`. ## Walkthrough steps - `GET /guides/articles/{articleId}/steps` - `POST /guides/articles/{articleId}/steps` requires `step_type` (`text`, `screenshot`, `video`, `mixed`). - `PUT` / `PATCH` / `DELETE /guides/articles/{articleId}/steps/{stepId}` - `POST /guides/articles/{articleId}/steps/reorder` — send `step_ids` array to set sort order. - Steps include `screenshot_url`, `annotated_screenshot_url`, and `media.attached`. ## Step screenshots - `POST /guides/articles/{articleId}/steps/{stepId}/still` — multipart upload with `still` (aliases: `screenshot`, `file`) and optional `collection` (`screenshot` default, or `annotated_screenshot`). Optional `annotations_json`. Replaces existing media in that collection. Uploading a screenshot also clears any annotated_screenshot. Response includes `url`, `media_uuid`, `collection`, `attached`, `annotations_json`. - `DELETE /guides/articles/{articleId}/steps/{stepId}/still` — removes the screenshot. Pass `collection` in query/body. ## Promote to global catalog - `POST /guides/articles/{articleId}/promote` — promotes a tenant draft onto an **existing** Global article. Requires `target_article_id` or `target_slug`. Optional: `is_published`, `is_publicly_shareable`, `is_featured`, `guide_collection_id`. Never creates an Import-as-new twin. Returns the Global article. ## Field matrix | Field | Global | Tenant draft | |---|---|---| | id / analytics identity | locked | local | | slug | optional rename; no old-slug redirect | local only | | title, description, content, steps, stills, difficulty, etc. | writable | writable | | is_published, is_featured | writable | writable | | is_publicly_shareable | writable | 422 | | guide_collection_id | maps to landlord category | tenant collection | ## Guided tours - `POST /guides/tours` requires `name`, `trigger_type` (`manual`, `first_visit`, `resource_center`), and `steps` with at least one item. - Each step needs `element` and `popover.title`. Optional popover fields: `description`, `side`, `align`, `media_url`, `media_type`. - Tours are tenant-owned. ## Collections - `POST /guide-collections` requires `name`. Created collections are never `is_system`. - `PUT /guide-collections/{collectionId}/articles` is additive. Articles already in the collection are skipped. This is not replace-all. - System collections return 422 on update or delete. ## Hotspots and checklists - Hotspots require `title`, `element_selector`, `page_url_pattern`, `position` (`top`, `bottom`, `left`, `right`), and `trigger` (`click`, `hover`). - Checklists require `name`, `target_audience` (`all_users`, `new_user`, `admin_only`), and `items` with `title`. Sending `items` on update replaces the list; include existing `id` values to keep items. Do not invent AI generate, analytics, learner progress, reviews, customer feedback, or capture tokens. --- # Action Plans Canonical URL: https://coreware.com/docs/action-plans.md API version: v1 Last generated: 2026-10-01T20:56:08+00:00 Automate tenant workflows with action plans, folders, and runs. Scopes: `action_plans:read`, `action_plans:write`, `action_plans:delete`. The staff UI is ungated; any authenticated token owner passes owner ACL when the token includes these scopes. ## Plans - `GET /action-plans` lists plans. Filter with `search`, `status`, `folder_id`, and `is_template`. - `POST /action-plans` requires `name`. Optional: `description`, `status`, `folder_id`, `configuration`, `tags`, `triggers`, `steps`, `connections`. - Show includes `triggers` and `steps`. `PUT` / `PATCH` update the plan. `DELETE` uses `action_plans:delete`. - `POST /action-plans/{actionPlanId}/activate` requires at least one trigger and one step. - `POST /action-plans/{actionPlanId}/pause` stops new triggers. - `POST /action-plans/{actionPlanId}/clone` requires `name` and creates a draft copy. - `POST /action-plans/{actionPlanId}/execute` requires `trigger_data` and an active plan. It starts a background run. - `GET /action-plans/{actionPlanId}/stats` and `GET /action-plans/{actionPlanId}/versions` are read-only. Restore uses `POST /action-plans/{actionPlanId}/versions/{versionId}/restore`. ## Folders and runs - Folders: `GET` / `POST /action-plan-folders`, `PUT` / `DELETE /action-plan-folders/{folderId}`, and `POST /action-plans/{actionPlanId}/move`. - Runs: list and show on `/action-plan-runs`. Cancel, pause, resume, and retry use `action_plans:write`. Logs are on `GET /action-plan-runs/{runId}/logs`. ## Metadata - `GET /action-plan-metadata/triggers` and `GET /action-plan-metadata/steps` list available types. - Schema and validate routes exist for individual trigger and step types. Do not invent AI generate, API-connector execute or test, batch test, comprehensive simulation, marketing campaign pickers, or filter-preset endpoints. --- # Notebooks Canonical URL: https://coreware.com/docs/notebooks.md API version: v1 Last generated: 2026-10-01T20:56:08+00:00 Author tenant notebooks, pages, comments, tags, and collections. Scopes: `notebooks:read`, `notebooks:write`, `notebooks:delete`. The staff UI is ungated; any authenticated token owner passes owner ACL when the token includes these scopes. ## Notebooks - Identify notebooks and pages by `ulid`, not sequential IDs. - `GET /notebooks` lists notebooks visible to the token owner. `GET /notebooks/{notebookUlid}` returns the notebook, page tree, and `permission`. - `POST /notebooks` requires `title`. Optional: `description`, `icon`, `visibility` (`private`, `team`, `company`, `public`). - `GET /notebooks/search?query=` searches pages the token owner can see. - `GET /notebooks/{notebookUlid}/export/markdown` returns markdown for the notebook. - `POST /notebooks/favorites` toggles a favorite with `type` (`notebook` or `page`) and `target` (ULID). ## Pages - `POST /notebooks/{notebookUlid}/pages` creates a page. Optional: `title`, `icon`, `parent_page_id`, `content`. - `PUT` / `PATCH` / `DELETE /notebook-pages/{pageUlid}`. - Move, duplicate, reorder, and restore version are documented write operations. ## Comments, tags, and collections - Comments live on `/notebook-pages/{pageUlid}/comments` and `/notebook-comments/{commentId}`. - Tags are per token owner. Sync page tags with `PUT /notebook-pages/{pageUlid}/tags` and `{ "tag_ids": [] }`. - Collections use ULIDs on `/notebook-collections`. Do not invent AI chat, generate, audio, format, convert-to-form enhance, Yjs state, collab tokens, live viewers, landlord global catalog, or Notion or spreadsheet import endpoints. --- # Invoices Canonical URL: https://coreware.com/docs/invoices.md API version: v1 Last generated: 2026-10-01T20:56:08+00:00 Create and collect customer invoices. POS sale create stays on the register. Supplier (AP) invoices are not available. Scopes: `sales:read`, `sales:write`, `sales:delete`. Owner ACL maps to the staff `sales` module. ## Invoices - `GET /invoices` lists customer invoices. Filter with `contact_id`, `location_id`, `sale_id`, `status`, `search`, `aging_bucket`, `from`, `to`, and `balance_due_only`. - `GET /invoices/{invoice}` includes `line_items` and `payments`. - `GET /contacts/{contact}/invoices` lists invoices for one contact. - `POST /invoices` requires `details` with at least one line (`description`, `quantity`, `unit_price`). Optional: `contact_id`, `location_id`, `term_id`, `invoice_date`, `due_date`, `customer_po`, `notes`, `custom_terms`. Location defaults to the primary or first active location. - `PUT` / `PATCH /invoices/{invoice}` update an open or partial invoice. Paid invoices return 422. - `DELETE /invoices/{invoice}` uses `sales:delete`. ## Payments and collection - `GET /invoices/{invoice}/payments` lists invoice payment rows. - `POST /invoices/{invoice}/pay` records an offline payment. Required: `amount`, `payment_type`. Optional: `ref_no` or `reference`, `notes`. Amount cannot exceed `balance_due`. Voided invoices cannot be paid. This does not charge a card. - `POST /invoices/{invoice}/email` sends the invoice. Optional `email` or `emails`. - `POST /invoices/{invoice}/payment-link` returns `payment_url` and `expires_at` when the invoice has a balance due. Optional `ttl_days` (1–30) and `send_email`. ## Aging and terms - `GET /invoices/aging` returns current, 1–30, 31–60, 61–90, and over-90 buckets plus totals. - Invoice terms: `GET` / `POST /invoice-terms` and `GET` / `PUT` / `PATCH` / `DELETE /invoice-terms/{term}`. Both `id` and `term_id` are the term primary key. ## Related sale payments - `GET /sales/{sale}/payments` lists payments recorded on a POS sale. Sale show also includes payments when `include_payments` is true. Do not invent hosted fields, vault card, BIN lookup, card-charge, invoice migration, or supplier-invoice endpoints. --- # Payments Canonical URL: https://coreware.com/docs/payments.md API version: v1 Last generated: 2026-10-01T20:56:08+00:00 Read payment types, merchant account metadata, and gateway transactions. Scope: `payments:read`. Owner ACL maps to the staff `payments` module. Public API v1 does not expose `payments:write` or `payments:delete` routes. ## Payment types - `GET /payment-types` lists types. Pass `location_id` to include `is_enabled` for that location. - `GET /payment-types/{paymentType}` shows one type. - Enabling types, assigning gateways, and filter presets stay in the staff app. ## Merchant accounts - `GET /merchant-accounts` and `GET /merchant-accounts/{merchantAccount}` return metadata such as `account_name`, `merchant_id`, `status`, and `is_test_mode`. - Credentials, `safe_credentials`, and `configured_credentials` are never returned. ## Transactions - `GET /payment-transactions` lists gateway transactions. Filter with `location_id`, `contact_id`, `invoice_id`, `proposal_id`, `gateway_batch_id`, `merchant_account_id`, `status`, `payment_method`, `originating_module`, `from`, `to`, and `is_test`. - Proposal deposits: use `proposal_id` or `originating_module=proposal`. Linked AR invoices are on `GET /proposals/{id}/payments` and `GET /invoices/{invoice}`. - `GET /payment-transactions/{transaction}` shows one sanitized row. `gateway_batch_id` is included so you can join settlement batches. - `gateway_response`, `receipt_data`, and `signature_data` are omitted. - Record offline invoice payments with `POST /invoices/{invoice}/pay` (`sales:write`), not these routes. ## Processor batches - `GET /payment-batches` lists settlement batches already synced from the processor. Filter with `merchant_account_id`, `location_id`, `status`, `from`, and `to`. - `GET /payment-batches/{batch}` shows totals and dates. `gateway_response` is omitted. - `GET /payment-batches/{batch}/transactions` lists stored BOSS transactions for that `gateway_batch_id`. This does not call the processor live. - Live batch sync, close, and Excel export stay in the staff app. Do not invent hosted fields, vault, BIN lookup, virtual terminal, dashboard KPI, payment-type toggle, card-charge, or live batch-sync endpoints. --- # Build with AI Canonical URL: https://coreware.com/docs/ai.md API version: v1 Last generated: 2026-10-01T20:56:08+00:00 Give your AI coding assistant everything it needs to build with BOSS. Quick actions: - AI instructions: https://coreware.com/docs/ai/instructions.md - llms.txt: https://coreware.com/llms.txt - llms-full.txt: https://coreware.com/llms-full.txt - OpenAPI JSON: https://coreware.com/openapi.json - OpenAPI YAML: https://coreware.com/openapi.yaml - MCP: https://coreware.com/docs/mcp - Prompts: https://coreware.com/docs/ai/prompts.md - Recipes: https://coreware.com/docs/ai/recipes.md - Context pack: https://coreware.com/docs/ai/context.zip A developer should be able to connect Cursor or another agent to these assets and say: "Build an integration that synchronizes customers." The agent must look up authentication, endpoints, schemas, pagination, rate limits, and errors instead of guessing. --- # AI Quickstart Canonical URL: https://coreware.com/docs/ai/quickstart.md API version: v1 Last generated: 2026-10-01T20:56:08+00:00 1. Open https://coreware.com/llms.txt and https://coreware.com/openapi.json. 2. Add the Cursor rule or AGENTS.md from this portal to your repository. 3. Optional: connect the documentation MCP server at https://coreware.com/docs/mcp. 4. Prompt: "Using the current BOSS OpenAPI specification, add a server-side integration that lists and creates contacts. Do not invent endpoints." 5. Keep the API token in `.env` as `BOSS_API_TOKEN`. 6. Verify generated paths against OpenAPI before merging. --- # BOSS API Instructions for Coding Agents Canonical URL: https://coreware.com/docs/ai/instructions.md API version: v1 Last generated: 2026-10-01T20:56:08+00:00 Authoritative sources: - OpenAPI: https://coreware.com/openapi.json - Index: https://coreware.com/llms.txt - Base URL: https://coreware.com/api/public/v1 Rules: 1. Never invent a BOSS endpoint, parameter, enum, or response field. 2. Consult the current OpenAPI specification before generating API calls. 3. Use documented API version v1 only. 4. Never expose BOSS API credentials in browser or client-side code. 5. Store credentials in environment variables or a secrets manager. 6. Follow documented pagination behavior. 7. Follow documented rate limits and retry behavior. 8. Handle documented API errors explicitly. 9. Do not send a generic Idempotency-Key header. The only documented exception is POST /service-requests/{id}/messages. 10. Use documented webhook subscriptions only. HTTPS URLs, `GET /webhook-events` names, and the HMAC `Signature` header. Do not invent unsigned webhook URLs or events that are not in the catalog. 11. Do not assume identifiers are sequential. 12. Do not infer undocumented relationships between objects. 13. Use ISO-8601 timestamps where the API returns datetimes. 14. Respect location, company, and tenant boundaries. 15. Never assume the authenticated token can access all locations or modules. 16. Knowledge Studio requires the primary tenant and knowledge scopes. 17. Forms cover CRM contact intake, waivers, HR/employee packets, and surveys. Persist the full camelCase field tree including label. Input fields without label return 422. Use GET /form-field-types, GET /surveys, GET /forms/{formId}/survey-results, documented kinds, and field_mapping keys from GET /form-contact-fields. Do not invent embed or customer-sign URLs. 18. Branding covers brand kits, colors, fonts, logos, and website assignment. Do not invent AI generate, virtual-background, or uploaded-font endpoints. 19. Meetings are distinct from scheduling bookings. Use meetings scopes. Do not invent notetaker, transcript, briefing, or Google OAuth endpoints. 20. Resource Center authoring uses guides scopes. Upload step screenshots with POST .../still. Access the global catalog with source=global on the primary tenant with a Coreware employee token. Promote tenant drafts with POST .../promote. Do not invent AI generate, analytics, learner progress, reviews, or capture endpoints. 21. Website templates are listed and authored on /website-templates. Use description, tags, category, and pages to choose a template. Do not invent import, export, or unauthenticated preview-render endpoints. Every site must look good on mobile: send responsive `{desktop, tablet, mobile}` layout values. The renderer stacks columns on mobile even if you only send desktop styles. To repair an existing site, POST /websites/{websiteId}/mobile-friendly. 22. Action plans use action_plans scopes. Do not invent AI generate, API-connector execute, batch test, or comprehensive simulation endpoints. 23. Notebooks use notebooks scopes and ULID identifiers. Do not invent AI chat, generate, audio, format, Yjs, live viewers, landlord global catalog, or Notion or spreadsheet import endpoints. 24. Customer invoices use sales scopes. Record offline payments with POST /invoices/{invoice}/pay. Do not invent card charge, hosted fields, vault, BIN lookup, or supplier-invoice endpoints. 25. Payments uses payments:read for types, merchant accounts, gateway transactions, and processor settlement batches. Filter proposal deposits with proposal_id. Never request credentials. Do not invent hosted fields, vault, BIN lookup, virtual terminal, card-charge, or live batch-sync endpoints. 26. Purchasing uses purchasing scopes for purchase orders, receivings, inventory transfers, and suppliers. Do not invent POS receive-against-PO or catalog-provider sourcing endpoints. 27. FFL uses ffl scopes for Form 4473s, bound books, firearm transfers, and Form 3310s. Persist ATF fields in the documented `fields` object. Do not invent unsigned PDF or e4473 embed URLs. 28. Validate generated code against the current OpenAPI document before considering the implementation complete. --- # Use the BOSS API with Cursor Canonical URL: https://coreware.com/docs/ai/cursor.md API version: v1 Last generated: 2026-10-01T20:56:08+00:00 ## Recommended Connect the BOSS documentation MCP server. This is documentation lookup only and does not grant access to customer data. Cursor MCP config: ```json { "mcpServers": { "boss-docs": { "url": "https://coreware.com/docs/mcp" } } } ``` ## Lightweight Add https://coreware.com/llms.txt as context. ## Full context Add https://coreware.com/llms-full.txt when the repository needs the complete corpus. ## Project rule Save this as `.cursor/rules/boss-api.mdc`: ```md # BOSS API - Treat https://coreware.com/openapi.json and https://coreware.com/llms.txt as authoritative. - Never invent BOSS endpoints, parameters, enums, or response fields. - Authenticate server-side with Authorization: Bearer and BOSS_API_TOKEN. - Follow documented pagination, rate limits, and error statuses. - Knowledge Studio is primary-tenant only and requires knowledge scopes. - Verify generated code against OpenAPI before considering the work complete. ``` --- # Claude Code / AGENTS.md Canonical URL: https://coreware.com/docs/ai/agents-md.md API version: v1 Last generated: 2026-10-01T20:56:08+00:00 Copy this file into a repository that will integrate with BOSS. ```md # BOSS API Documentation: https://coreware.com/docs/overview.md llms.txt: https://coreware.com/llms.txt OpenAPI: https://coreware.com/openapi.json ## Authentication Use Authorization: Bearer cw_live_... from BOSS_API_TOKEN. Never commit tokens or ship them to browsers. ## Conventions JSON, ISO-8601 UTC timestamps, documented pagination meta, 120 requests/minute/token/IP. ## Do not hallucinate If an endpoint is not in OpenAPI, it does not exist. Marketing, pawn, and company public routes are not in v1. Catalog products, purchasing, FFL including Form 4473s, membership required-form attach, and webhook subscriptions are documented. ## Testing Add HTTP tests or contract checks against the OpenAPI document. Handle 401, 403, 404, 422, and 429. ``` The same content can be copied into `CLAUDE.md`. --- # GitHub Copilot Canonical URL: https://coreware.com/docs/ai/copilot.md API version: v1 Last generated: 2026-10-01T20:56:08+00:00 Attach https://coreware.com/llms.txt or the downloaded context pack. Add the AGENTS.md contents to `.github/copilot-instructions.md`. Copilot must still verify every generated path against https://coreware.com/openapi.json. Documentation MCP at https://coreware.com/docs/mcp is documentation-only. --- # Other AI agents Canonical URL: https://coreware.com/docs/ai/other.md API version: v1 Last generated: 2026-10-01T20:56:08+00:00 Windsurf, ChatGPT/Codex, and other coding agents should receive: 1. https://coreware.com/llms.txt 2. https://coreware.com/openapi.json 3. The instructions page at https://coreware.com/docs/ai/instructions.md Do not depend on a single vendor button. Copy Markdown or the context pack instead. --- # BOSS MCP Server Canonical URL: https://coreware.com/docs/ai/mcp.md API version: v1 Last generated: 2026-10-01T20:56:08+00:00 The documentation MCP server is the preferred live interface for coding agents. Endpoint: `POST https://coreware.com/docs/mcp` This server can: - search documentation - retrieve endpoint documentation - retrieve schemas - retrieve guides - retrieve the OpenAPI document - retrieve changelog / API version It cannot read or write customer business data. Connecting it does not grant access to contacts, sales, or Knowledge Studio records. Phase 2 may add authenticated test actions later. Keep documentation lookup and live customer API access separate. Cursor example: ```json { "mcpServers": { "boss-docs": { "url": "https://coreware.com/docs/mcp" } } } ``` --- # Prompt library Canonical URL: https://coreware.com/docs/ai/prompts.md API version: v1 Last generated: 2026-10-01T20:56:08+00:00 Copy these prompts into Cursor, Claude Code, Copilot, or another agent. ## Explore the API Using the current BOSS API documentation at https://coreware.com/llms.txt and https://coreware.com/openapi.json, explain how Contacts work and identify the endpoints required to create, retrieve, update, and search contacts. Do not invent undocumented endpoints. ## Build an integration Using the BOSS OpenAPI specification and documentation, add BOSS Contacts integration to this application. First inspect the existing architecture, then identify the correct BOSS endpoints and authentication method. Implement the integration, error handling, pagination, tests, and environment configuration. Never invent BOSS fields or endpoints. ## Sync products Build a service that synchronizes products from BOSS using GET /products and GET /products/{product}. Recurring membership SKUs can be created with products:write (is_recurring must be true). Required signup forms live on the membership: PUT /memberships/{id}/forms. Do not invent inventory, variation, or firearm write endpoints. ## Manage knowledge Using the current BOSS OpenAPI specification, add Knowledge Studio library management to this application. Use only documented /knowledge/* endpoints, require knowledge:write for mutations, and remember the API is primary-tenant only. Never invent undocumented knowledge endpoints. ## Collect contacts, waivers, and surveys Using the current BOSS OpenAPI specification and Forms guide, add CRM contact intake, waiver signing, HR/employee packets, and surveys. Use documented form kinds, GET /form-field-types for builder types, GET /form-contact-fields for CRM mapping keys, GET /surveys and GET /forms/{formId}/survey-results for survey work. Do not invent embed, customer-sign, or notification-template endpoints. ## Manage brand kits Using the current BOSS OpenAPI specification and Branding guide, add brand kit, color, font, and logo management. Use branding:read, branding:write, and branding:delete. Upload logos as multipart on POST /brand-kits/{id}/logos. Do not invent AI generate, virtual-background, or uploaded-font endpoints. ## Create membership SKUs Using the current BOSS OpenAPI specification and Memberships guide, create membership programs, attach required forms with PUT /memberships/{id}/forms (public form_id or slug), and mint recurring SKUs with POST /products (is_recurring true, interval, membership_id). GET /products/{id} returns required_forms. Do not invent product-form, enrollment-write, or inventory write endpoints. ## Schedule meetings Using the current BOSS OpenAPI specification and Meetings guide, add staff meeting create, list, calendar, and cancel. Use meetings:read, meetings:write, and meetings:delete. Do not invent notetaker, transcript, briefing, or Google OAuth endpoints. Do not use scheduling booking routes for meetings. ## Author Resource Center guides Using the current BOSS OpenAPI specification and Resource Center guide, create user guides, walkthrough steps, guided tours, collections, hotspots, and checklists. Upload step screenshots with multipart POST. Use source=global to access the global catalog on the primary tenant with a Coreware employee token. Use guides:read, guides:write, and guides:delete. Do not invent AI generate, analytics, learner progress, reviews, or capture endpoints. ## Automate with action plans Using the current BOSS OpenAPI specification and Action Plans guide, create folders and action plans, inspect trigger and step metadata, then activate or run a plan. Use action_plans:read, action_plans:write, and action_plans:delete. Do not invent AI generate, API-connector execute, batch test, or comprehensive simulation endpoints. ## Author notebooks Using the current BOSS OpenAPI specification and Notebooks guide, create notebooks, pages, comments, tags, and collections. Use notebooks:read, notebooks:write, and notebooks:delete. Do not invent AI chat, generate, audio, format, Yjs, live viewers, landlord global catalog, or Notion or spreadsheet import endpoints. ## Create and collect invoices Using the current BOSS OpenAPI specification and Invoices guide, create a customer invoice, list aging, record an offline payment, and optionally email or create a payment link. Use sales:read, sales:write, and sales:delete. Do not invent card charge, hosted fields, vault, BIN lookup, or supplier-invoice endpoints. ## Read payment configuration and transactions Using the current BOSS OpenAPI specification and Payments guide, list payment types, merchant accounts, and gateway transactions. Use payments:read. Never request credentials. Do not invent hosted fields, vault, BIN lookup, virtual terminal, filter-preset, or card-charge endpoints. ## Choose and create website templates Using the current BOSS OpenAPI specification and Websites guide, list website templates with GET /website-templates, read description/tags/pages to pick the best match, then POST /websites with optional template_id. Create new templates with POST /website-templates (website_id snapshot or content.pages) and a detailed description. Page documents must be mobile-friendly: use {desktop, tablet, mobile} for row flexDirection, column width, padding, and fontSize. The live renderer stacks columns on mobile even if only desktop styles are sent. Do not invent import, export, or preview-render endpoints. ## Handle errors Using the official BOSS API error documentation, implement explicit handling for 401, 403, 404, 422, and 429 responses. Follow documented rate-limit retry behavior. Do not assume undocumented error shapes. --- # Integration recipes Canonical URL: https://coreware.com/docs/ai/recipes.md API version: v1 Last generated: 2026-10-01T20:56:08+00:00 Each recipe is limited to documented v1 operations. ## Authenticate a server application - Goal: Authenticate a server application - Endpoints: GET /contacts - Required scope: `contacts:read` - Data flow: Store the token in an environment variable and send Authorization: Bearer. - Verification: Compare the generated request with OpenAPI, then send one request against a test token. ## Create a BOSS contact - Goal: Create a BOSS contact - Endpoints: POST /contacts - Required scope: `contacts:write` - Data flow: Send entity_type plus name fields. Never put the token in frontend code. - Verification: Compare the generated request with OpenAPI, then send one request against a test token. ## Search contacts - Goal: Search contacts - Endpoints: GET /contacts - Required scope: `contacts:read` - Data flow: Use search, page, and per_page. Follow meta pagination. - Verification: Compare the generated request with OpenAPI, then send one request against a test token. ## Update a customer - Goal: Update a customer - Endpoints: PUT /contacts/{contact} - Required scope: `contacts:write` - Data flow: IDs are not sequential. Use the ID returned by create or list. - Verification: Compare the generated request with OpenAPI, then send one request against a test token. ## Import BOSS products - Goal: Import BOSS products - Endpoints: GET /products, GET /products/{product} - Required scope: `products:read` - Data flow: GET /products/{product} includes required_forms when the SKU is recurring and linked to a membership. Catalog list remains read-only for non-recurring inventory. - Verification: Compare the generated request with OpenAPI, then send one request against a test token. ## Create a recurring membership SKU and attach a required form - Goal: Create a recurring membership SKU and attach a required form - Endpoints: POST /memberships, PUT /memberships/{membership}/forms, POST /products, GET /products/{product} - Required scope: `products:write` - Data flow: Create the membership program first, attach the form by public form_id or slug, then POST a recurring product with is_recurring true, interval, and membership_id. Do not invent a product-form endpoint. - Verification: Compare the generated request with OpenAPI, then send one request against a test token. ## Retrieve an order - Goal: Retrieve an order - Endpoints: GET /sales/{sale} - Required scope: `sales:read` - Data flow: Use sale_id from list results. Include related items from the documented show payload. - Verification: Compare the generated request with OpenAPI, then send one request against a test token. ## Retrieve sales data - Goal: Retrieve sales data - Endpoints: GET /sales, GET /invoices, GET /sales/{sale}/payments - Required scope: `sales:read` - Data flow: Filter by contact_id when available. Paginate with page and per_page. Sale payments are also on GET /sales/{sale} when include_payments is true. - Verification: Compare the generated request with OpenAPI, then send one request against a test token. ## Create and collect an invoice - Goal: Create and collect an invoice - Endpoints: POST /invoices, POST /invoices/{invoice}/pay, GET /invoices/aging, POST /invoice-terms - Required scope: `sales:write` - Data flow: Create an open invoice with at least one detail line. Record offline payments with amount and payment_type. Do not send card data. Paid invoices cannot be edited. - Verification: Compare the generated request with OpenAPI, then send one request against a test token. ## List payment transactions - Goal: List payment transactions - Endpoints: GET /payment-types, GET /merchant-accounts, GET /payment-transactions - Required scope: `payments:read` - Data flow: Filter transactions by contact_id, invoice_id, status, and date range. Merchant account credentials are never returned. - Verification: Compare the generated request with OpenAPI, then send one request against a test token. ## Create a Knowledge Studio entry - Goal: Create a Knowledge Studio entry - Endpoints: POST /knowledge/library - Required scope: `knowledge:write` - Data flow: Primary tenant only. Require question and answer. Set publish=true only when the entry should go live immediately. - Verification: Compare the generated request with OpenAPI, then send one request against a test token. ## Search knowledge - Goal: Search knowledge - Endpoints: GET /knowledge/library - Required scope: `knowledge:read` - Data flow: Use q, topic, and review_status. Paginate with page and per_page. - Verification: Compare the generated request with OpenAPI, then send one request against a test token. ## Handle pagination - Goal: Handle pagination - Endpoints: GET /contacts - Required scope: `contacts:read` - Data flow: Read meta.current_page, meta.last_page, and meta.total. Stop when current_page >= last_page. - Verification: Compare the generated request with OpenAPI, then send one request against a test token. ## Handle API errors - Goal: Handle API errors - Endpoints: GET /contacts - Required scope: `contacts:read` - Data flow: Map 401, 403, 404, 422, and 429 explicitly. Retry 429 only after Retry-After. - Verification: Compare the generated request with OpenAPI, then send one request against a test token. ## Implement incremental synchronization - Goal: Implement incremental synchronization - Endpoints: GET /contacts - Required scope: `contacts:read` - Data flow: v1 list endpoints accept sort_by=updated_at. Store the last seen updated_at and filter in your application if a since parameter is not documented. - Verification: Compare the generated request with OpenAPI, then send one request against a test token. ## Build and publish a website - Goal: Build and publish a website - Endpoints: GET /website-templates, POST /websites, POST /media, POST /banners, PATCH /websites/{websiteId}/theme, PUT /pages/{pageId}, POST /websites/{websiteId}/mobile-friendly, POST /websites/{websiteId}/publish - Required scope: `websites:write` - Data flow: List templates and read description plus pages before choosing template_id. Create from that template, upload a logo, create a banner, PUT the home document with a banner block (type banner, props.bannerId) and responsive {desktop, tablet, mobile} layout values so the site looks good on phones. If a site already exists and looks broken on mobile, POST /websites/{websiteId}/mobile-friendly then publish. Verify GET /pages/{pageId} document matches the PUT body and preview_url is present. - Verification: Compare the generated request with OpenAPI, then send one request against a test token. ## Create a reusable website template - Goal: Create a reusable website template - Endpoints: GET /website-template-categories, POST /website-templates, GET /website-templates/{templateId} - Required scope: `websites:write` - Data flow: Write a long description of audience and page structure. Either pass website_id to snapshot a site or send content.pages. Publish the template before POST /websites can clone it. - Verification: Compare the generated request with OpenAPI, then send one request against a test token. ## Create an opportunity and send a proposal - Goal: Create an opportunity and send a proposal - Endpoints: POST /opportunities, POST /proposals, POST /proposals/{proposalId}/send - Required scope: `opportunities:write` - Data flow: Create the opportunity, create a draft proposal with items, then send. Sending requires a contact email. Customer sign and pay are not public API operations. - Verification: Compare the generated request with OpenAPI, then send one request against a test token. ## Create and reverse a journal entry - Goal: Create and reverse a journal entry - Endpoints: POST /journal-entries, POST /journal-entries/{id}/reverse - Required scope: `accounting:write` - Data flow: Post balanced IN/OUT lines with source_system MANUAL. Unbalanced lines return 422. Reverse creates an offsetting entry. - Verification: Compare the generated request with OpenAPI, then send one request against a test token. ## Export journal entries - Goal: Export journal entries - Endpoints: GET /journal-entries - Required scope: `accounting:read` - Data flow: Filter by date range and source_system. Paginate with page and per_page. Use source_id to reconcile sales and invoices already available on sales:read. - Verification: Compare the generated request with OpenAPI, then send one request against a test token. ## Add contacts to CRM from a form - Goal: Add contacts to CRM from a form - Endpoints: GET /form-contact-fields, POST /forms, POST /forms/{formId}/publish, POST /forms/{formId}/submissions - Required scope: `forms:write` - Data flow: Load the CRM field catalog, create a form with is_contact_form and field_mapping (firstName, lastName, email), publish, then submit responses. The submission returns contact_id when the contact is created or updated. - Verification: Compare the generated request with OpenAPI, then send one request against a test token. ## Collect a signed waiver - Goal: Collect a signed waiver - Endpoints: POST /forms, POST /forms/{formId}/publish, POST /forms/{formId}/submissions, GET /contacts/{contact}/waiver-status - Required scope: `forms:write` - Data flow: Create a waiver with a signature field and signer_email_field_id. Publish, submit signature plus email (or contact_id), then poll waiver-status and the submission PDF. - Verification: Compare the generated request with OpenAPI, then send one request against a test token. ## Run a survey and read results - Goal: Run a survey and read results - Endpoints: GET /form-field-types, POST /forms, POST /forms/{formId}/publish, POST /forms/{formId}/submissions, GET /surveys, GET /forms/{formId}/survey-results - Required scope: `forms:write` - Data flow: Create a form with is_survey and survey_settings. Use NPS, Likert, ranking, or rating field types from the catalog. Publish, submit responses, then read aggregates. Submit returns 422 after close_at or response_limit. - Verification: Compare the generated request with OpenAPI, then send one request against a test token. ## Collect an HR / employee packet - Goal: Collect an HR / employee packet - Endpoints: POST /forms, POST /forms/{formId}/submissions - Required scope: `forms:write` - Data flow: Create a form with is_employee_form true. Submit with employee_id to attach the packet to staff. Use GET /form-submissions/{id}/pdf when a signed packet PDF is generated. - Verification: Compare the generated request with OpenAPI, then send one request against a test token. ## Create a brand kit and assign it to a website - Goal: Create a brand kit and assign it to a website - Endpoints: POST /brand-kits, POST /brand-kits/{brandKitId}/colors, POST /brand-kits/{brandKitId}/logos, PATCH /websites/{websiteId}/brand-kit - Required scope: `branding:write` - Data flow: Create a kit, add hex colors and a multipart logo, then assign it to a site. Do not call AI generate, virtual-background, or uploaded-font endpoints. - Verification: Compare the generated request with OpenAPI, then send one request against a test token. ## Create a staff meeting - Goal: Create a staff meeting - Endpoints: POST /meetings, GET /meetings/calendar/events - Required scope: `meetings:write` - Data flow: The token owner must have Google Calendar connected. Send title, start_datetime, end_datetime, timezone, and attendees. Do not send with_corey_notetaker. Calendar events are the meetings layer only. - Verification: Compare the generated request with OpenAPI, then send one request against a test token. ## Create a user guide and guided tour - Goal: Create a user guide and guided tour - Endpoints: POST /guides/articles, POST /guides/articles/{articleId}/steps, POST /guides/articles/{articleId}/steps/reorder, POST /guides/articles/{articleId}/steps/{stepId}/still, POST /guides/articles/{articleId}/promote, POST /guides/tours, POST /guide-collections, PUT /guide-collections/{collectionId}/articles - Required scope: `guides:write` - Data flow: Create the article with title, content_type, and difficulty. Add walkthrough steps, upload screenshots, reorder steps, then a tour with at least one step.element and popover.title. Promote tenant drafts to global catalog with target_article_id. Collection assign is additive. Do not call AI, analytics, progress, or capture endpoints. - Verification: Compare the generated request with OpenAPI, then send one request against a test token. ## Create and clone an action plan - Goal: Create and clone an action plan - Endpoints: GET /action-plan-metadata/triggers, GET /action-plan-metadata/steps, POST /action-plan-folders, POST /action-plans, POST /action-plans/{actionPlanId}/clone - Required scope: `action_plans:write` - Data flow: Read trigger and step metadata first. Create a draft plan with name, triggers, and steps. Activate only after the configuration is valid. Do not call AI, API-connector, or simulation endpoints. - Verification: Compare the generated request with OpenAPI, then send one request against a test token. ## Create a notebook and page - Goal: Create a notebook and page - Endpoints: POST /notebooks, POST /notebooks/{notebookUlid}/pages, POST /notebook-pages/{pageUlid}/comments, POST /notebook-tags, PUT /notebook-pages/{pageUlid}/tags - Required scope: `notebooks:write` - Data flow: Create the notebook, add a page, then comment and tag. Identify notebooks and pages by ULID. Do not call AI, Yjs, collab, or import endpoints. - Verification: Compare the generated request with OpenAPI, then send one request against a test token. ## Subscribe to BOSS events - Goal: Subscribe to BOSS events - Endpoints: GET /webhook-events, POST /webhooks, POST /webhooks/{webhookId}/test - Required scope: `webhooks:write` - Data flow: Create an HTTPS endpoint, subscribe with events or *, store the secret returned once, and verify the Signature HMAC. Manage the same subscriptions in Developer API → Webhooks. - Verification: Compare the generated request with OpenAPI, then send one request against a test token. --- # Accounting Canonical URL: https://coreware.com/docs/api/accounting.md API version: v1 These operations are generated from OpenAPI. They are authoritative. ## GET /accounting-categories List chart of accounts Required scope: `accounting:read` Required scope: `accounting:read` - `tree` (query) — Return a parent/child tree instead of a flat page - `type` (query) — Filter by type ```json { "data": [ { "id": "01HXYZ", "name": "Office supplies", "type": "MONEY_OUT", "code": "6100", "is_system": false } ] } ``` ## POST /accounting-categories Create accounting category Required scope: `accounting:write` Required scope: `accounting:write` ```json { "data": { "id": "01HXYZ", "name": "Office supplies", "type": "MONEY_OUT", "code": "6100", "is_system": false } } ``` ## GET /accounting-categories/{id} Get accounting category Required scope: `accounting:read` Required scope: `accounting:read` - `id` (path, required) — Category ULID ```json { "data": { "id": "01HXYZ", "name": "Office supplies", "type": "MONEY_OUT", "code": "6100", "is_system": false } } ``` ## PUT /accounting-categories/{id} Update accounting category Cannot change is_system or system_key. Required scope: `accounting:write` Required scope: `accounting:write` - `id` (path, required) — Category ULID ```json { "data": { "id": "01HXYZ", "name": "Office supplies", "type": "MONEY_OUT", "code": "6100", "is_system": false } } ``` ## DELETE /accounting-categories/{id} Delete unused category Required scope: `accounting:delete` Required scope: `accounting:delete` - `id` (path, required) — Category ULID ```json { "message": "Accounting category deleted successfully" } ``` ## GET /accounting-accounts List bank and cash accounts Required scope: `accounting:read` Required scope: `accounting:read` ```json { "data": [ { "id": "01HACC", "name": "Operating cash", "kind": "CASH", "currency": "USD", "opening_balance": 0 } ] } ``` ## POST /accounting-accounts Create bank or cash account Required scope: `accounting:write` Required scope: `accounting:write` ```json { "data": { "id": "01HACC", "name": "Operating cash", "kind": "CASH", "currency": "USD", "opening_balance": 0 } } ``` ## GET /accounting-accounts/{id} Get accounting account Required scope: `accounting:read` Required scope: `accounting:read` - `id` (path, required) — Account ULID ```json { "data": { "id": "01HACC", "name": "Operating cash", "kind": "CASH", "currency": "USD", "opening_balance": 0 } } ``` ## PUT /accounting-accounts/{id} Update accounting account Required scope: `accounting:write` Required scope: `accounting:write` - `id` (path, required) — Account ULID ```json { "data": { "id": "01HACC", "name": "Operating cash", "kind": "CASH", "currency": "USD", "opening_balance": 0 } } ``` ## DELETE /accounting-accounts/{id} Delete accounting account Required scope: `accounting:delete` Required scope: `accounting:delete` - `id` (path, required) — Account ULID ```json { "message": "Accounting account deleted successfully" } ``` ## POST /accounting-accounts/{id}/deactivate Deactivate accounting account Required scope: `accounting:write` Required scope: `accounting:write` - `id` (path, required) — Account ULID ```json { "data": { "id": "01HACC", "name": "Operating cash", "kind": "CASH", "currency": "USD", "opening_balance": 0, "is_active": false } } ``` ## GET /journal-entries List journal entries Required scope: `accounting:read` Required scope: `accounting:read` - `from` (query) — Start date (YYYY-MM-DD) - `to` (query) — End date (YYYY-MM-DD) - `source_system` (query) — Filter by source_system such as MANUAL, SALE, or EXPENSE ```json { "data": [ { "id": "01HJE1", "date": "2026-09-01", "memo": "Manual adjustment", "source_system": "MANUAL", "source_id": null, "lines": [ { "category_id": "01HXYZ", "amount": 50, "side": "OUT" }, { "category_id": "01HCASH", "amount": 50, "side": "IN" } ] } ] } ``` ## POST /journal-entries Create a balanced manual journal entry Lines must balance (IN equals OUT). Unbalanced entries return 422. source_system is always MANUAL. Period close is not enforced in v1. Required scope: `accounting:write` Required scope: `accounting:write` ```json { "data": { "id": "01HJE1", "date": "2026-09-01", "memo": "Manual adjustment", "source_system": "MANUAL", "source_id": null, "lines": [ { "category_id": "01HXYZ", "amount": 50, "side": "OUT" }, { "category_id": "01HCASH", "amount": 50, "side": "IN" } ] } } ``` ## GET /journal-entries/{id} Get journal entry with lines Required scope: `accounting:read` Required scope: `accounting:read` - `id` (path, required) — Journal entry ULID ```json { "data": { "id": "01HJE1", "date": "2026-09-01", "memo": "Manual adjustment", "source_system": "MANUAL", "source_id": null, "lines": [ { "category_id": "01HXYZ", "amount": 50, "side": "OUT" }, { "category_id": "01HCASH", "amount": 50, "side": "IN" } ] } } ``` ## POST /journal-entries/{id}/reverse Reverse a journal entry Required scope: `accounting:write` Required scope: `accounting:write` - `id` (path, required) — Journal entry ULID ```json { "data": { "id": "01HJE1", "date": "2026-09-01", "memo": "REVERSAL: Manual adjustment", "source_system": "MANUAL", "source_id": null, "lines": [ { "category_id": "01HXYZ", "amount": 50, "side": "OUT" }, { "category_id": "01HCASH", "amount": 50, "side": "IN" } ] } } ``` ## GET /expenses List expenses Required scope: `accounting:read` Required scope: `accounting:read` ```json { "data": [ { "id": 1, "description": "Printer paper", "amount": 24.5 } ] } ``` ## POST /expenses Create expense and journal entry Required scope: `accounting:write` Required scope: `accounting:write` ```json { "data": { "id": 1, "description": "Printer paper", "amount": 24.5, "transaction_id": "01HJE1" } } ``` ## GET /expenses/{id} Get expense Required scope: `accounting:read` Required scope: `accounting:read` - `id` (path, required) — Expense ID ```json { "data": { "id": 1, "description": "Printer paper", "amount": 24.5 } } ``` ## PUT /expenses/{id} Update expense Required scope: `accounting:write` Required scope: `accounting:write` - `id` (path, required) — Expense ID ```json { "data": { "id": 1, "amount": 30 } } ``` ## DELETE /expenses/{id} Delete expense and reverse its journal entry Required scope: `accounting:delete` Required scope: `accounting:delete` - `id` (path, required) — Expense ID ```json { "message": "Expense deleted successfully" } ``` ## GET /accounting/reports/trial-balance Trial balance report Required scope: `accounting:read` Required scope: `accounting:read` - `as_of` (query) — As-of date (YYYY-MM-DD) ```json { "data": [] } ``` ## GET /accounting/reports/general-ledger General ledger report Required scope: `accounting:read` Required scope: `accounting:read` - `from` (query) — Start date - `to` (query) — End date ```json { "data": [] } ``` ## GET /accounting/reports/profit-loss Profit and loss report Required scope: `accounting:read` Required scope: `accounting:read` - `from` (query) — Start date - `to` (query) — End date ```json { "data": [] } ``` ## GET /accounting/reports/balance-sheet Balance sheet report Required scope: `accounting:read` Required scope: `accounting:read` - `as_of` (query) — As-of date ```json { "data": [] } ``` ## GET /accounting/posting-status Read auto-post readiness Read only. Auto-post settings cannot be changed through the public API. Required scope: `accounting:read` Required scope: `accounting:read` ```json { "data": { "auto_post_enabled": false, "shadow_post_enabled": false, "period_close_supported": false } } ``` --- # Action Plans Canonical URL: https://coreware.com/docs/api/action-plans.md API version: v1 These operations are generated from OpenAPI. They are authoritative. ## GET /action-plans List action plans Required scope: `action_plans:read` Required scope: `action_plans:read` - `search` (query) — Search by name - `page` (query) — Page number - `per_page` (query) — Results per page - `status` (query) — Filter by status - `folder_id` (query) — Folder ID or uncategorized - `is_template` (query) — Limit to templates ```json { "data": [ { "id": 12, "name": "Welcome new customers", "description": "Email new contacts after signup.", "status": "draft", "folder_id": 3, "configuration": { "execution_scope": "tenant" }, "tags": [ "onboarding" ], "is_template": false, "triggers": [ { "id": 1, "trigger_type": "contact_created", "configuration": [], "is_active": false } ], "steps": [ { "id": 4, "step_type": "send_email", "name": "Welcome email", "configuration": [] } ] } ], "meta": { "current_page": 1, "last_page": 1, "per_page": 25, "total": 1 } } ``` ## POST /action-plans Create an action plan Required scope: `action_plans:write` Required scope: `action_plans:write` ```json { "data": { "id": 12, "name": "Welcome new customers", "description": "Email new contacts after signup.", "status": "draft", "folder_id": 3, "configuration": { "execution_scope": "tenant" }, "tags": [ "onboarding" ], "is_template": false, "triggers": [ { "id": 1, "trigger_type": "contact_created", "configuration": [], "is_active": false } ], "steps": [ { "id": 4, "step_type": "send_email", "name": "Welcome email", "configuration": [] } ] }, "message": "Action plan created successfully" } ``` ## GET /action-plans/{actionPlanId} Get an action plan Required scope: `action_plans:read` Required scope: `action_plans:read` - `actionPlanId` (path, required) — Action plan ID ```json { "data": { "id": 12, "name": "Welcome new customers", "description": "Email new contacts after signup.", "status": "draft", "folder_id": 3, "configuration": { "execution_scope": "tenant" }, "tags": [ "onboarding" ], "is_template": false, "triggers": [ { "id": 1, "trigger_type": "contact_created", "configuration": [], "is_active": false } ], "steps": [ { "id": 4, "step_type": "send_email", "name": "Welcome email", "configuration": [] } ] } } ``` ## PUT /action-plans/{actionPlanId} Update an action plan Required scope: `action_plans:write` Required scope: `action_plans:write` - `actionPlanId` (path, required) — Action plan ID ```json { "data": { "id": 12, "name": "Welcome new customers", "description": "Email new contacts after signup.", "status": "draft", "folder_id": 3, "configuration": { "execution_scope": "tenant" }, "tags": [ "onboarding" ], "is_template": false, "triggers": [ { "id": 1, "trigger_type": "contact_created", "configuration": [], "is_active": false } ], "steps": [ { "id": 4, "step_type": "send_email", "name": "Welcome email", "configuration": [] } ] } } ``` ## PATCH /action-plans/{actionPlanId} Partially update an action plan Required scope: `action_plans:write` Required scope: `action_plans:write` - `actionPlanId` (path, required) — Action plan ID ```json { "data": { "id": 12, "name": "Welcome new customers", "description": "Email new contacts after signup.", "status": "draft", "folder_id": 3, "configuration": { "execution_scope": "tenant" }, "tags": [ "onboarding" ], "is_template": false, "triggers": [ { "id": 1, "trigger_type": "contact_created", "configuration": [], "is_active": false } ], "steps": [ { "id": 4, "step_type": "send_email", "name": "Welcome email", "configuration": [] } ] } } ``` ## DELETE /action-plans/{actionPlanId} Delete an action plan Required scope: `action_plans:delete` Required scope: `action_plans:delete` - `actionPlanId` (path, required) — Action plan ID ```json { "message": "Action plan deleted successfully" } ``` ## POST /action-plans/{actionPlanId}/activate Activate an action plan Required scope: `action_plans:write` Required scope: `action_plans:write` - `actionPlanId` (path, required) — Action plan ID ```json { "data": { "id": 12, "name": "Welcome new customers", "description": "Email new contacts after signup.", "status": "draft", "folder_id": 3, "configuration": { "execution_scope": "tenant" }, "tags": [ "onboarding" ], "is_template": false, "triggers": [ { "id": 1, "trigger_type": "contact_created", "configuration": [], "is_active": false } ], "steps": [ { "id": 4, "step_type": "send_email", "name": "Welcome email", "configuration": [] } ] } } ``` ## POST /action-plans/{actionPlanId}/pause Pause an action plan Required scope: `action_plans:write` Required scope: `action_plans:write` - `actionPlanId` (path, required) — Action plan ID ```json { "data": { "id": 12, "name": "Welcome new customers", "description": "Email new contacts after signup.", "status": "draft", "folder_id": 3, "configuration": { "execution_scope": "tenant" }, "tags": [ "onboarding" ], "is_template": false, "triggers": [ { "id": 1, "trigger_type": "contact_created", "configuration": [], "is_active": false } ], "steps": [ { "id": 4, "step_type": "send_email", "name": "Welcome email", "configuration": [] } ] } } ``` ## POST /action-plans/{actionPlanId}/clone Clone an action plan Required scope: `action_plans:write` Required scope: `action_plans:write` - `actionPlanId` (path, required) — Action plan ID ```json { "data": { "id": 12, "name": "Welcome new customers", "description": "Email new contacts after signup.", "status": "draft", "folder_id": 3, "configuration": { "execution_scope": "tenant" }, "tags": [ "onboarding" ], "is_template": false, "triggers": [ { "id": 1, "trigger_type": "contact_created", "configuration": [], "is_active": false } ], "steps": [ { "id": 4, "step_type": "send_email", "name": "Welcome email", "configuration": [] } ] } } ``` ## POST /action-plans/{actionPlanId}/move Move an action plan to a folder Required scope: `action_plans:write` Required scope: `action_plans:write` - `actionPlanId` (path, required) — Action plan ID ```json { "data": { "folder_id": 3 } } ``` ## POST /action-plans/{actionPlanId}/execute Start a manual run The plan must be active. Dispatches a background run. Do not use this for arbitrary HTTP connectors. Required scope: `action_plans:write` Required scope: `action_plans:write` - `actionPlanId` (path, required) — Action plan ID ```json { "data": { "id": 88, "run_uuid": "9c1f4a2e-1111-2222-3333-444455556666", "action_plan_id": 12, "status": "pending", "triggered_by": "manual", "is_simulation": false } } ``` ## GET /action-plans/{actionPlanId}/stats Get action plan statistics Required scope: `action_plans:read` Required scope: `action_plans:read` - `actionPlanId` (path, required) — Action plan ID - `period` (query) — 7d, 30d, 90d, or 1y ```json { "data": { "period": "30d", "total_executions": 4, "recent_stats": { "total_runs": 4 } } } ``` ## GET /action-plans/{actionPlanId}/versions List action plan versions Required scope: `action_plans:read` Required scope: `action_plans:read` - `actionPlanId` (path, required) — Action plan ID ```json { "data": [ { "id": 2, "version_number": 1 } ] } ``` ## POST /action-plans/{actionPlanId}/versions/{versionId}/restore Restore an action plan version Required scope: `action_plans:write` Required scope: `action_plans:write` - `actionPlanId` (path, required) — Action plan ID - `versionId` (path, required) — Version ID ```json { "data": { "id": 12, "name": "Welcome new customers", "description": "Email new contacts after signup.", "status": "draft", "folder_id": 3, "configuration": { "execution_scope": "tenant" }, "tags": [ "onboarding" ], "is_template": false, "triggers": [ { "id": 1, "trigger_type": "contact_created", "configuration": [], "is_active": false } ], "steps": [ { "id": 4, "step_type": "send_email", "name": "Welcome email", "configuration": [] } ] } } ``` ## GET /action-plan-folders List folders Required scope: `action_plans:read` Required scope: `action_plans:read` ```json { "data": [ { "id": 3, "name": "Onboarding", "parent_id": null, "plan_count": 1 } ] } ``` ## POST /action-plan-folders Create a folder Required scope: `action_plans:write` Required scope: `action_plans:write` ```json { "data": { "id": 3, "name": "Onboarding", "parent_id": null, "plan_count": 1 } } ``` ## PUT /action-plan-folders/{folderId} Update a folder Required scope: `action_plans:write` Required scope: `action_plans:write` - `folderId` (path, required) — Folder ID ```json { "data": { "id": 3, "name": "Onboarding", "parent_id": null, "plan_count": 1 } } ``` ## DELETE /action-plan-folders/{folderId} Delete a folder Required scope: `action_plans:delete` Required scope: `action_plans:delete` - `folderId` (path, required) — Folder ID ```json { "message": "Folder deleted successfully" } ``` ## GET /action-plan-runs List action plan runs Required scope: `action_plans:read` Required scope: `action_plans:read` - `search` (query) — Search by name - `page` (query) — Page number - `per_page` (query) — Results per page - `status` (query) — Filter by run status - `action_plan_id` (query) — Filter by plan - `triggered_by` (query) — Filter by trigger type - `is_simulation` (query) — Filter simulations ```json { "data": [ { "id": 88, "run_uuid": "9c1f4a2e-1111-2222-3333-444455556666", "action_plan_id": 12, "status": "pending", "triggered_by": "manual", "is_simulation": false } ], "meta": { "current_page": 1, "last_page": 1, "per_page": 25, "total": 1 } } ``` ## GET /action-plan-runs/{runId} Get an action plan run Required scope: `action_plans:read` Required scope: `action_plans:read` - `runId` (path, required) — Run ID ```json { "data": { "id": 88, "run_uuid": "9c1f4a2e-1111-2222-3333-444455556666", "action_plan_id": 12, "status": "pending", "triggered_by": "manual", "is_simulation": false } } ``` ## GET /action-plan-runs/{runId}/logs List run logs Required scope: `action_plans:read` Required scope: `action_plans:read` - `runId` (path, required) — Run ID - `level` (query) — Log level ```json { "data": [], "meta": { "current_page": 1, "last_page": 1, "per_page": 25, "total": 1 } } ``` ## POST /action-plan-runs/{runId}/cancel Cancel a run Required scope: `action_plans:write` Required scope: `action_plans:write` - `runId` (path, required) — Run ID ```json { "data": { "id": 88, "run_uuid": "9c1f4a2e-1111-2222-3333-444455556666", "action_plan_id": 12, "status": "pending", "triggered_by": "manual", "is_simulation": false } } ``` ## POST /action-plan-runs/{runId}/pause Pause a running run Required scope: `action_plans:write` Required scope: `action_plans:write` - `runId` (path, required) — Run ID ```json { "data": { "id": 88, "run_uuid": "9c1f4a2e-1111-2222-3333-444455556666", "action_plan_id": 12, "status": "pending", "triggered_by": "manual", "is_simulation": false } } ``` ## POST /action-plan-runs/{runId}/resume Resume a paused run Required scope: `action_plans:write` Required scope: `action_plans:write` - `runId` (path, required) — Run ID ```json { "data": { "id": 88, "run_uuid": "9c1f4a2e-1111-2222-3333-444455556666", "action_plan_id": 12, "status": "pending", "triggered_by": "manual", "is_simulation": false } } ``` ## POST /action-plan-runs/{runId}/retry Retry a failed run from the beginning Required scope: `action_plans:write` Required scope: `action_plans:write` - `runId` (path, required) — Run ID ```json { "data": { "id": 88, "run_uuid": "9c1f4a2e-1111-2222-3333-444455556666", "action_plan_id": 12, "status": "pending", "triggered_by": "manual", "is_simulation": false } } ``` ## GET /action-plan-metadata/triggers List trigger types Required scope: `action_plans:read` Required scope: `action_plans:read` ```json { "data": { "triggers": [], "total_count": 0 } } ``` ## GET /action-plan-metadata/steps List step types Required scope: `action_plans:read` Required scope: `action_plans:read` ```json { "data": { "steps": [], "total_count": 0 } } ``` ## GET /action-plan-metadata/step-schemas List all step schemas Required scope: `action_plans:read` Required scope: `action_plans:read` ```json { "data": [] } ``` ## GET /action-plan-metadata/triggers/{triggerType}/schema Get a trigger schema Required scope: `action_plans:read` Required scope: `action_plans:read` - `triggerType` (path, required) — Trigger type key ```json { "data": { "type": "contact_created" } } ``` ## GET /action-plan-metadata/steps/{stepType}/schema Get a step schema Required scope: `action_plans:read` Required scope: `action_plans:read` - `stepType` (path, required) — Step type key ```json { "data": [] } ``` ## POST /action-plan-metadata/triggers/{triggerType}/validate Validate a trigger configuration Required scope: `action_plans:write` Required scope: `action_plans:write` - `triggerType` (path, required) — Trigger type key ```json { "data": { "valid": true, "errors": [] } } ``` ## POST /action-plan-metadata/steps/{stepType}/validate Validate a step configuration Required scope: `action_plans:write` Required scope: `action_plans:write` - `stepType` (path, required) — Step type key ```json { "data": { "valid": true, "errors": [] } } ``` --- # Branding Canonical URL: https://coreware.com/docs/api/branding.md API version: v1 These operations are generated from OpenAPI. They are authoritative. ## GET /brand-kits List brand kits Required scope: `branding:read` Required scope: `branding:read` - `search` (query) — Search by name - `is_primary` (query) — Filter to the primary kit - `location_id` (query) — Filter by location ```json { "data": [ { "id": 3, "name": "Coreware brand", "description": "Primary store brand", "is_primary": true, "location_id": null, "color_count": 1, "logo_count": 1, "font_count": 2, "colors": [ { "id": 11, "brand_kit_id": 3, "name": "Navy", "hex_color": "#0B1F3A", "role": "primary" } ], "fonts": [ { "id": 21, "name": "Inter", "role": "body", "source_type": "google_fonts", "font_family": "Inter" } ], "logos": [ { "id": 31, "type": "full_color", "url": "https://cdn.example/logo.png" } ] } ] } ``` ## POST /brand-kits Create a brand kit Required scope: `branding:write` Required scope: `branding:write` ```json { "data": { "id": 3, "name": "Coreware brand", "description": "Primary store brand", "is_primary": true, "location_id": null, "color_count": 1, "logo_count": 1, "font_count": 2, "colors": [ { "id": 11, "brand_kit_id": 3, "name": "Navy", "hex_color": "#0B1F3A", "role": "primary" } ], "fonts": [ { "id": 21, "name": "Inter", "role": "body", "source_type": "google_fonts", "font_family": "Inter" } ], "logos": [ { "id": 31, "type": "full_color", "url": "https://cdn.example/logo.png" } ] } } ``` ## GET /brand-kits/primary Get the primary brand kit Required scope: `branding:read` Required scope: `branding:read` ```json { "data": { "id": 3, "name": "Coreware brand", "description": "Primary store brand", "is_primary": true, "location_id": null, "color_count": 1, "logo_count": 1, "font_count": 2, "colors": [ { "id": 11, "brand_kit_id": 3, "name": "Navy", "hex_color": "#0B1F3A", "role": "primary" } ], "fonts": [ { "id": 21, "name": "Inter", "role": "body", "source_type": "google_fonts", "font_family": "Inter" } ], "logos": [ { "id": 31, "type": "full_color", "url": "https://cdn.example/logo.png" } ] } } ``` ## GET /brand-kits/{brandKitId} Get a brand kit Required scope: `branding:read` Required scope: `branding:read` - `brandKitId` (path, required) — Brand kit ID ```json { "data": { "id": 3, "name": "Coreware brand", "description": "Primary store brand", "is_primary": true, "location_id": null, "color_count": 1, "logo_count": 1, "font_count": 2, "colors": [ { "id": 11, "brand_kit_id": 3, "name": "Navy", "hex_color": "#0B1F3A", "role": "primary" } ], "fonts": [ { "id": 21, "name": "Inter", "role": "body", "source_type": "google_fonts", "font_family": "Inter" } ], "logos": [ { "id": 31, "type": "full_color", "url": "https://cdn.example/logo.png" } ] } } ``` ## PUT /brand-kits/{brandKitId} Update a brand kit Required scope: `branding:write` Required scope: `branding:write` - `brandKitId` (path, required) — Brand kit ID ```json { "data": { "id": 3, "name": "Coreware brand", "description": "Primary store brand", "is_primary": true, "location_id": null, "color_count": 1, "logo_count": 1, "font_count": 2, "colors": [ { "id": 11, "brand_kit_id": 3, "name": "Navy", "hex_color": "#0B1F3A", "role": "primary" } ], "fonts": [ { "id": 21, "name": "Inter", "role": "body", "source_type": "google_fonts", "font_family": "Inter" } ], "logos": [ { "id": 31, "type": "full_color", "url": "https://cdn.example/logo.png" } ] } } ``` ## DELETE /brand-kits/{brandKitId} Delete a brand kit Blocked when the kit is primary or assigned to websites or blog settings. Required scope: `branding:delete` Required scope: `branding:delete` - `brandKitId` (path, required) — Brand kit ID ```json { "message": "Brand kit deleted successfully" } ``` ## POST /brand-kits/{brandKitId}/set-primary Set a brand kit as primary Required scope: `branding:write` Required scope: `branding:write` - `brandKitId` (path, required) — Brand kit ID ```json { "data": { "id": 3, "name": "Coreware brand", "description": "Primary store brand", "is_primary": true, "location_id": null, "color_count": 1, "logo_count": 1, "font_count": 2, "colors": [ { "id": 11, "brand_kit_id": 3, "name": "Navy", "hex_color": "#0B1F3A", "role": "primary" } ], "fonts": [ { "id": 21, "name": "Inter", "role": "body", "source_type": "google_fonts", "font_family": "Inter" } ], "logos": [ { "id": 31, "type": "full_color", "url": "https://cdn.example/logo.png" } ] } } ``` ## POST /brand-kits/{brandKitId}/duplicate Duplicate a brand kit Required scope: `branding:write` Required scope: `branding:write` - `brandKitId` (path, required) — Brand kit ID ```json { "data": { "id": 3, "name": "Coreware brand", "description": "Primary store brand", "is_primary": true, "location_id": null, "color_count": 1, "logo_count": 1, "font_count": 2, "colors": [ { "id": 11, "brand_kit_id": 3, "name": "Navy", "hex_color": "#0B1F3A", "role": "primary" } ], "fonts": [ { "id": 21, "name": "Inter", "role": "body", "source_type": "google_fonts", "font_family": "Inter" } ], "logos": [ { "id": 31, "type": "full_color", "url": "https://cdn.example/logo.png" } ] } } ``` ## GET /brand-kits/{brandKitId}/colors List brand colors Required scope: `branding:read` Required scope: `branding:read` - `brandKitId` (path, required) — Brand kit ID ```json { "data": [ { "id": 11, "brand_kit_id": 3, "name": "Navy", "hex_color": "#0B1F3A", "role": "primary" } ] } ``` ## POST /brand-kits/{brandKitId}/colors Add a brand color Required scope: `branding:write` Required scope: `branding:write` - `brandKitId` (path, required) — Brand kit ID ```json { "data": { "id": 11, "brand_kit_id": 3, "name": "Navy", "hex_color": "#0B1F3A", "role": "primary" } } ``` ## PUT /brand-kits/{brandKitId}/colors/{colorId} Update a brand color Required scope: `branding:write` Required scope: `branding:write` - `brandKitId` (path, required) — Brand kit ID - `colorId` (path, required) — Brand color ID ```json { "data": { "id": 11, "brand_kit_id": 3, "name": "Navy", "hex_color": "#0B1F3A", "role": "primary" } } ``` ## DELETE /brand-kits/{brandKitId}/colors/{colorId} Delete a brand color Required scope: `branding:delete` Required scope: `branding:delete` - `brandKitId` (path, required) — Brand kit ID - `colorId` (path, required) — Brand color ID ```json { "message": "Brand color deleted successfully" } ``` ## GET /brand-kits/{brandKitId}/color-categories List color categories Required scope: `branding:read` Required scope: `branding:read` - `brandKitId` (path, required) — Brand kit ID ```json { "data": [ { "id": 4, "name": "Print" } ] } ``` ## POST /brand-kits/{brandKitId}/color-categories Create color categories Send `categories` or a single `name` / `description`. Required scope: `branding:write` Required scope: `branding:write` - `brandKitId` (path, required) — Brand kit ID ```json { "data": [ { "id": 4, "name": "Print" } ] } ``` ## PUT /brand-kits/{brandKitId}/color-categories/{categoryId} Update a color category Required scope: `branding:write` Required scope: `branding:write` - `brandKitId` (path, required) — Brand kit ID - `categoryId` (path, required) — Color category ID ```json { "data": { "id": 4, "name": "Digital" } } ``` ## DELETE /brand-kits/{brandKitId}/color-categories/{categoryId} Delete a color category Required scope: `branding:delete` Required scope: `branding:delete` - `brandKitId` (path, required) — Brand kit ID - `categoryId` (path, required) — Color category ID ```json { "message": "Color category deleted successfully" } ``` ## GET /brand-kits/{brandKitId}/fonts List brand fonts Required scope: `branding:read` Required scope: `branding:read` - `brandKitId` (path, required) — Brand kit ID ```json { "data": [ { "id": 21, "name": "Inter", "role": "body", "source_type": "google_fonts", "font_family": "Inter" } ] } ``` ## POST /brand-kits/{brandKitId}/fonts Add a brand font Google Fonts, Adobe Fonts, and system fonts only. Uploaded font files are rejected. Required scope: `branding:write` Required scope: `branding:write` - `brandKitId` (path, required) — Brand kit ID ```json { "data": { "id": 21, "name": "Inter", "role": "body", "source_type": "google_fonts", "font_family": "Inter" } } ``` ## PUT /brand-kits/{brandKitId}/fonts/{fontId} Update a brand font Required scope: `branding:write` Required scope: `branding:write` - `brandKitId` (path, required) — Brand kit ID - `fontId` (path, required) — Brand font ID ```json { "data": { "id": 21, "name": "Inter", "role": "body", "source_type": "google_fonts", "font_family": "Inter" } } ``` ## DELETE /brand-kits/{brandKitId}/fonts/{fontId} Delete a brand font Required scope: `branding:delete` Required scope: `branding:delete` - `brandKitId` (path, required) — Brand kit ID - `fontId` (path, required) — Brand font ID ```json { "message": "Brand font deleted successfully" } ``` ## GET /brand-kits/{brandKitId}/logos List brand logos Required scope: `branding:read` Required scope: `branding:read` - `brandKitId` (path, required) — Brand kit ID ```json { "data": [ { "id": 31, "type": "full_color", "url": "https://cdn.example/logo.png" } ] } ``` ## POST /brand-kits/{brandKitId}/logos Upload a brand logo Multipart `file` plus `type`. Uses branding:write. There is no separate media:write scope. AI generate is not available. Required scope: `branding:write` Required scope: `branding:write` - `brandKitId` (path, required) — Brand kit ID ```json { "data": { "id": 31, "type": "full_color", "url": "https://cdn.example/logo.png" } } ``` ## GET /brand-kits/{brandKitId}/logos/{logoId} Get a brand logo Required scope: `branding:read` Required scope: `branding:read` - `brandKitId` (path, required) — Brand kit ID - `logoId` (path, required) — Brand logo ID ```json { "data": { "id": 31, "type": "full_color", "url": "https://cdn.example/logo.png" } } ``` ## DELETE /brand-kits/{brandKitId}/logos/{logoId} Delete a brand logo Required scope: `branding:delete` Required scope: `branding:delete` - `brandKitId` (path, required) — Brand kit ID - `logoId` (path, required) — Brand logo ID ```json { "message": "Brand logo deleted successfully" } ``` ## GET /websites/{websiteId}/brand-kit Get the brand kit assigned to a website When brand_kit_id is null, brand_kit resolves to the primary kit and resolved_from_primary is true. Required scope: `branding:read` Required scope: `branding:read` - `websiteId` (path, required) — Website ID ```json { "data": { "website_id": 1, "brand_kit_id": 3, "resolved_from_primary": false, "brand_kit": { "id": 3, "name": "Coreware brand", "description": "Primary store brand", "is_primary": true, "location_id": null, "color_count": 1, "logo_count": 1, "font_count": 2, "colors": [ { "id": 11, "brand_kit_id": 3, "name": "Navy", "hex_color": "#0B1F3A", "role": "primary" } ], "fonts": [ { "id": 21, "name": "Inter", "role": "body", "source_type": "google_fonts", "font_family": "Inter" } ], "logos": [ { "id": 31, "type": "full_color", "url": "https://cdn.example/logo.png" } ] } } } ``` ## PATCH /websites/{websiteId}/brand-kit Assign a brand kit to a website Send brand_kit_id or null to use the primary kit. Required scope: `branding:write` Required scope: `branding:write` - `websiteId` (path, required) — Website ID ```json { "data": { "website_id": 1, "brand_kit_id": 3, "resolved_from_primary": false, "brand_kit": { "id": 3, "name": "Coreware brand", "description": "Primary store brand", "is_primary": true, "location_id": null, "color_count": 1, "logo_count": 1, "font_count": 2, "colors": [ { "id": 11, "brand_kit_id": 3, "name": "Navy", "hex_color": "#0B1F3A", "role": "primary" } ], "fonts": [ { "id": 21, "name": "Inter", "role": "body", "source_type": "google_fonts", "font_family": "Inter" } ], "logos": [ { "id": 31, "type": "full_color", "url": "https://cdn.example/logo.png" } ] } } } ``` --- # Contacts Canonical URL: https://coreware.com/docs/api/contacts.md API version: v1 These operations are generated from OpenAPI. They are authoritative. ## GET /contact-types List contact types Required scope: `contacts:read` Required scope: `contacts:read` ```json { "data": [ { "id": 1, "name": "Customer", "is_system": true }, { "id": 2, "name": "Prospect", "is_system": true } ] } ``` ## GET /contacts List contacts Required scope: `contacts:read` Required scope: `contacts:read` - `search` (query) — Search by name, email, phone, or account number - `entity_type` (query) — Filter by entity type - `contact_type_id` (query) — Filter by a single contact type ID (recommended) - `contact_type_ids` (query) — Filter by multiple contact type IDs (comma-separated or array) - `is_active` (query) — Filter by active status - `include` (query) — Use include=full to return the complete contact payload for each row - `page` (query) — Page number - `per_page` (query) — Results per page (1–100) - `sort_by` (query) — Sort field - `sort_order` (query) — Sort direction ```json { "data": [ { "id": 42, "entity_type": "Individual", "is_active": true, "qualification_status": "prospect", "first_name": "Jane", "last_name": "Doe", "business_name": null, "name": "Jane Doe", "email": "jane@example.com", "phone": "+15551234567", "account_number": "A-1001", "contact_types": [ { "id": 1, "name": "Customer" } ], "created_at": "2026-07-01T12:00:00.000000Z", "updated_at": "2026-07-01T12:00:00.000000Z" } ], "meta": { "current_page": 1, "last_page": 1, "per_page": 25, "total": 1 } } ``` ## POST /contacts Create contact Required scope: `contacts:write` Required scope: `contacts:write` ```json { "data": { "id": 42, "entity_type": "Individual", "is_active": true, "qualification_status": "prospect", "account_number": "A-1001", "name": "Jane Doe", "person": { "id": 7, "contact_id": 42, "first_name": "Jane", "middle_name": null, "last_name": "Doe", "title": null, "suffix": null, "nickname": null, "timezone": "America/New_York", "name": "Jane Doe" }, "business": null, "emails": [ { "id": 1, "contact_id": 42, "email": "jane@example.com", "is_primary": true } ], "phones": [ { "id": 1, "contact_id": 42, "phone": "5551234567", "country_code": "US", "extension": null, "is_primary": true } ], "addresses": [ { "id": 1, "contact_id": 42, "address_line_1": "123 Main St", "address_line_2": null, "city": "Austin", "region": null, "state": "TX", "postal_code": "78701", "country": "US", "is_primary": true, "address_type": "billing" } ], "tags": [ { "id": 3, "name": "VIP", "color": "#0D6D6B", "scope": "contact" } ], "contact_types": [ { "id": 1, "name": "Customer" } ], "created_at": "2026-07-01T12:00:00.000000Z", "updated_at": "2026-07-01T12:00:00.000000Z" } } ``` ## GET /contacts/{contact} Get full contact Required scope: `contacts:read` Required scope: `contacts:read` - `contact` (path, required) — Contact ID - `include` (query) — Optional related data: subscriptions,memberships,purchases,bookings ```json { "data": { "id": 42, "entity_type": "Individual", "is_active": true, "qualification_status": "prospect", "account_number": "A-1001", "name": "Jane Doe", "person": { "id": 7, "contact_id": 42, "first_name": "Jane", "middle_name": null, "last_name": "Doe", "title": null, "suffix": null, "nickname": null, "timezone": "America/New_York", "name": "Jane Doe" }, "business": null, "emails": [ { "id": 1, "contact_id": 42, "email": "jane@example.com", "is_primary": true } ], "phones": [ { "id": 1, "contact_id": 42, "phone": "5551234567", "country_code": "US", "extension": null, "is_primary": true } ], "addresses": [ { "id": 1, "contact_id": 42, "address_line_1": "123 Main St", "address_line_2": null, "city": "Austin", "region": null, "state": "TX", "postal_code": "78701", "country": "US", "is_primary": true, "address_type": "billing" } ], "tags": [ { "id": 3, "name": "VIP", "color": "#0D6D6B", "scope": "contact" } ], "contact_types": [ { "id": 1, "name": "Customer" } ], "created_at": "2026-07-01T12:00:00.000000Z", "updated_at": "2026-07-01T12:00:00.000000Z" } } ``` ## PUT /contacts/{contact} Update contact Required scope: `contacts:write` Required scope: `contacts:write` - `contact` (path, required) — Contact ID ```json { "data": { "id": 42, "entity_type": "Individual", "is_active": true, "qualification_status": "prospect", "account_number": "A-1001", "name": "Jane Doe", "person": { "id": 7, "contact_id": 42, "first_name": "Jane", "middle_name": null, "last_name": "Doe", "title": null, "suffix": null, "nickname": null, "timezone": "America/New_York", "name": "Jane Doe" }, "business": null, "emails": [ { "id": 1, "contact_id": 42, "email": "jane@example.com", "is_primary": true } ], "phones": [ { "id": 1, "contact_id": 42, "phone": "5551234567", "country_code": "US", "extension": null, "is_primary": true } ], "addresses": [ { "id": 1, "contact_id": 42, "address_line_1": "123 Main St", "address_line_2": null, "city": "Austin", "region": null, "state": "TX", "postal_code": "78701", "country": "US", "is_primary": true, "address_type": "billing" } ], "tags": [ { "id": 3, "name": "VIP", "color": "#0D6D6B", "scope": "contact" } ], "contact_types": [ { "id": 1, "name": "Customer" } ], "created_at": "2026-07-01T12:00:00.000000Z", "updated_at": "2026-07-01T12:00:00.000000Z" } } ``` ## DELETE /contacts/{contact} Delete contact Required scope: `contacts:delete` Required scope: `contacts:delete` - `contact` (path, required) — Contact ID ```json { "message": "Contact deleted" } ``` ## GET /contacts/{contact}/tags List contact tags Required scope: `contacts:read` Required scope: `contacts:read` - `contact` (path, required) — Contact ID ```json { "data": [ { "id": 3, "name": "VIP", "color": "#0D6D6B" } ] } ``` ## POST /contacts/{contact}/tags Attach contact tags Required scope: `contacts:write` Required scope: `contacts:write` - `contact` (path, required) — Contact ID ```json { "data": [ { "id": 3, "name": "VIP", "color": "#0D6D6B" } ] } ``` ## DELETE /contacts/{contact}/tags/{tag} Detach contact tag Required scope: `contacts:delete` Required scope: `contacts:delete` - `contact` (path, required) — Contact ID - `tag` (path, required) — Tag ID ```json { "message": "Tag removed" } ``` ## GET /contacts/{contact}/touchpoints List contact notes/touchpoints Required scope: `contacts:read` Required scope: `contacts:read` - `contact` (path, required) — Contact ID - `per_page` (query) — Results per page ```json { "data": [ { "id": 9, "contact_id": 42, "notes": "Followed up about renewal", "date": "2026-07-01", "is_follow_up_required": true, "follow_up_date": "2026-07-08" } ], "meta": { "current_page": 1, "last_page": 1, "per_page": 50, "total": 1 } } ``` ## POST /contacts/{contact}/touchpoints Create contact touchpoint Required scope: `contacts:write` Required scope: `contacts:write` - `contact` (path, required) — Contact ID ```json { "data": { "id": 9, "contact_id": 42, "notes": "Followed up about renewal", "date": "2026-07-01", "is_follow_up_required": true, "follow_up_date": "2026-07-08" } } ``` ## GET /contacts/{contact}/subscriptions List contact subscriptions Required scope: `contacts:read` Required scope: `contacts:read` - `contact` (path, required) — Contact ID ```json { "data": [ { "id": 15, "contact_id": 42, "status": "active", "recurring_charge_amount": 29.99, "next_payment_date": "2026-08-01" } ] } ``` ## GET /contacts/{contact}/memberships List contact memberships Required scope: `contacts:read` Required scope: `contacts:read` - `contact` (path, required) — Contact ID ```json { "data": [ { "id": 7, "contact_id": 42, "membership_id": 2, "status": "active", "enrolled_at": "2026-01-15T10:00:00.000000Z" } ] } ``` --- # Facilities Canonical URL: https://coreware.com/docs/api/facilities.md API version: v1 These operations are generated from OpenAPI. They are authoritative. ## GET /resources List booking resources Resources include staff, equipment, and facility. There is no separate Facility model. Required scope: `scheduling:read` Required scope: `scheduling:read` - `type` (query) — Resource type - `location_id` (query) — Filter by location when resource-location links exist - `active` (query) — Filter active flag - `search` (query) — Search name/description - `per_page` (query) — Results per page ```json { "data": [ { "id": 12, "name": "Court A", "resource_type": "facility", "default_concurrent_capacity": 1, "is_active": true, "timezone": "America/Chicago" } ], "meta": { "current_page": 1, "last_page": 1, "per_page": 50, "total": 1 } } ``` ## POST /resources Create a booking resource Creates a staff, equipment, or facility resource. weekly_availability uses weekday 0-6 and HH:MM windows. Required scope: `scheduling:write` Required scope: `scheduling:write` ```json { "data": { "id": 12, "name": "Court A", "resource_type": "facility", "default_concurrent_capacity": 1, "is_active": true, "timezone": "America/Chicago", "weekly_availability": [ { "weekday": 1, "start_time": "09:00:00", "end_time": "17:00:00" } ], "description": "Indoor court" }, "message": "Resource created successfully" } ``` ## GET /resources/{resource} Get resource detail Required scope: `scheduling:read` Required scope: `scheduling:read` - `resource` (path, required) — Resource ID ```json { "data": { "id": 12, "name": "Court A", "resource_type": "facility", "default_concurrent_capacity": 1, "is_active": true, "timezone": "America/Chicago", "weekly_availability": [ { "weekday": 1, "start_time": "09:00:00", "end_time": "17:00:00" } ], "description": "Indoor court" } } ``` ## PATCH /resources/{resource} Update a booking resource Required scope: `scheduling:write` Required scope: `scheduling:write` - `resource` (path, required) — Resource ID ```json { "data": { "id": 12, "name": "Court A", "resource_type": "facility", "default_concurrent_capacity": 1, "is_active": true, "timezone": "America/Chicago", "weekly_availability": [ { "weekday": 1, "start_time": "09:00:00", "end_time": "17:00:00" } ], "description": "Indoor court" }, "message": "Resource updated successfully" } ``` ## DELETE /resources/{resource} Delete a booking resource Required scope: `scheduling:delete` Required scope: `scheduling:delete` - `resource` (path, required) — Resource ID ```json { "message": "Resource deleted successfully" } ``` ## GET /resources/{resource}/availability Get resource weekly/open windows Required scope: `scheduling:read` Required scope: `scheduling:read` - `resource` (path, required) — Resource ID - `from` (query) — Start date YYYY-MM-DD - `to` (query) — End date YYYY-MM-DD ```json { "data": { "resource_id": 12, "resource_type": "facility", "days": [ { "date": "2026-08-15", "weekday": 6, "is_available": true, "windows": [ { "start_time": "09:00:00", "end_time": "17:00:00" } ] } ] } } ``` ## GET /facilities List facilities Convenience alias of GET /resources?type=facility. Required scope: `scheduling:read` Required scope: `scheduling:read` - `location_id` (query) — Filter by location - `active` (query) — Filter active flag - `search` (query) — Search name/description - `per_page` (query) — Results per page ```json { "data": [ { "id": 12, "name": "Court A", "resource_type": "facility", "default_concurrent_capacity": 1, "is_active": true, "timezone": "America/Chicago" } ], "meta": { "current_page": 1, "last_page": 1, "per_page": 50, "total": 1 } } ``` ## POST /facilities Create a facility Creates a resource with resource_type forced to facility. Same body as POST /resources, except resource_type is ignored. Required scope: `scheduling:write` Required scope: `scheduling:write` ```json { "data": { "id": 12, "name": "Court A", "resource_type": "facility", "default_concurrent_capacity": 1, "is_active": true, "timezone": "America/Chicago", "weekly_availability": [ { "weekday": 1, "start_time": "09:00:00", "end_time": "17:00:00" } ], "description": "Indoor court" }, "message": "Resource created successfully" } ``` ## GET /facilities/{resource} Get facility detail Required scope: `scheduling:read` Required scope: `scheduling:read` - `resource` (path, required) — Facility resource ID ```json { "data": { "id": 12, "name": "Court A", "resource_type": "facility", "default_concurrent_capacity": 1, "is_active": true, "timezone": "America/Chicago", "weekly_availability": [ { "weekday": 1, "start_time": "09:00:00", "end_time": "17:00:00" } ], "description": "Indoor court" } } ``` ## PATCH /facilities/{resource} Update a facility Updates a facility resource. resource_type stays facility. Required scope: `scheduling:write` Required scope: `scheduling:write` - `resource` (path, required) — Facility resource ID ```json { "data": { "id": 12, "name": "Court A", "resource_type": "facility", "default_concurrent_capacity": 1, "is_active": true, "timezone": "America/Chicago", "weekly_availability": [ { "weekday": 1, "start_time": "09:00:00", "end_time": "17:00:00" } ], "description": "Indoor court" }, "message": "Resource updated successfully" } ``` ## DELETE /facilities/{resource} Delete a facility Required scope: `scheduling:delete` Required scope: `scheduling:delete` - `resource` (path, required) — Facility resource ID ```json { "message": "Resource deleted successfully" } ``` --- # FFL Canonical URL: https://coreware.com/docs/api/ffl.md API version: v1 These operations are generated from OpenAPI. They are authoritative. ## GET /form-4473s List Form 4473s Required scope: `ffl:read` Required scope: `ffl:read` - `location_id` (query) — Filter by location - `status` (query) — Filter by status - `search` (query) — Search display ID or transferee name - `per_page` (query) — Results per page ```json { "data": [ { "id": 447301, "display_id": "A-1001", "location_id": 1, "status": "open", "last_name": "DOE", "first_name": "JANE" } ], "meta": { "current_page": 1, "last_page": 1, "per_page": 25, "total": 1 } } ``` ## POST /form-4473s Create a Form 4473 Creates a 2020 Form 4473 at the location. Optional fields is a map of ATF version columns such as last_name_9. Required scope: `ffl:write` Required scope: `ffl:write` ```json { "data": { "id": 447301, "display_id": "A-1001", "location_id": 1, "status": "open", "last_name": "DOE", "first_name": "JANE" } } ``` ## GET /form-4473s/{form4473} Get a Form 4473 including ATF fields Required scope: `ffl:read` Required scope: `ffl:read` - `form4473` (path, required) — Form 4473 ID ```json { "data": { "id": 447301, "display_id": "A-1001", "location_id": 1, "status": "open", "last_name": "DOE", "first_name": "JANE", "fields": { "last_name_9": "DOE" } } } ``` ## PUT /form-4473s/{form4473} Update Form 4473 ATF fields Required scope: `ffl:write` Required scope: `ffl:write` - `form4473` (path, required) — Form 4473 ID ```json { "data": { "id": 447301, "display_id": "A-1001", "location_id": 1, "status": "open", "last_name": "DOE", "first_name": "JANE" } } ``` ## PATCH /form-4473s/{form4473} Partially update Form 4473 ATF fields Required scope: `ffl:write` Required scope: `ffl:write` - `form4473` (path, required) — Form 4473 ID ```json { "data": { "id": 447301, "display_id": "A-1001", "location_id": 1, "status": "open", "last_name": "DOE", "first_name": "JANE" } } ``` ## POST /form-4473s/{form4473}/close Close a Form 4473 Required scope: `ffl:write` Required scope: `ffl:write` - `form4473` (path, required) — Form 4473 ID ```json { "data": { "id": 447301, "display_id": "A-1001", "location_id": 1, "status": "closed", "last_name": "DOE", "first_name": "JANE" } } ``` ## POST /form-4473s/{form4473}/reopen Reopen a closed Form 4473 Required scope: `ffl:write` Required scope: `ffl:write` - `form4473` (path, required) — Form 4473 ID ```json { "data": { "id": 447301, "display_id": "A-1001", "location_id": 1, "status": "open", "last_name": "DOE", "first_name": "JANE" } } ``` ## POST /form-4473s/{form4473}/invalidate Invalidate a Form 4473 Required scope: `ffl:write` Required scope: `ffl:write` - `form4473` (path, required) — Form 4473 ID ```json { "data": { "id": 447301, "display_id": "A-1001", "location_id": 1, "status": "invalid", "last_name": "DOE", "first_name": "JANE" } } ``` ## GET /form-4473s/{form4473}/pdf Get a presigned Form 4473 PDF URL Required scope: `ffl:read` Required scope: `ffl:read` - `form4473` (path, required) — Form 4473 ID ```json { "data": { "url": "https://example.test/form.pdf", "filename": "Form 4473 - A-1001.pdf" } } ``` ## GET /bound-books List bound book records Required scope: `ffl:read` Required scope: `ffl:read` - `location_id` (query) — Filter by location - `serial_number` (query) — Search serial - `in_stock` (query) — Filter in-stock firearms - `disposed` (query) — Filter disposed firearms - `per_page` (query) — Results per page ```json { "data": [ { "id": 12, "serial_number": "ABC123", "manufacturer_name": "Glock", "in_stock": true, "disposed": false } ], "meta": { "current_page": 1, "last_page": 1, "per_page": 25, "total": 1 } } ``` ## GET /bound-books/{boundBook} Get a bound book record Required scope: `ffl:read` Required scope: `ffl:read` - `boundBook` (path, required) — Bound book record ID ```json { "data": { "id": 12, "serial_number": "ABC123", "manufacturer_name": "Glock", "in_stock": true, "disposed": false } } ``` ## POST /bound-books/acquire Acquire a firearm into the bound book Required scope: `ffl:write` Required scope: `ffl:write` ```json { "data": { "id": 12, "serial_number": "ABC123", "manufacturer_name": "Glock", "in_stock": true, "disposed": false } } ``` ## POST /bound-books/{boundBook}/dispose Dispose a bound book firearm Required scope: `ffl:write` Required scope: `ffl:write` - `boundBook` (path, required) — Bound book record ID ```json { "data": { "id": 12, "serial_number": "ABC123", "manufacturer_name": "Glock", "in_stock": false, "disposed": true } } ``` ## GET /bound-book-books List bound book books Required scope: `ffl:read` Required scope: `ffl:read` - `location_id` (query) — Filter by location - `per_page` (query) — Results per page ```json { "data": [ { "id": 3, "code": "A", "location_id": 1 } ], "meta": { "current_page": 1, "last_page": 1, "per_page": 25, "total": 1 } } ``` ## GET /firearm-transfers List firearm transfers Required scope: `ffl:read` Required scope: `ffl:read` - `status` (query) — Filter by status ID - `type` (query) — ffl or private_party - `location_id` (query) — Filter by sale location - `per_page` (query) — Results per page ```json { "data": [ { "id": 8, "sale_id": 55, "status": 1, "status_name": "New", "type": "ffl", "firearms": [ { "serial_number": "XYZ99", "type": "Pistol" } ] } ], "meta": { "current_page": 1, "last_page": 1, "per_page": 25, "total": 1 } } ``` ## POST /firearm-transfers Create a firearm transfer Creates the transfer sale, optional 4473, and bound-book acquisition when acquire_now is true. Required scope: `ffl:write` Required scope: `ffl:write` ```json { "data": { "id": 8, "sale_id": 55, "status": 1, "status_name": "New", "type": "ffl", "firearms": [ { "serial_number": "XYZ99", "type": "Pistol" } ] } } ``` ## GET /firearm-transfers/{firearmTransfer} Get a firearm transfer Required scope: `ffl:read` Required scope: `ffl:read` - `firearmTransfer` (path, required) — Firearm transfer ID ```json { "data": { "id": 8, "sale_id": 55, "status": 1, "status_name": "New", "type": "ffl", "firearms": [ { "serial_number": "XYZ99", "type": "Pistol" } ] } } ``` ## POST /firearm-transfers/{firearmTransfer}/status Change firearm transfer status Required scope: `ffl:write` Required scope: `ffl:write` - `firearmTransfer` (path, required) — Firearm transfer ID ```json { "data": { "id": 8, "sale_id": 55, "status": 2, "status_name": "New", "type": "ffl", "firearms": [ { "serial_number": "XYZ99", "type": "Pistol" } ] } } ``` ## GET /firearm-transfer-statuses List firearm transfer statuses Required scope: `ffl:read` Required scope: `ffl:read` ```json { "data": [ { "id": 1, "name": "New", "is_new": true } ] } ``` ## GET /form-3310s List Form 3310 records Required scope: `ffl:read` Required scope: `ffl:read` - `form_type` (query) — Filter by form type - `search` (query) — Search transferee or FFL number - `per_page` (query) — Results per page ```json { "data": [ { "id": 3, "form_type": "3310.4", "transferee_name": "Jane Doe" } ], "meta": { "current_page": 1, "last_page": 1, "per_page": 25, "total": 1 } } ``` ## GET /form-3310s/{form3310} Get a Form 3310 including ATF fields Required scope: `ffl:read` Required scope: `ffl:read` - `form3310` (path, required) — Form 3310 ID ```json { "data": { "id": 3, "fields": { "transferee_name_4": "Jane Doe" } } } ``` --- # Forms Canonical URL: https://coreware.com/docs/api/forms.md API version: v1 These operations are generated from OpenAPI. They are authoritative. ## GET /forms List forms Filter with kind=contact|waiver|employee|survey|standard, status, category_id, or search. Required scope: `forms:read` Required scope: `forms:read` - `status` (query) — Filter by status - `kind` (query) — Filter by form kind - `is_waiver` (query) — Shortcut for kind=waiver - `is_contact_form` (query) — Shortcut for kind=contact - `is_employee_form` (query) — Shortcut for kind=employee - `is_survey` (query) — Shortcut for kind=survey - `category_id` (query) — Filter by form category - `search` (query) — Search title, slug, or form id ```json { "data": [ { "id": "form_abc123", "title": "New member intake", "slug": "new-member-intake", "status": "published", "kinds": [ "contact" ], "is_contact_form": true, "is_waiver": false, "is_employee_form": false, "is_survey": false, "public_url": "https://example.test/form/new-member-intake", "embed_url": "https://example.test/embed/popup-form/new-member-intake", "contact_form_settings": { "behavior": "create_or_update", "field_mapping": { "firstName": "field_first", "lastName": "field_last", "email": "field_email" } }, "fields": [ { "id": "field_first", "type": "text", "name": "first_name", "label": "First name", "placeholder": "Ada", "required": true }, { "id": "field_last", "type": "text", "name": "last_name", "label": "Last name", "placeholder": "Lovelace", "required": true }, { "id": "field_email", "type": "email", "name": "email", "label": "Email", "placeholder": "you@example.com", "required": true } ] } ], "meta": { "current_page": 1, "last_page": 1, "per_page": 25, "total": 1 } } ``` ## POST /forms Create form Create a standard, CRM contact, waiver, HR/employee, or survey form. Field trees are native builder JSON. Input fields require label; name, placeholder, required, and content persist. Waivers require a signature field unless require_signature is false. Contact forms persist field_mapping and create/update CRM contacts on submit. Surveys persist survey_settings (allow_anonymous, close_at, response_limit, show_results_to_respondents) and accept NPS, Likert, ranking, and rating fields from GET /form-field-types. Required scope: `forms:write` Required scope: `forms:write` ```json { "data": { "id": "form_abc123", "title": "New member intake", "slug": "new-member-intake", "status": "published", "kinds": [ "contact" ], "is_contact_form": true, "is_waiver": false, "is_employee_form": false, "is_survey": false, "public_url": "https://example.test/form/new-member-intake", "embed_url": "https://example.test/embed/popup-form/new-member-intake", "contact_form_settings": { "behavior": "create_or_update", "field_mapping": { "firstName": "field_first", "lastName": "field_last", "email": "field_email" } }, "fields": [ { "id": "field_first", "type": "text", "name": "first_name", "label": "First name", "placeholder": "Ada", "required": true }, { "id": "field_last", "type": "text", "name": "last_name", "label": "Last name", "placeholder": "Lovelace", "required": true }, { "id": "field_email", "type": "email", "name": "email", "label": "Email", "placeholder": "you@example.com", "required": true } ] } } ``` ## GET /waivers List waiver forms Required scope: `forms:read` Required scope: `forms:read` ```json { "data": [ { "id": "form_waiver_1", "title": "Range waiver", "kinds": [ "waiver", "contact" ], "is_waiver": true, "is_contact_form": true, "waiver_settings": { "require_signature": true, "signer_email_field_id": "field_email", "expiration_type": "days", "validity_days": 365 } } ] } ``` ## GET /surveys List survey forms Shortcut for GET /forms?kind=survey. Includes NPS, Likert, ranking, and other survey field types. Required scope: `forms:read` Required scope: `forms:read` - `status` (query) — Filter by status - `category_id` (query) — Filter by form category - `search` (query) — Search title, slug, or form id ```json { "data": [ { "id": "form_survey_1", "title": "Visitor satisfaction", "kinds": [ "survey" ], "is_survey": true } ] } ``` ## GET /form-field-types List builder field types Catalog of native form field types for contact intake, waivers, surveys, and layout. Input types require label on write. Required scope: `forms:read` Required scope: `forms:read` ```json { "data": [ { "type": "nps", "label": "NPS score", "category": "survey", "requires_label": true }, { "type": "signature", "label": "Signature", "category": "advanced", "requires_label": true } ] } ``` ## GET /forms/{formId}/survey-results Get survey aggregates Returns question-level distribution, NPS breakdown, ranking averages, and daily response counts. 422 when the form is not a survey. Required scope: `forms:read` Required scope: `forms:read` - `formId` (path, required) — Public form id (form_id) - `date_from` (query) — Created at from (ISO date) - `date_to` (query) — Created at to (ISO date) ```json { "data": { "form_id": "form_survey_1", "total_responses": 1, "questions": [ { "field_id": "field_nps", "label": "How likely are you to recommend us?", "type": "nps", "response_count": 1, "average_score": 9, "nps_breakdown": { "detractors": 0, "passives": 0, "promoters": 1, "nps_score": 100 } } ], "daily_responses": [] } } ``` ## GET /forms/{formId} Get form definition Returns the full camelCase field tree including label, name, placeholder, required, and content. Required scope: `forms:read` Required scope: `forms:read` - `formId` (path, required) — Public form id (form_id) ```json { "data": { "id": "form_abc123", "title": "New member intake", "slug": "new-member-intake", "status": "published", "kinds": [ "contact" ], "is_contact_form": true, "is_waiver": false, "is_employee_form": false, "is_survey": false, "public_url": "https://example.test/form/new-member-intake", "embed_url": "https://example.test/embed/popup-form/new-member-intake", "contact_form_settings": { "behavior": "create_or_update", "field_mapping": { "firstName": "field_first", "lastName": "field_last", "email": "field_email" } }, "fields": [ { "id": "field_first", "type": "text", "name": "first_name", "label": "First name", "placeholder": "Ada", "required": true }, { "id": "field_last", "type": "text", "name": "last_name", "label": "Last name", "placeholder": "Lovelace", "required": true }, { "id": "field_email", "type": "email", "name": "email", "label": "Email", "placeholder": "you@example.com", "required": true } ] } } ``` ## PUT /forms/{formId} Update form Sending fields replaces the entire tree and must include label on each input field. A GET then PUT of the returned fields must round-trip labels. Incomplete id+type-only fields return 422 instead of silently stripping properties. Required scope: `forms:write` Required scope: `forms:write` - `formId` (path, required) — Public form id (form_id) ```json { "data": { "id": "form_abc123", "title": "New member intake", "slug": "new-member-intake", "status": "published", "kinds": [ "contact" ], "is_contact_form": true, "is_waiver": false, "is_employee_form": false, "is_survey": false, "public_url": "https://example.test/form/new-member-intake", "embed_url": "https://example.test/embed/popup-form/new-member-intake", "contact_form_settings": { "behavior": "create_or_update", "field_mapping": { "firstName": "field_first", "lastName": "field_last", "email": "field_email" } }, "fields": [ { "id": "field_first", "type": "text", "name": "first_name", "label": "First name", "placeholder": "Ada", "required": true }, { "id": "field_last", "type": "text", "name": "last_name", "label": "Last name", "placeholder": "Lovelace", "required": true }, { "id": "field_email", "type": "email", "name": "email", "label": "Email", "placeholder": "you@example.com", "required": true } ] } } ``` ## PATCH /forms/{formId} Partially update form Omitted keys are left unchanged. If fields is sent, the full camelCase tree is required; input fields without label return 422. Required scope: `forms:write` Required scope: `forms:write` - `formId` (path, required) — Public form id (form_id) ```json { "data": { "id": "form_abc123", "title": "New member intake", "slug": "new-member-intake", "status": "published", "kinds": [ "contact" ], "is_contact_form": true, "is_waiver": false, "is_employee_form": false, "is_survey": false, "public_url": "https://example.test/form/new-member-intake", "embed_url": "https://example.test/embed/popup-form/new-member-intake", "contact_form_settings": { "behavior": "create_or_update", "field_mapping": { "firstName": "field_first", "lastName": "field_last", "email": "field_email" } }, "fields": [ { "id": "field_first", "type": "text", "name": "first_name", "label": "First name", "placeholder": "Ada", "required": true }, { "id": "field_last", "type": "text", "name": "last_name", "label": "Last name", "placeholder": "Lovelace", "required": true }, { "id": "field_email", "type": "email", "name": "email", "label": "Email", "placeholder": "you@example.com", "required": true } ] } } ``` ## DELETE /forms/{formId} Delete form Required scope: `forms:delete` Required scope: `forms:delete` - `formId` (path, required) — Public form id (form_id) ```json { "message": "Form deleted successfully" } ``` ## POST /forms/{formId}/publish Publish form Required scope: `forms:write` Required scope: `forms:write` - `formId` (path, required) — Public form id (form_id) ```json { "data": { "id": "form_abc123", "title": "New member intake", "slug": "new-member-intake", "status": "published", "kinds": [ "contact" ], "is_contact_form": true, "is_waiver": false, "is_employee_form": false, "is_survey": false, "public_url": "https://example.test/form/new-member-intake", "embed_url": "https://example.test/embed/popup-form/new-member-intake", "contact_form_settings": { "behavior": "create_or_update", "field_mapping": { "firstName": "field_first", "lastName": "field_last", "email": "field_email" } }, "fields": [ { "id": "field_first", "type": "text", "name": "first_name", "label": "First name", "placeholder": "Ada", "required": true }, { "id": "field_last", "type": "text", "name": "last_name", "label": "Last name", "placeholder": "Lovelace", "required": true }, { "id": "field_email", "type": "email", "name": "email", "label": "Email", "placeholder": "you@example.com", "required": true } ] } } ``` ## POST /forms/{formId}/unpublish Unpublish form Required scope: `forms:write` Required scope: `forms:write` - `formId` (path, required) — Public form id (form_id) ```json { "data": { "id": "form_abc123", "title": "New member intake", "slug": "new-member-intake", "status": "draft", "kinds": [ "contact" ], "is_contact_form": true, "is_waiver": false, "is_employee_form": false, "is_survey": false, "public_url": "https://example.test/form/new-member-intake", "embed_url": "https://example.test/embed/popup-form/new-member-intake", "contact_form_settings": { "behavior": "create_or_update", "field_mapping": { "firstName": "field_first", "lastName": "field_last", "email": "field_email" } }, "fields": [ { "id": "field_first", "type": "text", "name": "first_name", "label": "First name", "placeholder": "Ada", "required": true }, { "id": "field_last", "type": "text", "name": "last_name", "label": "Last name", "placeholder": "Lovelace", "required": true }, { "id": "field_email", "type": "email", "name": "email", "label": "Email", "placeholder": "you@example.com", "required": true } ] } } ``` ## POST /forms/{formId}/archive Archive form Required scope: `forms:write` Required scope: `forms:write` - `formId` (path, required) — Public form id (form_id) ```json { "data": { "id": "form_abc123", "title": "New member intake", "slug": "new-member-intake", "status": "archived", "kinds": [ "contact" ], "is_contact_form": true, "is_waiver": false, "is_employee_form": false, "is_survey": false, "public_url": "https://example.test/form/new-member-intake", "embed_url": "https://example.test/embed/popup-form/new-member-intake", "contact_form_settings": { "behavior": "create_or_update", "field_mapping": { "firstName": "field_first", "lastName": "field_last", "email": "field_email" } }, "fields": [ { "id": "field_first", "type": "text", "name": "first_name", "label": "First name", "placeholder": "Ada", "required": true }, { "id": "field_last", "type": "text", "name": "last_name", "label": "Last name", "placeholder": "Lovelace", "required": true }, { "id": "field_email", "type": "email", "name": "email", "label": "Email", "placeholder": "you@example.com", "required": true } ] } } ``` ## POST /forms/{formId}/duplicate Duplicate form Required scope: `forms:write` Required scope: `forms:write` - `formId` (path, required) — Public form id (form_id) ```json { "data": { "id": "form_copy", "title": "New member intake", "slug": "new-member-intake", "status": "draft", "kinds": [ "contact" ], "is_contact_form": true, "is_waiver": false, "is_employee_form": false, "is_survey": false, "public_url": "https://example.test/form/new-member-intake", "embed_url": "https://example.test/embed/popup-form/new-member-intake", "contact_form_settings": { "behavior": "create_or_update", "field_mapping": { "firstName": "field_first", "lastName": "field_last", "email": "field_email" } }, "fields": [ { "id": "field_first", "type": "text", "name": "first_name", "label": "First name", "placeholder": "Ada", "required": true }, { "id": "field_last", "type": "text", "name": "last_name", "label": "Last name", "placeholder": "Lovelace", "required": true }, { "id": "field_email", "type": "email", "name": "email", "label": "Email", "placeholder": "you@example.com", "required": true } ] } } ``` ## GET /forms/{formId}/prefill Prefill a form from a CRM contact Required scope: `forms:read` Required scope: `forms:read` - `formId` (path, required) — Public form id (form_id) - `contact_id` (query, required) — Contact to map into field responses ```json { "data": { "contact_id": 42, "responses": { "field_first": "Jane", "field_email": "jane@example.com" } } } ``` ## GET /form-categories List form categories Required scope: `forms:read` Required scope: `forms:read` ```json { "data": [ { "id": 1, "name": "General", "description": null } ] } ``` ## GET /form-contact-fields CRM field catalog for contact form mapping Use standard keys such as firstName, lastName, email, phone, and address in contact_form_settings.field_mapping. Required scope: `forms:read` Required scope: `forms:read` ```json { "data": { "standard": [ { "key": "email", "label": "Email", "category": "standard" } ], "behaviors": [ "create_or_update", "always_create", "update_only", "link_existing" ] } } ``` ## GET /forms/{formId}/submissions List form submissions Required scope: `forms:read` Required scope: `forms:read` - `formId` (path, required) — Public form id (form_id) - `contact_id` (query) — Filter by contact - `compliance_status` (query) — Filter waiver compliance status - `from` (query) — Submitted at from (ISO date) - `to` (query) — Submitted at to (ISO date) ```json { "data": [ { "id": "sub_1", "form_id": "form_abc123", "status": "completed", "contact_id": 42, "signer_name": "Jane Doe", "signer_email": "jane@example.com" } ] } ``` ## POST /forms/{formId}/submissions Submit a published form Contact forms create or update CRM contacts from field_mapping. Waivers require signer email unless contact_id is provided. HR forms accept employee_id. Surveys return 422 after close_at or response_limit. Required scope: `forms:write` Required scope: `forms:write` - `formId` (path, required) — Public form id (form_id) ```json { "data": { "id": "sub_1", "form_id": "form_abc123", "status": "completed", "contact_id": 42, "signer_name": "Jane Doe", "signer_email": "jane@example.com" } } ``` ## GET /form-submissions/{submissionId} Get form submission Required scope: `forms:read` Required scope: `forms:read` - `submissionId` (path, required) — Submission id ```json { "data": { "id": "sub_1", "form_id": "form_abc123", "status": "completed", "contact_id": 42, "signer_name": "Jane Doe", "signer_email": "jane@example.com", "entries": [] } } ``` ## DELETE /form-submissions/{submissionId} Delete form submission Required scope: `forms:delete` Required scope: `forms:delete` - `submissionId` (path, required) — Submission id ```json { "message": "Form submission deleted successfully" } ``` ## GET /form-submissions/{submissionId}/pdf Get waiver or HR packet PDF status Required scope: `forms:read` Required scope: `forms:read` - `submissionId` (path, required) — Submission id ```json { "data": { "id": "sub_1", "pdf_status": "pending", "pdf_url": null } } ``` ## POST /forms/{formId}/files Upload a form field file Required scope: `forms:write` Required scope: `forms:write` - `formId` (path, required) — Public form id (form_id) ```json { "data": { "url": "https://cdn.example/file.pdf", "file_name": "file.pdf" } } ``` ## GET /contacts/{contact}/form-submissions List submissions for a contact Required scope: `forms:read` Required scope: `forms:read` - `contact` (path, required) — Contact id ```json { "data": [ { "id": "sub_1", "form_id": "form_abc123", "status": "completed", "contact_id": 42, "signer_name": "Jane Doe", "signer_email": "jane@example.com" } ] } ``` ## GET /contacts/{contact}/waivers List waiver submissions for a contact Required scope: `forms:read` Required scope: `forms:read` - `contact` (path, required) — Contact id ```json { "data": [ { "id": "sub_1", "form_id": "form_waiver_1", "status": "completed", "contact_id": 42, "signer_name": "Jane Doe", "signer_email": "jane@example.com" } ] } ``` ## GET /contacts/{contact}/waiver-status Check whether a contact has a valid waiver Required scope: `forms:read` Required scope: `forms:read` - `contact` (path, required) — Contact id - `form_id` (query, required) — Public form id of the waiver ```json { "data": { "contact_id": 42, "form_id": "form_waiver_1", "valid": false, "submission_id": null } } ``` --- # Invoices Canonical URL: https://coreware.com/docs/api/invoices.md API version: v1 These operations are generated from OpenAPI. They are authoritative. ## GET /invoices List customer invoices Required scope: `sales:read` Required scope: `sales:read` - `contact_id` (query) — Filter by contact ID - `location_id` (query) — Filter by location ID - `sale_id` (query) — Filter by related sale ID - `status` (query) — Filter by invoice status - `search` (query) — Search invoice number, customer PO, or INV-{id} - `aging_bucket` (query) — Filter open balances by aging bucket - `from` (query) — Invoice date from (YYYY-MM-DD) - `to` (query) — Invoice date to (YYYY-MM-DD) - `balance_due_only` (query) — Only invoices with balance_due > 0 - `per_page` (query) — Results per page ```json { "data": [ { "id": 501, "sale_id": 1001, "contact_id": 42, "location_id": 1, "invoice_date": "2026-07-01", "due_date": "2026-07-15", "subtotal": 49.99, "tax_amount": 0, "total": 49.99, "amount_paid": 0, "balance_due": 49.99, "status": "open", "customer_po": null } ], "meta": { "current_page": 1, "last_page": 1, "per_page": 50, "total": 1 } } ``` ## POST /invoices Create a customer invoice Creates an open customer invoice with line items. Totals are calculated from details. This is AR invoicing, not a POS sale. Supplier invoices are not available. Required scope: `sales:write` Required scope: `sales:write` ```json { "data": { "id": 501, "sale_id": 1001, "contact_id": 42, "location_id": 1, "invoice_date": "2026-07-01", "due_date": "2026-07-15", "subtotal": 49.99, "tax_amount": 0, "total": 49.99, "amount_paid": 0, "balance_due": 49.99, "status": "open", "customer_po": null, "notes": null, "custom_terms": null, "line_items": [ { "id": 1, "item_id": 88, "description": "Demo Product", "quantity": 1, "unit_price": 49.99, "total": 49.99 } ], "payments": [] } } ``` ## GET /invoices/aging Get invoice aging buckets Required scope: `sales:read` Required scope: `sales:read` - `location_id` (query) — Filter by location ID ```json { "data": { "buckets": { "current": { "count": 1, "balance": 125 }, "1_30": { "count": 0, "balance": 0 }, "31_60": { "count": 0, "balance": 0 }, "61_90": { "count": 0, "balance": 0 }, "over_90": { "count": 0, "balance": 0 } }, "totals": { "count": 1, "balance": 125 }, "revenue": { "amount_paid": 0 } } } ``` ## GET /invoices/{invoice} Get invoice with line items and payments Required scope: `sales:read` Required scope: `sales:read` - `invoice` (path, required) — Invoice ID ```json { "data": { "id": 501, "sale_id": 1001, "contact_id": 42, "location_id": 1, "invoice_date": "2026-07-01", "due_date": "2026-07-15", "subtotal": 49.99, "tax_amount": 0, "total": 49.99, "amount_paid": 0, "balance_due": 49.99, "status": "open", "customer_po": null, "notes": null, "custom_terms": null, "line_items": [ { "id": 1, "item_id": 88, "description": "Demo Product", "quantity": 1, "unit_price": 49.99, "total": 49.99 } ], "payments": [] } } ``` ## PUT /invoices/{invoice} Update a customer invoice Paid invoices cannot be edited. Sending details replaces line items that are omitted. Required scope: `sales:write` Required scope: `sales:write` - `invoice` (path, required) — Invoice ID ```json { "data": { "id": 501, "sale_id": 1001, "contact_id": 42, "location_id": 1, "invoice_date": "2026-07-01", "due_date": "2026-07-15", "subtotal": 49.99, "tax_amount": 0, "total": 49.99, "amount_paid": 0, "balance_due": 49.99, "status": "open", "customer_po": null, "notes": null, "custom_terms": null, "line_items": [ { "id": 1, "item_id": 88, "description": "Demo Product", "quantity": 1, "unit_price": 49.99, "total": 49.99 } ], "payments": [] } } ``` ## PATCH /invoices/{invoice} Partially update a customer invoice Required scope: `sales:write` Required scope: `sales:write` - `invoice` (path, required) — Invoice ID ```json { "data": { "id": 501, "sale_id": 1001, "contact_id": 42, "location_id": 1, "invoice_date": "2026-07-01", "due_date": "2026-07-15", "subtotal": 49.99, "tax_amount": 0, "total": 49.99, "amount_paid": 0, "balance_due": 49.99, "status": "open", "customer_po": null, "notes": null, "custom_terms": null, "line_items": [ { "id": 1, "item_id": 88, "description": "Demo Product", "quantity": 1, "unit_price": 49.99, "total": 49.99 } ], "payments": [] } } ``` ## DELETE /invoices/{invoice} Delete a customer invoice Required scope: `sales:delete` Required scope: `sales:delete` - `invoice` (path, required) — Invoice ID ```json { "message": "Invoice deleted successfully" } ``` ## GET /invoices/{invoice}/payments List invoice payments Required scope: `sales:read` Required scope: `sales:read` - `invoice` (path, required) — Invoice ID ```json { "data": [ { "id": 88, "payment_date": "2026-09-14T15:00:00.000000Z", "payment_type": "cash", "amount": 125, "reference": "CHK-1001", "notes": null, "transaction_id": null } ] } ``` ## POST /invoices/{invoice}/pay Record an offline invoice payment Records cash, check, or other offline payment against the invoice balance. This does not charge a card, capture hosted fields, or vault a payment method. Required scope: `sales:write` Required scope: `sales:write` - `invoice` (path, required) — Invoice ID ```json { "data": { "id": 501, "sale_id": 1001, "contact_id": 42, "location_id": 1, "invoice_date": "2026-07-01", "due_date": "2026-07-15", "subtotal": 49.99, "tax_amount": 0, "total": 49.99, "amount_paid": 125, "balance_due": 0, "status": "paid", "customer_po": null, "notes": null, "custom_terms": null, "line_items": [ { "id": 1, "item_id": 88, "description": "Demo Product", "quantity": 1, "unit_price": 49.99, "total": 49.99 } ], "payments": [ { "id": 88, "payment_date": "2026-09-14T15:00:00.000000Z", "payment_type": "cash", "amount": 125, "reference": "CHK-1001", "notes": null, "transaction_id": null } ] } } ``` ## POST /invoices/{invoice}/email Email a customer invoice Required scope: `sales:write` Required scope: `sales:write` - `invoice` (path, required) — Invoice ID ```json { "data": { "id": 501, "sale_id": 1001, "contact_id": 42, "location_id": 1, "invoice_date": "2026-07-01", "due_date": "2026-07-15", "subtotal": 49.99, "tax_amount": 0, "total": 49.99, "amount_paid": 0, "balance_due": 49.99, "status": "open", "customer_po": null, "notes": null, "custom_terms": null, "line_items": [ { "id": 1, "item_id": 88, "description": "Demo Product", "quantity": 1, "unit_price": 49.99, "total": 49.99 } ], "payments": [] }, "meta": { "email_send": { "allowed": [ "billing@example.com" ], "blocked": [] } } } ``` ## POST /invoices/{invoice}/payment-link Create an invoice payment link Creates a hosted payment URL for invoices with a balance due. Card capture, hosted fields, vault, and BIN lookup stay outside this API. Required scope: `sales:write` Required scope: `sales:write` - `invoice` (path, required) — Invoice ID ```json { "data": { "payment_url": "https://example.test/pay/inv_abc", "expires_at": "2026-09-21T00:00:00.000000Z" } } ``` ## GET /contacts/{contact}/invoices List invoices for a contact Required scope: `sales:read` Required scope: `sales:read` - `contact` (path, required) — Contact ID - `contact_id` (query) — Filter by contact ID - `location_id` (query) — Filter by location ID - `sale_id` (query) — Filter by related sale ID - `status` (query) — Filter by invoice status - `search` (query) — Search invoice number, customer PO, or INV-{id} - `aging_bucket` (query) — Filter open balances by aging bucket - `from` (query) — Invoice date from (YYYY-MM-DD) - `to` (query) — Invoice date to (YYYY-MM-DD) - `balance_due_only` (query) — Only invoices with balance_due > 0 - `per_page` (query) — Results per page ```json { "data": [ { "id": 501, "sale_id": 1001, "contact_id": 42, "location_id": 1, "invoice_date": "2026-07-01", "due_date": "2026-07-15", "subtotal": 49.99, "tax_amount": 0, "total": 49.99, "amount_paid": 0, "balance_due": 49.99, "status": "open", "customer_po": null } ], "meta": { "current_page": 1, "last_page": 1, "per_page": 50, "total": 1 } } ``` ## GET /invoice-terms List invoice payment terms Required scope: `sales:read` Required scope: `sales:read` ```json { "data": [ { "id": 3, "term_id": 3, "name": "Net 30", "description": "Due in 30 days", "days_due": 30, "discount_days": null, "discount_amount": null } ] } ``` ## POST /invoice-terms Create invoice payment terms Required scope: `sales:write` Required scope: `sales:write` ```json { "data": { "id": 3, "term_id": 3, "name": "Net 30", "description": "Due in 30 days", "days_due": 30, "discount_days": null, "discount_amount": null } } ``` ## GET /invoice-terms/{term} Get invoice payment terms Required scope: `sales:read` Required scope: `sales:read` - `term` (path, required) — Term ID ```json { "data": { "id": 3, "term_id": 3, "name": "Net 30", "description": "Due in 30 days", "days_due": 30, "discount_days": null, "discount_amount": null } } ``` ## PUT /invoice-terms/{term} Update invoice payment terms Required scope: `sales:write` Required scope: `sales:write` - `term` (path, required) — Term ID ```json { "data": { "id": 3, "term_id": 3, "name": "Net 15", "description": "Due in 30 days", "days_due": 15, "discount_days": null, "discount_amount": null } } ``` ## PATCH /invoice-terms/{term} Partially update invoice payment terms Required scope: `sales:write` Required scope: `sales:write` - `term` (path, required) — Term ID ```json { "data": { "id": 3, "term_id": 3, "name": "Net 30", "description": "Due in 30 days", "days_due": 30, "discount_days": null, "discount_amount": null } } ``` ## DELETE /invoice-terms/{term} Delete invoice payment terms Required scope: `sales:delete` Required scope: `sales:delete` - `term` (path, required) — Term ID ```json { "message": "Invoice term deleted successfully" } ``` --- # Knowledge Canonical URL: https://coreware.com/docs/api/knowledge.md API version: v1 These operations are generated from OpenAPI. They are authoritative. ## GET /knowledge/library List Knowledge Studio library entries Returns paginated Knowledge Studio Q&A entries. Primary tenant only. Required scope: `knowledge:read` Required scope: `knowledge:read` - `q` (query) — Search question or answer text - `topic` (query) — Filter by topic - `review_status` (query) — Filter by review status - `audience` (query) — Filter by audience - `industry_id` (query) — Filter by industry ID - `page` (query) — Page number - `per_page` (query) — Results per page (10–100) ```json { "data": [ { "id": "studio_demo_ab12cd", "question": "What identification is required for a firearm transfer?", "topic": "Compliance", "subtopic": "4473", "effective_status": "current", "review_status": "needs_review", "document_id": 88, "citation_count": 1 } ], "meta": { "current_page": 1, "last_page": 1, "per_page": 25, "total": 1, "question_count": 1, "topics": { "Compliance": 1 }, "pack_key": "firearms_ffl" } } ``` ## POST /knowledge/library Create a Knowledge Studio library entry Required scope: `knowledge:write` Required scope: `knowledge:write` ```json { "data": { "id": "studio_demo_ab12cd", "document_id": 88, "has_override": true, "review_status": "needs_review", "question": "What identification is required for a firearm transfer?", "answer": "Collect a valid government-issued photo ID and complete the required transfer form.", "topic": "Compliance", "subtopic": "4473", "effective_status": "current", "document_kind": "legal", "caution": "", "conflict_note": "", "question_variants": [ "What ID is needed for a 4473?" ], "legal_basis": [], "sources": [], "pack_key": "firearms_ffl", "industry_ids": [], "sub_industry_ids": [], "audience": "pack" } } ``` ## GET /knowledge/library/{entry} Get a Knowledge Studio library entry Required scope: `knowledge:read` Required scope: `knowledge:read` - `entry` (path, required) — Library entry ID or slug ```json { "data": { "id": "studio_demo_ab12cd", "document_id": 88, "has_override": true, "review_status": "needs_review", "question": "What identification is required for a firearm transfer?", "answer": "Collect a valid government-issued photo ID and complete the required transfer form.", "topic": "Compliance", "subtopic": "4473", "effective_status": "current", "document_kind": "legal", "caution": "", "conflict_note": "", "question_variants": [ "What ID is needed for a 4473?" ], "legal_basis": [], "sources": [], "pack_key": "firearms_ffl", "industry_ids": [], "sub_industry_ids": [], "audience": "pack" } } ``` ## PUT /knowledge/library/{entry} Update a Knowledge Studio library entry Required scope: `knowledge:write` Required scope: `knowledge:write` - `entry` (path, required) — Library entry ID or slug ```json { "data": { "id": "studio_demo_ab12cd", "document_id": 88, "has_override": true, "review_status": "needs_review", "question": "What identification is required for a firearm transfer?", "answer": "Collect a valid government-issued photo ID and complete the required transfer form.", "topic": "Compliance", "subtopic": "4473", "effective_status": "current", "document_kind": "legal", "caution": "", "conflict_note": "", "question_variants": [ "What ID is needed for a 4473?" ], "legal_basis": [], "sources": [], "pack_key": "firearms_ffl", "industry_ids": [], "sub_industry_ids": [], "audience": "pack" } } ``` ## POST /knowledge/library/{entry}/publish Publish a Knowledge Studio library entry Required scope: `knowledge:write` Required scope: `knowledge:write` - `entry` (path, required) — Library entry ID or slug ```json { "data": { "id": "studio_demo_ab12cd", "document_id": 88, "has_override": true, "review_status": "needs_review", "question": "What identification is required for a firearm transfer?", "answer": "Collect a valid government-issued photo ID and complete the required transfer form.", "topic": "Compliance", "subtopic": "4473", "effective_status": "current", "document_kind": "legal", "caution": "", "conflict_note": "", "question_variants": [ "What ID is needed for a 4473?" ], "legal_basis": [], "sources": [], "pack_key": "firearms_ffl", "industry_ids": [], "sub_industry_ids": [], "audience": "pack" } } ``` ## POST /knowledge/library/bulk-status Bulk update library review status Required scope: `knowledge:write` Required scope: `knowledge:write` ```json { "data": { "updated": 1, "failed": 0 } } ``` ## POST /knowledge/library/import/preview Preview a Knowledge Studio library import Accepts text or an uploaded file. Returns the parsed entries without writing them. Required scope: `knowledge:write` Required scope: `knowledge:write` ```json { "data": { "entries": 1, "errors": [] } } ``` ## POST /knowledge/library/import Import Knowledge Studio library entries Required scope: `knowledge:write` Required scope: `knowledge:write` ```json { "data": { "imported": 1, "skipped": 0 } } ``` ## GET /knowledge/sources List Knowledge Studio sources Required scope: `knowledge:read` Required scope: `knowledge:read` ```json { "data": [ { "id": 12, "source_key": "example_guidance", "name": "Example Guidance", "adapter_key": "generic_public", "pack_key": "platform", "base_url": "https://example.gov/guidance", "citation_visibility": "internal_only", "review_status": "needs_review", "crawl_enabled": true, "industry_ids": [], "sub_industry_ids": [] } ] } ``` ## POST /knowledge/sources Create a Knowledge Studio source Required scope: `knowledge:write` Required scope: `knowledge:write` ```json { "data": { "id": 12, "source_key": "example_guidance", "name": "Example Guidance", "adapter_key": "generic_public", "pack_key": "platform", "base_url": "https://example.gov/guidance", "citation_visibility": "internal_only", "review_status": "needs_review", "crawl_enabled": true, "industry_ids": [], "sub_industry_ids": [] } } ``` ## PUT /knowledge/sources/{source} Update a Knowledge Studio source Required scope: `knowledge:write` Required scope: `knowledge:write` - `source` (path, required) — Source ID ```json { "data": { "id": 12, "source_key": "example_guidance", "name": "Example Guidance", "adapter_key": "generic_public", "pack_key": "platform", "base_url": "https://example.gov/guidance", "citation_visibility": "internal_only", "review_status": "needs_review", "crawl_enabled": true, "industry_ids": [], "sub_industry_ids": [] } } ``` ## POST /knowledge/sources/bulk-status Bulk update source review status Required scope: `knowledge:write` Required scope: `knowledge:write` ```json { "data": { "updated": 1, "failed": 0 } } ``` ## POST /knowledge/sources/{source}/discover Discover documents from a Knowledge Studio source Required scope: `knowledge:write` Required scope: `knowledge:write` - `source` (path, required) — Source ID ```json { "data": { "discovered": 2, "stored": 0, "excluded": 0, "urls": [ { "url": "https://example.gov/page", "status": "preview" } ] } } ``` ## POST /knowledge/documents/{document}/publish Publish a crawled Knowledge Studio document Required scope: `knowledge:write` Required scope: `knowledge:write` - `document` (path, required) — Document ID ```json { "data": { "id": 88, "slug": "studio_demo_ab12cd", "review_status": "published" } } ``` --- # Meetings Canonical URL: https://coreware.com/docs/api/meetings.md API version: v1 These operations are generated from OpenAPI. They are authoritative. ## GET /meetings List meetings Required scope: `meetings:read` Required scope: `meetings:read` - `scope` (query) — upcoming, past, or all - `search` (query) — Search by title - `hr` (query) — Limit to internal employee meetings - `page` (query) — Page number - `per_page` (query) — Results per page ```json { "data": [ { "id": 40, "title": "Quarterly review", "description": null, "starts_at": "2026-09-12T15:00:00Z", "ends_at": "2026-09-12T16:00:00Z", "timezone": "UTC", "meet_link": "https://meet.google.com/hub-create", "lifecycle_status": "upcoming", "can_join_meet": false, "attendee_count": 1, "attendees": [ { "person_id": null, "contact_id": null, "email": "guest@example.com", "name": "Guest", "role": "attendee" } ], "is_hr": false, "is_organizer": true, "can_update": true, "can_cancel": true, "can_leave": false } ], "meta": { "current_page": 1, "last_page": 1, "per_page": 25, "total": 1 } } ``` ## POST /meetings Create a meeting The token owner is the organizer and must have a connected Google Calendar. with_corey_notetaker is not accepted. Required scope: `meetings:write` Required scope: `meetings:write` ```json { "data": { "id": 40, "title": "Quarterly review", "description": null, "starts_at": "2026-09-12T15:00:00Z", "ends_at": "2026-09-12T16:00:00Z", "timezone": "UTC", "meet_link": "https://meet.google.com/hub-create", "lifecycle_status": "upcoming", "can_join_meet": false, "attendee_count": 1, "attendees": [ { "person_id": null, "contact_id": null, "email": "guest@example.com", "name": "Guest", "role": "attendee" } ], "is_hr": false, "is_organizer": true, "can_update": true, "can_cancel": true, "can_leave": false } } ``` ## GET /meetings/calendar/events List meetings-layer calendar events Returns only the meetings layer. Google, scheduling, touchpoint, and reminder layers are not included. Required scope: `meetings:read` Required scope: `meetings:read` - `start` (query, required) — Range start - `end` (query, required) — Range end - `timezone` (query) — IANA timezone ```json { "data": { "events": [ { "id": "meeting:40", "layer": "meetings", "meeting_id": 40 } ], "layers": [ "meetings" ] } } ``` ## POST /meetings/calendar/availability Suggest attendee availability Required scope: `meetings:write` Required scope: `meetings:write` ```json { "data": { "suggestions": [] } } ``` ## GET /meetings/{meetingId} Get a meeting Required scope: `meetings:read` Required scope: `meetings:read` - `meetingId` (path, required) — Meeting ID ```json { "data": { "id": 40, "title": "Quarterly review", "description": null, "starts_at": "2026-09-12T15:00:00Z", "ends_at": "2026-09-12T16:00:00Z", "timezone": "UTC", "meet_link": "https://meet.google.com/hub-create", "lifecycle_status": "upcoming", "can_join_meet": false, "attendee_count": 1, "attendees": [ { "person_id": null, "contact_id": null, "email": "guest@example.com", "name": "Guest", "role": "attendee" } ], "is_hr": false, "is_organizer": true, "can_update": true, "can_cancel": true, "can_leave": false } } ``` ## PATCH /meetings/{meetingId} Update a meeting Required scope: `meetings:write` Required scope: `meetings:write` - `meetingId` (path, required) — Meeting ID ```json { "data": { "id": 40, "title": "Quarterly review", "description": null, "starts_at": "2026-09-12T15:00:00Z", "ends_at": "2026-09-12T16:00:00Z", "timezone": "UTC", "meet_link": "https://meet.google.com/hub-create", "lifecycle_status": "upcoming", "can_join_meet": false, "attendee_count": 1, "attendees": [ { "person_id": null, "contact_id": null, "email": "guest@example.com", "name": "Guest", "role": "attendee" } ], "is_hr": false, "is_organizer": true, "can_update": true, "can_cancel": true, "can_leave": false } } ``` ## POST /meetings/{meetingId}/duplicate Duplicate a meeting one week later Required scope: `meetings:write` Required scope: `meetings:write` - `meetingId` (path, required) — Meeting ID ```json { "data": { "id": 40, "title": "Quarterly review", "description": null, "starts_at": "2026-09-12T15:00:00Z", "ends_at": "2026-09-12T16:00:00Z", "timezone": "UTC", "meet_link": "https://meet.google.com/hub-create", "lifecycle_status": "upcoming", "can_join_meet": false, "attendee_count": 1, "attendees": [ { "person_id": null, "contact_id": null, "email": "guest@example.com", "name": "Guest", "role": "attendee" } ], "is_hr": false, "is_organizer": true, "can_update": true, "can_cancel": true, "can_leave": false } } ``` ## POST /meetings/{meetingId}/cancel Cancel a meeting Required scope: `meetings:delete` Required scope: `meetings:delete` - `meetingId` (path, required) — Meeting ID ```json { "message": "Meeting cancelled successfully" } ``` ## POST /meetings/{meetingId}/leave Leave a meeting as an attendee Required scope: `meetings:delete` Required scope: `meetings:delete` - `meetingId` (path, required) — Meeting ID ```json { "message": "You left the meeting" } ``` --- # Memberships Canonical URL: https://coreware.com/docs/api/memberships.md API version: v1 These operations are generated from OpenAPI. They are authoritative. ## GET /memberships List membership programs Required scope: `memberships:read` Required scope: `memberships:read` - `status` (query) — Filter by status - `search` (query) — Search by program name - `per_page` (query) — Results per page ```json { "data": [ { "id": 2, "name": "Gold Membership", "status": "active", "grace_period_days": 14, "required_forms": [ { "form_id": "hba-homepro-join-application", "slug": "hba-homepro-join-application", "title": "Home Pro Join Application", "status": "published", "public_url": "https://example.coreware.test/form/hba-homepro-join-application", "location_id": null, "validity_mode": "membership_enrollment", "validity_days": null, "reminder_days_before_expiry": null, "sort_order": 0 } ] } ], "meta": { "current_page": 1, "last_page": 1, "per_page": 25, "total": 1 } } ``` ## POST /memberships Create a membership program Required scope: `memberships:write` Required scope: `memberships:write` ```json { "data": { "id": 2, "name": "Gold Membership", "status": "active", "grace_period_days": 14, "required_forms": [ { "form_id": "hba-homepro-join-application", "slug": "hba-homepro-join-application", "title": "Home Pro Join Application", "status": "published", "public_url": "https://example.coreware.test/form/hba-homepro-join-application", "location_id": null, "validity_mode": "membership_enrollment", "validity_days": null, "reminder_days_before_expiry": null, "sort_order": 0 } ] } } ``` ## GET /memberships/{membership} Get membership program with required forms Required scope: `memberships:read` Required scope: `memberships:read` - `membership` (path, required) — Membership program ID ```json { "data": { "id": 2, "name": "Gold Membership", "status": "active", "grace_period_days": 14, "required_forms": [ { "form_id": "hba-homepro-join-application", "slug": "hba-homepro-join-application", "title": "Home Pro Join Application", "status": "published", "public_url": "https://example.coreware.test/form/hba-homepro-join-application", "location_id": null, "validity_mode": "membership_enrollment", "validity_days": null, "reminder_days_before_expiry": null, "sort_order": 0 } ] } } ``` ## PUT /memberships/{membership} Update a membership program Required scope: `memberships:write` Required scope: `memberships:write` - `membership` (path, required) — Membership program ID ```json { "data": { "id": 2, "name": "Gold Membership", "status": "active", "grace_period_days": 14, "required_forms": [ { "form_id": "hba-homepro-join-application", "slug": "hba-homepro-join-application", "title": "Home Pro Join Application", "status": "published", "public_url": "https://example.coreware.test/form/hba-homepro-join-application", "location_id": null, "validity_mode": "membership_enrollment", "validity_days": null, "reminder_days_before_expiry": null, "sort_order": 0 } ] } } ``` ## PATCH /memberships/{membership} Partially update a membership program Required scope: `memberships:write` Required scope: `memberships:write` - `membership` (path, required) — Membership program ID ```json { "data": { "id": 2, "name": "Gold Membership", "status": "active", "grace_period_days": 14, "required_forms": [ { "form_id": "hba-homepro-join-application", "slug": "hba-homepro-join-application", "title": "Home Pro Join Application", "status": "published", "public_url": "https://example.coreware.test/form/hba-homepro-join-application", "location_id": null, "validity_mode": "membership_enrollment", "validity_days": null, "reminder_days_before_expiry": null, "sort_order": 0 } ] } } ``` ## GET /memberships/{membership}/forms List required forms for a membership Required scope: `memberships:read` Required scope: `memberships:read` - `membership` (path, required) — Membership program ID ```json { "data": [ { "form_id": "hba-homepro-join-application", "slug": "hba-homepro-join-application", "title": "Home Pro Join Application", "status": "published", "public_url": "https://example.coreware.test/form/hba-homepro-join-application", "location_id": null, "validity_mode": "membership_enrollment", "validity_days": null, "reminder_days_before_expiry": null, "sort_order": 0 } ] } ``` ## PUT /memberships/{membership}/forms Replace required forms on a membership Replace-all sync. Send the full forms array. Identify each form by public form_id or slug (for example hba-homepro-join-application). This does not submit form responses. Required scope: `memberships:write` Required scope: `memberships:write` - `membership` (path, required) — Membership program ID ```json { "data": [ { "form_id": "hba-homepro-join-application", "slug": "hba-homepro-join-application", "title": "Home Pro Join Application", "status": "published", "public_url": "https://example.coreware.test/form/hba-homepro-join-application", "location_id": null, "validity_mode": "membership_enrollment", "validity_days": null, "reminder_days_before_expiry": null, "sort_order": 0 } ], "warnings": [] } ``` ## GET /contact-memberships List member enrollments Required scope: `memberships:read` Required scope: `memberships:read` - `contact_id` (query) — Filter by contact ID - `membership_id` (query) — Filter by membership program ID - `status` (query) — Filter by enrollment status - `per_page` (query) — Results per page ```json { "data": [ { "id": 7, "contact_id": 42, "membership_id": 2, "status": "active", "enrolled_at": "2026-01-15T10:00:00.000000Z" } ], "meta": { "current_page": 1, "last_page": 1, "per_page": 25, "total": 1 } } ``` ## GET /contact-memberships/{contactMembership} Get member enrollment Required scope: `memberships:read` Required scope: `memberships:read` - `contactMembership` (path, required) — Contact membership ID ```json { "data": { "id": 7, "contact_id": 42, "membership_id": 2, "status": "active", "enrolled_at": "2026-01-15T10:00:00.000000Z" } } ``` --- # Notebooks Canonical URL: https://coreware.com/docs/api/notebooks.md API version: v1 These operations are generated from OpenAPI. They are authoritative. ## GET /notebooks List notebooks Required scope: `notebooks:read` Required scope: `notebooks:read` - `search` (query) — Search by title - `page` (query) — Page number - `per_page` (query) — Results per page ```json { "data": [ { "id": 9, "ulid": "01JNOTEBOOKEXAMPLE000000", "title": "Store playbook", "description": "Front-counter notes", "visibility": "company" } ], "meta": { "current_page": 1, "last_page": 1, "per_page": 25, "total": 1 } } ``` ## POST /notebooks Create a notebook Required scope: `notebooks:write` Required scope: `notebooks:write` ```json { "data": { "id": 9, "ulid": "01JNOTEBOOKEXAMPLE000000", "title": "Store playbook", "description": "Front-counter notes", "visibility": "company" } } ``` ## GET /notebooks/search Search notebook pages Required scope: `notebooks:read` Required scope: `notebooks:read` - `query` (query, required) — Search query - `per_page` (query) — Results per page ```json { "data": [ { "ulid": "01JPAGEEXAMPLE0000000000", "title": "Opening checklist" } ], "meta": { "current_page": 1, "last_page": 1, "per_page": 25, "total": 1 } } ``` ## GET /notebooks/{notebookUlid} Get a notebook and its page tree Required scope: `notebooks:read` Required scope: `notebooks:read` - `notebookUlid` (path, required) — Notebook ULID ```json { "data": { "notebook": { "id": 9, "ulid": "01JNOTEBOOKEXAMPLE000000", "title": "Store playbook", "description": "Front-counter notes", "visibility": "company" }, "pages": [ { "id": 21, "ulid": "01JPAGEEXAMPLE0000000000", "title": "Opening checklist", "content": [ { "type": "paragraph", "props": { "content": "Unlock the register." } } ] } ], "permission": "owner" } } ``` ## PUT /notebooks/{notebookUlid} Update a notebook Required scope: `notebooks:write` Required scope: `notebooks:write` - `notebookUlid` (path, required) — Notebook ULID ```json { "data": { "id": 9, "ulid": "01JNOTEBOOKEXAMPLE000000", "title": "Store playbook", "description": "Front-counter notes", "visibility": "company" } } ``` ## PATCH /notebooks/{notebookUlid} Partially update a notebook Required scope: `notebooks:write` Required scope: `notebooks:write` - `notebookUlid` (path, required) — Notebook ULID ```json { "data": { "id": 9, "ulid": "01JNOTEBOOKEXAMPLE000000", "title": "Store playbook", "description": "Front-counter notes", "visibility": "company" } } ``` ## DELETE /notebooks/{notebookUlid} Delete a notebook Required scope: `notebooks:delete` Required scope: `notebooks:delete` - `notebookUlid` (path, required) — Notebook ULID ```json { "message": "Notebook deleted successfully" } ``` ## GET /notebooks/{notebookUlid}/export/markdown Export a notebook as markdown Required scope: `notebooks:read` Required scope: `notebooks:read` - `notebookUlid` (path, required) — Notebook ULID ```json { "data": { "title": "Store playbook", "markdown": "# Opening checklist" } } ``` ## POST /notebooks/favorites Toggle a notebook or page favorite Required scope: `notebooks:write` Required scope: `notebooks:write` ```json { "data": { "is_favorited": true } } ``` ## GET /notebooks/{notebookUlid}/pages List pages in a notebook Required scope: `notebooks:read` Required scope: `notebooks:read` - `notebookUlid` (path, required) — Notebook ULID ```json { "data": [ { "id": 21, "ulid": "01JPAGEEXAMPLE0000000000", "title": "Opening checklist", "content": [ { "type": "paragraph", "props": { "content": "Unlock the register." } } ] } ] } ``` ## POST /notebooks/{notebookUlid}/pages Create a notebook page Required scope: `notebooks:write` Required scope: `notebooks:write` - `notebookUlid` (path, required) — Notebook ULID ```json { "data": { "id": 21, "ulid": "01JPAGEEXAMPLE0000000000", "title": "Opening checklist", "content": [ { "type": "paragraph", "props": { "content": "Unlock the register." } } ] } } ``` ## POST /notebooks/{notebookUlid}/pages/reorder Reorder notebook pages Required scope: `notebooks:write` Required scope: `notebooks:write` - `notebookUlid` (path, required) — Notebook ULID ```json { "message": "Notebook pages reordered successfully" } ``` ## GET /notebook-pages/{pageUlid} Get a notebook page Required scope: `notebooks:read` Required scope: `notebooks:read` - `pageUlid` (path, required) — Page ULID ```json { "data": { "id": 21, "ulid": "01JPAGEEXAMPLE0000000000", "title": "Opening checklist", "content": [ { "type": "paragraph", "props": { "content": "Unlock the register." } } ] } } ``` ## PUT /notebook-pages/{pageUlid} Update a notebook page Required scope: `notebooks:write` Required scope: `notebooks:write` - `pageUlid` (path, required) — Page ULID ```json { "data": { "id": 21, "ulid": "01JPAGEEXAMPLE0000000000", "title": "Opening checklist", "content": [ { "type": "paragraph", "props": { "content": "Unlock the register." } } ] } } ``` ## DELETE /notebook-pages/{pageUlid} Delete a notebook page Required scope: `notebooks:delete` Required scope: `notebooks:delete` - `pageUlid` (path, required) — Page ULID ```json { "message": "Notebook page deleted successfully" } ``` ## POST /notebook-pages/{pageUlid}/move Move a notebook page Required scope: `notebooks:write` Required scope: `notebooks:write` - `pageUlid` (path, required) — Page ULID ```json { "data": { "id": 21, "ulid": "01JPAGEEXAMPLE0000000000", "title": "Opening checklist", "content": [ { "type": "paragraph", "props": { "content": "Unlock the register." } } ] } } ``` ## POST /notebook-pages/{pageUlid}/duplicate Duplicate a notebook page Required scope: `notebooks:write` Required scope: `notebooks:write` - `pageUlid` (path, required) — Page ULID ```json { "data": { "id": 21, "ulid": "01JPAGEEXAMPLE0000000000", "title": "Opening checklist", "content": [ { "type": "paragraph", "props": { "content": "Unlock the register." } } ] } } ``` ## GET /notebook-pages/{pageUlid}/versions List page versions Required scope: `notebooks:read` Required scope: `notebooks:read` - `pageUlid` (path, required) — Page ULID ```json { "data": [ { "id": 7, "title": "Opening checklist" } ] } ``` ## POST /notebook-pages/{pageUlid}/versions/{versionId}/restore Restore a page version Required scope: `notebooks:write` Required scope: `notebooks:write` - `pageUlid` (path, required) — Page ULID - `versionId` (path, required) — Version ID ```json { "data": { "id": 21, "ulid": "01JPAGEEXAMPLE0000000000", "title": "Opening checklist", "content": [ { "type": "paragraph", "props": { "content": "Unlock the register." } } ] } } ``` ## GET /notebook-pages/{pageUlid}/comments List page comments Required scope: `notebooks:read` Required scope: `notebooks:read` - `pageUlid` (path, required) — Page ULID ```json { "data": [ { "id": 5, "content": "Add the weekend hours.", "resolved_at": null } ] } ``` ## POST /notebook-pages/{pageUlid}/comments Create a page comment Required scope: `notebooks:write` Required scope: `notebooks:write` - `pageUlid` (path, required) — Page ULID ```json { "data": { "id": 5, "content": "Add the weekend hours.", "resolved_at": null } } ``` ## PUT /notebook-pages/{pageUlid}/tags Replace tags on a page Required scope: `notebooks:write` Required scope: `notebooks:write` - `pageUlid` (path, required) — Page ULID ```json { "data": [ { "id": 3, "name": "ops", "color": "#6366f1" } ] } ``` ## PUT /notebook-comments/{commentId} Update a comment Required scope: `notebooks:write` Required scope: `notebooks:write` - `commentId` (path, required) — Comment ID ```json { "data": { "id": 5, "content": "Add the weekend hours.", "resolved_at": null } } ``` ## DELETE /notebook-comments/{commentId} Delete a comment Required scope: `notebooks:delete` Required scope: `notebooks:delete` - `commentId` (path, required) — Comment ID ```json { "message": "Comment deleted successfully" } ``` ## POST /notebook-comments/{commentId}/resolve Resolve a comment Required scope: `notebooks:write` Required scope: `notebooks:write` - `commentId` (path, required) — Comment ID ```json { "data": { "id": 5, "content": "Add the weekend hours.", "resolved_at": null } } ``` ## GET /notebook-tags List notebook tags Required scope: `notebooks:read` Required scope: `notebooks:read` ```json { "data": [ { "id": 3, "name": "ops", "color": "#6366f1" } ] } ``` ## POST /notebook-tags Create a notebook tag Required scope: `notebooks:write` Required scope: `notebooks:write` ```json { "data": { "id": 3, "name": "ops", "color": "#6366f1" } } ``` ## PUT /notebook-tags/{tagId} Update a notebook tag Required scope: `notebooks:write` Required scope: `notebooks:write` - `tagId` (path, required) — Tag ID ```json { "data": { "id": 3, "name": "ops", "color": "#6366f1" } } ``` ## DELETE /notebook-tags/{tagId} Delete a notebook tag Required scope: `notebooks:delete` Required scope: `notebooks:delete` - `tagId` (path, required) — Tag ID ```json { "message": "Tag deleted successfully" } ``` ## GET /notebook-collections List notebook collections Required scope: `notebooks:read` Required scope: `notebooks:read` ```json { "data": [ { "id": 2, "ulid": "01JCOLLECTIONEXAMPLE0000", "name": "Front counter" } ] } ``` ## POST /notebook-collections Create a notebook collection Required scope: `notebooks:write` Required scope: `notebooks:write` ```json { "data": { "id": 2, "ulid": "01JCOLLECTIONEXAMPLE0000", "name": "Front counter" } } ``` ## PUT /notebook-collections/{collectionUlid} Update a notebook collection Required scope: `notebooks:write` Required scope: `notebooks:write` - `collectionUlid` (path, required) — Collection ULID ```json { "data": { "id": 2, "ulid": "01JCOLLECTIONEXAMPLE0000", "name": "Front counter" } } ``` ## DELETE /notebook-collections/{collectionUlid} Delete a notebook collection Required scope: `notebooks:delete` Required scope: `notebooks:delete` - `collectionUlid` (path, required) — Collection ULID ```json { "message": "Collection deleted successfully" } ``` --- # Offerings Canonical URL: https://coreware.com/docs/api/offerings.md API version: v1 These operations are generated from OpenAPI. They are authoritative. ## GET /offerings List offerings Lists active offerings by default. Filter by type (service, class_event, facility, walk_in_queue), location, or search. Required scope: `scheduling:read` Required scope: `scheduling:read` - `type` (query) — Offering type - `location_id` (query) — Filter by location - `search` (query) — Search name/slug - `status` (query) — Override default active status filter - `per_page` (query) — Results per page ```json { "data": [ { "id": 10, "name": "Court Rental", "public_name": "Court Rental", "slug": "court-rental", "type": "facility", "status": "active", "capacity": 4, "cancel_until_value": 24, "cancel_until_unit": "hours", "reschedule_until_value": 12, "reschedule_until_unit": "hours" } ], "meta": { "current_page": 1, "last_page": 1, "per_page": 50, "total": 1 } } ``` ## POST /offerings Create an offering Creates a service, class/event, facility, or walk-in queue. Status defaults to draft. Active status requires a bookable schedule for service, class_event, and facility. At least one location_id or custom_location_id is required. Sessions are materialized from schedule_rules. Required scope: `scheduling:write` Required scope: `scheduling:write` ```json { "data": { "id": 10, "name": "Court Rental", "public_name": "Court Rental", "slug": "court-rental", "type": "facility", "status": "active", "capacity": 4, "cancel_until_value": 24, "cancel_until_unit": "hours", "reschedule_until_value": 12, "reschedule_until_unit": "hours", "policy": { "cancel_until_value": 24, "cancel_until_unit": "hours", "reschedule_until_value": 12, "reschedule_until_unit": "hours", "no_refund_after_window": false }, "locations": [ { "location_id": 1, "custom_location_id": null, "name": "Main Campus", "timezone": "America/Chicago" } ] }, "message": "Offering created successfully" } ``` ## GET /offerings/{offering} Get offering detail Includes cancel/reschedule policy fields. Windows are per-offering, not a global 24h rule. Required scope: `scheduling:read` Required scope: `scheduling:read` - `offering` (path, required) — Offering ID ```json { "data": { "id": 10, "name": "Court Rental", "public_name": "Court Rental", "slug": "court-rental", "type": "facility", "status": "active", "capacity": 4, "cancel_until_value": 24, "cancel_until_unit": "hours", "reschedule_until_value": 12, "reschedule_until_unit": "hours", "policy": { "cancel_until_value": 24, "cancel_until_unit": "hours", "reschedule_until_value": 12, "reschedule_until_unit": "hours", "no_refund_after_window": false }, "locations": [ { "location_id": 1, "custom_location_id": null, "name": "Main Campus", "timezone": "America/Chicago" } ] } } ``` ## PATCH /offerings/{offering} Update an offering Partial update using the staff offering fields. Setting status to active still requires a bookable schedule for service, class_event, and facility. Required scope: `scheduling:write` Required scope: `scheduling:write` - `offering` (path, required) — Offering ID ```json { "data": { "id": 10, "name": "Court Rental", "public_name": "Court Rental", "slug": "court-rental", "type": "facility", "status": "active", "capacity": 4, "cancel_until_value": 24, "cancel_until_unit": "hours", "reschedule_until_value": 12, "reschedule_until_unit": "hours", "policy": { "cancel_until_value": 24, "cancel_until_unit": "hours", "reschedule_until_value": 12, "reschedule_until_unit": "hours", "no_refund_after_window": false }, "locations": [ { "location_id": 1, "custom_location_id": null, "name": "Main Campus", "timezone": "America/Chicago" } ] }, "message": "Offering updated successfully" } ``` ## DELETE /offerings/{offering} Delete an offering Soft-deletes the offering and releases class session resources, matching the staff delete action. Required scope: `scheduling:delete` Required scope: `scheduling:delete` - `offering` (path, required) — Offering ID ```json { "message": "Offering deleted successfully" } ``` ## GET /offerings/{offering}/waitlist List waitlist entries for an offering Required scope: `scheduling:read` Required scope: `scheduling:read` - `offering` (path, required) — Offering ID - `date` (query) — Filter by preferred date - `time` (query) — Filter by preferred time - `status` (query) — Filter by status ```json { "data": [ { "id": 1, "customer_name": "Alex Rivera", "status": "waiting", "position": 1, "preferred_date": "2026-10-02", "preferred_time": "18:00" } ] } ``` ## POST /offerings/{offering}/waitlist Add a customer to an offering waitlist The offering must have waitlist enabled. date is YYYY-MM-DD and time is HH:MM. Required scope: `scheduling:write` Required scope: `scheduling:write` - `offering` (path, required) — Offering ID ```json { "data": { "id": 1, "position": 1, "status": "waiting" }, "message": "Waitlist entry created successfully" } ``` ## POST /waitlist/{entry}/promote Offer the next waitlist spot Required scope: `scheduling:write` Required scope: `scheduling:write` - `entry` (path, required) — Waitlist entry ID ```json { "data": { "id": 1, "status": "offered" }, "message": "Waitlist entry offered successfully" } ``` ## DELETE /waitlist/{entry} Remove or cancel a waitlist entry Required scope: `scheduling:delete` Required scope: `scheduling:delete` - `entry` (path, required) — Waitlist entry ID ```json { "message": "Waitlist entry removed successfully" } ``` ## GET /offerings/{offering}/sessions List offering sessions Upcoming materialized sessions with capacity and remaining_capacity. Required scope: `scheduling:read` Required scope: `scheduling:read` - `offering` (path, required) — Offering ID - `location_id` (query) — Filter by location - `from` (query) — Start datetime - `to` (query) — End datetime - `per_page` (query) — Results per page ```json { "data": [ { "offering_id": 10, "offering_session_id": 555, "location_id": 1, "date": "2026-08-15", "start_time": "10:00:00", "end_time": "11:00:00", "capacity": 10, "booked_count": 3, "remaining_capacity": 7, "timezone": "America/Chicago", "is_available": true } ], "meta": { "current_page": 1, "last_page": 1, "per_page": 50, "total": 1 } } ``` ## GET /offerings/{offering}/available-dates List dates with open slots Required scope: `scheduling:read` Required scope: `scheduling:read` - `offering` (path, required) — Offering ID - `location_id` (query, required) — Location ID or custom_{id} - `days_ahead` (query) — How many days to scan ```json { "data": [ "2026-08-15", "2026-08-16" ], "meta": { "total": 2 } } ``` ## GET /offerings/{offering}/available-slots List slots for a date Required scope: `scheduling:read` Required scope: `scheduling:read` - `offering` (path, required) — Offering ID - `location_id` (query, required) — Location ID or custom_{id} - `date` (query, required) — Date YYYY-MM-DD ```json { "data": [ { "offering_id": 10, "offering_session_id": 555, "location_id": 1, "date": "2026-08-15", "start_time": "10:00:00", "end_time": "11:00:00", "capacity": 10, "booked_count": 3, "remaining_capacity": 7, "timezone": "America/Chicago", "is_available": true } ] } ``` ## GET /offerings/{offering}/reservation/search Guided facility reservation search Wraps guided-search availability for facility-style offerings (duration/guests/resource_count). Required scope: `scheduling:read` Required scope: `scheduling:read` - `offering` (path, required) — Offering ID - `date` (query, required) — Date YYYY-MM-DD - `duration_minutes` (query) — Requested duration - `guest_count` (query) — Guest count - `resource_count` (query) — Resource count - `location_id` (query) — Location ID or custom_{id} - `contact_id` (query) — Optional contact for entitlements ```json { "data": { "available_slots": [ { "offering_id": 10, "offering_session_id": 555, "location_id": 1, "date": "2026-08-15", "start_time": "10:00:00", "end_time": "11:00:00", "capacity": 10, "booked_count": 3, "remaining_capacity": 7, "timezone": "America/Chicago", "is_available": true } ], "maxDurationMinutes": 120, "maxGuests": 4 } } ``` --- # Opportunities Canonical URL: https://coreware.com/docs/api/opportunities.md API version: v1 These operations are generated from OpenAPI. They are authoritative. ## GET /pipelines List pipelines Required scope: `opportunities:read` Required scope: `opportunities:read` ```json { "data": [ { "id": 1, "name": "Default", "is_default": true } ] } ``` ## GET /pipelines/{pipelineId} Get pipeline Required scope: `opportunities:read` Required scope: `opportunities:read` - `pipelineId` (path, required) — Pipeline ID ```json { "data": { "id": 1, "name": "Default", "is_default": true } } ``` ## GET /pipelines/{pipelineId}/stages List stages for a pipeline Required scope: `opportunities:read` Required scope: `opportunities:read` - `pipelineId` (path, required) — Pipeline ID ```json { "data": [ { "id": 2, "name": "Qualified", "is_won_stage": false, "is_lost_stage": false } ] } ``` ## GET /opportunity-stages List opportunity stages Required scope: `opportunities:read` Required scope: `opportunities:read` - `pipeline_id` (query) — Filter by pipeline ```json { "data": [ { "id": 2, "pipeline_id": 1, "name": "Qualified" } ] } ``` ## GET /opportunity-sources List opportunity sources Required scope: `opportunities:read` Required scope: `opportunities:read` ```json { "data": [ { "id": 1, "name": "Website", "slug": "website" } ] } ``` ## GET /opportunities List opportunities Required scope: `opportunities:read` Required scope: `opportunities:read` - `pipeline_id` (query) — Filter by pipeline - `stage_id` (query) — Filter by stage - `status` (query) — open, won, or lost - `contact_id` (query) — Filter by contact - `assigned_to` (query) — Filter by assigned staff user - `search` (query) — Search by name ```json { "data": [ { "id": 9, "contact_id": 42, "name": "Range day package", "value": 1200, "pipeline_id": 1, "stage_id": 2, "status": "open" } ] } ``` ## POST /opportunities Create opportunity Required scope: `opportunities:write` Required scope: `opportunities:write` ```json { "data": { "id": 9, "contact_id": 42, "name": "Range day package", "value": 1200, "pipeline_id": 1, "stage_id": 2, "status": "open" } } ``` ## GET /opportunities/{opportunityId} Get opportunity Required scope: `opportunities:read` Required scope: `opportunities:read` - `opportunityId` (path, required) — Opportunity ID ```json { "data": { "id": 9, "contact_id": 42, "name": "Range day package", "value": 1200, "pipeline_id": 1, "stage_id": 2, "status": "open" } } ``` ## PUT /opportunities/{opportunityId} Update opportunity Required scope: `opportunities:write` Required scope: `opportunities:write` - `opportunityId` (path, required) — Opportunity ID ```json { "data": { "id": 9, "contact_id": 42, "name": "Range day package", "value": 1200, "pipeline_id": 1, "stage_id": 2, "status": "open" } } ``` ## DELETE /opportunities/{opportunityId} Soft-delete opportunity Required scope: `opportunities:delete` Required scope: `opportunities:delete` - `opportunityId` (path, required) — Opportunity ID ```json { "message": "Opportunity deleted successfully" } ``` ## POST /opportunities/{opportunityId}/move-stage Move opportunity to another stage Required scope: `opportunities:write` Required scope: `opportunities:write` - `opportunityId` (path, required) — Opportunity ID ```json { "data": { "id": 9, "contact_id": 42, "name": "Range day package", "value": 1200, "pipeline_id": 1, "stage_id": 2, "status": "won" } } ``` ## GET /contacts/{contact}/opportunities List opportunities for a contact Required scope: `opportunities:read` Required scope: `opportunities:read` - `contact` (path, required) — Contact ID ```json { "data": [ { "id": 9, "contact_id": 42, "name": "Range day package", "value": 1200, "pipeline_id": 1, "stage_id": 2, "status": "open" } ] } ``` --- # Payments Canonical URL: https://coreware.com/docs/api/payments.md API version: v1 These operations are generated from OpenAPI. They are authoritative. ## GET /payment-types List payment types Returns configured payment types. Pass location_id to include is_enabled for that location. Payment-type toggle and gateway assignment stay in the staff app. Required scope: `payments:read` Required scope: `payments:read` - `location_id` (query) — Include per-location is_enabled ```json { "data": [ { "id": 4, "code": "credit_card", "name": "Credit Card", "description": null, "is_system_type": true, "requires_gateway": true, "supports_in_person": true, "supports_online": true, "supports_unattended": false, "supports_subscriptions": true, "is_active": true, "sort_order": 1 } ] } ``` ## GET /payment-types/{paymentType} Get a payment type Required scope: `payments:read` Required scope: `payments:read` - `paymentType` (path, required) — Payment type ID ```json { "data": { "id": 4, "code": "credit_card", "name": "Credit Card", "description": null, "is_system_type": true, "requires_gateway": true, "supports_in_person": true, "supports_online": true, "supports_unattended": false, "supports_subscriptions": true, "is_active": true, "sort_order": 1 } } ``` ## GET /merchant-accounts List merchant accounts Returns merchant account metadata. Credentials, safe_credentials, and configured_credentials are never included. Required scope: `payments:read` Required scope: `payments:read` - `location_id` (query) — Filter by location ID ```json { "data": [ { "id": 2, "location_id": 1, "gateway_provider_id": 1, "gateway_provider_name": "CardConnect", "account_name": "Front counter", "merchant_id": "merchant_abc", "is_default": true, "is_test_mode": true, "is_external_billing": false, "status": "connected", "last_connected_at": "2026-09-01T12:00:00.000000Z" } ] } ``` ## GET /merchant-accounts/{merchantAccount} Get a merchant account Required scope: `payments:read` Required scope: `payments:read` - `merchantAccount` (path, required) — Merchant account ID ```json { "data": { "id": 2, "location_id": 1, "gateway_provider_id": 1, "gateway_provider_name": "CardConnect", "account_name": "Front counter", "merchant_id": "merchant_abc", "is_default": true, "is_test_mode": true, "is_external_billing": false, "status": "connected", "last_connected_at": "2026-09-01T12:00:00.000000Z" } } ``` ## GET /payment-transactions List gateway payment transactions Returns sanitized gateway transactions. gateway_response, receipt_data, and signature_data are omitted. Record offline invoice payments with POST /invoices/{invoice}/pay. Required scope: `payments:read` Required scope: `payments:read` - `location_id` (query) — Filter by location ID - `contact_id` (query) — Filter by contact ID - `invoice_id` (query) — Filter by invoice ID - `proposal_id` (query) — Filter gateway transactions for a proposal (originating_module=proposal) - `gateway_batch_id` (query) — Filter by processor settlement batch ID - `merchant_account_id` (query) — Filter by merchant account ID - `status` (query) — Filter by transaction status - `payment_method` (query) — Filter by payment method - `originating_module` (query) — Filter by originating module - `from` (query) — Processed-at from (ISO 8601 or YYYY-MM-DD) - `to` (query) — Processed-at to (ISO 8601 or YYYY-MM-DD) - `is_test` (query) — Filter test vs live transactions - `per_page` (query) — Results per page ```json { "data": [ { "id": 9001, "merchant_account_id": 2, "location_id": 1, "contact_id": 42, "invoice_id": 501, "customer_subscription_id": null, "gateway_batch_id": "gw_batch_44", "transaction_ref": "txn_abc", "gateway_transaction_id": "gw_abc", "transaction_type": "sale", "payment_method": "credit_card", "card_type": "visa", "last_four": "4242", "requested_amount": 49.99, "authorized_amount": 49.99, "captured_amount": 49.99, "surcharge_amount": 0, "total_amount": 49.99, "currency": "USD", "status": "captured", "approval_code": "OK123", "originating_module": "invoices", "originating_transaction_id": "501", "description": "Invoice 501", "is_test": true, "processed_at": "2026-09-14T15:00:00.000000Z", "voided_at": null, "refunded_at": null } ], "meta": { "current_page": 1, "last_page": 1, "per_page": 25, "total": 1 } } ``` ## GET /payment-transactions/{transaction} Get a gateway payment transaction Required scope: `payments:read` Required scope: `payments:read` - `transaction` (path, required) — Transaction ID ```json { "data": { "id": 9001, "merchant_account_id": 2, "location_id": 1, "contact_id": 42, "invoice_id": 501, "customer_subscription_id": null, "gateway_batch_id": "gw_batch_44", "transaction_ref": "txn_abc", "gateway_transaction_id": "gw_abc", "transaction_type": "sale", "payment_method": "credit_card", "card_type": "visa", "last_four": "4242", "requested_amount": 49.99, "authorized_amount": 49.99, "captured_amount": 49.99, "surcharge_amount": 0, "total_amount": 49.99, "currency": "USD", "status": "captured", "approval_code": "OK123", "originating_module": "invoices", "originating_transaction_id": "501", "description": "Invoice 501", "is_test": true, "processed_at": "2026-09-14T15:00:00.000000Z", "voided_at": null, "refunded_at": null } } ``` ## GET /payment-batches List processor settlement batches Returns batches already synced from the processor into BOSS. gateway_response is omitted. Live gateway refresh and POST sync stay in the staff app. Required scope: `payments:read` Required scope: `payments:read` - `merchant_account_id` (query) — Filter by merchant account ID - `location_id` (query) — Filter by location ID - `status` (query) — Filter by batch status - `from` (query) — Opened-at from (ISO 8601 or YYYY-MM-DD) - `to` (query) — Opened-at to (ISO 8601 or YYYY-MM-DD) - `per_page` (query) — Results per page ```json { "data": [ { "id": 44, "merchant_account_id": 2, "location_id": 1, "batch_number": "20260914-1", "gateway_batch_id": "gw_batch_44", "transaction_count": 12, "total_amount": 1840.5, "status": "settled", "opened_at": "2026-09-14T00:00:00.000000Z", "closed_at": "2026-09-14T23:59:00.000000Z", "settled_at": "2026-09-15T02:00:00.000000Z", "synced_at": "2026-09-15T02:05:00.000000Z" } ], "meta": { "current_page": 1, "last_page": 1, "per_page": 25, "total": 1 } } ``` ## GET /payment-batches/{batch} Get a processor settlement batch Required scope: `payments:read` Required scope: `payments:read` - `batch` (path, required) — Batch ID ```json { "data": { "id": 44, "merchant_account_id": 2, "location_id": 1, "batch_number": "20260914-1", "gateway_batch_id": "gw_batch_44", "transaction_count": 12, "total_amount": 1840.5, "status": "settled", "opened_at": "2026-09-14T00:00:00.000000Z", "closed_at": "2026-09-14T23:59:00.000000Z", "settled_at": "2026-09-15T02:00:00.000000Z", "synced_at": "2026-09-15T02:05:00.000000Z" } } ``` ## GET /payment-batches/{batch}/transactions List stored transactions in a settlement batch Returns sanitized BOSS transactions matching the batch gateway_batch_id. Does not call the processor live. Required scope: `payments:read` Required scope: `payments:read` - `batch` (path, required) — Batch ID ```json { "data": [ { "id": 9001, "merchant_account_id": 2, "location_id": 1, "contact_id": 42, "invoice_id": 501, "customer_subscription_id": null, "gateway_batch_id": "gw_batch_44", "transaction_ref": "txn_abc", "gateway_transaction_id": "gw_abc", "transaction_type": "sale", "payment_method": "credit_card", "card_type": "visa", "last_four": "4242", "requested_amount": 49.99, "authorized_amount": 49.99, "captured_amount": 49.99, "surcharge_amount": 0, "total_amount": 49.99, "currency": "USD", "status": "captured", "approval_code": "OK123", "originating_module": "invoices", "originating_transaction_id": "501", "description": "Invoice 501", "is_test": true, "processed_at": "2026-09-14T15:00:00.000000Z", "voided_at": null, "refunded_at": null } ] } ``` --- # Products Canonical URL: https://coreware.com/docs/api/products.md API version: v1 These operations are generated from OpenAPI. They are authoritative. ## GET /products List products Required scope: `products:read` Required scope: `products:read` - `search` (query) — Search by name or SKU - `category_id` (query) — Filter by category ID - `manufacturer_id` (query) — Filter by manufacturer ID - `is_bundle` (query) — Filter to product bundles - `is_recurring` (query) — Filter recurring membership SKUs - `is_serialized` (query) — Filter serialized products - `per_page` (query) — Results per page ```json { "data": [ { "item_id": 88, "name": "Demo Product", "item_number": "SKU-88", "category_id": 10, "unit_price": 49.99, "is_recurring": false, "membership_id": null, "is_bundle": false } ], "meta": { "current_page": 1, "last_page": 1, "per_page": 25, "total": 1 } } ``` ## POST /products Create a product Creates inventory or recurring membership SKUs. Recurring items require interval. POS register create is not this endpoint. Required scope: `products:write` Required scope: `products:write` ```json { "data": { "item_id": 88, "name": "Demo Product", "item_number": "SKU-88", "category_id": 10, "unit_price": 49.99, "is_recurring": false, "membership_id": null, "is_bundle": false } } ``` ## GET /products/{product} Get product with inventory, serials, variations, and required forms Required scope: `products:read` Required scope: `products:read` - `product` (path, required) — Product ID ```json { "data": { "item_id": 91, "name": "HBA Basic", "item_number": "HBA-BASIC", "category_id": 10, "unit_price": 29.99, "startup_cost": 0, "is_recurring": true, "interval": "monthly_init", "membership_id": 2, "is_bundle": false, "bundle": null, "inventory": [], "required_forms": [ { "form_id": "hba-homepro-join-application", "slug": "hba-homepro-join-application", "title": "Home Pro Join Application", "status": "published", "public_url": "https://example.coreware.test/form/hba-homepro-join-application", "location_id": null, "validity_mode": "membership_enrollment", "validity_days": null, "reminder_days_before_expiry": null, "sort_order": 0 } ] } } ``` ## PUT /products/{product} Update a product Required scope: `products:write` Required scope: `products:write` - `product` (path, required) — Product ID ```json { "data": { "item_id": 88, "name": "Demo Product", "item_number": "SKU-88", "category_id": 10, "unit_price": 49.99, "is_recurring": false, "membership_id": null, "is_bundle": false } } ``` ## PATCH /products/{product} Partially update a product Required scope: `products:write` Required scope: `products:write` - `product` (path, required) — Product ID ```json { "data": { "item_id": 88, "name": "Demo Product", "item_number": "SKU-88", "category_id": 10, "unit_price": 49.99, "is_recurring": false, "membership_id": null, "is_bundle": false } } ``` ## DELETE /products/{product} Soft-delete a product Required scope: `products:delete` Required scope: `products:delete` - `product` (path, required) — Product ID ```json { "message": "Product deleted successfully" } ``` ## GET /products/{product}/bundle Get product bundle composition Required scope: `products:read` Required scope: `products:read` - `product` (path, required) — Product ID ```json { "data": { "item_kit_id": 12, "item_id": 88, "name": "Range Kit", "display_mode": "expandable", "track_inventory": false, "components": [], "option_slots": [] } } ``` ## GET /products/{product}/inventory List per-location inventory Required scope: `products:read` Required scope: `products:read` - `product` (path, required) — Product ID ```json { "data": [ { "location_id": 1, "quantity": 4, "available": 4 } ] } ``` ## PUT /products/{product}/inventory Create or update inventory at a location Required scope: `products:write` Required scope: `products:write` - `product` (path, required) — Product ID ```json { "data": { "location_id": 1, "quantity": 10, "available": 10 } } ``` ## GET /products/{product}/serials List product serial numbers Required scope: `products:read` Required scope: `products:read` - `product` (path, required) — Product ID ```json { "data": [ { "serial_number": "SN-1", "in_stock": true } ] } ``` ## POST /products/{product}/serials Add a serial number Required scope: `products:write` Required scope: `products:write` - `product` (path, required) — Product ID ```json { "data": { "serial_number": "SN-1", "in_stock": true } } ``` ## PUT /products/{product}/serials/{serialNumber} Update a serial number Required scope: `products:write` Required scope: `products:write` - `product` (path, required) — Product ID - `serialNumber` (path, required) — Serial number ```json { "data": { "serial_number": "SN-1", "in_stock": false } } ``` ## PATCH /products/{product}/serials/{serialNumber} Partially update a serial number Required scope: `products:write` Required scope: `products:write` - `product` (path, required) — Product ID - `serialNumber` (path, required) — Serial number ```json { "data": { "serial_number": "SN-1", "in_stock": false } } ``` ## DELETE /products/{product}/serials/{serialNumber} Delete a serial number Required scope: `products:delete` Required scope: `products:delete` - `product` (path, required) — Product ID - `serialNumber` (path, required) — Serial number ```json { "message": "Serial number deleted successfully" } ``` ## GET /products/{product}/variations List product variations Required scope: `products:read` Required scope: `products:read` - `product` (path, required) — Product ID ```json { "data": [ { "id": 3, "name": "Large", "unit_price": 12 } ] } ``` ## POST /products/{product}/variations Create a product variation Required scope: `products:write` Required scope: `products:write` - `product` (path, required) — Product ID ```json { "data": { "id": 3, "name": "Large" } } ``` ## PUT /products/{product}/variations/{variation} Update a product variation Required scope: `products:write` Required scope: `products:write` - `product` (path, required) — Product ID - `variation` (path, required) — Variation ID ```json { "data": { "id": 3, "unit_price": 14 } } ``` ## PATCH /products/{product}/variations/{variation} Partially update a product variation Required scope: `products:write` Required scope: `products:write` - `product` (path, required) — Product ID - `variation` (path, required) — Variation ID ```json { "data": { "id": 3, "unit_price": 14 } } ``` ## DELETE /products/{product}/variations/{variation} Soft-delete a product variation Required scope: `products:delete` Required scope: `products:delete` - `product` (path, required) — Product ID - `variation` (path, required) — Variation ID ```json { "message": "Variation deleted successfully" } ``` ## GET /categories List categories Also available at GET /products/categories. Required scope: `products:read` Required scope: `products:read` - `per_page` (query) — Results per page ```json { "data": [ { "id": 10, "name": "Accessories", "parent_id": null } ], "meta": { "current_page": 1, "last_page": 1, "per_page": 25, "total": 1 } } ``` ## POST /categories Create a category Required scope: `products:write` Required scope: `products:write` ```json { "data": { "id": 11, "name": "Optics" } } ``` ## GET /categories/{category} Get a category Required scope: `products:read` Required scope: `products:read` - `category` (path, required) — Category ID ```json { "data": { "id": 10, "name": "Accessories" } } ``` ## PUT /categories/{category} Update a category Required scope: `products:write` Required scope: `products:write` - `category` (path, required) — Category ID ```json { "data": { "id": 10, "name": "Accessories" } } ``` ## PATCH /categories/{category} Partially update a category Required scope: `products:write` Required scope: `products:write` - `category` (path, required) — Category ID ```json { "data": { "id": 10, "color": "#111111" } } ``` ## DELETE /categories/{category} Soft-delete a category Required scope: `products:delete` Required scope: `products:delete` - `category` (path, required) — Category ID ```json { "message": "Category deleted successfully" } ``` ## GET /manufacturers List manufacturers Required scope: `products:read` Required scope: `products:read` - `per_page` (query) — Results per page ```json { "data": [ { "id": 4, "name": "Acme Arms" } ], "meta": { "current_page": 1, "last_page": 1, "per_page": 25, "total": 1 } } ``` ## POST /manufacturers Create a manufacturer Required scope: `products:write` Required scope: `products:write` ```json { "data": { "id": 4, "name": "Acme Arms" } } ``` ## GET /manufacturers/{manufacturer} Get a manufacturer Required scope: `products:read` Required scope: `products:read` - `manufacturer` (path, required) — Manufacturer ID ```json { "data": { "id": 4, "name": "Acme Arms" } } ``` ## PUT /manufacturers/{manufacturer} Update a manufacturer Required scope: `products:write` Required scope: `products:write` - `manufacturer` (path, required) — Manufacturer ID ```json { "data": { "id": 4, "name": "Acme Arms" } } ``` ## PATCH /manufacturers/{manufacturer} Partially update a manufacturer Required scope: `products:write` Required scope: `products:write` - `manufacturer` (path, required) — Manufacturer ID ```json { "data": { "id": 4, "name": "Acme Arms" } } ``` ## DELETE /manufacturers/{manufacturer} Delete a manufacturer Required scope: `products:delete` Required scope: `products:delete` - `manufacturer` (path, required) — Manufacturer ID ```json { "message": "Manufacturer deleted successfully" } ``` --- # Proposals Canonical URL: https://coreware.com/docs/api/proposals.md API version: v1 These operations are generated from OpenAPI. They are authoritative. ## GET /proposals List proposals Required scope: `proposals:read` Required scope: `proposals:read` - `status` (query) — Filter by status - `contact_id` (query) — Filter by contact - `opportunity_id` (query) — Filter by opportunity ```json { "data": [ { "id": 5, "proposal_number": "P-1005", "title": "Training package", "status": "draft", "contact_id": 42, "opportunity_id": 9, "total": 1200, "has_deposit": true, "deposit_percentage": 50, "deposit_amount": null, "customer_invoice_id": null } ] } ``` ## POST /proposals Create draft proposal Required scope: `proposals:write` Required scope: `proposals:write` ```json { "data": { "id": 5, "proposal_number": "P-1005", "title": "Training package", "status": "draft", "contact_id": 42, "opportunity_id": 9, "total": 1200, "has_deposit": true, "deposit_percentage": 50, "deposit_amount": null, "customer_invoice_id": null } } ``` ## GET /proposals/{proposalId} Get proposal Includes public_url. Does not return share_token. Required scope: `proposals:read` Required scope: `proposals:read` - `proposalId` (path, required) — Proposal ID ```json { "data": { "id": 5, "proposal_number": "P-1005", "title": "Training package", "status": "draft", "contact_id": 42, "opportunity_id": 9, "total": 1200, "has_deposit": true, "deposit_percentage": 50, "deposit_amount": null, "customer_invoice_id": null, "public_url": "https://example.test/p/abc", "deposit_charge": 600, "remaining_balance": 600, "checkout_charge": 600, "items": [ { "name": "Private lesson", "quantity": 2, "unit_price": 600, "total": 1200 } ], "deposit": { "has_deposit": true, "deposit_percentage": 50, "deposit_charge": 600, "remaining_balance": 600, "customer_invoice_id": 501, "invoice": null, "invoice_payments": [], "transactions": [] } } } ``` ## PUT /proposals/{proposalId} Update proposal Follows staff edit rules. Sent or viewed proposals reset to draft. Required scope: `proposals:write` Required scope: `proposals:write` - `proposalId` (path, required) — Proposal ID ```json { "data": { "id": 5, "proposal_number": "P-1005", "title": "Training package", "status": "draft", "contact_id": 42, "opportunity_id": 9, "total": 1200, "has_deposit": true, "deposit_percentage": 50, "deposit_amount": null, "customer_invoice_id": null } } ``` ## DELETE /proposals/{proposalId} Delete proposal Signed proposals cannot be deleted. Required scope: `proposals:delete` Required scope: `proposals:delete` - `proposalId` (path, required) — Proposal ID ```json { "message": "Proposal deleted successfully" } ``` ## GET /proposals/{proposalId}/payments Get proposal deposit, invoice, and payment activity Returns deposit configuration, the linked customer invoice when a deposit was collected, invoice payment rows, and gateway transactions for originating_module=proposal. Customer card capture stays on the tokenized web flow. Required scope: `proposals:read` Required scope: `proposals:read` - `proposalId` (path, required) — Proposal ID ```json { "data": { "has_deposit": true, "deposit_percentage": 50, "deposit_charge": 600, "remaining_balance": 600, "customer_invoice_id": 501, "invoice": null, "invoice_payments": [], "transactions": [] } } ``` ## POST /proposals/{proposalId}/send Send proposal email and mark sent Required scope: `proposals:write` Required scope: `proposals:write` - `proposalId` (path, required) — Proposal ID ```json { "data": { "id": 5, "proposal_number": "P-1005", "title": "Training package", "status": "sent", "contact_id": 42, "opportunity_id": 9, "total": 1200, "has_deposit": true, "deposit_percentage": 50, "deposit_amount": null, "customer_invoice_id": null } } ``` ## POST /proposals/{proposalId}/duplicate Duplicate proposal as a new draft Required scope: `proposals:write` Required scope: `proposals:write` - `proposalId` (path, required) — Proposal ID ```json { "data": { "id": 5, "proposal_number": "P-1005", "title": "Training package (Copy)", "status": "draft", "contact_id": 42, "opportunity_id": 9, "total": 1200, "has_deposit": true, "deposit_percentage": 50, "deposit_amount": null, "customer_invoice_id": null } } ``` ## GET /contacts/{contact}/proposals List proposals for a contact Required scope: `proposals:read` Required scope: `proposals:read` - `contact` (path, required) — Contact ID ```json { "data": [ { "id": 5, "proposal_number": "P-1005", "title": "Training package", "status": "draft", "contact_id": 42, "opportunity_id": 9, "total": 1200, "has_deposit": true, "deposit_percentage": 50, "deposit_amount": null, "customer_invoice_id": null } ] } ``` ## GET /opportunities/{opportunityId}/proposals List proposals for an opportunity Required scope: `proposals:read` Required scope: `proposals:read` - `opportunityId` (path, required) — Opportunity ID ```json { "data": [ { "id": 5, "proposal_number": "P-1005", "title": "Training package", "status": "draft", "contact_id": 42, "opportunity_id": 9, "total": 1200, "has_deposit": true, "deposit_percentage": 50, "deposit_amount": null, "customer_invoice_id": null } ] } ``` --- # Purchasing Canonical URL: https://coreware.com/docs/api/purchasing.md API version: v1 These operations are generated from OpenAPI. They are authoritative. ## GET /purchase-orders List purchase orders Required scope: `purchasing:read` Required scope: `purchasing:read` - `location_id` (query) — Filter by location - `supplier_id` (query) — Filter by supplier - `include_items` (query) — Include line items - `per_page` (query) — Results per page ```json { "data": [ { "receiving_id": 44, "is_po": true, "suspended": true, "location_id": 1, "supplier_id": 9, "total": 120.5, "items": [ { "item_id": 88, "quantity_purchased": 10, "quantity_received": 0 } ] } ], "meta": { "current_page": 1, "last_page": 1, "per_page": 25, "total": 1 } } ``` ## POST /purchase-orders Create a draft purchase order Creates a suspended purchase order and increments quantity on order. POS receive-against-PO is not available. Required scope: `purchasing:write` Required scope: `purchasing:write` ```json { "data": { "receiving_id": 44, "is_po": true, "suspended": true, "location_id": 1, "supplier_id": 9, "total": 120.5, "items": [ { "item_id": 88, "quantity_purchased": 10, "quantity_received": 0 } ] } } ``` ## GET /purchase-orders/{purchaseOrder} Get a purchase order Required scope: `purchasing:read` Required scope: `purchasing:read` - `purchaseOrder` (path, required) — Purchase order receiving ID ```json { "data": { "receiving_id": 44, "is_po": true, "suspended": true, "location_id": 1, "supplier_id": 9, "total": 120.5, "items": [ { "item_id": 88, "quantity_purchased": 10, "quantity_received": 0 } ] } } ``` ## DELETE /purchase-orders/{purchaseOrder} Delete a draft purchase order Only suspended purchase orders with no received quantity can be deleted. Required scope: `purchasing:delete` Required scope: `purchasing:delete` - `purchaseOrder` (path, required) — Purchase order receiving ID ```json { "message": "Purchase order deleted successfully" } ``` ## GET /receivings List receivings Excludes purchase orders and inventory transfers. Required scope: `purchasing:read` Required scope: `purchasing:read` - `location_id` (query) — Filter by location - `supplier_id` (query) — Filter by supplier - `include_items` (query) — Include line items - `per_page` (query) — Results per page ```json { "data": [ { "receiving_id": 70, "is_po": false, "total": 40 } ], "meta": { "current_page": 1, "last_page": 1, "per_page": 25, "total": 1 } } ``` ## GET /receivings/{receiving} Get a receiving Required scope: `purchasing:read` Required scope: `purchasing:read` - `receiving` (path, required) — Receiving ID ```json { "data": { "receiving_id": 70, "items": [] } } ``` ## GET /inventory-transfers List inventory transfers Required scope: `purchasing:read` Required scope: `purchasing:read` - `location_id` (query) — Source or destination location - `type` (query) — out, in, or immediate - `include_items` (query) — Include line items - `per_page` (query) — Results per page ```json { "data": [ { "receiving_id": 91, "receiving_type": "TRANSFER OUT", "location_id": 1, "transfer_to_location_id": 2, "suspended": true } ], "meta": { "current_page": 1, "last_page": 1, "per_page": 25, "total": 1 } } ``` ## GET /inventory-transfers/{transfer} Get an inventory transfer Required scope: `purchasing:read` Required scope: `purchasing:read` - `transfer` (path, required) — Inventory transfer receiving ID ```json { "data": { "receiving_id": 91, "receiving_type": "TRANSFER OUT", "location_id": 1, "transfer_to_location_id": 2, "suspended": true } } ``` ## POST /inventory-transfers/out Ship inventory to another location Required scope: `purchasing:write` Required scope: `purchasing:write` ```json { "data": { "transfer_id": 91 } } ``` ## POST /inventory-transfers/in Receive a pending transfer out Required scope: `purchasing:write` Required scope: `purchasing:write` ```json { "data": { "transfer_id": 91, "is_pending": false } } ``` ## POST /inventory-transfers/immediate Move inventory between locations immediately Required scope: `purchasing:write` Required scope: `purchasing:write` ```json { "data": { "transfer_id": 92 } } ``` ## POST /inventory-transfers/{transfer}/cancel Cancel a pending inventory transfer Required scope: `purchasing:write` Required scope: `purchasing:write` - `transfer` (path, required) — Inventory transfer receiving ID ```json { "message": "Inventory transfer cancelled successfully" } ``` ## GET /suppliers List suppliers Required scope: `purchasing:read` Required scope: `purchasing:read` - `search` (query) — Search company, account, or FFL number - `per_page` (query) — Results per page ```json { "data": [ { "id": 9, "company_name": "Acme Distributing" } ], "meta": { "current_page": 1, "last_page": 1, "per_page": 25, "total": 1 } } ``` ## POST /suppliers Create a supplier Required scope: `purchasing:write` Required scope: `purchasing:write` ```json { "data": { "id": 9, "company_name": "Acme Distributing" } } ``` ## GET /suppliers/{supplier} Get a supplier Required scope: `purchasing:read` Required scope: `purchasing:read` - `supplier` (path, required) — Supplier ID ```json { "data": { "id": 9, "company_name": "Acme Distributing" } } ``` ## PUT /suppliers/{supplier} Update a supplier Required scope: `purchasing:write` Required scope: `purchasing:write` - `supplier` (path, required) — Supplier ID ```json { "data": { "id": 9, "company_name": "Acme Distributing LLC" } } ``` ## PATCH /suppliers/{supplier} Partially update a supplier Required scope: `purchasing:write` Required scope: `purchasing:write` - `supplier` (path, required) — Supplier ID ```json { "data": { "id": 9, "account_number": "ACME-2" } } ``` --- # Resource Center Canonical URL: https://coreware.com/docs/api/resource-center.md API version: v1 These operations are generated from OpenAPI. They are authoritative. ## GET /guides/articles List user guides Pass source=global to list global catalog articles (primary tenant + Coreware employee only). Default is tenant. Required scope: `guides:read` Required scope: `guides:read` - `search` (query) — Search title, description, or slug - `content_type` (query) — video, article, walkthrough, or mixed - `difficulty` (query) — beginner, intermediate, or advanced - `is_published` (query) — Filter by published state - `guide_collection_id` (query) — Limit to one collection - `source` (query) — tenant (default) or global - `page` (query) — Page number - `per_page` (query) — Results per page ```json { "data": [ { "id": 12, "title": "Register a sale", "slug": "register-a-sale-1710000000", "description": "Walk staff through the register.", "content": "Open the register and scan the item.", "content_type": "walkthrough", "video_url": null, "difficulty": "beginner", "estimated_minutes": 5, "is_published": true, "is_featured": false, "sort_order": 0, "guide_collection_id": 3, "source": "tenant", "deep_link": "https://example.com/learning?guide=register-a-sale-1710000000&source=tenant&review=1", "steps": [ { "id": 40, "guide_article_id": 12, "title": "Open the register", "content": "Click Sales, then New sale.", "step_type": "text", "video_url": null, "annotations_json": null, "sort_order": 0, "screenshot_url": null, "annotated_screenshot_url": null, "media": { "attached": false } } ] } ], "meta": { "current_page": 1, "last_page": 1, "per_page": 25, "total": 1 } } ``` ## POST /guides/articles Create a user guide Creates a tenant-owned draft article. Slug is generated from title unless provided. To publish to the global catalog, use the promote endpoint. Video upload and AI generate are not available. Required scope: `guides:write` Required scope: `guides:write` ```json { "data": { "id": 12, "title": "Register a sale", "slug": "register-a-sale-1710000000", "description": "Walk staff through the register.", "content": "Open the register and scan the item.", "content_type": "walkthrough", "video_url": null, "difficulty": "beginner", "estimated_minutes": 5, "is_published": true, "is_featured": false, "sort_order": 0, "guide_collection_id": 3, "source": "tenant", "deep_link": "https://example.com/learning?guide=register-a-sale-1710000000&source=tenant&review=1", "steps": [ { "id": 40, "guide_article_id": 12, "title": "Open the register", "content": "Click Sales, then New sale.", "step_type": "text", "video_url": null, "annotations_json": null, "sort_order": 0, "screenshot_url": null, "annotated_screenshot_url": null, "media": { "attached": false } } ] } } ``` ## GET /guides/articles/{articleId} Get a user guide Includes walkthrough steps. Required scope: `guides:read` Required scope: `guides:read` - `articleId` (path, required) — Guide article ID ```json { "data": { "id": 12, "title": "Register a sale", "slug": "register-a-sale-1710000000", "description": "Walk staff through the register.", "content": "Open the register and scan the item.", "content_type": "walkthrough", "video_url": null, "difficulty": "beginner", "estimated_minutes": 5, "is_published": true, "is_featured": false, "sort_order": 0, "guide_collection_id": 3, "source": "tenant", "deep_link": "https://example.com/learning?guide=register-a-sale-1710000000&source=tenant&review=1", "steps": [ { "id": 40, "guide_article_id": 12, "title": "Open the register", "content": "Click Sales, then New sale.", "step_type": "text", "video_url": null, "annotations_json": null, "sort_order": 0, "screenshot_url": null, "annotated_screenshot_url": null, "media": { "attached": false } } ] } } ``` ## PUT /guides/articles/{articleId} Update a user guide Required scope: `guides:write` Required scope: `guides:write` - `articleId` (path, required) — Guide article ID ```json { "data": { "id": 12, "title": "Register a sale", "slug": "register-a-sale-1710000000", "description": "Walk staff through the register.", "content": "Open the register and scan the item.", "content_type": "walkthrough", "video_url": null, "difficulty": "beginner", "estimated_minutes": 5, "is_published": true, "is_featured": false, "sort_order": 0, "guide_collection_id": 3, "source": "tenant", "deep_link": "https://example.com/learning?guide=register-a-sale-1710000000&source=tenant&review=1", "steps": [ { "id": 40, "guide_article_id": 12, "title": "Open the register", "content": "Click Sales, then New sale.", "step_type": "text", "video_url": null, "annotations_json": null, "sort_order": 0, "screenshot_url": null, "annotated_screenshot_url": null, "media": { "attached": false } } ] } } ``` ## PATCH /guides/articles/{articleId} Update a user guide Required scope: `guides:write` Required scope: `guides:write` - `articleId` (path, required) — Guide article ID ```json { "data": { "id": 12, "title": "Register a sale", "slug": "register-a-sale-1710000000", "description": "Walk staff through the register.", "content": "Open the register and scan the item.", "content_type": "walkthrough", "video_url": null, "difficulty": "beginner", "estimated_minutes": 5, "is_published": true, "is_featured": false, "sort_order": 0, "guide_collection_id": 3, "source": "tenant", "deep_link": "https://example.com/learning?guide=register-a-sale-1710000000&source=tenant&review=1", "steps": [ { "id": 40, "guide_article_id": 12, "title": "Open the register", "content": "Click Sales, then New sale.", "step_type": "text", "video_url": null, "annotations_json": null, "sort_order": 0, "screenshot_url": null, "annotated_screenshot_url": null, "media": { "attached": false } } ] } } ``` ## DELETE /guides/articles/{articleId} Delete a user guide Required scope: `guides:delete` Required scope: `guides:delete` - `articleId` (path, required) — Guide article ID ```json { "message": "Guide deleted successfully" } ``` ## GET /guides/articles/{articleId}/steps List walkthrough steps Required scope: `guides:read` Required scope: `guides:read` - `articleId` (path, required) — Guide article ID ```json { "data": [ { "id": 40, "guide_article_id": 12, "title": "Open the register", "content": "Click Sales, then New sale.", "step_type": "text", "video_url": null, "annotations_json": null, "sort_order": 0, "screenshot_url": null, "annotated_screenshot_url": null, "media": { "attached": false } } ] } ``` ## POST /guides/articles/{articleId}/steps Add a walkthrough step Required scope: `guides:write` Required scope: `guides:write` - `articleId` (path, required) — Guide article ID ```json { "data": { "id": 40, "guide_article_id": 12, "title": "Open the register", "content": "Click Sales, then New sale.", "step_type": "text", "video_url": null, "annotations_json": null, "sort_order": 0, "screenshot_url": null, "annotated_screenshot_url": null, "media": { "attached": false } } } ``` ## PUT /guides/articles/{articleId}/steps/{stepId} Update a walkthrough step Required scope: `guides:write` Required scope: `guides:write` - `articleId` (path, required) — Guide article ID - `stepId` (path, required) — Guide step ID ```json { "data": { "id": 40, "guide_article_id": 12, "title": "Open the register", "content": "Click Sales, then New sale.", "step_type": "text", "video_url": null, "annotations_json": null, "sort_order": 0, "screenshot_url": null, "annotated_screenshot_url": null, "media": { "attached": false } } } ``` ## PATCH /guides/articles/{articleId}/steps/{stepId} Update a walkthrough step Required scope: `guides:write` Required scope: `guides:write` - `articleId` (path, required) — Guide article ID - `stepId` (path, required) — Guide step ID ```json { "data": { "id": 40, "guide_article_id": 12, "title": "Open the register", "content": "Click Sales, then New sale.", "step_type": "text", "video_url": null, "annotations_json": null, "sort_order": 0, "screenshot_url": null, "annotated_screenshot_url": null, "media": { "attached": false } } } ``` ## DELETE /guides/articles/{articleId}/steps/{stepId} Delete a walkthrough step Required scope: `guides:delete` Required scope: `guides:delete` - `articleId` (path, required) — Guide article ID - `stepId` (path, required) — Guide step ID ```json { "message": "Guide step deleted successfully" } ``` ## POST /guides/articles/{articleId}/steps/reorder Reorder walkthrough steps Replaces step sort_order using the ordered step_ids array. Required scope: `guides:write` Required scope: `guides:write` - `articleId` (path, required) — Guide article ID ```json { "data": [ { "id": 40, "guide_article_id": 12, "title": "Open the register", "content": "Click Sales, then New sale.", "step_type": "text", "video_url": null, "annotations_json": null, "sort_order": 0, "screenshot_url": null, "annotated_screenshot_url": null, "media": { "attached": false } } ] } ``` ## POST /guides/articles/{articleId}/steps/{stepId}/still Upload a step screenshot Replaces the existing screenshot or annotated_screenshot. Multipart file upload. Required scope: `guides:write` Required scope: `guides:write` - `articleId` (path, required) — Guide article ID - `stepId` (path, required) — Guide step ID ```json { "data": { "url": "https://...", "media_uuid": "abc-123", "collection": "screenshot", "attached": true } } ``` ## DELETE /guides/articles/{articleId}/steps/{stepId}/still Delete a step screenshot Required scope: `guides:delete` Required scope: `guides:delete` - `articleId` (path, required) — Guide article ID - `stepId` (path, required) — Guide step ID ```json { "data": { "id": 40, "guide_article_id": 12, "title": "Open the register", "content": "Click Sales, then New sale.", "step_type": "text", "video_url": null, "annotations_json": null, "sort_order": 0, "screenshot_url": null, "annotated_screenshot_url": null, "media": { "attached": false } } } ``` ## POST /guides/articles/{articleId}/promote Promote a tenant guide to the global catalog Promotes a tenant draft into an existing global article identified by target_article_id or target_slug. Requires primary tenant and Coreware employee. Required scope: `guides:write` Required scope: `guides:write` - `articleId` (path, required) — Guide article ID ```json { "data": { "id": 12, "title": "Register a sale", "slug": "register-a-sale-1710000000", "description": "Walk staff through the register.", "content": "Open the register and scan the item.", "content_type": "walkthrough", "video_url": null, "difficulty": "beginner", "estimated_minutes": 5, "is_published": true, "is_featured": false, "sort_order": 0, "guide_collection_id": 3, "source": "global", "deep_link": "https://example.com/learning?guide=register-a-sale-1710000000&source=tenant&review=1", "steps": [ { "id": 40, "guide_article_id": 12, "title": "Open the register", "content": "Click Sales, then New sale.", "step_type": "text", "video_url": null, "annotations_json": null, "sort_order": 0, "screenshot_url": null, "annotated_screenshot_url": null, "media": { "attached": false } } ] } } ``` ## GET /guides/tours List guided tours Required scope: `guides:read` Required scope: `guides:read` - `search` (query) — Search by name - `trigger_type` (query) — manual, first_visit, or resource_center - `is_published` (query) — Filter by published state - `page` (query) — Page number - `per_page` (query) — Results per page ```json { "data": [ { "id": 8, "name": "First sale tour", "description": "Highlight the register button.", "page_url_pattern": "/sales*", "trigger_type": "first_visit", "steps": [ { "element": "#new-sale", "popover": { "title": "Start a sale", "description": "Click here to open the register.", "side": "bottom", "align": "start" } } ], "is_published": true, "sort_order": 0 } ], "meta": { "current_page": 1, "last_page": 1, "per_page": 25, "total": 1 } } ``` ## POST /guides/tours Create a guided tour Creates a tenant-owned tour. source=global is not accepted. Each step needs element and popover.title. Required scope: `guides:write` Required scope: `guides:write` ```json { "data": { "id": 8, "name": "First sale tour", "description": "Highlight the register button.", "page_url_pattern": "/sales*", "trigger_type": "first_visit", "steps": [ { "element": "#new-sale", "popover": { "title": "Start a sale", "description": "Click here to open the register.", "side": "bottom", "align": "start" } } ], "is_published": true, "sort_order": 0 } } ``` ## GET /guides/tours/{tourId} Get a guided tour Required scope: `guides:read` Required scope: `guides:read` - `tourId` (path, required) — Guided tour ID ```json { "data": { "id": 8, "name": "First sale tour", "description": "Highlight the register button.", "page_url_pattern": "/sales*", "trigger_type": "first_visit", "steps": [ { "element": "#new-sale", "popover": { "title": "Start a sale", "description": "Click here to open the register.", "side": "bottom", "align": "start" } } ], "is_published": true, "sort_order": 0 } } ``` ## PUT /guides/tours/{tourId} Update a guided tour Required scope: `guides:write` Required scope: `guides:write` - `tourId` (path, required) — Guided tour ID ```json { "data": { "id": 8, "name": "First sale tour", "description": "Highlight the register button.", "page_url_pattern": "/sales*", "trigger_type": "first_visit", "steps": [ { "element": "#new-sale", "popover": { "title": "Start a sale", "description": "Click here to open the register.", "side": "bottom", "align": "start" } } ], "is_published": true, "sort_order": 0 } } ``` ## PATCH /guides/tours/{tourId} Update a guided tour Required scope: `guides:write` Required scope: `guides:write` - `tourId` (path, required) — Guided tour ID ```json { "data": { "id": 8, "name": "First sale tour", "description": "Highlight the register button.", "page_url_pattern": "/sales*", "trigger_type": "first_visit", "steps": [ { "element": "#new-sale", "popover": { "title": "Start a sale", "description": "Click here to open the register.", "side": "bottom", "align": "start" } } ], "is_published": true, "sort_order": 0 } } ``` ## DELETE /guides/tours/{tourId} Delete a guided tour Required scope: `guides:delete` Required scope: `guides:delete` - `tourId` (path, required) — Guided tour ID ```json { "message": "Guided tour deleted successfully" } ``` ## GET /guide-collections List guide collections Required scope: `guides:read` Required scope: `guides:read` - `page` (query) — Page number - `per_page` (query) — Results per page ```json { "data": [ { "id": 3, "ulid": "01HZXEXAMPLEULID000000000", "name": "Sales onboarding", "description": "Guides for new cashiers.", "parent_collection_id": null, "icon": "book", "color": "#2563EB", "sort_order": 1, "is_published": true, "is_system": false, "is_hidden": false } ], "meta": { "current_page": 1, "last_page": 1, "per_page": 25, "total": 1 } } ``` ## POST /guide-collections Create a guide collection Creates a non-system folder. is_system cannot be set from this API. Required scope: `guides:write` Required scope: `guides:write` ```json { "data": { "id": 3, "ulid": "01HZXEXAMPLEULID000000000", "name": "Sales onboarding", "description": "Guides for new cashiers.", "parent_collection_id": null, "icon": "book", "color": "#2563EB", "sort_order": 1, "is_published": true, "is_system": false, "is_hidden": false } } ``` ## GET /guide-collections/{collectionId} Get a guide collection Required scope: `guides:read` Required scope: `guides:read` - `collectionId` (path, required) — Guide collection ID ```json { "data": { "id": 3, "ulid": "01HZXEXAMPLEULID000000000", "name": "Sales onboarding", "description": "Guides for new cashiers.", "parent_collection_id": null, "icon": "book", "color": "#2563EB", "sort_order": 1, "is_published": true, "is_system": false, "is_hidden": false } } ``` ## PUT /guide-collections/{collectionId} Update a guide collection System collections cannot be changed. Required scope: `guides:write` Required scope: `guides:write` - `collectionId` (path, required) — Guide collection ID ```json { "data": { "id": 3, "ulid": "01HZXEXAMPLEULID000000000", "name": "Sales onboarding", "description": "Guides for new cashiers.", "parent_collection_id": null, "icon": "book", "color": "#2563EB", "sort_order": 1, "is_published": true, "is_system": false, "is_hidden": false } } ``` ## PATCH /guide-collections/{collectionId} Update a guide collection System collections cannot be changed. Required scope: `guides:write` Required scope: `guides:write` - `collectionId` (path, required) — Guide collection ID ```json { "data": { "id": 3, "ulid": "01HZXEXAMPLEULID000000000", "name": "Sales onboarding", "description": "Guides for new cashiers.", "parent_collection_id": null, "icon": "book", "color": "#2563EB", "sort_order": 1, "is_published": true, "is_system": false, "is_hidden": false } } ``` ## DELETE /guide-collections/{collectionId} Delete a guide collection System collections cannot be deleted. Required scope: `guides:delete` Required scope: `guides:delete` - `collectionId` (path, required) — Guide collection ID ```json { "message": "Guide collection deleted successfully" } ``` ## PUT /guide-collections/{collectionId}/articles Assign guides to a collection Additive. Articles already in the collection are skipped. This is not replace-all. Required scope: `guides:write` Required scope: `guides:write` - `collectionId` (path, required) — Guide collection ID ```json { "data": { "assigned_count": 1, "collection": { "id": 3, "ulid": "01HZXEXAMPLEULID000000000", "name": "Sales onboarding", "description": "Guides for new cashiers.", "parent_collection_id": null, "icon": "book", "color": "#2563EB", "sort_order": 1, "is_published": true, "is_system": false, "is_hidden": false } } } ``` ## GET /guide-hotspots List page hotspots Required scope: `guides:read` Required scope: `guides:read` - `search` (query) — Search by title or name - `is_published` (query) — Filter by published state - `page` (query) — Page number - `per_page` (query) — Results per page ```json { "data": [ { "id": 6, "hotspot_type": "info", "display_style": "beacon", "title": "New sale", "content": "Start a transaction here.", "button_text": "Got it", "element_selector": "#new-sale", "page_url_pattern": "/sales*", "position": "right", "beacon_color": "#2563EB", "trigger": "click", "is_published": true, "show_once": false } ], "meta": { "current_page": 1, "last_page": 1, "per_page": 25, "total": 1 } } ``` ## POST /guide-hotspots Create a page hotspot Required scope: `guides:write` Required scope: `guides:write` ```json { "data": { "id": 6, "hotspot_type": "info", "display_style": "beacon", "title": "New sale", "content": "Start a transaction here.", "button_text": "Got it", "element_selector": "#new-sale", "page_url_pattern": "/sales*", "position": "right", "beacon_color": "#2563EB", "trigger": "click", "is_published": true, "show_once": false } } ``` ## GET /guide-hotspots/{hotspotId} Get a page hotspot Required scope: `guides:read` Required scope: `guides:read` - `hotspotId` (path, required) — Guide hotspot ID ```json { "data": { "id": 6, "hotspot_type": "info", "display_style": "beacon", "title": "New sale", "content": "Start a transaction here.", "button_text": "Got it", "element_selector": "#new-sale", "page_url_pattern": "/sales*", "position": "right", "beacon_color": "#2563EB", "trigger": "click", "is_published": true, "show_once": false } } ``` ## PUT /guide-hotspots/{hotspotId} Update a page hotspot Required scope: `guides:write` Required scope: `guides:write` - `hotspotId` (path, required) — Guide hotspot ID ```json { "data": { "id": 6, "hotspot_type": "info", "display_style": "beacon", "title": "New sale", "content": "Start a transaction here.", "button_text": "Got it", "element_selector": "#new-sale", "page_url_pattern": "/sales*", "position": "right", "beacon_color": "#2563EB", "trigger": "click", "is_published": true, "show_once": false } } ``` ## PATCH /guide-hotspots/{hotspotId} Update a page hotspot Required scope: `guides:write` Required scope: `guides:write` - `hotspotId` (path, required) — Guide hotspot ID ```json { "data": { "id": 6, "hotspot_type": "info", "display_style": "beacon", "title": "New sale", "content": "Start a transaction here.", "button_text": "Got it", "element_selector": "#new-sale", "page_url_pattern": "/sales*", "position": "right", "beacon_color": "#2563EB", "trigger": "click", "is_published": true, "show_once": false } } ``` ## DELETE /guide-hotspots/{hotspotId} Delete a page hotspot Required scope: `guides:delete` Required scope: `guides:delete` - `hotspotId` (path, required) — Guide hotspot ID ```json { "message": "Hotspot deleted successfully" } ``` ## GET /guide-checklists List onboarding checklists Required scope: `guides:read` Required scope: `guides:read` - `page` (query) — Page number - `per_page` (query) — Results per page ```json { "data": [ { "id": 4, "name": "Day-one cashier", "description": "Complete these before your first shift.", "target_audience": "new_user", "is_published": true, "dismissible": true, "sort_order": 0, "items": [ { "id": 9, "title": "Read the register guide", "description": null, "action_type": "link", "action_target": "/guides/register-a-sale", "sort_order": 0 } ] } ], "meta": { "current_page": 1, "last_page": 1, "per_page": 25, "total": 1 } } ``` ## POST /guide-checklists Create an onboarding checklist Required scope: `guides:write` Required scope: `guides:write` ```json { "data": { "id": 4, "name": "Day-one cashier", "description": "Complete these before your first shift.", "target_audience": "new_user", "is_published": true, "dismissible": true, "sort_order": 0, "items": [ { "id": 9, "title": "Read the register guide", "description": null, "action_type": "link", "action_target": "/guides/register-a-sale", "sort_order": 0 } ] } } ``` ## GET /guide-checklists/{checklistId} Get an onboarding checklist Required scope: `guides:read` Required scope: `guides:read` - `checklistId` (path, required) — Guide checklist ID ```json { "data": { "id": 4, "name": "Day-one cashier", "description": "Complete these before your first shift.", "target_audience": "new_user", "is_published": true, "dismissible": true, "sort_order": 0, "items": [ { "id": 9, "title": "Read the register guide", "description": null, "action_type": "link", "action_target": "/guides/register-a-sale", "sort_order": 0 } ] } } ``` ## PUT /guide-checklists/{checklistId} Update a checklist Sending items replaces the checklist items. Include existing item id values to keep them. Required scope: `guides:write` Required scope: `guides:write` - `checklistId` (path, required) — Guide checklist ID ```json { "data": { "id": 4, "name": "Day-one cashier", "description": "Complete these before your first shift.", "target_audience": "new_user", "is_published": true, "dismissible": true, "sort_order": 0, "items": [ { "id": 9, "title": "Read the register guide", "description": null, "action_type": "link", "action_target": "/guides/register-a-sale", "sort_order": 0 } ] } } ``` ## PATCH /guide-checklists/{checklistId} Update a checklist Sending items replaces the checklist items. Include existing item id values to keep them. Required scope: `guides:write` Required scope: `guides:write` - `checklistId` (path, required) — Guide checklist ID ```json { "data": { "id": 4, "name": "Day-one cashier", "description": "Complete these before your first shift.", "target_audience": "new_user", "is_published": true, "dismissible": true, "sort_order": 0, "items": [ { "id": 9, "title": "Read the register guide", "description": null, "action_type": "link", "action_target": "/guides/register-a-sale", "sort_order": 0 } ] } } ``` ## DELETE /guide-checklists/{checklistId} Delete an onboarding checklist Required scope: `guides:delete` Required scope: `guides:delete` - `checklistId` (path, required) — Guide checklist ID ```json { "message": "Checklist deleted successfully" } ``` --- # Sales Canonical URL: https://coreware.com/docs/api/sales.md API version: v1 These operations are generated from OpenAPI. They are authoritative. ## GET /contacts/{contact}/sales List contact purchases (sales with line items) Required scope: `sales:read` Required scope: `sales:read` - `contact` (path, required) — Contact ID - `from` (query) — Start datetime - `to` (query) — End datetime - `include_items` (query) — Include purchased line items - `per_page` (query) — Results per page ```json { "data": [ { "sale_id": 1001, "contact_id": 42, "customer_id": 42, "location_id": 1, "sale_time": "2026-07-01T15:30:00.000000Z", "suspended": 0, "comment": null, "customer_note": null, "subtotal": 49.99, "tax": 0, "total": 49.99, "item_count": 1, "invoice_id": 501, "items": [ { "line": 1, "item_id": 88, "item_number": "SKU-88", "description": "Demo Product", "quantity_purchased": 1, "item_unit_price": 49.99, "subtotal": 49.99, "tax": 0, "total": 49.99 } ], "invoice": { "id": 501, "sale_id": 1001, "contact_id": 42, "location_id": 1, "invoice_date": "2026-07-01", "due_date": "2026-07-15", "subtotal": 49.99, "tax_amount": 0, "total": 49.99, "amount_paid": 0, "balance_due": 49.99, "status": "open", "customer_po": null, "notes": null, "line_items": [ { "id": 1, "item_id": 88, "description": "Demo Product", "quantity": 1, "unit_price": 49.99, "total": 49.99 } ], "payments": [] } } ], "meta": { "current_page": 1, "last_page": 1, "per_page": 25, "total": 1 } } ``` ## GET /sales List sales POS sale create and card charging are not available. Returns and notes are nested under a sale. Required scope: `sales:read` Required scope: `sales:read` - `contact_id` (query) — Filter by contact ID - `customer_id` (query) — Filter by legacy customer ID - `employee_id` (query) — Filter by employee person ID - `location_id` (query) — Filter by location ID - `sale_type_id` (query) — Filter by sale type ID - `from` (query) — Start datetime (ISO 8601 or YYYY-MM-DD) - `to` (query) — End datetime (ISO 8601 or YYYY-MM-DD) - `suspended` (query) — Filter suspended vs completed sales - `is_return` (query) — Filter sales that are returns of another sale - `include_items` (query) — Include purchased line items on each sale - `include_payments` (query) — Include payments on each sale - `include_invoice` (query) — Include linked invoice_id when available - `per_page` (query) — Results per page ```json { "data": [ { "sale_id": 1001, "contact_id": 42, "customer_id": 42, "location_id": 1, "sale_time": "2026-07-01T15:30:00.000000Z", "suspended": 0, "comment": null, "customer_note": null, "subtotal": 49.99, "tax": 0, "total": 49.99, "item_count": 1 } ], "meta": { "current_page": 1, "last_page": 1, "per_page": 25, "total": 1 } } ``` ## GET /sale-types List sale types Required scope: `sales:read` Required scope: `sales:read` ```json { "data": [ { "id": 1, "name": "Sale", "treat_as_sale": true } ] } ``` ## GET /sales/{sale} Get sale with items, payments, notes, returns, totals, and invoice Required scope: `sales:read` Required scope: `sales:read` - `sale` (path, required) — Sale ID ```json { "data": { "sale_id": 1001, "contact_id": 42, "customer_id": 42, "location_id": 1, "sale_time": "2026-07-01T15:30:00.000000Z", "suspended": 0, "comment": null, "customer_note": null, "subtotal": 49.99, "tax": 0, "total": 49.99, "item_count": 1, "invoice_id": 501, "items": [ { "line": 1, "item_id": 88, "item_number": "SKU-88", "description": "Demo Product", "quantity_purchased": 1, "item_unit_price": 49.99, "subtotal": 49.99, "tax": 0, "total": 49.99 } ], "invoice": { "id": 501, "sale_id": 1001, "contact_id": 42, "location_id": 1, "invoice_date": "2026-07-01", "due_date": "2026-07-15", "subtotal": 49.99, "tax_amount": 0, "total": 49.99, "amount_paid": 0, "balance_due": 49.99, "status": "open", "customer_po": null, "notes": null, "line_items": [ { "id": 1, "item_id": 88, "description": "Demo Product", "quantity": 1, "unit_price": 49.99, "total": 49.99 } ], "payments": [] }, "payments": [ { "payment_type": "credit_card", "payment_amount": 49.99, "payment_date": "2026-07-01T15:30:00.000000Z" } ] } } ``` ## GET /sales/{sale}/items List sale line items Required scope: `sales:read` Required scope: `sales:read` - `sale` (path, required) — Sale ID ```json { "data": [ { "item_id": 88, "quantity_purchased": 1, "total": 29.99 } ] } ``` ## GET /sales/{sale}/payments List payments recorded on a sale Required scope: `sales:read` Required scope: `sales:read` - `sale` (path, required) — Sale ID ```json { "data": [ { "payment_type": "credit_card", "payment_amount": 49.99, "payment_date": "2026-07-01T15:30:00.000000Z" } ] } ``` ## GET /sales/{sale}/notes List sale notes Required scope: `sales:read` Required scope: `sales:read` - `sale` (path, required) — Sale ID ```json { "data": [ { "note_id": 1, "note": "Hold for pickup", "internal": true } ] } ``` ## POST /sales/{sale}/notes Add a sale note Required scope: `sales:write` Required scope: `sales:write` - `sale` (path, required) — Sale ID ```json { "data": { "note_id": 1, "note": "Hold for pickup" } } ``` ## GET /sales/{sale}/returns List returns of this sale Required scope: `sales:read` Required scope: `sales:read` - `sale` (path, required) — Sale ID ```json { "data": [ { "sale_id": 902, "total": -29.99 } ] } ``` --- # Scheduling Canonical URL: https://coreware.com/docs/api/scheduling.md API version: v1 These operations are generated from OpenAPI. They are authoritative. ## GET /availability Cross-offering availability search Search open sessions/slots across offerings. When offering_id is omitted, class/event catalog sessions are returned. Required scope: `scheduling:read` Required scope: `scheduling:read` - `offering_id` (query) — Limit to one offering - `from` (query) — Start date YYYY-MM-DD - `to` (query) — End date YYYY-MM-DD - `location_id` (query) — Filter by location - `type` (query) — Offering type - `available_only` (query) — Only open slots - `page` (query) — Page - `per_page` (query) — Results per page ```json { "data": [ { "offering_id": 10, "offering_session_id": 555, "location_id": 1, "date": "2026-08-15", "start_time": "10:00:00", "end_time": "11:00:00", "capacity": 10, "booked_count": 3, "remaining_capacity": 7, "timezone": "America/Chicago", "is_available": true } ], "meta": { "current_page": 1, "last_page": 1, "per_page": 50, "total": 1 } } ``` ## GET /bookings List bookings Required scope: `scheduling:read` Required scope: `scheduling:read` - `contact_id` (query) — Filter by contact - `offering_id` (query) — Filter by offering - `location_id` (query) — Filter by location - `status` (query) — Filter by booking status - `from` (query) — Start date YYYY-MM-DD - `to` (query) — End date YYYY-MM-DD - `per_page` (query) — Results per page ```json { "data": [ { "id": 55, "booking_reference": "BK-10055", "status": "confirmed", "payment_status": "unpaid", "offering_id": 3, "offering_session_id": 555, "location_id": 1, "booking_date": "2026-07-15", "start_time": "14:00:00", "end_time": "15:00:00", "duration_minutes": 60, "customer_name": "Jane Doe", "customer_email": "jane@example.com", "total_price": 75, "amount_paid": 0, "can_cancel": true, "can_reschedule": true, "policy": { "can_cancel": true, "can_reschedule": true, "cancel_window": { "value": 24, "unit": "hours" }, "reschedule_window": { "value": 12, "unit": "hours" } }, "booking_contact_role": "primary" } ], "meta": { "current_page": 1, "last_page": 1, "per_page": 50, "total": 1 } } ``` ## POST /bookings Create booking (simple or checkout) Supports mode=simple (contact_id reservation) and mode=checkout (attendees[]). Capacity is enforced server-side; full slots return 422 with code capacity_exceeded and remaining_capacity. Payment gateway charging is out of scope for v1 — omit payment or leave pending/unpaid. Required scope: `scheduling:write` Required scope: `scheduling:write` ```json { "data": { "id": 55, "booking_reference": "BK-10055", "status": "confirmed", "payment_status": "unpaid", "offering_id": 3, "offering_session_id": 555, "location_id": 1, "booking_date": "2026-07-15", "start_time": "14:00:00", "end_time": "15:00:00", "duration_minutes": 60, "customer_name": "Jane Doe", "customer_email": "jane@example.com", "total_price": 75, "amount_paid": 0, "can_cancel": true, "can_reschedule": true, "policy": { "can_cancel": true, "can_reschedule": true, "cancel_window": { "value": 24, "unit": "hours" }, "reschedule_window": { "value": 12, "unit": "hours" } }, "booking_contact_role": "primary", "contacts": [ { "contact_id": 42, "booking_contact_role": "primary", "name": "Jane Doe" } ], "offering": { "id": 10, "name": "Court Rental", "public_name": "Court Rental", "slug": "court-rental", "type": "facility", "status": "active", "capacity": 4, "cancel_until_value": 24, "cancel_until_unit": "hours", "reschedule_until_value": 12, "reschedule_until_unit": "hours", "policy": { "cancel_until_value": 24, "cancel_until_unit": "hours", "reschedule_until_value": 12, "reschedule_until_unit": "hours", "no_refund_after_window": false }, "locations": [ { "location_id": 1, "custom_location_id": null, "name": "Main Campus", "timezone": "America/Chicago" } ] }, "session": { "id": 555, "capacity": 10, "booked_count": 3, "remaining_capacity": 7 } }, "message": "Booking created successfully" } ``` ## GET /contacts/{contact}/bookings List contact bookings/appointments Required scope: `scheduling:read` Required scope: `scheduling:read` - `contact` (path, required) — Contact ID - `status` (query) — Filter by booking status - `from` (query) — Start date (YYYY-MM-DD) - `to` (query) — End date (YYYY-MM-DD) - `per_page` (query) — Results per page ```json { "data": [ { "id": 55, "booking_reference": "BK-10055", "status": "confirmed", "payment_status": "unpaid", "offering_id": 3, "offering_session_id": 555, "location_id": 1, "booking_date": "2026-07-15", "start_time": "14:00:00", "end_time": "15:00:00", "duration_minutes": 60, "customer_name": "Jane Doe", "customer_email": "jane@example.com", "total_price": 75, "amount_paid": 0, "can_cancel": true, "can_reschedule": true, "policy": { "can_cancel": true, "can_reschedule": true, "cancel_window": { "value": 24, "unit": "hours" }, "reschedule_window": { "value": 12, "unit": "hours" } }, "booking_contact_role": "primary" } ], "meta": { "current_page": 1, "last_page": 1, "per_page": 25, "total": 1 } } ``` ## POST /bookings/reserve-spot Create soft hold on a slot Required scope: `scheduling:write` Required scope: `scheduling:write` ```json { "data": { "token": "11111111-2222-3333-4444-555555555555", "expires_at": "2026-08-15T10:15:00.000000Z", "ttl_seconds": 900, "spots_reserved": 1 } } ``` ## DELETE /bookings/reservations/{token} Release soft hold Required scope: `scheduling:write` Required scope: `scheduling:write` - `token` (path, required) — Reservation token ```json { "message": "Spot reservation released successfully" } ``` ## GET /bookings/{booking} Get booking details Enriched with offering, session, contacts, and policy (can_cancel / can_reschedule / cancel_window). Required scope: `scheduling:read` Required scope: `scheduling:read` - `booking` (path, required) — Booking ID ```json { "data": { "id": 55, "booking_reference": "BK-10055", "status": "confirmed", "payment_status": "unpaid", "offering_id": 3, "offering_session_id": 555, "location_id": 1, "booking_date": "2026-07-15", "start_time": "14:00:00", "end_time": "15:00:00", "duration_minutes": 60, "customer_name": "Jane Doe", "customer_email": "jane@example.com", "total_price": 75, "amount_paid": 0, "can_cancel": true, "can_reschedule": true, "policy": { "can_cancel": true, "can_reschedule": true, "cancel_window": { "value": 24, "unit": "hours" }, "reschedule_window": { "value": 12, "unit": "hours" } }, "booking_contact_role": "primary", "contacts": [ { "contact_id": 42, "booking_contact_role": "primary", "name": "Jane Doe" } ], "offering": { "id": 10, "name": "Court Rental", "public_name": "Court Rental", "slug": "court-rental", "type": "facility", "status": "active", "capacity": 4, "cancel_until_value": 24, "cancel_until_unit": "hours", "reschedule_until_value": 12, "reschedule_until_unit": "hours", "policy": { "cancel_until_value": 24, "cancel_until_unit": "hours", "reschedule_until_value": 12, "reschedule_until_unit": "hours", "no_refund_after_window": false }, "locations": [ { "location_id": 1, "custom_location_id": null, "name": "Main Campus", "timezone": "America/Chicago" } ] }, "session": { "id": 555, "capacity": 10, "booked_count": 3, "remaining_capacity": 7 } } } ``` ## PATCH /bookings/{booking} Update booking notes/customer fields Does not change schedule. Use reschedule for date/time changes. Required scope: `scheduling:write` Required scope: `scheduling:write` - `booking` (path, required) — Booking ID ```json { "data": { "id": 55, "booking_reference": "BK-10055", "status": "confirmed", "payment_status": "unpaid", "offering_id": 3, "offering_session_id": 555, "location_id": 1, "booking_date": "2026-07-15", "start_time": "14:00:00", "end_time": "15:00:00", "duration_minutes": 60, "customer_name": "Jane Doe", "customer_email": "jane@example.com", "total_price": 75, "amount_paid": 0, "can_cancel": true, "can_reschedule": true, "policy": { "can_cancel": true, "can_reschedule": true, "cancel_window": { "value": 24, "unit": "hours" }, "reschedule_window": { "value": 12, "unit": "hours" } }, "booking_contact_role": "primary", "contacts": [ { "contact_id": 42, "booking_contact_role": "primary", "name": "Jane Doe" } ], "offering": { "id": 10, "name": "Court Rental", "public_name": "Court Rental", "slug": "court-rental", "type": "facility", "status": "active", "capacity": 4, "cancel_until_value": 24, "cancel_until_unit": "hours", "reschedule_until_value": 12, "reschedule_until_unit": "hours", "policy": { "cancel_until_value": 24, "cancel_until_unit": "hours", "reschedule_until_value": 12, "reschedule_until_unit": "hours", "no_refund_after_window": false }, "locations": [ { "location_id": 1, "custom_location_id": null, "name": "Main Campus", "timezone": "America/Chicago" } ] }, "session": { "id": 555, "capacity": 10, "booked_count": 3, "remaining_capacity": 7 } }, "message": "Booking updated successfully" } ``` ## DELETE /bookings/{booking} Cancel booking (DELETE alias) Alias of POST /bookings/{booking}/cancel. Rejected past per-offering cancel window with code cancel_window_passed. Required scope: `scheduling:delete` Required scope: `scheduling:delete` - `booking` (path, required) — Booking ID ```json { "data": { "booking": { "id": 55, "booking_reference": "BK-10055", "status": "confirmed", "payment_status": "unpaid", "offering_id": 3, "offering_session_id": 555, "location_id": 1, "booking_date": "2026-07-15", "start_time": "14:00:00", "end_time": "15:00:00", "duration_minutes": 60, "customer_name": "Jane Doe", "customer_email": "jane@example.com", "total_price": 75, "amount_paid": 0, "can_cancel": true, "can_reschedule": true, "policy": { "can_cancel": true, "can_reschedule": true, "cancel_window": { "value": 24, "unit": "hours" }, "reschedule_window": { "value": 12, "unit": "hours" } }, "booking_contact_role": "primary", "contacts": [ { "contact_id": 42, "booking_contact_role": "primary", "name": "Jane Doe" } ], "offering": { "id": 10, "name": "Court Rental", "public_name": "Court Rental", "slug": "court-rental", "type": "facility", "status": "active", "capacity": 4, "cancel_until_value": 24, "cancel_until_unit": "hours", "reschedule_until_value": 12, "reschedule_until_unit": "hours", "policy": { "cancel_until_value": 24, "cancel_until_unit": "hours", "reschedule_until_value": 12, "reschedule_until_unit": "hours", "no_refund_after_window": false }, "locations": [ { "location_id": 1, "custom_location_id": null, "name": "Main Campus", "timezone": "America/Chicago" } ] }, "session": { "id": 555, "capacity": 10, "booked_count": 3, "remaining_capacity": 7 } }, "refund": { "refund_amount": 0, "policy_refund_amount": 0 }, "policy": { "can_cancel": true, "can_reschedule": true, "cancel_window": { "value": 24, "unit": "hours" }, "reschedule_window": { "value": 12, "unit": "hours" } } }, "message": "Booking cancelled successfully" } ``` ## POST /bookings/{booking}/cancel Cancel booking Uses per-offering cancel_until_value/unit. Returns policy snapshot and refund summary. Required scope: `scheduling:delete` Required scope: `scheduling:delete` - `booking` (path, required) — Booking ID ```json { "data": { "booking": { "id": 55, "booking_reference": "BK-10055", "status": "confirmed", "payment_status": "unpaid", "offering_id": 3, "offering_session_id": 555, "location_id": 1, "booking_date": "2026-07-15", "start_time": "14:00:00", "end_time": "15:00:00", "duration_minutes": 60, "customer_name": "Jane Doe", "customer_email": "jane@example.com", "total_price": 75, "amount_paid": 0, "can_cancel": true, "can_reschedule": true, "policy": { "can_cancel": true, "can_reschedule": true, "cancel_window": { "value": 24, "unit": "hours" }, "reschedule_window": { "value": 12, "unit": "hours" } }, "booking_contact_role": "primary", "contacts": [ { "contact_id": 42, "booking_contact_role": "primary", "name": "Jane Doe" } ], "offering": { "id": 10, "name": "Court Rental", "public_name": "Court Rental", "slug": "court-rental", "type": "facility", "status": "active", "capacity": 4, "cancel_until_value": 24, "cancel_until_unit": "hours", "reschedule_until_value": 12, "reschedule_until_unit": "hours", "policy": { "cancel_until_value": 24, "cancel_until_unit": "hours", "reschedule_until_value": 12, "reschedule_until_unit": "hours", "no_refund_after_window": false }, "locations": [ { "location_id": 1, "custom_location_id": null, "name": "Main Campus", "timezone": "America/Chicago" } ] }, "session": { "id": 555, "capacity": 10, "booked_count": 3, "remaining_capacity": 7 } }, "refund": { "refund_amount": 0, "policy_refund_amount": 0 }, "policy": { "can_cancel": true, "can_reschedule": true, "cancel_window": { "value": 24, "unit": "hours" }, "reschedule_window": { "value": 12, "unit": "hours" } } }, "message": "Booking cancelled successfully" } ``` ## POST /bookings/{booking}/reschedule Reschedule booking Uses per-offering reschedule window. Rejected with code reschedule_window_passed when outside policy. Required scope: `scheduling:write` Required scope: `scheduling:write` - `booking` (path, required) — Booking ID ```json { "data": { "id": 55, "booking_reference": "BK-10055", "status": "confirmed", "payment_status": "unpaid", "offering_id": 3, "offering_session_id": 555, "location_id": 1, "booking_date": "2026-07-15", "start_time": "14:00:00", "end_time": "15:00:00", "duration_minutes": 60, "customer_name": "Jane Doe", "customer_email": "jane@example.com", "total_price": 75, "amount_paid": 0, "can_cancel": true, "can_reschedule": true, "policy": { "can_cancel": true, "can_reschedule": true, "cancel_window": { "value": 24, "unit": "hours" }, "reschedule_window": { "value": 12, "unit": "hours" } }, "booking_contact_role": "primary", "contacts": [ { "contact_id": 42, "booking_contact_role": "primary", "name": "Jane Doe" } ], "offering": { "id": 10, "name": "Court Rental", "public_name": "Court Rental", "slug": "court-rental", "type": "facility", "status": "active", "capacity": 4, "cancel_until_value": 24, "cancel_until_unit": "hours", "reschedule_until_value": 12, "reschedule_until_unit": "hours", "policy": { "cancel_until_value": 24, "cancel_until_unit": "hours", "reschedule_until_value": 12, "reschedule_until_unit": "hours", "no_refund_after_window": false }, "locations": [ { "location_id": 1, "custom_location_id": null, "name": "Main Campus", "timezone": "America/Chicago" } ] }, "session": { "id": 555, "capacity": 10, "booked_count": 3, "remaining_capacity": 7 } }, "message": "Booking rescheduled successfully" } ``` ## GET /bookings/{booking}/refund-estimate Estimate cancellation refund Required scope: `scheduling:read` Required scope: `scheduling:read` - `booking` (path, required) — Booking ID ```json { "data": { "policy": { "can_cancel": true, "can_reschedule": true, "cancel_window": { "value": 24, "unit": "hours" }, "reschedule_window": { "value": 12, "unit": "hours" } }, "refund": { "total_paid": 75, "refund_amount": 75, "cancellation_fee": 0, "is_refundable": true } } } ``` ## GET /bookings/{booking}/available-slots List reschedule candidate slots Required scope: `scheduling:read` Required scope: `scheduling:read` - `booking` (path, required) — Booking ID - `date` (query, required) — Candidate date YYYY-MM-DD ```json { "data": { "slots": [ { "offering_id": 10, "offering_session_id": 555, "location_id": 1, "date": "2026-08-15", "start_time": "10:00:00", "end_time": "11:00:00", "capacity": 10, "booked_count": 3, "remaining_capacity": 7, "timezone": "America/Chicago", "is_available": true } ] } } ``` --- # Service Canonical URL: https://coreware.com/docs/api/service.md API version: v1 These operations are generated from OpenAPI. They are authoritative. ## GET /request-types List service request types Required scope: `service:read` Required scope: `service:read` ```json { "data": [ { "id": 1, "name": "Repair", "slug": "repair", "is_active": true } ] } ``` ## GET /request-statuses List service request statuses Required scope: `service:read` Required scope: `service:read` ```json { "data": [ { "id": 1, "name": "Open", "slug": "open", "is_active": true, "marks_closed": false } ] } ``` ## GET /request-priorities List service request priorities Required scope: `service:read` Required scope: `service:read` ```json { "data": [ { "id": 1, "name": "Normal", "slug": "normal", "is_active": true } ] } ``` ## GET /service-assignees Search staff people to assign Search staff by name or email. Use the returned `id` as `assignee_id` on PATCH. Required scope: `service:read` Required scope: `service:read` - `search` (query, required) — Name or email - `per_page` (query) — Max results ```json { "data": [ { "id": 7, "user_id": 3, "name": "Ryan Cole", "email": "ryan@example.com" } ] } ``` ## GET /service-requests List service requests Page + total pagination. `queue=mine` is assigned to the token owner. `queue=open` is `is_closed=false`. `request_type` and `status` accept id, slug, or name (for example Development or QA). Required scope: `service:read` Required scope: `service:read` - `contact_id` (query) — Filter by contact ID - `status_id` (query) — Filter by status ID - `status` (query) — Status id, slug, or name - `request_type_id` (query) — Filter by request type ID - `request_type` (query) — Request type id, slug, or name - `priority_id` (query) — Filter by priority ID - `assignee_id` (query) — Filter by assignee person ID - `assigned_to_me` (query) — Only tickets assigned to the token owner - `queue` (query) — Shortcut: mine or open - `assigned_team_id` (query) — Filter by assigned team ID - `is_closed` (query) — Filter open vs closed requests - `search` (query) — Search request number, title, or description - `from` (query) — Created from datetime - `to` (query) — Created to datetime - `created_from` (query) — Created from datetime - `created_to` (query) — Created to datetime - `updated_from` (query) — Updated from datetime - `updated_to` (query) — Updated to datetime - `page` (query) — Page number - `per_page` (query) — Results per page ```json { "data": [ { "id": 12, "request_number": "REQ-001012", "title": "Optic not holding zero", "type": { "id": 1, "name": "Development", "slug": "development" }, "category": null, "status": { "id": 1, "name": "In Progress", "slug": "in-progress" }, "priority": { "id": 1, "name": "Normal" }, "assignee": { "id": 7, "name": "Colt Hale" }, "contact": { "id": 42, "name": "Jane Customer", "email": "jane@example.com", "company": "Acme Arms" }, "original_poster": { "id": 7, "name": "Jane Reporter", "kind": "staff" }, "deep_link": "https://tenant.example.com/service/agent-view?filter=total_open&request=REQ-001012", "is_closed": false, "created_at": "2026-07-01T12:00:00Z", "updated_at": "2026-07-01T13:00:00Z" } ], "meta": { "current_page": 1, "last_page": 1, "per_page": 25, "total": 1 } } ``` ## POST /service-requests Create a service request Requires title, description, request_type_id, priority_id, and contact_id or on_behalf_of_contact_id. Work-order types are rejected. Required scope: `service:write` Required scope: `service:write` ```json { "data": { "id": 12, "request_number": "REQ-001012", "title": "Optic not holding zero", "type": { "id": 1, "name": "Development", "slug": "development" }, "category": null, "status": { "id": 1, "name": "In Progress", "slug": "in-progress" }, "priority": { "id": 1, "name": "Normal" }, "assignee": { "id": 7, "name": "Colt Hale" }, "contact": { "id": 42, "name": "Jane Customer", "email": "jane@example.com", "company": "Acme Arms" }, "original_poster": { "id": 7, "name": "Jane Reporter", "kind": "staff" }, "deep_link": "https://tenant.example.com/service/agent-view?filter=total_open&request=REQ-001012", "is_closed": false, "created_at": "2026-07-01T12:00:00Z", "updated_at": "2026-07-01T13:00:00Z", "description": "Customer reports optic shifted after transport.", "messages": [], "attachments": [] }, "message": "Service request created successfully" } ``` ## GET /service-requests/{serviceRequest} Get a ticket by REQ number or id Required scope: `service:read` Required scope: `service:read` - `serviceRequest` (path, required) — Numeric request ID or request number such as REQ-010299 ```json { "data": { "id": 12, "request_number": "REQ-001012", "title": "Optic not holding zero", "type": { "id": 1, "name": "Development", "slug": "development" }, "category": null, "status": { "id": 1, "name": "In Progress", "slug": "in-progress" }, "priority": { "id": 1, "name": "Normal" }, "assignee": { "id": 7, "name": "Colt Hale" }, "contact": { "id": 42, "name": "Jane Customer", "email": "jane@example.com", "company": "Acme Arms" }, "original_poster": { "id": 7, "name": "Jane Reporter", "kind": "staff" }, "deep_link": "https://tenant.example.com/service/agent-view?filter=total_open&request=REQ-001012", "is_closed": false, "created_at": "2026-07-01T12:00:00Z", "updated_at": "2026-07-01T13:00:00Z", "description": "Customer reports optic shifted after transport.", "messages": [], "attachments": [] } } ``` ## PATCH /service-requests/{serviceRequest} Update status and assignee in one call Statuses are tenant-configured. `status` accepts id, slug, or name from GET /request-statuses. `assignee_id` is the same person id as `assignee.id` / staff `original_poster.id` / GET /service-assignees. Required scope: `service:write` Required scope: `service:write` - `serviceRequest` (path, required) — Numeric request ID or request number such as REQ-010299 ```json { "data": { "id": 12, "request_number": "REQ-001012", "title": "Optic not holding zero", "type": { "id": 1, "name": "Development", "slug": "development" }, "category": null, "status": { "id": 1, "name": "In Progress", "slug": "in-progress" }, "priority": { "id": 1, "name": "Normal" }, "assignee": { "id": 7, "name": "Colt Hale" }, "contact": { "id": 42, "name": "Jane Customer", "email": "jane@example.com", "company": "Acme Arms" }, "original_poster": { "id": 7, "name": "Jane Reporter", "kind": "staff" }, "deep_link": "https://tenant.example.com/service/agent-view?filter=total_open&request=REQ-001012", "is_closed": false, "created_at": "2026-07-01T12:00:00Z", "updated_at": "2026-07-01T13:00:00Z", "description": "Customer reports optic shifted after transport.", "messages": [], "attachments": [] }, "message": "Service request updated successfully" } ``` ## GET /service-requests/{serviceRequest}/messages List messages on a service request Required scope: `service:read` Required scope: `service:read` - `serviceRequest` (path, required) — Numeric request ID or request number such as REQ-010299 ```json { "data": [ { "id": 88, "body": "What we fixed\nShips next morning release\nHow to check: open checkout and apply coupon", "internal": false, "is_from_customer": false, "author_id": 7, "created_at": "2026-07-01T13:00:00Z" } ] } ``` ## POST /service-requests/{serviceRequest}/messages Post a staff reply Visible staff reply by default. Newlines in `body` are preserved. Set `internal: true` for an internal note. Send `Idempotency-Key` or `idempotency_key` so retries return `posted: false` instead of a second comment. Required scope: `service:write` Required scope: `service:write` - `serviceRequest` (path, required) — Numeric request ID or request number such as REQ-010299 - `Idempotency-Key` (header) — Retry key. Replays return the original message with posted=false. ```json { "data": { "id": 88, "body": "What we fixed\nShips next morning release\nHow to check: open checkout and apply coupon", "internal": false, "is_from_customer": false, "author_id": 7, "created_at": "2026-07-01T13:00:00Z" }, "posted": true, "message": "Message created successfully" } ``` ## PATCH /service-requests/{serviceRequest}/messages/{message} Edit a message you created Required scope: `service:write` Required scope: `service:write` - `serviceRequest` (path, required) — Numeric request ID or request number such as REQ-010299 - `message` (path, required) — Message ID ```json { "data": { "id": 88, "body": "What we fixed\nShips next morning release\nHow to check: open checkout and apply coupon", "internal": false, "is_from_customer": false, "author_id": 7, "created_at": "2026-07-01T13:00:00Z" }, "message": "Message updated successfully" } ``` ## DELETE /service-requests/{serviceRequest}/messages/{message} Delete a message you created Required scope: `service:write` Required scope: `service:write` - `serviceRequest` (path, required) — Numeric request ID or request number such as REQ-010299 - `message` (path, required) — Message ID ```json { "message": "Message deleted successfully" } ``` ## GET /service-requests/{serviceRequest}/tasks List tasks on a service request Required scope: `service:read` Required scope: `service:read` - `serviceRequest` (path, required) — Numeric request ID or request number such as REQ-010299 ```json { "data": [ { "id": 4, "request_id": 12, "parent_task_id": null, "title": "Reproduce on staging", "is_completed": false, "assigned_person_id": 7, "due_date": "2026-07-03" } ] } ``` ## POST /service-requests/{serviceRequest}/tasks Create a task on a service request Required scope: `service:write` Required scope: `service:write` - `serviceRequest` (path, required) — Numeric request ID or request number such as REQ-010299 ```json { "data": { "id": 4, "request_id": 12, "parent_task_id": null, "title": "Reproduce on staging", "is_completed": false, "assigned_person_id": 7, "due_date": "2026-07-03" }, "message": "Task created successfully" } ``` ## PATCH /service-requests/{serviceRequest}/tasks/{task} Update a task Required scope: `service:write` Required scope: `service:write` - `serviceRequest` (path, required) — Numeric request ID or request number such as REQ-010299 - `task` (path, required) — Task ID ```json { "data": { "id": 4, "request_id": 12, "parent_task_id": null, "title": "Reproduce on staging", "is_completed": false, "assigned_person_id": 7, "due_date": "2026-07-03" }, "message": "Task updated successfully" } ``` ## DELETE /service-requests/{serviceRequest}/tasks/{task} Delete a task Required scope: `service:write` Required scope: `service:write` - `serviceRequest` (path, required) — Numeric request ID or request number such as REQ-010299 - `task` (path, required) — Task ID ```json { "message": "Task deleted successfully" } ``` ## GET /service-requests/{serviceRequest}/attachments List attachments on a service request Required scope: `service:read` Required scope: `service:read` - `serviceRequest` (path, required) — Numeric request ID or request number such as REQ-010299 ```json { "data": [ { "id": 21, "filename": "screenshot.png", "mime_type": "image/png", "size": 20480, "source": "request", "url": "https://example.test/media/screenshot.png" } ] } ``` ## POST /service-requests/{serviceRequest}/attachments Upload attachments Multipart form field `attachments[]`. Required scope: `service:write` Required scope: `service:write` - `serviceRequest` (path, required) — Numeric request ID or request number such as REQ-010299 ```json { "data": [ { "id": 21, "filename": "screenshot.png", "mime_type": "image/png", "size": 20480, "source": "request", "url": "https://example.test/media/screenshot.png" } ], "message": "Attachments uploaded successfully" } ``` ## DELETE /service-requests/{serviceRequest}/attachments/{attachment} Delete an attachment Required scope: `service:write` Required scope: `service:write` - `serviceRequest` (path, required) — Numeric request ID or request number such as REQ-010299 - `attachment` (path, required) — Attachment ID ```json { "message": "Attachment deleted successfully" } ``` ## GET /contacts/{contact}/service-requests List service requests for a contact Required scope: `service:read` Required scope: `service:read` - `contact` (path, required) — Contact ID - `is_closed` (query) — Filter open vs closed requests - `per_page` (query) — Results per page ```json { "data": [ { "id": 12, "request_number": "REQ-001012", "title": "Optic not holding zero", "type": { "id": 1, "name": "Development", "slug": "development" }, "category": null, "status": { "id": 1, "name": "In Progress", "slug": "in-progress" }, "priority": { "id": 1, "name": "Normal" }, "assignee": { "id": 7, "name": "Colt Hale" }, "contact": { "id": 42, "name": "Jane Customer", "email": "jane@example.com", "company": "Acme Arms" }, "original_poster": { "id": 7, "name": "Jane Reporter", "kind": "staff" }, "deep_link": "https://tenant.example.com/service/agent-view?filter=total_open&request=REQ-001012", "is_closed": false, "created_at": "2026-07-01T12:00:00Z", "updated_at": "2026-07-01T13:00:00Z" } ], "meta": { "current_page": 1, "last_page": 1, "per_page": 25, "total": 1 } } ``` ## GET /work-orders List work orders Required scope: `service:read` Required scope: `service:read` - `contact_id` (query) — Filter by contact ID - `request_id` (query) — Filter by service request ID - `status` (query) — Filter by work order status - `location_id` (query) — Filter by location ID - `search` (query) — Search work order or request number - `per_page` (query) — Results per page ```json { "data": [ { "id": 8, "work_order_number": "WO-000008", "request_id": 12, "request_number": "REQ-001012", "contact_id": 42, "status": "in_progress", "approval_status": "approved", "location_id": 1, "sale_id": null, "deposit_invoice_id": null, "final_invoice_id": null } ], "meta": { "current_page": 1, "last_page": 1, "per_page": 25, "total": 1 } } ``` ## GET /work-orders/{workOrder} Get work order with items, parts, and labor Required scope: `service:read` Required scope: `service:read` - `workOrder` (path, required) — Work order ID ```json { "data": { "id": 8, "work_order_number": "WO-000008", "request_id": 12, "request_number": "REQ-001012", "contact_id": 42, "status": "in_progress", "approval_status": "approved", "location_id": 1, "sale_id": null, "deposit_invoice_id": null, "final_invoice_id": null, "items": [ { "id": 1, "item_number": "SCOPE-1", "serial_number": "SN123", "customer_reported_issue": "Not holding zero" } ], "parts": [], "labor": [] } } ``` ## GET /contacts/{contact}/work-orders List work orders for a contact Required scope: `service:read` Required scope: `service:read` - `contact` (path, required) — Contact ID - `status` (query) — Filter by work order status - `per_page` (query) — Results per page ```json { "data": [ { "id": 8, "work_order_number": "WO-000008", "request_id": 12, "request_number": "REQ-001012", "contact_id": 42, "status": "in_progress", "approval_status": "approved", "location_id": 1, "sale_id": null, "deposit_invoice_id": null, "final_invoice_id": null } ], "meta": { "current_page": 1, "last_page": 1, "per_page": 25, "total": 1 } } ``` --- # Subscriptions Canonical URL: https://coreware.com/docs/api/subscriptions.md API version: v1 These operations are generated from OpenAPI. They are authoritative. ## GET /subscriptions List subscriptions Required scope: `subscriptions:read` Required scope: `subscriptions:read` - `contact_id` (query) — Filter by contact ID - `status` (query) — Filter by status - `per_page` (query) — Results per page ```json { "data": [ { "id": 15, "contact_id": 42, "status": "active", "recurring_charge_amount": 29.99, "next_payment_date": "2026-08-01" } ], "meta": { "current_page": 1, "last_page": 1, "per_page": 25, "total": 1 } } ``` ## POST /subscriptions Enroll a contact in a subscription Same enrollment path as staff. Requires contact_id, item_id, and payment_type_id. Saved cards use saved_payment_id. This charges through the existing enrollment service. Required scope: `subscriptions:write` Required scope: `subscriptions:write` ```json { "data": { "id": 15, "contact_id": 42, "status": "active", "recurring_charge_amount": 29.99, "next_payment_date": "2026-08-01" }, "message": "Subscription created successfully" } ``` ## GET /subscriptions/{subscription} Get subscription Required scope: `subscriptions:read` Required scope: `subscriptions:read` - `subscription` (path, required) — Subscription ID ```json { "data": { "id": 15, "contact_id": 42, "status": "active", "recurring_charge_amount": 29.99, "next_payment_date": "2026-08-01" } } ``` ## POST /subscriptions/{subscription}/pause Pause a subscription Full pause or temporary discount. Uses the same billing rules as the staff pause action. reason must be at least 10 characters. Send duration_cycles or duration_months. Required scope: `subscriptions:write` Required scope: `subscriptions:write` - `subscription` (path, required) — Subscription ID ```json { "data": { "id": 15, "contact_id": 42, "status": "active", "recurring_charge_amount": 29.99, "next_payment_date": "2026-08-01" }, "message": "Subscription paused" } ``` ## POST /subscriptions/{subscription}/resume Resume a paused subscription Required scope: `subscriptions:write` Required scope: `subscriptions:write` - `subscription` (path, required) — Subscription ID ```json { "data": { "id": 15, "contact_id": 42, "status": "active", "recurring_charge_amount": 29.99, "next_payment_date": "2026-08-01" }, "message": "Subscription resumed" } ``` ## POST /subscriptions/{subscription}/cancel Cancel a subscription Requires churn_reason_id, or skip_churn true with notes of at least 10 characters. cancellation_override is immediate or end_of_billing_cycle. Required scope: `subscriptions:write` Required scope: `subscriptions:write` - `subscription` (path, required) — Subscription ID ```json { "data": { "id": 15, "contact_id": 42, "status": "active", "recurring_charge_amount": 29.99, "next_payment_date": "2026-08-01" }, "message": "Subscription cancelled" } ``` --- # Webhooks Canonical URL: https://coreware.com/docs/api/webhooks.md API version: v1 These operations are generated from OpenAPI. They are authoritative. ## GET /webhook-events List webhook event names Required scope: `webhooks:read` Required scope: `webhooks:read` ```json { "data": [ { "event": "contact.created", "module": "contacts", "available": true } ] } ``` ## GET /webhooks List webhook subscriptions Required scope: `webhooks:read` Required scope: `webhooks:read` ```json { "data": [ { "id": "wh_abc123", "name": "CRM sync", "url": "https://example.test/hooks/coreware", "events": [ "contact.created", "form.submitted" ], "is_enabled": true } ], "meta": { "current_page": 1, "last_page": 1, "per_page": 25, "total": 1 } } ``` ## POST /webhooks Create a webhook subscription HTTPS url required. events may include * for every dispatched event. secret is returned once. Required scope: `webhooks:write` Required scope: `webhooks:write` ```json { "data": { "id": "wh_abc123", "name": "CRM sync", "url": "https://example.test/hooks/coreware", "events": [ "contact.created", "form.submitted" ], "is_enabled": true, "secret": "whsec_example" } } ``` ## GET /webhooks/{webhookId} Get a webhook subscription Required scope: `webhooks:read` Required scope: `webhooks:read` - `webhookId` (path, required) — Public webhook id ```json { "data": { "id": "wh_abc123", "name": "CRM sync", "url": "https://example.test/hooks/coreware", "events": [ "contact.created", "form.submitted" ], "is_enabled": true } } ``` ## PUT /webhooks/{webhookId} Update a webhook subscription Required scope: `webhooks:write` Required scope: `webhooks:write` - `webhookId` (path, required) — Public webhook id ```json { "data": { "id": "wh_abc123", "name": "CRM sync", "url": "https://example.test/hooks/coreware", "events": [ "contact.created", "form.submitted" ], "is_enabled": true } } ``` ## PATCH /webhooks/{webhookId} Partially update a webhook subscription Required scope: `webhooks:write` Required scope: `webhooks:write` - `webhookId` (path, required) — Public webhook id ```json { "data": { "id": "wh_abc123", "name": "CRM sync", "url": "https://example.test/hooks/coreware", "events": [ "contact.created", "form.submitted" ], "is_enabled": true } } ``` ## DELETE /webhooks/{webhookId} Delete a webhook subscription Required scope: `webhooks:delete` Required scope: `webhooks:delete` - `webhookId` (path, required) — Public webhook id ```json { "message": "Webhook deleted successfully" } ``` ## POST /webhooks/{webhookId}/rotate-secret Rotate the signing secret Required scope: `webhooks:write` Required scope: `webhooks:write` - `webhookId` (path, required) — Public webhook id ```json { "data": { "id": "wh_abc123", "name": "CRM sync", "url": "https://example.test/hooks/coreware", "events": [ "contact.created", "form.submitted" ], "is_enabled": true, "secret": "whsec_rotated" } } ``` ## POST /webhooks/{webhookId}/test Queue a webhook.test delivery Required scope: `webhooks:write` Required scope: `webhooks:write` - `webhookId` (path, required) — Public webhook id ```json { "data": { "queued": true, "event": "webhook.test" } } ``` --- # Websites Canonical URL: https://coreware.com/docs/api/websites.md API version: v1 These operations are generated from OpenAPI. They are authoritative. ## GET /websites List websites Required scope: `websites:read` Required scope: `websites:read` - `page` (query) — Page number - `per_page` (query) — Results per page ```json { "data": [ { "id": 1, "name": "Demo Site", "slug": "demo-site", "status": "draft", "preview_url": "https://example.test/website-preview/public/preview-token", "page_count": 1 } ], "meta": { "current_page": 1, "last_page": 1, "per_page": 25, "total": 1 } } ``` ## POST /websites Create website Creates a draft site. Optional template_id clones an active published template. Enforces website quota. Required scope: `websites:write` Required scope: `websites:write` ```json { "data": { "id": 1, "name": "Demo Site", "slug": "demo-site", "status": "draft", "preview_url": "https://example.test/website-preview/public/preview-token", "page_count": 1 } } ``` ## GET /websites/{websiteId} Get website Required scope: `websites:read` Required scope: `websites:read` - `websiteId` (path, required) — Website ID ```json { "data": { "id": 1, "name": "Demo Site", "slug": "demo-site", "status": "draft", "preview_url": "https://example.test/website-preview/public/preview-token", "page_count": 1, "theme": { "colors": { "primary": "#3B82F6" } }, "navigation": [], "global_sections": [] } } ``` ## PATCH /websites/{websiteId} Update website Required scope: `websites:write` Required scope: `websites:write` - `websiteId` (path, required) — Website ID ```json { "data": { "id": 1, "name": "Demo Site", "slug": "demo-site", "status": "draft", "preview_url": "https://example.test/website-preview/public/preview-token", "page_count": 1 } } ``` ## DELETE /websites/{websiteId} Delete website Required scope: `websites:delete` Required scope: `websites:delete` - `websiteId` (path, required) — Website ID ```json { "message": "Website deleted successfully" } ``` ## POST /websites/{websiteId}/publish Publish website Promotes draft pages and global sections, then marks the site published. Required scope: `websites:write` Required scope: `websites:write` - `websiteId` (path, required) — Website ID ```json { "data": { "id": 1, "name": "Demo Site", "slug": "demo-site", "status": "published", "preview_url": "https://example.test/website-preview/public/preview-token", "page_count": 1 } } ``` ## POST /websites/{websiteId}/mobile-friendly Make website mobile-friendly Rewrites stored page documents, global sections, and navigation so the live site stacks columns, uses 100% mobile widths, enables the mobile menu, and caps desktop-only padding and type. Explicit {desktop, tablet, mobile} values are left alone. Updates both draft and published content. Safe to call more than once. Required scope: `websites:write` Required scope: `websites:write` - `websiteId` (path, required) — Website ID ```json { "data": { "id": 1, "name": "Demo Site", "slug": "demo-site", "status": "draft", "preview_url": "https://example.test/website-preview/public/preview-token", "page_count": 1, "pages_updated": 1, "blocks_changed": 12, "navigation_updated": true } } ``` ## GET /websites/{websiteId}/theme Get website theme Required scope: `websites:read` Required scope: `websites:read` - `websiteId` (path, required) — Website ID ```json { "data": { "colors": { "primary": "#3B82F6" }, "fonts": { "heading": "Inter" }, "spacing": [], "logo_media_id": null } } ``` ## PATCH /websites/{websiteId}/theme Update website theme Required scope: `websites:write` Required scope: `websites:write` - `websiteId` (path, required) — Website ID ```json { "data": { "colors": { "primary": "#111827" }, "logo_media_id": 22 } } ``` ## GET /website-template-categories List website template categories Required scope: `websites:read` Required scope: `websites:read` ```json { "data": [ { "id": 2, "name": "Retail", "slug": "retail", "description": "Storefronts and showrooms" } ] } ``` ## GET /website-templates List website templates Returns every tenant template. Use is_published=true to limit to templates that POST /websites can clone. List includes description, tags, category, and page summaries so agents can choose a template. Full page documents are on GET /website-templates/{templateId}. Required scope: `websites:read` Required scope: `websites:read` - `search` (query) — Search name, slug, or description - `category_id` (query) — Limit to one category - `is_published` (query) — Filter by published state - `is_active` (query) — Filter by active state - `is_featured` (query) — Filter featured templates - `page` (query) — Page number - `per_page` (query) — Results per page ```json { "data": [ { "id": 3, "name": "Retail starter", "slug": "retail-starter", "description": "Multi-page storefront with home, about, and contact. Best for gun shops and outdoor retailers.", "tags": [ "retail", "storefront" ], "category": { "id": 2, "name": "Retail", "slug": "retail" }, "is_published": true, "is_ai_compatible": true, "page_count": 3, "pages": [ { "name": "Home", "slug": "home", "is_home": true } ] } ], "meta": { "current_page": 1, "last_page": 1, "per_page": 25, "total": 1 } } ``` ## POST /website-templates Create a website template Snapshot an existing site with website_id, or author content.pages directly. Description should explain audience and page structure so agents can choose this template later. Creating a site still requires an active published template. Required scope: `websites:write` Required scope: `websites:write` ```json { "data": { "id": 3, "name": "Retail starter", "is_published": true } } ``` ## GET /website-templates/{templateId} Get a website template Includes the full content snapshot (pages, theme, navigation) plus the agent-facing description and page list. Required scope: `websites:read` Required scope: `websites:read` - `templateId` (path, required) — Website template ID ```json { "data": { "id": 3, "name": "Retail starter", "content": { "pages": [] } } } ``` ## PUT /website-templates/{templateId} Update a website template Required scope: `websites:write` Required scope: `websites:write` - `templateId` (path, required) — Website template ID ```json { "data": { "id": 3, "is_published": true } } ``` ## PATCH /website-templates/{templateId} Partially update a website template Required scope: `websites:write` Required scope: `websites:write` - `templateId` (path, required) — Website template ID ```json { "data": { "id": 3, "is_featured": true } } ``` ## DELETE /website-templates/{templateId} Delete a website template Required scope: `websites:delete` Required scope: `websites:delete` - `templateId` (path, required) — Website template ID ```json { "message": "Website template deleted successfully" } ``` ## GET /websites/{websiteId}/pages List pages Required scope: `websites:read` Required scope: `websites:read` - `websiteId` (path, required) — Website ID ```json { "data": [ { "id": 10, "website_id": 1, "title": "Home", "slug": "home", "is_home": true, "is_published": false, "has_unpublished_changes": true, "preview_url": "https://example.test/website-preview/public/preview-token?page=10", "document": [ { "id": "sec-1", "type": "section", "props": [], "children": [] } ], "published_document": [] } ], "meta": { "current_page": 1, "last_page": 1, "per_page": 25, "total": 1 } } ``` ## POST /websites/{websiteId}/pages Create page Required scope: `websites:write` Required scope: `websites:write` - `websiteId` (path, required) — Website ID ```json { "data": { "id": 10, "website_id": 1, "title": "Home", "slug": "home", "is_home": true, "is_published": false, "has_unpublished_changes": true, "preview_url": "https://example.test/website-preview/public/preview-token?page=10", "document": [ { "id": "sec-1", "type": "section", "props": [], "children": [] } ], "published_document": [] } } ``` ## GET /pages/{pageId} Get page document document is draft_content ?? content. published_document is the live content. Layout props may be strings or {desktop, tablet, mobile}. Live render stacks multi-column rows on mobile unless flexDirection.mobile is explicit. Required scope: `websites:read` Required scope: `websites:read` - `pageId` (path, required) — Page ID ```json { "data": { "id": 10, "website_id": 1, "title": "Home", "slug": "home", "is_home": true, "is_published": false, "has_unpublished_changes": true, "preview_url": "https://example.test/website-preview/public/preview-token?page=10", "document": [ { "id": "sec-1", "type": "section", "props": [], "children": [] } ], "published_document": [] } } ``` ## PUT /pages/{pageId} Replace page draft document Replaces draft_content. Layout props may be strings or {desktop, tablet, mobile}. Published sites always stack multi-column rows on mobile unless flexDirection.mobile is set. Required scope: `websites:write` Required scope: `websites:write` - `pageId` (path, required) — Page ID ```json { "data": { "id": 10, "website_id": 1, "title": "Home", "slug": "home", "is_home": true, "is_published": false, "has_unpublished_changes": true, "preview_url": "https://example.test/website-preview/public/preview-token?page=10", "document": [ { "id": "sec-1", "type": "section", "props": [], "children": [] } ], "published_document": [] } } ``` ## PATCH /pages/{pageId} Update page metadata Required scope: `websites:write` Required scope: `websites:write` - `pageId` (path, required) — Page ID ```json { "data": { "id": 10, "website_id": 1, "title": "Home", "slug": "home", "is_home": true, "is_published": false, "has_unpublished_changes": true, "preview_url": "https://example.test/website-preview/public/preview-token?page=10", "document": [ { "id": "sec-1", "type": "section", "props": [], "children": [] } ], "published_document": [] } } ``` ## DELETE /pages/{pageId} Delete page Refuses system pages and the sole home page. Required scope: `websites:delete` Required scope: `websites:delete` - `pageId` (path, required) — Page ID ```json { "message": "Page deleted successfully" } ``` ## POST /pages/{pageId}/publish Publish page Required scope: `websites:write` Required scope: `websites:write` - `pageId` (path, required) — Page ID ```json { "data": { "id": 10, "website_id": 1, "title": "Home", "slug": "home", "is_home": true, "is_published": true, "has_unpublished_changes": true, "preview_url": "https://example.test/website-preview/public/preview-token?page=10", "document": [ { "id": "sec-1", "type": "section", "props": [], "children": [] } ], "published_document": [] } } ``` ## POST /media Upload website media Multipart upload. Field name is file. Optional website_id and alt_text. Required scope: `websites:write` Required scope: `websites:write` ```json { "data": { "id": 22, "url": "https://cdn.example/logo.png", "alt_text": "Logo", "website_id": 1 } } ``` ## GET /media/{mediaId} Get website media Required scope: `websites:read` Required scope: `websites:read` - `mediaId` (path, required) — Media ID ```json { "data": { "id": 22, "url": "https://cdn.example/logo.png", "alt_text": "Logo" } } ``` ## GET /banners List banners Required scope: `websites:read` Required scope: `websites:read` - `search` (query) — Search by name - `status` (query) — draft or published - `type` (query) — single or carousel ```json { "data": [ { "id": 12, "name": "Home Hero", "type": "single", "preset_size": "hero", "status": "draft" } ] } ``` ## POST /banners Create a banner Creates the banner and one empty slide. Reference the id from a page block with type banner and props.bannerId. Required scope: `websites:write` Required scope: `websites:write` ```json { "data": { "id": 12, "name": "Home Hero", "slide_count": 1 } } ``` ## GET /banners/{bannerId} Get a banner Required scope: `websites:read` Required scope: `websites:read` - `bannerId` (path, required) — Banner ID ```json { "data": { "id": 12, "name": "Home Hero", "slides": [ { "id": 44, "variant": "A" } ] } } ``` ## PATCH /banners/{bannerId} Update a banner Required scope: `websites:write` Required scope: `websites:write` - `bannerId` (path, required) — Banner ID ```json { "data": { "id": 12, "name": "Updated hero" } } ``` ## DELETE /banners/{bannerId} Delete a banner Required scope: `websites:delete` Required scope: `websites:delete` - `bannerId` (path, required) — Banner ID ```json { "message": "Banner deleted successfully" } ``` ## POST /banners/{bannerId}/publish Publish a banner Required scope: `websites:write` Required scope: `websites:write` - `bannerId` (path, required) — Banner ID ```json { "data": { "id": 12, "status": "published" } } ``` ## POST /banners/{bannerId}/slides Add a banner slide Required scope: `websites:write` Required scope: `websites:write` - `bannerId` (path, required) — Banner ID ```json { "data": { "id": 45, "banner_id": 12, "variant": "B" } } ``` ## PATCH /banners/{bannerId}/slides/{slideId} Update a banner slide Required scope: `websites:write` Required scope: `websites:write` - `bannerId` (path, required) — Banner ID - `slideId` (path, required) — Slide ID ```json { "data": { "id": 45, "link_type": "website_page" } } ``` ## DELETE /banners/{bannerId}/slides/{slideId} Delete a banner slide Required scope: `websites:delete` Required scope: `websites:delete` - `bannerId` (path, required) — Banner ID - `slideId` (path, required) — Slide ID ```json { "message": "Banner slide deleted successfully" } ``` ## POST /banners/{bannerId}/slides/reorder Reorder banner slides Required scope: `websites:write` Required scope: `websites:write` - `bannerId` (path, required) — Banner ID ```json { "message": "Banner slides reordered successfully" } ``` ## GET /banner-collections List banner collections Required scope: `websites:read` Required scope: `websites:read` ```json { "data": [ { "id": 3, "name": "Homepage rotation" } ] } ``` ## POST /banner-collections Create a banner collection Required scope: `websites:write` Required scope: `websites:write` ```json { "data": { "id": 3, "name": "Homepage rotation" } } ``` ## GET /banner-collections/{collectionId} Get a banner collection Required scope: `websites:read` Required scope: `websites:read` - `collectionId` (path, required) — Banner collection ID ```json { "data": { "id": 3, "items": [ { "id": 8, "banner_id": 12 } ] } } ``` ## PATCH /banner-collections/{collectionId} Update a banner collection Required scope: `websites:write` Required scope: `websites:write` - `collectionId` (path, required) — Banner collection ID ```json { "data": { "id": 3, "autoplay": true } } ``` ## DELETE /banner-collections/{collectionId} Delete a banner collection Required scope: `websites:delete` Required scope: `websites:delete` - `collectionId` (path, required) — Banner collection ID ```json { "message": "Banner collection deleted successfully" } ``` ## POST /banner-collections/{collectionId}/items Add a banner to a collection Required scope: `websites:write` Required scope: `websites:write` - `collectionId` (path, required) — Banner collection ID ```json { "data": { "id": 8, "banner_id": 12 } } ``` ## PATCH /banner-collections/{collectionId}/items/{itemId} Update a collection item schedule Required scope: `websites:write` Required scope: `websites:write` - `collectionId` (path, required) — Banner collection ID - `itemId` (path, required) — Collection item ID ```json { "data": { "id": 8, "banner_id": 12 } } ``` ## DELETE /banner-collections/{collectionId}/items/{itemId} Remove a banner from a collection Required scope: `websites:delete` Required scope: `websites:delete` - `collectionId` (path, required) — Banner collection ID - `itemId` (path, required) — Collection item ID ```json { "message": "Banner removed from collection successfully" } ``` ## POST /banner-collections/{collectionId}/items/reorder Reorder collection items Required scope: `websites:write` Required scope: `websites:write` - `collectionId` (path, required) — Banner collection ID ```json { "message": "Collection items reordered successfully" } ``` --- # Widget Canonical URL: https://coreware.com/docs/api/widget.md API version: v1 These operations are generated from OpenAPI. They are authoritative. ## POST /widget/auth-token Generate widget auth token Required scope: `widget:write` Required scope: `widget:write` ```json { "auth_token": "wgt_example_token", "expires_in": 300 } ``` ---