This guide covers three ways to send your Browse AI scraped data into Attio, from no-code automation to custom API integrations. Choose the method that best fits your team's technical comfort and use case.
π Prerequisites: You'll need an approved Browse AI robot with scraped data and an Attio workspace. For API-based methods, you'll also need a Browse AI API key and an Attio access token.
Which method should I use?
Here's a quick comparison to help you decide:
Method | Best for | Technical level | Speed |
Zapier / Make | Teams without developers | No code | Near real-time |
Webhooks | Real-time pipelines with custom logic | Intermediate | Real-time |
API polling | Batch processing, scheduled syncs | Intermediate | On schedule |
Method 1: Zapier or Make (no code)
The fastest way to connect Browse AI to Attio. No coding required. Just map your scraped fields to Attio objects.
Setting up with Zapier
Go to zapier.com and create a new Zap.
Trigger: Choose Browse AI as the trigger app, then select New Successful Task Finished as the event.
Connect your Browse AI account and select the robot you want to sync data from.
Action: Choose Attio as the action app, then select Create Record and choose the object type (People, Companies, or a custom object).
Connect your Attio workspace.
Map your fields: Match each Browse AI captured field to the corresponding Attio attribute. For example, map
emailβEmail addresses,company_nameβName.Test the Zap, then turn it on.
π‘ Tip: Attio automatically deduplicates People by email address and Companies by domain. If a record already exists, you can use Attio's Assert Record action in Zapier to update it instead of creating a duplicate.
Setting up with Make (formerly Integromat)
Create a new scenario in Make.
Add a Webhooks β Custom webhook module as the trigger. Copy the webhook URL.
In Browse AI, go to your robot's Integrate tab and add the Make webhook URL under Webhooks. Select the
taskFinishedSuccessfullyevent.Add an Attio β Create a Record module (or use HTTP module with Attio's API), connect your account, and map your fields.
Activate the scenario.
Method 2: Webhooks (real-time, custom code)
Use Browse AI webhooks to push data directly into Attio as soon as a task finishes. This gives you full control over data transformation and error handling.
How it works
Browse AI sends a webhook POST request to your server when a task completes.
Your server receives the scraped data and transforms it for Attio's REST API.
Your server calls Attio's API to create or assert (upsert) records.
Step 1: Create an Attio integration and access token
In Attio, go to Workspace Settings β Developers.
Click Create a new integration.
Name it (e.g. "Browse AI Integration").
Generate an access token and copy it.
β οΈ Keep your access token secure. Store it in environment variables. Never hardcode it in your source code or commit it to version control. Access tokens are scoped to a single workspace.
Step 2: Build your webhook endpoint
This example receives Browse AI webhook data and creates (or updates) a Person record in Attio. Attio's assert endpoint is ideal here as it creates a record if it doesn't exist, or returns the existing one if it matches on a unique attribute like email:
import requests
from flask import Flask, request, jsonifyapp = Flask(__name__)ATTIO_ACCESS_TOKEN = "your_attio_access_token"
ATTIO_API_URL = "https://api.attio.com/v2"def assert_person(person_data):
"""Create or find a Person record in Attio (upsert by email)."""
resp = requests.put(
f"{ATTIO_API_URL}/objects/people/records",
headers={
"Authorization": f"Bearer {ATTIO_ACCESS_TOKEN}",
"Content-Type": "application/json"
},
json={
"data": {
"values": person_data
},
"matching_attribute": "email_addresses"
}
)
return resp.json()def assert_company(company_data):
"""Create or find a Company record in Attio (upsert by domain)."""
resp = requests.put(
f"{ATTIO_API_URL}/objects/companies/records",
headers={
"Authorization": f"Bearer {ATTIO_ACCESS_TOKEN}",
"Content-Type": "application/json"
},
json={
"data": {
"values": company_data
},
"matching_attribute": "domains"
}
)
return resp.json()@app.route("/browse-ai-webhook", methods=["POST"])
def handle_webhook():
payload = request.get_json() if payload.get("event") != "taskFinishedSuccessfully":
return jsonify({"status": "ignored"}), 200 task = payload["task"]
captured = task.get("capturedTexts", {}) results = {} # Create or find the company first (if we have domain/name)
website = captured.get("website", "")
company_name = captured.get("company_name", "")
if website or company_name:
company_data = {}
if company_name:
company_data["name"] = [{"value": company_name}]
if website:
# Extract domain from URL
domain = website.replace("https://", "").replace("http://", "").split("/")[0]
company_data["domains"] = [{"domain": domain}]
results["company"] = assert_company(company_data) # Create or find the person
email = captured.get("email", "")
if email:
person_data = {
"email_addresses": [{"email_address": email}]
}
first_name = captured.get("first_name", "")
last_name = captured.get("last_name", "")
if first_name or last_name:
person_data["name"] = [{"first_name": first_name, "last_name": last_name}]
if captured.get("phone"):
person_data["phone_numbers"] = [{"phone_number": captured["phone"]}]
if captured.get("job_title"):
person_data["job_title"] = [{"value": captured["job_title"]}] results["person"] = assert_person(person_data) return jsonify({"status": "success", "results": results}), 200if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
Step 3: Register the webhook in Browse AI
Via the dashboard:
Open your robot and go to the Integrate tab.
Under Webhooks, click Add webhook.
Paste your endpoint URL (e.g.
https://yourdomain.com/browse-ai-webhook).Select the
taskFinishedSuccessfullyevent.
Via the API:
curl -X POST "https://api.browse.ai/v2/robots/YOUR_ROBOT_ID/webhooks" \
-H "Authorization: Bearer YOUR_BROWSE_AI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://yourdomain.com/browse-ai-webhook",
"events": ["taskFinishedSuccessfully"]
}'
π‘ IP allowlisting: Browse AI sends webhooks from IP address 3.228.254.190. If your server has a firewall, add this to your allowlist. See Webhooks: IP address for allowlisting.
Method 3: API polling (batch/scheduled)
If you prefer to pull data on a schedule rather than receive it in real time, you can poll the Browse AI API for completed tasks and push results into Attio in batches.
Example: Poll and sync to Attio
import requests
from datetime import datetime, timedeltaBROWSE_AI_API_KEY = "your_browse_ai_api_key"
ROBOT_ID = "your_robot_id"
ATTIO_ACCESS_TOKEN = "your_attio_access_token"def get_recent_tasks(since_hours=1):
"""Fetch tasks completed in the last N hours."""
resp = requests.get(
f"https://api.browse.ai/v2/robots/{ROBOT_ID}/tasks",
headers={"Authorization": f"Bearer {BROWSE_AI_API_KEY}"},
params={"pageSize": 100}
)
tasks = resp.json().get("result", {}).get("robotTasks", {}).get("items", []) cutoff = datetime.utcnow() - timedelta(hours=since_hours)
return [t for t in tasks if t.get("status") == "successful"
and datetime.fromisoformat(t["finishedAt"].replace("Z","")) > cutoff]def sync_to_attio(tasks):
"""Assert Person records in Attio from Browse AI task results."""
for task in tasks:
captured = task.get("capturedTexts", {})
email = captured.get("email", "")
if not email:
continue person_data = {
"email_addresses": [{"email_address": email}],
"name": [{"first_name": captured.get("first_name", ""),
"last_name": captured.get("last_name", "")}]
} requests.put(
"https://api.attio.com/v2/objects/people/records",
headers={
"Authorization": f"Bearer {ATTIO_ACCESS_TOKEN}",
"Content-Type": "application/json"
},
json={
"data": {"values": person_data},
"matching_attribute": "email_addresses"
}
)# Run this script on a schedule (e.g. cron job every hour)
π For full Browse AI API details, including pagination, bulk operations, and task filtering, see the API Guide: Getting started and API Guide: Bulk operations.
Attio-specific tips
Using the assert endpoint (built-in deduplication)
Attio's PUT /objects/{object}/records endpoint with a matching_attribute parameter is the recommended way to create records. It works as an upsert:
People: Match on
email_addresses. If a person with that email exists, it returns the existing record.Companies: Match on
domains. If a company with that domain exists, it returns the existing record.
This means you don't need to search before creating. The assert endpoint handles deduplication for you in a single API call.
Working with different objects
Attio uses the same API pattern for all objects. Change the object slug in the URL:
People:
/v2/objects/people/recordsCompanies:
/v2/objects/companies/recordsDeals:
/v2/objects/deals/recordsCustom objects:
/v2/objects/{your_custom_object_slug}/records
Rate limits
Attio allows 100 read requests per second and 25 write requests per second. If you're syncing large batches, add a small delay between write operations to stay within limits.
Common field mappings
Here are typical mappings between Browse AI captured fields and Attio Person attributes:
Browse AI field | Attio attribute | Notes |
|
| Used for deduplication |
|
| Object with |
|
| |
|
| |
| Company | Create a separate Company record and link |
| Company | Used for Company deduplication |
(origin URL) | Custom attribute or note | Available as |
π‘ Linking People to Companies: In Attio, People and Companies are separate objects. Once you've created both records, Attio automatically links them if the person's email domain matches the company's domain. No manual association needed.
Sending monitoring data to Attio
If you're using Browse AI monitors to track changes on websites, you can push change events into Attio. Use the taskCapturedDataChanged webhook event to trigger updates. For example, you could update a Person's job title when their LinkedIn profile changes, or add a note to a Company record when their pricing page is updated.
Troubleshooting
Attio returns a 401 Unauthorized error
Check that your access token is correct and that the integration hasn't been deleted. Go to Workspace Settings β Developers in Attio to verify.
Attio returns a 422 validation error
The attribute values are in the wrong format. Attio requires values to be wrapped in arrays with specific key names. For example, email must be [{"email_address": "..."}], not just a plain string.
Attio returns a 429 Too Many Requests error
You've hit the write rate limit (25 requests per second). Add a small delay between requests or batch your operations with brief pauses.
Records aren't linking to companies
Attio links People to Companies by matching email domain to company domain. Make sure you've created the Company record with the correct domain before creating the Person, or create both and let Attio auto-link.
Webhook isn't firing
Make sure the webhook URL is publicly accessible and that your server responds with a 200 status code. See the Webhooks: Set up guide for detailed debugging steps.
