Data Surfer API

Programmatic access to company and contact intelligence. Build powerful integrations with our REST API.

Base URL https://api.data-surfer.com/v1

API Key (for testing endpoints)

Enter your API key to test endpoints directly from this page

Authentication

The Data Surfer API uses API keys for authentication. Include your API key in the Authorization header of all requests.

Authorization Header
Authorization: Bearer ds_live_your_api_key_here

API keys can be created and managed from your Admin Dashboard. Keep your API keys secure and never expose them in client-side code.

Health Check

Verify the API is operational and check the current version. If an API key is provided, it also validates whether the key is valid.

GET /v1/health

This endpoint does not require authentication, but will validate an API key if one is provided.

Query Parameters

No query parameters required.

Response

application/json
{
  "status": "ok",
  "timestamp": "2024-01-15T10:30:00.000Z",
  "version": "1.0.0",
  "authenticated": true
}
application/json
{
  "status": "ok",
  "timestamp": "2024-01-15T10:30:00.000Z",
  "version": "1.0.0"
}

Response Fields

FieldTypeDescription
statusstringAPI status ("ok")
timestampstringCurrent server time in ISO 8601 format
versionstringAPI version number
authenticatedbooleanWhether a valid API key was provided (only present if Authorization header sent)

List Companies

Returns a paginated list of companies for your organization. Supports filtering by search query and tags.

GET /v1/companies

Query Parameters

ParameterTypeDefaultDescription
qstring-Search query. Searches company name and website.
tag_idsstring-Comma-separated tag IDs to filter by (e.g., 1,2,3)
sortstringcreated_atSort field: company_name, website, created_at, updated_at
orderstringdescSort direction: asc or desc
limitinteger20Items per page (1-100)
pageinteger1Page number

Response

application/json
{
  "data": [
    {
      "id": 1,
      "company_name": "Acme Corporation",
      "website": "https://acme.com",
      "tags": [
        { "id": 1, "name": "Enterprise", "color": "#3b82f6" }
      ],
      "custom_fields": {
        "industry": "Technology",
        "employees": "500-1000"
      },
      "created_at": "2024-01-15T10:30:00.000Z",
      "updated_at": "2024-01-18T14:20:00.000Z"
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 20,
    "total": 1,
    "total_pages": 1
  }
}
application/json
{
  "data": [],
  "pagination": {
    "page": 1,
    "limit": 20,
    "total": 0,
    "total_pages": 0
  }
}
application/json
{
  "error": "Invalid or missing API key"
}

Response Fields

FieldTypeDescription
idintegerUnique company identifier
company_namestringCompany name
websitestring | nullCompany website URL
tagsarrayArray of tag objects with id, name, and color
custom_fieldsobjectKey-value pairs of custom field data
created_atstringISO 8601 timestamp when company was created
updated_atstringISO 8601 timestamp when company was last updated

Get Company

Returns a single company by ID.

GET /v1/companies/:id

Path Parameters

ParameterTypeRequiredDescription
idintegerYesThe company ID

Response Fields

FieldTypeDescription
idintegerUnique company identifier
company_namestringCompany name
websitestring | nullCompany website URL
tagsarrayArray of tag objects with id, name, and color
custom_fieldsobjectSimple key-value pairs of custom field data
fieldsarrayDetailed field array with key, name, value, and ai_analysis
fields[].ai_analysisobject | nullAI analysis with score (confidence 1-5), explanation, and sources (URLs)
created_atstringISO 8601 timestamp when company was created
updated_atstringISO 8601 timestamp when company was last updated

Response

application/json
{
  "data": {
    "id": 1,
    "company_name": "Acme Corporation",
    "website": "https://acme.com",
    "tags": [{ "id": 1, "name": "Enterprise", "color": "#3b82f6" }],
    "custom_fields": { "industry": "Technology", "fit_score": "85" },
    "fields": [
      { "key": "industry", "name": "Industry", "value": "Technology", "ai_analysis": null },
      {
        "key": "fit_score", "name": "Fit Score", "value": "85",
        "ai_analysis": {
          "score": 5,
          "explanation": "Strong match based on company size and tech stack",
          "sources": ["https://acme.com", "https://linkedin.com/company/acme"]
        }
      }
    ],
    "created_at": "2024-01-15T10:30:00.000Z",
    "updated_at": "2024-01-18T14:20:00.000Z"
  }
}
application/json
{
  "error": "Invalid or missing API key"
}
application/json
{
  "error": "Company not found"
}

Create Company

Creates a new company in your organization.

POST /v1/companies

Request Body

FieldTypeRequiredDescription
company_namestringYesThe company name
websitestring | nullNoThe company website URL

Response

application/json
{
  "data": {
    "id": 42,
    "company_name": "New Company",
    "website": "https://newcompany.com",
    "created_at": "2024-01-20T10:30:00.000Z",
    "updated_at": "2024-01-20T10:30:00.000Z"
  }
}
application/json
{
  "error": "company_name is required"
}
application/json
{
  "error": "Invalid or missing API key"
}
application/json
{
  "error": "You've reached your plan limit for companies. Please upgrade your plan to add more companies."
}

Response Fields

FieldTypeDescription
idintegerUnique company identifier
company_namestringCompany name
websitestring | nullCompany website URL
created_atstringISO 8601 timestamp when company was created
updated_atstringISO 8601 timestamp when company was last updated

Update Company

Updates an existing company. At least one field must be provided.

PUT /v1/companies/:id

Path Parameters

ParameterTypeDescription
idintegerThe company ID to update

Request Body

FieldTypeRequiredDescription
company_namestringNoThe new company name
websitestring | nullNoThe new company website URL (set to null to clear)

Response

application/json
{
  "data": {
    "id": 42,
    "company_name": "Updated Company Name",
    "website": "https://updated-website.com",
    "created_at": "2024-01-15T10:30:00.000Z",
    "updated_at": "2024-01-20T14:45:00.000Z"
  }
}
application/json
{
  "error": "At least one field (company_name or website) must be provided"
}
application/json
{
  "error": "Invalid or missing API key"
}
application/json
{
  "error": "Company not found"
}

Response Fields

FieldTypeDescription
idintegerUnique company identifier
company_namestringCompany name
websitestring | nullCompany website URL
created_atstringISO 8601 timestamp when company was created
updated_atstringISO 8601 timestamp when company was last updated

Delete Company

Deletes a company (soft delete). The company will no longer appear in listings but may be recoverable by support.

DELETE /v1/companies/:id

Path Parameters

ParameterTypeDescription
idintegerThe company ID to delete

Response

application/json
{
  "success": true,
  "message": "Company deleted successfully"
}
application/json
{
  "error": "Invalid or missing API key"
}
application/json
{
  "error": "Company not found"
}

Response Fields

FieldTypeDescription
successbooleanWhether the deletion was successful
messagestringSuccess message

List Fields

Returns the custom field schema for your organization. Use this to understand what fields are available on companies.

GET /v1/companies/fields

Response Fields

FieldTypeDescription
idintegerUnique field identifier
keystringField key used in API responses (e.g., industry, fit_score)
namestringHuman-readable field name
orderintegerDisplay order of the field
created_atstringISO 8601 timestamp when field was created
updated_atstringISO 8601 timestamp when field was last updated

Response

application/json
{
  "data": [
    { "id": 1, "key": "industry", "name": "Industry", "order": 1, "created_at": "2024-01-15T10:30:00.000Z", "updated_at": "2024-01-15T10:30:00.000Z" },
    { "id": 2, "key": "fit_score", "name": "Fit Score", "order": 2, "created_at": "2024-01-15T10:30:00.000Z", "updated_at": "2024-01-15T10:30:00.000Z" },
    { "id": 3, "key": "is_hiring_engineers", "name": "Is Hiring Engineers", "order": 3, "created_at": "2024-01-15T10:30:00.000Z", "updated_at": "2024-01-15T10:30:00.000Z" }
  ],
  "total": 3
}
application/json
{
  "error": "Invalid or missing API key"
}

List Tags

Returns all tags for your organization with the number of companies in each tag.

GET /v1/companies/tags

Query Parameters

No query parameters required.

Response

application/json
{
  "data": [
    {
      "id": 1,
      "name": "Enterprise",
      "color": "#3b82f6",
      "company_count": 45,
      "created_at": "2024-01-15T10:30:00.000Z",
      "updated_at": "2024-01-15T10:30:00.000Z"
    },
    {
      "id": 2,
      "name": "Hot Lead",
      "color": "#ef4444",
      "company_count": 12,
      "created_at": "2024-01-16T14:20:00.000Z",
      "updated_at": "2024-01-18T09:15:00.000Z"
    },
    {
      "id": 3,
      "name": "Partner",
      "color": "#22c55e",
      "company_count": 8,
      "created_at": "2024-01-17T11:45:00.000Z",
      "updated_at": "2024-01-17T11:45:00.000Z"
    }
  ],
  "total": 3
}
application/json
{
  "data": [],
  "total": 0
}
application/json
{
  "error": "Invalid or missing API key"
}

Response Fields

FieldTypeDescription
idintegerUnique tag identifier
namestringTag name
colorstringHex color code for the tag (e.g., #3b82f6)
company_countintegerNumber of companies with this tag
created_atstringISO 8601 timestamp when tag was created
updated_atstringISO 8601 timestamp when tag was last updated

Contacts

List Contacts

Returns a paginated list of contacts for your organization. Supports filtering by search query, company, and tags.

GET /v1/contacts

Query Parameters

ParameterTypeDefaultDescription
qstring-Search query. Searches first name, last name, email, and company name.
company_idinteger-Filter contacts by company ID
tag_idsstring-Comma-separated contact tag IDs to filter by
sortstringcreated_atSort field: first_name, last_name, email, created_at, updated_at
orderstringdescSort direction: asc or desc
limitinteger20Items per page (1-100)
pageinteger1Page number

Response

application/json
{
  "data": [
    {
      "id": 1,
      "first_name": "John",
      "last_name": "Smith",
      "email": "john.smith@acme.com",
      "role": "decision-maker",
      "company_id": 1,
      "company_name": "Acme Corporation",
      "tags": [{ "id": 1, "name": "VIP", "color": "#ef4444" }],
      "created_at": "2024-01-15T10:30:00.000Z",
      "updated_at": "2024-01-18T14:20:00.000Z"
    }
  ],
  "pagination": { "page": 1, "limit": 20, "total": 1, "pages": 1 }
}
application/json
{ "error": "Invalid or missing API key" }

Response Fields

FieldTypeDescription
idintegerUnique contact identifier
first_namestring | nullContact first name
last_namestring | nullContact last name
emailstring | nullContact email address
rolestring | nullContact role: decision-maker, champion, or influencer
company_idinteger | nullID of the associated company
company_namestring | nullName of the associated company
tagsarrayArray of tag objects

Get Contact

Returns a single contact by ID with detailed field data including AI analysis.

GET /v1/contacts/:id

Path Parameters

ParameterTypeRequiredDescription
idintegerYesThe contact ID

Response

application/json
{
  "data": {
    "id": 1,
    "first_name": "John",
    "last_name": "Smith",
    "email": "john.smith@acme.com",
    "role": "decision-maker",
    "company_id": 1,
    "company_name": "Acme Corporation",
    "tags": [{ "id": 1, "name": "VIP", "color": "#ef4444" }],
    "fields": [
      { "key": "linkedin_url", "name": "LinkedIn URL", "value": "https://linkedin.com/in/johnsmith", "ai_analysis": null }
    ],
    "created_at": "2024-01-15T10:30:00.000Z",
    "updated_at": "2024-01-18T14:20:00.000Z"
  }
}
application/json
{ "error": "Contact not found" }

Create Contact

Creates a new contact in your organization. At least one of first_name, last_name, or email must be provided.

POST /v1/contacts

Request Body

FieldTypeRequiredDescription
first_namestring | nullNo*Contact first name
last_namestring | nullNo*Contact last name
emailstring | nullNo*Contact email address (must be valid email format)
company_idinteger | nullNoID of the company to associate this contact with
rolestring | nullNoContact role: decision-maker, champion, or influencer

* At least one of first_name, last_name, or email must be provided.

Response

application/json
{
  "data": {
    "id": 42,
    "first_name": "Jane",
    "last_name": "Doe",
    "email": "jane.doe@example.com",
    "role": "champion",
    "company_id": 1,
    "created_at": "2024-01-20T10:30:00.000Z",
    "updated_at": "2024-01-20T10:30:00.000Z"
  }
}
application/json
{ "error": "At least one of first_name, last_name, or email must be provided" }

Update Contact

Updates an existing contact. At least one field must be provided.

PUT /v1/contacts/:id

Path Parameters

ParameterTypeDescription
idintegerThe contact ID to update

Request Body

FieldTypeRequiredDescription
first_namestringNoNew first name
last_namestringNoNew last name
emailstringNoNew email address
company_idinteger | nullNoNew company ID (set to null to remove association)
rolestring | nullNoNew role (set to null to clear)

Response

application/json
{
  "data": {
    "id": 42,
    "first_name": "Jane",
    "last_name": "Smith",
    "email": "jane.smith@example.com",
    "role": "decision-maker",
    "company_id": 2,
    "created_at": "2024-01-15T10:30:00.000Z",
    "updated_at": "2024-01-20T14:45:00.000Z"
  }
}
application/json
{ "error": "Contact not found" }

Delete Contact

Deletes a contact (soft delete). The contact will no longer appear in listings but may be recoverable by support.

DELETE /v1/contacts/:id

Path Parameters

ParameterTypeDescription
idintegerThe contact ID to delete

Response

application/json
{ "success": true, "message": "Contact deleted successfully" }
application/json
{ "error": "Contact not found" }

List Contact Fields

Returns the custom field schema for contacts in your organization.

GET /v1/contacts/fields

Response

application/json
{
  "data": [
    { "id": 1, "key": "linkedin_url", "name": "LinkedIn URL", "order": 0, "meta": { "type": "link" }, "created_at": "2024-01-15T10:30:00.000Z", "updated_at": "2024-01-15T10:30:00.000Z" }
  ],
  "total": 1
}

List Contact Tags

Returns all contact tags for your organization with the number of contacts in each tag.

GET /v1/contacts/tags

Response

application/json
{
  "data": [
    { "id": 1, "name": "VIP", "color": "#ef4444", "contact_count": 25, "created_at": "2024-01-15T10:30:00.000Z", "updated_at": "2024-01-15T10:30:00.000Z" }
  ],
  "total": 1
}

Lookup Companies

Instant lookup of companies from a database of millions of records using keyword filters or semantic search. Keywords are automatically matched - no need to look up IDs first. Consumes 1 instant lookup from your plan limits.

POST /v1/companies/lookup

Request Body

FieldTypeRequiredDescription
querystringNoSemantic search query (e.g., "B2B SaaS for developers"). Min 3 chars.
broaden_searchbooleanNoInclude less similar matches in semantic search (default false)
industriesarrayNoIndustry keywords (e.g., ["software", "fintech"])
countriesarrayNoCountry names (e.g., ["united states", "canada"])
regionsarrayNoRegion/state names (e.g., ["california", "new york"])
citiesarrayNoCity names (e.g., ["san francisco", "austin"])
size_idsarrayNoCompany size IDs (1=1-10, 2=11-50, 3=51-200, 4=201-500, 5=501-1000, 6=1001-5000, 7=5001-10000, 8=10001+)
company_typesarrayNo"Privately Held", "Public Company", "Nonprofit", "Partnership", "Government Agency", "Educational"
founded_year_minintegerNoMinimum founding year
founded_year_maxintegerNoMaximum founding year
max_resultsintegerNoMax results (default 20). Plan limits: Basic=50, Starter=200, Pro=1000
savebooleanNoAuto-save found companies (default false)

Examples

Search by industry and location
{
  "industries": ["software", "fintech"],
  "countries": ["united states"],
  "regions": ["california"],
  "max_results": 25
}
Semantic search with filters
{
  "query": "B2B SaaS companies for developers",
  "countries": ["united states", "canada"],
  "size_ids": [3, 4, 5],
  "max_results": 25,
  "save": true
}
Simple city-based search
{
  "cities": ["san francisco", "austin", "seattle"],
  "industries": ["technology"]
}

Response

application/json
{
  "data": [
    {
      "name": "Stripe",
      "website": "https://stripe.com",
      "industry": "Financial Services",
      "size": "1001-5000 employees",
      "type": "Privately Held",
      "founded": 2010,
      "employee_count": 4500,
      "linkedin_url": "https://www.linkedin.com/company/stripe",
      "tagline": "Financial infrastructure for the internet",
      "similarity": 0.85
    }
  ],
  "total": 1
}
application/json
{
  "data": [
    {
      "name": "Stripe",
      "website": "https://stripe.com",
      "industry": "Financial Services",
      "size": "1001-5000 employees",
      "type": "Privately Held",
      "founded": 2010,
      "employee_count": 4500,
      "linkedin_url": "https://www.linkedin.com/company/stripe",
      "tagline": "Financial infrastructure for the internet"
    }
  ],
  "total": 1,
  "saved_count": 1
}
application/json
{ "error": "Instant lookup limit reached. Please upgrade your plan." }

Response Fields

FieldTypeDescription
dataarrayArray of company objects
data[].namestringCompany name
data[].websitestring | nullCompany website
data[].industrystring | nullIndustry name
data[].sizestring | nullCompany size range
data[].typestring | nullCompany type
data[].foundedinteger | nullYear founded
data[].employee_countinteger | nullEmployee count
data[].linkedin_urlstring | nullLinkedIn URL
data[].taglinestring | nullCompany tagline
data[].similaritynumberSemantic similarity score (0-1, only present for semantic searches)
totalintegerNumber of results returned
saved_countintegerCompanies saved (only if save=true)
quota_limitedintegerCompanies not saved due to plan limits

Start Company Deep Search

Start an AI-powered web search to find companies matching a semantic query. Uses multi-tier search including Google Places, web search, and internal database. Companies are automatically saved with an AI-generated tag. Consumes 1 deep search from your plan limits.

POST /v1/companies/deep-search

Request Body

FieldTypeRequiredDescription
querystringYesSemantic search query (e.g., "AI startups in healthcare"). Min 3 chars, max 1000.
max_resultsintegerNoTarget number of companies (default 20, max 100)

Response

application/json
{
  "job_id": 456,
  "status": "pending",
  "query": "AI startups in healthcare",
  "target_count": 20,
  "created_at": "2024-01-15T10:30:00.000Z",
  "message": "Company deep search started. Poll GET /v1/companies/deep-search/:job_id for status."
}
application/json
{ "error": "Deep search limit reached. Please upgrade your plan." }

Response Fields

FieldTypeDescription
job_idintegerJob ID for polling status
statusstringJob status: pending, running, completed, failed
querystringThe search query
target_countintegerTarget number of companies
created_atstringISO 8601 timestamp
messagestringInstructions for polling

Get Company Deep Search Status

Poll the status of a company deep search job. When status is "completed", companies are automatically saved to your list with an AI-generated tag. Poll every 5-10 seconds until complete.

GET /v1/companies/deep-search/:job_id

Path Parameters

ParameterTypeRequiredDescription
job_idintegerYesJob ID from POST /v1/companies/deep-search

Response

application/json
{
  "job_id": 456,
  "status": "running",
  "query": "AI startups in healthcare",
  "target_count": 20,
  "created_at": "2024-01-15T10:30:00.000Z",
  "started_at": "2024-01-15T10:30:05.000Z",
  "completed_at": null,
  "tag_id": null,
  "tag_name": null,
  "companies_found": 0,
  "companies_saved": 0,
  "error_message": null
}
application/json
{
  "job_id": 456,
  "status": "completed",
  "query": "AI startups in healthcare",
  "target_count": 20,
  "created_at": "2024-01-15T10:30:00.000Z",
  "started_at": "2024-01-15T10:30:05.000Z",
  "completed_at": "2024-01-15T10:32:45.000Z",
  "tag_id": 123,
  "tag_name": "AI Healthcare Startups",
  "companies_found": 25,
  "companies_saved": 20,
  "error_message": null
}
application/json
{ "error": "Company deep search job not found" }

Response Fields

FieldTypeDescription
job_idintegerJob ID
statusstringpending | running | completed | failed
querystringThe search query
target_countintegerTarget number of companies
created_atstringJob creation timestamp
started_atstring | nullJob start timestamp
completed_atstring | nullJob completion timestamp
tag_idinteger | nullID of auto-created tag (when completed)
tag_namestring | nullName of auto-created tag (when completed)
companies_foundintegerTotal companies found (when completed)
companies_savedintegerCompanies saved to your list (when completed)
error_messagestring | nullError details (when failed)

Start Contact Search

Start a contact search job for a company. Uses web research to find decision-makers, champions, and influencers at the target company. Returns a job_id for polling status. Consumes 1 deep search from your plan limits.

POST /v1/contacts/search

Request Body

FieldTypeRequiredDescription
company_idintegerYesID of the company to search contacts for
rolesarrayNoTarget job titles/roles (e.g., ["CEO", "CTO", "VP Sales"])
max_resultsintegerNoMaximum contacts to find (default 20, max 50)

Examples

Search for specific roles
{
  "company_id": 456,
  "roles": ["CEO", "CTO", "VP Sales", "Head of Engineering"],
  "max_results": 20
}
Find any contacts at company
{
  "company_id": 456
}
Limit to top 5 contacts
{
  "company_id": 456,
  "roles": ["CEO", "CFO"],
  "max_results": 5
}

Response

application/json
{
  "job_id": 123,
  "status": "pending",
  "company_id": 456,
  "created_at": "2024-01-15T10:30:00.000Z",
  "message": "Contact search started. Poll GET /v1/contacts/search/:job_id for status."
}
application/json
{ "error": "Company website is required for contact search" }
application/json
{ "error": "Contact search limit reached. Please upgrade your plan." }

Response Fields

FieldTypeDescription
job_idintegerJob ID for polling status
statusstringJob status: pending, searching, completed, failed
company_idintegerTarget company ID
created_atstringISO 8601 timestamp
messagestringInstructions for polling

Get Contact Search Status

Poll the status of a contact search job. When status is "completed", the contacts array contains the found contacts. Poll every 5-10 seconds until complete.

GET /v1/contacts/search/:job_id

Path Parameters

ParameterTypeDescription
job_idintegerJob ID from POST /v1/contacts/search

Response

application/json
{
  "job_id": 123,
  "status": "searching",
  "company_id": 456,
  "company_name": "Acme Corp",
  "created_at": "2024-01-15T10:30:00.000Z",
  "completed_at": null,
  "summary": null,
  "contacts": []
}
application/json
{
  "job_id": 123,
  "status": "completed",
  "company_id": 456,
  "company_name": "Acme Corp",
  "created_at": "2024-01-15T10:30:00.000Z",
  "completed_at": "2024-01-15T10:31:45.000Z",
  "summary": { "totalFound": 3 },
  "contacts": [
    {
      "id": 1,
      "first_name": "John",
      "last_name": "Smith",
      "job_title": "CEO",
      "role": "decision-maker",
      "confidence": "high",
      "source_url": "https://acme.com/about"
    },
    {
      "id": 2,
      "first_name": "Jane",
      "last_name": "Doe",
      "job_title": "VP Engineering",
      "role": "champion",
      "confidence": "high",
      "source_url": "https://linkedin.com/in/jane"
    }
  ]
}
application/json
{ "error": "Contact search job not found" }

Response Fields

FieldTypeDescription
job_idintegerJob ID
statusstringpending | searching | completed | failed | cancelled
company_idintegerTarget company ID
company_namestring | nullTarget company name
created_atstringJob creation timestamp
completed_atstring | nullJob completion timestamp
summaryobject | nullResults summary (when completed)
contactsarrayFound contacts (empty until completed)
contacts[].idintegerContact ID (for adding)
contacts[].first_namestring | nullFirst name
contacts[].last_namestring | nullLast name
contacts[].job_titlestring | nullJob title
contacts[].rolestring | nullRole: decision-maker, champion, influencer
contacts[].confidencestring | nullConfidence: high, medium, low
contacts[].source_urlstring | nullSource URL where contact was found

Add Contacts from Search

Add selected contacts from a completed search job to your permanent contacts list. Only works for completed jobs. Consumes contact capacity from your plan limits.

POST /v1/contacts/search/:job_id/add

Path Parameters

ParameterTypeDescription
job_idintegerJob ID of completed search

Request Body

FieldTypeRequiredDescription
contact_idsarrayYesArray of contact IDs to add (from search results)

Examples

Add all decision-makers from search
{
  "contact_ids": [1, 2, 3, 5, 8]
}
Add just the CEO
{
  "contact_ids": [1]
}

Response

application/json
{
  "success": true,
  "added": 2,
  "skipped": 1
}
application/json
{ "error": "Job must be completed before adding contacts" }
application/json
{ "error": "Job not found" }

Response Fields

FieldTypeDescription
successbooleanWhether operation succeeded
addedintegerNumber of contacts added
skippedintegerNumber skipped (duplicates or limit reached)

List Research Templates

Returns all research templates for your organization. Research templates define what data points to extract when researching companies.

GET /v1/research/templates

Response Fields

FieldTypeDescription
idstringUnique template identifier (UUID)
namestringTemplate name
descriptionstring | nullTemplate description
fieldsarrayArray of data point definitions with key, name, and type
run_countintegerNumber of times this template has been run
last_run_atstring | nullISO 8601 timestamp of last run
created_atstringISO 8601 timestamp when template was created
updated_atstringISO 8601 timestamp when template was last updated
application/json
{
  "data": [
    {
      "id": "b43936aa-80d4-426f-bf17-71f0d480831a",
      "name": "General Researcher",
      "description": "Research template for company analysis",
      "fields": [
        { "key": "tech_stack", "name": "Tech Stack", "type": "text" },
        { "key": "is_hiring", "name": "Is Hiring", "type": "boolean" },
        { "key": "fit_score", "name": "Fit Score", "type": "number" }
      ],
      "run_count": 5,
      "last_run_at": "2024-01-15T10:30:00.000Z",
      "created_at": "2024-01-10T09:00:00.000Z",
      "updated_at": "2024-01-15T10:30:00.000Z"
    }
  ],
  "total": 1
}
application/json
{
  "data": [],
  "total": 0
}
application/json
{
  "success": false,
  "message": "Invalid or missing API key",
  "errors": null
}

Get Research Template

Returns a single research template by ID with full details including settings.

GET /v1/research/templates/:id

Path Parameters

ParameterTypeDescription
idstringThe template ID (UUID)

Response Fields

FieldTypeDescription
idstringUnique template identifier (UUID)
namestringTemplate name
descriptionstring | nullTemplate description
fieldsarrayArray of data point definitions
settingsobjectTemplate settings including max_pages and web_search_enabled
run_countintegerNumber of times this template has been run
last_run_atstring | nullISO 8601 timestamp of last run
created_atstringISO 8601 timestamp when template was created
updated_atstringISO 8601 timestamp when template was last updated
application/json
{
  "data": {
    "id": "b43936aa-80d4-426f-bf17-71f0d480831a",
    "name": "General Researcher",
    "description": "Research template for company analysis",
    "fields": [
      { "key": "tech_stack", "name": "Tech Stack", "type": "text" },
      { "key": "is_hiring", "name": "Is Hiring", "type": "boolean" },
      { "key": "fit_score", "name": "Fit Score", "type": "number" }
    ],
    "settings": {
      "max_pages": 5,
      "web_search_enabled": false
    },
    "run_count": 5,
    "last_run_at": "2024-01-15T10:30:00.000Z",
    "created_at": "2024-01-10T09:00:00.000Z",
    "updated_at": "2024-01-15T10:30:00.000Z"
  }
}
application/json
{
  "success": false,
  "message": "Research template not found",
  "errors": null
}

Start Research Run

Start a research job to analyze companies using a template. Specify the template by ID or name. Returns a run_id for polling. Consumes 1 company enrichment per company from your plan limits.

POST /v1/research/run

Request Body

ParameterTypeRequiredDescription
template_idstringNo*Template ID (UUID). Either template_id or template_name is required.
template_namestringNo*Template name (case-insensitive). Either template_id or template_name is required.
company_idsarrayYesArray of company IDs to research (1-100 companies per request)
Start research by template name
curl -X POST https://api.data-surfer.com/v1/research/run \
  -H "Authorization: Bearer ds_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "template_name": "General Researcher",
    "company_ids": [1, 2, 3, 4, 5]
  }'
Start research by template ID
curl -X POST https://api.data-surfer.com/v1/research/run \
  -H "Authorization: Bearer ds_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "template_id": "b43936aa-80d4-426f-bf17-71f0d480831a",
    "company_ids": [1, 2, 3]
  }'

Response Fields

FieldTypeDescription
run_idstringRun ID (UUID) for polling status
template_idstringTemplate ID used
template_namestringTemplate name
statusstringRun status: running
companies_totalintegerNumber of companies being researched
created_atstringISO 8601 timestamp
messagestringInstructions for polling
application/json
{
  "run_id": "a472488f-c02b-45d3-b9eb-d60d59839495",
  "template_id": "b43936aa-80d4-426f-bf17-71f0d480831a",
  "template_name": "General Researcher",
  "status": "running",
  "companies_total": 5,
  "created_at": "2024-01-15T10:30:00.000Z",
  "message": "Research started. Poll GET /v1/research/run/:run_id for status."
}
application/json
{
  "success": false,
  "message": "Either template_id or template_name is required",
  "errors": null
}
application/json
{
  "success": false,
  "message": "Company enrichment limit reached. Please upgrade your plan.",
  "errors": null
}
application/json
{
  "success": false,
  "message": "Research template not found",
  "errors": null
}

Get Research Run Status

Poll the status of a research run. When status is "completed", the research results are saved to each company's custom fields. Poll every 5-10 seconds until complete.

GET /v1/research/run/:run_id

Path Parameters

ParameterTypeDescription
run_idstringRun ID (UUID) from POST /v1/research/run

Response Fields

FieldTypeDescription
run_idstringRun ID (UUID)
template_idstringTemplate ID used
template_namestringTemplate name
statusstringrunning | completed | failed
companies_totalintegerTotal companies in the run
companies_processedintegerCompanies successfully processed
companies_failedintegerCompanies that failed to process
started_atstringRun start timestamp
finished_atstring | nullRun completion timestamp (null if still running)
application/json
{
  "run_id": "a472488f-c02b-45d3-b9eb-d60d59839495",
  "template_id": "b43936aa-80d4-426f-bf17-71f0d480831a",
  "template_name": "General Researcher",
  "status": "running",
  "companies_total": 5,
  "companies_processed": 2,
  "companies_failed": 0,
  "started_at": "2024-01-15T10:30:00.000Z",
  "finished_at": null
}
application/json
{
  "run_id": "a472488f-c02b-45d3-b9eb-d60d59839495",
  "template_id": "b43936aa-80d4-426f-bf17-71f0d480831a",
  "template_name": "General Researcher",
  "status": "completed",
  "companies_total": 5,
  "companies_processed": 5,
  "companies_failed": 0,
  "started_at": "2024-01-15T10:30:00.000Z",
  "finished_at": "2024-01-15T10:35:00.000Z"
}
application/json
{
  "success": false,
  "message": "Research run not found",
  "errors": null
}

List Outreach Leads

Returns a paginated list of outreach leads for your organization. Filter by lead status (cold or warm).

GET /v1/outreach/leads

Query Parameters

ParameterTypeDefaultDescription
statusstringcoldLead status filter: cold or warm
limitinteger20Items per page (1-100)
pageinteger1Page number

Response

application/json
{
  "data": [
    {
      "id": 1,
      "company_id": 42,
      "company_name": "Acme Corporation",
      "website": "https://acme.com",
      "lead_status": "cold",
      "warming_score": 35,
      "target_score": 70,
      "activity_count": 8,
      "recommendation_count": 3,
      "monitoring_active": true,
      "last_activity_at": "2024-01-18T14:20:00.000Z",
      "created_at": "2024-01-15T10:30:00.000Z"
    }
  ],
  "total": 1,
  "page": 1,
  "limit": 20
}
application/json
{
  "success": false,
  "message": "Invalid or missing API key",
  "errors": null
}

Response Fields

FieldTypeDescription
idintegerUnique lead identifier
company_idintegerAssociated company ID
company_namestringCompany name
lead_statusstringLead status: cold or warm
warming_scoreintegerCurrent warming score (0-100)
activity_countintegerTotal number of logged activities
recommendation_countintegerNumber of active recommendations
monitoring_activebooleanWhether signal monitoring is enabled
last_activity_atstring | nullISO 8601 timestamp of last activity

Get Outreach Lead

Returns full details for an outreach lead including company info, warming score, monitoring pages, contacts, competitors, touchpoints, and recommendations.

GET /v1/outreach/leads/:id

Path Parameters

ParameterTypeDescription
idintegerThe lead ID

Response

application/json
{
  "id": 1,
  "company_id": 42,
  "company_name": "Acme Corporation",
  "website": "https://acme.com",
  "lead_status": "cold",
  "warming_score": 35,
  "target_score": 70,
  "deal_value": null,
  "deal_currency": "USD",
  "deal_won": false,
  "monitoring_pages": [
    { "id": 1, "page_type": "company", "page_url": "https://acme.com/blog", "label": "Company Blog", "monitoring_score": 9, "is_selected": true }
  ],
  "contacts": [
    { "id": 10, "first_name": "Jane", "last_name": "Doe", "title": "VP of Sales", "is_pinned": true }
  ],
  "competitors": [],
  "touchpoints": [
    { "id": 1, "description": "Commented on their blog post about AI", "detected_type": "comment", "created_at": "2024-01-18T14:20:00.000Z" }
  ],
  "recommendations": [
    { "id": 1, "action_type": "comment", "target_url": "https://acme.com/blog/ai-trends", "suggested_comment": "Great insights...", "priority": "high" }
  ],
  "last_activity_at": "2024-01-18T14:20:00.000Z",
  "created_at": "2024-01-15T10:30:00.000Z"
}
application/json
{
  "success": false,
  "message": "Invalid or missing API key",
  "errors": null
}
application/json
{
  "success": false,
  "message": "Outreach lead not found",
  "errors": null
}

Response Fields

FieldTypeDescription
idintegerUnique lead identifier
lead_statusstringLead status: cold or warm
warming_scoreintegerCurrent warming score (0-100)
monitoring_pagesarrayDiscovered monitoring sources with scores and selection status
contactsarrayKey contacts associated with this lead
competitorsarrayCompetitors being tracked
touchpointsarrayRecent activity log entries
recommendationsarrayActive engagement recommendations
deal_valuenumber | nullDeal value (warm leads)
deal_wonbooleanWhether the deal has been won

Add to Outreach

Add one or more saved companies to the outreach pipeline. Companies can be added as cold leads (for social warming) or warm leads (for active nurturing).

POST /v1/outreach/leads

Request Body

FieldTypeRequiredDescription
company_idsinteger[]YesArray of company IDs to add (1-100)
lead_statusstringNoLead status: cold (default) or warm

Response

application/json
{
  "success": true,
  "created": 2,
  "restored": 0,
  "moved": 0,
  "skipped": 1,
  "total": 3
}
application/json
{
  "success": false,
  "message": "Invalid or missing API key",
  "errors": null
}

Response Fields

FieldTypeDescription
successbooleanWhether the operation succeeded
createdintegerNumber of new leads created
restoredintegerNumber of previously deleted leads restored
movedintegerNumber of leads moved between cold/warm
skippedintegerNumber already in requested status
totalintegerTotal leads processed

Remove from Outreach

Remove one or more leads from the outreach pipeline. This soft-deletes the leads and frees any associated site signal slots.

DELETE /v1/outreach/leads

Request Body

FieldTypeRequiredDescription
lead_idsinteger[]YesArray of lead IDs to remove (1-100)

Response

application/json
{
  "success": true,
  "deleted": 2
}
application/json
{
  "success": false,
  "message": "Invalid or missing API key",
  "errors": null
}

Change Lead Status

Move a lead between cold and warm status. When moving to warm, contacts with monitoring pages are automatically pinned as key contacts.

PATCH /v1/outreach/leads/:id/status

Path Parameters

ParameterTypeDescription
idintegerThe lead ID

Request Body

FieldTypeRequiredDescription
lead_statusstringYesNew status: cold or warm

Response

application/json
{
  "success": true,
  "id": 1,
  "lead_status": "warm"
}
application/json
{
  "success": false,
  "message": "Invalid or missing API key",
  "errors": null
}
application/json
{
  "success": false,
  "message": "Outreach lead not found",
  "errors": null
}

Update Target Score

Update the target warming score for a lead. When the warming score reaches the target, the lead is considered ready for direct outreach.

PATCH /v1/outreach/leads/:id/target-score

Path Parameters

ParameterTypeDescription
idintegerThe lead ID

Request Body

FieldTypeRequiredDescription
target_scoreintegerYesTarget warming score (1-100)

Response

application/json
{
  "success": true,
  "id": 1,
  "target_score": 80
}
application/json
{
  "success": false,
  "message": "Invalid or missing API key",
  "errors": null
}
application/json
{
  "success": false,
  "message": "Outreach lead not found",
  "errors": null
}

Start Signal Scan

Start an AI-powered signal scan for an outreach lead. Discovers company web pages (blog, careers, press), social profiles, and key contacts' social accounts. Each source is scored 1-10 for monitoring value. Occupies 1 site signal slot (capacity-based -- slot is freed when lead is removed).

POST /v1/outreach/leads/:id/signal-scan

Path Parameters

ParameterTypeDescription
idintegerThe lead ID

Response

application/json
{
  "success": true,
  "job_id": "scan_abc123",
  "max_pages": 20,
  "estimated_time_minutes": 2
}
application/json
{
  "success": false,
  "message": "Invalid or missing API key",
  "errors": null
}
application/json
{
  "success": false,
  "message": "You've reached your plan limit for site signals.",
  "errors": null
}

Get Signal Scan Status

Check the progress of a signal scan. Returns the scan status, progress percentage, and discovered pages once complete.

GET /v1/outreach/leads/:id/signal-scan

Path Parameters

ParameterTypeDescription
idintegerThe lead ID

Response

application/json
{
  "status": "completed",
  "progress": 100,
  "pages_found": 12,
  "message": "Signal scan completed successfully"
}
application/json
{
  "status": "none",
  "message": "No signal scan has been started for this lead"
}
application/json
{
  "success": false,
  "message": "Invalid or missing API key",
  "errors": null
}

Toggle Monitoring Page

Enable or disable a specific monitoring source discovered by a signal scan. Enabled sources are monitored for new content and used to generate engagement recommendations.

PATCH /v1/outreach/leads/:id/monitoring-pages/:pageId

Path Parameters

ParameterTypeDescription
idintegerThe lead ID
pageIdintegerThe monitoring page ID

Request Body

FieldTypeRequiredDescription
is_selectedbooleanYesWhether to enable (true) or disable (false) monitoring

Response

application/json
{
  "success": true,
  "id": 5,
  "is_selected": true
}
application/json
{
  "success": false,
  "message": "Invalid or missing API key",
  "errors": null
}
application/json
{
  "success": false,
  "message": "Monitoring page not found",
  "errors": null
}

Get Recommendations

Get AI-generated engagement recommendations for a lead. For cold leads: social warming actions (comment on posts, follow, like). For warm leads: nurturing actions (share content, check in, congratulate). Each includes a target URL, suggested comment, priority, and engagement reason.

GET /v1/outreach/leads/:id/recommendations

Path Parameters

ParameterTypeDescription
idintegerThe lead ID

Response

application/json
{
  "recommendations": [
    {
      "id": 1,
      "action_type": "comment",
      "target_url": "https://acme.com/blog/ai-trends",
      "suggested_comment": "Great insights on AI adoption...",
      "priority": "high",
      "engagement_reason": "Recent blog post relevant to your offering",
      "source_page_label": "Company Blog",
      "created_at": "2024-01-18T10:00:00.000Z"
    }
  ],
  "count": 1,
  "progress": { "completed": 3, "dismissed": 1, "total": 5 }
}
application/json
{
  "success": false,
  "message": "Invalid or missing API key",
  "errors": null
}
application/json
{
  "success": false,
  "message": "Outreach lead not found",
  "errors": null
}

Generate Recommendations

Trigger AI generation of new engagement recommendations based on the lead's enabled monitoring sources. Recommendations are generated asynchronously -- poll with Get Recommendations to see results.

POST /v1/outreach/leads/:id/recommendations/generate

Path Parameters

ParameterTypeDescription
idintegerThe lead ID

Response

application/json
{
  "started": true
}
application/json
{
  "success": false,
  "message": "Invalid or missing API key",
  "errors": null
}
application/json
{
  "success": false,
  "message": "Outreach lead not found",
  "errors": null
}

Complete Recommendation

Mark a recommendation as completed. Completing a recommendation awards warming score points and auto-dismisses other recommendations targeting the same post.

POST /v1/outreach/leads/:id/recommendations/:recId/complete

Path Parameters

ParameterTypeDescription
idintegerThe lead ID
recIdintegerThe recommendation ID

Response

application/json
{
  "success": true,
  "dismissedIds": [4, 5]
}
application/json
{
  "success": false,
  "message": "Invalid or missing API key",
  "errors": null
}
application/json
{
  "success": false,
  "message": "Recommendation not found or already completed",
  "errors": null
}

Dismiss Recommendation

Dismiss a recommendation you don't want to act on. Dismissed recommendations are hidden from the active list.

POST /v1/outreach/leads/:id/recommendations/:recId/dismiss

Path Parameters

ParameterTypeDescription
idintegerThe lead ID
recIdintegerThe recommendation ID

Response

application/json
{
  "success": true
}
application/json
{
  "success": false,
  "message": "Invalid or missing API key",
  "errors": null
}
application/json
{
  "success": false,
  "message": "Recommendation not found or already dismissed",
  "errors": null
}

Log Activity

Log a sales or engagement activity. Describe what happened in natural language -- the AI automatically detects the activity type (email, call, meeting, LinkedIn message, comment, like, follow, etc.) and any dates mentioned. Activities contribute to the warming score with context-aware bonus points.

POST /v1/outreach/leads/:id/touchpoints

Path Parameters

ParameterTypeDescription
idintegerThe lead ID

Request Body

FieldTypeRequiredDescription
descriptionstringYesNatural language description of the activity (1-5000 chars)
contact_idinteger | nullNoOptional contact ID this activity is about

Response

application/json
{
  "success": true,
  "touchpoint": {
    "id": 15,
    "description": "Commented on their LinkedIn post about AI trends",
    "detected_type": "comment",
    "contact_id": null,
    "bonus_points": 3,
    "created_at": "2024-01-18T14:20:00.000Z"
  },
  "detected_type": "comment",
  "bonus_info": {
    "base_points": 2,
    "multiplier": 1.5,
    "total": 3,
    "reason": "First engagement with this company"
  }
}
application/json
{
  "success": false,
  "message": "Invalid or missing API key",
  "errors": null
}
application/json
{
  "success": false,
  "message": "Outreach lead not found",
  "errors": null
}

Get Lead Notes

Get all notes for an outreach lead, ordered by creation date (newest first).

GET /v1/outreach/leads/:id/notes

Path Parameters

ParameterTypeDescription
idintegerThe lead ID

Response

application/json
{
  "data": [
    {
      "id": 1,
      "content": "CEO mentioned they're evaluating new solutions in Q2",
      "created_at": "2024-01-18T14:20:00.000Z"
    }
  ],
  "total": 1
}
application/json
{
  "success": false,
  "message": "Invalid or missing API key",
  "errors": null
}
application/json
{
  "success": false,
  "message": "Outreach lead not found",
  "errors": null
}

Add Lead Note

Add a note to an outreach lead. Notes are free-form text for recording observations, strategy, or context about the lead.

POST /v1/outreach/leads/:id/notes

Path Parameters

ParameterTypeDescription
idintegerThe lead ID

Request Body

FieldTypeRequiredDescription
contentstringYesNote content (1-10000 chars)

Response

application/json
{
  "success": true,
  "note": {
    "id": 5,
    "content": "CEO mentioned they're evaluating new solutions in Q2",
    "created_at": "2024-01-18T14:20:00.000Z"
  }
}
application/json
{
  "success": false,
  "message": "Invalid or missing API key",
  "errors": null
}
application/json
{
  "success": false,
  "message": "Outreach lead not found",
  "errors": null
}

Toggle Key Contact

Pin or unpin a contact as a key contact for this lead. Key contacts appear in the lead dashboard and their social profiles can be monitored for engagement opportunities.

PATCH /v1/outreach/leads/:id/contacts/:contactId

Path Parameters

ParameterTypeDescription
idintegerThe lead ID
contactIdintegerThe contact ID

Request Body

FieldTypeRequiredDescription
is_selectedbooleanYesWhether to pin (true) or unpin (false) the contact

Response

application/json
{
  "success": true,
  "id": 12,
  "is_selected": true
}
application/json
{
  "success": false,
  "message": "Invalid or missing API key",
  "errors": null
}
application/json
{
  "success": false,
  "message": "Outreach lead not found",
  "errors": null
}

Update Deal

Update deal tracking information for a warm lead. Set the deal value, currency, and mark deals as won. All fields are optional -- only provided fields are updated.

PATCH /v1/outreach/leads/:id/deal

Path Parameters

ParameterTypeDescription
idintegerThe lead ID

Request Body

FieldTypeRequiredDescription
deal_valuenumber | nullNoDeal value (set to null to clear)
deal_currencystringNoCurrency code, e.g., USD, EUR, GBP (1-3 chars)
deal_wonbooleanNoWhether the deal has been won

Response

application/json
{
  "success": true,
  "id": 1,
  "deal_value": 50000,
  "deal_currency": "USD",
  "deal_won": false
}
application/json
{
  "success": false,
  "message": "Invalid or missing API key",
  "errors": null
}
application/json
{
  "success": false,
  "message": "Outreach lead not found",
  "errors": null
}

Add Competitor

Add a competitor to monitor for an outreach lead. Competitors can be scanned to discover their web pages and content for competitive intelligence.

POST /v1/outreach/leads/:id/competitors

Path Parameters

ParameterTypeDescription
idintegerThe lead ID

Request Body

FieldTypeRequiredDescription
namestringYesCompetitor company name (1-255 chars)
websitestringYesCompetitor website URL (1-255 chars)

Response

application/json
{
  "success": true,
  "competitor": {
    "id": 3,
    "name": "Competitor Inc",
    "website": "competitor.com",
    "is_pinned": false,
    "scan_status": "none"
  }
}
application/json
{
  "success": false,
  "error": "A competitor with this website already exists: Competitor Inc"
}
application/json
{
  "success": false,
  "message": "Invalid or missing API key",
  "errors": null
}
application/json
{
  "success": false,
  "message": "Outreach lead not found",
  "errors": null
}

Scan Competitor

Start an AI scan of a competitor's website to discover their pages, content, and positioning. Scan results are stored on the competitor record.

POST /v1/outreach/leads/:id/competitors/:competitorId/scan

Path Parameters

ParameterTypeDescription
idintegerThe lead ID
competitorIdintegerThe competitor ID

Response

application/json
{
  "success": true,
  "competitor_id": 3,
  "estimated_time_minutes": 1
}
application/json
{
  "success": false,
  "message": "Invalid or missing API key",
  "errors": null
}
application/json
{
  "success": false,
  "message": "Competitor not found",
  "errors": null
}

Toggle Competitor Pin

Pin or unpin a competitor. Pinned competitors are highlighted in the lead dashboard for quick access.

PATCH /v1/outreach/leads/:id/competitors/:competitorId

Path Parameters

ParameterTypeDescription
idintegerThe lead ID
competitorIdintegerThe competitor ID

Request Body

FieldTypeRequiredDescription
is_pinnedbooleanYesWhether to pin (true) or unpin (false) the competitor

Response

application/json
{
  "success": true,
  "id": 3,
  "is_pinned": true
}
application/json
{
  "success": false,
  "message": "Invalid or missing API key",
  "errors": null
}
application/json
{
  "success": false,
  "message": "Competitor not found",
  "errors": null
}

Rate Limits

API access is available on Starter and Pro plans. All API requests are subject to rate limiting to ensure fair usage and service stability.

Limits

Requests are limited to 100 requests per minute per API key. If you exceed this limit, requests will return a 429 Too Many Requests response.

Response Headers

Rate limit information is included in all API responses:

Response Headers
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 99
X-RateLimit-Reset: 1705320000

Errors

The API uses standard HTTP status codes to indicate success or failure of requests.

Status CodeDescription
200Success
400Bad Request - Invalid parameters
401Unauthorized - Invalid or missing API key
403Forbidden - API access not available on your plan
404Not Found - Resource does not exist
429Too Many Requests - Rate limit exceeded
500Internal Server Error

Error Response Format

application/json
{
  "error": "Invalid or missing API key"
}