# Update a contact > Move someone through your funnel, merge attributes, set your own id, or override a trust verdict. Source: https://getlaunchlist.com/help/docs/api/contacts-update --- Requires the `contacts:write` scope. ## Body parameters | Parameter | Type | Description | | ---------------- | ------ | -------------------------------------------------------------------------------------------------- | | `stage` | string | Must be one of **this project's** configured pipeline stages | | `attributes` | object | Merged by key, up to 50 keys. A `null` value deletes that key | | `email` | string | The address the contact is mailed at, for a contact identified by something else. `null` clears it | | `external_id` | string | Your own id, unique per project. `null` clears it | | `trust_override` | enum | `verified`, `valid`, `unknown`, `risky` or `fake`. `null` hands the verdict back to the engine | Every parameter is optional; send only what you are changing. Identity is not among them, for the reason below. > [!NOTE] An override recomputes the tier in the same request > Setting `trust_override` recomputes the verdict immediately, so the `trust_tier` in this response > already reflects the override. That tier is what the referral auto-gate reads, so an override here > takes effect at once rather than on a later pass. > [!WARNING] Moving somebody to an invited stage mints a real invitation > Setting `stage` to a stage that carries the **invited** role issues that contact a genuine > invitation code, because an invited contact whose invite email has a blank link is worse than no > email at all. This is a real side effect on a real person, not a label change. An `external_id` another contact in this project already holds returns `422 external_id_taken`. ## Attributes merge, they do not replace Sending `attributes` updates only the keys you send. Everything else stays. ```json { "attributes": { "cohort": "beta-2", "source": null } } ``` That sets `cohort` and deletes `source`, leaving every other attribute untouched. Replacing the whole object would mean every caller has to read-modify-write, which is a race waiting to happen. ## Contacts past your plan's size A contact hidden past your plan's contact size cannot be edited. The request answers **403** with `code: contact_hidden` and `param: contact`, and nothing is written: `stage`, `attributes`, `trust_override` and `external_id` stay as they were. The contact is saved, and editing works again once the workspace moves up a size. Deleting a hidden contact still works. ## Changing the email address `email` is where a contact is mailed, which is separate from who they are. On a contact identified by a phone number, a username or a wallet you can set it, change it, or clear it with `null`. It is canonicalized before it is stored. Changing or clearing an address the contact already has resets what was known about the old one: `verified_at` becomes `null` and `deliverability` returns to `unknown`, because both were facts about a mailbox the contact no longer uses. `status` stays as it is. Setting an address on a contact that had none resets nothing. On a contact identified by an email, `email` is ignored. There the address is the identifier. ## Identity is not editable There is no way to change `identifier`. Identity is **resolved**, not edited. A different identifier is a different person, and quietly rewriting one would corrupt deduplication and the referral graph. If you genuinely have two records for one human, that is a merge, and merging is not something this endpoint should do silently. > [!TIP] Moving people in bulk? Use stages, then automate > Set `stage` to `invited` for the people you are inviting, and let an > [automation](/help/docs/email/automations) send the mail on that trigger. That is far less code > than emailing them yourself and it keeps the record straight. ## Telling us somebody converted `converted` is the one stage nothing in LaunchList ever sets for you, and that is deliberate: whether somebody paid happens inside your product, on your servers, and we would only be guessing. This endpoint is how you close that loop. ```json { "stage": "converted" } ``` Call it when the thing you actually care about happens: subscription created, plan upgraded, first order placed. The same applies to any stage you added yourself: `stage` accepts any key from **your** funnel, so a project with an `activated` stage can report activation the same way. ## Next - [Delete a contact](/help/docs/api/contacts-delete) - [Core concepts](/help/docs/getting-started/concepts) for what a stage is