Skip to content

Deals & Contacts API Guide

Complete reference for creating deals, managing contacts, searching, updating, and storing external data.

Base URLs: - Bulk Deals: https://txpaxbxhnvnhsjwwaeoy.supabase.co/functions/v1/create-deals-bulk - REST Proxy: https://txpaxbxhnvnhsjwwaeoy.supabase.co/functions/v1/rest-api-proxy

Auth: Header X-API-Key: cg_your_api_key_here


1. How Deals Work

Each deal in ConnectGain has:

Field Type Required Notes
title string Free-text deal name (e.g., "Order 12345")
value number Numeric amount (NOT a string)
currency string ISO 4217 (e.g., SAR, USD, EUR)
stage string Must be lowercase pipeline stage ID
contact_id / contact_phone / contact_email ✅ (one of) How the deal links to a customer
create_contact_if_missing bool Auto-create contact if not found
expected_close_date ISO date e.g., "2025-12-31"
probability 0–100 Win probability %
source string e.g., shopify, referral, website
tags string[] Free-form labels
custom_fields object Any external structured data (JSON)

Valid stage values (default pipeline)

Stage ID Display
new New
lead Lead
qualified Qualified
proposal Proposal
won Won
lost Lost

⚠️ Stages are case-sensitive lowercase. "QUALIFIED" will be saved but won't appear in the dashboard pipeline.

How contact linking works

  1. If contact_id is supplied → deal links directly.
  2. Else if contact_phone or contact_email matches an existing contact → links to that one.
  3. Else if create_contact_if_missing: true → creates a new contact and links it.
  4. Else → request fails with Contact not found.

2. Create a Single Deal (existing contact by phone)

curl -X POST 'https://txpaxbxhnvnhsjwwaeoy.supabase.co/functions/v1/create-deals-bulk' \
 -H 'X-API-Key: cg_your_api_key_here' \
 -H 'Content-Type: application/json' \
 -d '{
 "deals": [{
 "title": "Order 68297795",
 "value": 5000,
 "currency": "SAR",
 "stage": "new",
 "contact_phone": "+201017177777"
 }]
 }'

If the contact with phone +201017177777 exists → deal is linked. If not → request fails (no create_contact_if_missing).


3. Create a Deal with create_contact_if_missing

⚠️ Common mistake fixed below: "stage": "QUALIFIED" (uppercase) saves the deal but it won't appear in the dashboard pipeline. Always use lowercase: "qualified".

❌ Wrong (deal created but invisible on dashboard)

{
 "default_stage": "QUALIFIED",
 "deals": [{
 "title": "Order 68297792",
 "value": "11523.00",
 "contact": { "phone": "+201017177777" }
 }]
}
Problems: uppercase stage, value as string, nested contact object, no create_contact_if_missing.

✅ Correct bulk create with mixed contact lookup

curl -X POST 'https://txpaxbxhnvnhsjwwaeoy.supabase.co/functions/v1/create-deals-bulk' \
 -H 'X-API-Key: cg_your_api_key_here' \
 -H 'Content-Type: application/json' \
 -d '{
 "default_currency": "SAR",
 "default_stage": "qualified",
 "skip_if_exists": true,
 "deals": [
 {
 "title": "Order 68297792",
 "value": 11523,
 "contact_phone": "+201017177777",
 "create_contact_if_missing": true,
 "contact_first_name": "Customer"
 },
 {
 "title": "Order 68297793",
 "value": 4500,
 "contact_phone": "+201020000000",
 "create_contact_if_missing": true,
 "contact_first_name": "Sara"
 },
 {
 "title": "Order 68297794",
 "value": 8900,
 "contact_email": "vip@example.com",
 "create_contact_if_missing": true,
 "contact_first_name": "VIP"
 }
 ]
 }'

Fix existing deals saved with wrong stage casing

curl -X PATCH \
 'https://txpaxbxhnvnhsjwwaeoy.supabase.co/functions/v1/rest-api-proxy/deals?stage=eq.QUALIFIED' \
 -H 'X-API-Key: cg_your_api_key_here' \
 -H 'Content-Type: application/json' \
 -d '{ "stage": "qualified" }'

Single deal with new contact

When the contact may or may not exist — let the API handle both cases:

curl -X POST 'https://txpaxbxhnvnhsjwwaeoy.supabase.co/functions/v1/create-deals-bulk' \
 -H 'X-API-Key: cg_your_api_key_here' \
 -H 'Content-Type: application/json' \
 -d '{
 "deals": [{
 "title": "Order 68297796",
 "value": 8900,
 "currency": "SAR",
 "stage": "qualified",
 "contact_phone": "+201020000000",
 "contact_email": "sara@example.com",
 "contact_first_name": "Sara",
 "contact_last_name": "Ahmed",
 "create_contact_if_missing": true
 }]
 }'

Response includes is_new_contact: true if a new one was created.

Bulk version with shared defaults

curl -X POST 'https://txpaxbxhnvnhsjwwaeoy.supabase.co/functions/v1/create-deals-bulk' \
 -H 'X-API-Key: cg_your_api_key_here' \
 -H 'Content-Type: application/json' \
 -d '{
 "default_currency": "SAR",
 "default_stage": "qualified",
 "skip_if_exists": true,
 "deals": [
 {
 "title": "Order 1001",
 "value": 11523,
 "contact_phone": "+201017177777",
 "create_contact_if_missing": true,
 "contact_first_name": "Ahmed"
 },
 {
 "title": "Order 1002",
 "value": 4500,
 "contact_email": "sara@example.com",
 "create_contact_if_missing": true,
 "contact_first_name": "Sara"
 }
 ]
 }'

skip_if_exists: true prevents duplicate deals (same title + same contact).


4. Search Deals by Title (contains) → then Update

The REST proxy supports PostgREST ilike for case-insensitive partial match.

Search deals where title contains "Order 6829"

curl -X GET \
 'https://txpaxbxhnvnhsjwwaeoy.supabase.co/functions/v1/rest-api-proxy/deals?title=ilike.*Order%206829*&order=created_at.desc' \
 -H 'X-API-Key: cg_your_api_key_here'

URL-encode spaces as %20. The * acts as wildcard (% in SQL).

Update all matching deals (e.g., move to won)

curl -X PATCH \
 'https://txpaxbxhnvnhsjwwaeoy.supabase.co/functions/v1/rest-api-proxy/deals?title=ilike.*Order%206829*' \
 -H 'X-API-Key: cg_your_api_key_here' \
 -H 'Content-Type: application/json' \
 -d '{
 "stage": "won",
 "probability": 100
 }'
curl -X PATCH \
 'https://txpaxbxhnvnhsjwwaeoy.supabase.co/functions/v1/rest-api-proxy/deals?id=eq.9ba51a0e-fa67-4fc2-8af7-6f4b9fedafc2' \
 -H 'X-API-Key: cg_your_api_key_here' \
 -H 'Content-Type: application/json' \
 -d '{
 "stage": "proposal",
 "value": 12000,
 "expected_close_date": "2025-12-31"
 }'

5. Search Contacts by Phone or Email

Phones and emails are stored as arrays → use the cs (contains) operator.

Search by phone

curl -X GET \
 'https://txpaxbxhnvnhsjwwaeoy.supabase.co/functions/v1/rest-api-proxy/contacts?phones=cs.{%22%2B201017177777%22}' \
 -H 'X-API-Key: cg_your_api_key_here'

%22 = " and %2B = +. Decoded: phones=cs.{"+201017177777"}

Search by email

curl -X GET \
 'https://txpaxbxhnvnhsjwwaeoy.supabase.co/functions/v1/rest-api-proxy/contacts?emails=cs.{%22sara@example.com%22}' \
 -H 'X-API-Key: cg_your_api_key_here'

Search by name (partial, case-insensitive)

curl -X GET \
 'https://txpaxbxhnvnhsjwwaeoy.supabase.co/functions/v1/rest-api-proxy/contacts?first_name=ilike.*sara*' \
 -H 'X-API-Key: cg_your_api_key_here'

Update a contact found by phone

curl -X PATCH \
 'https://txpaxbxhnvnhsjwwaeoy.supabase.co/functions/v1/rest-api-proxy/contacts?phones=cs.{%22%2B201017177777%22}' \
 -H 'X-API-Key: cg_your_api_key_here' \
 -H 'Content-Type: application/json' \
 -d '{
 "first_name": "Ahmed",
 "last_name": "Ali",
 "tags": ["vip", "shopify"]
 }'

6. Storing External Data (custom_fields)

Both deals and contacts have a custom_fields JSONB column for any external data (Shopify order ID, ERP ref, invoice number, anything).

Deal with external Shopify metadata

curl -X POST 'https://txpaxbxhnvnhsjwwaeoy.supabase.co/functions/v1/create-deals-bulk' \
 -H 'X-API-Key: cg_your_api_key_here' \
 -H 'Content-Type: application/json' \
 -d '{
 "deals": [{
 "title": "Shopify Order #68297797",
 "value": 7500,
 "currency": "SAR",
 "stage": "new",
 "source": "shopify",
 "contact_phone": "+201017177777",
 "create_contact_if_missing": true,
 "tags": ["shopify", "online"],
 "expected_close_date": "2025-12-15",
 "custom_fields": {
 "shopify_order_id": "68297797",
 "shopify_order_url": "https://mystore.myshopify.com/admin/orders/68297797",
 "payment_method": "mada",
 "shipping_city": "Riyadh",
 "items_count": 3,
 "external_invoice_ref": "INV-2025-0042"
 }
 }]
 }'

❌ Do not send "custom_fields": "None" (string). Either omit it or send a real object {}.

Contact with external CRM/ERP IDs

curl -X POST 'https://txpaxbxhnvnhsjwwaeoy.supabase.co/functions/v1/rest-api-proxy/contacts' \
 -H 'X-API-Key: cg_your_api_key_here' \
 -H 'Content-Type: application/json' \
 -d '{
 "first_name": "Sara",
 "last_name": "Ahmed",
 "emails": ["sara@example.com"],
 "phones": ["+201020000000"],
 "source": "external_erp",
 "tags": ["vip"],
 "custom_fields": {
 "erp_customer_id": "ERP-99821",
 "loyalty_tier": "gold",
 "lifetime_value": 45200,
 "preferred_language": "ar",
 "external_sync_at": "2025-04-22T10:00:00Z"
 }
 }'

Update only custom_fields on an existing deal

curl -X PATCH \
 'https://txpaxbxhnvnhsjwwaeoy.supabase.co/functions/v1/rest-api-proxy/deals?id=eq.DEAL_UUID' \
 -H 'X-API-Key: cg_your_api_key_here' \
 -H 'Content-Type: application/json' \
 -d '{
 "custom_fields": {
 "tracking_number": "DHL-9988776655",
 "fulfillment_status": "shipped"
 }
 }'

⚠️ custom_fields is replaced, not merged. Always send the full object you want to keep.


7. Common Pitfalls

Mistake Fix
"value": "11523.00" (string) Use number: "value": 11523
"stage": "QUALIFIED" Lowercase: "stage": "qualified"
"contact": { "phone": "..." } (nested) Flat: "contact_phone": "..."
"custom_fields": "None" Omit, or use {} / a real object
Missing create_contact_if_missing → "Contact not found" Add "create_contact_if_missing": true
Duplicate deals on retries Add "skip_if_exists": true

8. Useful Operators (REST proxy)

Operator Example Meaning
eq ?stage=eq.won Equals
neq ?stage=neq.lost Not equals
gt/gte/lt/lte ?value=gte.1000 Numeric comparisons
ilike ?title=ilike.*order* Case-insensitive contains
in ?stage=in.(new,qualified) In list
cs ?phones=cs.{"+20101..."} Array contains
is ?company_id=is.null Is null
order ?order=created_at.desc Sort
limit ?limit=50 Page size