Overview
This example shows how to fetch all matching profiles from a search query by combiningsearchByQuery with scrollSearch. Each batch is saved to the database immediately as it arrives — rather than collecting everything in memory first — to minimize memory usage and avoid data loss if the process is interrupted.
The pattern is:
- Send the initial
searchByQueryrequest — save the first batch, get ascrollId - Loop: save each batch, send
scrollSearchwith the newscrollId - Stop when no
scrollIdis returned in the response
The next
scrollSearch request must be sent within 15 seconds of the previous response. Do not perform slow operations (heavy processing, external API calls) between scroll requests — save to the database and immediately request the next batch.import requests
import psycopg2
import json
API_KEY = "your_secret_api_key"
BASE_URL = "https://www.signalhire.com/api/v1/candidate"
HEADERS = {"apikey": API_KEY, "Content-Type": "application/json"}
def save_batch(cur, profiles: list):
for profile in profiles:
cur.execute(
"""
INSERT INTO candidates (uid, full_name, location, skills, open_to_work, raw_data)
VALUES (%s, %s, %s, %s, %s, %s)
ON CONFLICT (uid) DO UPDATE SET
full_name = EXCLUDED.full_name,
location = EXCLUDED.location,
skills = EXCLUDED.skills,
open_to_work = EXCLUDED.open_to_work,
raw_data = EXCLUDED.raw_data
""",
(
profile["uid"],
profile.get("fullName"),
profile.get("location"),
json.dumps(profile.get("skills", [])),
profile.get("openToWork", False),
json.dumps(profile),
)
)
def search_and_save(query: dict):
conn = psycopg2.connect("postgresql://user:password@localhost/mydb")
cur = conn.cursor()
total_saved = 0
# Initial search
response = requests.post(f"{BASE_URL}/searchByQuery", headers=HEADERS, json=query)
response.raise_for_status()
data = response.json()
request_id = data["requestId"]
scroll_id = data.get("scrollId")
total = data["total"]
print(f"Total profiles found: {total}")
save_batch(cur, data.get("profiles", []))
conn.commit()
total_saved += len(data.get("profiles", []))
print(f"Saved {total_saved} / {total}")
# Scroll through remaining batches
while scroll_id:
response = requests.post(
f"{BASE_URL}/scrollSearch/{request_id}",
headers=HEADERS,
json={"scrollId": scroll_id}
)
response.raise_for_status()
data = response.json()
save_batch(cur, data.get("profiles", []))
conn.commit()
total_saved += len(data.get("profiles", []))
scroll_id = data.get("scrollId")
print(f"Saved {total_saved} / {total}")
cur.close()
conn.close()
print("Done")
search_and_save({
"currentTitle": "(Software AND Engineer) OR Developer",
"location": "New York, New York, United States",
"keywords": "PHP AND JavaScript",
"size": 50
})
const axios = require('axios');
const { Pool } = require('pg');
const API_KEY = 'your_secret_api_key';
const BASE_URL = 'https://www.signalhire.com/api/v1/candidate';
const HEADERS = { apikey: API_KEY, 'Content-Type': 'application/json' };
const pool = new Pool({ connectionString: 'postgresql://user:password@localhost/mydb' });
async function saveBatch(client, profiles) {
for (const profile of profiles) {
await client.query(
`INSERT INTO candidates (uid, full_name, location, skills, open_to_work, raw_data)
VALUES ($1, $2, $3, $4, $5, $6)
ON CONFLICT (uid) DO UPDATE SET
full_name = EXCLUDED.full_name,
location = EXCLUDED.location,
skills = EXCLUDED.skills,
open_to_work = EXCLUDED.open_to_work,
raw_data = EXCLUDED.raw_data`,
[
profile.uid,
profile.fullName ?? null,
profile.location ?? null,
JSON.stringify(profile.skills ?? []),
profile.openToWork ?? false,
JSON.stringify(profile),
]
);
}
}
async function searchAndSave(query) {
const client = await pool.connect();
let totalSaved = 0;
try {
// Initial search
const initial = await axios.post(`${BASE_URL}/searchByQuery`, query, { headers: HEADERS });
const { requestId, total } = initial.data;
let scrollId = initial.data.scrollId;
console.log(`Total profiles found: ${total}`);
await client.query('BEGIN');
await saveBatch(client, initial.data.profiles);
await client.query('COMMIT');
totalSaved += initial.data.profiles.length;
console.log(`Saved ${totalSaved} / ${total}`);
// Scroll through remaining batches
while (scrollId) {
const response = await axios.post(
`${BASE_URL}/scrollSearch/${requestId}`,
{ scrollId },
{ headers: HEADERS }
);
scrollId = response.data.scrollId;
await client.query('BEGIN');
await saveBatch(client, response.data.profiles);
await client.query('COMMIT');
totalSaved += response.data.profiles.length;
console.log(`Saved ${totalSaved} / ${total}`);
}
console.log('Done');
} catch (err) {
await client.query('ROLLBACK');
throw err;
} finally {
client.release();
await pool.end();
}
}
searchAndSave({
currentTitle: '(Software AND Engineer) OR Developer',
location: 'New York, New York, United States',
keywords: 'PHP AND JavaScript',
size: 50,
}).catch(console.error);
import java.net.http.*;
import java.net.URI;
import java.sql.*;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.*;
public class SignalHireSearch {
private static final String API_KEY = "your_secret_api_key";
private static final String BASE_URL = "https://www.signalhire.com/api/v1/candidate";
private static final HttpClient client = HttpClient.newHttpClient();
private static final ObjectMapper mapper = new ObjectMapper();
static void saveBatch(Connection conn, List<Map<String, Object>> profiles) throws Exception {
String sql = """
INSERT INTO candidates (uid, full_name, location, open_to_work, raw_data)
VALUES (?, ?, ?, ?, ?::jsonb)
ON CONFLICT (uid) DO UPDATE SET
full_name = EXCLUDED.full_name,
location = EXCLUDED.location,
open_to_work = EXCLUDED.open_to_work,
raw_data = EXCLUDED.raw_data
""";
try (PreparedStatement stmt = conn.prepareStatement(sql)) {
for (Map<String, Object> profile : profiles) {
stmt.setString(1, (String) profile.get("uid"));
stmt.setString(2, (String) profile.get("fullName"));
stmt.setString(3, (String) profile.get("location"));
stmt.setBoolean(4, Boolean.TRUE.equals(profile.get("openToWork")));
stmt.setString(5, mapper.writeValueAsString(profile));
stmt.addBatch();
}
stmt.executeBatch();
}
}
static Map<String, Object> post(String path, Object body) throws Exception {
String json = mapper.writeValueAsString(body);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(BASE_URL + path))
.header("apikey", API_KEY)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(json))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
return mapper.readValue(response.body(), Map.class);
}
public static void searchAndSave(Map<String, Object> query) throws Exception {
try (Connection conn = DriverManager.getConnection(
"jdbc:postgresql://localhost/mydb", "user", "password")) {
conn.setAutoCommit(false);
int totalSaved = 0;
// Initial search
Map<String, Object> data = post("/searchByQuery", query);
int requestId = (int) data.get("requestId");
int total = (int) data.get("total");
String scrollId = (String) data.get("scrollId");
System.out.println("Total profiles found: " + total);
saveBatch(conn, (List) data.get("profiles"));
conn.commit();
totalSaved += ((List) data.get("profiles")).size();
System.out.println("Saved " + totalSaved + " / " + total);
// Scroll through remaining batches
while (scrollId != null) {
Map<String, Object> scrollData = post(
"/scrollSearch/" + requestId,
Map.of("scrollId", scrollId)
);
List<Map<String, Object>> profiles = (List) scrollData.get("profiles");
scrollId = (String) scrollData.get("scrollId");
saveBatch(conn, profiles);
conn.commit();
totalSaved += profiles.size();
System.out.println("Saved " + totalSaved + " / " + total);
}
System.out.println("Done");
}
}
public static void main(String[] args) throws Exception {
Map<String, Object> query = new HashMap<>();
query.put("currentTitle", "(Software AND Engineer) OR Developer");
query.put("location", "New York, New York, United States");
query.put("keywords", "PHP AND JavaScript");
query.put("size", 50);
searchAndSave(query);
}
}
require 'net/http'
require 'json'
require 'pg'
API_KEY = 'your_secret_api_key'
BASE_URL = 'https://www.signalhire.com/api/v1/candidate'
def api_post(path, body)
uri = URI("#{BASE_URL}#{path}")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri)
request['apikey'] = API_KEY
request['Content-Type'] = 'application/json'
request.body = body.to_json
JSON.parse(http.request(request).body)
end
def save_batch(conn, profiles)
profiles.each do |profile|
conn.exec_params(
<<~SQL,
INSERT INTO candidates (uid, full_name, location, open_to_work, raw_data)
VALUES ($1, $2, $3, $4, $5)
ON CONFLICT (uid) DO UPDATE SET
full_name = EXCLUDED.full_name,
location = EXCLUDED.location,
open_to_work = EXCLUDED.open_to_work,
raw_data = EXCLUDED.raw_data
SQL
[
profile['uid'],
profile['fullName'],
profile['location'],
profile['openToWork'] ? 't' : 'f',
profile.to_json
]
)
end
end
def search_and_save(query)
conn = PG.connect(dbname: 'mydb', user: 'user', password: 'password', host: 'localhost')
total_saved = 0
# Initial search
data = api_post('/searchByQuery', query)
request_id = data['requestId']
total = data['total']
scroll_id = data['scrollId']
puts "Total profiles found: #{total}"
conn.transaction { save_batch(conn, data['profiles']) }
total_saved += data['profiles'].size
puts "Saved #{total_saved} / #{total}"
# Scroll through remaining batches
while scroll_id
data = api_post("/scrollSearch/#{request_id}", { scrollId: scroll_id })
scroll_id = data['scrollId']
conn.transaction { save_batch(conn, data['profiles']) }
total_saved += data['profiles'].size
puts "Saved #{total_saved} / #{total}"
end
conn.close
puts 'Done'
end
search_and_save(
currentTitle: '(Software AND Engineer) OR Developer',
location: 'New York, New York, United States',
keywords: 'PHP AND JavaScript',
size: 50
)