Install the Up2Data skill

Teach Claude Code to integrate the Up2Data API. Installs once, then loads automatically whenever you ask it about Up2Data.

  1. Run this in your terminal
    bash
    mkdir -p ~/.claude/skills/up2data-api && curl -fsSL https://up2data.ai/docs/legacy/skill.md -o ~/.claude/skills/up2data-api/SKILL.md
  2. Restart Claude CodeSo it picks up the new skill.
  3. Try it"Enrich these LinkedIn profiles with Up2Data and send results to my webhook"

Requires Claude Code. macOS, Linux, or WSL.

Prefer to read it first? View the skill file

Webhooks

Receive enrichment results automatically instead of polling, with tags, retries and the callback item shape.

Webhooks push enrichment results to your server as soon as they are ready, instead of you polling /open-refresh/status.

#Setting up webhooks

  1. Log in to the dashboard
  2. Navigate to Settings from the sidebar
  3. Click on Integrations
  4. Click Create Webhook
  5. Enter a name and your webhook URL (must be HTTPS)
  6. Optionally add custom headers for authentication
  7. Optionally add tags so individual requests can target this webhook
Info

Your webhook endpoint must respond with a 2xx status code within 3 seconds. Failed deliveries are retried with progressive backoff: 10 minutes, 1 hour, 8 hours, then 24 hours.

#Custom headers

Each webhook has a name, an endpoint URL, and optional custom headers for authenticating incoming requests:

json
[
  { "name": "X-Webhook-Secret", "value": "your-secret-key" },
  { "name": "Authorization", "value": "your-token" }
]
Info

Webhook payloads can contain both profiles and companies in the same request.

#Routing callbacks with tags

Tag your webhooks in the dashboard, then name those tags in a request with webhookTags to decide where that request's callbacks go. This is how you keep environments apart: give development, staging and production endpoints their own tags, and each one receives only the results it asked for.

bash
curl -X POST https://api.up2data.ai/api/open-refresh/profiles-bulk \
  -H "Content-Type: application/json" \
  -H "Authorization: YOUR_ACCESS_TOKEN" \
  -d '{
    "name": "Q1 Leads Enrichment",
    "profiles": ["https://www.linkedin.com/in/johndoe"],
    "priority": 2,
    "webhookTags": ["staging"]
  }'
webhookTags value Behavior
Omitted Every webhook on the team receives the callbacks, exactly as before; existing integrations need no changes
One or more tags Every webhook carrying any of the named tags receives the callbacks; webhooks can share a tag, so one tag can deliver to a whole group
[] (empty array) Callbacks are switched off for that request; poll status and list instead

Every enqueue response echoes back the webhooks your tags resolved to, so you can confirm the routing as soon as you wire an environment up:

json
{
  "message": "1 items have been enqueued.",
  "queueId": "507f1f77bcf86cd799439011",
  "webhooks": [
    {
      "_id": "6512c1a4f1b2c3d4e5f60789",
      "name": "Staging",
      "url": "https://staging.example.com/hooks/up2data"
    }
  ]
}
Warning

A tag that matches no webhook is rejected with 400 and nothing is enqueued, so a typo never leaves you waiting on callbacks that were never going to arrive.

json
{
  "message": "No webhook matches webhookTags: stage. Available tags: dev, prod, staging."
}
Info

Tags are matched after being lowercased and trimmed, may contain letters, digits, dot, underscore and hyphen, and are limited to 32 characters. A single request may name at most 10 of them. Tags are resolved each time a callback is sent, so a webhook you tag later still receives results from requests that are still running.

#The callback payload

Each webhook request is an array of callback items. type says what kind of record the item carries, and result holds the full enriched object.

json
{
  "queueId": "string",
  "name": "string",
  "url": "string",
  "type": "string (\"Profile\" | \"Company\" | \"Activity\" | \"ActivityPosts\" | \"ActivityComments\" | \"ActivityReactions\" | \"LatestPost\" | \"PostByUrl\")",
  "status": "number",
  "_id": "string",
  "totalResults": "number (Sales Navigator callbacks only)",
  "result": {}
}

See the Callback item schema for the full field list, and Profile, Company, Activity and Post by URL for what result contains for each type.

Info

totalResults is only sent by callbacks from a Sales Navigator queue. It is how many results the search itself reported, not how many you receive, it is repeated on every item, and it is absent until the first page of that queue has been scraped.

#Handling failures

Attempt Delay
Retry 1 10 minutes
Retry 2 1 hour
Retry 3 8 hours
Retry 4 24 hours

After 4 failed attempts, the webhook delivery is abandoned. Use GET /open-refresh/list with failed=true to find items whose webhook delivery failed.

#Webhooks vs polling

Polling Webhooks
How it works Your server repeatedly calls the API to check for updates Results are pushed to your server the moment they are ready
Cost Most requests return no new data, and count toward your rate limits Zero wasted requests
Latency Delayed by your poll interval Real time, no delays
Code You build and maintain polling loops, timers and state tracking Point at an endpoint URL and process incoming data
bash
# Poll every 30 seconds until done
while true; do
  STATUS=$(curl -s -H "Authorization: $TOKEN" \
    "https://api.up2data.ai/api/open-refresh/status?queueId=$QUEUE_ID")

  if echo "$STATUS" | grep -q '"completed"'; then
    # Now fetch the results
    curl -H "Authorization: $TOKEN" \
      "https://api.up2data.ai/api/open-refresh/list?queueId=$QUEUE_ID&page=0&limit=25"
    break
  fi

  sleep 30
done
bash
# Your server just listens for incoming POST requests
# No polling, no timers, no state management

# Up2Data sends results as soon as they're ready:
# POST https://your-server.com/webhook
# Content-Type: application/json
# X-Webhook-Secret: your-secret
#
# [{ "type": "Profile", "result": { } }]