The Xagle API
The Xagle API allows you to programmatically manage clients, invoices, quotes, payments, and related resources within your Xagle system. This API provides both read and write capabilities, enabling integration with other business systems or custom tooling.
Getting Started
For the purposes of testing, we are using Postman. You can use whatever API testing tool you prefer - The Postman collection can be found at the link below:.
Below are the variables we have set up for our local environment to make testing easier. Note that the token value must be retrieved using the login endpoint. More on that next.
Your API auth token can be retrieved by using the Xagle login endpoint. Before doing this, it’s a good idea to create a new admin level user within your Xagle installation, which you will use for API calls. For instance, create a called api user with an email of
[email protected]
and make note of the password. Next, we’ll go back to Postman and call the login endpoint with this new user’s credentials. (see example below)
Click the Send button and your personal access token will be generated. You will see it in the response area.
At this point, you can go back to your Postman environment variables and paste in the value from the token you received. (In this example you would paste in the value starting with 2|Wlf. Do not paste the quotation marks. Now that you have a token saved to your environment, you should be able to start making API calls from Postman.
How the API endpoints are organized
Our endpoints are organized by module and we currently support:
- Client
- Invoices
- Quotes
- Payments
Let’s explore the Client endpoints first. There are 5 endpoints that can be called for clients.
List endpoints end in the name of the module, for instance the list endpoint for clients would be something like: https://myfiurl.com/api/v1/clients
List endpoints support many query parameter options for filtering criteria.
•paginated_response
•include_custom_fields
•active
In addition, you can specify any valid field name that matches (or does not match) a certain value. For instance, if we wanted to retrieve a list of clients that had a timezone in America, we would add a “timezone” key and give it a value of “america”.
If you add multiple key values to filter by, the conditions are joined with an OR operator. For instance, timezone=america OR name=acme shoe repair. However, you can change the operator to an AND condition by adding the key “where_operator” and giving it a value of “AND”. The OR or AND operator works on the entire expression and at this time you cannot mix and match OR and AND operators.
Show endpoints are meant to fetch the data for a single record. In the case of a client, we would specify the ID of the client to show in the URL. Ie. https://myfiurl.com/api/v1/clients/2
List endpoints support many query parameter options for filtering criteria.
- include_custom_fields
Add endpoints use a POST method and are used to add a new record. The URL is the resource collection itself. In the case of a client, https://myfiurl.com/api/v1/clients
There are several required fields for each add endpoint. In the case of clients, the required fields are:
•name
•email
•type
You can however pass any valid field name and value pair.
Update endpoints use a PUT method and are used to update an existing record and have a URL ending with ID of the record. A client example, https://myfiurl.com/api/v1/clients/1201
List the fields name and values you would like to update. Here is a JSON body example for the client with ID 1201:
{
“name” : “Barnes and Associates”,
“city” : “Tallahassee””,
“state” : “FL”,
“active” : 1
}
Delete endpoints use a DEL method and are used to delete an existing record. They have a URL ending with ID of the record. A client example, https://myfiurl.com/api/v1/clients/1302
There are no additional parameters for delete endpoints.
Formatting the request parameters
The API can accept parameters as normal URL query stings (key value pairs) or as JSON in the body of the request. The JSON collection included shows both methods in the various examples.
Base URL
All endpoints are accessed via a base URL, which generally follows this pattern:
{{protocol}}://{{host}}/api/v{{version}}/
- protocol: Usually
https - host: Your Xagle installation domain (e.g.,
app.yourdomain.com) - version: The current API version (e.g.,
1)
For example:
https://app.yourdomain.com/api/v1/
Notes on Authentication
The Xagle API uses Bearer Token authentication. To authenticate:
- Obtain a token by calling the
POST /loginendpoint with valid credentials. - Include the token in subsequent requests via the
Authorizationheader:
Authorization: Bearer {{token}}
Request and Response Formats
- Request Formats: Endpoints generally accept JSON or form-data. Check the specific endpoint details for the required content type.
- Response Format: JSON is returned in responses unless otherwise specified.
- Error Handling: If an error occurs (e.g., validation failures, unauthorized access), the response will typically include an HTTP status code (4xx or 5xx) and a JSON payload describing the error.
Pagination
Many listing endpoints support pagination. To enable pagination, include the query parameter paginated_response=1. The response will then include pagination metadata (like current_page, last_page, per_page).
Custom Fields
Some endpoints support include_custom_fields=1 to include custom field data. Additionally, you can filter or search using custom fields by passing parameters such as custom->column_1.
Authentication
Login
Endpoint: POST /login
Description: Obtain a bearer token by providing valid Xagle user credentials.
Form Data Parameters:
email(string, required): Your account email.password(string, required): Your account password.
Example:
curl -X POST "{{protocol}}://{{host}}/api/v{{version}}/login" \
-F "[email protected]" \
-F "password=12345678"
Upon success, the response will contain a token you can use for subsequent requests.
Clients
Manage client information, including creation, retrieval, updating, and deletion.
Note on field names: in April 2026 the client's phone columns were renamed for consistency with the contacts schema. The new names are primary_phone / alternate_phone (+ matching primary_isd_code / alternate_isd_code) and two primary_is_mobile / alternate_is_mobile flags. Existing integrations that post the old names — phone, mobile, phone_isd_code, mobile_isd_code — continue to work: the API maps them to the new names automatically on POST /clients and PUT /clients/{id}. If both old and new keys are posted, the new names win. New integrations should use the new names directly.
List Clients
Endpoint: GET /clients
Query Parameters:
paginated_response(int, optional):1to paginate results.name(string, optional): Filter clients by name.- Additional filters like
active,include_custom_fields, orcustom->column_xmay be available.
Example:
curl -X GET "{{protocol}}://{{host}}/api/v{{version}}/clients?paginated_response=1&page=1" \
-H "Authorization: Bearer {{token}}"
Show Client
Endpoint: GET /clients/{id}
Description: Retrieve details for a single client by ID.
Example:
curl -X GET "{{protocol}}://{{host}}/api/v{{version}}/clients/2" \
-H "Authorization: Bearer {{token}}"
Add Client
Endpoint: POST /clients
Body (JSON):
Core fields
name(string, required)email(string, optional) — the client's business email (e.g.[email protected]). Required ifallow_client_center_login=1.type(string, required) — one oflead,prospect,trial,customer,affiliate,other,referral_partner,integration_partner,corporate.company_profile_id(int, optional) — defaults to the system default company profile.invoice_prefix(string, optional, max 5 chars)allow_child_accounts(int, 1 or 0, optional)third_party_bill_payer(int, 1 or 0, optional)allow_client_center_login(int, 1 or 0, optional)passwordandpassword_confirmation(required ifallow_client_center_login=1)
Address
address(string, optional)city(string, optional)state(string, optional)zip(string, optional)country(string, optional)
Phone numbers (client's own business contact info)
primary_phone(string, optional, max 20 chars) — legacy key:phone.primary_isd_code(string, optional) — country calling code prefix, e.g.+1. Legacy key:phone_isd_code.primary_is_mobile(int, 1 or 0, optional, default 0) — flag whether this phone is a mobile number.alternate_phone(string, optional, max 20 chars) — legacy key:mobile.alternate_isd_code(string, optional) — legacy key:mobile_isd_code.alternate_is_mobile(int, 1 or 0, optional, default 0).fax(string, optional)
Other
Primary Contact (optional nested object)
The client's primary contact is a separate record representing the person-at-the-company (distinct from the client's own business email/phone). If you omit this block, a primary contact is auto-seeded from the client's own fields. To set it explicitly, submit the nested payload:
primary_contact.name(string, optional, max 255)primary_contact.email(string, optional, must be a valid email)primary_contact.primary_phone(string, optional, max 20)primary_contact.primary_isd_code(string, optional)primary_contact.primary_is_mobile(int, 1 or 0, optional)primary_contact.alternate_phone(string, optional, max 20)primary_contact.alternate_isd_code(string, optional)primary_contact.alternate_is_mobile(int, 1 or 0, optional)
Example — minimal:
curl -X POST "{{protocol}}://{{host}}/api/v{{version}}/clients" \
-H "Authorization: Bearer {{token}}" \
-H "Content-Type: application/json" \
-d '{
"name": "Acme Corporation",
"email": "[email protected]",
"type": "customer",
"primary_phone": "+1-555-0100",
"primary_is_mobile": 0,
"alternate_phone": "+1-555-0101",
"alternate_is_mobile": 1
}'
Example — with Primary Contact (person at the company):
curl -X POST "{{protocol}}://{{host}}/api/v{{version}}/clients" \
-H "Authorization: Bearer {{token}}" \
-H "Content-Type: application/json" \
-d '{
"name": "Acme Corporation",
"email": "[email protected]",
"type": "customer",
"primary_phone": "+1-555-0100",
"primary_contact": {
"name": "Jane Doe",
"email": "[email protected]",
"primary_phone": "+1-555-0200",
"primary_is_mobile": 1
}
}'
Update Client
Endpoint: PUT /clients/{id}
Description: Update client information. Accepts the same fields as POST /clients, all optional. Include only the fields you want to change. The legacy phone / mobile / phone_isd_code / mobile_isd_code names are accepted and mapped to the new names automatically.
If the primary_contact object is included, the client's existing primary contact is updated in place. Omit the object (or send an empty one) to leave the primary contact untouched.
Example:
curl -X PUT "{{protocol}}://{{host}}/api/v{{version}}/clients/33657" \
-H "Authorization: Bearer {{token}}" \
-H "Content-Type: application/json" \
-d '{
"name": "Acme Corporation (renamed)",
"primary_phone": "+1-555-0999",
"primary_contact": {
"email": "[email protected]"
}
}'
Delete Client
Endpoint: DELETE /clients/{id}
Example:
curl -X DELETE "{{protocol}}://{{host}}/api/v{{version}}/clients/1302" \
-H "Authorization: Bearer {{token}}"
Notes
Add transactional notes to a client, invoice, quote, payment, or task — the entries shown in the Communication tab on the corresponding view page. Useful for logging call notes, follow-ups, internal context, or any commentary from third-party integrations.
⚠️ Permission requirement: all notes endpoints require the notes.create permission on the API user. If the authenticated user lacks this permission, the request will return 403 Forbidden. When provisioning a service-account user for API integrations, make sure to assign a role that includes notes.create in addition to whatever resource-specific permissions the integration needs (e.g. clients.view, invoices.view).
Endpoints
All five endpoints accept the same request body and return the same response shape:
POST /clients/{id}/notesPOST /invoices/{id}/notesPOST /quotes/{id}/notesPOST /payments/{id}/notesPOST /tasks/{id}/notesPOST /vendors/{id}/notes
Body (JSON)
Required:
note(string) — the note content. HTML tags are stripped.
Optional:
Contact resolution rule: contact_id wins if provided; otherwise contact_email is looked up; otherwise the note has no contact attribution. Notes attributed to a specific contact appear under that contact's filter on the Communication tab; unattributed notes appear under "All Contacts" only.
Response
201 Createdwith the new note record (including its attached tags) on success.404 Not Foundif the parent resource (client, invoice, etc.) doesn’t exist.422 Unprocessable Entityon validation failure.403 Forbiddenif the API user lacks thenotes.createpermission.
Example
curl -X POST "{{protocol}}://{{host}}/api/v{{version}}/clients/1201/notes" \
-H "Authorization: Bearer {{token}}" \
-H "Content-Type: application/json" \
-d '{
"note": "Called Jake — interested in HD forks, follow up in 45 days.",
"tags": ["fup-call", "torqueforks"],
"is_private": false,
"contact_email": "[email protected]"
}'
Expenses
Allows you to list, view, add, update, and delete expenses.
List Expenses
Endpoint: GET /expenses
Query Parameters:
paginated_response=1for pagination.- Optional filters like
include_custom_fields,search, etc.
Show Expense
Endpoint: GET /expenses/{id}
Example:
curl -X GET "{{protocol}}://{{host}}/api/v{{version}}/expenses/2?include_custom_fields=1" \
-H "Authorization: Bearer {{token}}"
Add Expense
Endpoint: POST /expenses
Body (form-data):
type(required, e.g.,standard_expense)amount(required iftype=standard_expense)- Other fields like
company_profile_id,vendor_name,category_name,description,expense_date, etc.
Update Expense
Endpoint: PUT /expenses/{id}
Description: Update an existing expense by ID. Uses form-data parameters similar to adding.
Delete Expense
Endpoint: DELETE /expenses/{id}
Vendors
Manage vendor records — the suppliers and contractors you record expenses against. Vendors live within the Expenses module but have their own dedicated endpoints for full CRUD and note creation.
List Vendors
Endpoint: GET /vendors
Query Parameters:
paginated_response(int, optional):1to paginate results.search(string, optional): keyword search across vendor name, email, phone, and mobile.
Example:
curl -X GET "{{protocol}}://{{host}}/api/v{{version}}/vendors?paginated_response=1&page=1" \
-H "Authorization: Bearer {{token}}"
Show Vendor
Endpoint: GET /vendors/{id}
Description: Retrieve details for a single vendor by ID.
Add Vendor
Endpoint: POST /vendors
Body (JSON):
name(string, required, max 255 chars, must be unique across vendors)email(string, optional, must be a valid email)phone(string, optional, max 20)phone_isd_code(string, optional) — country calling code prefix, e.g.+1.mobile(string, optional, max 20)mobile_isd_code(string, optional)web(string, optional, max 255)contact_names(string, optional)address(string, optional)notes(string, optional)category_id(int or string, optional) — expense-category ID, or a category name (a new category is auto-created if it does not exist).active(int, 1 or 0, optional, default 1)
Example:
curl -X POST "{{protocol}}://{{host}}/api/v{{version}}/vendors" \
-H "Authorization: Bearer {{token}}" \
-H "Content-Type: application/json" \
-d '{
"name": "Acme Office Supply",
"email": "[email protected]",
"phone": "+1-555-0150",
"active": 1
}'
Update Vendor
Endpoint: PUT /vendors/{id}
Description: Update vendor information. Accepts the same fields as POST /vendors, all optional. Include only the fields you want to change. The name field must remain unique across vendors when changed.
Delete Vendor
Endpoint: DELETE /vendors/{id}
If the vendor has related records — expenses, shipping methods, item-master entries, or purchase orders — the request returns 400 Bad Request with a message listing the modules that block the deletion. Clear those references first, then retry.
Add Vendor Note
Endpoint: POST /vendors/{id}/notes
Add a transactional note to a vendor. See the Notes section above for the full request body and contact-resolution rules. Notes attributed to a specific vendor contact use the same morph lookup as client/invoice/quote/payment/task notes.
Quotes
Manage quotes: list, show, create, add items, custom fields, send email, and download PDF.
List Quotes
Endpoint: GET /quotes
Query Parameters:
paginated_response=1,page,include_custom_fields, etc.
Show Quote
Endpoint: GET /quotes/{id}
Add Quote
Endpoint: POST /quotes
Form Data Parameters:
client_nameorclient_idquote_date(Y-m-d)company_profile_id
Delete Quote
Endpoint: DELETE /quotes/{id}
Add Quote Items
Endpoint: PUT /quotes/items/add
Body (JSON):
quote_id(int)name(string)quantity(int)price(numeric)
Add Custom Field to Quote
Endpoint: PATCH /quotes/custom-fields/add
Body (JSON):
quote_idcolumn_1(example custom field)
Send Quote via Email
Endpoint: POST /quotes/email
Form Data Parameters:
id(quote ID)subjectto[]attach_pdf(0 or 1)
Download Quote PDF
Endpoint: GET /quotes/{id}/pdf
Invoices
Manage invoices: list, show, create, add items, send email, download PDF, and apply credit memos.
List Invoices
Endpoint: GET /invoices
Query Parameters:
paginated_response=1,page,include_custom_fields, etc.
Show Invoice
Endpoint: GET /invoices/{id}
Download Invoice PDF
Endpoint: GET /invoices/{id}/pdf
Send Invoice via Email
Endpoint: POST /invoices/email
Form Data Parameters:
id(invoice ID)subjectto[]attach_pdf(0 or 1)
Add Invoice
Endpoint: POST /invoices
Form Data Parameters:
client_nameorclient_idinvoice_date(Y-m-d)company_profile_id
Apply Credit Memo to Invoice
Endpoint: POST /invoices/apply/credit-memo
Form Data Parameters:
invoice_idcredit_memo_idamount
Add Invoice Items
Endpoint: PUT /invoices/items/add
Body (JSON):
invoice_idnamequantityprice
Delete Invoice
Endpoint: DELETE /invoices/{id}
Payments
Manage payments: list, show, add, and delete.
List Payments
Endpoint: GET /payments
Show Payment
Endpoint: GET /payments/{id}
Add Payment
Endpoint: POST /payments
Form Data Parameters:
payment_method_idpaid_at(date)amountinvoice_idclient_id
Add Custom Fields to Payment
Endpoint: PATCH /payments/custom-fields/add
Delete Payment
Endpoint: DELETE /payments/{id}
Client Contacts
Manage client contacts associated with a particular client.
List Client Contacts
Endpoint: GET /client/contact/{client_id}
Lists all contacts associated with the given client ID.
Show Client Contact
Endpoint: GET /client/contact/view/{id}
Add Contact
Endpoint: POST /client/contact
Form Data Parameters:
titlenameprimary_isd_code,primary_phone,primary_is_mobilealternate_isd_code,alternate_phone,alternate_is_mobileemaildefault_to,default_cc,default_bcc(1 or 0)notesclient_id
Update Contact
Endpoint: PUT /client/contact/{id}
Body (JSON): Fields similar to adding a contact.
Delete Contact
Endpoint: DELETE /client/contact/{id}
Vendor Contacts
Manage individual contacts (people) attached to a vendor. Mirrors the Client Contacts endpoints but scoped to vendors.
List Vendor Contacts
Endpoint: GET /vendor/contact/{vendor_id}
Lists all contacts attached to the given vendor ID. Supports paginated_response=1 and a search keyword.
Show Vendor Contact
Endpoint: GET /vendor/contact/view/{id}
Only contacts whose parent is a vendor are returned by this endpoint; client contacts cannot be accessed through it.
Add Vendor Contact
Endpoint: POST /vendor/contact
Body (JSON):
name(string, required)email(string, required, valid email)vendor_id(int, required) — the parent vendor. Alternatively sendcontact_iddirectly (the morph identifier the contacts table stores).title(string, optional)primary_phone,primary_isd_code,primary_is_mobile(optional)alternate_phone,alternate_isd_code,alternate_is_mobile(optional)default_to,default_cc,default_bcc(1 or 0, optional)notes(string, optional)
Example:
curl -X POST "{{protocol}}://{{host}}/api/v{{version}}/vendor/contact" \
-H "Authorization: Bearer {{token}}" \
-H "Content-Type: application/json" \
-d '{
"vendor_id": 42,
"name": "Pat Lee",
"email": "[email protected]",
"primary_phone": "+1-555-0151"
}'
Update Vendor Contact
Endpoint: PUT /vendor/contact/{id}
Body (JSON): fields similar to adding a contact. The contact's parent vendor cannot be reassigned through this endpoint.
Delete Vendor Contact
Endpoint: DELETE /vendor/contact/{id}
Tasks
Manage tasks: list, show, create, update, and delete.
List Tasks
Endpoint: GET /tasks
Query Parameters:
paginated_response(int, optional):1to paginate results.search(string, optional): keyword search across task fields.status(string, optional): filter by status. Defaults toopen.from_date(date, optional): filter by due-date range start.to_date(date, optional): filter by due-date range end.
Show Task
Endpoint: GET /tasks/{id}
Retrieve a single task by ID.
Add Task
Endpoint: POST /tasks
Body (JSON or form-data):
Required:
title(string)assignee_id(int) — must reference an existing user.task_section_id(int) — must reference an existing task section.
Optional:
description(string)due_date(date or datetime, e.g.2026-04-25or2026-04-25 02:30 PM)client_id(int)is_recurring(int,0or1)recurring_frequency(int)recurring_period(string) — e.g.day,week,month
Response: { "success": true, "message": "...", "task_id": <new id> }
Update Task
Endpoint: PUT /tasks/{id}
Body (JSON or form-data):
Required:
title(string)
Optional: all of the Add Task fields above, plus:
is_complete(int,0or1) — mark the task complete or reopen it. Only updated when the field is sent.completion_note(string)
Delete Task
Endpoint: DELETE /tasks/{id}
Permanently deletes a task.
Additional Notes
- Always include the bearer token in authorized requests.
- For complex filtering and searching, refer to the query parameters shown in examples.
- If you encounter issues or need clarification on any endpoint, please contact Xagle support.



