Directory Sync API #

Sync team-member (directory) records from any system — HR/HRIS (Workday, BambooHR), CRM (Salesforce), or your own tools. These are the same endpoints the official Zapier integration uses, so you can build directly or use Zapier.

Base path: /v2/api/pub/team-member-actions Auth: Authorization: Bearer <YOUR_API_KEY> (see getting-started). Response envelope: { "success": …, "data": …, "messages": [] } (list endpoints add meta).


Create or update a member — POST /upsert #

Adds a member, or updates them if they already exist.

  • PATCH semantics: only the fields you send are changed; omitted fields are left untouched, so a partial sync never blanks existing data. To clear a field, send it as an empty string.
  • Matching: by default members match by email. To survive email changes, send a stable employee_id and set match_field to employee_id — then an email change updates the same person instead of creating a duplicate.

Body fields #

Field Notes
first_name required
email required, must be valid
last_name, job_position, profile_image_url optional strings (profile_image_url must be publicly fetchable)
employee_id stable external ID (recommended for HR syncs)
manager_email resolved to an existing member → manager link; if not present yet, resolves on a later sync
department or departments[] one or more names; auto-created if missing. Send one or the other — if both are present, departments[] is used and department is ignored.
date_of_birth, hire_date, custom_date_1..N dates; accepts YYYY-MM-DD, M/D/YYYY, etc. (birthday year is privacy-masked automatically; Feb 29 → Feb 28)
active false deactivates, true reactivates; omit to leave unchanged (an update never silently reactivates)
match_field email (default) or employee_id
mode add_update (default) or add_new_only (skip if the member already exists)
skip_if_missing_required true → skip rows missing first_name/email instead of erroring (invalid emails still error)
dry_run true → validate and report the would-be action without saving

Example #

curl -X POST https://app.ecardwidget.com/v2/api/pub/team-member-actions/upsert \
  -H "Authorization: Bearer $ECW_API_KEY" -H "Content-Type: application/json" \
  -d '{
    "first_name": "Grace", "last_name": "Hopper", "email": "[email protected]",
    "employee_id": "EMP-100", "match_field": "employee_id",
    "job_position": "Engineer", "manager_email": "[email protected]",
    "departments": ["Engineering", "Leadership"], "hire_date": "2021-03-01"
  }'

Response data #

{ "action": "created", "id": 123, "changed_fields": ["job_position"], "warnings": [] }

action is one of created, updated, unchanged, or skipped. Sending active:false still reports created/updated (the member is just marked inactive) — the deactivated / reactivated / deleted actions come from the dedicated deactivate/reactivate/delete endpoints.


Find a member — GET /find #

GET /[email protected]
GET /find?employee_id=EMP-100

Returns the member in data and meta.found. data is empty when not found.


List members — GET /list #

Paginated, for reconciliation / verifying a sync.

Query param Notes
status active (default), deactivated, or all
perPage page size, ≤ 500
after_id cursor — pass the previous page's meta.next_after_id
updated_since ISO datetime — only members changed since then (delta pulls)

meta includes next_after_id and has_more.

Removing people (the streaming-safe replacement for a spreadsheet "Sync" that deletes everyone not in the file): periodically GET /list, diff against your source system, and deactivate anyone no longer present.


Deactivate / Reactivate — POST /deactivate, POST /reactivate #

Body: { "email": "…" } or { "employee_id": "…" }.

Deactivation is reversible: the member stops receiving automated cards and no longer counts toward your seat limit, but their record/history is kept. Reactivate to restore (subject to seat availability).


Delete (permanent) — POST /delete #

Body: { "email": "…" } or { "employee_id": "…" }.

Irreversible — for GDPR erasure only. For normal offboarding use deactivate.


Field discovery — GET /fields, GET /departments #

  • GET /fields — lists mappable fields (standard fields + your active custom date fields) with label, type, required, and accepted format (e.g. date formats). Use this to drive field mapping in your integration.
  • GET /departments — lists your departments as { "id": …, "name": "…" }.
curl https://app.ecardwidget.com/v2/api/pub/team-member-actions/fields \
  -H "Authorization: Bearer $ECW_API_KEY"

Initial backfill #

For the first load of an existing employee base, use the in-app spreadsheet importer (or page through many upsert calls). Use the API/Zapier for ongoing deltas thereafter. The bulk importer does not emit member-change webhooks — those fire only for per-record changes.

Notifications when the directory changes #

To push changes back to another system, subscribe to member-change webhooks.


Documentation