Property elements
Elements describe what a property physically contains: its rooms and areas, and the appliances and fixtures inside them. They come in three layers. Categories are the organization’s taxonomy (bedroom, kitchen, dishwasher). Catalog entries are reusable make/model templates filed under a category, such as one TV model you install across many units. Property elements (element instances) are the actual things on one property, arranged as a tree of areas with items nested under them. Task checklists and repair tasks point at property elements. This guide covers the nine element operations and the three that manage element attachments. Every field is listed on the operation’s page in the API Reference.
| Operation | Request | Permission |
|---|---|---|
listElementCategories | GET /element-categories | view_properties |
listElementCatalog | GET /element-catalog | view_properties |
createElementCatalogEntry | POST /element-catalog | modify_properties |
updateElementCatalogEntry | PATCH /element-catalog/{id} | modify_properties |
deleteElementCatalogEntry | DELETE /element-catalog/{id} | modify_properties |
listPropertyElements | GET /property-elements | view_properties |
createPropertyElement | POST /property-elements | modify_properties |
updatePropertyElement | PATCH /property-elements/{id} | modify_properties |
deletePropertyElement | DELETE /property-elements/{id} | modify_properties |
listElementAttachments | GET /element-attachments | view_properties |
createElementAttachment | POST /element-attachments | modify_properties |
deleteElementAttachment | DELETE /element-attachments/{id} | modify_properties |
Paths are relative to your region’s base URL, for example https://api-us.suiteop.com/api/v1. See Authentication for permissions.
Catalog entries and property elements can be deleted permanently. The API has no read-one operation (the lists already return every field), and no way to create, rename or delete a category.
Categories
Section titled “Categories”GET /element-categories takes no parameters and returns every category as one unpaginated array, areas first and then items, each sorted by name. Retired categories are included.
curl "https://api-us.suiteop.com/api/v1/element-categories" \ -H "Authorization: Bearer sk_live_your_key_here"{ "data": [ { "id": "3c9a1f7e-2b4d-4e8a-9f10-5a6b7c8d9e01", "name": "kitchen", "level": "area", "icon": null, "isSystem": true, "parentCategoryId": null, "archived": false }, { "id": "8e2d4b6a-1c3f-4a5b-8d7e-9f0a1b2c3d4e", "name": "dishwasher", "level": "item", "icon": null, "isSystem": true, "parentCategoryId": null, "archived": false } ], "meta": { "requestId": "3f1c9a52-…" }}- Every organization is seeded with a fixed set of system categories (
isSystem: true). Theirnameis a snake_case key such asliving_roomorhalf_bathroom, not a display label. Custom categories added in the app carry the name as it was typed. - Category ids are generated per organization, so the same
kitchenhas a differentidin every organization. This operation is the only way to get one, and both create operations below require it. Look ids up bynameandlevelrather than hard-coding them. archived: truemarks a retired category. It is listed so thecategoryIdan existing element still carries resolves, but it takes no new elements: creating an element in it, or moving one onto it, returns400 validation_error. Pick categories witharchived: false.- Only
areaanditemcategories are returned. The legacysub_itemlevel is retired.
Catalog entries
Section titled “Catalog entries”A catalog entry is a template, not something placed anywhere. Linking a property element to it is optional.
Listing the catalog
Section titled “Listing the catalog”GET /element-catalog returns every entry as one unpaginated array, sorted by name. Pass categoryId to narrow it to one category.
curl "https://api-us.suiteop.com/api/v1/element-catalog?categoryId=8e2d4b6a-1c3f-4a5b-8d7e-9f0a1b2c3d4e" \ -H "Authorization: Bearer sk_live_your_key_here"Each entry carries id, name, description, categoryId, make, model, sleepingCapacity, notes and metadata.
Creating an entry
Section titled “Creating an entry”POST /element-catalog requires name (up to 200 characters) and categoryId. The response is 201 with the new entry under data.
curl -X POST https://api-us.suiteop.com/api/v1/element-catalog \ -H "Authorization: Bearer sk_live_your_key_here" \ -H "Idempotency-Key: 6f1d2c3b-4a5e-4f60-8a7b-9c0d1e2f3a4b" \ -H "Content-Type: application/json" \ -d '{ "name": "Bosch 300 Series dishwasher", "categoryId": "8e2d4b6a-1c3f-4a5b-8d7e-9f0a1b2c3d4e", "make": "Bosch", "model": "SHE53C85N", "notes": "Clean the filter monthly.", "metadata": { "warrantyYears": 2 } }'namemust be unique within its category for the organization. A duplicate returns409 conflict_error, so readlistElementCatalogbefore retrying a create. The same name in a different category is fine.categoryIdmust be anitemcategory: a catalog entry is a product, so anareacategory returns400 validation_error. One from another organization returns404 not_found_error.description(up to 2,000 characters),makeandmodel(up to 200 each),notes(up to 5,000) andmetadata(a flat JSON object) are optional.sleepingCapacity(an integer, 0 or more) is how many people this type of element sleeps, for example2for a queen bed. Placed elements linked to the entry inherit it unless they override it. It is accepted only for categories with the built-in Sleeping capacity field (beds, sofa beds, bunk beds and cribs); for any other category it returns400 validation_error. It reads back asnullfor those other categories.- The
name,descriptionandnotesyou send are translated into the organization’s other languages automatically after the create.
Updating an entry
Section titled “Updating an entry”PATCH /element-catalog/{id} takes a data object with any of name, description, categoryId, make, model, sleepingCapacity, notes and metadata. Send null to clear any of them except name and categoryId.
curl -X PATCH https://api-us.suiteop.com/api/v1/element-catalog/5b7c9d1e-2f3a-4b5c-8d6e-7f8091a2b3c4 \ -H "Authorization: Bearer sk_live_your_key_here" \ -H "Content-Type: application/json" \ -d '{"data": {"model": "SHE53C85N-2", "notes": null}}'categoryIdre-files the entry under another category at the same level. A category at a different level returns400 validation_error.- A rename onto a name its category already holds returns
409 conflict_error. metadatareplaces the whole stored object. Merge on your side before sending it.- Resending a text field unchanged doesn’t re-translate it. Only text that actually changes is re-translated, which replaces the other languages’ copies of that field.
Deleting a catalog entry
Section titled “Deleting a catalog entry”DELETE /element-catalog/{id} deletes the entry permanently and returns {"success": true}. Property elements linked to it stay where they are, with catalogEntryId cleared. The attachments the entry owns, such as manuals and photos, are deleted with it, so those elements stop showing them. There is no undo: read the entry with listElementCatalog first if you may need to recreate it.
Property elements
Section titled “Property elements”The tree
Section titled “The tree”A property’s elements form a two-level tree:
area: a room or zone, such as a kitchen, a bedroom or the pool. Areas are the roots and never have a parent. An area can carry alabelIdsaying which kind of its category it is, for example Outdoor for a pool; it isnullon items.item: something inside an area, such as a dishwasher or a smoke alarm. Every item has exactly one area as its parent, on the same property.
A third level, sub_item, has been retired. It can’t be created, and existing sub-items were converted into supply lines on their parent area or item. The tree can still show a legacy one under its item’s childInstances, so read that array rather than assuming it is empty.
A new property starts with one area per bedroom and full bathroom it was created with, a half_bathroom area for a half bath, and any areas picked when it was created. Each room is its own area. Organizations whose system arrival, completion and general area categories are still live (archived: false) also get one of each of those. Where they are retired, they were replaced by task sections, and new properties get one only while a live template group still targets that category or an item inside it.
Listing a property’s elements
Section titled “Listing a property’s elements”GET /property-elements?propertyId=… returns the property’s areas as an unpaginated array. Each area nests its items under childInstances. Each item also carries a childInstances array, which is empty unless the item still has a legacy sub_item.
curl "https://api-us.suiteop.com/api/v1/property-elements?propertyId=9a3d7e40-5b6c-4d7e-8f90-1a2b3c4d5e6f" \ -H "Authorization: Bearer sk_live_your_key_here"{ "data": [ { "id": "0d4e6f80-1a2b-4c3d-9e4f-5a6b7c8d9e0f", "name": "Kitchen", "level": "area", "categoryId": "3c9a1f7e-2b4d-4e8a-9f10-5a6b7c8d9e01", "labelId": null, "catalogEntryId": null, "propertyId": "9a3d7e40-5b6c-4d7e-8f90-1a2b3c4d5e6f", "parentInstanceId": null, "quantity": 1, "sleepingCapacity": null, "serialNumber": null, "make": null, "model": null, "description": null, "notes": null, "lastServicedAt": null, "warrantyExpiresAt": null, "metadata": null, "condition": null, "childInstances": [ { "id": "7a8b9c0d-1e2f-4a3b-8c4d-5e6f7a8b9c0d", "name": "Dishwasher", "level": "item", "categoryId": "8e2d4b6a-1c3f-4a5b-8d7e-9f0a1b2c3d4e", "labelId": null, "catalogEntryId": "5b7c9d1e-2f3a-4b5c-8d6e-7f8091a2b3c4", "propertyId": "9a3d7e40-5b6c-4d7e-8f90-1a2b3c4d5e6f", "parentInstanceId": "0d4e6f80-1a2b-4c3d-9e4f-5a6b7c8d9e0f", "quantity": 1, "sleepingCapacity": null, "serialNumber": "FD9601234567", "make": "Bosch", "model": "SHE53C85N", "description": null, "notes": null, "lastServicedAt": "2026-06-01T00:00:00.000Z", "warrantyExpiresAt": "2027-06-01T00:00:00.000Z", "metadata": null, "condition": { "result": "flag", "answerLabel": "Worn", "checkedAt": "2026-09-28T14:05:00.000Z" }, "childInstances": [] } ] } ], "meta": { "requestId": "3f1c9a52-…" }}conditionis the element’s newest condition check from a task: itsresult(pass,flag,failorna, typed as an open string), theanswerLabelin your language when there is a translation, andcheckedAt;resultandanswerLabelmay each benull. It isnullwhen the element has never been checked, and when the caller holds neitherview_tasksnormodify_tasks. Create and update responses always returnnull. An answer list withlogsElementConditionon records its answers as checks; see Answer lists.- Order is display order: siblings appear in the order they were created. There is no reorder operation.
- An unknown property, one in another organization, or one outside an OAuth token’s property access returns an empty array, not
404. An empty result doesn’t prove the property exists. - Only elements filed on the property itself are listed. Elements an organization shares at the property-group level don’t appear here, and the API has no operation that lists them.
levelis typed as an open string so a future level can still be read. Handle values other thanareaanditem.
Creating an element
Section titled “Creating an element”POST /property-elements requires name, categoryId, level and propertyId. The reference marks propertyId optional, but a create without it is rejected with 400 validation_error. The response is 201 with the new element under data, without childInstances. Create an area first, then its items:
# 1. The areacurl -X POST https://api-us.suiteop.com/api/v1/property-elements \ -H "Authorization: Bearer sk_live_your_key_here" \ -H "Idempotency-Key: 2a3b4c5d-6e7f-4a8b-9c0d-1e2f3a4b5c6d" \ -H "Content-Type: application/json" \ -d '{ "name": "Kitchen", "level": "area", "categoryId": "3c9a1f7e-2b4d-4e8a-9f10-5a6b7c8d9e01", "propertyId": "9a3d7e40-5b6c-4d7e-8f90-1a2b3c4d5e6f" }'
# 2. An item in it, using the area's data.id as parentInstanceIdcurl -X POST https://api-us.suiteop.com/api/v1/property-elements \ -H "Authorization: Bearer sk_live_your_key_here" \ -H "Idempotency-Key: 3b4c5d6e-7f8a-4b9c-8d0e-2f3a4b5c6d7e" \ -H "Content-Type: application/json" \ -d '{ "name": "Dishwasher", "level": "item", "categoryId": "8e2d4b6a-1c3f-4a5b-8d7e-9f0a1b2c3d4e", "propertyId": "9a3d7e40-5b6c-4d7e-8f90-1a2b3c4d5e6f", "parentInstanceId": "0d4e6f80-1a2b-4c3d-9e4f-5a6b7c8d9e0f", "catalogEntryId": "5b7c9d1e-2f3a-4b5c-8d6e-7f8091a2b3c4", "make": "Bosch", "model": "SHE53C85N", "serialNumber": "FD9601234567" }'levelisareaoritem.sub_itemis rejected with400 validation_error.categoryIdmust be a live category (archived: false); a retired one returns400 validation_error.parentInstanceIdis required for an item and must be an area; leave it out for an area. An item without a parent, an area with one, or an item under another item returns400 validation_error. A parent that doesn’t exist or that you can’t reach returns404 not_found_errorfirst.propertyIdis required for areas and items alike. For an item it must be the same property as its parent area, or the create returns400 validation_error.groupIdappears in the schema but is rejected with400 validation_error. This operation places elements on a property only.catalogEntryIdonly links the template.make,model,notesand the other text fields are not copied from it, so send them yourself if you want them on the element.sleepingCapacityis the exception: see below.labelIdis for areas only; on an item it returns400 validation_error. It must be a label of the area’s own category. The API has no operation that lists labels: reuse alabelIdthatlistPropertyElementsreturns on an existing area of that category.quantity: leave it out. Each physical thing is its own element, so a pair of nightstands is twocreatePropertyElementcalls. The field is still accepted only so older SuiteOp apps keep working, and it’s being removed. If you send it, it creates that many separate elements and returns only the first: read the others’ ids withlistPropertyElements. The copies of an area are named<name> 2to<name> N, the copies of an item keep the name, and no copy gets theserialNumberornotes. Every element reads back withquantity: 1.sleepingCapacityoverrides the catalog entry’s value for this placement. Omit it to inherit. As on catalog entries, it’s accepted only for categories with the built-in Sleeping capacity field, and returns400 validation_errorfor any other.- A
categoryId,catalogEntryId,propertyIdorparentInstanceIdfrom another organization returns404 not_found_error, and so does a property outside an OAuth token’s property access. - The
name,descriptionandnotesyou send are translated into the organization’s other languages automatically.
Updating an element
Section titled “Updating an element”PATCH /property-elements/{id} takes a data object with any of name, description, categoryId, labelId, catalogEntryId, sleepingCapacity, serialNumber, make, model, lastServicedAt, warrantyExpiresAt, notes and metadata. Send null to clear any of them except name and categoryId.
curl -X PATCH https://api-us.suiteop.com/api/v1/property-elements/7a8b9c0d-1e2f-4a3b-8c4d-5e6f7a8b9c0d \ -H "Authorization: Bearer sk_live_your_key_here" \ -H "Content-Type: application/json" \ -d '{"data": {"lastServicedAt": "2026-09-15T00:00:00Z", "notes": "Replaced drain pump."}}'categoryIdre-files the element under another category at the same level: a room can’t become furniture. A category at a different level, or a retired one, returns400 validation_error. Re-filing clears the element’slabelIdunless you send a label of the new category in the same call.- An element can’t be moved to another property or parent, and its level can’t change. Those fields are rejected with
400 validation_error. To move something, create a new element where it belongs. quantityis still accepted for older clients but ignored. To add more of something, create one element per item.lastServicedAtandwarrantyExpiresAtare ISO 8601 timestamps in UTC, such as2026-09-15T00:00:00Z. They come back with milliseconds.catalogEntryId: nullunlinks the template.sleepingCapacity: nulldrops the override so the catalog value applies again.metadatareplaces the whole stored object, and unchanged text isn’t re-translated, as for catalog entries.- An element in another organization, or on a property outside an OAuth token’s access, returns
404 not_found_error.
Deleting an element
Section titled “Deleting an element”DELETE /property-elements/{id} deletes the element permanently and returns {"success": true}. It also deletes every item nested under it, and the attachments and inventory items recorded against each deleted element. Tasks, task checklist rows and scopes that pointed at a deleted element keep existing but lose that link. There is no undo: read the tree with listPropertyElements first if you may need to recreate it.
- While your organization’s system
arrival,completionandgeneralcategories are live, the last area of each on a property can’t be deleted: the call returns403. A duplicate of one of those areas can go while another remains. Once a category is retired, its leftover areas can be deleted. - If the element moves to another category, property or level while the delete runs, the call returns
409. Read it again and retry.
Sleeping capacity
Section titled “Sleeping capacity”A property’s sleeping total is derived from its elements: each element counts its own sleepingCapacity, or its catalog entry’s when its own is null, or 0. Only elements whose category has the Sleeping capacity field count, and elements the property’s group shares count too. It doesn’t change the property’s maxGuests.
Attachments
Section titled “Attachments”An element or a catalog entry can hold files for staff: manuals, warranties, receipts and photos. A file on a catalog entry shows on every element linked to it. Attachments are private staff documents. They aren’t shown to guests; for a guest-facing house manual use createPortalManual.
Listing attachments
Section titled “Listing attachments”GET /element-attachments returns an unpaginated array, sorted by attachmentType and then oldest first. It returns each file’s name, type and size, not its contents or a download link.
curl "https://api-us.suiteop.com/api/v1/element-attachments?instanceId=7a8b9c0d-1e2f-4a3b-8c4d-5e6f7a8b9c0d" \ -H "Authorization: Bearer sk_live_your_key_here"{ "data": [ { "id": "c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f", "fileName": "Dishwasher warranty.pdf", "fileType": "application/pdf", "size": 284113, "attachmentType": "warranty", "catalogEntryId": null, "instanceId": "7a8b9c0d-1e2f-4a3b-8c4d-5e6f7a8b9c0d", "createdAt": "2026-10-12T09:14:02.000Z" } ], "meta": { "requestId": "…" }}instanceIdreturns the element’s own files plus those of its linked catalog entry. Tell them apart by which ofinstanceIdandcatalogEntryIdis set.catalogEntryIdalone returns that entry’s files.- With neither, you get every catalog entry’s files plus those of the elements on properties you can reach.
- An element you can’t reach, or one that doesn’t exist, returns an empty array rather than
404.
Attaching a file
Section titled “Attaching a file”You can’t upload bytes to this operation. POST /element-attachments copies a file SuiteOp already holds, from one of two sources:
dropBoxFileId: areadyfile from an image drop box, theidthatlistDropBoxImagesreturns. A PDF is copied as the PDF, up to 10 MB. An image is copied as its re-encoded JPEG, and its name ends in.jpg.fileUrl: one of your organization’s SuiteOp image links, for example animageUrlanother operation returned. It must be a PDF or an image of at most 10 MB.
Send exactly one source, and exactly one owner: instanceId (an element from listPropertyElements) or catalogEntryId (an entry from listElementCatalog). attachmentType is required: manual, warranty, photo, receipt or other.
curl -X POST https://api-us.suiteop.com/api/v1/element-attachments \ -H "Authorization: Bearer sk_live_your_key_here" \ -H "Idempotency-Key: 4d5e6f70-8192-4a3b-9c4d-5e6f7a8b9c0d" \ -H "Content-Type: application/json" \ -d '{ "instanceId": "7a8b9c0d-1e2f-4a3b-8c4d-5e6f7a8b9c0d", "attachmentType": "warranty", "dropBoxFileId": "9c4d2e7f-3a1b-4c8d-9e6f-1a2b3c4d5e6f", "fileName": "Dishwasher warranty.pdf" }'The response is 201 with the attachment, in the same shape as a list row.
fileName(up to 255 characters) is the name shown on the element. Omit it to keep the source’s name.- The file is copied into private storage, so the attachment outlives the drop box, which is deleted 30 days after its newest upload.
400 validation_error: both or neither owner, both or neither source, a drop box file that is stillpending,failedor over 10 MB, or afileUrlthat isn’t one of your SuiteOp links, is over 10 MB, or isn’t a PDF or an image.404 not_found_error: an element, catalog entry or drop box file of another organization, an element on a property you can’t reach, or afileUrlwhose file doesn’t exist.
Deleting an attachment
Section titled “Deleting an attachment”DELETE /element-attachments/{id} deletes the attachment permanently and returns {"success": true}. The stored file is removed right after. Deleting a catalog entry’s file removes it from every element linked to that entry. There is no undo. An attachment of an element you can’t reach returns 404 not_found_error.
Attachments are also deleted with their owner: see Deleting a catalog entry and Deleting an element.
How elements connect to tasks
Section titled “How elements connect to tasks”Property element ids are what the task operations take when work is about one specific thing:
createTaskacceptselementInstanceId, for example to file a repair against one dishwasher so it lands in that element’s history. It needs apropertyId. Elements shared by the property’s group are accepted too.updateTaskcan re-pointelementInstanceIdor clear it withnull. It is checked against the property the task ends up on, so moving a task to another property while it still points at the old property’s element is rejected. Sendnullin the same call.listTasksfilters byelementInstanceId, which gives the repair history of one element. Combine it withstatusesto check whether a repair is already open.addTaskRequirement(POST /task-requirements) attaches a checklist row to one element of the task’s property withelementInstanceId. Instead,elementCategory(general,arrivalorcompletion) targets that property’s mandatory area and creates it if it is missing.- Template requirement groups (
addTemplateGroup,POST /task-template-groups) target categories, not elements.targetCategoryId, a category id fromlistElementCategories, makes the group repeat once for every matching element on the task’s property.targetLevel(areaoritem) andparentCategoryIdnarrow the match.targetLevelwithouttargetCategoryIdis rejected, and so isparentCategoryIdwithtargetLevel: "area". So the elements a property has decide which checklist rows a templated task gets.