CCUBE API v2 Reference
Overview
Base URLs:
- Production: https://{tenant-subdomain}.inc.construction
Replace {tenant-subdomain} with your tenant subdomain provided by CCUBE.
Conventions
- Content-Type: All endpoints accept and return JSON. Send requests with header
Content-Type: application/json. - Pagination: Index endpoints support
page(default: 1) andper_page(default: 20). If not specified, defaults apply. - Dates: All dates are returned in ISO 8601 format (YYYY-MM-DDTHH:MM:SS.sssZ) with timezone information.
- Error handling: See the Error Handling section below for comprehensive error response formats.
Authentication
The CCUBE API uses token-based authentication. Each request must include an Authorization header with your API token and user email:
Authorization: Token YOUR_API_TOKEN,user_email=YOUR_EMAIL@example.com
Format: Token {your_token},user_email={your_email}
Authentication Errors
If authentication fails, you'll receive a 401 Unauthorized response:
{
"error": "Unauthorized",
"message": "Invalid or missing authentication token"
}
Error Handling
The API uses standard HTTP status codes and returns consistent error response formats.
HTTP Status Codes
| Status Code | Meaning |
|---|---|
| 200 | OK - Request succeeded |
| 201 | Created - Resource was successfully created |
| 204 | No Content - Request succeeded with no response body |
| 400 | Bad Request - Invalid request format or parameters |
| 401 | Unauthorized - Missing or invalid authentication |
| 403 | Forbidden - Authenticated but lacking permissions |
| 404 | Not Found - Requested resource does not exist |
| 422 | Unprocessable Entity - Validation error |
| 429 | Too Many Requests - Rate limit exceeded |
| 500 | Internal Server Error - Server error (contact support) |
| 503 | Service Unavailable - Temporary server issue |
Error Response Format
All error responses follow this consistent structure:
{
"error": "Error type",
"message": "Human-readable error description"
}
Validation Errors (422)
When request data fails validation, you'll receive field-specific errors:
{
"error": "Validation failed",
"message": "The following fields have errors",
"errors": {
"email": ["is required", "must be a valid email"],
"password": ["is too short (minimum is 8 characters)"]
}
}
Rate Limiting
API requests are rate-limited to ensure fair usage. If you exceed the limit, you'll receive a 429 Too Many Requests response:
{
"error": "Rate limit exceeded",
"message": "Too many requests. Please try again in 60 seconds.",
"retry_after": 60
}
Rate limit information is included in response headers:
- X-RateLimit-Limit: Maximum requests per time window
- X-RateLimit-Remaining: Requests remaining in current window
- X-RateLimit-Reset: Unix timestamp when the limit resets
API Access
To obtain API access, please contact us at info@ccubeapp.com.
What to Include in Your Request:
- Your company name and CCUBE tenant subdomain
- Intended use case for the API
- Expected request volume
- Any specific endpoints or features you need
Access Information:
- API access is available for Enterprise plans
- Development sandbox environments available upon request
- Full API documentation access included
- Technical support for integration questions
- We're continuously improving the API based on customer feedback
Users
Get All Users
curl "https://{tenant-subdomain}.inc.construction/api/v2/users" \
-H 'Authorization: Token YourApiToken,user_email=YourEmail'
The above command returns JSON structured like this:
[
{
"id": 1,
"email": "user1@example.com",
"name": "User One",
"role": "admin",
"created_at": "2024-01-01T12:00:00Z",
"updated_at": "2024-01-15T09:30:00Z"
},
{
"id": 2,
"email": "user2@example.com",
"name": "User Two",
"role": "user",
"created_at": "2024-01-02T10:00:00Z",
"updated_at": "2024-01-16T08:45:00Z"
}
]
This endpoint retrieves all Users.
HTTP Request
GET https://{tenant-subdomain}.inc.construction/api/v2/users
Query Parameters
| Parameter | Default | Description |
|---|---|---|
| page | 1 | Page number to retrieve |
| per_page | 20 | Number of results per page |
Get Specific User
This endpoint retrieves a specific User.
HTTP Request
GET https://{tenant-subdomain}.inc.construction/api/v2/users/{id}
URL Parameters
| Parameter | Description |
|---|---|
| ID | The ID of the User to retrieve |
curl "https://{tenant-subdomain}.inc.construction/api/v2/users/1" \
-H 'Authorization: Token YourApiToken,user_email=YourEmail'
The above command returns JSON structured like this:
{
"id": 1,
"email": "user1@example.com",
"given_name": "User",
"family_name": "One"
}
Create a New User
This endpoint to create a new User.
HTTP Request
POST https://{tenant-subdomain}.inc.construction/api/v2/users
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| user[email] | string | yes | User's email address |
| user[password] | string | yes | User's password (min 8 chars) |
| user[password_confirmation] | string | yes | Must match password |
| user[user_type] | string | yes | Either "full" or "limited" |
| user[contact_id] | integer | no | Associated contact ID |
| user[role_ids][] | array[integer] | yes | Array of role IDs |
| user[given_name] | string | no | User's first name |
| user[family_name] | string | no | User's last name |
| user[suspended] | boolean | no | Account status (default: false) |
| user[super_user] | boolean | no | Super user status (default: false) |
| user[paid_tenant] | boolean | no | Paid tenant status |
| user[dunning_tenant] | boolean | no | Dunning status |
| user[trial] | boolean | no | Trial status |
| user[demo] | boolean | no | Demo account status |
| user[paid_user] | boolean | no | Paid user status |
curl 'https://{tenant-subdomain}.inc.construction/api/v2/users' -X POST \
-H 'Authorization: Token YourApiToken,user_email=YourEmail' \
-H 'Content-Type: application/json' \
--data-raw '{
"user": {
"email": "newuser@example.com",
"password": "password123",
"password_confirmation": "password123",
"user_type": "full",
"contact_id": 1234,
"role_ids": [1],
"given_name": "John",
"family_name": "Doe",
"suspended": false,
"super_user": false,
"paid_tenant": false,
"dunning_tenant": false,
"trial": false,
"demo": false,
"paid_user": false
}
}'
The above command returns JSON structured like this:
{
"id": 3,
"email": "newuser@example.com",
"role": "user",
"name": "New User",
"suspended": false,
"current_sign_in_at": "2023-10-01T12:00:00Z",
"last_sign_in_at": "2023-09-30T12:00:00Z",
"given_name": "New",
"family_name": "User"
}
Delete a User
This endpoint deletes a specific User.
HTTP Request
DELETE https://{tenant-subdomain}.inc.construction/api/v2/users/{id}
URL Parameters
| Parameter | Description |
|---|---|
| ID | The ID of the User to delete |
curl 'https://{tenant-subdomain}.inc.construction/api/v2/users/{id}' -X DELETE \
-H 'Authorization: Token YourApiToken,user_email=YourEmail' \
-H 'Content-Type: application/json'
The above command returns JSON structured like this:
{"message": "User deleted successfully"}
Update a User
Endpoint: PUT /api/v2/users/{id}
Description: This endpoint updates an existing user’s details by their ID.
Body Parameters (Update)
| Parameter | Type | Required | Description |
|---|---|---|---|
| user[email] | string | no | New email address |
| user[given_name] | string | no | First name |
| user[family_name] | string | no | Last name |
| user[suspended] | boolean | no | Account status |
| user[user_type] | string | no | Either "full" or "limited" |
| user[contact_id] | integer | no | Associated contact ID |
| user[role_ids][] | array[integer] | no | Role assignments |
CURL Request
curl 'https://{tenant-subdomain}.inc.construction/api/v2/users/{id}' -X PUT \
-H 'Authorization: Token YourApiToken,user_email=YourEmail' \
-H 'Content-Type: application/json' \
--data-raw '{
"user": {
"email": "updated@example.com",
"given_name": "John",
"family_name": "Doe",
"suspended": false,
"user_type": "full",
"contact_id": 1234,
"role_ids": [1]
}
}'
The above command returns JSON structured like this:
{
"id": 1,
"email": "updated@example.com",
"given_name": "John",
"family_name": "Doe"
}
Projects
Get All Projects
curl "https://{tenant-subdomain}.inc.construction/api/v2/projects" \
-H 'Authorization: Token YourToken,user_email=YourEmail'
The above command returns JSON structured like this:
[
{
"id": 1,
"status": "Active",
"stage": { "id": 2, "name": "Active" },
"number": "P-001",
"work_start": "2024-01-01",
"work_end": "2024-12-31",
"follow_up_date": "2024-01-10",
"quote_by": "2024-01-15",
"external_id": null,
"building_id": 10,
"building_section": "North Wing",
"call_notes": null,
"client": { "name": "Client Co", "email": "client@example.com", "phone": "+11234567890" },
"estimator": { "name": "John Doe", "email": "john@example.com", "phone": "+11234567890" },
"workflow_id": 3,
"work_type": { "id": 5, "name": "Roofing" },
"property_type": { "id": 7, "name": "Residential" },
"technology": { "id": 1, "name": "Solar" },
"communication_status": { "id": 1, "name": "Open" },
"custom_fields": [
{ "name": "Priority", "value": "High" },
{ "name": "Source", "value": "Website" }
],
"created_at": "2024-01-01T00:00:00Z",
"updated_at": "2024-01-05T00:00:00Z"
}
]
This endpoint retrieves all Projects.
HTTP Request
GET https://{tenant-subdomain}.inc.construction/api/v2/projects
Query Parameters
| Parameter | Default | Description |
|---|---|---|
| page | 1 | Page number to retrieve |
| per_page | 20 | Number of results per page |
Get Specific Project
This endpoint retrieves a specific Project.
HTTP Request
GET https://{tenant-subdomain}.inc.construction/api/v2/projects/{id}
URL Parameters
| Parameter | Description |
|---|---|
| ID | The ID of the Project to retrieve |
curl "https://{tenant-subdomain}.inc.construction/api/v2/projects/1" \
-H 'Authorization: Token YourApiToken,user_email=YourEmail'
The above command returns JSON structured like this:
{
"id": 1,
"number": "P-001",
"status": "Active",
"stage": { "id": 2, "name": "Active" },
"work_start": "2024-01-01",
"work_end": "2024-12-31",
"follow_up_date": "2024-01-10",
"quote_by": "2024-01-15",
"external_id": null,
"building_id": 10,
"building_section": "North Wing",
"call_notes": null,
"work_type": { "id": 5, "name": "Roofing" },
"property_type": { "id": 7, "name": "Residential" },
"technology": { "id": 1, "name": "Solar" },
"communication_status": { "id": 1, "name": "Open" },
"client": { "name": "Client Co", "email": "client@example.com", "phone": "+11234567890" },
"estimator": { "name": "John Doe", "email": "john@example.com", "phone": "+11234567890" },
"address": { "id": 99, "street": "123 Main St", "city": "City", "state": "ST", "zip": "12345", "country": "USA" },
"custom_fields": [
{ "name": "Priority", "value": "High" },
{ "name": "Source", "value": "Website" }
]
}
Create a New Project
This endpoint creates a new Project.
HTTP Request
POST https://{tenant-subdomain}.inc.construction/api/v2/projects
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| project[name] | string | yes | Project title |
| project[number] | string | yes | Unique project number/reference |
| project[client_id] | integer | yes | Associated client ID |
| project[manager_id] | integer | yes | Project manager ID |
| project[work_start] | date | no | Project start date (YYYY-MM-DD) |
| project[work_end] | date | no | Project end date (YYYY-MM-DD) |
| project[stage_id] | integer | no | Current stage ID (pipeline stage) |
| project[stage_name] | string | no | Stage name (alternative to stage_id — e.g., "call", "quote", "work"). Works across all tenants. |
| project[work_type_id] | integer | no | Work type ID |
| project[property_type_id] | integer | no | Property type ID |
| project[communication_status_id] | integer | no | Communication status ID |
| project[call_notes] | string | no | Call notes or client request details |
curl -X POST "https://{tenant-subdomain}.inc.construction/api/v2/projects" \
-H 'Authorization: Token YourToken,user_email=YourEmail' \
-H "Content-Type: application/json" \
-d '{
"project": {
"name": "Project Name",
"number": "P-001",
"client_id": 1,
"manager_id": 1,
"work_start": "2024-01-01",
"work_end": "2024-12-31",
"stage_name": "quote",
"work_type_id": 5,
"property_type_id": 7,
"communication_status_id": 1,
"call_notes": "Left voicemail; follow-up tomorrow"
}
}'
The above command returns JSON structured like this:
{
"id": 1,
"name": "project 1"
}
Delete a Project
This endpoint deletes a specific Project.
HTTP Request
DELETE https://{tenant-subdomain}.inc.construction/api/v2/projects/{id}
URL Parameters
curl 'https://{tenant-subdomain}.inc.construction/api/v2/projects/{id}' -X DELETE \
-H 'Authorization: Token YourApiToken,user_email=YourEmail' \
-H 'Content-Type: application/json'
The above command returns JSON structured like this:
{
"message": "Project deleted"
}
Update a Project
Endpoint: PUT /api/v2/projects/{id}
Description: This endpoint updates an existing project's details by ID.
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| project[name] | string | no | Project title |
| project[number] | string | no | Project reference |
| project[client_id] | integer | no | Associated client ID |
| project[manager_id] | integer | no | Project manager ID |
| project[work_start] | date | no | Start date (YYYY-MM-DD) |
| project[work_end] | date | no | End date (YYYY-MM-DD) |
| project[stage_id] | integer | no | Stage ID (use GET /api/v2/projects/stages to find IDs) |
| project[stage_name] | string | no | Stage name (alternative to stage_id — e.g., "call", "quote", "work"). Works across all tenants. |
| project[work_type_id] | integer | no | Work type ID |
| project[property_type_id] | integer | no | Property type ID |
| project[communication_status_id] | integer | no | Communication status ID |
| project[call_notes] | string | no | Call notes |
CURL Request
curl 'https://{tenant-subdomain}.inc.construction/api/v2/projects/{id}' -X PUT \
-H 'Authorization: Token YourApiToken,user_email=YourEmail' \
-H 'Content-Type: application/json' \
--data-raw '{
"project": {
"name": "Updated Project",
"number": "P-001",
"client_id": 1,
"manager_id": 1,
"work_start": "2024-02-01",
"work_end": "2024-12-15",
"stage_id": 3,
"work_type_id": 6,
"property_type_id": 8,
"communication_status_id": 2,
"call_notes": "Client confirmed meeting 08/20"
}
}'
The above command returns JSON structured like this:
{
"id": 1,
"name": "project 1",
"number": "23",
"updated_at": "2024-08-28T19:27:32.094-04:00"
}
Get Communication Statuses
This endpoint retrieves all available communication statuses for projects.
HTTP Request
GET https://{tenant-subdomain}.inc.construction/api/v2/projects/communication_statuses
curl "https://{tenant-subdomain}.inc.construction/api/v2/projects/communication_statuses" \
-H 'Authorization: Token YourToken,user_email=YourEmail'
The above command returns JSON structured like this:
[
{
"id": 1,
"name": "New Inquiry",
"value": "new_inquiry",
"position": 1,
"is_first": true,
"is_last": false,
"type": "Projects/CommunicationStatus"
},
{
"id": 2,
"name": "Called Back",
"value": "called_back",
"position": 2,
"is_first": false,
"is_last": false,
"type": "Projects/CommunicationStatus"
},
{
"id": 3,
"name": "Scheduled",
"value": "scheduled",
"position": 3,
"is_first": false,
"is_last": true,
"type": "Projects/CommunicationStatus"
}
]
Get Work Types
This endpoint retrieves all available work types for projects.
HTTP Request
GET https://{tenant-subdomain}.inc.construction/api/v2/projects/work_types
curl "https://{tenant-subdomain}.inc.construction/api/v2/projects/work_types" \
-H 'Authorization: Token YourToken,user_email=YourEmail'
The above command returns JSON structured like this:
[
{
"id": 1,
"name": "Borne Résidentielle",
"value": "borne_residentielle",
"position": 1,
"type": "Projects/WorkType"
},
{
"id": 2,
"name": "Borne Commerciale",
"value": "borne_commerciale",
"position": 2,
"type": "Projects/WorkType"
},
{
"id": 3,
"name": "Multi Logements",
"value": "multi_logements",
"position": 3,
"type": "Projects/WorkType"
},
{
"id": 4,
"name": "Autres travaux électriques",
"value": "autres_travaux_electriques",
"position": 4,
"type": "Projects/WorkType"
}
]
Get Project Stages
This endpoint retrieves all available stages for projects based on the default workflow.
HTTP Request
GET https://{tenant-subdomain}.inc.construction/api/v2/projects/stages
curl "https://{tenant-subdomain}.inc.construction/api/v2/projects/stages" \
-H 'Authorization: Token YourToken,user_email=YourEmail'
The above command returns JSON structured like this:
[
{
"id": 1,
"name": "call",
"workflow_id": 1,
"previous_steps": [],
"next_steps": [2, 6, 8]
},
{
"id": 2,
"name": "quote",
"workflow_id": 1,
"previous_steps": [1],
"next_steps": [3, 6, 7]
},
{
"id": 3,
"name": "work",
"workflow_id": 1,
"previous_steps": [2],
"next_steps": [4, 7]
}
]
Get Project Costs
This endpoint returns the complete estimated and actual cost summary for a project.
HTTP Request
GET https://{tenant-subdomain}.inc.construction/api/v2/projects/{id}/costs
curl "https://{tenant-subdomain}.inc.construction/api/v2/projects/123/costs" \
-H 'Authorization: Token YourToken,user_email=YourEmail'
The above command returns JSON structured like this:
{
"project_cost": {
"id": 123,
"number": "P-2026-0042",
"estimated_hours": { "hours": 120.0, "cost": 7200.0, "price": 10800.0 },
"real_hours": { "hours": 87.5, "cost": 5250.0, "price": 7875.0 },
"estimated_materials": { "cost": 15000.0, "price": 21000.0 },
"real_materials": { "cost": 14250.0, "price": 19950.0 },
"estimated_suppliers": { "cost": 5000.0, "price": 6500.0 },
"real_suppliers": { "cost": 4750.0, "price": 6175.0 },
"estimated_mat_suppliers": { "cost": 20000.0, "price": 27500.0 },
"real_mat_suppliers": { "cost": 19000.0, "price": 26125.0 },
"quoted_price": 42000.0,
"total_amount_in_invoices": 31500.0,
"total_adjustments": 750.0,
"is_closed": false,
"rebate": 0.0,
"estimated_cost": 27200.0,
"real_cost": 24250.0,
"estimated_selling": 38300.0,
"real_selling": 34000.0,
"estimated_profit": 11100.0,
"real_profit": 9750.0,
"estimated_markup": 40.81,
"real_markup": 40.21,
"profit_factor": 1.4,
"profit_graph": { "time": 28, "material": 55, "supplier": 17, "group_by": "cost" },
"estimator": { "id": 8, "name": "Alex Martin", "type": "Users::User" },
"assignees": [
{ "id": 12, "name": "Sam Roy", "type": "Users::User" }
]
}
}
Project Cost Fields
| Field | Description |
|---|---|
estimated_hours, real_hours |
Labor totals. Each object contains hours, cost, and price. |
estimated_materials, real_materials |
Material totals, containing cost and price. |
estimated_suppliers, real_suppliers |
Supplier totals, containing cost and price. |
estimated_mat_suppliers, real_mat_suppliers |
Combined material and supplier totals. |
quoted_price |
Total price quoted to the client. |
total_amount_in_invoices |
Total amount invoiced for the project. |
total_adjustments |
Total project adjustments. |
estimated_cost, real_cost |
Estimated and actual project costs. |
estimated_selling, real_selling |
Estimated and actual selling totals. |
estimated_profit, real_profit |
Estimated and actual profit. |
estimated_markup, real_markup |
Estimated and actual markup percentage. |
profit_graph |
Profit distribution by time, material, and supplier. |
estimator, assignees |
Compact user objects containing id, name, and type. |
Configuration API
The Trades, Rates, Materials, Assemblies, and QuickWorks endpoints expose the configuration data used to build estimates and quotes.
Collection endpoints are paginated. They accept page and per_page; per_page defaults to 25 and is limited to 200.
Confirmed Write Requests
Creating or updating configuration data is a two-step operation. First, preview the exact request body. Then repeat the same request with the confirmation values returned by the preview.
1. Preview
curl -X POST "https://{tenant-subdomain}.inc.construction/api/v2/trades" \
-H 'Authorization: Token YourToken,user_email=YourEmail' \
-H 'Content-Type: application/json' \
-H 'X-Ccube-Mcp-Tool: create_trade' \
-H 'X-Ccube-Mcp-Confirm-Mode: preview' \
-d '{"trade":{"name":"Electricity","value":"electricity"}}'
The preview returns confirmation values:
{
"would_do": "Create trade Electricity",
"confirm_with": "confirmation-token",
"params_digest": "request-body-digest",
"expires_at": "2026-07-16T16:05:00Z"
}
2. Commit
Repeat the same method, URL, and JSON body, replacing the preview header with the returned confirmation headers. Each committed request also requires a unique idempotency key.
curl -X POST "https://{tenant-subdomain}.inc.construction/api/v2/trades" \
-H 'Authorization: Token YourToken,user_email=YourEmail' \
-H 'Content-Type: application/json' \
-H 'X-Ccube-Mcp-Tool: create_trade' \
-H 'X-Ccube-Mcp-Confirm-Token: confirmation-token' \
-H 'X-Ccube-Mcp-Params-Digest: request-body-digest' \
-H 'Idempotency-Key: 2e6cb1e8-b181-4e1e-947b-a5ac28f85d52' \
-d '{"trade":{"name":"Electricity","value":"electricity"}}'
| Resource | Create tool | Update tool |
|---|---|---|
| Trade | create_trade |
update_trade |
| Rate | create_rate |
update_rate |
| Material | create_material |
update_material |
| Assembly | create_assembly |
update_assembly |
| QuickWork | create_quickwork |
update_quickwork |
Trades
List Trades
GET https://{tenant-subdomain}.inc.construction/api/v2/trades
curl "https://{tenant-subdomain}.inc.construction/api/v2/trades?page=1&per_page=25" \
-H 'Authorization: Token YourToken,user_email=YourEmail'
[
{
"id": 7,
"name": "Electricity",
"value": "electricity",
"position": 3,
"type": "Projects::Trade",
"created_at": "2026-07-16T14:00:00Z",
"updated_at": "2026-07-16T14:00:00Z"
}
]
Get a Trade
GET https://{tenant-subdomain}.inc.construction/api/v2/trades/{id}
Create or Update a Trade
POST https://{tenant-subdomain}.inc.construction/api/v2/trades
PATCH https://{tenant-subdomain}.inc.construction/api/v2/trades/{id}
| Field | Required | Description |
|---|---|---|
trade.name |
Yes | Display name. |
trade.value |
Yes | Internal value. |
expected_updated_at |
Update only | Latest updated_at value for conflict detection. |
{
"trade": {
"name": "Electricity",
"value": "electricity"
},
"expected_updated_at": "2026-07-16T14:00:00Z"
}
Rates
List Rates
GET https://{tenant-subdomain}.inc.construction/api/v2/rates
[
{
"id": 15,
"name": "Electrician",
"cost_rate": 45.0,
"sell_rate": 75.0,
"trade_id": 7,
"system_rate": false,
"trade": { "id": 7, "name": "Electricity", "value": "electricity", "position": 3, "type": "Projects::Trade" },
"created_at": "2026-07-16T14:00:00Z",
"updated_at": "2026-07-16T14:00:00Z"
}
]
Get a Rate
GET https://{tenant-subdomain}.inc.construction/api/v2/rates/{id}
Create or Update a Rate
POST https://{tenant-subdomain}.inc.construction/api/v2/rates
PATCH https://{tenant-subdomain}.inc.construction/api/v2/rates/{id}
| Field | Required | Description |
|---|---|---|
rate.name |
Yes | Rate name. |
rate.cost_rate |
Yes | Internal hourly cost. |
rate.sell_rate |
Yes | Hourly selling price. |
rate.trade_id |
No | Associated trade ID. |
{
"rate": {
"name": "Electrician",
"cost_rate": 45.0,
"sell_rate": 75.0,
"trade_id": 7
},
"expected_updated_at": "2026-07-16T14:00:00Z"
}
Materials
List Materials
GET https://{tenant-subdomain}.inc.construction/api/v2/materials
| Parameter | Description |
|---|---|
composite |
Filter by composite status (true or false). |
category |
Filter by category. |
unit_id |
Filter by unit ID. |
supplier_id |
Filter by supplier ID. |
page, per_page |
Pagination. |
curl "https://{tenant-subdomain}.inc.construction/api/v2/materials?category=Electrical&composite=false" \
-H 'Authorization: Token YourToken,user_email=YourEmail'
[
{
"id": 88,
"name": "Electrical cable",
"code": "CAB-12-2",
"note": "12/2 copper cable",
"unit_id": 4,
"quantity": 1.0,
"price": 1.25,
"sell_price": 1.85,
"category": "Electrical",
"supplier_id": 31,
"file_id": null,
"composite": false,
"user_attributes": { "color": "White" },
"unit": { "id": 4, "name": "Foot", "value": "ft", "position": 4, "type": "Projects::Unit" },
"supplier": { "id": 31, "name": "ABC Supply", "type": "Parties::Supplier" },
"created_at": "2026-07-16T14:00:00Z",
"updated_at": "2026-07-16T14:00:00Z"
}
]
Get a Material
GET https://{tenant-subdomain}.inc.construction/api/v2/materials/{id}
Get Material Reference Data
| Endpoint | Response |
|---|---|
GET /api/v2/materials/categories |
Array of category strings. |
GET /api/v2/materials/units |
Array of objects containing id, name, value, position, and type. |
GET /api/v2/materials/user_attributes |
Array of user-defined attribute objects containing id, name, value, position, and type. |
Create or Update a Material
POST https://{tenant-subdomain}.inc.construction/api/v2/materials
PATCH https://{tenant-subdomain}.inc.construction/api/v2/materials/{id}
| Field | Description |
|---|---|
material.name, material.code |
Name and internal code. |
material.unit_id, material.quantity |
Unit and default quantity. |
material.price, material.sell_price |
Cost and selling price. |
material.note, material.category |
Notes and category. |
material.supplier_id, material.file_id |
Optional supplier and file IDs; may be null. |
material.composite |
Whether the material is composite. |
material.user_attributes |
Object containing custom attribute values. |
{
"material": {
"name": "Electrical cable",
"code": "CAB-12-2",
"unit_id": 4,
"quantity": 1.0,
"price": 1.25,
"sell_price": 1.85,
"note": "12/2 copper cable",
"category": "Electrical",
"supplier_id": 31,
"file_id": null,
"composite": false,
"user_attributes": { "color": "White" }
},
"expected_updated_at": "2026-07-16T14:00:00Z"
}
Assemblies
Assemblies combine typed inputs, calculated variables, and outputs. Supported input types are numeric, material, rate, and boolean.
List Assemblies
GET https://{tenant-subdomain}.inc.construction/api/v2/assemblies
Use q to search by name. page and per_page control pagination.
Get an Assembly
GET https://{tenant-subdomain}.inc.construction/api/v2/assemblies/{id}
{
"id": 20,
"name": "Partition wall",
"inputs": [
{
"id": 101,
"name": "Wall length",
"type": "numeric",
"assembly_id": 20,
"material_id": null,
"rate_id": null,
"position": 1,
"is_optional": false,
"default_value": 10,
"search_filter": null
}
],
"variables": [
{ "id": 201, "name": "Stud count", "expression": "ceil(input_101 / 1.33)", "assembly_id": 20 }
],
"outputs": [
{ "id": 301, "assembly_input_id": 101, "assembly_variable_id": 201, "assembly_id": 20, "position": 1 }
],
"created_by_id": 8,
"created_at": "2026-07-16T14:00:00Z",
"updated_at": "2026-07-16T14:00:00Z"
}
Create or Update an Assembly
POST https://{tenant-subdomain}.inc.construction/api/v2/assemblies
PATCH https://{tenant-subdomain}.inc.construction/api/v2/assemblies/{id}
{
"assembly": {
"name": "Partition wall",
"inputs": [
{
"id": 101,
"name": "Wall length",
"type": "numeric",
"assembly_id": 20,
"material_id": null,
"rate_id": null,
"position": 1,
"is_optional": false,
"default_value": 10,
"search_filter": null
}
],
"variables": [
{ "id": 201, "name": "Stud count", "expression": "ceil(input_101 / 1.33)", "assembly_id": 20 }
],
"outputs": [
{ "id": 301, "assembly_input_id": 101, "assembly_variable_id": 201, "assembly_id": 20, "position": 1 }
]
},
"expected_updated_at": "2026-07-16T14:00:00Z"
}
QuickWorks
QuickWorks are reusable quote lines. They may contain material, labor, and supplier line items.
List QuickWorks
GET https://{tenant-subdomain}.inc.construction/api/v2/quickworks
Use q to search by name. page and per_page control pagination.
Get a QuickWork
GET https://{tenant-subdomain}.inc.construction/api/v2/quickworks/{id}
{
"id": 42,
"name": "Install electrical outlet",
"quote_line": {
"id": 900,
"description": "Supply and install one outlet",
"description_text": "Supply and install one outlet",
"name": "Electrical outlet",
"total": 245.0,
"position": 1,
"quote_id": null,
"unit_id": 1,
"quantity": 1.0,
"service_id": null,
"materials": [
{
"id": 901,
"material_id": 88,
"quantity": 1.0,
"unit_cost": 35.0,
"sell_price": 50.0,
"total": 50.0,
"description": "Outlet and box",
"is_sub": false,
"material": { "id": 88, "name": "Outlet kit", "code": "OUT-01" }
}
],
"time_lines": [
{
"id": 902,
"rate_id": 15,
"quantity": 2.0,
"unit_price": 75.0,
"unit_cost": 45.0,
"total": 150.0,
"description": "Installation",
"rate": { "id": 15, "name": "Electrician", "cost_rate": 45.0, "sell_rate": 75.0 }
}
],
"sub_lines": [
{
"id": 903,
"supplier_id": 31,
"description": "Special inspection",
"cost": 30.0,
"price": 45.0,
"generate_po": true,
"supplier": { "id": 31, "name": "ABC Supply", "type": "Parties::Supplier" }
}
]
},
"created_at": "2026-07-16T14:00:00Z",
"updated_at": "2026-07-16T14:00:00Z"
}
QuickWork Response Fields
| Object | Fields |
|---|---|
| QuickWork | id, name, created_at, updated_at. |
| Quote line | id, description, description_text, name, total, position, quote_id, unit_id, quantity, service_id, created_at, updated_at. |
| Material line | id, quote_id, quote_line_id, material_id, created_by_id, quantity, unit_cost, sell_price, total, description, description_text, is_sub, top_composite_line_id, expected_quantity, created_at, updated_at, material. |
| Time line | id, quote_id, quote_line_id, rate_id, created_by_id, quantity, unit_price, unit_cost, total, description, description_text, top_composite_line_id, created_at, updated_at, rate. |
| Supplier line | id, quote_id, quote_line_id, created_by_id, supplier_id, description, description_text, cost, price, generate_po, created_at, updated_at, supplier. |
Composite material lines include their nested material children and any associated composite time lines.
Create or Update a QuickWork
POST https://{tenant-subdomain}.inc.construction/api/v2/quickworks
PATCH https://{tenant-subdomain}.inc.construction/api/v2/quickworks/{id}
{
"quickwork": {
"name": "Install electrical outlet",
"quote_line": {
"name": "Electrical outlet",
"description": "Supply and install one outlet",
"unit_id": 1,
"quantity": 1
},
"materials": [
{
"material_id": 88,
"quantity": 1,
"unit_cost": 35,
"sell_price": 50,
"total": 50,
"description": "Outlet and box",
"is_sub": false
}
],
"time_lines": [
{
"rate_id": 15,
"quantity": 2,
"unit_price": 75,
"unit_cost": 45,
"description": "Installation"
}
],
"sub_lines": [
{
"supplier_id": 31,
"description": "Special inspection",
"cost": 30,
"price": 45,
"generate_po": true
}
]
},
"expected_updated_at": "2026-07-16T14:00:00Z"
}
Reports
Get Time Entries Report
This endpoint returns time entries grouped by employee, including totals for every hour category.
HTTP Request
GET https://{tenant-subdomain}.inc.construction/api/v2/reports/time_entries
| Parameter | Description |
|---|---|
from |
Start date. Alias for created_date_start_of_day_gteq. |
to |
End date. Alias for created_date_end_of_day_lteq. |
employee_id_eq |
Filter by employee ID. |
work_order_project_number_cont |
Filter by a full or partial project number. |
rate_trade_id_in[] |
Filter by one or more trade IDs. |
rate_id_in[] |
Filter by one or more rate IDs. |
curl "https://{tenant-subdomain}.inc.construction/api/v2/reports/time_entries?from=2026-07-01&to=2026-07-15&employee_id_eq=12&rate_id_in[]=15" \
-H 'Authorization: Token YourToken,user_email=YourEmail'
{
"report_time_entries": [
{
"id": 12,
"employee_name": "Sam Roy",
"time_entries": [
{
"id": 4001,
"created_date": "2026-07-15",
"start_time": "08:00",
"end_time": "16:30",
"break_hours": 0.5,
"travel_hours": 1.0,
"other_hours": 0.0,
"work_hours": 7.0,
"total_hours": 8.0,
"absence": false,
"in_zone": true,
"in_zone_end": true,
"notes": "Electrical rough-in",
"intervention_id": 502,
"rate": { "id": 15, "name": "Electrician" },
"project_number": "P-2026-0042",
"work_order_number": "WO-1048",
"property_type": "Commercial",
"building": "100 Main Street",
"verified": true,
"is_modified": false,
"in_progress": false
}
],
"total_break_hours": 0.5,
"total_travel_hours": 1.0,
"total_other_hours": 0.0,
"total_work_hours": 7.0,
"total_total_hours": 8.0
}
]
}
Quotes
Get Quote Stages
This endpoint retrieves all available stages for quotes based on the default workflow.
HTTP Request
GET https://{tenant-subdomain}.inc.construction/api/v2/quotes/stages
curl "https://{tenant-subdomain}.inc.construction/api/v2/quotes/stages" \
-H 'Authorization: Token YourToken,user_email=YourEmail'
The above command returns JSON structured like this:
[
{
"id": 9,
"name": "opened",
"workflow_id": 2,
"previous_steps": [],
"next_steps": [10, 14]
},
{
"id": 10,
"name": "presented",
"workflow_id": 2,
"previous_steps": [9],
"next_steps": [11, 12, 13]
}
]
Get All Quotes
GET /api/v2/quotes
shell
curl "https://{tenant-subdomain}.inc.construction/api/v2/quotes" \
-H 'Authorization: Token YourToken,user_email=YourEmail'
The above command returns JSON structured like this:
[
{
"id": 1,
"project_id": 12,
"client_id": 34,
"number": "Q-001",
"quoted_total": 3500.0,
"created_at": "2024-01-01T00:00:00Z",
"updated_at": "2024-01-02T00:00:00Z",
"status": "Draft",
"subtotal": 3200.0,
"external_id": null,
"client": { "id": 34, "name": "Client Co", "email": "client@example.com", "phone": "+11234567890" }
}
]
This endpoint retrieves all Quotes.
HTTP Request
GET https://{tenant-subdomain}.inc.construction/api/v2/quotes
Query Parameters
| Parameter | Default | Description |
|---|---|---|
| page | 1 | Page number to retrieve |
| per_page | 20 | Number of results per page |
Get Specific Quote
This endpoint retrieves a specific Quote.
HTTP Request
GET https://{tenant-subdomain}.inc.construction/api/v2/quotes/{id}
URL Parameters
| Parameter | Description |
|---|---|
| ID | The ID of the Quote to retrieve |
curl "https://{tenant-subdomain}.inc.construction/api/v2/quotes/1" \
-H 'Authorization: Token YourApiToken,user_email=YourEmail'
The above command returns JSON structured like this:
{
"id": 4,
"number": "Q-1002",
"status": "Approved",
"external_id": null,
"client": { "name": "Client Co", "email": "client@example.com", "phone": "+11234567890" }
}
Create a New Quote
This endpoint to create a new Quote.
HTTP Request
POST https://{tenant-subdomain}.inc.construction/api/v2/quotes
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| quote[number] | string | yes | Quote number/reference |
| quote[description] | string | no | Quote details/notes |
| quote[project_id] | integer | yes | Associated project ID |
| quote[department_id] | integer | yes | Department ID |
| quote[terms] | string | no | Payment terms |
| quote[title] | string | no | Quote title |
| quote[stage_id] | integer | no | Stage ID (use GET /api/v2/quotes/stages to find IDs) |
| quote[stage_name] | string | no | Stage name (alternative to stage_id — e.g., "opened", "presented", "won", "lost"). Works across all tenants. |
curl -X POST "https://{tenant-subdomain}.inc.construction/api/v2/quotes" \
-H 'Authorization: Token YourToken,user_email=YourEmail' \
-H "Content-Type: application/json" \
-d '{
"quote": {
"number": "Q-001",
"description": "Description",
"project_id": 1,
"department_id": 1,
"terms": "Payment terms",
"title": "Quote title",
"stage_name": "opened"
}
}'
The above command returns JSON structured like this:
{
"id": 36,
"number": "Q-1033",
"status": "Draft",
"external_id": null
}
Delete a Quote
This endpoint deletes a specific Quote.
HTTP Request
DELETE https://{tenant-subdomain}.inc.construction/api/v2/quotes/{id}
URL Parameters
curl 'https://{tenant-subdomain}.inc.construction/api/v2/quotes/{id}' -X DELETE \
-H 'Authorization: Token YourApiToken,user_email=YourEmail' \
-H 'Content-Type: application/json'
The above command returns JSON structured like this:
{"message": "Quote deleted successfully"}
Update a Quote
Endpoint: PUT /api/v2/quotes/{id}
Description: This endpoint updates an existing quote's details by ID.
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| quote[number] | string | no | Quote number/reference |
| quote[description] | string | no | Quote description |
| quote[project_id] | integer | no | Associated project ID |
| quote[department_id] | integer | no | Department ID |
| quote[quote_type] | string | no | Quote type |
| quote[stage_id] | integer | no | Stage ID (use GET /api/v2/quotes/stages to find IDs) |
| quote[stage_name] | string | no | Stage name (alternative to stage_id — e.g., "opened", "presented", "won", "lost"). Works across all tenants. |
CURL Request
curl 'https://{tenant-subdomain}.inc.construction/api/v2/quotes/{id}' -X PUT \
-H 'Authorization: Token YourApiToken,user_email=YourEmail' \
-H 'Content-Type: application/json' \
--data-raw '{
"quote": {
"number": "Q-001",
"description": "Updated description",
"project_id": 1,
"department_id": 1,
"quote_type": "standard",
"stage_name": "won"
}
}'
The above command returns JSON structured like this:
{
"id": 164,
"name": "1154-S1157",
"description": "roof fix"
}
Get Sent Quotes
This endpoint retrieves all quotes that have been presented to clients.
HTTP Request
GET https://{tenant-subdomain}.inc.construction/api/v2/quotes/sent
curl "https://{tenant-subdomain}.inc.construction/api/v2/quotes/sent" \
-H 'Authorization: Token YourToken,user_email=YourEmail'
The above command returns JSON structured like this:
[
{
"id": 1,
"number": "Q-001",
"status": "Presented",
"external_id": null,
"client": { "name": "Client Co", "email": "client@example.com", "phone": "+11234567890" }
}
]
Get Won Quotes
This endpoint retrieves all quotes that have been won.
HTTP Request
GET https://{tenant-subdomain}.inc.construction/api/v2/quotes/won
curl "https://{tenant-subdomain}.inc.construction/api/v2/quotes/won" \
-H 'Authorization: Token YourToken,user_email=YourEmail'
The above command returns JSON structured like this:
[
{
"id": 2,
"number": "Q-002",
"status": "Won",
"external_id": null,
"client": { "name": "Client Co", "email": "client@example.com", "phone": "+11234567890" }
}
]
Get Lost Quotes
This endpoint retrieves all quotes that have been lost.
HTTP Request
GET https://{tenant-subdomain}.inc.construction/api/v2/quotes/lost
curl "https://{tenant-subdomain}.inc.construction/api/v2/quotes/lost" \
-H 'Authorization: Token YourToken,user_email=YourEmail'
The above command returns JSON structured like this:
[
{
"id": 3,
"number": "Q-003",
"status": "Lost",
"external_id": null,
"client": { "name": "Client Co", "email": "client@example.com", "phone": "+11234567890" }
}
]
Get Won Quotes Pending Deposit
This endpoint retrieves all quotes that have been won but are pending deposit payment.
HTTP Request
GET https://{tenant-subdomain}.inc.construction/api/v2/quotes/won_pending_deposit
curl "https://{tenant-subdomain}.inc.construction/api/v2/quotes/won_pending_deposit" \
-H 'Authorization: Token YourToken,user_email=YourEmail'
The above command returns JSON structured like this:
[
{
"id": 4,
"number": "Q-004",
"status": "Won - Pending Deposit",
"external_id": null,
"client": { "name": "Client Co", "email": "client@example.com", "phone": "+11234567890" }
}
]
Invoices
Get Invoice Stages
This endpoint retrieves all available stages for invoices based on the default workflow.
HTTP Request
GET https://{tenant-subdomain}.inc.construction/api/v2/invoices/stages
curl "https://{tenant-subdomain}.inc.construction/api/v2/invoices/stages" \
-H 'Authorization: Token YourToken,user_email=YourEmail'
The above command returns JSON structured like this:
[
{
"id": 1,
"name": "draft",
"workflow_id": 5,
"previous_steps": [],
"next_steps": [2, 5]
},
{
"id": 2,
"name": "sent",
"workflow_id": 5,
"previous_steps": [1],
"next_steps": [3, 4]
}
]
Get All Invoices
GET /api/v2/invoices
curl "https://{tenant-subdomain}.inc.construction/api/v2/invoices" \
-H 'Authorization: Token YourToken,user_email=YourEmail'
The above command returns JSON structured like this:
[
{
"id": 1,
"number": "INV-1000",
"full_number": "2024-INV-1000",
"issue_date": "2024-01-01",
"due_date": "2024-01-31",
"project_id": 1,
"project_name": "Sample Project",
"client_id": 1,
"status": "draft",
"status_display": "draft",
"external_id": null,
"subtotal": 1000.0,
"tax1_rate": "5.0",
"tax2_rate": "9.975",
"tax1_amount": "50.0",
"tax2_amount": "99.75",
"total": 1149.75,
"paid_total": 800.0,
"balance": 349.75,
"created_at": "2024-01-01T00:00:00Z",
"updated_at": "2024-01-01T00:00:00Z",
"paid_on": null,
"is_credit": false,
"combined_lines_names": "Materials, Labor",
"combined_lines_kinds": "Material, Labor",
"client": {
"name": "Example Client",
"email": "client@example.com",
"phone": "+1234567890"
},
"lines": [
{
"id": 1,
"description": "Roof replacement - materials",
"full_description": "Material: Shingles - Roof replacement - materials",
"quantity": 20,
"unit_price": 25.0,
"total": 500.0,
"position": 1,
"account": { "id": 10, "name": "Materials" }
}
]
}
]
This endpoint retrieves all Invoices.
HTTP Request
GET https://{tenant-subdomain}.inc.construction/api/v2/invoices
### Query Parameters
| Parameter | Default | Description |
|---|---|---|
| page | 1 | Page number to retrieve |
| per_page | 100 | Number of results per page |
Get Specific Invoice
This endpoint retrieves a specific Invoice.
HTTP Request
GET https://{tenant-subdomain}.inc.construction/api/v2/invoices/{id}
URL Parameters
| Parameter | Description |
|---|---|
| ID | The ID of the Invoice to retrieve |
curl "https://{tenant-subdomain}.inc.construction/api/v2/invoices/1" \
-H 'Authorization: Token YourApiToken,user_email=YourEmail'
The above command returns JSON structured like this:
{
"id": 1,
"number": "INV-1000",
"full_number": "2024-INV-1000",
"issue_date": "2024-01-01",
"due_date": "2024-01-31",
"project_id": 1,
"project_name": "Sample Project",
"client_id": 1,
"status": "sent",
"status_display": "sent",
"external_id": null,
"subtotal": 1000.0,
"tax1_rate": "5.0",
"tax2_rate": "9.975",
"tax1_amount": "50.0",
"tax2_amount": "99.75",
"total": 1149.75,
"paid_total": 1000.0,
"balance": 149.75,
"subject": "Invoice subject",
"description": "Invoice description",
"terms": "Payable 30 jours",
"created_at": "2024-01-01T00:00:00Z",
"updated_at": "2024-01-01T00:00:00Z",
"paid_on": "2024-01-15",
"estimator_id": null,
"is_credit": false,
"combined_lines_names": "Shingles - 3-tab, Labor",
"combined_lines_kinds": "Material, Labor",
"combined_lines_names_safe": "Shingles - 3-tab | Labor",
"combined_lines_kinds_safe": "Material | Labor",
"combined_lines_descriptions": "Roof replacement - materials, Labor",
"combined_lines_descriptions_safe": "Roof replacement - materials | Labor",
"client": { "name": "Client Co", "email": "client@example.com", "phone": "+11234567890" },
"lines": [
{
"id": 11,
"description": "Roof replacement - materials",
"description_safe": "Roof replacement - materials",
"full_description": "Material: Shingles - 3-tab - Roof replacement - materials",
"full_description_safe": "Material: Shingles - 3-tab - Roof replacement - materials",
"quantity": 20,
"unit_price": 25.0,
"total": 500.0,
"position": 1,
"material": { "id": 5, "name": "Shingles - 3-tab" },
"account": { "id": 10, "name": "Materials" }
},
{
"id": 12,
"description": "Labor",
"description_safe": "Labor",
"full_description": "Rate: Standard Rate - Labor",
"full_description_safe": "Rate: Standard Rate - Labor",
"quantity": 10,
"unit_price": 50.0,
"total": 500.0,
"position": 2,
"rate": { "id": 1, "name": "Standard Rate" },
"account": { "id": 20, "name": "Labor" }
}
]
}
Notes:
- status is the internal stage name stored in the database (e.g., draft, sent, partial, paid, canceled).
- status_display is the human-readable display name shown in the app (e.g., draft, sent, partial, paid, canceled).
- paid_total represents the total of all payments applied to this invoice (no individual payments listed here).
- subtotal, tax1_amount, tax2_amount, and total are numeric string amounts; tax1_rate and tax2_rate are percentage strings (e.g., "5.0" for 5%).
- client_id corresponds to the Party (client/contact) associated to the invoice; some UIs may label this as Party ID.
- combined_lines_names, combined_lines_kinds, and combined_lines_descriptions provide comma-separated summaries of all line items.
- Fields ending in _safe use pipe (|) separators instead of commas, suitable for CSV/Zapier integrations.
- lines[].material, lines[].rate, and lines[].account are objects with id and name properties when present, or omitted when not applicable.
- Balance = total - paid_total.
Line item fields returned in lines[]: - id: integer - description: string - description_safe: string (commas replaced with pipes) - full_description: string (includes material/rate name prefix) - full_description_safe: string (commas replaced with pipes) - quantity: number - unit_price: number - total: number (quantity x unit_price) - position: integer (ordering) - material: object { id, name } (present only when line has a material) - rate: object { id, name } (present only when line has a rate) - account: object { id, name } (present only when line has an account)
Create a New Invoice
This endpoint creates a new Invoice.
HTTP Request
POST https://{tenant-subdomain}.inc.construction/api/v2/invoices
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| invoice[project_id] | integer | yes | Project ID |
| invoice[client_id] | integer | no | Client ID (aka Party ID). If omitted, uses the project's client |
| invoice[number] | string | no | Invoice number/reference. Auto-assigned if omitted |
| invoice[subject] | string | no | Invoice subject/title |
| invoice[description] | string | no | Invoice description |
| invoice[issue_date] | date | yes | Issue date (YYYY-MM-DD). Due date auto-set to +30 days if omitted |
| invoice[due_date] | date | no | Due date (YYYY-MM-DD) |
| invoice[external_id] | string | no | External system invoice ID (e.g., QuickBooks invoice ID) for integration |
| invoice[lines_attributes][] | array[object] | no | Line items to create with the invoice. Each item supports: description (string, required), quantity (number, required), unit_price (number, required), position (integer, optional), material_id (integer, optional), rate_id (integer, optional), account_id (integer, optional) |
| invoice[stage_id] | integer | no | Stage ID (use GET /api/v2/invoices/stages to find IDs) |
| invoice[stage_name] | string | no | Stage name (alternative to stage_id — e.g., "draft", "sent", "paid"). Works across all tenants. |
curl -X POST "https://{tenant-subdomain}.inc.construction/api/v2/invoices" \
-H 'Authorization: Token YourToken,user_email=YourEmail' \
-H "Content-Type: application/json" \
-d '{
"invoice": {
"project_id": 1,
"subject": "Invoice subject",
"description": "Details",
"issue_date": "2024-01-01",
"due_date": "2024-01-31",
"stage_name": "draft",
"lines_attributes": [
{"description": "Materials", "quantity": 20, "unit_price": 25.0, "material_id": 5, "position": 1},
{"description": "Labor", "quantity": 10, "unit_price": 50.0, "rate_id": 1, "position": 2}
]
}
}'
The above command returns JSON structured like this:
{
"id": 67,
"number": "1011",
"full_number": "2024-1011",
"issue_date": "2024-01-01",
"due_date": "2024-01-31",
"project_id": 1,
"project_name": "Sample Project",
"client_id": 1,
"status": "draft",
"status_display": "draft",
"external_id": null,
"subtotal": 1000.0,
"tax1_rate": "5.0",
"tax2_rate": "9.975",
"tax1_amount": "50.0",
"tax2_amount": "99.75",
"total": 1149.75,
"paid_total": 0.0,
"balance": 1149.75,
"subject": "Invoice subject",
"description": "Details",
"terms": null,
"created_at": "2024-01-01T00:00:00Z",
"updated_at": "2024-01-01T00:00:00Z",
"paid_on": null,
"estimator_id": null,
"is_credit": false,
"client": { "name": "Client Co", "email": "client@example.com", "phone": "+11234567890" },
"lines": [
{
"id": 1,
"description": "Materials",
"description_safe": "Materials",
"full_description": "Material: Shingles - 3-tab - Materials",
"full_description_safe": "Material: Shingles - 3-tab - Materials",
"quantity": 20,
"unit_price": 25.0,
"total": 500.0,
"position": 1,
"material": { "id": 5, "name": "Shingles - 3-tab" },
"account": { "id": 10, "name": "Materials" }
},
{
"id": 2,
"description": "Labor",
"description_safe": "Labor",
"full_description": "Rate: Standard Rate - Labor",
"full_description_safe": "Rate: Standard Rate - Labor",
"quantity": 10,
"unit_price": 50.0,
"total": 500.0,
"position": 2,
"rate": { "id": 1, "name": "Standard Rate" },
"account": { "id": 20, "name": "Labor" }
}
]
}
Delete an Invoice
This endpoint deletes a specific Invoice.
HTTP Request
DELETE https://{tenant-subdomain}.inc.construction/api/v2/invoices/{id}
URL Parameters
| Parameter | Description |
|---|---|
| id | The ID of the invoice to delete |
Response Status Codes
| Status Code | Meaning |
|---|---|
| 200 | Successfully deleted - Invoice was removed |
| 404 | Not Found - Invoice ID does not exist |
| 422 | Unprocessable Entity - Invoice cannot be deleted (e.g., already paid) |
| 401 | Unauthorized - Invalid or missing authentication token |
| 403 | Forbidden - User lacks permission to delete invoices |
curl 'https://{tenant-subdomain}.inc.construction/api/v2/invoices/{id}' -X DELETE \
-H 'Authorization: Token YourApiToken,user_email=YourEmail' \
-H 'Content-Type: application/json'
Success response (200):
{
"message": "Invoice deleted"
}
Error response (422) - Cannot delete paid invoice:
{
"error": "Cannot delete invoice",
"message": "This invoice has been paid and cannot be deleted. Please create a credit note instead."
}
Error response (404) - Invoice not found:
{
"error": "Not found",
"message": "Invoice with ID {id} does not exist"
}
Update an Invoice
Endpoint: PUT /api/v2/invoices/{id}
Description: This endpoint updates an existing invoice's details by ID.
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| invoice[number] | string | no | Invoice number/reference |
| invoice[subject] | string | no | Invoice subject/title |
| invoice[description] | string | no | Invoice description |
| invoice[client_id] | integer | no | Client ID |
| invoice[project_id] | integer | no | Project ID |
| invoice[issue_date] | date | no | Issue date (YYYY-MM-DD) |
| invoice[due_date] | date | no | Due date (YYYY-MM-DD) |
| invoice[external_id] | string | no | External system invoice ID (e.g., QuickBooks invoice ID) |
| invoice[stage_id] | integer | no | Stage ID (use GET /api/v2/invoices/stages to find IDs) |
| invoice[stage_name] | string | no | Stage name (alternative to stage_id — e.g., "draft", "sent", "paid"). Works across all tenants. |
CURL Request
curl 'https://{tenant-subdomain}.inc.construction/api/v2/invoices/{id}' -X PUT \
-H 'Authorization: Token YourApiToken,user_email=YourEmail' \
-H 'Content-Type: application/json' \
--data-raw '{
"invoice": {
"number": "INV-001",
"subject": "Updated subject",
"client_id": 1,
"project_id": 1,
"external_id": "QBO-INV-67890",
"stage_name": "sent"
}
}'
The above command returns JSON structured like this:
{
"id": 6,
"number": "INV-001",
"subject": "Updated subject",
"external_id": "QBO-INV-67890",
"status": "draft",
"status_display": "draft"
}
Get Sent Invoices
This endpoint retrieves all invoices that have been sent to clients.
HTTP Request
GET https://{tenant-subdomain}.inc.construction/api/v2/invoices/sent
curl "https://{tenant-subdomain}.inc.construction/api/v2/invoices/sent" \
-H 'Authorization: Token YourToken,user_email=YourEmail'
The above command returns JSON structured like this:
[
{
"id": 1,
"number": "INV-1000",
"full_number": "2024-INV-1000",
"issue_date": "2024-01-01",
"due_date": "2024-01-31",
"project_id": 1,
"project_name": "Sample Project",
"client_id": 1,
"status": "sent",
"status_display": "sent",
"subtotal": 1000.0,
"total": 1000.0,
"paid_total": 0.0,
"balance": 1000.0,
"client": { "name": "Client Co", "email": "client@example.com", "phone": "+11234567890" }
}
]
Get Canceled Invoices
This endpoint retrieves all invoices that were canceled.
HTTP Request
GET https://{tenant-subdomain}.inc.construction/api/v2/invoices/canceled
curl "https://{tenant-subdomain}.inc.construction/api/v2/invoices/canceled" \
-H 'Authorization: Token YourToken,user_email=YourEmail'
The above command returns JSON structured like this:
[
{
"id": 1,
"number": "INV-1000",
"full_number": "2024-INV-1000",
"issue_date": "2024-01-01",
"due_date": "2024-01-31",
"project_id": 1,
"project_name": "Sample Project",
"client_id": 1,
"status": "canceled",
"status_display": "canceled",
"subtotal": 1000.0,
"total": 1000.0,
"paid_total": 0.0,
"balance": 1000.0,
"client": { "name": "Client Co", "email": "client@example.com", "phone": "+11234567890" }
}
]
Payments
Get All Payments
This endpoint retrieves all payments across all invoices.
HTTP Request
GET https://{tenant-subdomain}.inc.construction/api/v2/payments
Query Parameters
| Parameter | Default | Description |
|---|---|---|
| page | 1 | Page number to retrieve |
| per_page | 100 | Number of results per page |
curl "https://{tenant-subdomain}.inc.construction/api/v2/payments" \
-H 'Authorization: Token YourToken,user_email=YourEmail'
The above command returns JSON structured like this:
[
{
"id": 1,
"client_id": 34,
"total": 100.0,
"note": "Payment received",
"paid_on": "2024-01-15",
"external_id": null,
"created_at": "2024-01-15T10:00:00Z",
"updated_at": "2024-01-15T10:00:00Z",
"client": {
"id": 34,
"name": "Client Co"
},
"invoices": [
{
"id": 774,
"number": "INV-1000"
}
]
}
]
Get Specific Payment
This endpoint retrieves a specific payment by ID.
HTTP Request
GET https://{tenant-subdomain}.inc.construction/api/v2/payments/{id}
URL Parameters
| Parameter | Description |
|---|---|
| id | The ID of the payment to retrieve |
curl "https://{tenant-subdomain}.inc.construction/api/v2/payments/1" \
-H 'Authorization: Token YourToken,user_email=YourEmail'
The above command returns JSON structured like this:
{
"id": 1,
"client_id": 34,
"total": 100.0,
"note": "Payment received from QuickBooks",
"paid_on": "2024-01-15",
"external_id": "QBO-PMT-12345",
"created_at": "2024-01-15T10:00:00Z",
"updated_at": "2024-01-15T10:00:00Z",
"client": {
"id": 34,
"name": "Client Co"
},
"invoices": [
{
"id": 774,
"number": "INV-1000",
"total": 1000.0
}
]
}
Create a Payment
This endpoint creates a new payment and applies it to one or more invoices.
HTTP Request
POST https://{tenant-subdomain}.inc.construction/api/v2/payments
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| payment[client_id] | integer | yes | Client ID (the party receiving the payment) |
| payment[total] | decimal | yes | Total payment amount |
| payment[paid_on] | date | yes | Date payment was received (YYYY-MM-DD) |
| payment[note] | string | no | Payment notes or description |
| payment[external_id] | string | no | External system payment ID (e.g., QuickBooks payment ID) |
| payment[payment_invoices_attributes][] | array[object] | no | Array of invoice allocations |
| payment[payment_invoices_attributes][][invoice_id] | integer | conditional | Invoice ID to apply payment to |
| payment[payment_invoices_attributes][][total] | decimal | conditional | Amount to apply to this invoice |
curl -X POST "https://{tenant-subdomain}.inc.construction/api/v2/payments" \
-H 'Authorization: Token YourToken,user_email=YourEmail' \
-H "Content-Type: application/json" \
-d '{
"payment": {
"client_id": 34,
"total": 100.0,
"paid_on": "2024-01-15",
"note": "Payment received from QuickBooks",
"external_id": "QBO-PMT-12345",
"payment_invoices_attributes": [
{
"invoice_id": 774,
"total": 100.0
}
]
}
}'
The above command returns JSON structured like this:
{
"id": 1,
"client_id": 34,
"total": 100.0,
"note": "Payment received from QuickBooks",
"paid_on": "2024-01-15",
"external_id": "QBO-PMT-12345",
"created_at": "2024-01-15T10:00:00Z",
"updated_at": "2024-01-15T10:00:00Z"
}
Update a Payment
Endpoint: PUT /api/v2/payments/{id}
Description: This endpoint updates an existing payment's details by ID.
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| payment[total] | decimal | no | Updated total payment amount |
| payment[paid_on] | date | no | Updated payment date (YYYY-MM-DD) |
| payment[note] | string | no | Payment notes |
| payment[external_id] | string | no | External system payment ID |
| payment[payment_invoices_attributes][] | array[object] | no | Update invoice allocations |
CURL Request
curl 'https://{tenant-subdomain}.inc.construction/api/v2/payments/{id}' -X PUT \
-H 'Authorization: Token YourApiToken,user_email=YourEmail' \
-H 'Content-Type: application/json' \
--data-raw '{
"payment": {
"total": 150.0,
"paid_on": "2024-01-16",
"external_id": "QBO-PMT-12345-UPDATED",
"note": "Payment amount updated"
}
}'
The above command returns JSON structured like this:
{
"id": 1,
"client_id": 34,
"total": 150.0,
"note": "Payment amount updated",
"paid_on": "2024-01-16",
"external_id": "QBO-PMT-12345-UPDATED",
"updated_at": "2024-01-16T09:30:00Z"
}
Delete a Payment
This endpoint deletes (soft deletes) a specific payment.
HTTP Request
DELETE https://{tenant-subdomain}.inc.construction/api/v2/payments/{id}
URL Parameters
| Parameter | Description |
|---|---|
| id | The ID of the payment to delete |
curl 'https://{tenant-subdomain}.inc.construction/api/v2/payments/{id}' -X DELETE \
-H 'Authorization: Token YourApiToken,user_email=YourEmail' \
-H 'Content-Type: application/json'
The above command returns JSON structured like this:
{
"message": "Payment deleted"
}
Purchase Orders
Get Purchase Order Stages
This endpoint retrieves all available stages for purchase orders based on the default workflow.
HTTP Request
GET https://{tenant-subdomain}.inc.construction/api/v2/purchase_orders/stages
curl "https://{tenant-subdomain}.inc.construction/api/v2/purchase_orders/stages" \
-H 'Authorization: Token YourToken,user_email=YourEmail'
The above command returns JSON structured like this:
[
{
"id": 22,
"name": "opened",
"workflow_id": 4,
"previous_steps": [],
"next_steps": [23]
},
{
"id": 23,
"name": "sent",
"workflow_id": 4,
"previous_steps": [22],
"next_steps": [24, 25]
},
{
"id": 24,
"name": "partial",
"workflow_id": 4,
"previous_steps": [23],
"next_steps": [25]
}
]
Get All Purchase Orders
HTTP Request
GET https://{tenant-subdomain}.inc.construction/api/v2/purchase_orders
This endpoint retrieves all Purchase Orders.
Query Parameters
| Parameter | Default | Description |
|---|---|---|
| page | 1 | Page number to retrieve |
| per_page | 20 | Number of results per page |
curl "https://{tenant-subdomain}.inc.construction/api/v2/purchase_orders" \
-H 'Authorization: Token YourApiToken,user_email=YourEmail'
The above command returns JSON structured like this:
[
{
"id": 1,
"project_id": 22,
"number": "PO-1001",
"description": "Complete kitchen renovation.",
"expected_start": "2024-02-01",
"created_at": "2024-01-10T00:00:00Z",
"updated_at": "2024-01-12T00:00:00Z",
"external_id": null
},
{
"id": 2,
"project_id": 23,
"number": "PO-1002",
"description": "Bathroom remodeling.",
"expected_start": null,
"created_at": "2024-01-11T00:00:00Z",
"updated_at": "2024-01-13T00:00:00Z",
"external_id": null
}
]
Get Specific Purchase Order
This endpoint retrieves a specific Purchase Order.
HTTP Request
GET https://{tenant-subdomain}.inc.construction/api/v2/purchase_orders/ID
URL Parameters
| Parameter | Description |
|---|---|
| ID | The ID of the Purchase order to retrieve |
curl "https://{tenant-subdomain}.inc.construction/api/v2/purchase_orders/1" \
-H 'Authorization: Token YourApiToken,user_email=YourEmail'
The above command returns JSON structured like this:
{
"id": 1,
"number": "PO-1011",
"external_id": null
}
Create a New Purchase Order
This endpoint to create a new Purchase Order.
HTTP Request
POST https://{tenant-subdomain}.inc.construction/api/v2/purchase_orders
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| purchase_order[number] | string | yes | Purchase order number/reference |
| purchase_order[description] | string | no | Purchase order description |
| purchase_order[department_id] | integer | yes | Department ID |
| purchase_order[stage_id] | integer | no | Stage ID (use GET /api/v2/purchase_orders/stages to find IDs) |
| purchase_order[stage_name] | string | no | Stage name (alternative to stage_id — e.g., "opened", "sent", "partial", "receipt"). Works across all tenants. |
curl 'https://{tenant-subdomain}.inc.construction/api/v2/purchase_orders' -X POST \
-H 'Authorization: Token YourApiToken,user_email=YourEmail' \
-H 'Content-Type: application/json' \
--data-raw '{
"purchase_order": {
"number": "PO-001",
"description": "Purchase order description",
"department_id": 1,
"stage_name": "opened"
}
}'
The above command returns JSON structured like this:
{
"id": 1,
"number": "PO-1011",
"description": "Purchase order description",
"external_id": null
}
Delete a Purchase Order
This endpoint deletes a specific Purchase Order.
HTTP Request
DELETE https://{tenant-subdomain}.inc.construction/api/v2/purchase_orders/{id}
URL Parameters
curl 'https://{tenant-subdomain}.inc.construction/api/v2/purchase_orders/{id}' -X DELETE \
-H 'Authorization: Token YourApiToken,user_email=YourEmail' \
-H 'Content-Type: application/json'
The above command returns JSON structured like this:
{
"id": 1,
"number": "1011"
}
Update a Purchase Order
Endpoint: PUT /api/v2/purchase_orders/{id}
Description: This endpoint updates an existing purchase order's details by ID.
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| purchase_order[number] | string | no | Purchase order number/reference |
| purchase_order[description] | string | no | Purchase order description |
| purchase_order[department_id] | integer | no | Department ID |
| purchase_order[stage_id] | integer | no | Stage ID (use GET /api/v2/purchase_orders/stages to find IDs) |
| purchase_order[stage_name] | string | no | Stage name (alternative to stage_id — e.g., "opened", "sent", "partial", "receipt"). Works across all tenants. |
CURL Request
curl 'https://{tenant-subdomain}.inc.construction/api/v2/purchase_orders/{id}' -X PUT \
-H 'Authorization: Token YourApiToken,user_email=YourEmail' \
-H 'Content-Type: application/json' \
--data-raw '{
"purchase_order": {
"number": "PO-001",
"description": "Updated description",
"department_id": 1,
"stage_name": "sent"
}
}'
Work Orders
Get Work Order Stages
This endpoint retrieves all available stages for work orders based on the default workflow.
HTTP Request
GET https://{tenant-subdomain}.inc.construction/api/v2/work_orders/stages
curl "https://{tenant-subdomain}.inc.construction/api/v2/work_orders/stages" \
-H 'Authorization: Token YourToken,user_email=YourEmail'
The above command returns JSON structured like this:
[
{
"id": 15,
"name": "opened",
"workflow_id": 3,
"previous_steps": [],
"next_steps": [16]
},
{
"id": 16,
"name": "execution",
"workflow_id": 3,
"previous_steps": [15],
"next_steps": [17, 20]
},
{
"id": 17,
"name": "verification",
"workflow_id": 3,
"previous_steps": [16],
"next_steps": [18]
}
]
Get All Work Orders
curl "https://{tenant-subdomain}.inc.construction/api/v2/work_orders" \
-H 'Authorization: Token YourApiToken,user_email=YourEmail'
The above command returns JSON structured like this:
[
{
"id": 1,
"project_id": 50,
"number": "WO-1001",
"created_at": "2024-01-10T00:00:00Z",
"updated_at": "2024-01-12T00:00:00Z",
"external_id": null
},
{
"id": 2,
"project_id": 51,
"number": "WO-1002",
"created_at": "2024-01-11T00:00:00Z",
"updated_at": "2024-01-13T00:00:00Z",
"external_id": null
}
]
This endpoint retrieves all Work Orders.
HTTP Request
GET https://{tenant-subdomain}.inc.construction/api/v2/work_orders
Query Parameters
| Parameter | Default | Description |
|---|---|---|
| page | 1 | Page number to retrieve |
| per_page | 20 | Number of results per page |
Get Specific Work Order
This endpoint retrieves a specific Work Order.
HTTP Request
GET https://{tenant-subdomain}.inc.construction/api/v2/work_orders/ID
URL Parameters
| Parameter | Description |
|---|---|
| ID | The ID of the Work order to retrieve |
curl "https://{tenant-subdomain}.inc.construction/api/v2/work_orders/1" \
-H 'Authorization: Token YourApiToken,user_email=YourEmail'
{
"id": 1,
"name": "WO-1001",
"external_id": null
}
Create a New Work Order
This endpoint to create a new Work Order.
HTTP Request
POST https://{tenant-subdomain}.inc.construction/api/v2/work_orders
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| work_order[number] | string | yes | Work order number/reference |
| work_order[description] | string | yes | Work details/notes |
| work_order[department_id] | integer | yes | Department ID |
| work_order[start_date] | date | no | Start date (YYYY-MM-DD) |
| work_order[end_date] | date | no | End date (YYYY-MM-DD) |
| work_order[stage_id] | integer | no | Stage ID (use GET /api/v2/work_orders/stages to find IDs) |
| work_order[stage_name] | string | no | Stage name (alternative to stage_id — e.g., "opened", "execution", "verification", "closed"). Works across all tenants. |
curl 'https://{tenant-subdomain}.inc.construction/api/v2/work_orders' -X POST \
-H 'Authorization: Token YourApiToken,user_email=YourEmail' \
-H 'Content-Type: application/json' \
--data-raw '{
"work_order": {
"number": "WO-001",
"description": "Description",
"department_id": 1,
"start_date": "2024-01-01",
"end_date": "2024-01-31",
"stage_name": "opened"
}
}'
{
"id": 1,
"number": "WO-001",
"description": "Work order description"
}
Delete a Work Order
This endpoint deletes a specific Work Order.
HTTP Request
DELETE https://{tenant-subdomain}.inc.construction/api/v2/work_orders/{id}
URL Parameters
curl 'https://{tenant-subdomain}.inc.construction/api/v2/work_orders/{id}' -X DELETE \
-H 'Authorization: Token YourApiToken,user_email=YourEmail' \
-H 'Content-Type: application/json'
Update a Work Order
Endpoint: PUT /api/v2/work_orders/{id}
Description: This endpoint updates an existing Work Order's details by ID.
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| work_order[number] | string | no | Work order number/reference |
| work_order[description] | string | no | Work details/notes |
| work_order[department_id] | integer | no | Department ID |
| work_order[start_date] | date | no | Start date (YYYY-MM-DD) |
| work_order[end_date] | date | no | End date (YYYY-MM-DD) |
| work_order[stage_id] | integer | no | Stage ID (use GET /api/v2/work_orders/stages to find IDs) |
| work_order[stage_name] | string | no | Stage name (alternative to stage_id — e.g., "opened", "execution", "verification", "closed"). Works across all tenants. |
CURL Request
curl 'https://{tenant-subdomain}.inc.construction/api/v2/work_orders/{id}' -X PUT \
-H 'Authorization: Token YourApiToken,user_email=YourEmail' \
-H 'Content-Type: application/json' \
--data-raw '{
"work_order": {
"number": "WO-001",
"description": "Updated description",
"department_id": 1,
"stage_name": "execution"
}
}'
Parties (contacts)
Get All Parties
curl "https://{tenant-subdomain}.inc.construction/api/v2/parties" \
-H 'Authorization: Token YourApiToken,user_email=YourEmail'
The above command returns JSON structured like this:
[
{
"id": 1,
"name": "Example Company",
"type": "company",
"addresses": [
{
"street": "123 Main St",
"city": "Example City",
"state": "EX",
"zip": "12345",
"country": "USA"
}
]
},
{
"id": 2,
"name": "John Doe",
"type": "contact",
"first_name": "John",
"last_name": "Doe",
"subtypes": ["client", "supplier"],
"company_id": 1,
"phones": [
{
"id": 10,
"value": "+1234567890",
"type": "mobile"
}
],
"emails": [
{
"id": 5,
"value": "john@example.com"
}
]
}
]
This endpoint retrieves all Parties.
HTTP Request
GET https://{tenant-subdomain}.inc.construction/api/v2/parties
Query Parameters
Pagination
⚠️ Coming Soon
| Parameter | Default | Description |
|---|---|---|
| type | null | Filter by party type (building, company, contact) |
| subtype | null | Filter by party subtype |
| role | null | Filter by party role |
# Role-based filters:
/api/v2/parties?role=employee # Gets employee contacts
/api/v2/parties?role=supplier # Gets both supplier contacts and companies
/api/v2/parties?role=client # Gets both client contacts and companies
# Type-based filters:
/api/v2/parties?type=building # Gets all buildings
/api/v2/parties?type=company # Gets all companies
/api/v2/parties?type=contact # Gets all contacts
Subtype-based filters (using the SUBTYPES arrays from your models):
/api/v2/parties?subtype=supplier # Gets entities with supplier subtype
/api/v2/parties?subtype=client # Gets entities with client subtype
/api/v2/parties?subtype=contact # Gets entities with contact subtype
/api/v2/parties?subtype=employee # Gets entities with employee subtype
# Type and Subtype combinations:
/api/v2/parties?type=contact&subtype=supplier # Gets only contact-type suppliers
/api/v2/parties?type=company&subtype=client # Gets only company-type clients
# Multiple subtypes (using comma separation):
/api/v2/parties?subtype=supplier,client # Gets entities that are both suppliers and clients
/api/v2/parties?type=contact&subtype=employee,supplier # Gets contacts that are both employees and suppliers
Create Party
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| party[name] | string | yes | Party name |
| party[type] | string | yes | One of: Crm::Parties::Contact, Crm::Parties::Company, Crm::Parties::Building |
| party[first_name] | string | conditional | Required when type is Contact |
| party[last_name] | string | conditional | Required when type is Contact |
| party[owner_id] | integer | no | Identifies the owner contact for buildings |
| party[addresses_attributes][] | array[object] | no | Nested addresses |
| addresses_attributes[][street] | string | no | Street |
| addresses_attributes[][city] | string | no | City |
| addresses_attributes[][state] | string | no | State/province |
| addresses_attributes[][zip] | string | no | Postal/ZIP code |
| addresses_attributes[][country] | string | no | Country |
| party[phones_attributes][] | array[object] | no | Nested phones |
| phones_attributes[][value] | string | no | Phone number |
| phones_attributes[][type] | string | no | e.g., office, mobile |
| party[emails_attributes][] | array[object] | no | Nested emails |
| emails_attributes[][value] | string | no | Email address |
curl -X POST "https://{tenant-subdomain}.inc.construction/api/v2/parties" \
-H 'Authorization: Token YourToken,user_email=YourEmail' \
-H "Content-Type: application/json" \
-d '{
"party": {
"name": "Company Name",
"type": "Crm::Parties::Contact",
"first_name": "John",
"last_name": "Doe",
"addresses_attributes": [
{
"street": "123 Main St",
"city": "Example City",
"state": "EX",
"zip": "12345"
}
],
"phones_attributes": [
{
"value": "+1234567890",
"type": "office"
}
],
"emails_attributes": [
{
"value": "contact@example.com"
}
]
}
}'
The above command returns JSON structured like this:
{
"id": 1,
"name": "Example Company",
"type": "Crm::Parties::Contact"
}
Update Party
curl -X PUT "https://{tenant-subdomain}.inc.construction/api/v2/parties/1" \
-H 'Authorization: Token YourToken,user_email=YourEmail' \
-H "Content-Type: application/json" \
-d '{
"party": {
"name": "Updated Company Name"
}
}'
Delete Party
curl "https://{tenant-subdomain}.inc.construction/api/v2/parties/1" -X DELETE \
-H 'Authorization: Token YourApiToken,user_email=YourEmail'
Contact Information
Create Phone
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| phone[value] | string | yes | Phone number in international format if possible |
| phone[type] | string | no | e.g., office, mobile, home |
curl -X POST "https://{tenant-subdomain}.inc.construction/api/v2/phones" \
-H 'Authorization: Token YourToken,user_email=YourEmail' \
-H "Content-Type: application/json" \
-d '{
"phone": {
"value": "+1234567890",
"type": "office"
}
}'
Create Email
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| email[value] | string | yes | Email address |
curl -X POST "https://{tenant-subdomain}.inc.construction/api/v2/emails" \
-H 'Authorization: Token YourToken,user_email=YourEmail' \
-H "Content-Type: application/json" \
-d '{
"email": {
"value": "contact@example.com"
}
}'
Create Address
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| address[street] | string | yes | Street |
| address[city] | string | yes | City |
| address[state] | string | yes | State/province |
| address[zip] | string | yes | Postal/ZIP code |
| address[country] | string | no | Country |
curl -X POST "https://{tenant-subdomain}.inc.construction/api/v2/addresses" \
-H 'Authorization: Token YourToken,user_email=YourEmail' \
-H "Content-Type: application/json" \
-d '{
"address": {
"street": "123 Main St",
"city": "Example City",
"state": "EX",
"zip": "12345",
"country": "USA"
}
}'
Notes
Get All Notes
curl "https://{tenant-subdomain}.inc.construction/api/v2/notes?page=1&per_page=20" \
-H 'Authorization: Token YourApiToken,user_email=YourEmail'
The above command returns JSON structured like this:
[
{
"id": 1,
"note": "Example note text",
"notable_type": "Projects::Project",
"notable_id": 123,
"employee_id": 45,
"deleted_at": null,
"created_at": "2024-01-01T00:00:00Z",
"updated_at": "2024-01-02T00:00:00Z"
}
]
This endpoint retrieves all Notes.
HTTP Request
GET https://{tenant-subdomain}.inc.construction/api/v2/notes
Query Parameters
| Parameter | Default | Description |
|---|---|---|
| page | 1 | Page number to retrieve |
| per_page | 20 | Number of results per page |
| notable_type | Filter by parent type (Project, Quote, Invoice, WorkOrder, PurchaseOrder) | |
| notable_id | Filter by parent ID |
Get Specific Note
This endpoint retrieves a specific Note.
HTTP Request
GET https://{tenant-subdomain}.inc.construction/api/v2/notes/{id}
URL Parameters
| Parameter | Description |
|---|---|
| id | The ID of the Note to retrieve |
curl "https://{tenant-subdomain}.inc.construction/api/v2/notes/1" \
-H 'Authorization: Token YourApiToken,user_email=YourEmail'
Returns JSON like:
{
"id": 1,
"note": "Example note text",
"notable_type": "Projects::Project",
"notable_id": 123,
"employee_id": 45,
"deleted_at": null,
"created_at": "2024-01-01T00:00:00Z",
"updated_at": "2024-01-02T00:00:00Z"
}
Create a New Note
HTTP Request
POST https://{tenant-subdomain}.inc.construction/api/v2/notes
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| note[notable_type] | string | yes | Parent entity type (Projects::Project, Accounting::Quote, Accounting::Invoice, Accounting::WorkOrder, Accounting::PurchaseOrder) |
| note[notable_id] | integer | yes | Parent entity ID |
| note[note] | string | yes | Note text/body |
curl 'https://{tenant-subdomain}.inc.construction/api/v2/notes' -X POST \
-H 'Authorization: Token YourApiToken,user_email=YourEmail' \
-H 'Content-Type: application/json' \
--data-raw '{
"note": {
"notable_type": "Projects::Project",
"notable_id": 123,
"note": "Kickoff scheduled for Friday"
}
}'
Returns JSON structured like this:
{
"id": 777,
"note": "Kickoff scheduled for Friday",
"notable_type": "Projects::Project",
"notable_id": 123,
"employee_id": 45,
"deleted_at": null,
"created_at": "2024-01-03T10:30:00Z",
"updated_at": "2024-01-03T10:30:00Z"
}
Update a Note
Endpoint: PUT /api/v2/notes/{id}
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| note[note] | string | yes | New note text/body |
CURL Request
curl 'https://{tenant-subdomain}.inc.construction/api/v2/notes/{id}' -X PUT \
-H 'Authorization: Token YourApiToken,user_email=YourEmail' \
-H 'Content-Type: application/json' \
--data-raw '{
"note": {
"note": "Updated note text"
}
}'
Delete a Note
HTTP Request
DELETE https://{tenant-subdomain}.inc.construction/api/v2/notes/{id}
curl 'https://{tenant-subdomain}.inc.construction/api/v2/notes/{id}' -X DELETE \
-H 'Authorization: Token YourApiToken,user_email=YourEmail'
Project Files
Upload File to Project
curl -X POST "https://{tenant-subdomain}.inc.construction/api/v2/projects/{PROJECT_ID}/files" \
-H "Authorization: Token YourApiToken,user_email=YourEmail" \
-F "file=@/path/to/your/file.jpg" \
-F "name=project_attachment.jpg"
The above command returns JSON structured like this:
{
"id": 456,
"name": "project_attachment.jpg",
"url": "https://presigned-s3-url...",
"content_type": "image/jpeg",
"position": 1,
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T10:30:00Z",
"extension": "jpg",
"is_image": true,
"external_url": "http://tenant.inc.construction/api/core/files/...",
"attachable": {
"id": 123,
"type": "Projects::Project"
},
"thumb_urls": {
"sm": "https://presigned-thumb-sm-url...",
"md": "https://presigned-thumb-md-url...",
"lg": "https://presigned-thumb-lg-url...",
"sm_aspect": "https://presigned-thumb-sm-aspect-url..."
}
}
This endpoint uploads a file to a specific project. Files are stored securely in Amazon S3 with automatic thumbnail generation for images and PDF preview support.
HTTP Request
POST https://{tenant-subdomain}.inc.construction/api/v2/projects/{PROJECT_ID}/files
URL Parameters
| Parameter | Description |
|---|---|
| PROJECT_ID | The ID of the project to attach the file to |
Form Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| file | file | yes | The file to upload (supports images, PDFs, documents) |
| name | string | no | Custom filename (defaults to original filename) |
Get All Project Files
curl "https://{tenant-subdomain}.inc.construction/api/v2/projects/{PROJECT_ID}/files" \
-H "Authorization: Token YourApiToken,user_email=YourEmail"
The above command returns JSON structured like this:
[
{
"id": 456,
"name": "project_plan.jpg",
"url": "https://presigned-s3-url...",
"content_type": "image/jpeg",
"position": 1,
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T10:30:00Z",
"extension": "jpg",
"is_image": true,
"external_url": "http://tenant.inc.construction/api/core/files/...",
"thumb_urls": {
"sm": "https://presigned-thumb-sm-url...",
"md": "https://presigned-thumb-md-url...",
"lg": "https://presigned-thumb-lg-url...",
"sm_aspect": "https://presigned-thumb-sm-aspect-url..."
}
}
]
This endpoint retrieves all files attached to a specific project.
HTTP Request
GET https://{tenant-subdomain}.inc.construction/api/v2/projects/{PROJECT_ID}/files
URL Parameters
| Parameter | Description |
|---|---|
| PROJECT_ID | The ID of the project to retrieve files for |
Get a Specific Project File
curl "https://{tenant-subdomain}.inc.construction/api/v2/projects/{PROJECT_ID}/files/{ID}" \
-H "Authorization: Token YourApiToken,user_email=YourEmail"
The above command returns JSON structured like this:
{
"id": 456,
"name": "project_plan.jpg",
"url": "https://presigned-s3-url...",
"content_type": "image/jpeg",
"position": 1,
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T10:30:00Z",
"extension": "jpg",
"is_image": true,
"external_url": "http://tenant.inc.construction/api/core/files/...",
"attachable": {
"id": 123,
"type": "Projects::Project"
},
"thumb_urls": {
"sm": "https://presigned-thumb-sm-url...",
"md": "https://presigned-thumb-md-url...",
"lg": "https://presigned-thumb-lg-url...",
"sm_aspect": "https://presigned-thumb-sm-aspect-url..."
}
}
This endpoint retrieves a specific file attached to a project.
HTTP Request
GET https://{tenant-subdomain}.inc.construction/api/v2/projects/{PROJECT_ID}/files/{ID}
URL Parameters
| Parameter | Description |
|---|---|
| PROJECT_ID | The ID of the project |
| ID | The ID of the file to retrieve |
Update Project File
curl -X PATCH "https://{tenant-subdomain}.inc.construction/api/v2/projects/{PROJECT_ID}/files/{ID}" \
-H "Authorization: Token YourApiToken,user_email=YourEmail" \
-H "Content-Type: application/json" \
-d '{
"file": {
"name": "updated_filename.jpg"
}
}'
The above command returns JSON structured like this:
{
"id": 456,
"name": "updated_filename.jpg",
"url": "https://presigned-s3-url...",
"content_type": "image/jpeg",
"position": 1,
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T11:45:00Z",
"extension": "jpg",
"is_image": true,
"external_url": "http://tenant.inc.construction/api/core/files/..."
}
This endpoint updates the metadata of a specific project file.
HTTP Request
PATCH https://{tenant-subdomain}.inc.construction/api/v2/projects/{PROJECT_ID}/files/{ID}
URL Parameters
| Parameter | Description |
|---|---|
| PROJECT_ID | The ID of the project |
| ID | The ID of the file to update |
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| file[name] | string | no | New filename for the file |
Delete Project File
curl -X DELETE "https://{tenant-subdomain}.inc.construction/api/v2/projects/{PROJECT_ID}/files/{ID}" \
-H "Authorization: Token YourApiToken,user_email=YourEmail"
The above command returns JSON structured like this:
{
"message": "File deleted"
}
This endpoint deletes a specific file from a project. The file is permanently removed from S3 storage.
HTTP Request
DELETE https://{tenant-subdomain}.inc.construction/api/v2/projects/{PROJECT_ID}/files/{ID}
URL Parameters
| Parameter | Description |
|---|---|
| PROJECT_ID | The ID of the project |
| ID | The ID of the file to delete |
Errors
CCUBE API uses conventional HTTP status codes to indicate the success or failure of an API request. Errors are returned as JSON.
Example error response:
{
"error": {
"code": "unauthorized",
"message": "Invalid or expired token",
"details": null
}
}
| Status | Meaning |
|---|---|
| 400 | Bad Request — Your request is invalid (e.g., malformed JSON, missing required parameter). |
| 401 | Unauthorized — Missing or invalid credentials. Ensure Authorization header is set: Authorization: Token <token>,user_email=<email>. |
| 403 | Forbidden — You do not have permission to perform this action. |
| 404 | Not Found — The requested resource does not exist. |
| 405 | Method Not Allowed — The HTTP method is not supported for this endpoint. |
| 406 | Not Acceptable — You requested a format that isn't JSON. |
| 409 | Conflict — The request could not be completed due to a resource conflict. |
| 410 | Gone — The requested resource is no longer available. |
| 422 | Unprocessable Entity — Validation failed; check details for field errors. |
| 429 | Too Many Requests — You have exceeded the rate limit. |
| 500 | Internal Server Error — We had a problem with our server. Try again later. |
| 503 | Service Unavailable — We're temporarily offline for maintenance. Try again later. |