POST
/
api
/
public
/
contact
/
{aiContactId}
/
update-metadata
Update metadata to an AI contact
curl --request POST \
  --url https://api.vambe.me/api/public/contact/{aiContactId}/update-metadata \
  --header 'Content-Type: application/json' \
  --header 'x-api-key: <x-api-key>' \
  --data '{
  "metadata": {}
}'

Overview

Update or add custom metadata fields to a contact. Metadata allows you to store any additional information about a contact beyond the standard fields (name, email, phone). This endpoint is intelligent - it processes unstructured data and automatically fills custom field definitions if they exist in your account. Field keys are automatically converted to snake_case for consistency.

Use Cases

  • E-commerce Data: Store order history, lifetime value, preferred products
  • Customer Segmentation: Add custom attributes for targeting and personalization
  • CRM Integration: Sync custom fields from external CRM systems
  • Lead Scoring: Store qualification scores, lead source, conversion probability
  • Business Context: Add company info, industry, decision maker status
  • Behavioral Data: Track user actions, preferences, interaction history
  • Support Information: Store account tier, support plan, SLA requirements

Authentication

This endpoint requires authentication using an API key. Include your API key in the request header:
x-api-key: your_api_key_here

Path Parameters

ParameterTypeRequiredDescription
aiContactIdstring (UUID)YesUnique identifier of the contact

Request Body

The request body is a flexible object where you can include any key-value pairs:
FieldTypeRequiredDescription
{any}anyNoAny custom field as key-value pair
You can send any metadata fields as a JSON object. Common examples:
{
  "company": "Acme Corp",
  "industry": "Technology",
  "employeeCount": "500-1000",
  "annualRevenue": "5000000",
  "leadScore": "85",
  "source": "website",
  "interests": "AI, Automation",
  "custom_field_1": "value1"
}

Response Structure

The endpoint returns an array of updated custom field values:
FieldTypeDescription
ArrayarrayArray of custom field value objects that were updated
Each item in the response array contains information about the updated field.

Example Request

curl --request POST \
  'https://api.vambe.ai/api/public/contact/1937bd2c-a13c-4365-af06-43af9a988e36/update-metadata' \
  --header 'Content-Type: application/json' \
  --header 'x-api-key: your_api_key_here' \
  --data-raw '{
    "company": "Acme Corporation",
    "position": "CEO",
    "industry": "Technology",
    "employeeCount": "100-500",
    "annualRevenue": "2000000",
    "leadScore": 95,
    "leadSource": "LinkedIn Campaign",
    "interestedProducts": "Enterprise Plan, API Access",
    "notes": "Very interested in automation features",
    "budget": "50000-100000",
    "decisionMaker": true,
    "expectedCloseDate": "2024-12-31"
  }'

Example Response

[
  {
    "id": 123,
    "key": "company",
    "value": "Acme Corporation",
    "custom_field_definition_id": 45
  },
  {
    "id": 124,
    "key": "position",
    "value": "CEO",
    "custom_field_definition_id": 46
  },
  {
    "id": 125,
    "key": "lead_score",
    "value": "95",
    "custom_field_definition_id": 47
  }
]

Common Use Cases

1. Update E-commerce Customer Data

const updateEcommerceData = async (contactId, orderData) => {
  const response = await fetch(
    `https://api.vambe.ai/api/public/contact/${contactId}/update-metadata`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'x-api-key': 'your_api_key_here',
      },
      body: JSON.stringify({
        totalOrders: orderData.orderCount.toString(),
        lifetimeValue: orderData.totalSpent.toString(),
        averageOrderValue: orderData.averageValue.toString(),
        lastPurchaseDate: orderData.lastPurchase,
        favoriteCategory: orderData.topCategory,
        customerSince: orderData.firstOrderDate,
        vipStatus: orderData.isVIP ? 'Yes' : 'No',
      }),
    },
  );

  const result = await response.json();
  console.log(`Updated ${result.length} metadata fields`);
  return result;
};

2. Lead Qualification Data

const updateLeadQualification = async (contactId, qualificationData) => {
  const response = await fetch(
    `https://api.vambe.ai/api/public/contact/${contactId}/update-metadata`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'x-api-key': 'your_api_key_here',
      },
      body: JSON.stringify({
        leadScore: qualificationData.score,
        leadGrade: qualificationData.grade, // A, B, C, D
        qualificationDate: new Date().toISOString(),
        budgetRange: qualificationData.budget,
        timeframe: qualificationData.timeframe, // Immediate, 1-3 months, 3-6 months
        authority: qualificationData.isDecisionMaker
          ? 'Decision Maker'
          : 'Influencer',
        need: qualificationData.painPoints,
        source: qualificationData.leadSource,
      }),
    },
  );

  return await response.json();
};

3. Sync from External CRM

const syncFromExternalCRM = async (contactId, crmData) => {
  // Map CRM fields to Vambe metadata
  const metadata = {
    crmContactId: crmData.id,
    accountType: crmData.account_type,
    industry: crmData.industry,
    companySize: crmData.employee_count,
    annualRevenue: crmData.revenue,
    territory: crmData.sales_territory,
    accountOwner: crmData.owner_name,
    lastActivityDate: crmData.last_activity,
    stage: crmData.sales_stage,
    probability: crmData.win_probability,
  };

  const response = await fetch(
    `https://api.vambe.ai/api/public/contact/${contactId}/update-metadata`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'x-api-key': 'your_api_key_here',
      },
      body: JSON.stringify(metadata),
    },
  );

  return await response.json();
};

4. Add Support Plan Information

const updateSupportPlan = async (contactId, planDetails) => {
  const response = await fetch(
    `https://api.vambe.ai/api/public/contact/${contactId}/update-metadata`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'x-api-key': 'your_api_key_here',
      },
      body: JSON.stringify({
        supportPlan: planDetails.tier, // Basic, Premium, Enterprise
        planStartDate: planDetails.startDate,
        planExpiryDate: planDetails.expiryDate,
        slaLevel: planDetails.sla, // 24h, 12h, 4h
        dedicatedManager: planDetails.hasManager ? 'Yes' : 'No',
        maxTicketsPerMonth: planDetails.ticketLimit.toString(),
        supportChannels: planDetails.channels.join(', '), // Email, Phone, Chat
      }),
    },
  );

  return await response.json();
};

5. Update Form Submission Data

const saveFormSubmission = async (contactId, formData) => {
  const response = await fetch(
    `https://api.vambe.ai/api/public/contact/${contactId}/update-metadata`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'x-api-key': 'your_api_key_here',
      },
      body: JSON.stringify({
        formName: formData.formType,
        submissionDate: new Date().toISOString(),
        utmSource: formData.utm_source,
        utmMedium: formData.utm_medium,
        utmCampaign: formData.utm_campaign,
        referrer: formData.referrer,
        landingPage: formData.landingPage,
        message: formData.userMessage,
      }),
    },
  );

  return await response.json();
};

6. Bulk Update Multiple Contacts

const bulkUpdateMetadata = async (contactIds, metadata) => {
  const results = [];

  for (const contactId of contactIds) {
    try {
      const response = await fetch(
        `https://api.vambe.ai/api/public/contact/${contactId}/update-metadata`,
        {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
            'x-api-key': 'your_api_key_here',
          },
          body: JSON.stringify(metadata),
        },
      );

      const result = await response.json();
      results.push({ contactId, success: true, fieldsUpdated: result.length });
    } catch (error) {
      results.push({ contactId, success: false, error: error.message });
    }

    // Add small delay to avoid rate limiting
    await new Promise((resolve) => setTimeout(resolve, 100));
  }

  return results;
};

// Usage: Apply same metadata to multiple contacts
bulkUpdateMetadata(['contact-id-1', 'contact-id-2'], {
  campaignParticipant: 'Summer Sale 2024',
  enrolled: new Date().toISOString(),
});

Key Behavior & Features

Automatic Snake Case Conversion

Field keys are automatically converted to snake_case: You send: { "companyName": "Acme Corp" } Stored as: { "company_name": "Acme Corp" } You send: { "LeadScore": "95" } Stored as: { "lead_score": "95" }

Intelligent Field Processing

The endpoint uses AI to process unstructured data and match it to your custom field definitions:
  • If a custom field definition exists for a key, it will be used
  • Data types are automatically formatted based on field definitions
  • Missing fields are created as needed
  • All fields are updated (not merged)

Update Behavior

  • Additive: New metadata fields are added to existing ones
  • Overwrite: If a field already exists, its value is updated
  • Flexible Types: Values can be strings, numbers, booleans, or objects (converted to strings)
  • No Validation: You can send any fields - they don’t need to be pre-defined

Data Type Handling

Metadata values should generally be sent as strings for consistency:
Data Type You SendHow It’s StoredExample
StringAs is"Acme Corp"
NumberConverted to string95 → "95"
BooleanConverted to stringtrue → "true"
ObjectJSON stringConverted to JSON
ArrayJoined string["a","b"] → "a, b"
Best Practice: Send everything as strings for predictable behavior.

Error Responses

Status CodeDescription
400Bad Request - Invalid metadata format
401Unauthorized - Invalid or missing API key
404Not Found - Contact not found
500Internal Server Error - Something went wrong

Important Notes

  • No Field Limit: You can send as many metadata fields as needed
  • Size Limit: Very large metadata objects may be rejected (keep under 1MB)
  • Field Names: Use descriptive, consistent naming (prefer snake_case or camelCase)
  • Retrieve Metadata: Use GET /api/public/contact/{aiContactId}/info to see all metadata
  • Type Coercion: All values are ultimately stored as strings in custom fields
  • Activity Logging: Metadata updates are logged in contact activity

Best Practices

1. Use Consistent Field Names

// Good: Consistent naming convention
const goodMetadata = {
  leadSource: 'Website',
  leadScore: '85',
  companyName: 'Acme Corp',
  isQualified: 'true',
};

// Avoid: Inconsistent naming
const badMetadata = {
  'Lead Source': 'Website', // Has spaces
  LeadScore: '85', // Mixed case
  company_name: 'Acme Corp', // Different convention
  qualified: 'true', // Missing is prefix
};

2. Include Timestamps

const metadataWithTimestamps = {
  lastUpdated: new Date().toISOString(),
  dataSource: 'API Import',
  importBatch: 'batch-2024-09-30',
  // ... other fields
};

3. Validate Before Sending

const validateAndUpdateMetadata = (contactId, metadata) => {
  // Remove null/undefined values
  const cleanMetadata = Object.entries(metadata).reduce((acc, [key, value]) => {
    if (value !== null && value !== undefined && value !== '') {
      acc[key] = String(value); // Ensure all values are strings
    }
    return acc;
  }, {});

  return fetch(
    `https://api.vambe.ai/api/public/contact/${contactId}/update-metadata`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'x-api-key': 'your_api_key_here',
      },
      body: JSON.stringify(cleanMetadata),
    },
  );
};

4. Handle Errors Gracefully

const safeUpdateMetadata = async (contactId, metadata) => {
  try {
    const response = await fetch(
      `https://api.vambe.ai/api/public/contact/${contactId}/update-metadata`,
      {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'x-api-key': 'your_api_key_here',
        },
        body: JSON.stringify(metadata),
      },
    );

    if (!response.ok) {
      throw new Error(`HTTP ${response.status}: ${await response.text()}`);
    }

    return await response.json();
  } catch (error) {
    console.error('Failed to update metadata:', error);
    // Maybe retry or log to error tracking
    throw error;
  }
};

Complete Workflow Example

// Complete workflow: Create ticket with metadata, then update it
const createAndEnrichContact = async (phoneNumber, initialData) => {
  // 1. Create ticket/contact
  const createResponse = await fetch(
    'https://api.vambe.ai/api/public/ticket/open/web-whatsapp/your_phone_id',
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'x-api-key': 'your_api_key_here',
      },
      body: JSON.stringify({
        to_phone_number: phoneNumber,
        stage_id: 'your_stage_id',
        contact_name: initialData.name,
        contact_metadata: {
          source: initialData.source,
        },
      }),
    },
  );

  const { aiContactId } = await createResponse.json();

  // 2. Enrich with additional metadata after qualification
  const enrichResponse = await fetch(
    `https://api.vambe.ai/api/public/contact/${aiContactId}/update-metadata`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'x-api-key': 'your_api_key_here',
      },
      body: JSON.stringify({
        company: initialData.company,
        industry: initialData.industry,
        employeeCount: initialData.size,
        leadScore: calculateLeadScore(initialData),
        enrichedAt: new Date().toISOString(),
        dataQuality: 'High',
      }),
    },
  );

  const metadata = await enrichResponse.json();

  console.log(`Created and enriched contact ${aiContactId}`);
  console.log(`Updated ${metadata.length} metadata fields`);

  return { aiContactId, metadata };
};

Metadata vs Tags

FeatureMetadata (This Endpoint)Tags
PurposeStore detailed dataCategorize/label contacts
FormatKey-value pairsSimple labels
QuantityUnlimited fieldsMultiple tags
SearchBy field name and valueBy tag name
Use CaseCustomer details, scores, historySegments, categories, flags
Use metadata for detailed information and tags for simple categorization.

Headers

x-api-key
string
required

API key required to authorize the request

Path Parameters

aiContactId
string
required

ID of the AI contact

Body

application/json
metadata
object

Response

Metadata added successfully.