Skip to content

Update an opportunity ​

Partial update — send only the fields you want to change. Existing values for omitted fields are preserved.

Just like Retrieve, the URL accepts either the numeric id or a contact phone number (digits only, country code prefix, no + — e.g. 201062691152).

Narrow the phone lookup with pipeline_id

The same contact phone can be linked to opportunities in more than one pipeline. When you update by phone, pass an optional pipeline_id query parameter to make sure you hit the opportunity in the pipeline you care about. Ignored when the identifier is a numeric id.

PUT/api/user/opportunities/{identifier}
Required scope: opportunities:write
Path parameters
integer | string
Query parameters
integer
Request body (JSON)
curl -X PUT "https://loopx-console.test/api/user/opportunities/98314" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"stage_id":189,"notes":"notes","status":"active","context":{"source_campaign":"summer-2026"},"tags":["vip"],"deleted_tags":[],"contact":{"name":"Ali","priority":"high"}}'
<?php

use GuzzleHttp\Client;

$client = new Client();
$response = $client->request('PUT', 'https://loopx-console.test/api/user/opportunities/98314', [
    'headers' => [
        'Authorization' => 'Bearer $TOKEN',
        'Accept'        => 'application/json',
    ],
    'json' => [
        'stage_id' => 189,
        'notes' => 'notes',
        'status' => 'active',
        'context' => [
            'source_campaign' => 'summer-2026'
        ],
        'tags' => [
            'vip'
        ],
        'deleted_tags' => [

        ],
        'contact' => [
            'name' => 'Ali',
            'priority' => 'high'
        ]
    ]
]);

$data = json_decode($response->getBody(), true);
const res = await fetch('https://loopx-console.test/api/user/opportunities/98314', {
  method: 'PUT',
  headers: {
    Authorization: 'Bearer $TOKEN',
    Accept: 'application/json',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    "stage_id": 189,
    "notes": "notes",
    "status": "active",
    "context": {
      "source_campaign": "summer-2026"
    },
    "tags": [
      "vip"
    ],
    "deleted_tags": [],
    "contact": {
      "name": "Ali",
      "priority": "high"
    }
  })
});

const data = await res.json();

Example response

{
  "status": true,
  "code": 200,
  "validation": [],
  "message": "Success",
  "data": {
    "id": 98314,
    "status": "active",
    "contact_id": 44016,
    "stage_id": 189,
    "pipeline_id": 17,
    "name": null,
    "notes": "notes",
    "created_at": "2026-07-06T12:28:57.000000Z",
    "updated_at": "2026-07-06T13:36:27.000000Z",
    "source": "facebook",
    "source_id": "120248819452900116",
    "contact_name": "Ali",
    "contact_custom_name": null,
    "contact_priority": null,
    "contact_identifier": "36537370685479@lid",
    "contact_email": null,
    "contact_custom_email": null,
    "contact_phone": "201062691152",
    "contact_custom_phone": null,
    "stage_nature": "neutral",
    "stage_name": "New Lead",
    "tags": [],
    "conversations": [
      {
        "id": 89328,
        "is_test": false,
        "last_message_at": "2026-07-06T13:10:21.000000Z",
        "messages_count": 2,
        "cost_for_user": 0.014633325,
        "status": "active",
        "last_message_content": "…last AI reply preview…",
        "avg_response_time": 0,
        "pivot": {
          "opportunity_id": 98314,
          "conversation_id": 89328
        }
      }
    ]
  }
}

The response mirrors the Retrieve response — you get the fully updated opportunity back so you don't need a follow-up request.

Path parameters ​

NameTypeRequiredDescription
identifierinteger | stringyesOpportunity id or contact phone number

Query parameters ​

NameTypeDescription
pipeline_idintegerOptional. When updating by phone, restricts the match to a single pipeline. Ignored when the identifier is a numeric id.

Request body ​

All fields optional — send only what you want to change.

FieldTypeDescription
stage_idintegerMove the opportunity to this stage (must belong to the same pipeline)
namestringMax 255 chars
notesstring | nullFree-form notes
statusenumactive, won, lost (see OpportunityStatus)
contextobject | nullArbitrary key-value metadata attached to the opportunity (merged on the AI side)
tagsstring[]Tag names to add to this opportunity (created if they don't exist yet)
deleted_tagsinteger[]Tag ids to remove from this opportunity
contact.namestring | nullContact display name override
contact.emailstring | nullValid email override
contact.phonestring | nullPhone number override
contact.priorityenumlow, medium, high (see ContactPriority)

Errors ​

CodeWhen
403Token missing opportunities:write scope
404Opportunity not found in your workspace
422stage_id doesn't exist, invalid enum value, or bad contact.email