Customers API
REST endpoints for customers in SKUFlow: 19 operations with parameters, request and response fields, permissions and curl examples.
Base URL: https://app.skuflow.ai/api. Every call needs Authorization: Bearer skf_… (Authentication); {organizationId} ($ORGANIZATION_ID in the examples) is your business's id.
| Operation | Endpoint | Risk |
|---|---|---|
| List customers | GET /organizations/{organizationId}/customers | read |
| Create a customer | POST /organizations/{organizationId}/customers | write |
| All customers that are not archived, for pickers | GET /organizations/{organizationId}/customers/options | read |
| Get a customer | GET /organizations/{organizationId}/customers/{customerId} | read |
| Update a customer | PATCH /organizations/{organizationId}/customers/{customerId} | write |
| Delete a customer | DELETE /organizations/{organizationId}/customers/{customerId} | sensitive |
| List a customer's contacts | GET /organizations/{organizationId}/customers/{customerId}/contacts | read |
| Add a contact | POST /organizations/{organizationId}/customers/{customerId}/contacts | write |
| Update a contact | PATCH /organizations/{organizationId}/customers/{customerId}/contacts/{contactId} | write |
| Delete a contact | DELETE /organizations/{organizationId}/customers/{customerId}/contacts/{contactId} | sensitive |
| List notes on a customer or property | GET /organizations/{organizationId}/{ownerType}/{ownerId}/notes | read |
| Add a note | POST /organizations/{organizationId}/{ownerType}/{ownerId}/notes | write |
| Edit or pin a note | PATCH /organizations/{organizationId}/{ownerType}/{ownerId}/notes/{noteId} | write |
| Delete a note | DELETE /organizations/{organizationId}/{ownerType}/{ownerId}/notes/{noteId} | sensitive |
| List files on a customer or property | GET /organizations/{organizationId}/{ownerType}/{ownerId}/attachments | read |
| Start a file upload | POST /organizations/{organizationId}/{ownerType}/{ownerId}/attachments | write |
| Confirm a finished upload | POST /organizations/{organizationId}/{ownerType}/{ownerId}/attachments/{attachmentId}/confirm | write |
| Delete a file | DELETE /organizations/{organizationId}/{ownerType}/{ownerId}/attachments/{attachmentId} | sensitive |
| Attach a file (content in the request) | POST /organizations/{organizationId}/{ownerType}/{ownerId}/attachments/content | write |
List customers
GET /organizations/{organizationId}/customers
Customers by name, filtered by text (name, company, email, phone), status, type, tag or lead source; paged with a total. Archived customers are left out unless status=ARCHIVED. Read-only. Use GET /customers/options for a picker list.
| Permission | customers.read (Owner, Admin, Staff, Member) |
| Risk | read, OAuth scope skuflow.read. Read: reads only. |
| MCP tool | customers_list (toolset customers, in the default set) |
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
query | query | string | No | Free-text search over the list's names, numbers and contact details. Up to 200 characters. |
status | query | enum | No | One of LEAD, ACTIVE, INACTIVE, ARCHIVED. |
type | query | enum | No | One of RESIDENTIAL, COMMERCIAL. |
tag | query | string | No | Up to 200 characters. |
leadSourceId | query | string | No | |
limit | query | integer | No | Page size, 1–200. Default 50. |
offset | query | integer | No | Rows to skip; the answer's total counts every match. Default 0. |
Example
curl "https://app.skuflow.ai/api/organizations/$ORGANIZATION_ID/customers" \
-H "Authorization: Bearer $SKUFLOW_API_KEY"Response
200: the answer's fields
| Field | Type | Description |
|---|---|---|
items | array of object | |
items[].id | string | Customer id. |
items[].organizationId | string | |
items[].type | enum | One of RESIDENTIAL, COMMERCIAL. |
items[].title | string or null | |
items[].firstName | string or null | |
items[].lastName | string or null | |
items[].companyName | string or null | |
items[].name | string | Display name, derived from the person or company name. |
items[].status | enum | LEAD until the first sale, then ACTIVE; ARCHIVED customers leave the lists. One of LEAD, ACTIVE, INACTIVE, ARCHIVED. |
items[].email | string or null | The primary contact's main email. |
items[].phone | string or null | The primary contact's main phone. |
items[].address | string or null | Billing address (street). |
items[].address2 | string or null | |
items[].city | string or null | |
items[].state | string or null | |
items[].zip | string or null | |
items[].country | string or null | |
items[].leadSourceId | string or null | From GET /lead-sources. |
items[].tags | array of string | |
items[].paymentTerms | enum | Invoice due date rule; CUSTOM uses paymentTermsDays. One of DUE_ON_RECEIPT, NET_7, NET_15, NET_30, NET_45, NET_60, CUSTOM. |
items[].paymentTermsDays | integer or null | Days to pay, for CUSTOM terms. |
items[].taxExempt | boolean | Invoices and quotes carry no tax. |
items[].taxExemptId | string or null | |
items[].creditLimitCents | integer or null | Credit limit in integer cents; null = none. |
items[].doNotService | boolean | Quote and invoice screens warn before new work is sold. |
items[].doNotServiceReason | string or null | |
items[].allowEmail | boolean | |
items[].allowSms | boolean | |
items[].allowMarketing | boolean | |
items[].notes | string or null | |
items[].customFields | any | Custom field values by key (definitions: GET /custom-fields). |
items[].createdAt | string (date-time) | |
items[].updatedAt | string (date-time) | |
items[].contacts | array of object | Contacts, primary first. |
items[].contacts[].id | string | Contact id. |
items[].contacts[].customerId | string | |
items[].contacts[].firstName | string or null | |
items[].contacts[].lastName | string or null | |
items[].contacts[].jobTitle | string or null | |
items[].contacts[].roles | array of enum | What the contact is for; the primary contact has PRIMARY. |
items[].contacts[].isPrimary | boolean | The primary contact; its main email and phone mirror onto the customer. |
items[].contacts[].notes | string or null | |
items[].contacts[].createdAt | string (date-time) | |
items[].contacts[].updatedAt | string (date-time) | |
items[].contacts[].methods | array of object | Phones and emails, primary first. |
items[].leadSource | object or null | |
items[].leadSource.id | string | |
items[].leadSource.name | string | |
items[].balanceCents | integer | Open balance: due on SENT/OVERDUE invoices less unapplied deposits; negative = in credit, in integer cents. |
items[].lifetimeValueCents | integer | Every payment collected, net of refunds, tips excluded, in integer cents. |
items[].overCreditLimit | boolean | The balance is above creditLimitCents. |
total | integer | How many match the filters, across all pages. |
Create a customer
POST /organizations/{organizationId}/customers
Creates a customer, optionally with contacts (else one primary contact from the names, email and phone) and an address (property) the customer owns, e.g. their ship-to location. Custom field values are validated against GET /custom-fields. Search GET /customers first to avoid duplicates. Nothing is sent to the customer. Returns the customer.
| Permission | customers.write (Owner, Admin, Staff) |
| Risk | write, OAuth scope skuflow.write. Write: creates or changes records inside the business; nothing leaves it. |
| MCP tool | customers_create (toolset customers, in the default set) |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
type | enum | No | One of RESIDENTIAL, COMMERCIAL. |
title | string or null | No | |
firstName | string or null | No | |
lastName | string or null | No | |
companyName | string or null | No | |
name | string | No | |
status | enum | No | One of LEAD, ACTIVE, INACTIVE, ARCHIVED. |
email | empty string or string (email) or null | No | |
phone | string or null | No | |
address | string or null | No | |
address2 | string or null | No | |
city | string or null | No | |
state | string or null | No | |
zip | string or null | No | |
country | string or null | No | |
leadSourceId | string or null | No | |
tags | array of string | No | Up to 100 items. |
paymentTerms | enum | No | One of DUE_ON_RECEIPT, NET_7, NET_15, NET_30, NET_45, NET_60, CUSTOM. |
paymentTermsDays | integer or null | No | 0–3650. |
taxExempt | boolean | No | |
taxExemptId | string or null | No | |
creditLimitCents | integer or null | No | 0–1000000000. |
doNotService | boolean | No | |
doNotServiceReason | string or null | No | |
allowEmail | boolean | No | |
allowSms | boolean | No | |
allowMarketing | boolean | No | |
notes | string or null | No | |
customFields | object | No | |
contacts | array of object | No | Up to 50 items. |
contacts[].firstName | string or null | No | |
contacts[].lastName | string or null | No | |
contacts[].jobTitle | string or null | No | |
contacts[].roles | array of enum | No | Up to 7 items. |
contacts[].isPrimary | boolean | No | |
contacts[].notes | string or null | No | |
contacts[].methods | array of object | No | Up to 50 items. |
contacts[].methods[].kind | enum | Yes | One of PHONE, EMAIL. |
contacts[].methods[].label | enum | No | One of MAIN, MOBILE, WORK, HOME, FAX, OTHER. |
contacts[].methods[].value | string | Yes | Up to 500 characters. |
contacts[].methods[].isPrimary | boolean | No | |
contacts[].methods[].smsConsent | enum | No | One of UNKNOWN, GRANTED, DENIED. |
property | object or null | No | |
property.name | string or null | No | Up to 500 characters. |
property.address | string or null | No | Up to 500 characters. |
property.address2 | string or null | No | Up to 500 characters. |
property.city | string or null | No | Up to 500 characters. |
property.county | string or null | No | County (or parish), for tax rules that name one. Up to 500 characters. |
property.state | string or null | No | Up to 500 characters. |
property.zip | string or null | No | Up to 500 characters. |
property.country | string or null | No | Up to 500 characters. |
property.propertyType | enum | No | One of RESIDENTIAL, COMMERCIAL, MULTI_FAMILY, INDUSTRIAL. |
property.contactName | string or null | No | Up to 500 characters. |
property.contactPhone | string or null | No | Up to 500 characters. |
property.contactEmail | empty string or string (email) or null | No | |
property.timezone | string | No | An IANA timezone, e.g. America/Chicago. Up to 100 characters. |
property.accessInstructions | string or null | No | Delivery or drop-off instructions for this address. Up to 5000 characters. |
property.tags | array of string | No | Up to 100 items. |
property.taxRate | number or null | No | Tax rate in percent for sales shipped here; null = the business default. -1000–1000. |
property.customFields | object | No | |
property.role | enum | No | One of OWNER, TENANT, LANDLORD, PROPERTY_MANAGER. |
Example
curl -X POST "https://app.skuflow.ai/api/organizations/$ORGANIZATION_ID/customers" \
-H "Authorization: Bearer $SKUFLOW_API_KEY" \
-H "Content-Type: application/json" \
-d '{"type":"RESIDENTIAL","title":"string","firstName":"string"}'Response
201: the answer's fields
| Field | Type | Description |
|---|---|---|
id | string | Customer id. |
organizationId | string | |
type | enum | One of RESIDENTIAL, COMMERCIAL. |
title | string or null | |
firstName | string or null | |
lastName | string or null | |
companyName | string or null | |
name | string | Display name, derived from the person or company name. |
status | enum | LEAD until the first sale, then ACTIVE; ARCHIVED customers leave the lists. One of LEAD, ACTIVE, INACTIVE, ARCHIVED. |
email | string or null | The primary contact's main email. |
phone | string or null | The primary contact's main phone. |
address | string or null | Billing address (street). |
address2 | string or null | |
city | string or null | |
state | string or null | |
zip | string or null | |
country | string or null | |
leadSourceId | string or null | From GET /lead-sources. |
tags | array of string | |
paymentTerms | enum | Invoice due date rule; CUSTOM uses paymentTermsDays. One of DUE_ON_RECEIPT, NET_7, NET_15, NET_30, NET_45, NET_60, CUSTOM. |
paymentTermsDays | integer or null | Days to pay, for CUSTOM terms. |
taxExempt | boolean | Invoices and quotes carry no tax. |
taxExemptId | string or null | |
creditLimitCents | integer or null | Credit limit in integer cents; null = none. |
doNotService | boolean | Quote and invoice screens warn before new work is sold. |
doNotServiceReason | string or null | |
allowEmail | boolean | |
allowSms | boolean | |
allowMarketing | boolean | |
notes | string or null | |
customFields | any | Custom field values by key (definitions: GET /custom-fields). |
createdAt | string (date-time) | |
updatedAt | string (date-time) | |
contacts | array of object | |
contacts[].id | string | Contact id. |
contacts[].customerId | string | |
contacts[].firstName | string or null | |
contacts[].lastName | string or null | |
contacts[].jobTitle | string or null | |
contacts[].roles | array of enum | What the contact is for; the primary contact has PRIMARY. |
contacts[].isPrimary | boolean | The primary contact; its main email and phone mirror onto the customer. |
contacts[].notes | string or null | |
contacts[].createdAt | string (date-time) | |
contacts[].updatedAt | string (date-time) | |
contacts[].methods | array of object | Phones and emails, primary first. |
contacts[].methods[].id | string | |
contacts[].methods[].contactId | string | |
contacts[].methods[].kind | enum | One of PHONE, EMAIL. |
contacts[].methods[].label | enum | One of MAIN, MOBILE, WORK, HOME, FAX, OTHER. |
contacts[].methods[].value | string | The phone number or email address. |
contacts[].methods[].isPrimary | boolean | The contact's main phone or email. |
contacts[].methods[].smsConsent | enum | OK to text this phone (DENIED after the number replied STOP). One of UNKNOWN, GRANTED, DENIED. |
contacts[].methods[].sortOrder | integer |
All customers that are not archived, for pickers
GET /organizations/{organizationId}/customers/options
Every customer that is not archived, as id and display name only, for pickers and to find a customer's id by name. Read-only.
| Permission | customers.read (Owner, Admin, Staff, Member) |
| Risk | read, OAuth scope skuflow.read. Read: reads only. |
| MCP tool | customers_options (toolset customers) |
Example
curl "https://app.skuflow.ai/api/organizations/$ORGANIZATION_ID/customers/options" \
-H "Authorization: Bearer $SKUFLOW_API_KEY"Response
200: the answer's fields
An array of objects:
| Field | Type | Description |
|---|---|---|
id | string | Customer id. |
name | string | |
address | string or null | |
city | string or null | |
doNotService | boolean | |
doNotServiceReason | string or null | |
creditLimitCents | integer or null | |
taxExempt | boolean | |
paymentTerms | enum | Invoice due date rule; CUSTOM uses paymentTermsDays. One of DUE_ON_RECEIPT, NET_7, NET_15, NET_30, NET_45, NET_60, CUSTOM. |
paymentTermsDays | integer or null | |
balanceCents | integer | Open balance: due on SENT/OVERDUE invoices less unapplied deposits; negative = in credit, in integer cents. |
lifetimeValueCents | integer | Every payment collected, net of refunds, tips excluded, in integer cents. |
overCreditLimit | boolean | The balance is above creditLimitCents. |
Get a customer
GET /organizations/{organizationId}/customers/{customerId}
One customer with contacts, current and past properties (their addresses / ship-to locations), open balance (balanceCents, negative = in credit) and lifetime value. Quotes, invoices and requests have their own list calls filtered by customer. Read-only.
| Permission | customers.read (Owner, Admin, Staff, Member) |
| Risk | read, OAuth scope skuflow.read. Read: reads only. |
| MCP tool | customers_get (toolset customers, in the default set) |
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
customerId | path | string | Yes |
Example
curl "https://app.skuflow.ai/api/organizations/$ORGANIZATION_ID/customers/{customerId}" \
-H "Authorization: Bearer $SKUFLOW_API_KEY"Response
200: the answer's fields
| Field | Type | Description |
|---|---|---|
id | string | Customer id. |
organizationId | string | |
type | enum | One of RESIDENTIAL, COMMERCIAL. |
title | string or null | |
firstName | string or null | |
lastName | string or null | |
companyName | string or null | |
name | string | Display name, derived from the person or company name. |
status | enum | LEAD until the first sale, then ACTIVE; ARCHIVED customers leave the lists. One of LEAD, ACTIVE, INACTIVE, ARCHIVED. |
email | string or null | The primary contact's main email. |
phone | string or null | The primary contact's main phone. |
address | string or null | Billing address (street). |
address2 | string or null | |
city | string or null | |
state | string or null | |
zip | string or null | |
country | string or null | |
leadSourceId | string or null | From GET /lead-sources. |
tags | array of string | |
paymentTerms | enum | Invoice due date rule; CUSTOM uses paymentTermsDays. One of DUE_ON_RECEIPT, NET_7, NET_15, NET_30, NET_45, NET_60, CUSTOM. |
paymentTermsDays | integer or null | Days to pay, for CUSTOM terms. |
taxExempt | boolean | Invoices and quotes carry no tax. |
taxExemptId | string or null | |
creditLimitCents | integer or null | Credit limit in integer cents; null = none. |
doNotService | boolean | Quote and invoice screens warn before new work is sold. |
doNotServiceReason | string or null | |
allowEmail | boolean | |
allowSms | boolean | |
allowMarketing | boolean | |
notes | string or null | |
customFields | any | Custom field values by key (definitions: GET /custom-fields). |
createdAt | string (date-time) | |
updatedAt | string (date-time) | |
contacts | array of object | Contacts, primary first. |
contacts[].id | string | Contact id. |
contacts[].customerId | string | |
contacts[].firstName | string or null | |
contacts[].lastName | string or null | |
contacts[].jobTitle | string or null | |
contacts[].roles | array of enum | What the contact is for; the primary contact has PRIMARY. |
contacts[].isPrimary | boolean | The primary contact; its main email and phone mirror onto the customer. |
contacts[].notes | string or null | |
contacts[].createdAt | string (date-time) | |
contacts[].updatedAt | string (date-time) | |
contacts[].methods | array of object | Phones and emails, primary first. |
contacts[].methods[].id | string | |
contacts[].methods[].contactId | string | |
contacts[].methods[].kind | enum | One of PHONE, EMAIL. |
contacts[].methods[].label | enum | One of MAIN, MOBILE, WORK, HOME, FAX, OTHER. |
contacts[].methods[].value | string | The phone number or email address. |
contacts[].methods[].isPrimary | boolean | The contact's main phone or email. |
contacts[].methods[].smsConsent | enum | OK to text this phone (DENIED after the number replied STOP). One of UNKNOWN, GRANTED, DENIED. |
contacts[].methods[].sortOrder | integer | |
leadSource | object or null | |
leadSource.id | string | |
leadSource.name | string | |
occupancies | array of object | Current and past properties (current first), with the property. |
occupancies[].id | string | Occupancy id (for PATCH …/occupancies/{occupancyId}). |
occupancies[].propertyId | string | |
occupancies[].customerId | string | |
occupancies[].role | enum | One of OWNER, TENANT, LANDLORD, PROPERTY_MANAGER. |
occupancies[].isPrimary | boolean | The property's primary current occupant. |
occupancies[].startedOn | string | First day of the link, YYYY-MM-DD. |
occupancies[].endedOn | string or null | Last day, YYYY-MM-DD; null = current. |
occupancies[].createdAt | string (date-time) | |
occupancies[].property | object | |
occupancies[].property.id | string | Property id. |
occupancies[].property.organizationId | string | |
occupancies[].property.name | string or null | |
occupancies[].property.address | string or null | |
occupancies[].property.address2 | string or null | |
occupancies[].property.city | string or null | |
occupancies[].property.county | string or null | County, for tax rules that name one. |
occupancies[].property.state | string or null | |
occupancies[].property.zip | string or null | |
occupancies[].property.country | string or null | |
occupancies[].property.propertyType | enum | One of RESIDENTIAL, COMMERCIAL, MULTI_FAMILY, INDUSTRIAL. |
occupancies[].property.contactName | string or null | Who receives deliveries at this address. |
occupancies[].property.contactPhone | string or null | |
occupancies[].property.contactEmail | string or null | |
occupancies[].property.timezone | string | IANA timezone of the property. |
occupancies[].property.accessInstructions | string or null | Delivery or drop-off instructions for this address. |
occupancies[].property.tags | array of string | |
occupancies[].property.taxRate | number or null | Tax rate in percent for sales shipped here, overriding the tax rules; null = the business's tax rules for the address. |
occupancies[].property.customFields | any | Custom field values by key (definitions: GET /custom-fields). |
occupancies[].property.createdAt | string (date-time) | |
occupancies[].property.updatedAt | string (date-time) | |
balanceCents | integer | Open balance: due on SENT/OVERDUE invoices less unapplied deposits; negative = in credit, in integer cents. |
lifetimeValueCents | integer | Every payment collected, net of refunds, tips excluded, in integer cents. |
overCreditLimit | boolean | The balance is above creditLimitCents. |
Update a customer
PATCH /organizations/{organizationId}/customers/{customerId}
Changes the fields sent (status, names, billing terms, tax exemption, tags, custom fields …). Email and phone mirror the primary contact; change contacts with the /contacts calls. Returns the customer.
| Permission | customers.write (Owner, Admin, Staff) |
| Risk | write, OAuth scope skuflow.write. Write: creates or changes records inside the business; nothing leaves it. |
| MCP tool | customers_update (toolset customers, in the default set) |
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
customerId | path | string | Yes |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
type | enum | No | One of RESIDENTIAL, COMMERCIAL. |
title | string or null | No | |
firstName | string or null | No | |
lastName | string or null | No | |
companyName | string or null | No | |
name | string | No | |
status | enum | No | One of LEAD, ACTIVE, INACTIVE, ARCHIVED. |
email | empty string or string (email) or null | No | |
phone | string or null | No | |
address | string or null | No | |
address2 | string or null | No | |
city | string or null | No | |
state | string or null | No | |
zip | string or null | No | |
country | string or null | No | |
leadSourceId | string or null | No | |
tags | array of string | No | Up to 100 items. |
paymentTerms | enum | No | One of DUE_ON_RECEIPT, NET_7, NET_15, NET_30, NET_45, NET_60, CUSTOM. |
paymentTermsDays | integer or null | No | 0–3650. |
taxExempt | boolean | No | |
taxExemptId | string or null | No | |
creditLimitCents | integer or null | No | 0–1000000000. |
doNotService | boolean | No | |
doNotServiceReason | string or null | No | |
allowEmail | boolean | No | |
allowSms | boolean | No | |
allowMarketing | boolean | No | |
notes | string or null | No | |
customFields | object | No |
Example
curl -X PATCH "https://app.skuflow.ai/api/organizations/$ORGANIZATION_ID/customers/{customerId}" \
-H "Authorization: Bearer $SKUFLOW_API_KEY" \
-H "Content-Type: application/json" \
-d '{"type":"RESIDENTIAL","title":"string","firstName":"string"}'Response
200: the answer's fields
| Field | Type | Description |
|---|---|---|
id | string | Customer id. |
organizationId | string | |
type | enum | One of RESIDENTIAL, COMMERCIAL. |
title | string or null | |
firstName | string or null | |
lastName | string or null | |
companyName | string or null | |
name | string | Display name, derived from the person or company name. |
status | enum | LEAD until the first sale, then ACTIVE; ARCHIVED customers leave the lists. One of LEAD, ACTIVE, INACTIVE, ARCHIVED. |
email | string or null | The primary contact's main email. |
phone | string or null | The primary contact's main phone. |
address | string or null | Billing address (street). |
address2 | string or null | |
city | string or null | |
state | string or null | |
zip | string or null | |
country | string or null | |
leadSourceId | string or null | From GET /lead-sources. |
tags | array of string | |
paymentTerms | enum | Invoice due date rule; CUSTOM uses paymentTermsDays. One of DUE_ON_RECEIPT, NET_7, NET_15, NET_30, NET_45, NET_60, CUSTOM. |
paymentTermsDays | integer or null | Days to pay, for CUSTOM terms. |
taxExempt | boolean | Invoices and quotes carry no tax. |
taxExemptId | string or null | |
creditLimitCents | integer or null | Credit limit in integer cents; null = none. |
doNotService | boolean | Quote and invoice screens warn before new work is sold. |
doNotServiceReason | string or null | |
allowEmail | boolean | |
allowSms | boolean | |
allowMarketing | boolean | |
notes | string or null | |
customFields | any | Custom field values by key (definitions: GET /custom-fields). |
createdAt | string (date-time) | |
updatedAt | string (date-time) | |
contacts | array of object | |
contacts[].id | string | Contact id. |
contacts[].customerId | string | |
contacts[].firstName | string or null | |
contacts[].lastName | string or null | |
contacts[].jobTitle | string or null | |
contacts[].roles | array of enum | What the contact is for; the primary contact has PRIMARY. |
contacts[].isPrimary | boolean | The primary contact; its main email and phone mirror onto the customer. |
contacts[].notes | string or null | |
contacts[].createdAt | string (date-time) | |
contacts[].updatedAt | string (date-time) | |
contacts[].methods | array of object | Phones and emails, primary first. |
contacts[].methods[].id | string | |
contacts[].methods[].contactId | string | |
contacts[].methods[].kind | enum | One of PHONE, EMAIL. |
contacts[].methods[].label | enum | One of MAIN, MOBILE, WORK, HOME, FAX, OTHER. |
contacts[].methods[].value | string | The phone number or email address. |
contacts[].methods[].isPrimary | boolean | The contact's main phone or email. |
contacts[].methods[].smsConsent | enum | OK to text this phone (DENIED after the number replied STOP). One of UNKNOWN, GRANTED, DENIED. |
contacts[].methods[].sortOrder | integer |
Delete a customer
DELETE /organizations/{organizationId}/customers/{customerId}
Destructive: deletes the customer with their contacts, notes, quotes, invoices and payments. Refused (409) while the customer has or had a property (end or transfer it, or archive the customer with PATCH status: ARCHIVED, which keeps everything).
| Permission | customers.delete (Owner, Admin) |
| Risk | sensitive, OAuth scope skuflow.sensitive. Sensitive: moves money, deletes, sends or shows something to a customer, or grants or revokes access. |
| MCP tool | customers_delete (toolset customers) |
| Approval | Over MCP, this tool always waits for your approval in SKUFlow. See Approvals. |
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
customerId | path | string | Yes |
Example
curl -X DELETE "https://app.skuflow.ai/api/organizations/$ORGANIZATION_ID/customers/{customerId}" \
-H "Authorization: Bearer $SKUFLOW_API_KEY"Response
200: the answer's fields
| Field | Type | Description |
|---|---|---|
ok | true |
List a customer's contacts
GET /organizations/{organizationId}/customers/{customerId}/contacts
The customer's contacts with their roles (primary, billing, site …) and phones and emails. Read-only.
| Permission | customers.read (Owner, Admin, Staff, Member) |
| Risk | read, OAuth scope skuflow.read. Read: reads only. |
| MCP tool | contacts_list (toolset customers) |
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
customerId | path | string | Yes |
Example
curl "https://app.skuflow.ai/api/organizations/$ORGANIZATION_ID/customers/{customerId}/contacts" \
-H "Authorization: Bearer $SKUFLOW_API_KEY"Response
200: the answer's fields
An array of objects:
| Field | Type | Description |
|---|---|---|
id | string | Contact id. |
customerId | string | |
firstName | string or null | |
lastName | string or null | |
jobTitle | string or null | |
roles | array of enum | What the contact is for; the primary contact has PRIMARY. |
isPrimary | boolean | The primary contact; its main email and phone mirror onto the customer. |
notes | string or null | |
createdAt | string (date-time) | |
updatedAt | string (date-time) | |
methods | array of object | Phones and emails, primary first. |
methods[].id | string | |
methods[].contactId | string | |
methods[].kind | enum | One of PHONE, EMAIL. |
methods[].label | enum | One of MAIN, MOBILE, WORK, HOME, FAX, OTHER. |
methods[].value | string | The phone number or email address. |
methods[].isPrimary | boolean | The contact's main phone or email. |
methods[].smsConsent | enum | OK to text this phone (DENIED after the number replied STOP). One of UNKNOWN, GRANTED, DENIED. |
methods[].sortOrder | integer |
Add a contact
POST /organizations/{organizationId}/customers/{customerId}/contacts
Adds a contact to the customer with roles and phone/email methods. The first contact, or one marked primary, becomes the primary contact and its email/phone mirror onto the customer. Returns the contact.
| Permission | customers.write (Owner, Admin, Staff) |
| Risk | write, OAuth scope skuflow.write. Write: creates or changes records inside the business; nothing leaves it. |
| MCP tool | contacts_create (toolset customers, in the default set) |
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
customerId | path | string | Yes |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
firstName | string or null | No | |
lastName | string or null | No | |
jobTitle | string or null | No | |
roles | array of enum | No | Up to 7 items. |
isPrimary | boolean | No | |
notes | string or null | No | |
methods | array of object | No | Up to 50 items. |
methods[].kind | enum | Yes | One of PHONE, EMAIL. |
methods[].label | enum | No | One of MAIN, MOBILE, WORK, HOME, FAX, OTHER. |
methods[].value | string | Yes | Up to 500 characters. |
methods[].isPrimary | boolean | No | |
methods[].smsConsent | enum | No | One of UNKNOWN, GRANTED, DENIED. |
Example
curl -X POST "https://app.skuflow.ai/api/organizations/$ORGANIZATION_ID/customers/{customerId}/contacts" \
-H "Authorization: Bearer $SKUFLOW_API_KEY" \
-H "Content-Type: application/json" \
-d '{"firstName":"string","lastName":"string","jobTitle":"string"}'Response
201: the answer's fields
| Field | Type | Description |
|---|---|---|
id | string | Contact id. |
customerId | string | |
firstName | string or null | |
lastName | string or null | |
jobTitle | string or null | |
roles | array of enum | What the contact is for; the primary contact has PRIMARY. |
isPrimary | boolean | The primary contact; its main email and phone mirror onto the customer. |
notes | string or null | |
createdAt | string (date-time) | |
updatedAt | string (date-time) | |
methods | array of object | Phones and emails, primary first. |
methods[].id | string | |
methods[].contactId | string | |
methods[].kind | enum | One of PHONE, EMAIL. |
methods[].label | enum | One of MAIN, MOBILE, WORK, HOME, FAX, OTHER. |
methods[].value | string | The phone number or email address. |
methods[].isPrimary | boolean | The contact's main phone or email. |
methods[].smsConsent | enum | OK to text this phone (DENIED after the number replied STOP). One of UNKNOWN, GRANTED, DENIED. |
methods[].sortOrder | integer |
Update a contact
PATCH /organizations/{organizationId}/customers/{customerId}/contacts/{contactId}
Changes the fields sent. methods, when given, replaces the contact's phones and emails (send the full list). Returns the contact.
| Permission | customers.write (Owner, Admin, Staff) |
| Risk | write, OAuth scope skuflow.write. Write: creates or changes records inside the business; nothing leaves it. |
| MCP tool | contacts_update (toolset customers) |
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
customerId | path | string | Yes | |
contactId | path | string | Yes |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
firstName | string or null | No | |
lastName | string or null | No | |
jobTitle | string or null | No | |
roles | array of enum | No | Up to 7 items. |
isPrimary | boolean | No | |
notes | string or null | No | |
methods | array of object | No | Up to 50 items. |
methods[].kind | enum | Yes | One of PHONE, EMAIL. |
methods[].label | enum | No | One of MAIN, MOBILE, WORK, HOME, FAX, OTHER. |
methods[].value | string | Yes | Up to 500 characters. |
methods[].isPrimary | boolean | No | |
methods[].smsConsent | enum | No | One of UNKNOWN, GRANTED, DENIED. |
Example
curl -X PATCH "https://app.skuflow.ai/api/organizations/$ORGANIZATION_ID/customers/{customerId}/contacts/{contactId}" \
-H "Authorization: Bearer $SKUFLOW_API_KEY" \
-H "Content-Type: application/json" \
-d '{"firstName":"string","lastName":"string","jobTitle":"string"}'Response
200: the answer's fields
| Field | Type | Description |
|---|---|---|
id | string | Contact id. |
customerId | string | |
firstName | string or null | |
lastName | string or null | |
jobTitle | string or null | |
roles | array of enum | What the contact is for; the primary contact has PRIMARY. |
isPrimary | boolean | The primary contact; its main email and phone mirror onto the customer. |
notes | string or null | |
createdAt | string (date-time) | |
updatedAt | string (date-time) | |
methods | array of object | Phones and emails, primary first. |
methods[].id | string | |
methods[].contactId | string | |
methods[].kind | enum | One of PHONE, EMAIL. |
methods[].label | enum | One of MAIN, MOBILE, WORK, HOME, FAX, OTHER. |
methods[].value | string | The phone number or email address. |
methods[].isPrimary | boolean | The contact's main phone or email. |
methods[].smsConsent | enum | OK to text this phone (DENIED after the number replied STOP). One of UNKNOWN, GRANTED, DENIED. |
methods[].sortOrder | integer |
Delete a contact
DELETE /organizations/{organizationId}/customers/{customerId}/contacts/{contactId}
Destructive: deletes the contact and its phones and emails. Deleting the primary contact promotes the next one.
| Permission | customers.write (Owner, Admin, Staff) |
| Risk | sensitive, OAuth scope skuflow.sensitive. Sensitive: moves money, deletes, sends or shows something to a customer, or grants or revokes access. |
| MCP tool | contacts_delete (toolset customers) |
| Approval | Over MCP, this tool always waits for your approval in SKUFlow. See Approvals. |
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
customerId | path | string | Yes | |
contactId | path | string | Yes |
Example
curl -X DELETE "https://app.skuflow.ai/api/organizations/$ORGANIZATION_ID/customers/{customerId}/contacts/{contactId}" \
-H "Authorization: Bearer $SKUFLOW_API_KEY"Response
200: the answer's fields
| Field | Type | Description |
|---|---|---|
ok | true |
List notes on a customer or property
GET /organizations/{organizationId}/{ownerType}/{ownerId}/notes
Internal notes on a customer or property (ownerType is customers or properties, ownerId its id). Pinned first, then newest first. Read-only.
| Permission | customers.read (Owner, Admin, Staff, Member) |
| Risk | read, OAuth scope skuflow.read. Read: reads only. |
| MCP tool | recordNotes_list (toolset customers) |
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
ownerType | path | enum | Yes | One of customers, properties. |
ownerId | path | string | Yes |
Example
curl "https://app.skuflow.ai/api/organizations/$ORGANIZATION_ID/{ownerType}/{ownerId}/notes" \
-H "Authorization: Bearer $SKUFLOW_API_KEY"Response
200: the answer's fields
An array of objects:
| Field | Type | Description |
|---|---|---|
id | string | Note id. |
customerId | string or null | |
propertyId | string or null | |
content | string | Internal note text; never shown to the customer. |
pinned | boolean | |
authorUserId | string or null | |
createdAt | string (date-time) |
Add a note
POST /organizations/{organizationId}/{ownerType}/{ownerId}/notes
Adds an internal note (never shown to the customer) to a customer or property, authored by the caller. Returns the note.
| Permission | customers.write (Owner, Admin, Staff) |
| Risk | write, OAuth scope skuflow.write. Write: creates or changes records inside the business; nothing leaves it. |
| MCP tool | recordNotes_create (toolset customers, in the default set) |
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
ownerType | path | enum | Yes | One of customers, properties. |
ownerId | path | string | Yes |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
content | string | Yes | |
pinned | boolean | No |
Example
curl -X POST "https://app.skuflow.ai/api/organizations/$ORGANIZATION_ID/{ownerType}/{ownerId}/notes" \
-H "Authorization: Bearer $SKUFLOW_API_KEY" \
-H "Content-Type: application/json" \
-d '{"content":"string"}'Response
201: the answer's fields
| Field | Type | Description |
|---|---|---|
id | string | Note id. |
customerId | string or null | |
propertyId | string or null | |
content | string | Internal note text; never shown to the customer. |
pinned | boolean | |
authorUserId | string or null | |
createdAt | string (date-time) |
Edit or pin a note
PATCH /organizations/{organizationId}/{ownerType}/{ownerId}/notes/{noteId}
Changes a note's text and/or pins it to the top. Note ids come from the list call.
| Permission | customers.write (Owner, Admin, Staff) |
| Risk | write, OAuth scope skuflow.write. Write: creates or changes records inside the business; nothing leaves it. |
| MCP tool | recordNotes_update (toolset customers) |
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
ownerType | path | enum | Yes | One of customers, properties. |
ownerId | path | string | Yes | |
noteId | path | string | Yes |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
content | string | No | |
pinned | boolean | No |
Example
curl -X PATCH "https://app.skuflow.ai/api/organizations/$ORGANIZATION_ID/{ownerType}/{ownerId}/notes/{noteId}" \
-H "Authorization: Bearer $SKUFLOW_API_KEY" \
-H "Content-Type: application/json" \
-d '{"content":"string","pinned":true}'Response
200: the answer's fields
| Field | Type | Description |
|---|---|---|
id | string | Note id. |
customerId | string or null | |
propertyId | string or null | |
content | string | Internal note text; never shown to the customer. |
pinned | boolean | |
authorUserId | string or null | |
createdAt | string (date-time) |
Delete a note
DELETE /organizations/{organizationId}/{ownerType}/{ownerId}/notes/{noteId}
Destructive: deletes the note from the customer or property. Cannot be undone.
| Permission | customers.write (Owner, Admin, Staff) |
| Risk | sensitive, OAuth scope skuflow.sensitive. Sensitive: moves money, deletes, sends or shows something to a customer, or grants or revokes access. |
| MCP tool | recordNotes_delete (toolset customers) |
| Approval | Over MCP, this tool always waits for your approval in SKUFlow. See Approvals. |
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
ownerType | path | enum | Yes | One of customers, properties. |
ownerId | path | string | Yes | |
noteId | path | string | Yes |
Example
curl -X DELETE "https://app.skuflow.ai/api/organizations/$ORGANIZATION_ID/{ownerType}/{ownerId}/notes/{noteId}" \
-H "Authorization: Bearer $SKUFLOW_API_KEY"Response
200: the answer's fields
| Field | Type | Description |
|---|---|---|
ok | true |
List files on a customer or property
GET /organizations/{organizationId}/{ownerType}/{ownerId}/attachments
Files on a customer or property, each with a download URL valid for 10 minutes. Read-only.
| Permission | customers.read (Owner, Admin, Staff, Member) |
| Risk | read, OAuth scope skuflow.read. Read: reads only. |
| MCP tool | attachments_list (toolset customers) |
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
ownerType | path | enum | Yes | One of customers, properties. |
ownerId | path | string | Yes |
Example
curl "https://app.skuflow.ai/api/organizations/$ORGANIZATION_ID/{ownerType}/{ownerId}/attachments" \
-H "Authorization: Bearer $SKUFLOW_API_KEY"Response
200: the answer's fields
An array of objects:
| Field | Type | Description |
|---|---|---|
id | string | Attachment id. |
organizationId | string | |
customerId | string or null | |
propertyId | string or null | |
tag | enum or null | One of BEFORE, AFTER, GENERAL, ISSUE, SIGNATURE. |
fileName | string | |
contentType | string | |
sizeBytes | integer | |
uploadedByUserId | string or null | |
uploadedAt | string (date-time) or null | Set once the upload is confirmed; null before. |
createdAt | string (date-time) | |
downloadUrl | string | Download URL, valid for 10 minutes. |
Start a file upload
POST /organizations/{organizationId}/{ownerType}/{ownerId}/attachments
Step 1 of 2 of attaching a file: returns uploadUrl; PUT the file there (with the same Content-Type) within 5 minutes, then call POST …/attachments/{attachmentId}/confirm. Unconfirmed uploads are not listed.
| Permission | customers.write (Owner, Admin, Staff) |
| Risk | write, OAuth scope skuflow.write. Write: creates or changes records inside the business; nothing leaves it. |
| MCP tool | attachments_createUpload (toolset customers) |
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
ownerType | path | enum | Yes | One of customers, properties. |
ownerId | path | string | Yes |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
fileName | string | Yes | Up to 500 characters. |
contentType | string | Yes | Up to 200 characters. |
sizeBytes | integer | Yes |
Example
curl -X POST "https://app.skuflow.ai/api/organizations/$ORGANIZATION_ID/{ownerType}/{ownerId}/attachments" \
-H "Authorization: Bearer $SKUFLOW_API_KEY" \
-H "Content-Type: application/json" \
-d '{"fileName":"string","contentType":"string","sizeBytes":1}'Response
201: the answer's fields
| Field | Type | Description |
|---|---|---|
attachment | object | |
attachment.id | string | Attachment id. |
attachment.organizationId | string | |
attachment.customerId | string or null | |
attachment.propertyId | string or null | |
attachment.tag | enum or null | One of BEFORE, AFTER, GENERAL, ISSUE, SIGNATURE. |
attachment.fileName | string | |
attachment.contentType | string | |
attachment.sizeBytes | integer | |
attachment.uploadedByUserId | string or null | |
attachment.uploadedAt | string (date-time) or null | Set once the upload is confirmed; null before. |
attachment.createdAt | string (date-time) | |
uploadUrl | string | PUT the file here (same Content-Type) within 5 minutes, then confirm. |
Confirm a finished upload
POST /organizations/{organizationId}/{ownerType}/{ownerId}/attachments/{attachmentId}/confirm
Step 2 of 2: marks the uploaded file as present so it is listed. Call after the PUT to uploadUrl from POST …/attachments succeeded.
| Permission | customers.write (Owner, Admin, Staff) |
| Risk | write, OAuth scope skuflow.write. Write: creates or changes records inside the business; nothing leaves it. |
| MCP tool | attachments_confirm (toolset customers) |
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
ownerType | path | enum | Yes | One of customers, properties. |
ownerId | path | string | Yes | |
attachmentId | path | string | Yes |
Example
curl -X POST "https://app.skuflow.ai/api/organizations/$ORGANIZATION_ID/{ownerType}/{ownerId}/attachments/{attachmentId}/confirm" \
-H "Authorization: Bearer $SKUFLOW_API_KEY"Response
200: the answer's fields
| Field | Type | Description |
|---|---|---|
id | string | Attachment id. |
organizationId | string | |
customerId | string or null | |
propertyId | string or null | |
tag | enum or null | One of BEFORE, AFTER, GENERAL, ISSUE, SIGNATURE. |
fileName | string | |
contentType | string | |
sizeBytes | integer | |
uploadedByUserId | string or null | |
uploadedAt | string (date-time) or null | Set once the upload is confirmed; null before. |
createdAt | string (date-time) |
Delete a file
DELETE /organizations/{organizationId}/{ownerType}/{ownerId}/attachments/{attachmentId}
Destructive: deletes the file and its stored object.
| Permission | customers.write (Owner, Admin, Staff) |
| Risk | sensitive, OAuth scope skuflow.sensitive. Sensitive: moves money, deletes, sends or shows something to a customer, or grants or revokes access. |
| MCP tool | attachments_delete (toolset customers) |
| Approval | Over MCP, this tool always waits for your approval in SKUFlow. See Approvals. |
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
ownerType | path | enum | Yes | One of customers, properties. |
ownerId | path | string | Yes | |
attachmentId | path | string | Yes |
Example
curl -X DELETE "https://app.skuflow.ai/api/organizations/$ORGANIZATION_ID/{ownerType}/{ownerId}/attachments/{attachmentId}" \
-H "Authorization: Bearer $SKUFLOW_API_KEY"Response
200: the answer's fields
| Field | Type | Description |
|---|---|---|
ok | true |
Attach a file (content in the request)
POST /organizations/{organizationId}/{ownerType}/{ownerId}/attachments/content
Attaches a file to a customer or property in one call (ownerType customers or properties, ownerId its id): send contentBase64 or a public sourceUrl (up to 10 MB; any type). The file is stored privately and listed with the record's files (GET …/attachments, with a 10-minute download URL). The browser uploads with POST …/attachments and confirm instead. Returns the file.
| Permission | customers.write (Owner, Admin, Staff) |
| Risk | write, OAuth scope skuflow.write. Write: creates or changes records inside the business; nothing leaves it. |
| MCP tool | attachments_upload (toolset customers, in the default set) |
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
ownerType | path | enum | Yes | One of customers, properties. |
ownerId | path | string | Yes |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
fileName | string | No | The file's name, e.g. receipt.jpg (default: from the URL, else 'file'). Up to 500 characters. |
contentType | string | No | MIME type, e.g. image/jpeg or application/pdf. Optional: taken from a data: URL, the URL's answer or the file's first bytes. Up to 200 characters. |
contentBase64 | string | No | The file's bytes as base64 (a data: URL is accepted too). Up to 10 MB. Give this or sourceUrl. |
sourceUrl | string (uri) | No | A public http(s) URL SKUFlow downloads the file from (up to 10 MB, 20 s, 3 redirects; private and internal addresses are refused). Give this or contentBase64. Up to 2000 characters. |
Example
curl -X POST "https://app.skuflow.ai/api/organizations/$ORGANIZATION_ID/{ownerType}/{ownerId}/attachments/content" \
-H "Authorization: Bearer $SKUFLOW_API_KEY" \
-H "Content-Type: application/json" \
-d '{"fileName":"string","contentType":"string","contentBase64":"string"}'Response
201: the answer's fields
| Field | Type | Description |
|---|---|---|
id | string | The attachment id. |
fileName | string | |
contentType | string | |
sizeBytes | integer | |
uploadedAt | string (date-time) or null | |
createdAt | string (date-time) |
Customer portal API
REST endpoints for customer portal in SKUFlow: 6 operations with parameters, request and response fields, permissions and curl examples.
Properties API
REST endpoints for properties in SKUFlow: 8 operations with parameters, request and response fields, permissions and curl examples.