Update Contact
This endpoint updates an existing contact by ID.
Endpoint
Section titled “Endpoint”PUT /v1/contacts/contacts/:idAuthentication
Section titled “Authentication”Include your API key in the x-api-key header. See Authentication.
Required Scope
Section titled “Required Scope”contacts:writePath Parameters
Section titled “Path Parameters”| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Contact ID to update. |
Request Body
Section titled “Request Body”Send only the fields you want to change. Omitted fields keep their current values. The server validates the request body. A body that fails validation returns 400 with {"message": "Error parsing body"}. Unknown fields are ignored. The body must be a JSON object, even if it is empty ({}).
| Field | Type | Required | Description |
|---|---|---|---|
type | "EXTERNAL" | "INTERNAL" | No | New contact record type. Omit it to keep the current type. |
userId | string | null | When type is "INTERNAL" | ID of the user to link. Required whenever the body contains "type": "INTERNAL", even if the contact is already internal. Accepts the user ID (for example user-a) or the internal <tenantId>#<userId> form. On an internal contact, sending userId without type links a different user. Ignored for external contacts. |
email | string | When type is "EXTERNAL" | Contact email address. Required and must not be blank whenever the body contains "type": "EXTERNAL", even if the contact is already external. This includes switching an internal contact to external. Ignored for internal contacts. |
name | string | No | New contact name (external contacts only). When omitted on an internal-to-external switch, the contact keeps the name it took from the linked user. Ignored for internal contacts. |
clientId | string | null | No | New client. Send null to remove the client. The client must exist in your account and must not be deleted. |
confirmedProjectIdsToRemove | string[] | No | Project IDs from a 409 client-change preview. Confirms that those project associations can be removed. |
description | string | null | No | New contact description. Send null to clear it. |
isArchived | boolean | No | New archive state. |
hasPortalAccess | boolean | No | Turns portal access on or off. |
Example Request Body
Section titled “Example Request Body”{ "clientId": "client-ghi123jkl4567890", "type": "EXTERNAL", "name": "Contact Name", "email": "placeholder@example.com", "description": "Updated description", "hasPortalAccess": true}Example Request
Section titled “Example Request”curl -X PUT "https://api.crewsum.com/v1/contacts/contacts/contact-jkl123mno4567890" \ -H "x-api-key: $CREWSUM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "clientId": "client-ghi123jkl4567890", "type": "EXTERNAL", "name": "Contact Name", "email": "placeholder@example.com", "description": "Updated description", "hasPortalAccess": true }'const response = await fetch( "https://api.crewsum.com/v1/contacts/contacts/contact-jkl123mno4567890", { method: "PUT", headers: { "x-api-key": process.env.CREWSUM_API_KEY, "Content-Type": "application/json", }, body: JSON.stringify({ clientId: "client-ghi123jkl4567890", type: "EXTERNAL", name: "Contact Name", email: "placeholder@example.com", description: "Updated description", hasPortalAccess: true, }), },);const result = await response.json();PUT /v1/contacts/contacts/contact-jkl123mno4567890 HTTP/1.1Host: api.crewsum.comx-api-key: <your-api-key>Content-Type: application/json
{ "clientId": "client-ghi123jkl4567890", "type": "EXTERNAL", "name": "Contact Name", "email": "placeholder@example.com", "description": "Updated description", "hasPortalAccess": true}Response
Section titled “Response”On success, the API returns HTTP 200 OK. The response has no data field.
Example Response
Section titled “Example Response”{ "message": "Successfully updated contact: contact-jkl123mno4567890"}Example Conflict Response
Section titled “Example Conflict Response”When a client change needs confirmation, the API returns HTTP 409 Conflict:
{ "message": "Changing the client removes project associations", "data": { "projectsToRemove": [ { "projectId": "project-mno123pqr4567890", "projectName": "Project Name", "clientId": "client-old0000000000000", "clientName": "Old Client" } ] }}Behavior
Section titled “Behavior”- Every successful update sets
updatedAtto the current time. - Changing
typeto"INTERNAL"links the user inuserId, clears the stored contact email, and sets the name to the user’s full name. Responses then return the user’s email. - Changing
typeto"EXTERNAL"removes the linked user and stores theemailfrom the body. - An internal contact’s name is refreshed from the linked user’s full name on every update.
- Sending
clientId: nullremoves the contact’s client and keeps all of its project associations. - Moving the contact to a different client can conflict with its project associations. If the contact is associated with projects that belong to other clients, the API returns
409with those projects indata.projectsToRemove. Nothing is written. - To go ahead, send the same update again with
confirmedProjectIdsToRemoveset to exactly the project IDs from the preview. The client change and the removal of those associations are saved together. The projects themselves are not changed. If the list of affected projects has changed since the preview, the API returns a new409. - Associations removed this way are deleted permanently. No
associations.removedwebhook event is sent for them. - After the contact is updated, Crewsum attempts to send the
project-contacts.updatedwebhook event. A webhook failure does not change the response.
Error Responses
Section titled “Error Responses”| Status | Meaning |
|---|---|
400 | {"message": "Error parsing body"}. The body failed validation: it is missing or not a JSON object, a field has the wrong type, "type": "INTERNAL" was sent without userId, or "type": "EXTERNAL" was sent without a non-blank email. The response does not say which field failed. |
400 | {"message": "ContactID missing"}. The ID path segment is empty. |
400 | {"message": "Client must belong to the current tenant"}. clientId does not exist in your account, or the client is deleted. |
400 | {"message": "Internal contact user must belong to the current tenant"}. No user with this userId exists in your account. |
400 | {"message": "Email is required for external contacts"}. The contact is external and the update would leave it without an email, for example because email is an empty string. |
409 | {"message": "Changing the client removes project associations", "data": {...}}. The client change needs confirmation. See Behavior. |
500 | {"message": "Error updating contact: <id>"}. Crewsum could not update the contact. This is also the response when no contact with this ID exists in your account. |
Authentication and scope failures are described in Errors.
Related Webhooks
Section titled “Related Webhooks” project-contacts.updated View webhook event details.