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¶
- If
contact_idis supplied → deal links directly. - Else if
contact_phoneorcontact_emailmatches an existing contact → links to that one. - Else if
create_contact_if_missing: true→ creates a new contact and links it. - 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" }
}]
}
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
}'
Update one deal by ID (recommended for safety)¶
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_fieldsis 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 |