Synal
Contact lookups default to email only. Send channel=phone to the REST unlock endpoint for an explicit phone lookup, and poll email_unlocked or phone_unlocked for the requested channel. One contact uses one monthly unlock, even when both channels are requested. This allowance is shared with investor email unlocks in the app. Existing contact access is preserved.

Reference

Synal REST API

A read-and-unlock JSON API over the funding events feed. All endpoints live under /api/v1 and return JSON.

Base URL

https://app.synal.to/api/v1/funding_events

Authentication

Every request must include your secret API token as a bearer token in the Authorization header. Grab or rotate your key on the API key page after signing in.

curl -H "Authorization: Bearer YOUR_API_KEY" \
  https://app.synal.to/api/v1/funding_events

A missing or unknown token returns 401 Unauthorized with { "error": "Invalid or missing API token" }.

Endpoints

GET /api/v1/funding_events

Latest funding events, newest first, paginated. Each event includes a contacts_count so you can see which companies have decision-maker contacts available.

Query parameters

  • page — page number (default 1).
  • per_page — results per page (default 50, max 100).

Response

{
  "data": [
    {
      "id": 123,
      "company_name": "Acme Inc",
      "round": "Series A",
      "amount": 12000000,
      "investors": "…",
      "source": "exa_funding",
      "source_url": "…",
      "status": "enriched",
      "summary": "…",
      "discovered_at": "2026-08-09T12:00:00Z",
      "contacts_count": 3
    }
  ],
  "pagination": {
    "page": 1,
    "per_page": 50,
    "total_pages": 20,
    "total_count": 1000
  }
}
GET /api/v1/funding_events/:id

A single event with its full company object and the company's contacts. Unknown ids return 404 Not found.

Response

{
  "data": {
    "id": 123,
    "company_name": "Acme Inc",
    "…": "…",
    "company": {
      "id": 45,
      "name": "Acme Inc",
      "domain": "acme.com",
      "industry": "…",
      "employee_range": "…",
      "hq_city": "…",
      "hq_country": "…",
      "total_funding": 20000000,
      "website_url": "…",
      "linkedin_url": "…"
    },
    "contacts": [ /* see Contact object */ ]
  }
}
GET /api/v1/funding_events/:funding_event_id/contacts

Decision-maker contacts for the event's company. email and phone stay hidden until the corresponding channel is unlocked for your account.

Contact object

{
  "id": 77,
  "first_name": "Jane",
  "last_name": "Doe",
  "title": "VP Engineering",
  "seniority": "vp",
  "city": "London",
  "country": "GB",
  "linkedin_url": "…",
  "details_unlocked": false,
  "email_unlocked": false,
  "phone_unlocked": false,
  "unlock_channel": "email",
  "unlock_status": "idle"
  // Each field appears only when its own *_unlocked flag is true.
  // unlock_status is "idle", "processing", "no_result", or "failed" (see below).
}

Getting a contact's details

A contact's email and phone are locked by default. Because the lookup runs in the background, retrieving them is a three-step flow:

  1. Start the unlock. POST /api/v1/contacts/:id/unlock — returns 202 with unlock_status: "processing" (or 200 with the details right away if it's already unlocked).
  2. Poll. GET /api/v1/contacts/:id/unlock every second or two while unlock_status is "processing".
  3. Read the details. Once the requested email_unlocked or phone_unlocked flag is true, the corresponding field is present on the Contact object.

A run that finishes at "no_result" found nothing (safe to retry, but won't cost a credit unless new data becomes available); "failed" means the lookup errored. Yearly plans include unlimited unlocks.

POST /api/v1/contacts/:id/unlock

Finds email by default. Send channel=phone to request a phone number separately. The lookup runs in the background, so this returns 202 Accepted right away with unlock_status: "processing" — poll the endpoint below until the requested channel is unlocked or unlock_status settles. It's a no-op (spends nothing, returns 200 with the details) if the contact is already unlocked.

curl -X POST \
  -H "Authorization: Bearer YOUR_API_KEY" \
  https://app.synal.to/api/v1/contacts/77/unlock
GET /api/v1/contacts/:id/unlock

Returns the contact's current state so you can poll after kicking off an unlock. Keep polling while unlock_status is "processing". On success the requested channel’s *_unlocked flag becomes true and that field appears; "no_result" after a run means nothing was found (safe to retry) and "failed" means the lookup errored.

curl \
  -H "Authorization: Bearer YOUR_API_KEY" \
  https://app.synal.to/api/v1/contacts/77/unlock

Errors

Errors return the matching HTTP status and a JSON body of the shape { "error": "…" }.

  • 401 Unauthorized — missing or invalid API token.
  • 403 Forbidden — API access requires an active yearly plan.
  • 404 Not found — the requested record does not exist.

Find more contacts

Two decision-makers are discovered automatically. GET the event's contacts and read discovery.token. POST that token to /api/v1/funding_events/:id/contacts/discover to request the next two. Poll the contacts GET while discovery.status is processing. Each completed search returns a new token and more_available. Repeated submissions of an old token do not buy another page. Discovery does not reveal email or phone.