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_idand setmatch_fieldtoemployee_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, anddeactivateanyone 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) withlabel,type,required, and acceptedformat(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.