Skip to main content

Requirements

You need the account owner or admin role and a system that can make HTTPS requests. Each key belongs to one Eva account.

What syncs

You can also search records and read or add opportunity notes. The API does not delete records, create fields, or assign your own external_id. Save Eva’s id in your system. API calls do not consume credits.

Connect

  1. Open Settings → Integrations → Eva API and choose Create key.
  2. Name the system and choose Read only (crm:read) or Read and write (crm:write). A write key can also read.
  3. Copy the eva_sk_… key immediately. Eva shows its complete value once. Store it as a server secret, never in a public web page.
Send Authorization: Bearer <key> and a real User-Agent so Cloudflare accepts the request:
Create or update a contact by email or phone:
The response says created: true for a new contact and false for a matching one. Use GET /fields?entity=contact for valid keys; add entity=opportunity&pipeline_id=<id> for opportunity fields. Top-level source is the contact origin; do not put it in fields. Eva trims it, accepts up to 128 characters, and returns 422 for a blank string. On POST, it fills a matched contact’s origin only when that origin is empty; null leaves it unchanged. PATCH /contacts/{id} with {"source":"Trade show"} replaces the origin, and {"source":null} clears it. Contact responses include source. Any other text is accepted, including the panel’s origin options such as website, referral or meta_ads. Only these case-insensitive system values return 422 invalid_source: capture, personal_whatsapp_import, tokko_import, shopify, and any value starting with connected:. If the stored origin is one of those system values, PATCH with source returns 422 invalid_source and POST leaves it unchanged. An incoming WhatsApp message preserves an origin that is already set.

Endpoints and test

All paths start with /api/v1/public/crm. Use GET /contacts, GET /contacts/{id}, POST /contacts, PATCH /contacts/{id}, the corresponding /opportunities routes, and GET /fields, GET /owners, GET /pipelines. Lists accept ISO 8601 updated_since, a 1–100 limit, and the opaque cursor from the previous page. Opportunities also accept pipeline_id and stage_id. On POST /opportunities, send Idempotency-Key: <unique-identifier> and reuse it for retries; Eva honors a replay of the same request for 30 days. POST /contacts needs an email or a phone: if either already belongs to a contact, Eva updates that contact instead of creating another. Search contact names, emails, phones, and fields enabled for search; search opportunity titles and linked contacts. q is trimmed and must contain 2–100 characters, type is contact or opportunity, and limit is 1–25. The response contains type and data with the same fields as public contact or opportunity reads:
Read an opportunity’s notes with GET /opportunities/{id}/notes?limit=50; pass next_cursor back as cursor for the next page. Newest notes come first. author.kind is api_key, member, or system, and author.name shows the key or member name.
Notes require nonblank text (up to 10,000 characters) and do not accept attachments. Reuse the idempotency key on retries: Eva returns the same note with replayed: true for 30 days; a different request with that key returns 409. Start with GET /owners: 200 confirms the key works and lists active members; 401 means the key is absent, invalid or revoked; 403 means the operation needs another scope.

Change notifications

Configure Custom webhook in Integrations with your HTTPS receiver and signing secret. Deliveries include X-Eva-Event-Id and, when a signing secret exists, X-Eva-Timestamp and X-Eva-Signature-256. Verify the signature before processing the body and deduplicate by event ID. Each record’s events carry an increasing revision and can arrive out of order: keep the one with the highest revision for each record. Changes made through the API arrive with source: "api". The webhook connection is separate from your API key; revoking the key does not disconnect it.

Eva appointments in your CRM

GET /appointments lists visits linked to a contact or opportunity, and GET /appointments/{id} reads one. Each record has Eva’s event ID, eva_contact_id and eva_opportunity_id (either may be null, but at least one is valid), UTC start and end, timezone, location, owner, status (active or cancelled), and updated_at; it contains no title or notes. For example, a contact-only visit has "eva_contact_id": "44444444-4444-4444-8444-444444444444", "eva_opportunity_id": null. A cancellation remains readable after Eva deletes the visit. The list accepts limit from 1 to 100, timezone-aware updated_since, and the previous page’s cursor. Scan all pages and overlap incremental reads by a few minutes. These routes are read-only: your system cannot edit Eva appointments. To receive notices, enable Appointments → outbound on Custom webhook. It starts off and requires a signing secret. appointment.upserted carries the same object as GET /appointments/{id}. status: "cancelled" means mark the visit cancelled in your CRM. Verify the HMAC-SHA256 signature over the exact bytes of <X-Eva-Timestamp>.<body>, deduplicate by X-Eva-Event-Id, and apply only the highest revision for each visit. Recover missed notices through the paginated list; enabling the webhook does not replay historical visits.

Revoke and errors

Choose Revoke next to the key in Eva API. Its next call returns 401. Existing records remain. To change permissions, create another key, switch your system, then revoke the old one.

Privacy and limits

Each key accesses only its account. Eva stores a hash of the secret plus its name, scopes, and last-use time. The limit is 120 requests per minute per key. Use pagination and updated_since for incremental sync rather than rereading the whole account.