developer_tools.public_docs_home developer_tools.public_api_reference
developer_tools.subtitle
developer_tools.nav_module_contacts
Returns the active contact directory for the authenticated business, using the same contact visibility rules as the web customer and supplier selectors.
| Property | Value |
|---|---|
| Method | GET |
| Path | /api/v1/integration/contacts |
| Authentication | Bearer token or API key + secret required. |
| Permission | customer.view or customer.view_own when kind=customer; supplier.view or supplier.view_own when kind=supplier. |
| Visibility rules | Users with only *_view_own access only see contacts they created or contacts shared through user_contact_access. |
| CSV behavior | format=csv streams all matching rows with a UTF-8 BOM and ignores page and per_page. |
| Success response | 200 with paginated ContactListItem rows. |
| Parameter | Type | Required | Description |
|---|---|---|---|
kind | string | Yes | customer or supplier. Customer mode includes contacts with type customer and both; supplier mode includes supplier and both. |
per_page | integer | No | Results per page from 1 to 100. Defaults to 20. |
page | integer | No | Pagination page number. Defaults to 1. |
q | string | No | Minimum 2 characters when sent. Matches name, supplier business name, mobile, landline, alternate number, email, and contact code. |
format | string | No | json (default) or csv. |
ContactListItem object| Field | Type | Description |
|---|---|---|
id | integer | Contact primary key. |
type | string | customer, supplier, or both. |
name | string | Display name used throughout the UI. |
supplier_business_name | string | null | Supplier business label when the contact represents a company. |
contact_id | string | null | Business-facing contact code. |
email | string | null | Primary email address. |
mobile | string | null | Primary mobile number. |
landline | string | null | Landline number. |
city, state, country | string | null | Saved address summary fields. |
tax_number | string | null | Stored tax number. |
contact_status | string | null | Usually active or inactive. |
credit_limit | number | null | Credit limit for the contact. |
| Field | Type | Description |
|---|---|---|
data | array<ContactListItem> | Paginated contact rows. |
meta.current_page | integer | Current paginator page. |
meta.last_page | integer | Last available page. |
meta.per_page | integer | Applied page size. |
meta.total | integer | Total matching contacts. |
| Status | When it happens | Response shape |
|---|---|---|
200 | Contacts were returned successfully. | JSON { "data": [...], "meta": { ... } } or CSV download. |
403 | The token user lacks the required customer or supplier permission. | { "message": "Unauthorized" } |
422 | The query string failed validation, such as a 1-character q or missing kind. | Laravel validation JSON or an explicit message. |
Returns one contact record for the current business. This endpoint extends ContactListItem with address, balance, custom fields, and export-related details.
| Property | Value |
|---|---|
| Method | GET |
| Path | /api/v1/integration/contacts/{id} |
| Authentication | Bearer token or API key + secret required. |
| Permission | Any of supplier.view, supplier.view_own, customer.view, or customer.view_own. |
| Visibility rules | If the user only has *_view_own access, the contact must be created by that user or shared through user_contact_access. |
| CSV behavior | format=csv streams a single row with a data_json column whose value matches the JSON data object. |
| Success response | 200 with a ContactDetail object. |
| Parameter | Type | Required | Description |
|---|---|---|---|
format | string | No | json (default) or csv. |
ContactDetail additional fields| Field | Type | Description |
|---|---|---|
prefix, first_name, middle_name, last_name | string | null | Name parts stored on the contact form. |
address_line_1, address_line_2, zip_code, shipping_address | string | null | Address fields and shipping address. |
alternate_number | string | null | Secondary phone number. |
balance | number | null | Current contact balance snapshot. |
pay_term_number | integer | null | Pay term value. |
pay_term_type | string | null | Pay term unit such as days or months. |
dob | string | null | Date of birth in Y-m-d format. |
is_default | boolean | Whether the contact is the system default contact. |
created_by | integer | null | User id that created the contact. |
customer_group_id | integer | null | Linked customer group id. |
created_at, updated_at | string | null | ISO-8601 timestamps. |
custom_field1 to custom_field10 | string | null | Business-defined custom contact fields. |
custom_field_labels | object | Business label map keyed like custom_field_1. |
shipping_custom_field_details | array | object | null | Stored shipping custom field payload. |
is_export | boolean | Whether export-specific fields are enabled for the contact. |
export_custom_field_1 to export_custom_field_6 | string | null | Export custom fields saved when is_export is enabled. |
| Field | Type | Description |
|---|---|---|
data | ContactDetail | Full contact payload for the requested row. |
| Status | When it happens | Response shape |
|---|---|---|
200 | The contact was returned successfully. | JSON { "data": ContactDetail } or CSV download. |
403 | The token user lacks view access to this contact. | { "message": "Unauthorized" } |
404 | The contact id does not exist in the current business. | { "message": "Not found" } |
Lists the audit/activity log rows for a single contact, using the same underlying Spatie activity stream shown in the web contact Activities tab.
| Property | Value |
|---|---|
| Method | GET |
| Path | /api/v1/integration/contacts/{id}/activities |
| Permission | Same access and visibility rules as Get contact. |
| Sort order | Newest activity first. |
| CSV behavior | format=csv streams all matching activities and ignores pagination. Nested causer and properties values are JSON-encoded in cells. |
| Success response | 200 with paginated activity rows. |
| Parameter | Type | Required | Description |
|---|---|---|---|
per_page | integer | No | Results per page from 1 to 100. Defaults to 20. |
page | integer | No | Pagination page number. Defaults to 1. |
q | string | No | Minimum 2 characters when sent. Matches description, numeric activity id, or the causer name, username, or email. |
format | string | No | json (default) or csv. |
ContactActivity object| Field | Type | Description |
|---|---|---|
id | integer | Activity primary key. |
description | string | Human-readable activity message. |
event | string | null | Event name stored by the activity log package. |
log_name | string | null | Activity log channel. |
created_at | string | null | ISO-8601 timestamp. |
causer | object | null | When present: { "id": integer, "name": string }. |
properties | object | array | null | Decoded activity metadata payload. |
| Field | Type | Description |
|---|---|---|
data.contact_id | integer | Contact id from the path. |
data.activities | array<ContactActivity> | Paginated activity rows. |
meta.current_page, meta.last_page, meta.per_page, meta.total | integer | Pagination metadata. |
meta.q | string | null | Echoed search term when present. |
| Status | When it happens | Response shape |
|---|---|---|
200 | Activity rows were returned successfully. | JSON { "data": { ... }, "meta": { ... } } or CSV download. |
403 | The token user cannot access this contact. | { "message": "Unauthorized" } |
404 | The contact id does not exist in the current business. | { "message": "Not found" } |
422 | The query string failed validation, such as a 1-character q. | Laravel validation JSON or an explicit message. |
Checks whether a mobile number already exists in the current business, using the same duplicate-check rule as the web contact form.
| Property | Value |
|---|---|
| Method | GET |
| Path | /api/v1/integration/contacts/validate/mobile |
| Permission | Any permission that allows listing or viewing contacts. |
| Match rule | Checks contacts.mobile with the same suffix-match logic used by the web duplicate checker. |
| CSV behavior | format=csv returns one row with a data_json column. |
| Success response | 200 with duplicate-check details. |
| Parameter | Type | Required | Description |
|---|---|---|---|
mobile_number | string | Yes | Mobile number to check. |
contact_id | integer | No | Existing contact id to exclude during edit flows. It must exist in the same business and be visible to the token user. |
format | string | No | json (default) or csv. |
| Field | Type | Description |
|---|---|---|
data.is_mobile_exists | boolean | true when one or more matching contacts were found. |
data.msg | string | Translated duplicate-check message returned by the controller. |
data.matching_contact_names | array<string> | Matched contact display names. |
| Status | When it happens | Response shape |
|---|---|---|
200 | The duplicate check completed successfully. | { "data": { "is_mobile_exists": bool, "msg": string, "matching_contact_names": [...] } } |
403 | The token user cannot use contact validation endpoints or cannot access the excluded contact_id. | { "message": "Unauthorized" } |
422 | The query string failed validation or the excluded contact_id is invalid. | Laravel validation JSON or an explicit message. |
Checks whether a tax number already exists in the current business, matching the same duplicate rule used by the web contact form.
| Property | Value |
|---|---|
| Method | GET |
| Path | /api/v1/integration/contacts/validate/tax-number |
| Permission | Same access gate as Validate contact mobile. |
| Match rule | Exact tax-number match within the current business. |
| CSV behavior | format=csv returns one row with a data_json column. |
| Success response | 200 with duplicate-check details. |
| Parameter | Type | Required | Description |
|---|---|---|---|
tax_number | string | Yes | Tax number to check. |
contact_id | integer | No | Existing contact id to exclude during edit flows. It must exist in the same business and be visible to the token user. |
format | string | No | json (default) or csv. |
| Field | Type | Description |
|---|---|---|
data.is_tax_number_exists | boolean | true when one or more matching contacts were found. |
data.msg | string | Translated duplicate-check message. Empty when there is no duplicate. |
data.matching_contact_names | array<string> | Matched contact display names. |
| Status | When it happens | Response shape |
|---|---|---|
200 | The duplicate check completed successfully. | { "data": { "is_tax_number_exists": bool, "msg": string, "matching_contact_names": [...] } } |
403 | The token user cannot use contact validation endpoints or cannot access the excluded contact_id. | { "message": "Unauthorized" } |
422 | The query string failed validation or the excluded contact_id is invalid. | Laravel validation JSON or an explicit message. |
Returns geocoded contacts for the current business, using the same active-contact and visibility rules as the web contact map.
| Property | Value |
|---|---|
| Method | GET |
| Path | /api/v1/integration/contacts/map |
| Permission | At least one of supplier.view, supplier.view_own, customer.view, or customer.view_own. |
| Source rows | Only active contacts whose position field can be parsed into latitude and longitude values. |
| CSV behavior | format=csv streams all matching rows and is not paginated. |
| Success response | 200 with contact map rows plus a small meta block. |
| Parameter | Type | Required | Description |
|---|---|---|---|
kind | string | No | all (default), customer, or supplier. |
contact_ids[] | array<integer> | No | Optional contact ids to narrow the result set. Contacts still have to be visible to the token user. |
format | string | No | json (default) or csv. |
ContactMapRow object| Field | Type | Description |
|---|---|---|
id | integer | Contact primary key. |
name | string | Display name. |
contact_id | string | null | Business-facing contact code. |
type | string | customer, supplier, or both. |
supplier_business_name | string | null | Supplier business label when present. |
shipping_address | string | null | Saved shipping address. |
latitude, longitude | number | Parsed coordinates from the contact position field. |
| Field | Type | Description |
|---|---|---|
data | array<ContactMapRow> | Matching map markers. |
meta.kind | string | Applied kind filter. |
meta.total | integer | Total returned markers. |
| Status | When it happens | Response shape |
|---|---|---|
200 | Map rows were returned successfully. | JSON { "data": [...], "meta": { "kind": string, "total": integer } } or CSV download. |
403 | The token user lacks permission to read contacts. | { "message": "Unauthorized" } |
Imports contacts from the same spreadsheet template used by the web Import contacts flow, including creation of opening balance transactions when present in the sheet.
| Property | Value |
|---|---|
| Method | POST |
| Path | /api/v1/integration/contacts/import |
| Permission | supplier.create or customer.create. |
| Request encoding | multipart/form-data |
| Subscription rule | Returns 402 when package enforcement is enabled and the business subscription is not active. |
| Demo mode | Returns 403 in demo environments. |
| Success response | 201 with imported row count. |
| Header | Required | Description |
|---|---|---|
Authorization | Yes | Bearer YOUR_ACCESS_TOKEN |
Content-Type | Yes | multipart/form-data |
Accept | No | Recommended: application/json. |
| Field | Type | Required | Description |
|---|---|---|---|
contacts_csv | file | Yes | Spreadsheet file in csv, xls, xlsx, or txt format, up to 15360 KB. |
| Rule | Description |
|---|---|
| Template layout | The first row is a header row and is skipped. Each data row must follow the same column order as the downloadable contact import template. |
| Contact type column | The importer expects the same numeric contact-type markers as the web template (1, 2, 3). |
| Validation behavior | The importer enforces the same rules as the web flow, including duplicate contact_id, invalid email format, bad column counts, and invalid type mappings. |
| Side effects | Successful imports create contacts, optionally create opening balance transactions, and record the same imported activity trail as the web import flow. |
| Field | Type | Description |
|---|---|---|
message | string | Localized import success message. |
data.imported | integer | Number of contacts created from the uploaded sheet. |
| Status | When it happens | Response shape |
|---|---|---|
201 | The spreadsheet was imported successfully. | { "message": string, "data": { "imported": integer } } |
402 | The business is not subscribed when package enforcement is active. | { "message": string } |
403 | Demo mode or missing create permission. | { "message": string } |
422 | The uploaded file is missing, invalid, empty, or the importer rejected one or more rows. | Laravel validation JSON or { "message": string }. |
Returns the current due amount for one contact using the same balance calculation shown in the web contact view.
| Property | Value |
|---|---|
| Method | GET |
| Path | /api/v1/integration/contacts/{id}/balance |
| Permission | Same access and visibility rules as Get contact. |
| Calculation | Uses the same due calculation as the web contact screen, including sells, purchases, returns, opening balance, and related payments. |
| CSV behavior | format=csv streams one row with a data_json column. |
| Success response | 200 with balance data. |
| Parameter | Type | Required | Description |
|---|---|---|---|
format | string | No | json (default) or csv. |
| Field | Type | Description |
|---|---|---|
data.contact_id | integer | Contact id from the path. |
data.due | number | Raw outstanding amount. |
data.due_formatted | string | Currency-formatted due amount. |
| Status | When it happens | Response shape |
|---|---|---|
200 | The balance was returned successfully. | { "data": { "contact_id": integer, "due": number, "due_formatted": string } } or CSV download. |
403 | The token user cannot access this contact. | { "message": "Unauthorized" } |
404 | The contact id does not exist in the current business. | { "message": "Not found" } |
Returns the contact's opening-balance transaction when one exists. This is a read-only lookup; the registered integration route is GET only.
| Property | Value |
|---|---|
| Method | GET |
| Path | /api/v1/integration/contacts/{id}/opening-balance |
| Permission | Same access and visibility rules as Get contact. |
| Behavior | Returns the single system opening-balance transaction for the contact when present; otherwise transaction is null. |
| CSV behavior | format=csv streams one row with a data_json column. |
| Success response | 200 with opening balance state. |
| Parameter | Type | Required | Description |
|---|---|---|---|
format | string | No | json (default) or csv. |
OpeningBalanceTransaction object| Field | Type | Description |
|---|---|---|
id | integer | Transaction primary key. |
ref_no | string | null | Opening-balance reference number. |
final_total | number | Opening-balance amount. |
total_paid | number | Total payments applied to the opening balance. |
due | number | Outstanding amount after payments. |
due_formatted | string | Currency-formatted due amount. |
payment_status | string | null | Payment status computed for the transaction. |
transaction_date | string | null | ISO-8601 transaction timestamp. |
location_id | integer | null | Business location id on the transaction. |
location_name | string | null | Business location name. |
| Field | Type | Description |
|---|---|---|
data.contact_id | integer | Contact id from the path. |
data.has_opening_balance | boolean | Whether an opening-balance transaction exists. |
data.transaction | OpeningBalanceTransaction | null | Transaction payload when one exists. |
| Status | When it happens | Response shape |
|---|---|---|
200 | The opening-balance state was returned successfully. | { "data": { "contact_id": integer, "has_opening_balance": bool, "transaction": object|null } } or CSV download. |
403 | The token user cannot access this contact. | { "message": "Unauthorized" } |
404 | The contact id does not exist in the current business. | { "message": "Not found" } |
Returns the contact ledger for a date range using the same ledger engine as the web contact ledger screen.
| Property | Value |
|---|---|
| Method | GET |
| Path | /api/v1/integration/contacts/{id}/ledger |
| Permission | Same access and visibility rules as Get contact. |
| Ledger formats | format_1 (default), format_2, or csv. |
| CSV behavior | format=csv streams all filtered ledger lines using the format-1 layout. The summary block stays JSON-only. |
| Success response | 200 with ledger lines, summary totals, and echoed filter metadata. |
| Parameter | Type | Required | Description |
|---|---|---|---|
start_date | string | Yes | Inclusive start date in Y-m-d format. |
end_date | string | Yes | Inclusive end date in Y-m-d format and must be on or after start_date. |
location_id | integer | No | Optional business location filter. |
format | string | No | format_1, format_2, or csv. Defaults to format_1. |
q | string | No | Minimum 2 characters when sent. Filters ledger lines only by reference, type, location, payment status, note, or numeric transaction id. |
ContactLedgerLine object| Field | Type | Description |
|---|---|---|
date | string | null | ISO-8601 ledger row date. |
ref_no | string | Transaction or payment reference number. |
type | string | Ledger row type label from the web ledger. |
location | string | Business location label. |
payment_status | string | Payment status text for transaction rows. |
debit, credit | number | null | Ledger debit and credit amounts. |
balance | string | Running balance display string. |
note | string | Plain-text notes/other row details. |
transaction_id, transaction_type | integer | string | Present when the row maps to a transaction record. |
payment_method_key | string | null | Present for payment rows when the ledger engine exposes the payment method. |
final_total, total_due, total_paid | number | null | Invoice-style totals used by the alternate ledger layout. |
due_date | string | null | Due date when the ledger row represents an invoice-like transaction. |
ContactLedgerSummary object| Field | Type | Description |
|---|---|---|
start_date, end_date | string | Applied reporting window. |
beginning_balance | number | Balance before the requested period. |
balance_due | number | Balance due for the requested period. |
total_invoice, total_purchase, total_paid, total_reverse_payment, ledger_discount | number | Totals for the requested period. |
all_total_invoice, all_invoice_paid, all_total_purchase, all_purchase_paid, all_balance_due, all_ledger_discount | number | Overall ledger totals for the contact. |
| Field | Type | Description |
|---|---|---|
data.contact_id | integer | Contact id from the path. |
data.lines | array<ContactLedgerLine> | Filtered ledger lines. |
data.summary | ContactLedgerSummary | Totals block for the selected range. |
meta.start_date, meta.end_date | string | Echoed date filters. |
meta.location_id | integer | null | Echoed location filter. |
meta.format | string | Applied ledger layout. |
meta.q | string | null | Echoed search term when present. |
| Status | When it happens | Response shape |
|---|---|---|
200 | The ledger was returned successfully. | JSON { "data": { ... }, "meta": { ... } } or CSV download. |
403 | The token user cannot access this contact. | { "message": "Unauthorized" } |
404 | The contact id does not exist in the current business. | { "message": "Not found" } |
422 | The date range or search query failed validation. | Laravel validation JSON or an explicit message. |
Builds the same ledger PDF used by the web Send ledger action and emails it using the business notification settings.
| Property | Value |
|---|---|
| Method | POST |
| Path | /api/v1/integration/contacts/{id}/ledger/send |
| Permission | Same access and visibility rules as Get contact and Contact ledger. |
| Request encoding | application/json |
| Demo mode | Returns 403 in demo environments. |
| Success response | 200 with a success message after the email is queued/sent. |
| Header | Required | Description |
|---|---|---|
Authorization | Yes | Bearer YOUR_ACCESS_TOKEN |
Content-Type | Yes | application/json |
Accept | No | Recommended: application/json. |
| Field | Type | Required | Description |
|---|---|---|---|
to_email | string | Yes | Recipient email list, matching the web ledger email flow. |
subject | string | Yes | Email subject line. |
email_body | string | Yes | Email body text. Template tags and {balance_due} work the same way as in the web notification template. |
ledger_format | string | Yes | format_1, format_2, format_3, or format_4. |
start_date, end_date | string | Yes | Date range in Y-m-d format for the attached ledger. |
cc, bcc | string | null | No | Optional CC and BCC address lists. |
location_id | integer | null | No | Optional location filter for the generated ledger. |
| Status | When it happens | Response shape |
|---|---|---|
200 | The ledger email was sent successfully. | { "message": string } |
403 | Demo mode or the token user cannot access this contact. | { "message": string } |
404 | The contact or related business record was not found. | { "message": "Not found" } or equivalent translated message. |
422 | The payload failed validation or email/PDF generation failed. | Laravel validation JSON or { "message": string }. |
Lists ledger discount rows for a single contact. It is the same data contract as the global ledger-discount list, but with the contact fixed by the path.
| Property | Value |
|---|---|
| Method | GET |
| Path | /api/v1/integration/contacts/{id}/ledger-discounts |
| Permission | Same ledger-style read access as Contact ledger, plus visibility to the contact from the path. |
| Contact binding | The controller forces contact_id={id}; any query-string contact_id is ignored. |
| CSV behavior | format=csv streams all matching rows and ignores pagination. |
| Success response | 200 with paginated LedgerDiscountRow rows. |
| Parameter | Type | Required | Description |
|---|---|---|---|
per_page | integer | No | Results per page from 1 to 100. Defaults to 20. |
page | integer | No | Pagination page number. |
start_date, end_date | string | No | Optional business-date range on transaction_date. Both must be provided together. |
q | string | No | Minimum 2 characters when sent. Matches note text, numeric id, or contact name. |
format | string | No | json (default) or csv. |
LedgerDiscountRow object| Field | Type | Description |
|---|---|---|
id | integer | Ledger-discount transaction id. |
contact_id | integer | Linked contact id. |
contact_name | string | Linked contact display name. |
contact_type | string | customer, supplier, or both. |
sub_type | string | sell_discount or purchase_discount. |
amount | number | Discount amount. |
note | string | null | Saved note on the discount transaction. |
transaction_date | string | null | ISO-8601 transaction timestamp. |
created_by | integer | null | User id that created the ledger discount. |
| Field | Type | Description |
|---|---|---|
data | array<LedgerDiscountRow> | Paginated discount rows for the bound contact. |
meta.current_page, meta.last_page, meta.per_page, meta.total | integer | Pagination metadata. |
| Status | When it happens | Response shape |
|---|---|---|
200 | Ledger discount rows were returned successfully. | JSON { "data": [...], "meta": { ... } } or CSV download. |
403 | The token user cannot read this contact's ledger data. | { "message": "Unauthorized" } |
404 | The contact id does not exist in the current business. | { "message": "Not found" } |
422 | The date filters or search query failed validation. | Laravel validation JSON or an explicit message. |
Lists all ledger discount rows visible to the authenticated user across accessible contacts.
| Property | Value |
|---|---|
| Method | GET |
| Path | /api/v1/integration/ledger-discounts |
| Permission | Same ledger-style read access as Contact ledger. |
| Visibility rules | Rows are limited to contacts the token user can access. |
| CSV behavior | format=csv streams all matching rows and ignores pagination. |
| Success response | 200 with paginated LedgerDiscountRow rows. |
| Parameter | Type | Required | Description |
|---|---|---|---|
per_page | integer | No | Results per page from 1 to 100. Defaults to 20. |
page | integer | No | Pagination page number. |
contact_id | integer | No | Optional contact filter. The contact must exist in the same business and be visible to the token user. |
start_date, end_date | string | No | Optional business-date range on transaction_date. Both must be provided together. |
q | string | No | Minimum 2 characters when sent. Matches note text, numeric id, or contact name. |
format | string | No | json (default) or csv. |
| Field | Type | Description |
|---|---|---|
data | array<LedgerDiscountRow> | Paginated ledger discount rows. |
meta.current_page, meta.last_page, meta.per_page, meta.total | integer | Pagination metadata. |
| Status | When it happens | Response shape |
|---|---|---|
200 | Ledger discount rows were returned successfully. | JSON { "data": [...], "meta": { ... } } or CSV download. |
403 | The token user lacks ledger-style read access or cannot access the filtered contact. | { "message": "Unauthorized" } |
404 | The supplied contact_id does not exist in the current business. | { "message": "Not found" } |
422 | The date filters or search query failed validation. | Laravel validation JSON or an explicit message. |
Returns one ledger discount row when the transaction belongs to the current business and its contact is visible to the token user.
| Property | Value |
|---|---|
| Method | GET |
| Path | /api/v1/integration/ledger-discounts/{id} |
| Permission | Same ledger-style read access as List ledger discounts. |
| CSV behavior | format=csv streams one row with a data_json column. |
| Success response | 200 with a LedgerDiscountRow object. |
| Parameter | Type | Required | Description |
|---|---|---|---|
format | string | No | json (default) or csv. |
| Field | Type | Description |
|---|---|---|
data | LedgerDiscountRow | Ledger discount payload for the requested transaction. |
| Status | When it happens | Response shape |
|---|---|---|
200 | The ledger discount was returned successfully. | { "data": LedgerDiscountRow } or CSV download. |
403 | The token user lacks access to the linked contact. | { "message": "Unauthorized" } |
404 | The transaction is not a ledger discount in the current business. | { "message": "Not found" } |
Creates a ledger-discount transaction using the same business rules as the web Add discount flow in the contact ledger.
| Property | Value |
|---|---|
| Method | POST |
| Path | /api/v1/integration/ledger-discounts |
| Permission | Same ledger-style read access as List ledger discounts, plus access to the supplied contact. |
| Request encoding | application/json |
| Demo mode | Returns 403 in demo environments. |
| Success response | 201 with the created LedgerDiscountRow. |
| Header | Required | Description |
|---|---|---|
Authorization | Yes | Bearer YOUR_ACCESS_TOKEN |
Content-Type | Yes | application/json |
Accept | No | Recommended: application/json. |
| Field | Type | Required | Description |
|---|---|---|---|
contact_id | integer | Yes | Contact to apply the discount to. The contact must belong to the current business and be visible to the token user. |
date | string | Yes | Transaction date in the business date format accepted by the app. |
amount | number | Yes | Discount amount. |
note | string | null | No | Optional free-text note. |
sub_type | string | null | Conditional | Required only when the contact type is both; allowed values are sell_discount or purchase_discount. |
| Status | When it happens | Response shape |
|---|---|---|
201 | The ledger discount was created successfully. | { "message": string, "data": LedgerDiscountRow } |
403 | Demo mode or the token user cannot access the supplied contact. | { "message": string } |
404 | The supplied contact_id does not exist in the current business. | { "message": string } |
422 | The payload failed validation or a both-type contact was sent without a valid sub_type. | Laravel validation JSON or { "message": string, "errors": { ... } }. |
500 | The create transaction failed unexpectedly. | { "message": "something_went_wrong" } |
Updates an existing ledger-discount transaction. This matches the web edit flow and is restricted to business admins.
| Property | Value |
|---|---|
| Methods | PATCH or PUT |
| Path | /api/v1/integration/ledger-discounts/{id} |
| Permission | Business admin access plus the same contact visibility rules as List ledger discounts. |
| Request encoding | application/json |
| Demo mode | Returns 403 in demo environments. |
| Success response | 200 with the updated LedgerDiscountRow. |
| Field | Type | Required | Description |
|---|---|---|---|
date | string | Yes | Transaction date in the business date format accepted by the app. |
amount | number | Yes | Updated discount amount. |
note | string | null | No | Optional replacement note. |
sub_type | string | null | No | Optional replacement sub-type: sell_discount or purchase_discount. |
| Status | When it happens | Response shape |
|---|---|---|
200 | The ledger discount was updated successfully. | { "message": string, "data": LedgerDiscountRow } |
403 | Demo mode, the token user is not an admin, or the linked contact is not visible. | { "message": string } |
404 | The ledger discount id does not exist in the current business. | { "message": "Not found" } |
422 | The payload failed validation or a business rule blocked the update. | Laravel validation JSON or { "message": string }. |
500 | The update transaction failed unexpectedly. | { "message": "something_went_wrong" } |
Deletes a ledger-discount transaction. This matches the web delete action and is restricted to business admins.
| Property | Value |
|---|---|
| Method | DELETE |
| Path | /api/v1/integration/ledger-discounts/{id} |
| Permission | Business admin access plus the same contact visibility rules as List ledger discounts. |
| Demo mode | Returns 403 in demo environments. |
| Success response | 200 with a success message. |
| Status | When it happens | Response shape |
|---|---|---|
200 | The ledger discount was deleted successfully. | { "message": string } |
403 | Demo mode, the token user is not an admin, or the linked contact is not visible. | { "message": string } |
404 | The ledger discount id does not exist in the current business. | { "message": "Not found" } |
500 | The delete transaction failed unexpectedly. | { "message": "something_went_wrong" } |
Lists purchase rows for one supplier contact. This endpoint delegates to the same purchase index used by the dedicated purchases module.
| Property | Value |
|---|---|
| Method | GET |
| Path | /api/v1/integration/contacts/{id}/purchases |
| Permission | Same purchase-list permission and location rules as the dedicated List purchases endpoint. |
| Contact rule | The contact must exist, be visible to the token user, and have type supplier or both. |
| CSV behavior | format=csv streams the same purchase export used by the dedicated purchases list and ignores pagination. |
| Success response | 200 with the same list-row contract as List purchases, with contact_id fixed by the path. |
| Parameter | Type | Required | Description |
|---|---|---|---|
per_page, page | integer | No | Standard purchase-list pagination controls. |
location_id | integer | No | Optional location filter. |
start_date, end_date | string | No | Optional purchase date range. |
payment_status, status | string | No | Standard purchase filters inherited from the main purchases list. |
q | string | No | Optional purchase search term when supported by the delegated index. |
format | string | No | json (default) or csv. |
| Field | Type | Description |
|---|---|---|
data | array | Same purchase rows documented in the dedicated purchases module. Each row includes the purchase id, reference fields, dates, totals, and related contact/location summaries. |
meta | object | Standard paginator metadata from the delegated purchases index. |
| Status | When it happens | Response shape |
|---|---|---|
200 | Purchase rows were returned successfully. | Same JSON or CSV shape as List purchases. |
403 | The token user cannot access the contact or cannot use the purchase list endpoint. | { "message": "Unauthorized" } |
404 | The contact id does not exist in the current business. | { "message": "Not found" } |
422 | The contact exists but is not a supplier-type contact, or delegated purchase filters failed validation. | Laravel validation JSON or { "message": string }. |
Lists sell rows for one customer contact. This endpoint delegates to the same sell index used by the dedicated sells module.
| Property | Value |
|---|---|
| Method | GET |
| Path | /api/v1/integration/contacts/{id}/sells |
| Permission | Same sell-list permission and location rules as the dedicated List sells endpoint. |
| Contact rule | The contact must exist, be visible to the token user, and have type customer or both. |
| CSV behavior | format=csv streams the same sell export used by the dedicated sells list and ignores pagination. |
| Success response | 200 with the same list-row contract as List sells, with contact_id fixed by the path. |
| Parameter | Type | Required | Description |
|---|---|---|---|
per_page, page | integer | No | Standard sell-list pagination controls. |
location_id, created_by | integer | No | Optional sell filters inherited from the main sells list. |
start_date, end_date | string | No | Optional sale date range. |
payment_status, only_shipments, is_direct_sale | string | boolean | No | Standard sell filters supported by the delegated index. |
q | string | No | Optional sell search term when supported by the delegated index. |
format | string | No | json (default) or csv. |
| Field | Type | Description |
|---|---|---|
data | array | Same sell rows documented in the dedicated sells module. Each row includes invoice identifiers, dates, totals, payment status, and related contact/location summaries. |
meta | object | Standard paginator metadata from the delegated sells index. |
| Status | When it happens | Response shape |
|---|---|---|
200 | Sell rows were returned successfully. | Same JSON or CSV shape as List sells. |
403 | The token user cannot access the contact or cannot use the sell list endpoint. | { "message": "Unauthorized" } |
404 | The contact id does not exist in the current business. | { "message": "Not found" } |
422 | The contact exists but is not a customer-type contact, or delegated sell filters failed validation. | Laravel validation JSON or { "message": string }. |
Returns the aggregated stock report for a supplier contact, matching the supplier Stock report tab in the web contact screen.
| Property | Value |
|---|---|
| Method | GET |
| Path | /api/v1/integration/contacts/{id}/supplier-stock |
| Permission | supplier.view or supplier.view_own, plus visibility to the contact from the path. |
| Contact rule | The contact must be type supplier or both. |
| CSV behavior | format=csv streams all matching rows and ignores pagination. |
| Success response | 200 with paginated SupplierStockRow rows. |
| Parameter | Type | Required | Description |
|---|---|---|---|
per_page | integer | No | Results per page from 1 to 100. Defaults to 50. |
page | integer | No | Pagination page number. |
location_id | integer | No | Optional business location filter. |
q | string | No | Minimum 2 characters when sent. Matches product name, variation name, variation template name, sub_sku, or numeric variation id. |
format | string | No | json (default) or csv. |
SupplierStockRow object| Field | Type | Description |
|---|---|---|
variation_id | integer | Variation primary key. |
product_label | string | Combined product/variation label with SKU. |
product_name, variation_name, product_variation_name | string | null | Product and variation naming fields used in the report. |
product_type | string | Product type such as single or variable. |
sub_sku | string | null | Variation SKU. |
unit | string | null | Unit short label. |
purchase_quantity, total_quantity_returned, total_quantity_sold, total_quantity_transfered | number | Movement quantities used by the stock report. |
stock_value | number | Current stock valuation. |
current_stock | number | Current on-hand quantity. |
| Field | Type | Description |
|---|---|---|
data | array<SupplierStockRow> | Paginated supplier stock rows. |
meta.current_page, meta.last_page, meta.per_page, meta.total | integer | Pagination metadata. |
meta.location_id | integer | null | Echoed location filter. |
meta.q | string | null | Echoed search term when present. |
| Status | When it happens | Response shape |
|---|---|---|
200 | Supplier stock rows were returned successfully. | JSON { "data": [...], "meta": { ... } } or CSV download. |
403 | The token user cannot access this supplier contact. | { "message": "Unauthorized" } |
404 | The contact id does not exist in the current business. | { "message": "Not found" } |
422 | The contact exists but is not a supplier-type contact, or the query string failed validation. | Laravel validation JSON or an explicit message. |
Lists recurring subscription rows for a single customer contact, using the same subscription mapper and schedule logic as the web subscriptions table.
| Property | Value |
|---|---|
| Method | GET |
| Path | /api/v1/integration/contacts/{id}/subscriptions |
| Module requirement | The business must have the subscription module enabled. |
| Permission | sell.view or direct_sell.access, plus permitted-location access. |
| Contact rule | The contact must be type customer or both and be visible to the token user. |
| CSV behavior | format=csv streams all matching rows and ignores pagination. |
| Success response | 200 with paginated SubscriptionRow rows. |
| Parameter | Type | Required | Description |
|---|---|---|---|
per_page, page | integer | No | Standard subscription-list pagination controls. |
start_date, end_date | string | No | Optional parent transaction date range in Y-m-d. Both must be provided together. |
location_id | integer | No | Optional location filter, still restricted by permitted locations. |
q | string | No | Minimum 2 characters when sent. Matches invoice number, subscription number, location name, or numeric transaction id. |
format | string | No | json (default) or csv. |
SubscriptionRow object| Field | Type | Description |
|---|---|---|
id | integer | Recurring parent sale id. |
contact_id | integer | null | Linked customer id. |
contact_name | string | null | Linked customer display name. |
transaction_date | string | null | ISO-8601 parent transaction timestamp. |
invoice_no, subscription_no | string | null | Parent invoice number and subscription number. |
location_id | integer | null | Business location id. |
location_name | string | null | Business location label. |
is_direct_sale | boolean | Whether the recurring sale is a direct sale. |
recur_stopped_on | string | null | Date when recurring generation was stopped. |
is_stopped | boolean | Convenience flag derived from recur_stopped_on. |
recur_interval, subscription_repeat_on, recur_repetitions | integer | null | Recurring schedule settings saved on the parent sale. |
recur_interval_type, recur_interval_label | string | null | Recurring cadence and its UI-friendly label. |
generated_invoices | array<object> | Generated child invoices. Each item contains id, invoice_no, and transaction_date. |
generated_invoice_count | integer | Number of generated child invoices. |
last_generated_at | string | null | ISO-8601 timestamp of the most recently generated child invoice. |
upcoming_invoice_date | string | null | Next scheduled invoice date, or null when the subscription is stopped. |
| Field | Type | Description |
|---|---|---|
data | array<SubscriptionRow> | Paginated subscription rows for the bound contact. |
meta.current_page, meta.last_page, meta.per_page, meta.total | integer | Pagination metadata. |
meta.location_id, meta.start_date, meta.end_date, meta.q | mixed | Echoed filters when present. |
| Status | When it happens | Response shape |
|---|---|---|
200 | Subscription rows were returned successfully. | JSON { "data": [...], "meta": { ... } } or CSV download. |
403 | The subscription module is disabled, the token user lacks permission, or permitted locations block the request. | { "message": string } |
404 | The contact id does not exist in the current business. | { "message": "Not found" } |
422 | The contact exists but is not a customer-type contact, or the query string failed validation. | Laravel validation JSON or an explicit message. |
Lists all recurring subscription rows visible to the authenticated user across permitted locations.
| Property | Value |
|---|---|
| Method | GET |
| Path | /api/v1/integration/subscriptions |
| Module requirement | The business must have the subscription module enabled. |
| Permission | sell.view or direct_sell.access, plus permitted-location access. |
| CSV behavior | format=csv streams all matching rows and ignores pagination. |
| Success response | 200 with paginated SubscriptionRow rows. |
| Parameter | Type | Required | Description |
|---|---|---|---|
per_page, page | integer | No | Standard pagination controls. |
start_date, end_date | string | No | Optional parent transaction date range in Y-m-d. Both must be provided together. |
location_id | integer | No | Optional location filter within permitted locations. |
contact_id | integer | No | Optional customer filter for the current business. |
q | string | No | Minimum 2 characters when sent. Matches invoice number, subscription number, location name, customer name, or numeric transaction id. |
format | string | No | json (default) or csv. |
| Field | Type | Description |
|---|---|---|
data | array<SubscriptionRow> | Paginated subscription rows. |
meta.current_page, meta.last_page, meta.per_page, meta.total | integer | Pagination metadata. |
meta.location_id, meta.contact_id, meta.start_date, meta.end_date, meta.q | mixed | Echoed filters when present. |
| Status | When it happens | Response shape |
|---|---|---|
200 | Subscription rows were returned successfully. | JSON { "data": [...], "meta": { ... } } or CSV download. |
403 | The subscription module is disabled, the token user lacks permission, or permitted locations block the request. | { "message": string } |
422 | The query string failed validation, such as an invalid date range or a 1-character q. | Laravel validation JSON or an explicit message. |
Returns one recurring parent sale subscription when it belongs to the current business and falls within the user's permitted locations.
| Property | Value |
|---|---|
| Method | GET |
| Path | /api/v1/integration/subscriptions/{id} |
| Module requirement | The business must have the subscription module enabled. |
| Permission | sell.view or direct_sell.access, plus permitted-location access. |
| CSV behavior | format=csv streams one row with a data_json column. |
| Success response | 200 with a SubscriptionRow object. |
| Parameter | Type | Required | Description |
|---|---|---|---|
format | string | No | json (default) or csv. |
| Field | Type | Description |
|---|---|---|
data | SubscriptionRow | Mapped recurring subscription payload, including generated invoice details. |
| Status | When it happens | Response shape |
|---|---|---|
200 | The subscription was returned successfully. | { "data": SubscriptionRow } or CSV download. |
403 | The subscription module is disabled or the token user lacks permission. | { "message": string } |
404 | The subscription id does not exist in the current business or falls outside permitted locations. | { "message": "Not found" } |
500 | An unexpected load error occurred while mapping the subscription. | { "message": string } |
Stops or resumes automatic invoice generation for a recurring parent sale by toggling its recur_stopped_on value.
| Property | Value |
|---|---|
| Method | POST |
| Path | /api/v1/integration/subscriptions/{id}/toggle-recurring |
| Module requirement | The business must have the subscription module enabled. |
| Permission | sell.create, plus permitted-location access. |
| Request body | No request body is required. |
| Demo mode | Returns 403 in demo environments. |
| Success response | 200 with the updated SubscriptionRow. |
| Current state | Effect |
|---|---|
recur_stopped_on is empty | The controller sets it to the current timestamp, stopping future recurring invoice generation. |
recur_stopped_on already has a value | The controller clears it, resuming the recurring schedule. |
| Status | When it happens | Response shape |
|---|---|---|
200 | The recurring state was toggled successfully. | { "data": SubscriptionRow } |
403 | Demo mode, the subscription module is disabled, or the token user lacks sell.create. | { "message": string } |
404 | The subscription id does not exist in the current business or falls outside permitted locations. | { "message": "Not found" } |
Lists documents and notes attached to a contact, matching the visibility rules from the web Documents & notes tab.
| Property | Value |
|---|---|
| Method | GET |
| Path | /api/v1/integration/contacts/{id}/documents-and-notes |
| Permission | Same access and visibility rules as Get contact. |
| Visibility rules | Public notes are visible to all authorized users. Private notes are only visible when created_by is the authenticated user. |
| CSV behavior | format=csv streams all matching rows and ignores pagination. Nested media values are JSON-encoded in cells. |
| Success response | 200 with paginated ContactDocumentNoteRow rows. |
| Parameter | Type | Required | Description |
|---|---|---|---|
per_page | integer | No | Results per page from 1 to 100. Defaults to 20. |
page | integer | No | Pagination page number. |
q | string | No | Minimum 2 characters when sent. Matches heading, description, or numeric document id. |
format | string | No | json (default) or csv. |
ContactDocumentNoteRow object| Field | Type | Description |
|---|---|---|
id | integer | Document/note primary key. |
heading | string | Saved heading/title. |
description | string | null | Saved note body. |
is_private | boolean | Whether the note is private to its creator. |
created_by | integer | null | Creator user id. |
created_by_name | string | null | Creator full name. |
created_at, updated_at | string | null | ISO-8601 timestamps. |
media | array<object> | Attached files. Each item includes id, file_name, display_name, and url. |
media_count | integer | Total number of attached media rows. |
| Field | Type | Description |
|---|---|---|
data | array<ContactDocumentNoteRow> | Paginated document/note rows. |
meta.current_page, meta.last_page, meta.per_page, meta.total | integer | Pagination metadata. |
meta.q | string | null | Echoed search term when present. |
| Status | When it happens | Response shape |
|---|---|---|
200 | Document/note rows were returned successfully. | JSON { "data": [...], "meta": { ... } } or CSV download. |
403 | The token user cannot access this contact. | { "message": "Unauthorized" } |
404 | The contact id does not exist in the current business. | { "message": "Not found" } |
422 | The query string failed validation. | Laravel validation JSON or an explicit message. |
Returns one contact document/note row when it belongs to the contact from the path and passes the same visibility rules as the list endpoint.
| Property | Value |
|---|---|
| Method | GET |
| Path | /api/v1/integration/contacts/{id}/documents-and-notes/{documentId} |
| Permission | Same access and visibility rules as the list endpoint. |
| CSV behavior | format=csv streams one row with a data_json column. |
| Success response | 200 with a ContactDocumentNoteRow object. |
| Parameter | Type | Required | Description |
|---|---|---|---|
format | string | No | json (default) or csv. |
| Field | Type | Description |
|---|---|---|
data | ContactDocumentNoteRow | Document/note payload for the requested row. |
| Status | When it happens | Response shape |
|---|---|---|
200 | The document/note row was returned successfully. | { "data": ContactDocumentNoteRow } or CSV download. |
404 | The contact, document id, or visibility rule check failed. | { "message": "Not found" } |
Creates a document or note on a contact and optionally uploads attachments, using the same persistence path as the web Documents & notes form.
| Property | Value |
|---|---|
| Method | POST |
| Path | /api/v1/integration/contacts/{id}/documents-and-notes |
| Permission | Contact update-level access for the contact type, visibility to the contact, and permission to manage that contact type. |
| Request encoding | application/json or multipart/form-data. |
| Upload rules | Supports up to 10 attached files. Each file must fit within config('constants.document_size_limit'). |
| Success response | 201 with the created ContactDocumentNoteRow. |
| Field | Type | Required | Description |
|---|---|---|---|
heading | string | Yes | Document/note heading. |
description | string | null | No | Optional note body. |
is_private | boolean | No | Marks the note as private to its creator. |
files[] | array<file> | No | Optional multipart attachments. Files are uploaded and attached to the created document/note row. |
| Status | When it happens | Response shape |
|---|---|---|
201 | The document/note row was created successfully. | { "message": string, "data": ContactDocumentNoteRow } |
403 | The token user cannot update this contact or cannot manage its contact type. | { "message": "Unauthorized" } |
404 | The contact id does not exist in the current business. | { "message": "Not found" } |
422 | The payload or uploaded files failed validation. | Laravel validation JSON. |
500 | The create transaction failed unexpectedly. | { "message": string } |
Updates a contact document/note row and optionally appends new attachments.
| Property | Value |
|---|---|
| Methods | PATCH or PUT |
| Path | /api/v1/integration/contacts/{id}/documents-and-notes/{documentId} |
| Permission | Same write permissions as Create contact document / note. |
| Request encoding | application/json or multipart/form-data. |
| Visibility rules | The note must belong to the contact from the path and still satisfy the same private/public visibility checks as the show endpoint. |
| Success response | 200 with the updated ContactDocumentNoteRow. |
| Field | Type | Required | Description |
|---|---|---|---|
heading | string | Yes | Updated heading. |
description | string | null | No | Updated note body. |
is_private | boolean | No | Updated private/public flag. |
files[] | array<file> | No | Optional additional multipart attachments to append to the note. |
| Status | When it happens | Response shape |
|---|---|---|
200 | The document/note row was updated successfully. | { "message": string, "data": ContactDocumentNoteRow } |
403 | The token user cannot update this contact or cannot manage its contact type. | { "message": "Unauthorized" } |
404 | The contact or document id does not exist in the current business. | { "message": "Not found" } |
422 | The payload or uploaded files failed validation. | Laravel validation JSON. |
500 | The update transaction failed unexpectedly. | { "message": string } |
Deletes a contact document/note row and its related media links.
| Property | Value |
|---|---|
| Method | DELETE |
| Path | /api/v1/integration/contacts/{id}/documents-and-notes/{documentId} |
| Permission | Same write permissions as Create contact document / note. |
| Success response | 200 with the deleted document id. |
| Status | When it happens | Response shape |
|---|---|---|
200 | The document/note row was deleted successfully. | { "message": string, "data": { "id": integer } } |
403 | The token user cannot update this contact or cannot manage its contact type. | { "message": "Unauthorized" } |
404 | The contact or document id does not exist in the current business. | { "message": "Not found" } |
500 | The delete transaction failed unexpectedly. | { "message": string } |
Lists payment rows for a contact, matching the parent-payment view shown on the web contact Payments tab.
| Property | Value |
|---|---|
| Method | GET |
| Path | /api/v1/integration/contacts/{id}/payments |
| Permission | Same access and visibility rules as Get contact. |
| Parent-row behavior | Only parent payments are listed in data.payments; child splits are nested under each parent row. |
| Summary behavior | data.summary is calculated across all payment rows matching the date filter, including child rows, and is not narrowed by q. |
| CSV behavior | format=csv streams all matching parent rows and ignores pagination. Nested child_payments values are JSON-encoded in cells. |
| Success response | 200 with payments, summary totals, and paginator metadata. |
| Parameter | Type | Required | Description |
|---|---|---|---|
per_page | integer | No | Results per page from 1 to 100. Defaults to 20. |
page | integer | No | Pagination page number. |
start_date, end_date | string | No | Optional payment date range in Y-m-d. Both must be provided together. |
q | string | No | Minimum 2 characters when sent. Matches payment reference fields, note, method, cheque/card/bank fields, linked transaction reference numbers, or numeric payment id. |
format | string | No | json (default) or csv. |
ContactPaymentRow object| Field | Type | Description |
|---|---|---|
id | integer | Payment primary key. |
amount | number | Payment amount. |
is_return | boolean | Whether the payment is a return payment. |
method, method_label | string | null | Stored payment method key and display label. |
paid_on | string | null | ISO-8601 payment timestamp. |
payment_ref_no, transaction_no | string | null | Payment reference fields. |
invoice_no, ref_no | string | null | Linked transaction invoice/reference numbers. |
transaction_type | string | null | Linked transaction type. |
transaction_id, return_parent_id | integer | null | Linked transaction ids when present. |
cheque_number, card_transaction_number, bank_account_number | string | null | Method-specific reference fields. |
child_payments | array<object> | Child payment splits. Each child row includes id, amount, method, method_label, paid_on, and payment_ref_no. |
ContactPaymentSummary object| Field | Type | Description |
|---|---|---|
count | integer | Total payment rows counted for the summary query. |
total_paid | number | Total paid amount for the summary query. |
methods_count | integer | Number of distinct payment methods in the summary query. |
latest_paid_on | string | null | Most recent payment timestamp in ISO-8601 format. |
| Field | Type | Description |
|---|---|---|
data.contact_id | integer | Contact id from the path. |
data.payments | array<ContactPaymentRow> | Paginated parent payment rows. |
data.summary | ContactPaymentSummary | Summary block for the applied date filter. |
meta.current_page, meta.last_page, meta.per_page, meta.total | integer | Pagination metadata. |
meta.start_date, meta.end_date, meta.q | string | null | Echoed filters when present. |
| Status | When it happens | Response shape |
|---|---|---|
200 | Payment rows were returned successfully. | JSON { "data": { ... }, "meta": { ... } } or CSV download. |
403 | The token user cannot access this contact. | { "message": "Unauthorized" } |
404 | The contact id does not exist in the current business. | { "message": "Not found" } |
422 | The query string failed validation, such as incomplete date filters or a 1-character q. | Laravel validation JSON or an explicit message. |
Records a payment against a contact due balance using the same payment engine as the web Pay due action.
| Property | Value |
|---|---|
| Method | POST |
| Path | /api/v1/integration/contacts/{id}/payments |
| Authentication | Bearer token or API key + secret required. |
| Permission | sell.payments for sell/sell_return; purchase.payments for purchase/purchase_return. When due_payment_type is omitted, the controller defaults to sell for customer/both contacts and purchase for supplier contacts. |
| Visibility rules | Same contact access rules as Get contact. |
| Request encoding | application/json |
| Success response | 201 with a compact created-payment summary. |
| Header | Required | Description |
|---|---|---|
Authorization | Yes | Bearer YOUR_ACCESS_TOKEN |
Content-Type | Yes | application/json |
Accept | No | Recommended: application/json. |
| Field | Type | Required | Description |
|---|---|---|---|
amount | number | Yes | Payment amount to post against the contact due balance. |
method | string | Yes | Payment method key. It must exist in the app's enabled payment method set. |
paid_on | string | null | No | Payment date/datetime accepted by Laravel's date validator. |
due_payment_type | string | null | No | sell, purchase, sell_return, or purchase_return. |
is_reverse | boolean | No | Marks the payment as a reverse payment when true. |
note | string | null | No | Optional payment note. |
account_id | integer | null | No | Optional account id for the payment. |
cheque_number, bank_account_number, card_transaction_number | string | null | No | Method-specific reference fields. |
card_number, card_holder_name, card_type, card_month, card_year, card_security | string | null | No | Card payment metadata. |
transaction_no_1 to transaction_no_7 | string | null | No | Custom payment-method reference fields. |
| Field | Type | Description |
|---|---|---|
message | string | Localized payment success message. |
data.id | integer | Created payment id. |
data.contact_id | integer | Contact id from the path. |
data.payment_ref_no | string | null | Generated payment reference number. |
data.amount | number | Stored payment amount. |
data.method | string | Stored payment method key. |
data.paid_on | string | null | ISO-8601 payment timestamp. |
data.is_advance | boolean | Whether the payment was treated as an advance payment. |
data.payment_type | string | null | Stored payment type returned by the payment engine. |
| Status | When it happens | Response shape |
|---|---|---|
201 | The payment was recorded successfully. | { "message": string, "data": { ... } } |
403 | The token user cannot access the contact or lacks the required payment permission for the selected payment type. | { "message": "Unauthorized" } |
404 | The contact id does not exist in the current business. | { "message": "Not found" } |
422 | The payload failed validation, the payment method is unknown, or the payment engine rejected the request. | Laravel validation JSON or { "message": string, "errors": { ... } }. |
Creates a new contact in the authenticated business using the same contact pipeline as the web contact form.
| Property | Value |
|---|---|
| Method | POST |
| Path | /api/v1/integration/contacts |
| Authentication | Bearer token or API key + secret required. |
| Permission | At least one of supplier.create, customer.create, supplier.view_own, or customer.view_own. The chosen type must also pass the controller's type-specific create permission check. |
| Subscription rule | Returns 402 when package enforcement is active and the business is not subscribed. |
| Request encoding | application/json |
| Success response | 201 with the full ContactDetail object. |
| Header | Required | Description |
|---|---|---|
Authorization | Yes | Bearer YOUR_ACCESS_TOKEN |
Content-Type | Yes | application/json |
Accept | No | Recommended: application/json. |
| Field | Type | Required | Description |
|---|---|---|---|
type | string | Yes | customer, supplier, or both. |
name | string | Yes | Primary contact display name. |
mobile | string | null | No | Primary mobile number. Defaults to an empty string when omitted. |
supplier_business_name | string | null | No | Business label for supplier/company contacts. |
prefix, first_name, middle_name, last_name | string | null | No | Optional person-name fields. |
email | string | null | No | Email address. |
contact_id | string | null | No | Optional business-facing contact code. Duplicate values return 422. |
tax_number | string | null | No | Tax number for the contact. |
contact_type | string | null | No | individual or business. |
contact_status | string | null | No | Optional initial status such as active or inactive. |
dob | string | null | No | Date of birth in Y-m-d format. |
| Field | Type | Required | Description |
|---|---|---|---|
pay_term_number, pay_term_type | integer | string | null | No | Optional supplier pay-term settings. pay_term_type must be days or months. |
landline, alternate_number | string | null | No | Additional phone numbers. |
city, state, country | string | null | No | Address summary fields. |
address_line_1, address_line_2, zip_code | string | null | No | Street and zip details. |
shipping_address | string | null | No | Shipping address text. |
position | string | null | No | Stored latitude/longitude string used by the contact map. |
land_mark, street_name, building_number, additional_number | string | null | No | Extended address fields. |
customer_group_id | integer | null | No | Customer group id for this business. |
credit_limit | number | null | No | Credit limit for the contact. |
opening_balance | number | null | No | Opening balance amount. The contact pipeline will create the related opening balance transaction when needed. |
assigned_to_users | array<integer> | null | No | User ids assigned to this contact. |
| Field | Type | Required | Description |
|---|---|---|---|
shipping_custom_field_details | array | null | No | Structured shipping custom field payload. |
custom_field1 to custom_field10 | string | null | No | Business-defined custom contact fields. |
is_export | boolean | No | Enables export custom fields when true. |
export_custom_field_1 to export_custom_field_6 | string | null | No | Export-only custom fields stored when is_export is enabled. |
| Field | Type | Description |
|---|---|---|
message | string | Localized create success message. |
data | ContactDetail | Full contact payload, matching Get contact. |
| Status | When it happens | Response shape |
|---|---|---|
201 | The contact was created successfully. | { "message": string, "data": ContactDetail } |
402 | The business is not subscribed when package enforcement is active. | { "message": string } |
403 | The token user lacks permission to create the requested contact type. | { "message": "Unauthorized" } |
422 | The payload failed validation or the supplied contact_id is already used in the current business. | Laravel validation JSON or { "message": string }. |
500 | The create transaction failed unexpectedly. | { "message": "something_went_wrong" } |
Partially updates an existing contact. The endpoint accepts the same field set as Add contact, but every field is optional.
| Property | Value |
|---|---|
| Methods | PATCH or PUT |
| Path | /api/v1/integration/contacts/{id} |
| Authentication | Bearer token or API key + secret required. |
| Permission | At least one of supplier.update, customer.update, supplier.view_own, or customer.view_own, plus visibility to the contact from the path. |
| Type rule | The effective type after the update must still pass the controller's type-specific create permission check. |
| Subscription rule | Returns 402 when package enforcement is active and the business is not subscribed. |
| Request encoding | application/json |
| Success response | 200 with the full updated ContactDetail object. |
| Rule | Description |
|---|---|
| Accepted fields | All request fields documented under Add contact are accepted here as optional patch fields. |
| Empty body | An empty or unusable payload returns 422 with No valid fields to update. |
contact_status | Use active or inactive to toggle the same status flag used by the web contact status control. |
mobile | When present, the controller normalizes null to an empty string. |
is_export | When set to false, export custom fields sent in the same payload are ignored. |
| Duplicate contact codes | Updating contact_id to a value already used in the same business returns 422. |
| Field | Type | Description |
|---|---|---|
message | string | Localized update success message. |
data | ContactDetail | Full updated contact payload, matching Get contact. |
| Status | When it happens | Response shape |
|---|---|---|
200 | The contact was updated successfully. | { "message": string, "data": ContactDetail } |
402 | The business is not subscribed when package enforcement is active. | { "message": string } |
403 | The token user cannot update the contact or the effective contact type is not allowed. | { "message": "Unauthorized" } |
404 | The contact id does not exist in the current business. | { "message": "Not found" } |
422 | The patch payload failed validation, no valid fields were supplied, or the updated contact_id is duplicated. | Laravel validation JSON or { "message": string }. |
500 | The update transaction failed unexpectedly. | { "message": "something_went_wrong" } |
Deletes a contact when it is safe to do so. This follows the same delete blockers and side effects as the web contact delete action.
| Property | Value |
|---|---|
| Method | DELETE |
| Path | /api/v1/integration/contacts/{id} |
| Authentication | Bearer token or API key + secret required. |
| Permission | At least one of supplier.delete, customer.delete, supplier.view_own, or customer.view_own. For both-type contacts, the delete check must pass for both customer and supplier sides. |
| Visibility rules | Same contact visibility rules as Get contact. |
| Success response | 200 with the deleted contact id. |
| Rule | Description |
|---|---|
| Existing transactions | If any transactions row exists for this contact in the current business, the delete is blocked with 422. |
| Default contact | Default contacts such as the walk-in customer cannot be deleted. |
| CRM-linked users | On success, users linked by crm_contact_id have allow_login disabled. |
| Delete behavior | The controller records activity, soft-deletes the contact, and dispatches the same contact modified event used elsewhere in the app. |
| Status | When it happens | Response shape |
|---|---|---|
200 | The contact was deleted successfully. | { "message": string, "data": { "id": integer } } |
403 | The token user cannot delete the contact or lacks the required type-specific delete permission. | { "message": "Unauthorized" } |
404 | The contact id does not exist in the current business. | { "message": "Not found" } |
422 | The contact still has transactions or is the default contact. | { "message": string } |
500 | The delete transaction failed unexpectedly. | { "message": "something_went_wrong" } |
Lists customer groups for the authenticated business, using the same rows shown in the web Customer Groups screen.
| Property | Value |
|---|---|
| Method | GET |
| Path | /api/v1/integration/customer-groups |
| Authentication | Bearer token or API key + secret required. |
| Permission | customer.view |
| CSV behavior | format=csv streams all matching rows with a UTF-8 BOM and ignores pagination. |
| Success response | 200 with paginated CustomerGroup rows. |
| Parameter | Type | Required | Description |
|---|---|---|---|
per_page | integer | No | Results per page from 1 to 100. Defaults to 20. |
page | integer | No | Pagination page number. |
q | string | No | Minimum 2 characters when sent. Matches the customer group name, linked selling price group name, or numeric customer group id. |
format | string | No | json (default) or csv. |
CustomerGroup object| Field | Type | Description |
|---|---|---|
id | integer | Customer group primary key. |
name | string | Customer group name. |
price_calculation_type | string | percentage or selling_price_group. |
amount | number | Stored percentage amount or zero when the customer group is driven by a selling price group. |
selling_price_group_id | integer | null | Linked selling price group id when price_calculation_type is selling_price_group. |
selling_price_group_name | string | null | Linked selling price group name when applicable. |
created_by | integer | null | Creator user id. |
created_at, updated_at | string | null | ISO-8601 timestamps. |
| Field | Type | Description |
|---|---|---|
data | array<CustomerGroup> | Paginated customer group rows. |
meta.current_page, meta.last_page, meta.per_page, meta.total | integer | Pagination metadata. |
| Status | When it happens | Response shape |
|---|---|---|
200 | Customer groups were returned successfully. | JSON { "data": [...], "meta": { ... } } or CSV download. |
403 | The token user lacks customer.view. | { "message": "Unauthorized" } |
422 | The query string failed validation, such as a 1-character q. | Laravel validation JSON or an explicit message. |
Returns one customer group for the authenticated business.
| Property | Value |
|---|---|
| Method | GET |
| Path | /api/v1/integration/customer-groups/{id} |
| Permission | customer.view |
| CSV behavior | format=csv streams one row with a data_json column. |
| Success response | 200 with a CustomerGroup object. |
| Parameter | Type | Required | Description |
|---|---|---|---|
format | string | No | json (default) or csv. |
| Field | Type | Description |
|---|---|---|
data | CustomerGroup | Customer group payload for the requested row. |
| Status | When it happens | Response shape |
|---|---|---|
200 | The customer group was returned successfully. | { "data": CustomerGroup } or CSV download. |
403 | The token user lacks customer.view. | { "message": "Unauthorized" } |
404 | The customer group id does not exist in the current business. | { "message": "Not found" } |
Creates a customer group for the authenticated business.
| Property | Value |
|---|---|
| Method | POST |
| Path | /api/v1/integration/customer-groups |
| Permission | customer.create |
| Request encoding | application/json |
| Success response | 201 with the created CustomerGroup object. |
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Customer group name. |
price_calculation_type | string | Yes | percentage or selling_price_group. |
amount | number | null | No | Percentage amount. The controller stores zero when omitted. |
selling_price_group_id | integer | null | Conditional | Required when price_calculation_type is selling_price_group. The id must belong to the current business. |
| Status | When it happens | Response shape |
|---|---|---|
201 | The customer group was created successfully. | { "message": string, "data": CustomerGroup } |
403 | The token user lacks customer.create. | { "message": "Unauthorized" } |
422 | The payload failed validation or a selling price group was required but not provided. | Laravel validation JSON or { "message": string }. |
500 | The create transaction failed unexpectedly. | { "message": string } |
Updates an existing customer group. The endpoint accepts partial updates and applies the same price-calculation rules as the create flow.
| Property | Value |
|---|---|
| Methods | PATCH or PUT |
| Path | /api/v1/integration/customer-groups/{id} |
| Permission | customer.update |
| Request encoding | application/json |
| Success response | 200 with the updated CustomerGroup object. |
| Field | Type | Required | Description |
|---|---|---|---|
name | string | No | Updated customer group name. |
price_calculation_type | string | No | percentage or selling_price_group. |
amount | number | null | No | Updated percentage amount. |
selling_price_group_id | integer | null | Conditional | Required when the effective calculation type is selling_price_group. The id must belong to the current business. |
| Status | When it happens | Response shape |
|---|---|---|
200 | The customer group was updated successfully. | { "message": string, "data": CustomerGroup } |
403 | The token user lacks customer.update. | { "message": "Unauthorized" } |
404 | The customer group id does not exist in the current business. | { "message": "Not found" } |
422 | The payload failed validation or a required selling price group id is missing. | Laravel validation JSON or { "message": string }. |
500 | The update transaction failed unexpectedly. | { "message": string } |
Deletes a customer group in the authenticated business.
| Property | Value |
|---|---|
| Method | DELETE |
| Path | /api/v1/integration/customer-groups/{id} |
| Permission | customer.delete |
| Success response | 200 with the deleted customer group id. |
| Status | When it happens | Response shape |
|---|---|---|
200 | The customer group was deleted successfully. | { "message": string, "data": { "id": integer } } |
403 | The token user lacks customer.delete. | { "message": "Unauthorized" } |
404 | The customer group id does not exist in the current business. | { "message": "Not found" } |
500 | The delete transaction failed unexpectedly. | { "message": string } |