Contacts
List, read, create, update, archive, and restore supported contacts under existing customers or suppliers.
List contacts
GET /contacts
Required scope:
contacts:read
GET /contacts requires parentType and parentId. It is not a broad contact export.
curl "$SHELFCYCLE_API_BASE_URL/contacts?parentType=customer&parentId=customer-id&limit=10" \
-H "Authorization: Bearer $SHELFCYCLE_API_KEY"
Optional filters are q, limit, and includeArchived.
Create a contact
POST /contacts
Required scope:
contacts:create
Required header:
Idempotency-Key: <stable-key>
Add dryRun=true to check the exact intended contact write without mutating records.
Create a contact under an existing customer or supplier. Use externalId for the source contact's stable identity.
curl "$SHELFCYCLE_API_BASE_URL/contacts" \
-X POST \
-H "Authorization: Bearer $SHELFCYCLE_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: gmail-thread-123:customer-id:create-contact" \
-d '{
"parent": { "type": "customer", "id": "customer-id" },
"name": "Jane Buyer",
"externalId": "CRM-CONTACT-7781",
"title": "Purchasing",
"email": "jane@example.com",
"phone": "555-0100",
"mobilePhone": "555-0101",
"faxNumber": "555-0102",
"isEmergency": true,
"documentTypes": ["PRICE_QUOTE", "INVOICE"]
}'
{
"data": {
"type": "contact",
"id": "contact-id",
"contactKind": "customer",
"displayName": "Jane Buyer",
"subtitle": "jane@example.com | Purchasing | 555-0100",
"url": "https://app.shelfcycle.com/org-northstar/customers/customer-id/contacts",
"parent": { "type": "customer", "id": "customer-id", "displayName": "Northstar Chemical" },
"createdAt": "2026-05-29T14:30:00.000Z",
"updatedAt": "2026-05-29T14:30:00.000Z",
"name": "Jane Buyer",
"externalId": "CRM-CONTACT-7781",
"title": "Purchasing",
"email": "jane@example.com",
"phone": "555-0100",
"mobilePhone": "555-0101",
"faxNumber": "555-0102",
"isEmergency": true,
"documentTypes": ["PRICE_QUOTE", "INVOICE"],
"idempotencyStatus": "created"
}
}
Repeating the same route plus Idempotency-Key with the same request body in the same org returns the existing contact with idempotencyStatus: "replayed", even after key rotation. Keep externalId separate from the idempotency key: one identifies the source contact and the other identifies this create request.
When the source contact deliberately represents a separate person despite a reviewable name or email similarity, add top-level duplicateResolution: "create_distinct". Exact source-ID conflicts, hidden candidates, and other identity conflicts still require correction.
Read a contact
GET /contacts/{id}
Required scope:
contacts:read
For v1 compatibility and optimistic-concurrency readback, search:read or contacts:update can also read a visible contact by id. The response includes the latest updatedAt value to use as If-Match.
Update a contact
PATCH /contacts/{id}
Required scope:
contacts:update
Required header:
If-Match: <updatedAt from latest response or GET /contacts/{id}>
Sparse patch fields:
{
"name": "Jane Buyer",
"externalId": "CRM-CONTACT-7781",
"title": "Director of Purchasing",
"email": "jane@example.com",
"phone": "555-0101",
"mobilePhone": "555-0102",
"faxNumber": null,
"isEmergency": false,
"documentTypes": ["SALES_CONFIRMATION"]
}
Omitted fields stay unchanged. externalId, title, email, phone, mobilePhone, and faxNumber can be sent as null to clear. name cannot be cleared.
Supplier contacts support title, email, phone, isEmergency, and supplier document types such as PURCHASE_ORDERS and LOGISTICS. They reject mobilePhone and faxNumber because the supplier-contact model has no matching fields.
If the record changed after the updatedAt value sent in If-Match, ShelfCycle returns 409 stale_record.
Archive or restore a contact
Use POST /contacts/{id}/archive or POST /contacts/{id}/unarchive with contacts:update and the current updatedAt value in If-Match. The acting person must also have current permission to archive the contact's customer or supplier records.
Add dryRun=true to preview the action or recover the current timestamp without changing the contact. Archived contacts remain available through the selected parent's contact list with includeArchived=true. See Archive and restore for request and receipt examples.
Use contact endpoints for bounded contact details tied to the selected customer or supplier.