Skip to main content

Sync strategy

The recommended method to sync Pylote freelancers into your database:

Initial sync

Use modifiedTime=0 to fetch all freelancers, then paginate:
const axios = require('axios');

async function syncAllFreelances() {
  const API_URL = 'https://client-p.pylote.io/freelances';
  const API_KEY = process.env.PYLOTE_API_KEY;
  const limit = 1000;
  let page = 1;
  let totalPages = 1;
  let allFreelances = [];

  while (page <= totalPages) {
    const response = await axios.get(API_URL, {
      headers: { 'x-api-key': API_KEY },
      params: { modifiedTime: 0, page, limit, showDeleted: false }
    });

    allFreelances.push(...response.data.freelances);
    totalPages = response.data.infos.totalPages;
    page++;
  }

  console.log(`${allFreelances.length} freelancers synced`);
  return allFreelances;
}

Incremental sync

Store the timestamp of your last sync, then fetch only the profiles modified since then:
async function syncUpdatedFreelances(lastSyncTimestamp) {
  const response = await axios.get(API_URL, {
    headers: { 'x-api-key': API_KEY },
    params: {
      modifiedTime: lastSyncTimestamp,  // Unix timestamp in seconds
      page: 1,
      limit: 1000,
      showDeleted: true  // include deleted profiles
    }
  });

  for (const freelance of response.data.freelances) {
    if (freelance.meta.status === 'deleted') {
      // REQUIRED: delete from your database
      await deleteFromYourDB(freelance.meta.id);
    } else {
      await upsertInYourDB(freelance);
    }
  }

  // Store the new timestamp for the next sync
  return Math.floor(Date.now() / 1000);
}
Freelancers with status: deleted must be deleted from your database within 30 days. This is a contractual commitment. The possible deletion statuses are: account_deleted, account_banned, account_excluded, account_invisible.

Pagination

Responses are paginated with the page and limit parameters. Each response includes an infos object:
{
  "freelances": [...],
  "infos": {
    "total": 1234,
    "page": 1,
    "limit": 50,
    "totalPages": 25
  }
}
ParameterTypeRequiredDescription
pageintegerYesPage number (starts at 1)
limitintegerYesFreelancers per page (max 1000)
modifiedTimeintegerYesUnix timestamp (seconds). 0 = all
showDeletedbooleanNoInclude deleted profiles (default: true)
Use limit=1000 to minimize the number of requests. An initial sync of ~22,000 freelancers takes about 22 pages.

Deduplication

The same freelancer may be present on several platforms. The meta.personalEmail field (SHA-256 hash of the personal email) lets you deduplicate:
// If two profiles share the same personalEmail, it's the same freelancer.
// Keep the one with the most recent updatedAt.
const seen = new Map();
for (const f of freelances) {
  const hash = f.meta.personalEmail;
  if (!seen.has(hash) || f.meta.updatedAt > seen.get(hash).meta.updatedAt) {
    seen.set(hash, f);
  }
}

Tracked URLs (hive.pylote.io)

The basics.url field (LinkedIn) and the CV link in basics.profiles go through the hive.pylote.io proxy. These URLs:
  • Track profile views for the freelancer’s stats
  • Contain a watermark (the caller’s IP encoded in base64)
  • Must not be shared outside your platform
Format:
https://hive.pylote.io/linkedin/{encrypted_id}/{clientPk}?t={watermark}
https://hive.pylote.io/cv/{encrypted_id}/{clientPk}?t={watermark}

Fetching specific freelancers

If you need to re-sync a subset of profiles (for example after an incident), use POST /freelances with a list of IDs:
curl -X POST "https://client-p.pylote.io/freelances?modifiedTime=0&page=1&limit=100" \
  -H "x-api-key: your-key" \
  -H "Content-Type: application/json" \
  -d '{
    "ids": [
      "bc1d49cf-1cc7-4afe-92aa-0ebd545fcd12",
      "960a1c67-645d-4ac5-b8b2-875fb2f740d3"
    ]
  }'
StrategyFrequencyUse case
Classic cronEvery hourEnough for most clients
Near real-timeEvery 15 minIf availability freshness is critical
Initial syncOnceFirst population of your database
The API cache is refreshed every hour. Syncing more often than hourly brings no additional data.