Property

Manage developments, plots, reservations and call-offs in Signal's optional Property module.

The Property module is an optional part of Signal for businesses that sell or supply homes on developments. Its records are:

  • Developments: a site or project.
  • Plots: the individual homes or lots on a development.
  • Reservations: a buyer's hold on a plot.
  • Call-offs: a client's order for a batch of plots on a development (or for bulk stock), which then moves through checking, dispatch, invoicing and payment.

All Property endpoints use the property:read and property:write scopes.

Developments and plots support custom fields: they include custom_fields in responses and accept custom field values on create and update.


Developments#

The development object#

Field Type Notes
id string UUID.
name string
description string or null
location string or null
status string Signal uses active (the default), planning, launching and completed.
launch_date string or null ISO 8601 timestamp.
completion_date string or null ISO 8601 timestamp.
image_url string or null
order_number string or null Your order number for the development, up to 100 characters. New call-offs on the development use it as their client order number unless given another.
file_path string or null Where the development's files live (a path or link), up to 1,000 characters.
created_at string ISO 8601 timestamp.
updated_at string ISO 8601 timestamp.
custom_fields object Custom fields for developments, keyed by internal name.
JSON
{
  "id": "1f3b5d7e-9a2c-4e4b-8d6f-0b2d4f6a8c37",
  "name": "Riverside Gardens",
  "description": "42 homes on the old mill site.",
  "location": "Portland, OR",
  "status": "active",
  "launch_date": "2026-04-01T00:00:00.000Z",
  "completion_date": null,
  "image_url": null,
  "order_number": "PO-7781",
  "file_path": null,
  "created_at": "2026-11-12T09:30:00.000Z",
  "updated_at": "2026-11-12T09:30:00.000Z",
  "custom_fields": { "phase": "2" }
}

List developments#

GET /property/developments · Scope: property:read

Newest first.

Query parameter Type Required Notes
status string No Exact match, such as active.
limit integer No 1 to 100. Default 20.
cursor string No meta.cursor from the previous page.
curl "https://crm.empiraa.app/api/v1/property/developments?status=active" \
  -H "Authorization: Bearer YOUR_API_KEY"

Response 200: an array of development objects in data; meta has cursor and hasMore.

Create a development#

POST /property/developments · Scope: property:write

Field Type Required Notes
name string Yes
description string or null No
location string or null No
status string or null No Defaults to active.
launch_date string or null No ISO 8601 date or timestamp.
completion_date string or null No ISO 8601 date or timestamp.
image_url string or null No
order_number string or null No Up to 100 characters. Trimmed; blank means none.
file_path string or null No Up to 1,000 characters. Trimmed; blank means none.
custom_fields object No Other unrecognized top-level fields are also treated as custom fields.
curl -X POST https://crm.empiraa.app/api/v1/property/developments \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Riverside Gardens", "location": "Portland, OR", "order_number": "PO-7781"}'

Response 201: the new development object in data.

Status Code When
400 INVALID_REQUEST name is missing; a date isn't valid; order_number or file_path isn't text or is too long.

Webhook events: development.created.

Get a development#

GET /property/developments/{id} · Scope: property:read

curl https://crm.empiraa.app/api/v1/property/developments/1f3b5d7e-9a2c-4e4b-8d6f-0b2d4f6a8c37 \
  -H "Authorization: Bearer YOUR_API_KEY"

Response 200: the development object in data. 404 NOT_FOUND if it isn't in this workspace.

Update a development#

PATCH /property/developments/{id} · Scope: property:write

Send only the fields you want to change. Takes the same fields as create.

curl -X PATCH https://crm.empiraa.app/api/v1/property/developments/1f3b5d7e-9a2c-4e4b-8d6f-0b2d4f6a8c37 \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"status": "completed", "completion_date": "2026-11-30"}'

Response 200: the updated development object in data.

Status Code When
400 INVALID_REQUEST A date isn't valid; order_number or file_path isn't text or is too long.
404 NOT_FOUND The development isn't in this workspace.

Webhook events: development.updated.

Delete a development#

DELETE /property/developments/{id} · Scope: property:write

A development that has call-offs keeps its billing history, so it can't be deleted (409 CONFLICT). Archive it in Signal instead.

curl -X DELETE https://crm.empiraa.app/api/v1/property/developments/1f3b5d7e-9a2c-4e4b-8d6f-0b2d4f6a8c37 \
  -H "Authorization: Bearer YOUR_API_KEY"

Response 204: no body.

Status Code When
404 NOT_FOUND The development isn't in this workspace.
409 CONFLICT The development has call-offs.

Plots#

The plot object#

Field Type Notes
id string UUID.
development_id string
plot_number string Text, so 12A works.
property_type string or null
bedrooms integer or null
bathrooms number or null
sqft number or null Floor area in square feet.
price number In the workspace's currency. Defaults to 0.
status string Signal uses available (the default), reserved, sold and unavailable.
notes string or null
image_url string or null
website_url string or null
created_at string ISO 8601 timestamp.
updated_at string ISO 8601 timestamp.
custom_fields object Custom fields for plots, keyed by internal name.
JSON
{
  "id": "5d7f9b1c-3e4a-4c6d-9f8b-2a4c6e8a0c51",
  "development_id": "1f3b5d7e-9a2c-4e4b-8d6f-0b2d4f6a8c37",
  "plot_number": "12",
  "property_type": "Townhouse",
  "bedrooms": 3,
  "bathrooms": 2,
  "sqft": 1450,
  "price": 489000,
  "status": "available",
  "notes": null,
  "image_url": null,
  "website_url": null,
  "created_at": "2026-11-12T09:30:00.000Z",
  "updated_at": "2026-11-12T09:30:00.000Z",
  "custom_fields": {}
}

List plots#

GET /property/plots · Scope: property:read

Newest first.

Query parameter Type Required Notes
development_id string No Only plots on this development.
status string No Exact match, such as available.
limit integer No 1 to 100. Default 20.
cursor string No meta.cursor from the previous page.
curl "https://crm.empiraa.app/api/v1/property/plots?development_id=1f3b5d7e-9a2c-4e4b-8d6f-0b2d4f6a8c37&status=available" \
  -H "Authorization: Bearer YOUR_API_KEY"

Response 200: an array of plot objects in data; meta has cursor and hasMore.

Create a plot#

POST /property/plots · Scope: property:write

Field Type Required Notes
development_id string Yes A development in this workspace.
plot_number string Yes Send as a string, even for numbers.
property_type string or null No
bedrooms integer or null No
bathrooms number or null No
sqft number or null No
price number No Defaults to 0.
status string or null No Defaults to available.
notes string or null No
image_url string or null No
website_url string or null No
custom_fields object No Other unrecognized top-level fields are also treated as custom fields.
curl -X POST https://crm.empiraa.app/api/v1/property/plots \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"development_id": "1f3b5d7e-9a2c-4e4b-8d6f-0b2d4f6a8c37", "plot_number": "12", "bedrooms": 3, "price": 489000}'

Response 201: the new plot object in data.

Status Code When
400 INVALID_REQUEST development_id or plot_number is missing or not a string.
404 NOT_FOUND The development isn't in this workspace.

Webhook events: plot.created.

Get a plot#

GET /property/plots/{id} · Scope: property:read

curl https://crm.empiraa.app/api/v1/property/plots/5d7f9b1c-3e4a-4c6d-9f8b-2a4c6e8a0c51 \
  -H "Authorization: Bearer YOUR_API_KEY"

Response 200: the plot object in data. 404 NOT_FOUND if it isn't in this workspace.

Update a plot#

PATCH /property/plots/{id} · Scope: property:write

Send only the fields you want to change. Takes the same fields as create, except development_id, which can't be changed.

curl -X PATCH https://crm.empiraa.app/api/v1/property/plots/5d7f9b1c-3e4a-4c6d-9f8b-2a4c6e8a0c51 \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"status": "sold"}'

Response 200: the updated plot object in data. 404 NOT_FOUND if it isn't in this workspace.

Webhook events: plot.updated, plus plot.status_changed when status changes.

Delete a plot#

DELETE /property/plots/{id} · Scope: property:write

A plot on a call-off can't be deleted (409 CONFLICT).

curl -X DELETE https://crm.empiraa.app/api/v1/property/plots/5d7f9b1c-3e4a-4c6d-9f8b-2a4c6e8a0c51 \
  -H "Authorization: Bearer YOUR_API_KEY"

Response 204: no body.

Status Code When
404 NOT_FOUND The plot isn't in this workspace.
409 CONFLICT The plot is on a call-off.

Reservations#

The reservation object#

Field Type Notes
id string UUID.
plot_id string
contact_id string or null The buyer's Contact, if any.
account_id string or null
buyer_name string
deposit_amount number In the workspace's currency.
total_price number In the workspace's currency.
reserved_at string When the reservation was made.
expires_at string When the hold lapses.
status string active for a new reservation, cancelled once canceled.
notes string or null
created_at string ISO 8601 timestamp.
updated_at string ISO 8601 timestamp.
JSON
{
  "id": "7f9b1d3e-5a6c-4e8f-8b0d-4c6e8a0c2e73",
  "plot_id": "5d7f9b1c-3e4a-4c6d-9f8b-2a4c6e8a0c51",
  "contact_id": "a1d4e7b2-5c3f-4e8a-b9d6-0f2c7e1a3b54",
  "account_id": null,
  "buyer_name": "Maria Garcia",
  "deposit_amount": 5000,
  "total_price": 489000,
  "reserved_at": "2026-11-12T09:30:00.000Z",
  "expires_at": "2026-12-12T00:00:00.000Z",
  "status": "active",
  "notes": null,
  "created_at": "2026-11-12T09:30:00.000Z",
  "updated_at": "2026-11-12T09:30:00.000Z"
}

List reservations#

GET /property/reservations · Scope: property:read

Newest first.

Query parameter Type Required Notes
plot_id string No
status string No Such as active or cancelled.
contact_id string No
limit integer No 1 to 100. Default 20.
cursor string No meta.cursor from the previous page.
curl "https://crm.empiraa.app/api/v1/property/reservations?status=active" \
  -H "Authorization: Bearer YOUR_API_KEY"

Response 200: an array of reservation objects in data; meta has cursor and hasMore.

Create a reservation#

POST /property/reservations · Scope: property:write

Reserves a plot. The plot's status changes to reserved.

Field Type Required Notes
plot_id string Yes A plot in this workspace.
buyer_name string Yes
expires_at string Yes ISO 8601 date or timestamp.
contact_id string or null No
account_id string or null No
deposit_amount number No Defaults to 0.
total_price number No Defaults to 0.
notes string or null No
curl -X POST https://crm.empiraa.app/api/v1/property/reservations \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"plot_id": "5d7f9b1c-3e4a-4c6d-9f8b-2a4c6e8a0c51", "buyer_name": "Maria Garcia", "expires_at": "2026-12-12", "deposit_amount": 5000, "total_price": 489000}'

Response 201: the new reservation object in data.

Status Code When
400 INVALID_REQUEST plot_id, buyer_name or expires_at is missing, or expires_at isn't a date.
404 NOT_FOUND The plot isn't in this workspace.

Webhook events: none.

Get a reservation#

GET /property/reservations/{id} · Scope: property:read

curl https://crm.empiraa.app/api/v1/property/reservations/7f9b1d3e-5a6c-4e8f-8b0d-4c6e8a0c2e73 \
  -H "Authorization: Bearer YOUR_API_KEY"

Response 200: the reservation object in data. 404 NOT_FOUND if it isn't in this workspace.

Update a reservation#

PATCH /property/reservations/{id} · Scope: property:write

Send only the fields you want to change: buyer_name, contact_id, account_id, deposit_amount, total_price, expires_at (can't be cleared), status and notes. To cancel, use DELETE, which also frees the plot.

curl -X PATCH https://crm.empiraa.app/api/v1/property/reservations/7f9b1d3e-5a6c-4e8f-8b0d-4c6e8a0c2e73 \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"expires_at": "2027-01-12", "deposit_amount": 7500}'

Response 200: the updated reservation object in data.

Status Code When
400 INVALID_REQUEST expires_at is empty or isn't a date.
404 NOT_FOUND The reservation isn't in this workspace.

Cancel a reservation#

DELETE /property/reservations/{id} · Scope: property:write

Sets the reservation's status to cancelled and the plot's status back to available. The reservation is kept.

curl -X DELETE https://crm.empiraa.app/api/v1/property/reservations/7f9b1d3e-5a6c-4e8f-8b0d-4c6e8a0c2e73 \
  -H "Authorization: Bearer YOUR_API_KEY"

Response 204: no body. 404 NOT_FOUND if it isn't in this workspace.


Call-offs#

A call-off is a client's order for a batch of plots on a development, quoted by its reference (such as CO-0042). A call-off moves through these statuses in Signal:

booked → checked → scheduled → dispatched → invoiced → paid (or canceled)

Each plot can be on one live call-off per stage: reservation or completion.

A bulk call-off is for stock that isn't tied to a development. It's booked for the client's Account with a description and a value you enter, and has no development, plots or stage.

Through the API you can list and read call-offs, book new ones, and record their invoice and payment. Checking, scheduling and dispatching are done in Signal.

The call-off object#

Field Type Notes
id string UUID.
reference string Such as CO-0042.
number integer The number in reference.
development_id string or null null for a bulk call-off.
development_name string or null
account_id string or null The client Account: the development's developer, or a bulk call-off's own Account.
account_name string or null
developer_account_id string or null Same as account_id.
developer_account_name string or null Same as account_name.
description string or null What a bulk call-off is for. null on a development call-off.
client_order_number string or null The client's purchase order number.
requested_by_contact_id string or null The Contact who requested it.
requested_by_name string or null
requested_by_email string or null
requested_by_phone string or null Their phone, or their mobile when they have no phone.
requested_at string ISO 8601 timestamp.
required_by string or null Calendar day, YYYY-MM-DD.
status string booked, checked, scheduled, dispatched, invoiced, paid or canceled.
stage string or null reservation or completion. null for a bulk call-off.
value number Plot prices when booked (or the bulk value), in the workspace's currency. Later price changes don't alter it.
notes string or null
plots array Plots currently on the call-off: id, plot_number, price. Empty once canceled.
booked_plot_numbers array of strings Plot numbers as booked. Kept after a cancel.
created_by_user_id string or null null when booked through the API.
checked_by_user_id string or null
checked_at string or null
dispatch_date string or null Calendar day, YYYY-MM-DD.
dispatched_at string or null
invoice_number string or null
invoiced_at string or null
paid_at string or null
canceled_at string or null
created_at string ISO 8601 timestamp.
updated_at string ISO 8601 timestamp.
JSON
{
  "id": "3a5c7e9b-1d2f-4a4c-8e6b-5d7f9b1d3f84",
  "reference": "CO-0042",
  "number": 42,
  "development_id": "1f3b5d7e-9a2c-4e4b-8d6f-0b2d4f6a8c37",
  "development_name": "Riverside Gardens",
  "developer_account_id": "3f6c2a1e-8b4d-4c2a-9f1e-2b7d5a9c4e10",
  "developer_account_name": "Contoso Homes",
  "account_id": "3f6c2a1e-8b4d-4c2a-9f1e-2b7d5a9c4e10",
  "account_name": "Contoso Homes",
  "description": null,
  "client_order_number": "PO-7781",
  "requested_by_contact_id": "a1d4e7b2-5c3f-4e8a-b9d6-0f2c7e1a3b54",
  "requested_by_name": "Maria Garcia",
  "requested_by_email": "maria.garcia@contoso.example",
  "requested_by_phone": "+1 312 555 0187",
  "requested_at": "2026-11-12T09:30:00.000Z",
  "required_by": "2026-12-01",
  "status": "booked",
  "stage": "reservation",
  "value": 978000,
  "notes": null,
  "plots": [
    { "id": "5d7f9b1c-3e4a-4c6d-9f8b-2a4c6e8a0c51", "plot_number": "12", "price": 489000 },
    { "id": "6e8a0c2d-4f5b-4d7e-8a9c-3b5d7f9b1d62", "plot_number": "13", "price": 489000 }
  ],
  "booked_plot_numbers": ["12", "13"],
  "created_by_user_id": null,
  "checked_by_user_id": null,
  "checked_at": null,
  "dispatch_date": null,
  "dispatched_at": null,
  "invoice_number": null,
  "invoiced_at": null,
  "paid_at": null,
  "canceled_at": null,
  "created_at": "2026-11-12T09:30:00.000Z",
  "updated_at": "2026-11-12T09:30:00.000Z"
}

List call-offs#

GET /property/call-offs · Scope: property:read

Highest number (most recent) first.

Query parameter Type Required Notes
status string No One status.
statuses string No Several statuses, comma-separated, such as invoiced,paid.
development_id string No
dispatch_from string No YYYY-MM-DD. Only call-offs with a dispatch_date on or after this day.
dispatch_to string No YYYY-MM-DD. Only call-offs with a dispatch_date on or before this day.
limit integer No 1 to 100. Default 20.
cursor string No meta.cursor from the previous page.
curl "https://crm.empiraa.app/api/v1/property/call-offs?statuses=dispatched,invoiced" \
  -H "Authorization: Bearer YOUR_API_KEY"

Response 200: an array of call-off objects in data; meta has cursor and hasMore.

Status Code When
400 INVALID_REQUEST A status isn't one of the values above, or a date isn't a real YYYY-MM-DD day.

Book a call-off#

POST /property/call-offs · Scope: property:write

Send either a development call-off (development_id and plots) or a bulk call-off (account_id, description and value), not both. Fields not listed here are refused with 400, so a mistyped field isn't silently ignored.

Development call-off#

Field Type Required Notes
development_id string Yes A development in this workspace.
plots string Yes Plot numbers as text, with ranges: "10-25" or "10-25, 30".
stage string No reservation (default) or completion.
create_missing_plots boolean No Create plots in the list that the development doesn't have yet. Default false.
requested_by_contact_id string or null No
requested_at string No ISO 8601 date or date and time, such as 2026-11-12 or 2026-11-12T09:30:00Z. A time with no offset is UTC. Defaults to now.
required_by string No YYYY-MM-DD.
client_order_number string or null No Up to 100 characters. Defaults to the development's order_number.
notes string or null No Up to 2,000 characters.

Bulk call-off#

Field Type Required Notes
account_id string Yes The client's Account.
description string Yes What is being called off, up to 500 characters.
value number Yes Such as 1250.50.
requested_by_contact_id string or null No
requested_at string No As above.
required_by string No YYYY-MM-DD.
client_order_number string or null No Up to 100 characters.
notes string or null No Up to 2,000 characters.

Dates must be real calendar days between the years 1900 and 2100; 2026-02-30 is refused rather than moved to March.

curl -X POST https://crm.empiraa.app/api/v1/property/call-offs \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "development_id": "1f3b5d7e-9a2c-4e4b-8d6f-0b2d4f6a8c37",
    "plots": "12-13",
    "stage": "reservation",
    "requested_by_contact_id": "a1d4e7b2-5c3f-4e8a-b9d6-0f2c7e1a3b54",
    "required_by": "2026-12-01"
  }'

Response 201: the new call-off object in data, with status booked.

Status Code When
400 INVALID_REQUEST A required field is missing; an unknown field was sent; development and bulk fields were mixed; a date or text field isn't valid; or the booking can't be made (for example a plot is already on a live call-off for that stage, or doesn't exist and create_missing_plots is false). title explains, using your workspace's name for a plot.
404 NOT_FOUND The development or Account isn't in this workspace.
409 CONFLICT The call-off changed while it was being booked. Retry.

Webhook events: none.

Get a call-off#

GET /property/call-offs/{id} · Scope: property:read

curl https://crm.empiraa.app/api/v1/property/call-offs/3a5c7e9b-1d2f-4a4c-8e6b-5d7f9b1d3f84 \
  -H "Authorization: Bearer YOUR_API_KEY"

Response 200: the call-off object in data. 404 NOT_FOUND if it isn't in this workspace.

Record an invoice or payment#

PATCH /property/call-offs/{id} · Scope: property:write

Records a call-off's invoice, its payment, or both, so your accounting tool (or Zapier, Make or n8n) can keep Signal up to date. This is the only change the API makes to a call-off.

Field Type Notes
invoice_number string Records the invoice. Up to 100 characters; can't be blank. A call-off has one invoice number.
invoiced_at string When it was invoiced. ISO 8601 date or date and time. Needs invoice_number. Defaults to now.
paid_at string Records the payment, at this time. ISO 8601 date or date and time.
paid boolean true records the payment now. Only true is accepted: a payment can't be taken back.

Send invoice_number to record an invoice, paid_at or paid: true to record a payment, or both. When you send both, the invoice is recorded first. A payment can only be recorded once the call-off is invoiced. Any other field is refused with 400.

curl -X PATCH https://crm.empiraa.app/api/v1/property/call-offs/3a5c7e9b-1d2f-4a4c-8e6b-5d7f9b1d3f84 \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"invoice_number": "INV-20931", "invoiced_at": "2026-11-12"}'

Response 200: the updated call-off object in data.

Status Code When
400 INVALID_REQUEST Nothing to record was sent; an unknown field was sent; invoice_number is blank or too long; invoiced_at was sent without invoice_number; paid isn't true; or a date isn't valid.
404 NOT_FOUND The call-off isn't in this workspace.
409 CONFLICT The call-off's state doesn't allow it (for example it already has a different invoice number, or isn't invoiced yet when recording a payment), or it changed during the request. title explains.

Webhook events: none.