CloudTop Office API
API reference

CloudTop Office API

Read-only access to your QuoteWerks data, hosted by CloudTop Office. Throughout this page, <tenant> is your tenant name and <key> is the API key we send you.

Overview

Base URLhttps://api.cloudtopoffice.com/<tenant>
RESThttps://api.cloudtopoffice.com/<tenant>/api/<Entity>
GraphQLhttps://api.cloudtopoffice.com/<tenant>/graphql (POST)
MCPhttps://api.cloudtopoffice.com/<tenant>/mcp (Streamable HTTP, for Claude and other AI tools)
AuthHTTP header X-Api-Key: <key> on every request. A missing or wrong key returns 403. Keys are case-sensitive.
AccessRead only. Create, update and delete are refused (403 on REST; MCP write tools are disabled).
TLSTLS 1.2 / 1.3

We send your key separately. Keep it out of email bodies, tickets and source control. A key only works for its own tenant path (/<tenant>/...).

Entities & fields

EntityKeyWhat it is
DocumentsDocumentIdQuotes, orders and invoices, one row per document. Templates and crash-recovery copies are left out.
DocumentItemsDocumentItemIdLine items. DocumentId links each item to its document.
CustomersCustomerKeyCustomers built from the sold-to company on your documents (QuoteWerks has no separate customer list). Contact and address come from the most recent document, with counts and order totals.
ProductsProductId*Your QuoteWerks product catalogue (sell prices only).

* If you have more than one catalogue, Products also has a Catalog column and the key is Catalog + ProductId.

Documents

DocumentId, DocNo, DocType (QUOTE / ORDER / INVOICE), DocStatus (free text: Open, Closed, Won, Lost, ...), DocName, IsSuperseded, RevisionMasterDocNo, ConvertedRef, ConvertedOn, ConvertedFromRef, DocDate, DocDueDate, ExpirationDate, CreatedOn, LastModifiedOn, SalesRep, PreparedBy, CustomerKey, CustomerName, SoldToContact, SoldToEmail, SoldToPhone, SoldToCity, SoldToState, SoldToCountry, CustomerPONumber, CrmCompanyId, CrmContactId, ShipToCompany, ShipToCity, ShipToState, ShipToCountry, ShipVia, Terms, ProjectNo, Currency, SubTotal, ShippingAmount, TotalTax, GrandTotal, DepositAmount, ContractStartDate, ContractEndDate.

Your own QuoteWerks custom fields are added after these, named after your labels. Use describe_entities (MCP) or GraphQL introspection to see your exact list.

DocumentItems

DocumentItemId, DocumentId, DocNo, DocType, LineNumber, LineSequence, LineType (1 = normal item; other values are comments, subtotals or headings), ItemType, Manufacturer, ManufacturerPartNumber, InternalPartNumber, Vendor, VendorPartNumber, Category, Description, Quantity, UnitOfMeasure, UnitList, UnitPrice, ExtendedPrice, SalesTax, TaxCode, RecurringBillingCycle, RecurringStartDate, RecurringEndDate, PONumber, SONumber, OrderDate, ReceivedDate, LastModifiedOn.

Customers

CustomerKey, CustomerName, PrimaryContact, PrimaryContactTitle, Email, Phone, Address1, Address2, City, State, PostalCode, Country, LastSalesRep, CrmCompanyId, DocumentCount, QuoteCount, OrderCount, InvoiceCount, OrderTotal (non-superseded orders), FirstDocDate, LastDocDate.

Products

ProductId, ManufacturerPartNumber, InternalPartNumber, VendorPartNumber, Manufacturer, Vendor, Category, ItemType, ShortDescription, Description, Price, ListPrice, UnitOfMeasure, TaxCode, IsDiscontinued, CreatedOn, LastModifiedOn.

Not exposed, by design: costs, profit, commission, internal and purchasing notes, payment data and QuoteWerks users. If you need a different set of fields, talk to us.

REST

Query options (OData style): $select, $filter, $orderby, $first (page size, default 100) and $after (cursor). Responses look like {"value":[...], "nextLink":"..."}; follow nextLink as-is to get the next page. In a shell, quote URLs so $ isn't expanded.

KEY='<key>'
B='https://api.cloudtopoffice.com/<tenant>/api'

# 10 most recent open quotes
curl -H "X-Api-Key: $KEY" "$B/Documents?\$filter=DocType eq 'QUOTE' and DocStatus eq 'Open'&\$orderby=DocDate desc&\$first=10&\$select=DocumentId,DocNo,DocDate,CustomerName,GrandTotal,SalesRep"

# one document by key
curl -H "X-Api-Key: $KEY" "$B/Documents/DocumentId/4"

# a document's line items, in order
curl -H "X-Api-Key: $KEY" "$B/DocumentItems?\$filter=DocumentId eq 4&\$orderby=LineSequence"

# documents changed since a date (incremental sync)
curl -H "X-Api-Key: $KEY" "$B/Documents?\$filter=LastModifiedOn ge 2026-11-01T00:00:00Z&\$orderby=LastModifiedOn"

# customers whose name starts with A
curl -H "X-Api-Key: $KEY" "$B/Customers?\$filter=CustomerName ge 'A' and CustomerName lt 'B'&\$select=CustomerKey,CustomerName"

REST $filter supports eq ne gt ge lt le and or. It does not support startswith() or contains() (they return 400); use GraphQL for partial-text search.

OpenAPI description (for tools that import one): GET https://api.cloudtopoffice.com/<tenant>/api/openapi with the X-Api-Key header. It lists relative server paths and no security scheme, so set the server URL and the API-key header in the importing tool.

PowerShell:

$h = @{ 'X-Api-Key' = '<key>' }
Invoke-RestMethod -Headers $h 'https://api.cloudtopoffice.com/<tenant>/api/Products?$first=5'

GraphQL

Query names are the plural entity names in lower camel case (documents, documentItems, customers, products). To get a single row by key, use document_by_pk(DocumentId: ...) and so on. Introspection is on, so tools like Postman or Insomnia can load the schema.

curl -H "X-Api-Key: $KEY" -H 'Content-Type: application/json' \
  -d '{"query":"{ documents(first: 5, filter: { DocType: { eq: \"ORDER\" } }, orderBy: { DocDate: DESC }) { items { DocumentId DocNo DocDate CustomerName GrandTotal } hasNextPage endCursor } }"}' \
  https://api.cloudtopoffice.com/<tenant>/graphql

# partial-text search (startsWith / endsWith / contains)
curl -H "X-Api-Key: $KEY" -H 'Content-Type: application/json' \
  -d '{"query":"{ customers(first: 10, filter: { CustomerName: { contains: \"Inc\" } }) { items { CustomerKey CustomerName LastDocDate } } }"}' \
  https://api.cloudtopoffice.com/<tenant>/graphql

MCP (AI assistants)

The MCP endpoint speaks Streamable HTTP (protocol 2025-06-18). It offers three tools: describe_entities, read_records and aggregate_records. Write tools are disabled. Every request needs the X-Api-Key header, except the claude.ai path-key URL described below.

Quick test:

curl -H "X-Api-Key: $KEY" -H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"test","version":"1"}}}' \
  https://api.cloudtopoffice.com/<tenant>/mcp

Clients that only speak stdio (for example Claude Desktop's config file) can use the mcp-remote bridge, which needs Node.js:

{
  "mcpServers": {
    "quotewerks": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "https://api.cloudtopoffice.com/<tenant>/mcp", "--header", "X-Api-Key:${CLOUDTOP_API_KEY}"],
      "env": { "CLOUDTOP_API_KEY": "<key>" }
    }
  }
}

Connect from Claude

claude.ai (custom connector)

claude.ai's custom-connector dialog only takes a URL, so for MCP only you can put the key in the path:

https://api.cloudtopoffice.com/<tenant>/mcp/<key>
  1. In claude.ai, open Settings → Connectors → Add custom connector.
  2. Name: for example QuoteWerks.
  3. URL: https://api.cloudtopoffice.com/<tenant>/mcp/<key>. Use the 32-character key exactly as we sent it; it is case-sensitive.
  4. Leave OAuth and the advanced settings empty, then select Add.
  5. In a chat, turn the connector on from the tools menu and ask, for example, "List my 5 most recent open quotes".

The key is part of the URL. Anyone who can see the connector settings, or who is given the URL, can read your data. Only add the connector on claude.ai accounts or organisations your company controls (on Team or Enterprise, ideally added by an admin), never on shared or personal accounts, and don't paste the URL into chats, tickets or email. If the URL leaks, ask us to rotate the key; the connector then needs the new URL.

The path key only works on /<tenant>/mcp/.... REST and GraphQL always need the X-Api-Key header (/<tenant>/api/<key>/... returns 403). URL paths and query strings are not recorded in our web server logs, so the key isn't written to disk.

Claude Code (CLI)

claude mcp add --transport http quotewerks https://api.cloudtopoffice.com/<tenant>/mcp --header "X-Api-Key: <key>"

This sends the key as a header, so it never appears in a URL.

Connect an AI assistant

Use MCP where the tool supports it, and the REST API everywhere else. The steps below are a summary; follow the linked vendor docs for the screens themselves.

ToolHow it connectsAuthStatus
Claude claude.ai: add a custom connector with the path-key MCP URL https://api.cloudtopoffice.com/<tenant>/mcp/<key> (see Connect from Claude). Claude Code / Desktop: MCP URL …/<tenant>/mcp with the header.
Claude custom connectors · Claude Code MCP
Key in the MCP path (claude.ai) or X-Api-Key header Tested
ChatGPT Custom GPT Action on the REST API. Download the OpenAPI description from GET /<tenant>/api/openapi (send your key; it isn't public), set servers to https://api.cloudtopoffice.com/<tenant>/api, give each operation an operationId, and paste it into the Action. Where your plan supports MCP connectors, use the MCP URL instead.
GPT Actions · ChatGPT connectors
Action auth: API key, custom header X-Api-Key Documented, not yet tested
Microsoft Copilot Copilot Studio: add an MCP tool pointing at …/<tenant>/mcp, or build a custom connector from the OpenAPI description (same edits as for ChatGPT).
MCP in Copilot Studio · Custom connectors
API key in header X-Api-Key Documented, not yet tested
Gemini Gemini CLI: add an MCP server in settings.json with httpUrl …/<tenant>/mcp and headers {"X-Api-Key": "<key>"}. Elsewhere, call the REST API.
Gemini CLI MCP servers
X-Api-Key header Documented, not yet tested

Help

Questions, a new key, or more data exposed: email api@cloudtopoffice.com or call 866-710-4228. New here? Get started.