API & Zapier

Import and export leads, enroll campaigns, and receive webhooks. Works with Zapier's built-in Webhooks by Zapier — no extra app needed.

Authentication

Create an API key in the app: Settings → API & Zapier → + Create API key (paid plans; account owner only). The full key is shown once — if you lose it, revoke it and create a new one. Send it on every request:

Authorization: Bearer drei_0b12c…
An API key is a password for your whole account. Keep it in server-side code or Zapier's header field — never in a public web page, and never in a URL.

Base URL:

https://vrgnjfatqasljgzrhyub.supabase.co/functions/v1/api/v1

Quick check that a key works (also what Zapier connection tests should call):

curl -H "Authorization: Bearer drei_..." \
  https://vrgnjfatqasljgzrhyub.supabase.co/functions/v1/api/v1/me

Basics

Endpoints

EndpointWhat it does
GET/meKey + account check (name, plan).
GET/contactsList / export leads with filters + pagination.
POST/contactsCreate 1–100 leads with duplicate handling.
GET/contacts/:idOne contact.
PATCH/contacts/:idUpdate fields, add/remove tags, append a note.
POST/contacts/:id/enrollAdd the contact to a campaign (membership only — see below).
GET/campaignsYour campaigns (ids for enrolling).
GET/deals, /deals/:idRead-only deals.
GET/properties, /properties/:idRead-only properties.

GET /contacts — export leads

Query parameters (all optional):

ParamMeaning
roleseller, buyer, both, or other
statuspending, contacted, replied, qualified, active, or notinterested
tagExact tag match
marketExact market match
created_since / updated_sinceISO 8601 timestamp, e.g. 2026-08-01T00:00:00Z — for incremental syncs and polling
qName contains (min 2 characters)
limit / offsetPage size (max 200, default 50) / start position

Rows are ordered newest-updated first and include more than the in-app CSV export: extra phones/emails, notes, mailing address, opt-out timestamps, tags, and source.

POST /contacts — import leads

Send one contact object, an array of up to 100, or {"contacts": [...], "on_duplicate": "skip"}:

{
  "name": "Jane Seller",
  "role": "seller",
  "email": "jane@example.com",
  "phone": "(512) 555-0134",
  "market": "Austin, TX",
  "tags": ["website-form"],
  "mailing_street": "100 Main St", "mailing_city": "Austin",
  "mailing_state": "TX", "mailing_zip": "78701",
  "notes": "Asked about the Travis Co. lot"
}

PATCH /contacts/:id

{ "status": "qualified", "add_tags": ["hot"], "remove_tags": ["cold"], "append_note": "Called back — motivated." }

Updatable fields: name, email, phone, company, title, contact_type, market, website, mailing_*, status, role, plus add_tags / remove_tags and append_note (notes are append-only through the API — it never overwrites your existing notes). Opt-out flags and compliance fields are not writable.

POST /contacts/:id/enroll

{ "campaign_id": "…uuid from GET /campaigns…" }
Enrollment is membership only — it adds the contact to the campaign's member list, but does not queue a message by itself. To automatically start outreach for imported leads, create an automation in the app: Tasks → Automations → When a new lead is added + only if Source is apiEnroll in campaign. The automation enrolls AND queues the first message with every compliance gate applied. Note: enrolling a contact into a campaign with AI auto-replies on arms those replies for that contact.

Webhooks (Zapier triggers)

Webhooks push events to your URL the moment they happen. Manage them in Settings → API & Zapier. Events:

EventFires when
contact.createdA contact is created — from any source (API, CSV import, website lead form, Property Records, manual).
contact.repliedA contact's status becomes replied.
contact.opted_outA contact opts out of email or sms (the payload names the channel).

Each delivery is one JSON object per POST (never an array — Zapier Catch Hooks split arrays into multiple runs, so we don't send them):

{
  "id": "delivery-uuid",
  "event": "contact.created",
  "created_at": "2026-08-24T18:00:00Z",
  "data": {
    "id": "…", "name": "Jane Seller", "email": "jane@example.com",
    "phone": "(512) 555-0134", "company": "", "role": "seller",
    "status": "pending", "market": "Austin, TX", "tags": ["website-form"],
    "source": "api", "mailing_street": "…", "mailing_city": "…",
    "mailing_state": "…", "mailing_zip": "…",
    "created_at": "…", "updated_at": "…"
  }
}

Delivery behavior:

Verifying signatures (optional)

Every delivery carries X-Drei-Event, X-Drei-Delivery, and X-Drei-Signature: t=<unix seconds>,v1=<hex>, where v1 is an HMAC-SHA256 of t + "." + rawBody using your webhook's signing secret (viewable via the Secret button in Settings). Zapier Catch Hooks can't check signatures without a Code step — that's fine; verification is optional. On your own server:

// Node.js
const crypto = require('crypto')
function verify(secret, header, rawBody, toleranceSec = 300) {
  const m = /^t=(\d+),v1=([0-9a-f]{64})$/.exec(header || '')
  if (!m) return false
  if (Math.abs(Date.now() / 1000 - Number(m[1])) > toleranceSec) return false
  const expected = crypto.createHmac('sha256', secret)
    .update(m[1] + '.' + rawBody).digest('hex')
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(m[2]))
}

Zapier recipes

Trigger: a new Direct REI lead starts your Zap

Action: your Zap creates a Direct REI lead

Alternative trigger: polling

If you'd rather poll than receive webhooks, call GET /contacts?updated_since=<last check> on a schedule — rows are newest-first with stable ids.

Questions? info@directrei.com