POST /v1/actions/create-contact

Upsert a contact by phone number with optional name, email, tags, and metadata.

Creates a contact, or updates the existing one if a contact with the same phone already exists in your tenant. Returns 201 regardless of whether the row was inserted or updated.

POST /api/v1/actions/create-contact

Request body

Field Type Required Notes
phone string yes International format with +. Normalized server-side.
name string no Display name
email string no Must be a valid email if provided
tags string no Comma-separated list, e.g. "vip, beta-testers, fr-FR". Tags are created on the fly if they don't exist.
metadata object no Free-form JSON, max ~64 KB
curl -X POST https://www.qyvo.io/api/v1/actions/create-contact \
  -H "Authorization: Bearer YOUR_TOKEN_HERE" \
  -H "Content-Type: application/json" \
  -d '{
    "phone": "+14155550123",
    "name": "Romain",
    "email": "[email protected]",
    "tags": "vip, fr-FR",
    "metadata": { "shopify_id": "12345", "lifetime_value": 8742.00 }
  }'
const contact = await fetch('https://www.qyvo.io/api/v1/actions/create-contact', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.QYVO_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    phone: '+14155550123',
    name: 'Romain',
    email: '[email protected]',
    tags: 'vip, fr-FR',
    metadata: { shopify_id: '12345', lifetime_value: 8742.0 },
  }),
}).then((r) => r.json());
$contact = Http::withToken(env('QYVO_TOKEN'))
    ->post('https://www.qyvo.io/api/v1/actions/create-contact', [
        'phone' => '+14155550123',
        'name' => 'Romain',
        'email' => '[email protected]',
        'tags' => 'vip, fr-FR',
        'metadata' => ['shopify_id' => '12345', 'lifetime_value' => 8742.0],
    ])
    ->json();
import os, httpx
contact = httpx.post(
    'https://www.qyvo.io/api/v1/actions/create-contact',
    headers={'Authorization': f"Bearer {os.environ['QYVO_TOKEN']}"},
    json={
        'phone': '+14155550123',
        'name': 'Romain',
        'email': '[email protected]',
        'tags': 'vip, fr-FR',
        'metadata': {'shopify_id': '12345', 'lifetime_value': 8742.0},
    },
).json()

Response — 201 Created

{
  "id": "01J1Y...",
  "phone": "+14155550123",
  "name": "Romain",
  "email": "[email protected]",
  "tags": ["vip", "fr-FR"],
  "created_at": "2026-05-07T08:14:23+00:00"
}

Errors

Status Cause
422 Validation (phone missing, invalid email, name too long)
422 No workspace configured for this account.

Idempotency

The endpoint upserts on (tenant_id, phone) — calling it twice with the same phone never creates two contacts. The second call updates name, email, metadata, and tags. To avoid wiping fields you didn't intend to change, use update-contact for partial updates.