Funkel AIDevelopersDashboard ↗

DEVELOPER GUIDE

CSV imports through the API

Import known people or search target companies, then review results before campaign enrollment.

Before you start

Create a personal access token in Settings → Security. Set FUNKEL_API_TOKEN and APP_ID in your shell.

Use GET /api/apps to find your product ID. API paths call products apps and use appId or app_id.

Use canonical CSV headers in API uploads. The app converts mapped columns before upload; these endpoints do not accept UI mapping objects.

These examples use curl. Replace placeholder IDs and fingerprints with values from your responses. Do not run paid confirmation automatically.

For company requests, set UPLOAD_KEY and BATCH_KEY to distinct unique values. Preserve each key only for identical retries.

Preview known people

Use a CSV up to 10 MB with a header row. Each person needs linkedin_url or email.

LinkedIn-only rows need first_name or last_name. Optional headers include company, headline, already_connected, and notes.

Provide exactly one destination: name for a new list, or list_id for an existing list. Do not send campaign_id.

The response contains import_id, selection_fingerprint, row counts, and items. Review errors and duplicates before committing.

bash
curl --fail-with-body "https://api.funkel.ai/api/apps/$APP_ID/lists/import-drafts/preview" \
  -H "Authorization: Bearer $FUNKEL_API_TOKEN" \
  -F 'name=September prospects' \
  -F '[email protected]' 

Import reviewed people

Set IMPORT_ID from import_id. Save the reviewed selection_fingerprint in people-commit.json, as shown below.

POST the JSON file to commit valid rows. This step does not enroll people into a campaign.

GET the same import path without /commit to reload results. Use offset and limit to read all rows.

Review created, skipped_duplicate, failed, and each created_member_id. Imported email addresses do not prove work-email verification.

bash
# people-commit.json: {"selection_fingerprint":"REPLACE_WITH_REVIEWED_FINGERPRINT"}
curl --fail-with-body "https://api.funkel.ai/api/apps/$APP_ID/lists/import-drafts/$IMPORT_ID/commit" \
  -H "Authorization: Bearer $FUNKEL_API_TOKEN" \
  -H 'Content-Type: application/json' \
  --data-binary @people-commit.json

Preview a company CSV

Use canonical headers name, domain, linkedin_url. Each row needs name and either domain or linkedin_url.

Set target roles and search_channel to linkedin, email, or both. Set max_people_per_company to 1.

This example selects LinkedIn. Use a fresh Idempotency-Key for a new logical upload; preserve it when retrying the same upload.

Save the returned draft job_id as DRAFT_ID. GET /api/company-decision-maker-imports/{jobID}/rows to review company IDs and validation results.

bash
curl --fail-with-body https://api.funkel.ai/api/company-decision-maker-imports/preview \
  -H "Authorization: Bearer $FUNKEL_API_TOKEN" \
  -H "Idempotency-Key: $UPLOAD_KEY" \
  -F "app_id=$APP_ID" \
  -F 'roles=["CTO"]' \
  -F 'search_channel=linkedin' \
  -F 'max_people_per_company=1' \
  -F '[email protected]' 

Price the selected companies

Set UPLOAD_KEY and BATCH_KEY to distinct unique values before their requests.

Create company-batch.json with selected_row_ids from the draft rows, roles, search_channel, and max_people_per_company.

Review estimated_credits, maximum_credits, usage, and selected_rows in the response.

Save this response’s job_id as JOB_ID. Use this child job for confirmation and result requests.

Save its selection_fingerprint. Changing the selection requires a new batch preview and cost review.

bash
# company-batch.json:
# {"selected_row_ids":["REPLACE_WITH_ROW_ID"],"roles":["CTO"],"search_channel":"linkedin","max_people_per_company":1}
curl --fail-with-body "https://api.funkel.ai/api/company-decision-maker-imports/$DRAFT_ID/batch-preview" \
  -H "Authorization: Bearer $FUNKEL_API_TOKEN" \
  -H "Idempotency-Key: $BATCH_KEY" \
  -H 'Content-Type: application/json' \
  --data-binary @company-batch.json

Start the paid search

Confirmation starts paid work. Searches can consume credits without finding a match. Search does not send messages.

Create company-confirm.json with list_name and the reviewed child job selection_fingerprint.

After confirmation, GET /api/company-decision-maker-imports/{jobID}/progress until work completes.

Read /results and /company-outcomes for the same job. Paginate result collections with their documented offset and limit fields.

bash
# company-confirm.json:
# {"list_name":"Reviewed company prospects","selection_fingerprint":"REPLACE_WITH_REVIEWED_FINGERPRINT"}
curl --fail-with-body "https://api.funkel.ai/api/company-decision-maker-imports/$JOB_ID/confirm" \
  -H "Authorization: Bearer $FUNKEL_API_TOKEN" \
  -H 'Content-Type: application/json' \
  --data-binary @company-confirm.json

Approve people and continue

Review candidate evidence and contact readiness before approval. Submit only your selected candidate IDs.

POST /api/company-decision-maker-imports/{jobID}/approval with selections and target_list_id set to the job’s review_list_id.

Each selection contains candidate_id and contact_route: linkedin_ready or email_fallback. Choose only an eligible result route.

GET /api/company-decision-maker-imports/{jobID}/approved-members to retrieve approved list members.

For either import path, POST /api/campaigns/{campaignId}/enroll-list-contacts selects list contacts for a campaign.

Read the API reference for its request schema and enrollment receipts. Review the campaign before starting outreach.

bash
# company-approval.json:
# {"target_list_id":"REPLACE_WITH_REVIEW_LIST_ID","selections":[{"candidate_id":"REPLACE_WITH_CANDIDATE_ID","contact_route":"linkedin_ready"}]}
curl --fail-with-body "https://api.funkel.ai/api/company-decision-maker-imports/$JOB_ID/approval" \
  -H "Authorization: Bearer $FUNKEL_API_TOKEN" \
  -H 'Content-Type: application/json' \
  --data-binary @company-approval.json

Handle failures

A 401 requires valid authentication. A 403 can indicate missing access or entitlement.

Read the error code and response before retrying. Fix invalid rows or stale selection fingerprints through a new preview.

Do not assume a timed-out write failed. Reload the import or job before retrying.

Follow Retry-After when present on 429 responses. There is no global idempotency guarantee across all routes.