Home Integration API reference
REST API for integrations and external apps
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 } |