REST API - Objects
CRUD endpoints, query parameters, response formats, and webhooks for the Tuli REST API.
Object API
Every P9 object automatically gets a full REST API. No additional configuration required.
Base URL
https://api.tuli.io/api/v1
Endpoints
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/v1/{object} |
List records |
GET |
/api/v1/{object}/{key} |
Get single record |
POST |
/api/v1/{object} |
Create record |
PUT |
/api/v1/{object}/{key} |
Update record |
PATCH |
/api/v1/{object}/{key} |
Partial update |
DELETE |
/api/v1/{object}/{key} |
Delete record |
Note: All endpoints use Keys (string identifiers), not numeric IDs.
List Records
GET /api/v1/contracts?page=1&pageSize=25&sort=-CreatedAt&filter=Status eq 'Active'
Query Parameters
| Parameter | Type | Description |
|---|---|---|
page |
integer | Page number (default: 1) |
pageSize |
integer | Records per page (default: 25, max: 100) |
sort |
string | Sort field, prefix with - for descending |
filter |
string | OData-style filter expression |
fields |
string | Comma-separated field names to include |
expand |
string | Lookup fields to expand with full records |
search |
string | Full-text search across all text fields |
Response Format
{
"Success": true,
"Data": [
{
"Id": "contract-1CAjiqwbu2g3LkcoByCL4Y",
"ContractNumber": "CON-2025-00042",
"Tenant": {
"Id": "tenant-2DBkjrxcv3h4MldpCzDM5Z",
"Name": "Acme Corp"
},
"MonthlyRent": {
"Amount": 5000,
"Currency": "AED"
},
"Status": "Active",
"CreatedAt": "2025-01-15T10:30:00Z",
"ModifiedAt": "2025-02-01T14:20:00Z"
}
],
"Pagination": {
"Page": 1,
"PageSize": 25,
"TotalRecords": 142,
"TotalPages": 6
}
}
Get Single Record
GET /api/v1/contracts/contract-1CAjiqwbu2g3LkcoByCL4Y
Response:
{
"Success": true,
"Data": {
"Id": "contract-1CAjiqwbu2g3LkcoByCL4Y",
"ContractNumber": "CON-2025-00042",
"Tenant": {
"Id": "tenant-2DBkjrxcv3h4MldpCzDM5Z",
"Name": "Acme Corp"
},
"Building": {
"Id": "building-3ECljsydw4i5NmeqDaEN6A",
"Name": "Tower A"
},
"Unit": {
"Id": "unit-4FDmktzex5j6OlfpEbFO7B",
"Number": "1201"
},
"MonthlyRent": {
"Amount": 5000,
"Currency": "AED"
},
"StartDate": "2025-03-01",
"EndDate": "2027-02-28",
"Duration": 24,
"Status": "Active",
"CreatedAt": "2025-01-15T10:30:00Z",
"ModifiedAt": "2025-02-01T14:20:00Z"
}
}
Create Record
POST /api/v1/contracts
Content-Type: application/json
{
"Tenant": "tenant-2DBkjrxcv3h4MldpCzDM5Z",
"Building": "building-3ECljsydw4i5NmeqDaEN6A",
"Unit": "unit-4FDmktzex5j6OlfpEbFO7B",
"MonthlyRent": {
"Amount": 5000,
"Currency": "AED"
},
"StartDate": "2025-03-01",
"Duration": 24,
"LeaseType": "New"
}
Response:
{
"Success": true,
"Data": {
"Id": "contract-5GEnluafy6k7PmgqFcGP8C",
"ContractNumber": "CON-2025-00043",
"Status": "Draft"
}
}
Update Record
PUT /api/v1/contracts/contract-1CAjiqwbu2g3LkcoByCL4Y
Content-Type: application/json
{
"MonthlyRent": {
"Amount": 5500,
"Currency": "AED"
},
"Status": "Active"
}
Partial Update
PATCH /api/v1/contracts/contract-1CAjiqwbu2g3LkcoByCL4Y
Content-Type: application/json
{
"Status": "Active"
}
Delete Record
DELETE /api/v1/contracts/contract-1CAjiqwbu2g3LkcoByCL4Y
Error Handling
{
"Success": false,
"Error": {
"Code": "VALIDATION_ERROR",
"Message": "Validation failed",
"Details": [
{ "Field": "MonthlyRent", "Message": "MonthlyRent is required" },
{ "Field": "StartDate", "Message": "StartDate must be in the future" }
]
}
}
| Code | Status | Description |
|---|---|---|
VALIDATION_ERROR |
400 | Request body validation failed |
UNAUTHORIZED |
401 | Missing or invalid authentication |
FORBIDDEN |
403 | Insufficient permissions |
NOT_FOUND |
404 | Record not found |
CONFLICT |
409 | Duplicate or conflict |
RATE_LIMITED |
429 | Too many requests |
SERVER_ERROR |
500 | Internal server error |
Webhooks
Register webhooks to receive real-time notifications:
POST /api/v1/webhooks
Content-Type: application/json
{
"Url": "https://your-app.com/webhook",
"Events": [
"contract.created",
"contract.updated",
"contract.workflow.approved"
],
"Secret": "your-webhook-secret"
}
Webhook payload:
{
"Event": "contract.created",
"Timestamp": "2025-03-15T10:30:00Z",
"Data": {
"Id": "contract-1CAjiqwbu2g3LkcoByCL4Y",
"ContractNumber": "CON-2025-00042",
"Status": "Draft"
}
}
Workflow Actions
Trigger workflow actions on records:
POST /api/v1/contracts/contract-1CAjiqwbu2g3LkcoByCL4Y/workflow/submit
POST /api/v1/contracts/contract-1CAjiqwbu2g3LkcoByCL4Y/workflow/approve
Content-Type: application/json
{
"Comment": "Approved for processing"
}
Search & Filtering
Full-Text Search
GET /api/v1/contracts?search=acme
Filter Expressions (OData-style)
# Equals
GET /api/v1/contracts?filter=Status eq 'Active'
# Not equals
GET /api/v1/contracts?filter=Status ne 'Draft'
# Greater than
GET /api/v1/contracts?filter=MonthlyRent gt 5000
# Contains (text)
GET /api/v1/contracts?filter=contains(Tenant/Name, 'Acme')
# Multiple conditions
GET /api/v1/contracts?filter=Status eq 'Active' and MonthlyRent gt 5000
Expanding Lookups
Include related record data:
GET /api/v1/contracts?expand=Tenant,Building,Unit
Response includes full lookup data:
{
"Id": "contract-1CAjiqwbu2g3LkcoByCL4Y",
"Tenant": {
"Id": "tenant-2DBkjrxcv3h4MldpCzDM5Z",
"Name": "Acme Corp",
"Email": "contact@acme.com",
"Phone": "+971501234567"
}
}