Sumqo/Developers

Sumqo API

Read and add Sumqo records from your own servers and integrations with an API key, and get events as they happen with webhooks. Keys are created by the workspace in Settings → External access and are limited to the permissions, addresses and lifetime chosen when they were created.

Authentication

Send the key in the Authorization header of every request. Never in a URL.

curl https://connect.sumqo.com/v1/workspace \
  -H "Authorization: Bearer sumqo_live_EXAMPLEkeyEXAMPLEkeyEXAMPLEke_a1B2c3"

Keys look like sumqo_live_ followed by 32 letters and digits, an underscore and a 6-character checksum. Treat a key like a password: keep it in a secret store, never in a browser, an app sent to customers, a repository or a chat. Calls from a web browser are refused.

GET /workspace needs no permission: it returns the workspace name, currency, time zone and the key's own permissions and expiry — a good first call.

Base URL

https://connect.sumqo.com/v1

The older address https://api.sumqo.com/connect/v1 still works but is deprecated — move your integration to the address above.

Every address below is relative to it. GET reads; POST and PATCH create and change records when the key has a permission that allows it. Write bodies are JSON (Content-Type: application/json).

Responses and pages

Every answer is JSON with the data under data. Lists add meta:

{
  "data": [
    {
      "id": "5b9e…",
      "invoiceNumber": "INV-1042",
      "totalCents": 125000
    }
  ],
  "meta": {
    "total": 1,
    "count": 1,
    "nextCursor": null
  }
}

Lists that page accept limit; when meta.nextCursor is not null, send it back as cursor to get the next page. Money is in cents (totalCents: 125000 = $1,250.00); dates are YYYY-MM-DD. Full tax IDs and bank account numbers are never returned.

Limits

  • 60 requests per minute per key, counted in fixed one-minute windows (up to 60 may arrive at once).
  • 300 requests per minute for all keys of one workspace together.
  • Lists return 50 records per page unless you ask for a different limit (at most 100).
  • Over the limit you get 429 with a Retry-After header in seconds.
  • Keys expire: 90 days by default, at most 365 days (180 for keys that can change records, 90 for high-risk permissions and recording payments).

IP address rules

A key can be limited to up to 20 IPv4 or IPv6 addresses or ranges (for example 198.51.100.24 or 198.51.100.0/24). Calls from any other address get 403 ip_not_allowed. Keys with high-risk permissions or that record payments must have rules, with ranges no wider than /24 (IPv4) or /48 (IPv6). Rules are recommended for every key that can change records: without them the workspace must confirm that anyone holding the key can change its books from anywhere. Rules protect you best when the key runs on your own servers; shared platforms call from addresses all their customers share.

Errors

Errors are JSON: {"error": {"code": "insufficient_scope", "message": "…"}}

StatusCodeMeaning
400invalid_queryA query parameter is not part of this address, or its value is not valid.
400invalid_bodyThe body is not a JSON object, or a field has the wrong shape.
400field_not_allowedThe body sets a field an API key may not set (for example a tax ID).
400idempotency_key_missingWrites need an Idempotency-Key header of 16 to 128 characters.
400request_failedSumqo refused the change for a business reason; the message says why.
401missing_keyNo API key was sent.
401invalid_keyThe key is not a Sumqo key, or it does not exist.
401key_revokedThe key was revoked.
401key_expiredThe key expired.
403browser_not_allowedKeys only work from servers, never from a web browser.
403access_disabledThe workspace turned off all external access.
403workspace_inactiveThe workspace is not active.
403ip_not_allowedThe call came from an address that is not on the key's list.
403insufficient_scopeThe key doesn't have the permission this address needs.
403approval_unsupportedThe workspace requires approval for records created by keys, and this kind of record cannot wait for approval.
404not_foundThere is no such address in the Sumqo API (or the record does not exist).
405method_not_allowedThe address exists but not with this method; see the Allow header.
409idempotency_key_reuseThis Idempotency-Key was already used with a different body or address.
409request_in_progressA request with this Idempotency-Key is still running; retry in a few seconds.
409idempotency_outcome_unknownThe first request with this Idempotency-Key did not finish. Check whether the record was created before retrying with a new key.
413body_too_largeThe body is larger than this address accepts.
415unsupported_media_typeWrites must send Content-Type: application/json.
429rate_limitedToo many requests; wait the number of seconds in Retry-After.
500internal_errorSomething went wrong on our side. The same Idempotency-Key replays this answer; check whether the record was created before retrying with a new key.

What you can read

Each permission unlocks only the addresses listed under it.

Products and services

Read products and servicesView
  • GET /itemsList products and servicesParameters: status, type, search
  • GET /items/{itemId}Read a product or service
curl https://connect.sumqo.com/v1/items \
  -H "Authorization: Bearer sumqo_live_EXAMPLEkeyEXAMPLEkeyEXAMPLEke_a1B2c3"
{
  "data": [
    {
      "id": "a1f0…",
      "name": "Website maintenance",
      "type": "service",
      "rateCents": 12000,
      "status": "active"
    }
  ],
  "meta": {
    "total": 1,
    "count": 1,
    "nextCursor": null
  }
}

Categories

Read categories (chart of accounts)View
  • GET /accountsList categories
  • GET /accounts/{accountId}Read a category
curl https://connect.sumqo.com/v1/accounts \
  -H "Authorization: Bearer sumqo_live_EXAMPLEkeyEXAMPLEkeyEXAMPLEke_a1B2c3"
{
  "data": [
    {
      "id": "c7d2…",
      "name": "Office supplies",
      "type": "expense",
      "accountNumber": "6100"
    }
  ],
  "meta": {
    "total": 1,
    "count": 1,
    "nextCursor": null
  }
}

Invoices

Read invoices, estimates and credit notesConfidential

Anyone holding this key can read every invoice, amount and customer you billed.

  • GET /invoicesList invoicesParameters: limit, cursor, status, customerId, from, to, search
  • GET /invoices/{invoiceId}Read an invoice
  • GET /estimatesList estimatesParameters: limit, cursor, status, customerId, from, to, search
  • GET /estimates/{estimateId}Read an estimate
  • GET /credit-notesList credit notesParameters: limit, cursor, status, customerId, from, to
  • GET /credit-notes/{creditNoteId}Read a credit note
  • GET /recurring-invoicesList recurring invoicesParameters: status
curl https://connect.sumqo.com/v1/invoices \
  -H "Authorization: Bearer sumqo_live_EXAMPLEkeyEXAMPLEkeyEXAMPLEke_a1B2c3"
{
  "data": [
    {
      "id": "5b9e…",
      "invoiceNumber": "INV-1042",
      "customerName": "Northwind Bakery",
      "txnDate": "2026-09-02",
      "dueDate": "2026-10-02",
      "totalCents": 125000,
      "balanceCents": 125000,
      "status": "sent"
    }
  ],
  "meta": {
    "total": 1,
    "count": 1,
    "nextCursor": null
  }
}

Payments received

Read payments receivedConfidential

Anyone holding this key can see who paid you and how much.

  • GET /paymentsList payments receivedParameters: customerId, from, to
  • GET /payments/{paymentId}Read a payment
curl https://connect.sumqo.com/v1/payments \
  -H "Authorization: Bearer sumqo_live_EXAMPLEkeyEXAMPLEkeyEXAMPLEke_a1B2c3"
{
  "data": [
    {
      "id": "9a41…",
      "customerName": "Northwind Bakery",
      "amountCents": 50000,
      "date": "2026-09-15",
      "method": "ach"
    }
  ],
  "meta": {
    "total": 1,
    "count": 1,
    "nextCursor": null
  }
}

Income

Read income recordsConfidential

Anyone holding this key can see your income line by line.

  • GET /incomeList incomeParameters: limit, cursor, customerId, accountId, from, to, search
  • GET /income/{incomeId}Read an income record
curl https://connect.sumqo.com/v1/income \
  -H "Authorization: Bearer sumqo_live_EXAMPLEkeyEXAMPLEkeyEXAMPLEke_a1B2c3"

Expenses

Read expensesConfidential

Anyone holding this key can see what you spend and whom you pay.

  • GET /expensesList expensesParameters: limit, cursor, vendorId, accountId, from, to, search
  • GET /expenses/{expenseId}Read an expense
curl https://connect.sumqo.com/v1/expenses \
  -H "Authorization: Bearer sumqo_live_EXAMPLEkeyEXAMPLEkeyEXAMPLEke_a1B2c3"
{
  "data": [
    {
      "id": "3e77…",
      "vendorName": "Office Depot",
      "amountCents": 8999,
      "date": "2026-09-20",
      "accountName": "Office supplies"
    }
  ],
  "meta": {
    "total": 1,
    "count": 1,
    "nextCursor": null
  }
}

Bills

Read billsConfidential

Anyone holding this key can see the bills you owe and to whom.

  • GET /billsList billsParameters: limit, cursor, status, vendorId, from, to
  • GET /bills/{billId}Read a bill
curl https://connect.sumqo.com/v1/bills \
  -H "Authorization: Bearer sumqo_live_EXAMPLEkeyEXAMPLEkeyEXAMPLEke_a1B2c3"

Bill payments

Read bill paymentsConfidential

Anyone holding this key can see when and how much you paid your vendors.

  • GET /bill-paymentsList bill paymentsParameters: vendorId, from, to
  • GET /bill-payments/{billPaymentId}Read a bill payment
curl https://connect.sumqo.com/v1/bill-payments \
  -H "Authorization: Bearer sumqo_live_EXAMPLEkeyEXAMPLEkeyEXAMPLEke_a1B2c3"

Reports

Read financial reportsConfidential

Anyone holding this key can see your full financial picture (profit, cash, debts).

  • GET /reports/profit-lossRead the profit and loss reportParameters: from, to, basis
  • GET /reports/ar-agingRead who owes you (receivables aging)Parameters: asOf
  • GET /reports/ap-agingRead what you owe (payables aging)Parameters: asOf
  • GET /reports/summaryRead the yearly summaryParameters: year
curl https://connect.sumqo.com/v1/reports/profit-loss \
  -H "Authorization: Bearer sumqo_live_EXAMPLEkeyEXAMPLEkeyEXAMPLEke_a1B2c3"

Customers

Read customers and their contact detailsHigh riskNeeds IP rules · max 90 days

Anyone holding this key can download your customer list with names, emails, phones and addresses.

  • GET /customersList customersParameters: limit, cursor, status, search
  • GET /customers/{customerId}Read a customer
curl https://connect.sumqo.com/v1/customers \
  -H "Authorization: Bearer sumqo_live_EXAMPLEkeyEXAMPLEkeyEXAMPLEke_a1B2c3"
{
  "data": [
    {
      "id": "d3c1…",
      "displayName": "Northwind Bakery",
      "email": "billing@northwind.example",
      "phone": "+1 555 0100"
    }
  ],
  "meta": {
    "total": 1,
    "count": 1,
    "nextCursor": null
  }
}

Vendors

Read vendors and their contact detailsHigh riskNeeds IP rules · max 90 days

Anyone holding this key can download your vendor and contractor list with contact details.

  • GET /vendorsList vendorsParameters: limit, cursor, status, type, search
  • GET /vendors/{vendorId}Read a vendor
curl https://connect.sumqo.com/v1/vendors \
  -H "Authorization: Bearer sumqo_live_EXAMPLEkeyEXAMPLEkeyEXAMPLEke_a1B2c3"

Banking

Read bank and card accounts and their transactionsHigh riskNeeds IP rules · max 90 days

Anyone holding this key can read your bank transactions line by line.

  • GET /bank-accountsList bank and card accountsParameters: status
  • GET /bank-accounts/{accountId}Read a bank or card account
  • GET /bank-transactionsList bank transactionsParameters: accountId, direction, matchStatus, from, to, search
curl https://connect.sumqo.com/v1/bank-accounts \
  -H "Authorization: Bearer sumqo_live_EXAMPLEkeyEXAMPLEkeyEXAMPLEke_a1B2c3"

Downloads

Download the customer list as a fileHigh riskNeeds IP rules · max 90 days

Lets the key download your whole customer list in one file.

  • GET /exports/customersDownload the customer listParameters: format, status, from, to, search
curl https://connect.sumqo.com/v1/exports/customers \
  -H "Authorization: Bearer sumqo_live_EXAMPLEkeyEXAMPLEkeyEXAMPLEke_a1B2c3"
Download the vendor list as a fileHigh riskNeeds IP rules · max 90 days

Lets the key download your whole vendor list in one file.

  • GET /exports/vendorsDownload the vendor listParameters: format, status, from, to, search
curl https://connect.sumqo.com/v1/exports/vendors \
  -H "Authorization: Bearer sumqo_live_EXAMPLEkeyEXAMPLEkeyEXAMPLEke_a1B2c3"
Download invoices as a fileHigh riskNeeds IP rules · max 90 days

Lets the key download every invoice in one file.

  • GET /exports/invoicesDownload invoicesParameters: format, status, from, to, search
curl https://connect.sumqo.com/v1/exports/invoices \
  -H "Authorization: Bearer sumqo_live_EXAMPLEkeyEXAMPLEkeyEXAMPLEke_a1B2c3"
Download expenses as a fileHigh riskNeeds IP rules · max 90 days

Lets the key download every expense in one file.

  • GET /exports/expensesDownload expensesParameters: format, status, from, to, search
curl https://connect.sumqo.com/v1/exports/expenses \
  -H "Authorization: Bearer sumqo_live_EXAMPLEkeyEXAMPLEkeyEXAMPLEke_a1B2c3"
Download bills as a fileHigh riskNeeds IP rules · max 90 days

Lets the key download every bill in one file.

  • GET /exports/billsDownload billsParameters: format, status, from, to, search
curl https://connect.sumqo.com/v1/exports/bills \
  -H "Authorization: Bearer sumqo_live_EXAMPLEkeyEXAMPLEkeyEXAMPLEke_a1B2c3"

Writes

These permissions let a key add records and change some of them. Every write needs an Idempotency-Key header (see Idempotency), and only the fields listed are accepted — anything else is refused with 400. Every change is recorded in the workspace's history under the key's name.

Add and change customersChanges your bookscustomers:writeIP rules recommended · max 180 days

A leaked key could add customers or change a customer's email address so invoices go to someone else. Tax IDs can't be set with a key.

  • POST /customersCreate a customerRequires Idempotency-Key.
    FieldRequiredNotes
    displayNameYes
    companyNameNo
    firstNameNo
    lastNameNo
    emailNoMain email address
    phoneNo
    mobilePhoneNo
    faxNo
    altPhoneNo
    websiteNo
    categoryNo
    notesNo
    paymentTermsNo
    paymentTerms.termsTypeYesOne of: due_on_receipt, net, custom.
    paymentTerms.netDaysNo
    paymentTerms.labelNo
    billingAddressNo
    billingAddress.line1No
    billingAddress.line2No
    billingAddress.cityNo
    billingAddress.stateNo
    billingAddress.postalCodeNo
    billingAddress.countryNo
    shippingAddressNo
    shippingAddress.line1No
    shippingAddress.line2No
    shippingAddress.cityNo
    shippingAddress.stateNo
    shippingAddress.postalCodeNo
    shippingAddress.countryNo
    shippingSameAsBillingNo
    creditLimitCentsNoCredit limit in cents
    contacts[]NoA list.
    contacts[].nameYes
    contacts[].roleNo
    contacts[].emailNo
    contacts[].phoneNo
    contacts[].notesNo
  • PATCH /customers/{customerId}Change a customerRequires Idempotency-Key.
    FieldRequiredNotes
    displayNameNo
    companyNameNo
    firstNameNo
    lastNameNo
    emailNoMain email address
    phoneNo
    mobilePhoneNo
    faxNo
    altPhoneNo
    websiteNo
    categoryNo
    notesNo
    paymentTermsNo
    paymentTerms.termsTypeYesOne of: due_on_receipt, net, custom.
    paymentTerms.netDaysNo
    paymentTerms.labelNo
    billingAddressNo
    billingAddress.line1No
    billingAddress.line2No
    billingAddress.cityNo
    billingAddress.stateNo
    billingAddress.postalCodeNo
    billingAddress.countryNo
    shippingAddressNo
    shippingAddress.line1No
    shippingAddress.line2No
    shippingAddress.cityNo
    shippingAddress.stateNo
    shippingAddress.postalCodeNo
    shippingAddress.countryNo
    shippingSameAsBillingNo
    creditLimitCentsNoCredit limit in cents
curl -X POST https://connect.sumqo.com/v1/customers \
  -H "Authorization: Bearer sumqo_live_EXAMPLEkeyEXAMPLEkeyEXAMPLEke_a1B2c3" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 3f0c9a52-6e1b-4d7a-b8c2-9e4f1a7d2b60" \
  -d '{"displayName":"<displayName>"}'
Add and change vendorsChanges your booksvendors:writeIP rules recommended · max 180 days

A leaked key could add vendors or change a vendor's contact details. Tax IDs, 1099 settings and account numbers can't be set with a key.

  • POST /vendorsCreate a vendorRequires Idempotency-Key.
    FieldRequiredNotes
    displayNameYes
    companyNameNo
    firstNameNo
    lastNameNo
    emailNoMain email address
    phoneNo
    mobilePhoneNo
    faxNo
    altPhoneNo
    websiteNo
    categoryNo
    notesNo
    paymentTermsNo
    paymentTerms.termsTypeYesOne of: due_on_receipt, net, custom.
    paymentTerms.netDaysNo
    paymentTerms.labelNo
    addressNo
    address.line1No
    address.line2No
    address.cityNo
    address.stateNo
    address.postalCodeNo
    address.countryNo
    defaultAccountIdNoDefault expense category
  • PATCH /vendors/{vendorId}Change a vendorRequires Idempotency-Key.
    FieldRequiredNotes
    displayNameNo
    companyNameNo
    firstNameNo
    lastNameNo
    emailNoMain email address
    phoneNo
    mobilePhoneNo
    faxNo
    altPhoneNo
    websiteNo
    categoryNo
    notesNo
    paymentTermsNo
    paymentTerms.termsTypeYesOne of: due_on_receipt, net, custom.
    paymentTerms.netDaysNo
    paymentTerms.labelNo
    addressNo
    address.line1No
    address.line2No
    address.cityNo
    address.stateNo
    address.postalCodeNo
    address.countryNo
    defaultAccountIdNoDefault expense category
curl -X POST https://connect.sumqo.com/v1/vendors \
  -H "Authorization: Bearer sumqo_live_EXAMPLEkeyEXAMPLEkeyEXAMPLEke_a1B2c3" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 3f0c9a52-6e1b-4d7a-b8c2-9e4f1a7d2b60" \
  -d '{"displayName":"<displayName>"}'
Add and change products and servicesChanges your booksitems:writeIP rules recommended · max 180 days

A leaked key could change the names and prices of what you sell.

  • POST /itemsCreate a product or serviceRequires Idempotency-Key.
    FieldRequiredNotes
    nameYes
    unitPriceCentsYes
    descriptionNo
    typeNoOne of: service, product.
    incomeAccountIdNoIncome category
    taxableNo
  • PATCH /items/{itemId}Change a product or serviceRequires Idempotency-Key.
    FieldRequiredNotes
    nameNo
    unitPriceCentsNo
    descriptionNo
    typeNoOne of: service, product.
    incomeAccountIdNoIncome category
    taxableNo
curl -X POST https://connect.sumqo.com/v1/items \
  -H "Authorization: Bearer sumqo_live_EXAMPLEkeyEXAMPLEkeyEXAMPLEke_a1B2c3" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 3f0c9a52-6e1b-4d7a-b8c2-9e4f1a7d2b60" \
  -d '{"name":"<name>","unitPriceCents":"<unitPriceCents>"}'
Create invoicesChanges your booksinvoices:writeIP rules recommended · max 180 days

A leaked key could create invoices (as drafts or issued) that change what your customers owe you. It can never send, void or delete them.

  • POST /invoicesCreate an invoiceRequires Idempotency-Key.
    FieldRequiredNotes
    customerIdYes
    txnDateYesInvoice date (YYYY-MM-DD)
    dueDateNoYYYY-MM-DD; default from the customer’s payment terms
    statusNodraft (default) or sent (issued, not emailed) One of: draft, sent.
    lines[]YesA list.
    lines[].itemIdNo
    lines[].descriptionYes
    lines[].quantityYes
    lines[].rateCentsYesPrice per unit in cents
    lines[].kindNoOne of: item, discount.
    lines[].taxableNo
    lines[].taxRateBpNoTax rate in basis points (825 = 8.25 %)
    lines[].taxFixedCentsNo
    taxCentsNo
    memoNoMessage shown to the customer
    internalNotesNo
curl -X POST https://connect.sumqo.com/v1/invoices \
  -H "Authorization: Bearer sumqo_live_EXAMPLEkeyEXAMPLEkeyEXAMPLEke_a1B2c3" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 3f0c9a52-6e1b-4d7a-b8c2-9e4f1a7d2b60" \
  -d '{"customerId":"<customerId>","txnDate":"<txnDate>","lines":[{"description":"<description>","quantity":"<quantity>","rateCents":"<rateCents>"}]}'
Record payments receivedChanges your bookspayments:writeNeeds IP rules · max 180 days

A leaked key could mark invoices as paid. Keys with this permission only work from the addresses you list.

  • POST /paymentsRecord a paymentRequires Idempotency-Key.
    FieldRequiredNotes
    customerIdYes
    txnDateYesDate received (YYYY-MM-DD)
    amountCentsYes
    methodNoOne of: cash, check, card, bank_transfer, other.
    referenceNumberNo
    instrumentDateNoDate written on the check or transfer order (YYYY-MM-DD), reference only
    memoNo
    depositToNoOne of: account, undeposited.
    appliedTo[]NoInvoices the payment pays A list.
    appliedTo[].invoiceIdYes
    appliedTo[].amountCentsYes
  • POST /sales-receiptsCreate a sales receiptRequires Idempotency-Key.Also needs: invoices:write
    FieldRequiredNotes
    customerIdNoDefault: the walk-in customer
    txnDateYes
    lines[]YesA list.
    lines[].itemIdNo
    lines[].descriptionYes
    lines[].quantityYes
    lines[].rateCentsYesPrice per unit in cents
    lines[].kindNoOne of: item, discount.
    lines[].taxableNo
    lines[].taxRateBpNoTax rate in basis points (825 = 8.25 %)
    lines[].taxFixedCentsNo
    taxCentsNo
    memoNo
    paymentYes
    payment.methodYesOne of: cash, check, card, bank_transfer, other.
    payment.dateNo
    payment.depositToNoOne of: account, undeposited.
    payment.referenceNo
curl -X POST https://connect.sumqo.com/v1/payments \
  -H "Authorization: Bearer sumqo_live_EXAMPLEkeyEXAMPLEkeyEXAMPLEke_a1B2c3" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 3f0c9a52-6e1b-4d7a-b8c2-9e4f1a7d2b60" \
  -d '{"customerId":"<customerId>","txnDate":"<txnDate>","amountCents":"<amountCents>"}'
Add expensesChanges your booksexpenses:writeIP rules recommended · max 180 days

A leaked key could add expenses that change your profit and your taxes.

  • POST /expensesCreate an expenseRequires Idempotency-Key.
    FieldRequiredNotes
    accountIdYesExpense category
    vendorIdNo
    txnDateYes
    amountCentsYes
    paymentMethodNoOne of: cash, check, card, bank_transfer, other.
    paymentMethodLabelNo
    referenceNumberNo
    memoNo
    classIdNo
    locationIdNo
curl -X POST https://connect.sumqo.com/v1/expenses \
  -H "Authorization: Bearer sumqo_live_EXAMPLEkeyEXAMPLEkeyEXAMPLEke_a1B2c3" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 3f0c9a52-6e1b-4d7a-b8c2-9e4f1a7d2b60" \
  -d '{"accountId":"<accountId>","txnDate":"<txnDate>","amountCents":"<amountCents>"}'
Add billsChanges your booksbills:writeIP rules recommended · max 180 days

A leaked key could add bills you owe. It can never pay or approve them.

  • POST /billsCreate a billRequires Idempotency-Key.
    FieldRequiredNotes
    vendorIdYes
    txnDateYes
    dueDateYes
    billNumberNo
    memoNo
    lines[]YesA list.
    lines[].accountIdYesExpense category
    lines[].descriptionNo
    lines[].amountCentsYes
    lines[].itemIdNo
    lines[].quantityNo
    lines[].classIdNo
    lines[].locationIdNo
curl -X POST https://connect.sumqo.com/v1/bills \
  -H "Authorization: Bearer sumqo_live_EXAMPLEkeyEXAMPLEkeyEXAMPLEke_a1B2c3" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 3f0c9a52-6e1b-4d7a-b8c2-9e4f1a7d2b60" \
  -d '{"vendorId":"<vendorId>","txnDate":"<txnDate>","dueDate":"<dueDate>","lines":[{"accountId":"<accountId>","amountCents":"<amountCents>"}]}'
Upload receipts and documentsChanges your booksdocuments:writeIP rules recommended · max 180 days

A leaked key could upload files into your workspace. They are kept without reading unless you turn on reading, and never posted.

  • POST /documentsUpload a documentRequires Idempotency-Key.
    FieldRequiredNotes
    fileNameYes
    mimeTypeYesOne of: application/pdf, image/jpeg, image/png, image/webp, image/heic, image/heif.
    contentBase64YesThe file, base64-encoded (at most 4 MB)
    processingNostore_only (default): keep the file without reading it; review: read it and put it in Documents to review — only when the workspace turned on reading of documents uploaded with keys (at most 30 a day per key) One of: review, store_only.
    noteNo
curl -X POST https://connect.sumqo.com/v1/documents \
  -H "Authorization: Bearer sumqo_live_EXAMPLEkeyEXAMPLEkeyEXAMPLEke_a1B2c3" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 3f0c9a52-6e1b-4d7a-b8c2-9e4f1a7d2b60" \
  -d '{"fileName":"<fileName>","mimeType":"application/pdf","contentBase64":"<contentBase64>"}'
Subscribe to events (webhooks)Changes your bookswebhooks:manageIP rules recommended · max 180 days

A leaked key could send notices about your new invoices, payments and other records to an address of its choice. Notices carry ids and amounts, never names or contact details.

  • POST /hooksSubscribe to eventsRequires Idempotency-Key.
    FieldRequiredNotes
    urlYeshttps address that receives the events
    events[]NoEvent types (see Webhooks) A list.
    eventNoOne event type (Zapier style)
  • DELETE /hooks/{hookId}Unsubscribe from events
curl -X POST https://connect.sumqo.com/v1/hooks \
  -H "Authorization: Bearer sumqo_live_EXAMPLEkeyEXAMPLEkeyEXAMPLEke_a1B2c3" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 3f0c9a52-6e1b-4d7a-b8c2-9e4f1a7d2b60" \
  -d '{"url":"<url>"}'

Idempotency

Networks fail: a write can succeed while its answer is lost. Send a unique Idempotency-Key header with every POST and PATCH — 16 to 128 characters, for example a UUID — and retry with the same value.

  • For 24 hours, a retry with the same key and the same body gets the first answer again instead of creating a second record. That answer carries Idempotent-Replayed: true.
  • The same key with a different body (or another address) is refused with 409 idempotency_key_reuse.
  • A retry that arrives while the first call is still running gets 409 request_in_progress — wait a moment and retry.
  • A write without the header is refused with 400.

Approvals

A workspace can ask for records created by API keys to be approved by a person (for every key, or only for some keys). When that applies to your key:

  • Expenses and bills are created but wait for approval; they don't count in the books until approved. The answer includes "pendingApproval": true.
  • Invoices are always created as drafts — nothing is sent to the customer.
  • Payments are not recorded yet: the answer is 202 Accepted with "pendingApproval": true, and the payment is recorded only when someone approves it (you'll get payment.received by webhook then).
  • Sales receipts are refused, because they record money received at once.
{
  "data": {
    "pendingApproval": true,
    "message": "The payment waits for approval in the workspace."
  }
}

Webhooks

The workspace adds up to 5 endpoints in Settings → External access → Webhooks. Each event is sent as a POST with a JSON body:

{
  "id": "evt_2Jd8x0aQ…",
  "type": "invoice.created",
  "createdAt": "2026-10-01T15:04:05.000Z",
  "data": {
    "object": "invoice",
    "id": "a1b2c3d4-0000-4000-8000-000000000001",
    "invoiceNumber": "INV-1042",
    "status": "draft",
    "totalCents": 125000,
    "paidCents": 0,
    "balanceCents": 125000,
    "currency": "USD",
    "txnDate": "2026-10-01",
    "dueDate": "2026-10-31",
    "customerId": "a1b2c3d4-0000-4000-8000-000000000002"
  },
  "workspace": {
    "id": "9c1f…",
    "name": "Acme Plumbing"
  }
}

Delivery

  • Headers: Content-Type: application/json, User-Agent: Sumqo-Webhooks/1.0, Sumqo-Event-Id, Sumqo-Event-Type and Sumqo-Signature.
  • Answer with any 2xx within 5 seconds. Anything else, or no answer, is retried after 1 minute, 5 minutes, 30 minutes, 2 hours and 12 hours.
  • An endpoint that keeps failing for 3 days is turned off, and the workspace is told.
  • The same event can arrive more than once and events can arrive out of order: use id to skip ones you already handled.
  • Bodies never carry tax IDs, bank account numbers or other secrets — fetch the record with your key when you need more.

Checking the signature

Sumqo-Signature: t=1759330800,v1=5257a869… — t is when it was sent (Unix seconds) and v1 is the hex HMAC-SHA256 of t + "." + body with your endpoint's signing secret.

  • Hash the raw body bytes exactly as received — parsing and re-serializing the JSON changes them.
  • Accept the delivery if anyv1 matches: while a secret is being rotated (24 hours) each delivery carries one signature per secret.
  • Reject it if t is more than 5 minutes (300 seconds) away from your clock — that stops replays.
  • Compare in constant time.
// Node.js 18+ — no dependencies. Run: SUMQO_WEBHOOK_SECRET=whsec_... node server.js
const crypto = require('node:crypto');
const http = require('node:http');

const TOLERANCE_SECONDS = 300;

/**
 * True when rawBody (a Buffer with the exact bytes received) was signed by Sumqo.
 * header = the Sumqo-Signature header, secret = your endpoint's signing secret (whsec_...).
 */
function verifySumqoSignature(rawBody, header, secret, nowSeconds = Math.floor(Date.now() / 1000)) {
  if (typeof header !== 'string' || !secret) return false;
  let timestamp = null;
  const signatures = [];
  for (const part of header.split(',')) {
    const i = part.indexOf('=');
    if (i === -1) continue;
    const key = part.slice(0, i).trim();
    const value = part.slice(i + 1).trim();
    if (key === 't') timestamp = value;
    else if (key === 'v1') signatures.push(value);
  }
  if (!timestamp || !/^\d+$/.test(timestamp) || signatures.length === 0) return false;
  if (Math.abs(nowSeconds - Number(timestamp)) > TOLERANCE_SECONDS) return false;

  const expected = crypto
    .createHmac('sha256', secret)
    .update(Buffer.concat([Buffer.from(timestamp + '.', 'utf8'), rawBody]))
    .digest();
  // Accept if ANY v1 matches: during a secret rotation each delivery carries two.
  return signatures.some((sig) => {
    if (!/^[0-9a-fA-F]{64}$/.test(sig)) return false;
    const given = Buffer.from(sig, 'hex');
    return given.length === expected.length && crypto.timingSafeEqual(given, expected);
  });
}

http
  .createServer((req, res) => {
    if (req.method !== 'POST') {
      res.writeHead(405);
      return res.end();
    }
    const chunks = [];
    req.on('data', (chunk) => chunks.push(chunk));
    req.on('end', () => {
      const rawBody = Buffer.concat(chunks); // the exact bytes — never JSON.stringify(parsed)
      const ok = verifySumqoSignature(rawBody, req.headers['sumqo-signature'], process.env.SUMQO_WEBHOOK_SECRET);
      if (!ok) {
        res.writeHead(400);
        return res.end('Invalid signature');
      }
      const event = JSON.parse(rawBody.toString('utf8'));
      // Deliveries can repeat: skip an event.id you have already handled.
      console.log('Received', event.type, event.id);
      res.writeHead(200);
      res.end('ok');
    });
  })
  .listen(3000, () => console.log('Listening on http://localhost:3000'));

Events

invoice.createdInvoice createdA new invoice was saved (draft or sent).
{
  "object": "invoice",
  "id": "a1b2c3d4-0000-4000-8000-000000000001",
  "invoiceNumber": "INV-1042",
  "status": "draft",
  "totalCents": 125000,
  "paidCents": 0,
  "balanceCents": 125000,
  "currency": "USD",
  "txnDate": "2026-10-01",
  "dueDate": "2026-10-31",
  "customerId": "a1b2c3d4-0000-4000-8000-000000000002"
}
invoice.sentInvoice sentAn invoice was emailed to the customer or marked as sent.
{
  "object": "invoice",
  "id": "a1b2c3d4-0000-4000-8000-000000000001",
  "invoiceNumber": "INV-1042",
  "status": "sent",
  "totalCents": 125000,
  "paidCents": 0,
  "balanceCents": 125000,
  "currency": "USD",
  "txnDate": "2026-10-01",
  "dueDate": "2026-10-31",
  "customerId": "a1b2c3d4-0000-4000-8000-000000000002"
}
invoice.paidInvoice paidA payment (or an online payment, or an applied credit note) brought an invoice to a zero balance.
{
  "object": "invoice",
  "id": "a1b2c3d4-0000-4000-8000-000000000001",
  "invoiceNumber": "INV-1042",
  "status": "paid",
  "totalCents": 125000,
  "paidCents": 125000,
  "balanceCents": 0,
  "currency": "USD",
  "txnDate": "2026-10-01",
  "dueDate": "2026-10-31",
  "customerId": "a1b2c3d4-0000-4000-8000-000000000002",
  "paidAt": "2026-10-20"
}
invoice.voidedInvoice voidedAn invoice was voided.
{
  "object": "invoice",
  "id": "a1b2c3d4-0000-4000-8000-000000000001",
  "invoiceNumber": "INV-1042",
  "status": "void",
  "totalCents": 125000,
  "paidCents": 0,
  "balanceCents": 125000,
  "currency": "USD",
  "txnDate": "2026-10-01",
  "dueDate": "2026-10-31",
  "customerId": "a1b2c3d4-0000-4000-8000-000000000002"
}
payment.receivedPayment receivedA customer payment was recorded (by hand, online or from a bank deposit).
{
  "object": "payment",
  "id": "a1b2c3d4-0000-4000-8000-000000000003",
  "status": "active",
  "amountCents": 125000,
  "currency": "USD",
  "txnDate": "2026-10-20",
  "method": "card",
  "customerId": "a1b2c3d4-0000-4000-8000-000000000002",
  "invoiceIds": [
    "a1b2c3d4-0000-4000-8000-000000000001"
  ]
}
payment.voidedPayment voidedA customer payment was voided: the invoices it paid are open again for the amount it covered. `invoiceIds` names them.
{
  "object": "payment",
  "id": "a1b2c3d4-0000-4000-8000-000000000003",
  "status": "voided",
  "amountCents": 125000,
  "currency": "USD",
  "txnDate": "2026-10-20",
  "method": "card",
  "customerId": "a1b2c3d4-0000-4000-8000-000000000002",
  "invoiceIds": [
    "a1b2c3d4-0000-4000-8000-000000000001"
  ]
}
expense.createdExpense createdA new expense was recorded.
{
  "object": "expense",
  "id": "a1b2c3d4-0000-4000-8000-000000000004",
  "amountCents": 4599,
  "currency": "USD",
  "txnDate": "2026-10-02",
  "paymentMethod": "card",
  "accountId": "a1b2c3d4-0000-4000-8000-000000000005",
  "vendorId": "a1b2c3d4-0000-4000-8000-000000000011"
}
bill.createdBill createdA new bill from a vendor was recorded.
{
  "object": "bill",
  "id": "a1b2c3d4-0000-4000-8000-000000000010",
  "billNumber": "B-77",
  "status": "open",
  "totalCents": 48000,
  "paidCents": 0,
  "balanceCents": 48000,
  "currency": "USD",
  "txnDate": "2026-10-01",
  "dueDate": "2026-10-15",
  "vendorId": "a1b2c3d4-0000-4000-8000-000000000011"
}
bill.paidBill paidA bill payment (or an applied vendor credit) brought a bill to a zero balance.
{
  "object": "bill",
  "id": "a1b2c3d4-0000-4000-8000-000000000010",
  "billNumber": "B-77",
  "status": "paid",
  "totalCents": 48000,
  "paidCents": 48000,
  "balanceCents": 0,
  "currency": "USD",
  "txnDate": "2026-10-01",
  "dueDate": "2026-10-15",
  "vendorId": "a1b2c3d4-0000-4000-8000-000000000011"
}
bill_payment.voidedBill payment voidedA payment to a vendor was voided: the bills it paid are open again for the amount it covered. `billIds` names them.
{
  "object": "bill_payment",
  "id": "a1b2c3d4-0000-4000-8000-000000000012",
  "status": "voided",
  "amountCents": 48000,
  "currency": "USD",
  "txnDate": "2026-10-16",
  "method": "check",
  "vendorId": "a1b2c3d4-0000-4000-8000-000000000011",
  "billIds": [
    "a1b2c3d4-0000-4000-8000-000000000010"
  ]
}
customer.createdCustomer createdA new customer was added.
{
  "object": "customer",
  "id": "a1b2c3d4-0000-4000-8000-000000000002",
  "active": true
}
customer.updatedCustomer updatedA customer was changed. `changedFields` names what changed (never the values).
{
  "object": "customer",
  "id": "a1b2c3d4-0000-4000-8000-000000000002",
  "active": true,
  "changedFields": [
    "email",
    "phone"
  ]
}
vendor.createdVendor createdA new vendor was added.
{
  "object": "vendor",
  "id": "a1b2c3d4-0000-4000-8000-000000000011",
  "active": true
}
vendor.updatedVendor updatedA vendor was changed. `changedFields` names what changed (never the values).
{
  "object": "vendor",
  "id": "a1b2c3d4-0000-4000-8000-000000000011",
  "active": true,
  "changedFields": [
    "paymentTerms"
  ]
}
sales_receipt.createdSales receipt createdA sale was recorded and paid in one step.
{
  "object": "sales_receipt",
  "id": "a1b2c3d4-0000-4000-8000-000000000006",
  "number": "SR-1007",
  "status": "paid",
  "totalCents": 8900,
  "currency": "USD",
  "txnDate": "2026-10-03",
  "customerId": "a1b2c3d4-0000-4000-8000-000000000002",
  "paymentId": "a1b2c3d4-0000-4000-8000-000000000007"
}
statement.importedBank statement importedA bank or card statement file was imported (or imported again with new transactions).
{
  "object": "statement",
  "id": "a1b2c3d4-0000-4000-8000-000000000008",
  "accountId": "a1b2c3d4-0000-4000-8000-000000000009",
  "periodStart": "2026-09-01",
  "periodEnd": "2026-09-30",
  "transactionsAdded": 42,
  "alreadyImported": 0
}

A ping event is sent when someone clicks "Send test event"; it can't be subscribed to.

REST hooks (Zapier and similar)

A key with the webhooks:manage permission can add and remove its own endpoints, so an integration can subscribe when it is turned on:

  • POST /hooks with {"url": "https://hooks.example.com/…", "events": ["invoice.paid"]} → 201 with the hook's id, its events and its signing secret (shown once).
  • DELETE /hooks/{hookId} removes it. A key can only remove hooks it added itself.
  • Hooks count towards the 5-endpoint limit, show in the workspace's settings as "Added by API key · name", and stop when the key is revoked.
curl -X POST https://connect.sumqo.com/v1/hooks \
  -H "Authorization: Bearer sumqo_live_EXAMPLEkeyEXAMPLEkeyEXAMPLEke_a1B2c3" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: b71e4d09-2c5a-4f83-9d16-7a0e3c8b5f24" \
  -d '{"url":"https://hooks.example.com/catch/123/abc","events":["invoice.paid"]}'

AI assistants (MCP)

Sumqo runs a remote Model Context Protocol server (Streamable HTTP, stateless) so Claude, ChatGPT, Cursor and other AI assistants can read a workspace's books and propose changes. Server address: https://connect.sumqo.com/mcp.

  • Sign in with Sumqo (OAuth 2.1). The assistant discovers Sumqo's authorization server from the server's protected-resource metadata; the person chooses the workspace and what the assistant may do. PKCE (S256) is required, tokens are bound to this server, access tokens last one hour and refresh tokens rotate on every use. Apps can identify themselves with a Client ID Metadata Document or register dynamically (shown as unverified).
  • Or an API key as Authorization: Bearer sumqo_live_… for tools that take a header — the key's own permissions, address rules and limits apply.
  • Changes are proposals. Every propose_* tool files a request in the workspace's Needs review queue and answers "Waiting for approval"; nothing changes until a person approves it.
  • Results are data. Free text written by other people (memos, notes, descriptions, document text) comes back as {"untrusted_text": …} and must never be treated as instructions.
  • No tool sends email, pays, voids, deletes, downloads files or manages webhooks.
ToolWhat it doesPermission
list_products_and_services List products and services items:readView
get_product_or_service Get product or service items:readView
list_categories List categories accounts:readView
get_category Get category accounts:readView
list_invoices List invoices invoices:readConfidential
get_invoice Get invoice invoices:readConfidential
list_estimates List estimates invoices:readConfidential
get_estimate Get estimate invoices:readConfidential
list_credit_notes List credit notes invoices:readConfidential
get_credit_note Get credit note invoices:readConfidential
list_recurring_invoices List recurring invoices invoices:readConfidential
list_payments_received List payments received payments:readConfidential
get_payment_received Get payment received payments:readConfidential
list_income List income income:readConfidential
get_income Get income income:readConfidential
list_expenses List expenses expenses:readConfidential
get_expense Get expense expenses:readConfidential
list_bills List bills bills:readConfidential
get_bill Get bill bills:readConfidential
list_bill_payments List bill payments bill_payments:readConfidential
get_bill_payment Get bill payment bill_payments:readConfidential
report_profit_and_loss Report profit and loss reports:readConfidential
report_receivables_aging Report receivables aging reports:readConfidential
report_payables_aging Report payables aging reports:readConfidential
report_yearly_summary Report yearly summary reports:readConfidential
list_customers List customers customers:readHigh risk
get_customer Get customer customers:readHigh risk
list_vendors List vendors vendors:readHigh risk
get_vendor Get vendor vendors:readHigh risk
list_bank_accounts List bank accounts banking:readHigh risk
get_bank_account Get bank account banking:readHigh risk
list_bank_transactions List bank transactions banking:readHigh risk
propose_new_customer Propose new customer Waits for approvalcustomers:writeChanges your books
propose_customer_change Propose customer change Waits for approvalcustomers:writeChanges your books
propose_new_vendor Propose new vendor Waits for approvalvendors:writeChanges your books
propose_vendor_change Propose vendor change Waits for approvalvendors:writeChanges your books
propose_new_product_or_service Propose new product or service Waits for approvalitems:writeChanges your books
propose_product_or_service_change Propose product or service change Waits for approvalitems:writeChanges your books
propose_invoice Propose invoice Waits for approvalinvoices:writeChanges your books
propose_payment_received Propose payment received Waits for approvalpayments:writeChanges your books
propose_expense Propose expense Waits for approvalexpenses:writeChanges your books
propose_bill Propose bill Waits for approvalbills:writeChanges your books
profit_and_loss_by_category Profit and loss by category reports:readConfidential
aging_summary Who owes what (aging) reports:readConfidential
business_overview Business overview reports:readConfidential
cash_flow_forecast Cash-flow forecast reports:readConfidential

Not available to API keys

Whatever their permissions, keys can never:

  • Void, delete or restore anything
  • Pay bills or record any other money going out
  • Change bank accounts, bank transactions or reconciliations
  • Approve bills or expenses
  • Send emails to customers or vendors
  • Post journal entries or change the chart of accounts
  • Prepare or change tax filings, or see full tax IDs
  • Change workspace settings, payment or payout details, people or permissions
  • Download a full backup of the workspace

A person signed in to Sumqo does these.

Examples use made-up data. Questions? Ask the workspace owner who created your key.