Export API
The Export API lets you query the results of a motion's execution. Use it to retrieve contacts and accounts that have been processed by a motion, along with their evaluation outcomes and action summaries.
This API is ideal for building custom integrations that need to poll for motion results at regular intervals (e.g., every minute).
Authentication
All requests must be authenticated using your Server Secret Key (starts with sk_live_). You can find this in your Syft settings.
Pass the key in one of the following ways:
| Method | Example |
|---|---|
Authorization header | Authorization: Bearer sk_live_... |
x-syft-secret-key header | x-syft-secret-key: sk_live_... |
Endpoint
GET https://app.syftdata.com/api/motion/{motionId}/export
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
cursor | string | — | Pagination cursor. Pass the nextCursor value returned by the previous response. Treat it as an opaque token. |
limit | number | 50 | Max items per page (max 100) |
since | ISO8601 string | — | Only return runs created/updated after this timestamp |
until | ISO8601 string | — | Only return runs created/updated before this timestamp |
status | string | completed | Filter by status (e.g., completed, failed, skipped, in_progress). Defaults to completed. To retrieve runs with other statuses, you must explicitly provide the status parameter. |
order | string | asc | Sort order: asc, desc. Note: Runs are always ordered by their execution timestamp. |
fields | string | — | Comma-separated list of top-level fields to include in each item (e.g., status,account,contacts). Omit to return all fields. runId is always included. Use this to shrink the response to only what you need. See Selecting fields. |
Examples
Get recent motion exports
curl -X GET "https://app.syftdata.com/api/motion/{motionId}/export?limit=50" \
-H "Authorization: Bearer sk_live_YOUR_SECRET_KEY"
Poll for new exports since last check
curl -X GET "https://app.syftdata.com/api/motion/{motionId}/export?since=2024-01-01T00:00:00Z&order=asc" \
-H "Authorization: Bearer sk_live_YOUR_SECRET_KEY"
Get failed exports
curl -X GET "https://app.syftdata.com/api/motion/{motionId}/export?status=failed" \
-H "Authorization: Bearer sk_live_YOUR_SECRET_KEY"
Return only specific fields
curl -X GET "https://app.syftdata.com/api/motion/{motionId}/export?fields=status,account,contacts" \
-H "Authorization: Bearer sk_live_YOUR_SECRET_KEY"
Paginate through results
# First page
curl -X GET "https://app.syftdata.com/api/motion/{motionId}/export?limit=50" \
-H "Authorization: Bearer sk_live_YOUR_SECRET_KEY"
# Next page (use nextCursor from previous response)
curl -X GET "https://app.syftdata.com/api/motion/{motionId}/export?limit=50&cursor=clxxx..." \
-H "Authorization: Bearer sk_live_YOUR_SECRET_KEY"
Response
{
"items": [
{
"runId": "clxxx...",
"status": "completed",
"createdAt": "2024-01-01T10:00:00.000Z",
"updatedAt": "2024-01-01T10:05:00.000Z",
"completedAt": "2024-01-01T10:05:00.000Z",
"targetType": "MarContact",
"triggerSource": "webhook",
"account": {
"domain": "example.com",
"name": "Example Inc",
"employeeCount": 100
},
"contacts": [
{
"email": "lead@example.com",
"firstName": "John",
"lastName": "Doe",
"title": "VP Marketing"
}
],
"session": null,
"evaluationSummary": "High intent lead from pricing page",
"triggerReason": "Matched ICP criteria",
"actionPlanSummary": "Send welcome email",
"error": null
}
],
"nextCursor": "clxxx...",
"hasMore": true
}
Response Fields
| Field | Type | Description |
|---|---|---|
items | array | Array of motion run results |
nextCursor | string | null | Cursor for the next page (null if no more items) |
hasMore | boolean | Whether more items exist beyond this page |
Run Item Fields
| Field | Type | Description |
|---|---|---|
runId | string | Unique identifier for the run |
status | string | Run status: completed, failed, skipped, open, running, etc. |
createdAt | string | ISO8601 timestamp when run was created |
updatedAt | string | ISO8601 timestamp when run was last updated |
completedAt | string | null | ISO8601 timestamp when run completed (null if still in progress) |
targetType | string | Type of target: MarContact, MarCompany, MarSessionSummary |
triggerSource | string | null | Source that triggered the run: webhook, csv, mcp, etc. |
account | object | null | Company data (see Webhook Company Object) |
contacts | array | Contact data (see Webhook Contact Fields) |
session | object | null | Session data if applicable (see Webhook Session Object) |
evaluationSummary | string | null | AI evaluation summary |
triggerReason | string | null | Reason the motion was triggered |
actionPlanSummary | string | null | Summary of actions taken |
error | string | null | Error message if run failed |
Selecting fields
By default each item includes every field above. For large exports — or when you only need a subset — pass fields with a comma-separated list of the top-level fields you want. This reduces response size and processing time, which is especially useful when feeding results into an LLM or other token-sensitive pipeline.
# Only return run status, account, and contacts
curl -X GET "https://app.syftdata.com/api/motion/{motionId}/export?fields=status,account,contacts" \
-H "Authorization: Bearer sk_live_YOUR_SECRET_KEY"
Each returned item then contains only the requested fields (plus runId):
{
"items": [
{
"runId": "clxxx...",
"status": "completed",
"account": { "domain": "example.com", "name": "Example Inc" },
"contacts": [{ "email": "lead@example.com", "firstName": "John" }]
}
],
"nextCursor": "clxxx...",
"hasMore": true
}
Notes:
runIdis always included, even if not listed infields.- Omitting
fields(or passing an empty value) returns all fields — fully backward compatible. - Omitting the heavy fields (
account,contacts,session) also skips the underlying data lookups, so narrowingfieldsmakes requests faster, not just smaller. - Valid field names:
runId,status,createdAt,updatedAt,completedAt,targetType,triggerSource,account,contacts,session,evaluationSummary,triggerReason,actionPlanSummary,error. An unrecognized name returns a400error.
Polling Pattern
For efficient minute-interval polling, use this pattern:
- Track the
completedAt(orupdatedAt) of the last processed item - Poll with
since={lastTimestamp}&order=asc - Process items and update your tracking timestamp
- Continue with
cursor={nextCursor}ifhasMore=true
let lastTimestamp = loadLastCheckpoint();
while (true) {
const url = `/api/motion/${motionId}/export?since=${lastTimestamp}&order=asc`;
const { items, nextCursor, hasMore } = await fetch(url);
for (const item of items) {
await processRunItem(item);
lastTimestamp = item.completedAt || item.updatedAt;
}
if (!hasMore) {
saveCheckpoint(lastTimestamp);
await sleep(60000); // Wait 1 minute
}
}
Error Responses
| Status | Body | Description |
|---|---|---|
400 | { "error": "Invalid 'since' timestamp format. Use ISO8601." } | Invalid timestamp format |
400 | { "error": "Invalid fields: ... Valid fields: ..." } | One or more fields values are not recognized |
401 | { "error": "Unauthorized" } | Invalid or missing secret key |
403 | { "error": "API access requires a Pro plan..." } | Organization does not have API access |
404 | { "error": "Motion not found" } | Motion does not exist or belongs to different organization |
Limits
- Maximum 100 items per page (requests for more are capped automatically)
- Default page size is 50 items
- The API is designed for polling at minute intervals or less frequently