Back to API Overview

Complete API Reference

All 215 endpoints across 26 sections

Authentication

User authentication and session management

1 endpoint
POST/api/auth/signup
Public

Sign Up

Register a new user account

Rate Limited: 5 requests per 15 minutes per IP address

Request Body

NameTypeRequiredDescription
emailstringRequiredUser email address
passwordstringRequiredPassword (minimum 8 characters)
namestringOptionalUser display name
termsAcceptedbooleanRequiredMust be true to accept terms
privacyAcceptedbooleanRequiredMust be true to accept privacy policy
marketingConsentbooleanOptionalOpt-in to marketing emails

Example Request

curl -X POST "https://scrappy.gg/api/auth/signup" \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"password": "your-password",
"termsAccepted": true,
"privacyAccepted": true
}'

Responses

201User created successfully
{
"success": true,
"data": {
"user": {
"id": "abc123",
"email": "user@example.com"
}
}
}
400Invalid input
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid email format"
}
}
429Rate limit exceeded
{
"success": false,
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Too many attempts"
}
}

Users

User profile and account management

2 endpoints
GET/api/users
Auth Required

Get Current User

Get the authenticated user's profile and statistics

Example Request

curl -X GET "https://scrappy.gg/api/users" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200User profile returned
{
"success": true,
"data": {
"user": {
"id": "abc123",
"email": "user@example.com",
"name": "John Doe",
"createdAt": "2024-01-01T00:00:00Z"
},
"stats": {
"sources": 10,
"jobs": 25,
"leads": 500
}
}
}
401Not authenticated
{
"success": false,
"error": {
"code": "UNAUTHORIZED",
"message": "Authentication required"
}
}
PATCH/api/users/password
Auth Required

Change Password

Update the authenticated user's password

Request Body

NameTypeRequiredDescription
currentPasswordstringRequiredCurrent password for verification
newPasswordstringRequiredNew password (minimum 8 characters)

Example Request

curl -X PATCH "https://scrappy.gg/api/users/password" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"currentPassword": "your-password",
"newPassword": "your-password"
}'

Responses

200Password updated
{
"success": true
}
400Invalid password
{
"success": false,
"error": {
"code": "INVALID_PASSWORD",
"message": "Current password is incorrect"
}
}

Data Export

GDPR subject access requests - export your data

3 endpoints
POST/api/users/export
Auth Required

Request Data Export

Request an export of all your data (JSON or CSV format)

Rate Limited: 3 requests per 1 week per user

Request Body

NameTypeRequiredDescription
formatstringOptionalExport formatOptions: JSON, CSVDefault: JSON

Example Request

curl -X POST "https://scrappy.gg/api/users/export" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"

Responses

200Export request created
{
"success": true,
"data": {
"requestId": "abc123",
"status": "PENDING",
"downloadExpiry": "2024-01-08T00:00:00Z"
}
}
429Rate limit exceeded
{
"success": false,
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Maximum 3 exports per week"
}
}
GET/api/users/export/status
Auth Required

Get Export Status

Check the status of all export requests

Example Request

curl -X GET "https://scrappy.gg/api/users/export/status" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Export status returned
{
"success": true,
"data": {
"exports": [
{
"requestId": "abc123",
"status": "COMPLETED",
"format": "JSON",
"createdAt": "2024-01-01T00:00:00Z",
"downloadExpiry": "2024-01-08T00:00:00Z"
}
]
}
}
GET/api/users/export/download
Auth Required

Download Export

Download a completed data export (7-day download window)

Query Parameters

NameTypeRequiredDescription
requestIdstringRequiredThe export request ID

Example Request

curl -X GET ?requestId=abc123"https://scrappy.gg/api/users/export/download" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200File download
{
"note": "Returns file attachment"
}
404Export not found or expired
{
"success": false,
"error": {
"code": "NOT_FOUND",
"message": "Export not found or expired"
}
}

Account Deletion

GDPR right to erasure - delete your account

4 endpoints
POST/api/users/deletion
Auth Required

Request Account Deletion

Request deletion of your account. A confirmation email will be sent.

Rate Limited: 1 requests per 1 day per user

Example Request

curl -X POST "https://scrappy.gg/api/users/deletion" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Deletion requested
{
"success": true,
"data": {
"expiresAt": "2024-01-02T00:00:00Z"
}
}
429Rate limit exceeded
{
"success": false,
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "One deletion request per day"
}
}
POST/api/users/deletion/confirm
Auth Required

Confirm Account Deletion

Confirm account deletion using the token from email. Starts 30-day grace period.

Request Body

NameTypeRequiredDescription
tokenstringRequiredConfirmation token from email (24-hour expiry)

Example Request

curl -X POST "https://scrappy.gg/api/users/deletion/confirm" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"token": "string"
}'

Responses

200Deletion confirmed
{
"success": true,
"data": {
"scheduledAt": "2024-01-31T00:00:00Z",
"gracePeriodDays": 30
}
}
400Invalid or expired token
{
"success": false,
"error": {
"code": "INVALID_TOKEN",
"message": "Token is invalid or expired"
}
}
POST/api/users/deletion/cancel
Auth Required

Cancel Account Deletion

Cancel a pending account deletion during the grace period

Example Request

curl -X POST "https://scrappy.gg/api/users/deletion/cancel" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Deletion cancelled
{
"success": true,
"message": "Account deletion cancelled"
}
400No pending deletion
{
"success": false,
"error": {
"code": "NO_PENDING_DELETION",
"message": "No deletion request to cancel"
}
}
GET/api/users/deletion/status
Auth Required

Get Deletion Status

Check the status of any pending account deletion

Example Request

curl -X GET "https://scrappy.gg/api/users/deletion/status" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Deletion status returned
{
"success": true,
"data": {
"status": "scheduled",
"scheduledAt": "2024-01-31T00:00:00Z",
"gracePeriodDays": 30
}
}

Projects

Organize sources and leads into projects

5 endpoints
GET/api/projects
Auth Required

List Projects

Get all projects for the authenticated user

Example Request

curl -X GET "https://scrappy.gg/api/projects" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Projects returned
{
"success": true,
"data": {
"projects": [
{
"id": "abc123",
"name": "My Project",
"description": "Project description",
"color": "#3b82f6",
"createdAt": "2024-01-01T00:00:00Z"
}
]
}
}
POST/api/projects
Auth Required

Create Project

Create a new project

Request Body

NameTypeRequiredDescription
namestringRequiredProject name
descriptionstringOptionalProject description
colorstringOptionalHex color code (e.g., #3b82f6)

Example Request

curl -X POST "https://scrappy.gg/api/projects" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Example Name"
}'

Responses

201Project created
{
"success": true,
"data": {
"project": {
"id": "abc123",
"name": "My Project"
}
}
}
GET/api/projects/[id]
Auth Required

Get Project

Get a specific project by ID

Path Parameters

NameTypeRequiredDescription
idstringRequiredProject ID

Example Request

curl -X GET "https://scrappy.gg/api/projects/abc123" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Project returned
{
"success": true,
"data": {
"project": {
"id": "abc123",
"name": "My Project"
}
}
}
404Project not found
{
"success": false,
"error": {
"code": "NOT_FOUND",
"message": "Project not found"
}
}
PATCH/api/projects/[id]
Auth Required

Update Project

Update a project's details

Path Parameters

NameTypeRequiredDescription
idstringRequiredProject ID

Request Body

NameTypeRequiredDescription
namestringOptionalProject name
descriptionstringOptionalProject description
colorstringOptionalHex color code

Example Request

curl -X PATCH "https://scrappy.gg/api/projects/abc123" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"

Responses

200Project updated
{
"success": true,
"data": {
"project": {
"id": "abc123",
"name": "Updated Project"
}
}
}
403Not authorized
{
"success": false,
"error": {
"code": "FORBIDDEN",
"message": "Not authorized to update this project"
}
}
DELETE/api/projects/[id]
Auth Required

Delete Project

Delete a project and all associated data

Path Parameters

NameTypeRequiredDescription
idstringRequiredProject ID

Example Request

curl -X DELETE "https://scrappy.gg/api/projects/abc123" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Project deleted
{
"success": true
}
403Not authorized
{
"success": false,
"error": {
"code": "FORBIDDEN",
"message": "Not authorized to delete this project"
}
}

Sources

Manage data sources for lead scraping

10 endpoints
GET/api/sources
Auth Required

List Sources

Get all sources with optional filtering

Query Parameters

NameTypeRequiredDescription
statusstringOptionalFilter by statusOptions: DRAFT, ACTIVE, ARCHIVED
projectIdstringOptionalFilter by project ID

Example Request

curl -X GET "https://scrappy.gg/api/sources" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Sources returned
{
"success": true,
"data": {
"sources": [
{
"id": "abc123",
"name": "Example Source",
"url": "https://example.com",
"scrapeType": "STATIC",
"status": "ACTIVE"
}
]
}
}
POST/api/sources
Auth Required

Create Source

Create a new source for lead scraping

Request Body

NameTypeRequiredDescription
namestringRequiredSource name
urlstringRequiredURL to scrape
scrapeTypestringRequiredType of scrapingOptions: STATIC, DYNAMIC, API
selectorsobjectRequiredCSS/XPath selectors for data extraction
descriptionstringOptionalSource description
scheduleTypestringOptionalScraping scheduleOptions: MANUAL, HOURLY, DAILY, WEEKLY

Example Request

curl -X POST "https://scrappy.gg/api/sources" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Example Name",
"url": "https://example.com",
"scrapeType": "string",
"selectors": {}
}'

Responses

201Source created
{
"success": true,
"data": {
"source": {
"id": "abc123",
"name": "Example Source",
"status": "DRAFT"
}
}
}
GET/api/sources/[id]
Auth Required

Get Source

Get a specific source by ID

Path Parameters

NameTypeRequiredDescription
idstringRequiredSource ID

Example Request

curl -X GET "https://scrappy.gg/api/sources/abc123" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Source returned
{
"success": true,
"data": {
"source": {
"id": "abc123",
"name": "Example Source"
}
}
}
PATCH/api/sources/[id]
Auth Required

Update Source

Update a source's configuration

Path Parameters

NameTypeRequiredDescription
idstringRequiredSource ID

Request Body

NameTypeRequiredDescription
namestringOptionalSource name
urlstringOptionalURL to scrape
selectorsobjectOptionalCSS/XPath selectors

Example Request

curl -X PATCH "https://scrappy.gg/api/sources/abc123" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"

Responses

200Source updated
{
"success": true,
"data": {
"source": {
"id": "abc123",
"name": "Updated Source"
}
}
}
DELETE/api/sources/[id]
Auth Required

Delete Source

Delete a source

Path Parameters

NameTypeRequiredDescription
idstringRequiredSource ID

Example Request

curl -X DELETE "https://scrappy.gg/api/sources/abc123" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Source deleted
{
"success": true
}
POST/api/sources/[id]/activate
Auth Required

Activate Source

Activate a draft source to enable scraping

Path Parameters

NameTypeRequiredDescription
idstringRequiredSource ID

Example Request

curl -X POST "https://scrappy.gg/api/sources/abc123/activate" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Source activated
{
"success": true,
"data": {
"source": {
"id": "abc123",
"status": "ACTIVE"
}
}
}
400Already active
{
"success": false,
"error": {
"code": "ALREADY_ACTIVE",
"message": "Source is already active"
}
}
POST/api/sources/discover
Auth Required

Discover Sources (AI)

Use AI to discover relevant sources based on a search query

Request Body

NameTypeRequiredDescription
querystringRequiredSearch query (minimum 3 characters)
countnumberOptionalNumber of sources to discover (1-10)Default: 5

Example Request

curl -X POST "https://scrappy.gg/api/sources/discover" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "search term"
}'

Responses

200Sources discovered
{
"success": true,
"data": {
"sources": [
{
"name": "Example Directory",
"url": "https://example.com/directory",
"category": "Business Directory",
"estimatedLeads": 500,
"scrapeType": "STATIC",
"description": "A directory of local businesses",
"reasoning": "This directory contains relevant business listings"
}
]
}
}
POST/api/sources/generate
Auth Required

Generate Sources (AI)

Use AI to generate source suggestions based on a project description

Request Body

NameTypeRequiredDescription
projectDescriptionstringRequiredProject description (minimum 10 characters)
targetAudiencestringOptionalTarget audience for the leads
industrystringOptionalIndustry to focus on
locationstringOptionalGeographic location
countnumberOptionalNumber of sources to generate (1-5)Default: 3
autoSavebooleanOptionalAutomatically save generated sources to the projectDefault: false

Example Request

curl -X POST "https://scrappy.gg/api/sources/generate" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"projectDescription": "string"
}'

Responses

200Sources generated
{
"success": true,
"data": {
"sources": [
{
"name": "Industry Directory",
"url": "https://example.com/directory",
"description": "Relevant directory for target industry",
"scrapeType": "STATIC",
"reasoning": "Matches project requirements"
}
]
}
}
400Invalid input
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Project description must be at least 10 characters"
}
}
POST/api/sources/batch
Auth Required

Batch Create Sources

Create multiple sources at once from AI suggestions or manual input

Request Body

NameTypeRequiredDescription
sourcesarrayRequiredArray of source objects to create (1 or more)
sources[].namestringRequiredSource name
sources[].urlstringRequiredURL to scrape
sources[].descriptionstringOptionalSource description
sources[].scrapeTypestringOptionalType of scrapingOptions: STATIC, DYNAMIC, APIDefault: DYNAMIC
sources[].selectorsobjectOptionalCSS/XPath selectors for data extraction
sources[].reasoningstringOptionalAI reasoning for why this source was suggested

Example Request

curl -X POST "https://scrappy.gg/api/sources/batch" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"sources": [],
"sources[].name": "Example Name",
"sources[].url": "https://example.com"
}'

Responses

201Sources created
{
"success": true,
"data": {
"sources": [
{
"id": "abc123",
"name": "Example Source",
"status": "DRAFT"
}
],
"count": 1
}
}
400Invalid input
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "At least one source is required"
}
}
POST/api/sources/analyze
Auth Required

Analyze Source

Analyze project requirements from a URL or PDF file

Request Body

NameTypeRequiredDescription
filefileOptionalPDF file to analyze (mutually exclusive with url)
urlstringOptionalURL to analyze (mutually exclusive with file)

Example Request

curl -X POST "https://scrappy.gg/api/sources/analyze" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: multipart/form-data"

Responses

200Analysis results
{
"success": true,
"data": {
"requirements": {
"targetAudience": "Small businesses",
"industry": "Technology",
"location": "United States",
"suggestedSources": []
}
}
}
400Invalid input
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Either file or url is required"
}
}

Jobs

Scraping job management and monitoring

5 endpoints
GET/api/jobs
Auth Required

List Jobs

Get all scraping jobs

Example Request

curl -X GET "https://scrappy.gg/api/jobs" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Jobs returned
{
"success": true,
"data": {
"jobs": [
{
"id": "abc123",
"name": "Scrape Job",
"status": "COMPLETED",
"progress": 100,
"leadsFound": 150,
"createdAt": "2024-01-01T00:00:00Z"
}
]
}
}
POST/api/jobs
Auth Required

Create Job

Create and enqueue a new scraping job

Request Body

NameTypeRequiredDescription
namestringRequiredJob name
sourceIdstringRequiredSource ID to scrape

Example Request

curl -X POST "https://scrappy.gg/api/jobs" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Example Name",
"sourceId": "abc123"
}'

Responses

201Job created
{
"success": true,
"data": {
"job": {
"id": "abc123",
"name": "Scrape Job",
"status": "PENDING"
}
}
}
GET/api/jobs/[id]
Auth Required

Get Job

Get job details and progress

Path Parameters

NameTypeRequiredDescription
idstringRequiredJob ID

Example Request

curl -X GET "https://scrappy.gg/api/jobs/abc123" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Job returned
{
"success": true,
"data": {
"job": {
"id": "abc123",
"status": "RUNNING",
"progress": 45,
"leadsFound": 67
}
}
}
DELETE/api/jobs/[id]
Auth Required

Cancel/Delete Job

Cancel a running job or delete a completed job

Path Parameters

NameTypeRequiredDescription
idstringRequiredJob ID

Example Request

curl -X DELETE "https://scrappy.gg/api/jobs/abc123" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Job cancelled/deleted
{
"success": true
}
POST/api/jobs/[id]/retry
Auth Required

Retry Job

Retry a failed job

Path Parameters

NameTypeRequiredDescription
idstringRequiredJob ID

Example Request

curl -X POST "https://scrappy.gg/api/jobs/abc123/retry" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Job retry started
{
"success": true,
"message": "Job retry started"
}
400Job cannot be retried
{
"success": false,
"error": {
"code": "INVALID_STATUS",
"message": "Only failed jobs can be retried"
}
}

Leads

Lead management and email validation

9 endpoints
GET/api/leads
Auth Required

List Leads

Get all leads with optional filtering

Query Parameters

NameTypeRequiredDescription
statusstringOptionalFilter by status
companystringOptionalFilter by company name
jobIdstringOptionalFilter by job ID

Example Request

curl -X GET "https://scrappy.gg/api/leads" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Leads returned
{
"success": true,
"data": {
"leads": [
{
"id": "abc123",
"firstName": "John",
"lastName": "Doe",
"email": "john@example.com",
"company": "Example Inc",
"status": "VALID"
}
]
}
}
POST/api/leads
Auth Required

Create Lead

Create a lead manually

Request Body

NameTypeRequiredDescription
sourceUrlstringRequiredSource URL where lead was found
firstNamestringOptionalFirst name
lastNamestringOptionalLast name
emailstringOptionalEmail address
phonestringOptionalPhone number
companystringOptionalCompany name
jobTitlestringOptionalJob title

Example Request

curl -X POST "https://scrappy.gg/api/leads" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"sourceUrl": "https://example.com"
}'

Responses

201Lead created
{
"success": true,
"data": {
"lead": {
"id": "abc123",
"firstName": "John",
"lastName": "Doe"
}
}
}
GET/api/leads/[id]
Auth Required

Get Lead

Get lead details with job and source info

Path Parameters

NameTypeRequiredDescription
idstringRequiredLead ID

Example Request

curl -X GET "https://scrappy.gg/api/leads/abc123" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Lead returned
{
"success": true,
"data": {
"lead": {
"id": "abc123",
"email": "john@example.com"
}
}
}
PATCH/api/leads/[id]
Auth Required

Update Lead

Update a lead's information

Path Parameters

NameTypeRequiredDescription
idstringRequiredLead ID

Request Body

NameTypeRequiredDescription
firstNamestringOptionalFirst name
lastNamestringOptionalLast name
emailstringOptionalEmail address
statusstringOptionalLead status
tagsarrayOptionalTags for categorization
notesstringOptionalNotes about the lead

Example Request

curl -X PATCH "https://scrappy.gg/api/leads/abc123" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"

Responses

200Lead updated
{
"success": true,
"data": {
"lead": {
"id": "abc123"
}
}
}
DELETE/api/leads/[id]
Auth Required

Delete Lead

Delete a lead

Path Parameters

NameTypeRequiredDescription
idstringRequiredLead ID

Example Request

curl -X DELETE "https://scrappy.gg/api/leads/abc123" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Lead deleted
{
"success": true
}
POST/api/leads/[id]/validate-email
Auth Required

Validate Lead Email

Trigger email validation for a specific lead

Path Parameters

NameTypeRequiredDescription
idstringRequiredLead ID

Example Request

curl -X POST "https://scrappy.gg/api/leads/abc123/validate-email" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Validation result
{
"success": true,
"data": {
"status": "VALID",
"tier": "TIER_1",
"errors": [],
"flags": []
}
}
400No email on lead
{
"success": false,
"error": {
"code": "NO_EMAIL",
"message": "Lead has no email to validate"
}
}
POST/api/leads/bulk
Auth Required

Bulk Delete Leads

Delete multiple leads at once (max 1000)

Request Body

NameTypeRequiredDescription
leadIdsarrayRequiredArray of lead IDs to delete (1-1000)

Example Request

curl -X POST "https://scrappy.gg/api/leads/bulk" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"leadIds": []
}'

Responses

200Bulk delete result
{
"success": true,
"data": {
"deleted": 50,
"failed": 0,
"errors": []
}
}
PATCH/api/leads/bulk
Auth Required

Bulk Update Leads

Update multiple leads at once

Request Body

NameTypeRequiredDescription
leadIdsarrayRequiredArray of lead IDs to update
updatesobjectRequiredUpdates to apply (status, tags add/remove)

Example Request

curl -X PATCH "https://scrappy.gg/api/leads/bulk" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"leadIds": [],
"updates": {}
}'

Responses

200Bulk update result
{
"success": true,
"data": {
"updated": 50,
"failed": 0
}
}
POST/api/leads/validate-bulk
Auth Required

Bulk Validate Emails

Queue email validation for multiple leads (max 1000)

Request Body

NameTypeRequiredDescription
leadIdsarrayRequiredArray of lead IDs to validate (1-1000)

Example Request

curl -X POST "https://scrappy.gg/api/leads/validate-bulk" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"leadIds": []
}'

Responses

200Validation queued
{
"success": true,
"data": {
"totalRequested": 100,
"totalQueued": 95,
"skipped": [
{
"id": "abc",
"reason": "No email"
}
]
}
}

Profile Types & Profiles

Custom entity schemas for structured data extraction, plus management of extracted profiles

11 endpoints
GET/api/profile-types
Auth Required

List Profile Types

Get all profile type schemas defined by the user.

Example Request

curl -X GET "https://scrappy.gg/api/profile-types" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Profile types returned
{
"success": true,
"data": {
"profileTypes": [
{
"id": "pt_123",
"name": "Private Lender",
"slug": "private-lender",
"fields": [],
"createdAt": "2024-01-01T00:00:00Z"
}
]
}
}
POST/api/profile-types
Auth Required

Create Profile Type

Define a new custom entity schema for structured data extraction.

Request Body

NameTypeRequiredDescription
namestringRequiredProfile type name
slugstringRequiredURL-safe identifier
descriptionstringOptionalSchema description
fieldsarrayRequiredJSON Schema field definitions

Example Request

curl -X POST "https://scrappy.gg/api/profile-types" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Example Name",
"slug": "string",
"fields": []
}'

Responses

201Profile type created
{
"success": true,
"data": {
"id": "pt_123",
"name": "Private Lender"
}
}
GET/api/profile-types/{id}
Auth Required

Get Profile Type

Get a profile type by ID.

Path Parameters

NameTypeRequiredDescription
idstringRequiredProfile type ID

Example Request

curl -X GET "https://scrappy.gg/api/profile-types/{id}" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Profile type returned
{
"success": true,
"data": {}
}
PATCH/api/profile-types/{id}
Auth Required

Update Profile Type

Update a profile type's fields or metadata.

Path Parameters

NameTypeRequiredDescription
idstringRequiredProfile type ID

Request Body

NameTypeRequiredDescription
namestringOptionalProfile type name
fieldsarrayOptionalUpdated field definitions

Example Request

curl -X PATCH "https://scrappy.gg/api/profile-types/{id}" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"

Responses

200Profile type updated
{
"success": true,
"data": {}
}
DELETE/api/profile-types/{id}
Auth Required

Delete Profile Type

Delete a profile type schema.

Path Parameters

NameTypeRequiredDescription
idstringRequiredProfile type ID

Example Request

curl -X DELETE "https://scrappy.gg/api/profile-types/{id}" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Deleted
{
"success": true
}
GET/api/profile-types/templates
Auth Required

List Built-in Templates

Get built-in profile type templates (e.g. Private Lender, Restaurant, SaaS Company).

Example Request

curl -X GET "https://scrappy.gg/api/profile-types/templates" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Templates returned
{
"success": true,
"data": {
"templates": [
{
"slug": "private-lender",
"name": "Private Lender",
"description": "Real estate lender schema"
},
{
"slug": "restaurant",
"name": "Restaurant",
"description": "Food & beverage schema"
}
]
}
}
GET/api/profiles
Auth Required

List Profiles

Get extracted profiles with optional filtering.

Query Parameters

NameTypeRequiredDescription
profileTypeIdstringOptionalFilter by profile type
statusstringOptionalFilter by statusOptions: NEW, VERIFIED, OUTDATED, ARCHIVED
pagenumberOptionalPage number
limitnumberOptionalItems per page (max: 100)

Example Request

curl -X GET "https://scrappy.gg/api/profiles" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Profiles returned
{
"success": true,
"data": {
"profiles": [
{
"id": "prof_123",
"profileTypeId": "pt_123",
"data": {},
"status": "NEW"
}
],
"total": 5
}
}
GET/api/profiles/{id}
Auth Required

Get Profile

Get a single profile by ID.

Path Parameters

NameTypeRequiredDescription
idstringRequiredProfile ID

Example Request

curl -X GET "https://scrappy.gg/api/profiles/{id}" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Profile returned
{
"success": true,
"data": {}
}
PATCH/api/profiles/{id}
Auth Required

Update Profile

Update profile data or status.

Path Parameters

NameTypeRequiredDescription
idstringRequiredProfile ID

Request Body

NameTypeRequiredDescription
dataobjectOptionalProfile field data
statusstringOptionalProfile statusOptions: NEW, VERIFIED, OUTDATED, ARCHIVED

Example Request

curl -X PATCH "https://scrappy.gg/api/profiles/{id}" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"

Responses

200Profile updated
{
"success": true,
"data": {}
}
GET/api/profiles/export
Auth Required

Export Profiles

Export profiles as CSV. Optionally filter by matching rule (includes matchScore + aiReasoning columns).

Query Parameters

NameTypeRequiredDescription
profileTypeIdstringOptionalFilter by profile type
ruleIdstringOptionalExport profiles matching a specific matching rule

Example Request

curl -X GET "https://scrappy.gg/api/profiles/export" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200CSV file returned
{
"Content-Type": "text/csv"
}
GET/api/profiles/stats
Auth Required

Profile Stats

Get aggregate statistics across all profiles.

Example Request

curl -X GET "https://scrappy.gg/api/profiles/stats" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Stats returned
{
"success": true,
"data": {
"total": 150,
"byStatus": {
"NEW": 80,
"VERIFIED": 50,
"OUTDATED": 15,
"ARCHIVED": 5
},
"byProfileType": [
{
"profileTypeId": "pt_123",
"count": 90
}
]
}
}

Matching Engine

Rules-based and AI-powered filtering and scoring of profiles — FILTER, SCORED, and AI match types

8 endpoints
GET/api/matching/rules
Auth Required

List Matching Rules

Get all matching rules for the current user.

Query Parameters

NameTypeRequiredDescription
profileTypeIdstringOptionalFilter by profile type

Example Request

curl -X GET "https://scrappy.gg/api/matching/rules" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Rules returned
{
"success": true,
"data": {
"rules": [
{
"id": "rule_123",
"name": "High-value Leads",
"type": "SCORED",
"profileTypeId": "pt_123"
}
]
}
}
POST/api/matching/rules
Auth Required

Create Matching Rule

Create a new matching rule. Three types are supported: FILTER (boolean pass/fail), SCORED (weighted 0–100 score), and AI (LLM-powered evaluation using aiPrompt).

Request Body

NameTypeRequiredDescription
namestringRequiredRule name
typestringRequiredMatch typeOptions: FILTER, SCORED, AI
profileTypeIdstringRequiredProfile type this rule applies to
criteriaobjectOptionalMatching criteria (field conditions and weights). Can be empty {} for AI type.
aiPromptstringOptionalLLM prompt template for AI type rules. Use {{profile}} and {{profileType}} placeholders.
minimumScorenumberOptionalMinimum score threshold for SCORED type (0–100)

Example Request

curl -X POST "https://scrappy.gg/api/matching/rules" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Example Name",
"type": "string",
"profileTypeId": "abc123"
}'

Responses

201Rule created
{
"success": true,
"data": {
"id": "rule_123",
"name": "High-value Leads"
}
}
GET/api/matching/rules/{id}
Auth Required

Get Matching Rule

Get a matching rule by ID.

Path Parameters

NameTypeRequiredDescription
idstringRequiredRule ID

Example Request

curl -X GET "https://scrappy.gg/api/matching/rules/{id}" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Rule returned
{
"success": true,
"data": {}
}
PATCH/api/matching/rules/{id}
Auth Required

Update Matching Rule

Update a matching rule's criteria or metadata.

Path Parameters

NameTypeRequiredDescription
idstringRequiredRule ID

Request Body

NameTypeRequiredDescription
namestringOptionalRule name
criteriaobjectOptionalUpdated criteria
aiPromptstringOptionalUpdated AI prompt

Example Request

curl -X PATCH "https://scrappy.gg/api/matching/rules/{id}" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"

Responses

200Rule updated
{
"success": true,
"data": {}
}
DELETE/api/matching/rules/{id}
Auth Required

Delete Matching Rule

Delete a matching rule.

Path Parameters

NameTypeRequiredDescription
idstringRequiredRule ID

Example Request

curl -X DELETE "https://scrappy.gg/api/matching/rules/{id}" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Rule deleted
{
"success": true
}
POST/api/matching/preview
Auth Required

Preview Matching

Test a matching rule against sample profiles before saving.

Request Body

NameTypeRequiredDescription
ruleIdstringOptionalExisting rule to preview
typestringOptionalRule type for inline previewOptions: FILTER, SCORED, AI
criteriaobjectOptionalInline criteria to test
aiPromptstringOptionalInline AI prompt to test
profileTypeIdstringOptionalProfile type to test against
sampleSizenumberOptionalNumber of sample profiles (default: 10)

Example Request

curl -X POST "https://scrappy.gg/api/matching/preview" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"

Responses

200Preview results
{
"success": true,
"data": {
"matched": 7,
"notMatched": 3,
"results": [
{
"profileId": "prof_123",
"matched": true,
"score": 87,
"reasoning": "Revenue > $1M"
}
]
}
}
POST/api/matching/execute
Auth Required

Execute Matching Rule

Run a matching rule against all profiles and store results.

Request Body

NameTypeRequiredDescription
ruleIdstringRequiredRule ID to execute

Example Request

curl -X POST "https://scrappy.gg/api/matching/execute" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"ruleId": "abc123"
}'

Responses

200Execution results
{
"success": true,
"data": {
"matched": 42,
"notMatched": 18,
"executionTimeMs": 1250
}
}
GET/api/matching/results
Auth Required

List Matching Results

Get stored matching results for a rule.

Query Parameters

NameTypeRequiredDescription
ruleIdstringRequiredRule ID
matchedbooleanOptionalFilter matched/unmatched
pagenumberOptionalPage number
limitnumberOptionalItems per page

Example Request

curl -X GET ?ruleId=abc123"https://scrappy.gg/api/matching/results" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Results returned
{
"success": true,
"data": {
"results": [
{
"profileId": "prof_123",
"matched": true,
"score": 87,
"matchedAt": "2024-01-15"
}
],
"total": 42
}
}

Accounts

Aggregated company/account view derived from lead data

4 endpoints
GET/api/accounts
Auth Required

List Accounts

Returns companies aggregated from lead data with enriched metadata. Accounts are computed dynamically — not stored as a separate entity.

Query Parameters

NameTypeRequiredDescription
searchstringOptionalSearch by company name or domain
projectIdstringOptionalFilter by project ID
pagenumberOptionalPage number (default: 1)
limitnumberOptionalItems per page (default: 20, max: 100)

Example Request

curl -X GET "https://scrappy.gg/api/accounts" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Accounts returned
{
"success": true,
"data": {
"accounts": [
{
"id": "acme.com",
"companyDomain": "acme.com",
"companyName": "Acme Corp",
"leadCount": 12,
"latestLeadAt": "2024-01-15T10:30:00Z",
"emailsFound": 10,
"linkedinUrl": "https://linkedin.com/company/acme"
}
],
"total": 42,
"page": 1,
"limit": 20
}
}
GET/api/accounts/{accountId}
Auth Required

Get Account

Get a single account with all associated leads.

Path Parameters

NameTypeRequiredDescription
accountIdstringRequiredCompany domain used as account identifier

Example Request

curl -X GET "https://scrappy.gg/api/accounts/{accountId}" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Account returned
{
"success": true,
"data": {
"id": "acme.com",
"companyDomain": "acme.com",
"companyName": "Acme Corp",
"leadCount": 12,
"leads": []
}
}
PATCH/api/accounts/{accountId}
Auth Required

Update Account

Update account metadata (e.g. notes, tags).

Path Parameters

NameTypeRequiredDescription
accountIdstringRequiredCompany domain used as account identifier

Request Body

NameTypeRequiredDescription
notesstringOptionalNotes about this account

Example Request

curl -X PATCH "https://scrappy.gg/api/accounts/{accountId}" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"

Responses

200Account updated
{
"success": true,
"data": {
"id": "acme.com"
}
}
GET/api/accounts/{accountId}/commonalities
Auth Required

Analyze LinkedIn Commonalities

Use AI (Perplexity) to analyze shared traits among leads at this account. Requires PERPLEXITY_API_KEY.

Path Parameters

NameTypeRequiredDescription
accountIdstringRequiredCompany domain

Example Request

curl -X GET "https://scrappy.gg/api/accounts/{accountId}/commonalities" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Commonalities analysis
{
"success": true,
"data": {
"commonalities": [
"Alumni of MIT",
"Previously worked at Google",
"Focus on B2B SaaS"
],
"analyzedProfiles": 8
}
}
503PERPLEXITY_API_KEY not configured
{
"success": false,
"error": {
"code": "SERVICE_UNAVAILABLE",
"message": "AI service not configured"
}
}

Deal Campaigns

AI-powered deal flow management — discover sources, rank accounts, and generate matching rules

8 endpoints
GET/api/campaigns
Auth Required

List Campaigns

Get all deal campaigns for the current user.

Query Parameters

NameTypeRequiredDescription
statusstringOptionalFilter by statusOptions: ACTIVE, PAUSED, COMPLETED, ARCHIVED
projectIdstringOptionalFilter by project ID

Example Request

curl -X GET "https://scrappy.gg/api/campaigns" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Campaigns returned
{
"success": true,
"data": {
"campaigns": [
{
"id": "camp_123",
"name": "Q1 Outreach",
"status": "ACTIVE"
}
]
}
}
POST/api/campaigns
Auth Required

Create Campaign

Create a new deal campaign.

Rate Limited: 5 requests per 1h per user

Request Body

NameTypeRequiredDescription
namestringRequiredCampaign name
descriptionstringOptionalCampaign description
targetIndustrystringOptionalTarget industry vertical
targetGeographystringOptionalTarget geography (e.g. 'US', 'New York')
targetCompanySizestringOptionalTarget company size (e.g. '50-200 employees')
dealParamsobjectOptionalCustom deal parameters (loan size, revenue threshold, etc.)
projectIdstringOptionalAssociate with a project

Example Request

curl -X POST "https://scrappy.gg/api/campaigns" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Example Name"
}'

Responses

201Campaign created
{
"success": true,
"data": {
"id": "camp_123",
"name": "Q1 Outreach",
"status": "ACTIVE"
}
}
GET/api/campaigns/{id}
Auth Required

Get Campaign

Get a single campaign by ID.

Path Parameters

NameTypeRequiredDescription
idstringRequiredCampaign ID

Example Request

curl -X GET "https://scrappy.gg/api/campaigns/{id}" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Campaign returned
{
"success": true,
"data": {
"id": "camp_123"
}
}
PATCH/api/campaigns/{id}
Auth Required

Update Campaign

Update campaign fields.

Path Parameters

NameTypeRequiredDescription
idstringRequiredCampaign ID

Request Body

NameTypeRequiredDescription
namestringOptionalCampaign name
statusstringOptionalCampaign statusOptions: ACTIVE, PAUSED, COMPLETED, ARCHIVED

Example Request

curl -X PATCH "https://scrappy.gg/api/campaigns/{id}" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"

Responses

200Campaign updated
{
"success": true,
"data": {
"id": "camp_123"
}
}
DELETE/api/campaigns/{id}
Auth Required

Delete Campaign

Permanently delete a campaign.

Path Parameters

NameTypeRequiredDescription
idstringRequiredCampaign ID

Example Request

curl -X DELETE "https://scrappy.gg/api/campaigns/{id}" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Campaign deleted
{
"success": true
}
POST/api/campaigns/{id}/discover-sources
Auth Required

Discover Sources for Campaign

Use AI to find relevant data sources based on the campaign's target parameters. Requires PERPLEXITY_API_KEY.

Path Parameters

NameTypeRequiredDescription
idstringRequiredCampaign ID

Example Request

curl -X POST "https://scrappy.gg/api/campaigns/{id}/discover-sources" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Sources suggested
{
"success": true,
"data": {
"sources": [
{
"name": "NYC Construction Directory",
"url": "https://example.com",
"reasoning": "High density of target companies"
}
]
}
}
POST/api/campaigns/{id}/suggest-matching
Auth Required

Suggest Matching Rules

Generate AI-suggested matching rules based on campaign deal parameters.

Path Parameters

NameTypeRequiredDescription
idstringRequiredCampaign ID

Example Request

curl -X POST "https://scrappy.gg/api/campaigns/{id}/suggest-matching" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Matching rules suggested
{
"success": true,
"data": {
"rules": [
{
"type": "FILTER",
"field": "jobTitle",
"operator": "contains",
"value": "CFO"
}
]
}
}
GET/api/campaigns/{id}/ranked-accounts
Auth Required

Ranked Accounts

Get accounts ranked by fit score for this campaign.

Path Parameters

NameTypeRequiredDescription
idstringRequiredCampaign ID

Example Request

curl -X GET "https://scrappy.gg/api/campaigns/{id}/ranked-accounts" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Ranked accounts returned
{
"success": true,
"data": {
"accounts": [
{
"companyDomain": "acme.com",
"companyName": "Acme Corp",
"score": 92,
"leadCount": 8
}
]
}
}

AI Chat Agent

Conversational AI assistant (Claude) that can query and manage leads, sources, and jobs via natural language

5 endpoints
GET/api/chat/conversations
Auth Required

List Conversations

Get all chat conversations for the current user.

Example Request

curl -X GET "https://scrappy.gg/api/chat/conversations" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Conversations returned
{
"success": true,
"data": {
"conversations": [
{
"id": "conv_123",
"title": "Lead search for NYC dentists",
"messageCount": 6,
"createdAt": "2024-01-15T10:00:00Z",
"updatedAt": "2024-01-15T10:05:00Z"
}
]
}
}
POST/api/chat/conversations
Auth Required

Create Conversation

Start a new chat conversation.

Rate Limited: 50 requests per 1h per user

Request Body

NameTypeRequiredDescription
titlestringOptionalConversation title (auto-generated if omitted)

Example Request

curl -X POST "https://scrappy.gg/api/chat/conversations" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"

Responses

201Conversation created
{
"success": true,
"data": {
"id": "conv_123",
"title": "New Conversation",
"messages": []
}
}
GET/api/chat/conversations/{id}
Auth Required

Get Conversation

Get a conversation with its full message history.

Path Parameters

NameTypeRequiredDescription
idstringRequiredConversation ID

Example Request

curl -X GET "https://scrappy.gg/api/chat/conversations/{id}" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Conversation returned
{
"success": true,
"data": {
"id": "conv_123",
"title": "Lead search",
"messages": [
{
"role": "user",
"content": "Find leads in healthcare",
"createdAt": "..."
},
{
"role": "assistant",
"content": "I found 24 leads...",
"createdAt": "..."
}
]
}
}
DELETE/api/chat/conversations/{id}
Auth Required

Delete Conversation

Delete a conversation and all its messages.

Path Parameters

NameTypeRequiredDescription
idstringRequiredConversation ID

Example Request

curl -X DELETE "https://scrappy.gg/api/chat/conversations/{id}" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Conversation deleted
{
"success": true
}
POST/api/chat/conversations/{id}/messages
Auth Required

Send Message (Streaming)

Send a message to the AI agent and receive a streaming SSE response. The agent has 9 built-in tools: search leads, get lead, update lead, search sources, get source, search jobs, get job, create job, and get stats. Requires ANTHROPIC_API_KEY.

Rate Limited: 50 requests per 1h per user

Path Parameters

NameTypeRequiredDescription
idstringRequiredConversation ID

Request Body

NameTypeRequiredDescription
messagestringRequiredUser message to send to the agent

Example Request

curl -X POST "https://scrappy.gg/api/chat/conversations/{id}/messages" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"message": "string"
}'

Responses

200Server-Sent Events stream. Event types: text (delta), tool_start, tool_call (with input), tool_result, done, error.
{
"event: text": {
"delta": "I found "
},
"event: tool_start": {
"tool": "search_leads"
},
"event: tool_call": {
"tool": "search_leads",
"input": {
"query": "healthcare"
}
},
"event: tool_result": {
"tool": "search_leads",
"result": {
"count": 24
}
},
"event: done": {}
}
503ANTHROPIC_API_KEY not configured
{
"success": false,
"error": {
"code": "SERVICE_UNAVAILABLE",
"message": "AI chat not configured"
}
}

Email Verification

Email bounce tracking and spam trap detection

3 endpoints
POST/api/email-verification/bounce
Auth Required

Record Bounce

Record an email bounce event

Request Body

NameTypeRequiredDescription
emailstringRequiredEmail address that bounced
bounceTypestringRequiredType of bounceOptions: HARD, SOFT, SPAM
bounceReasonstringOptionalDetailed bounce reason
smtpCodestringOptionalSMTP error code

Example Request

curl -X POST "https://scrappy.gg/api/email-verification/bounce" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"bounceType": "string"
}'

Responses

200Bounce recorded
{
"success": true
}
POST/api/email-verification/spam-trap
Auth Required

Mark as Spam Trap

Mark an email as a known spam trap

Request Body

NameTypeRequiredDescription
emailstringRequiredEmail address
confidencenumberOptionalConfidence level 0-100Default: 100
trapTypestringOptionalType of spam trap
sourcestringOptionalSource of identification

Example Request

curl -X POST "https://scrappy.gg/api/email-verification/spam-trap" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com"
}'

Responses

200Spam trap marked
{
"success": true
}
GET/api/email-verification/stats
Auth Required

Get Verification Stats

Get email verification and bounce statistics

Query Parameters

NameTypeRequiredDescription
bouncesbooleanOptionalInclude recent bounces
bounceLimitnumberOptionalNumber of recent bouncesDefault: 100

Example Request

curl -X GET "https://scrappy.gg/api/email-verification/stats" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Stats returned
{
"success": true,
"data": {
"verificationStats": {
"total": 1000,
"valid": 850,
"invalid": 100,
"unknown": 50
},
"bounceStats": {
"hard": 50,
"soft": 30,
"spam": 20
},
"recentBounces": []
}
}

LinkedIn Integration

LinkedIn scraping, lead enrichment, profile validation, and commonalities analysis

7 endpoints
GET/api/linkedin/credentials
Auth Required

List LinkedIn Credentials

Get stored LinkedIn credentials with computed status (ACTIVE, RATE_LIMITED, SOFT_BANNED, INVALID), daily limit, and requests remaining.

Example Request

curl -X GET "https://scrappy.gg/api/linkedin/credentials" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Credentials returned
{
"success": true,
"data": {
"credentials": [
{
"id": "cred_123",
"email": "linkedin@example.com",
"status": "ACTIVE",
"dailyLimit": 80,
"requestsRemaining": 45
}
]
}
}
POST/api/linkedin/credentials
Auth Required

Add LinkedIn Credentials

Store LinkedIn account credentials for scraping.

Request Body

NameTypeRequiredDescription
emailstringRequiredLinkedIn account email
passwordstringRequiredLinkedIn account password (encrypted at rest)

Example Request

curl -X POST "https://scrappy.gg/api/linkedin/credentials" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"password": "your-password"
}'

Responses

201Credentials added
{
"success": true,
"data": {
"id": "cred_123",
"email": "linkedin@example.com"
}
}
POST/api/linkedin/validate
Auth Required

Validate LinkedIn Profile

Validate that a LinkedIn profile URL is reachable and extract basic metadata.

Request Body

NameTypeRequiredDescription
profileUrlstringRequiredLinkedIn profile URL to validate

Example Request

curl -X POST "https://scrappy.gg/api/linkedin/validate" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"profileUrl": "https://example.com"
}'

Responses

200Validation result
{
"success": true,
"data": {
"valid": true,
"name": "John Doe",
"headline": "CEO at Acme Corp"
}
}
POST/api/linkedin/enrich
Auth Required

Enrich Lead from LinkedIn

Enrich a lead's data by scraping their LinkedIn profile.

Request Body

NameTypeRequiredDescription
leadIdstringRequiredLead ID to enrich
profileUrlstringOptionalLinkedIn profile URL (uses lead.linkedin if omitted)

Example Request

curl -X POST "https://scrappy.gg/api/linkedin/enrich" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"leadId": "abc123"
}'

Responses

200Lead enriched
{
"success": true,
"data": {
"leadId": "lead_123",
"enriched": {
"jobTitle": "VP of Sales",
"company": "Acme Corp",
"location": "New York"
}
}
}
GET/api/linkedin/jobs
Auth Required

List LinkedIn Jobs

Get all LinkedIn scraping jobs.

Example Request

curl -X GET "https://scrappy.gg/api/linkedin/jobs" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Jobs returned
{
"success": true,
"data": {
"jobs": [
{
"id": "ljob_123",
"status": "COMPLETED",
"profilesScraped": 150
}
]
}
}
GET/api/linkedin/jobs/{id}
Auth Required

Get LinkedIn Job

Get a LinkedIn scraping job by ID.

Path Parameters

NameTypeRequiredDescription
idstringRequiredJob ID

Example Request

curl -X GET "https://scrappy.gg/api/linkedin/jobs/{id}" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Job returned
{
"success": true,
"data": {}
}
POST/api/linkedin/commonalities
Auth Required

Batch Commonalities Analysis

Analyze common traits across a batch of LinkedIn profiles using AI. Useful for identifying shared backgrounds, schools, companies, or interests. Requires PERPLEXITY_API_KEY.

Rate Limited: 10 requests per 1h per user

Request Body

NameTypeRequiredDescription
profileUrlsarrayRequiredArray of LinkedIn profile URLs to analyze
contextstringOptionalAdditional context for the analysis

Example Request

curl -X POST "https://scrappy.gg/api/linkedin/commonalities" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"profileUrls": []
}'

Responses

200Commonalities analysis
{
"success": true,
"data": {
"commonalities": [
"All attended Ivy League universities",
"Previously worked in investment banking",
"Active in real estate investing communities"
],
"analyzedProfiles": 12,
"confidence": 0.85
}
}
503PERPLEXITY_API_KEY not configured
{
"success": false,
"error": {
"code": "SERVICE_UNAVAILABLE",
"message": "AI service not configured"
}
}

Warmup Domains

Email domain warming system — 5-week progressive warmup for improved deliverability

7 endpoints
GET/api/warmup-domains
Auth Required

List Warmup Domains

Get all warmup domains for the current user.

Example Request

curl -X GET "https://scrappy.gg/api/warmup-domains" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Warmup domains returned
{
"success": true,
"data": {
"warmupDomains": [
{
"id": "wd_123",
"domain": "outreach.acme.com",
"status": "WARMING",
"currentDailyVolume": 25,
"targetDailyVolume": 100,
"weekNumber": 2,
"openRate": 0.42,
"bounceRate": 0.02
}
]
}
}
POST/api/warmup-domains
Auth Required

Create Warmup Domain

Register a new domain for warmup. The domain must have valid SPF and DKIM before warmup can start.

Request Body

NameTypeRequiredDescription
domainstringRequiredDomain to warm up (e.g. outreach.acme.com)
targetDailyVolumenumberOptionalTarget daily email volume after warmup (default: 100)

Example Request

curl -X POST "https://scrappy.gg/api/warmup-domains" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"domain": "string"
}'

Responses

201Warmup domain created
{
"success": true,
"data": {
"id": "wd_123",
"domain": "outreach.acme.com",
"status": "PENDING_DNS",
"spfValid": false,
"dkimValid": false
}
}
GET/api/warmup-domains/{id}
Auth Required

Get Warmup Domain

Get a warmup domain by ID with full metrics.

Path Parameters

NameTypeRequiredDescription
idstringRequiredWarmup domain ID

Example Request

curl -X GET "https://scrappy.gg/api/warmup-domains/{id}" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Warmup domain returned
{
"success": true,
"data": {
"id": "wd_123",
"domain": "outreach.acme.com",
"status": "WARMING",
"spfValid": true,
"dkimValid": true,
"dmarcValid": true,
"currentDailyVolume": 25,
"openRate": 0.42,
"replyRate": 0.08,
"bounceRate": 0.02,
"spamRate": 0.001
}
}
PATCH/api/warmup-domains/{id}
Auth Required

Update Warmup Domain

Update warmup domain settings (e.g. pause/resume).

Path Parameters

NameTypeRequiredDescription
idstringRequiredWarmup domain ID

Request Body

NameTypeRequiredDescription
statusstringOptionalOverride statusOptions: PAUSED, WARMING
targetDailyVolumenumberOptionalUpdated target daily volume

Example Request

curl -X PATCH "https://scrappy.gg/api/warmup-domains/{id}" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"

Responses

200Domain updated
{
"success": true,
"data": {}
}
DELETE/api/warmup-domains/{id}
Auth Required

Delete Warmup Domain

Remove a warmup domain.

Path Parameters

NameTypeRequiredDescription
idstringRequiredWarmup domain ID

Example Request

curl -X DELETE "https://scrappy.gg/api/warmup-domains/{id}" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Domain deleted
{
"success": true
}
POST/api/warmup-domains/{id}/start
Auth Required

Start Warmup

Begin the 5-week progressive warmup schedule. Requires valid SPF and DKIM records. Warmup progresses: 5→10→25→50→100 emails/day. Auto-pauses if bounce rate >10% or spam rate >0.5%.

Path Parameters

NameTypeRequiredDescription
idstringRequiredWarmup domain ID

Example Request

curl -X POST "https://scrappy.gg/api/warmup-domains/{id}/start" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Warmup started
{
"success": true,
"data": {
"status": "WARMING",
"startedAt": "2024-01-15T10:00:00Z",
"estimatedCompletionWeeks": 5
}
}
400DNS not configured
{
"success": false,
"error": {
"code": "DNS_NOT_CONFIGURED",
"message": "SPF and DKIM records are required before warmup can start"
}
}
POST/api/warmup-domains/{id}/metrics
Auth Required

Update Warmup Metrics

Update sending metrics (open rate, reply rate, bounce rate, spam rate). Used to feed real delivery data back into the warmup monitor.

Path Parameters

NameTypeRequiredDescription
idstringRequiredWarmup domain ID

Request Body

NameTypeRequiredDescription
openRatenumberOptionalOpen rate (0–1)
replyRatenumberOptionalReply rate (0–1)
bounceRatenumberOptionalBounce rate (0–1). >0.10 triggers auto-pause.
spamRatenumberOptionalSpam rate (0–1). >0.005 triggers auto-pause.
emailsSentnumberOptionalEmails sent today

Example Request

curl -X POST "https://scrappy.gg/api/warmup-domains/{id}/metrics" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"

Responses

200Metrics updated
{
"success": true,
"data": {
"status": "WARMING",
"bounceRateAlert": false,
"spamRateAlert": false
}
}

AngelSend Integration

Cold email outreach platform integration — export leads, send emails, AI personalization, and domain warmup

16 endpoints
GET/api/angelsend/status
Auth Required

Connection Status

Check if AngelSend is connected and return account info.

Example Request

curl -X GET "https://scrappy.gg/api/angelsend/status" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Status returned
{
"success": true,
"data": {
"connected": true,
"email": "user@example.com",
"plan": "pro"
}
}
GET/api/angelsend/credits
Auth Required

Get AngelSend Credits

Get the current AngelSend credit balance.

Example Request

curl -X GET "https://scrappy.gg/api/angelsend/credits" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Credits returned
{
"success": true,
"data": {
"credits": 5000,
"used": 1200
}
}
POST/api/angelsend/leads/export
Auth Required

Export Leads to AngelSend

Export selected leads to an AngelSend project for outreach.

Request Body

NameTypeRequiredDescription
leadIdsarrayRequiredArray of lead IDs to export
projectNamestringOptionalAngelSend project to export into

Example Request

curl -X POST "https://scrappy.gg/api/angelsend/leads/export" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"leadIds": []
}'

Responses

200Leads exported
{
"success": true,
"data": {
"exported": 45,
"failed": 2
}
}
POST/api/angelsend/send-email
Auth Required

Send Single Email

Send an individual email via AngelSend.

Request Body

NameTypeRequiredDescription
tostringRequiredRecipient email address
subjectstringRequiredEmail subject
bodystringRequiredEmail body (HTML or plain text)
leadIdstringOptionalAssociated lead ID

Example Request

curl -X POST "https://scrappy.gg/api/angelsend/send-email" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "string",
"subject": "string",
"body": "string"
}'

Responses

200Email sent
{
"success": true,
"data": {
"messageId": "msg_123"
}
}
POST/api/angelsend/send-emails
Auth Required

Send Bulk Emails

Send emails to multiple leads in one request.

Request Body

NameTypeRequiredDescription
emailsarrayRequiredArray of { to, subject, body, leadId } objects

Example Request

curl -X POST "https://scrappy.gg/api/angelsend/send-emails" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"emails": []
}'

Responses

200Bulk send results
{
"success": true,
"data": {
"sent": 48,
"failed": 2
}
}
POST/api/angelsend/generate-emails
Auth Required

Generate Email Copy

Use AI to generate personalized email copy for leads.

Request Body

NameTypeRequiredDescription
leadIdsarrayRequiredLead IDs to generate emails for
templatestringOptionalEmail template or campaign context
tonestringOptionalEmail toneOptions: professional, casual, friendly

Example Request

curl -X POST "https://scrappy.gg/api/angelsend/generate-emails" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"leadIds": []
}'

Responses

200Generated emails
{
"success": true,
"data": {
"emails": [
{
"leadId": "lead_123",
"subject": "Quick question about Acme",
"body": "Hi John, ..."
}
]
}
}
POST/api/angelsend/personalize
Auth Required

Personalize Email

AI-personalize an existing email template for a specific lead.

Request Body

NameTypeRequiredDescription
leadIdstringRequiredLead ID
templatestringRequiredBase email template

Example Request

curl -X POST "https://scrappy.gg/api/angelsend/personalize" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"leadId": "abc123",
"template": "string"
}'

Responses

200Personalized email
{
"success": true,
"data": {
"subject": "...",
"body": "..."
}
}
POST/api/angelsend/verify-emails
Auth Required

Verify Emails via AngelSend

Trigger email verification for leads through AngelSend.

Request Body

NameTypeRequiredDescription
leadIdsarrayRequiredLead IDs to verify

Example Request

curl -X POST "https://scrappy.gg/api/angelsend/verify-emails" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"leadIds": []
}'

Responses

200Verification queued
{
"success": true
}
POST/api/angelsend/ai/analyze-intent
Auth Required

Analyze Reply Intent

Use AI to analyze the intent of a reply email (interested, not interested, objection, etc.).

Request Body

NameTypeRequiredDescription
emailBodystringRequiredReply email body to analyze
leadIdstringOptionalAssociated lead ID

Example Request

curl -X POST "https://scrappy.gg/api/angelsend/ai/analyze-intent" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"emailBody": "user@example.com"
}'

Responses

200Intent analysis
{
"success": true,
"data": {
"intent": "INTERESTED",
"confidence": 0.92,
"summary": "Lead expressed interest and asked for a call",
"suggestedAction": "Schedule a call"
}
}
POST/api/angelsend/ai/auto-reply
Auth Required

Generate Auto-Reply

Generate an AI-powered reply to an inbound email.

Request Body

NameTypeRequiredDescription
originalEmailstringRequiredThe original outbound email
replyEmailstringRequiredThe inbound reply to respond to
leadIdstringOptionalAssociated lead ID

Example Request

curl -X POST "https://scrappy.gg/api/angelsend/ai/auto-reply" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"originalEmail": "user@example.com",
"replyEmail": "user@example.com"
}'

Responses

200Generated reply
{
"success": true,
"data": {
"subject": "Re: ...",
"body": "Thanks for getting back to me..."
}
}
POST/api/angelsend/ai/follow-ups
Auth Required

Generate Follow-up Sequence

Generate a multi-step follow-up email sequence for a campaign.

Request Body

NameTypeRequiredDescription
leadIdstringRequiredLead ID
initialEmailstringRequiredThe initial outbound email
stepsnumberOptionalNumber of follow-up steps (default: 3)

Example Request

curl -X POST "https://scrappy.gg/api/angelsend/ai/follow-ups" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"leadId": "abc123",
"initialEmail": "user@example.com"
}'

Responses

200Follow-up sequence
{
"success": true,
"data": {
"followUps": [
{
"dayOffset": 3,
"subject": "Following up",
"body": "Just checking in..."
},
{
"dayOffset": 7,
"subject": "Last try",
"body": "One more thought..."
}
]
}
}
POST/api/angelsend/import/apollo
Auth Required

Import from Apollo

Import leads from an Apollo.io CSV export into Scrappy.

Request Body

NameTypeRequiredDescription
filefileRequiredApollo CSV export file
projectIdstringOptionalTarget project ID

Example Request

curl -X POST "https://scrappy.gg/api/angelsend/import/apollo" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: multipart/form-data" \
-d '{
"file": ""
}'

Responses

200Import results
{
"success": true,
"data": {
"imported": 200,
"duplicates": 15,
"errors": 2
}
}
POST/api/angelsend/import/hunter
Auth Required

Import from Hunter

Import leads from a Hunter.io CSV export into Scrappy.

Request Body

NameTypeRequiredDescription
filefileRequiredHunter CSV export file
projectIdstringOptionalTarget project ID

Example Request

curl -X POST "https://scrappy.gg/api/angelsend/import/hunter" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: multipart/form-data" \
-d '{
"file": ""
}'

Responses

200Import results
{
"success": true,
"data": {
"imported": 100,
"duplicates": 5,
"errors": 0
}
}
POST/api/angelsend/warmup
Auth Required

Schedule Warmup

Schedule domain warmup sending via AngelSend.

Request Body

NameTypeRequiredDescription
domainstringRequiredDomain to warm up
dailyVolumenumberOptionalTarget daily send volume

Example Request

curl -X POST "https://scrappy.gg/api/angelsend/warmup" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"domain": "string"
}'

Responses

200Warmup scheduled
{
"success": true
}
GET/api/user/integrations/angelsend
Auth Required

Get User AngelSend Config

Get the current user's AngelSend API key configuration.

Example Request

curl -X GET "https://scrappy.gg/api/user/integrations/angelsend" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Config returned
{
"success": true,
"data": {
"hasApiKey": true,
"isUsingGlobal": false
}
}
PATCH/api/user/integrations/angelsend
Auth Required

Update User AngelSend Config

Set a per-user AngelSend API key (overrides the global environment variable).

Request Body

NameTypeRequiredDescription
apiKeystringOptionalAngelSend API key (null to remove)

Example Request

curl -X PATCH "https://scrappy.gg/api/user/integrations/angelsend" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"

Responses

200Config updated
{
"success": true
}

Credits

Credit balance and purchasing

3 endpoints
GET/api/credits/balance
Auth Required

Get Credit Balance

Get the current user's credit balance

Example Request

curl -X GET "https://scrappy.gg/api/credits/balance" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Balance returned
{
"success": true,
"data": {
"balance": 500,
"configured": true,
"costs": {
"scrape": 1,
"emailValidation": 0.5,
"aiDiscovery": 5
}
}
}
GET/api/credits/pricing
Auth Required

Get Pricing Tiers

Get available credit pricing tiers for purchase

Example Request

curl -X GET "https://scrappy.gg/api/credits/pricing" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Pricing returned
{
"success": true,
"data": {
"tiers": [
{
"id": "starter",
"credits": 100,
"price": 9.99
},
{
"id": "pro",
"credits": 500,
"price": 39.99
}
],
"configured": true
}
}
POST/api/credits/checkout
Auth Required

Create Checkout Session

Create a checkout session to purchase credits

Request Body

NameTypeRequiredDescription
pricingTierIdstringRequiredID of the pricing tier to purchase

Example Request

curl -X POST "https://scrappy.gg/api/credits/checkout" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"pricingTierId": "abc123"
}'

Responses

200Checkout created
{
"success": true,
"data": {
"url": "https://checkout.stripe.com/..."
}
}
503Credits not configured
{
"success": false,
"error": {
"code": "SERVICE_UNAVAILABLE",
"message": "Credits system not configured"
}
}

Markdown Exports

Export websites to markdown format

7 endpoints
GET/api/markdown-exports
Auth Required

List Exports

Get all markdown exports

Example Request

curl -X GET "https://scrappy.gg/api/markdown-exports" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Exports returned
{
"success": true,
"data": {
"exports": [
{
"id": "abc123",
"name": "Example Site",
"url": "https://example.com",
"status": "COMPLETED",
"pageCount": 25
}
]
}
}
POST/api/markdown-exports
Auth Required

Create Export

Create a new markdown export job

Request Body

NameTypeRequiredDescription
namestringRequiredExport name
urlstringRequiredURL to export
exportTypestringOptionalExport typeOptions: SINGLE_PAGE, FULL_SITEDefault: SINGLE_PAGE
maxPagesnumberOptionalMaximum pages to export (1-1000)Default: 100
followExternalbooleanOptionalFollow external links
useSitemapbooleanOptionalUse sitemap.xml for discovery

Example Request

curl -X POST "https://scrappy.gg/api/markdown-exports" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Example Name",
"url": "https://example.com"
}'

Responses

201Export created
{
"success": true,
"data": {
"export": {
"id": "abc123",
"status": "PENDING"
}
}
}
GET/api/markdown-exports/[id]
Auth Required

Get Export

Get export details

Path Parameters

NameTypeRequiredDescription
idstringRequiredExport ID

Example Request

curl -X GET "https://scrappy.gg/api/markdown-exports/abc123" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Export returned
{
"success": true,
"data": {
"export": {
"id": "abc123",
"status": "COMPLETED"
}
}
}
DELETE/api/markdown-exports/[id]
Auth Required

Delete Export

Delete an export

Path Parameters

NameTypeRequiredDescription
idstringRequiredExport ID

Example Request

curl -X DELETE "https://scrappy.gg/api/markdown-exports/abc123" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Export deleted
{
"success": true
}
GET/api/markdown-exports/[id]/pages
Auth Required

Get Export Pages

Get all pages from an export with their markdown content

Path Parameters

NameTypeRequiredDescription
idstringRequiredExport ID

Example Request

curl -X GET "https://scrappy.gg/api/markdown-exports/abc123/pages" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Pages returned
{
"success": true,
"data": {
"pages": [
{
"id": "page123",
"url": "https://example.com/page",
"title": "Page Title",
"markdown": "# Page Content..."
}
]
}
}
GET/api/markdown-exports/[id]/download
Auth Required

Download Export

Download export as ZIP or single markdown file

Path Parameters

NameTypeRequiredDescription
idstringRequiredExport ID

Query Parameters

NameTypeRequiredDescription
formatstringOptionalDownload formatOptions: zip, singleDefault: zip

Example Request

curl -X GET "https://scrappy.gg/api/markdown-exports/abc123/download" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200File download
{
"note": "Returns file attachment"
}
404No content
{
"success": false,
"error": {
"code": "NOT_FOUND",
"message": "Export has no content"
}
}
POST/api/markdown-exports/[id]/retry
Auth Required

Retry Export

Retry a failed export

Path Parameters

NameTypeRequiredDescription
idstringRequiredExport ID

Example Request

curl -X POST "https://scrappy.gg/api/markdown-exports/abc123/retry" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Export retry started
{
"success": true,
"message": "Export retry started"
}

Analytics

Pipeline overview, source performance, capacity planning, and credit burn rate metrics

4 endpoints
GET/api/analytics/pipeline-overview
Auth Required

Pipeline Overview

Get high-level pipeline metrics: lead counts by status, conversion rates, email validation rates, and recent activity.

Query Parameters

NameTypeRequiredDescription
projectIdstringOptionalFilter by project ID
daysnumberOptionalLookback window in days (default: 30)

Example Request

curl -X GET "https://scrappy.gg/api/analytics/pipeline-overview" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Pipeline overview returned
{
"success": true,
"data": {
"totalLeads": 1250,
"leadsByStatus": {
"NEW": 420,
"CONTACTED": 380,
"QUALIFIED": 210,
"UNQUALIFIED": 180,
"ARCHIVED": 60
},
"emailValidationRate": 0.84,
"newLeadsLast7Days": 145,
"conversionRate": 0.168
}
}
GET/api/analytics/sources
Auth Required

Source Analytics

Get performance metrics for each source: lead yield, success rate, and average run time.

Query Parameters

NameTypeRequiredDescription
projectIdstringOptionalFilter by project ID
daysnumberOptionalLookback window in days (default: 30)

Example Request

curl -X GET "https://scrappy.gg/api/analytics/sources" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Source analytics returned
{
"success": true,
"data": {
"sources": [
{
"sourceId": "src_123",
"sourceName": "NYC Dentist Directory",
"totalJobs": 12,
"successRate": 0.92,
"totalLeads": 340,
"avgLeadsPerJob": 28,
"avgRunTimeMs": 45000,
"lastRunAt": "2024-01-15T08:00:00Z"
}
]
}
}
GET/api/analytics/capacity
Auth Required

Capacity Planning

Get capacity metrics including active sources, scheduled jobs, job queue depth, and estimated throughput.

Example Request

curl -X GET "https://scrappy.gg/api/analytics/capacity" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Capacity data returned
{
"success": true,
"data": {
"activeSources": 24,
"scheduledJobsNext24h": 8,
"queueDepth": 3,
"estimatedLeadsPerDay": 450,
"workerUtilization": 0.35
}
}
GET/api/analytics/credit-burn-rate
Auth Required

Credit Burn Rate

Get credit usage trends and burn rate projection.

Query Parameters

NameTypeRequiredDescription
daysnumberOptionalLookback window in days (default: 30)

Example Request

curl -X GET "https://scrappy.gg/api/analytics/credit-burn-rate" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Credit burn rate returned
{
"success": true,
"data": {
"currentBalance": 8500,
"dailyBurnRate": 125,
"projectedDaysRemaining": 68,
"burnByCategory": {
"emailValidation": 80,
"aiFeatures": 30,
"scraping": 15
},
"usageTrend": [
{
"date": "2024-01-14",
"credits": 120
},
{
"date": "2024-01-15",
"credits": 135
}
]
}
}

Admin

Admin-only endpoints for platform management

3 endpoints
GET/api/admin/users
Admin Only

List All Users

Get all users with their statistics (admin only)

Example Request

curl -X GET "https://scrappy.gg/api/admin/users" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Users returned
{
"success": true,
"data": {
"users": [
{
"id": "abc123",
"email": "user@example.com",
"stats": {
"projects": 5,
"sources": 20,
"leads": 500,
"jobs": 50
}
}
],
"summary": {
"total": 100,
"active": 85,
"newThisWeek": 10
}
}
}
403Not admin
{
"success": false,
"error": {
"code": "FORBIDDEN",
"message": "Admin access required"
}
}
GET/api/admin/metrics/summary
Admin Only

Get Metrics Summary

Get aggregated platform metrics (admin only). Note: Response does not use standard wrapper format.

Example Request

curl -X GET "https://scrappy.gg/api/admin/metrics/summary" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Metrics returned
{
"database": {
"users": 100,
"projects": 500,
"sources": 1000,
"leads": 50000,
"jobs": 2000
},
"queues": {
"waiting": {
"scraping": 5,
"emailValidation": 10
},
"active": {
"scraping": 2,
"emailValidation": 5
},
"failed": {
"scraping": 0,
"emailValidation": 1
}
},
"timestamp": "2024-01-01T00:00:00Z"
}
GET/api/admin/metrics/prometheus
Admin Only

Prometheus Query

Proxy Prometheus queries (admin only)

Query Parameters

NameTypeRequiredDescription
querystringRequiredPrometheus query string
typestringOptionalQuery typeOptions: instant, rangeDefault: instant
startstringOptionalStart time for range queries (ISO 8601)
endstringOptionalEnd time for range queries (ISO 8601)
stepstringOptionalQuery resolution stepDefault: 15s

Example Request

curl -X GET ?query=search term"https://scrappy.gg/api/admin/metrics/prometheus" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Prometheus query results
{
"status": "success",
"data": {
"resultType": "vector",
"result": [
{
"metric": {
"__name__": "up"
},
"value": [
1704067200,
"1"
]
}
]
}
}
400Missing query
{
"error": "Query parameter is required"
}

Webhooks

Manage outbound webhooks for event notifications and receive inbound webhooks from external services

8 endpoints
GET/api/user/webhooks
Auth Required

List Webhooks

Get all configured outbound webhooks for the current user.

Example Request

curl -X GET "https://scrappy.gg/api/user/webhooks" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Webhooks returned
{
"success": true,
"data": {
"webhooks": [
{
"id": "wh_123",
"url": "https://your-app.com/webhook",
"events": [
"lead.created",
"lead.updated"
],
"enabled": true,
"createdAt": "2024-01-01T00:00:00Z"
}
]
}
}
POST/api/user/webhooks
Auth Required

Create Webhook

Register an outbound webhook endpoint. Events are signed with HMAC-SHA256 via the X-Scrappy-Signature header.

Request Body

NameTypeRequiredDescription
urlstringRequiredHTTPS endpoint to receive events
eventsarrayRequiredEvent types to subscribe to (e.g. lead.created, lead.updated, job.completed, email.validated)
secretstringOptionalSecret for HMAC signature verification (auto-generated if omitted)
enabledbooleanOptionalEnable the webhook on creation (default: true)

Example Request

curl -X POST "https://scrappy.gg/api/user/webhooks" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com",
"events": []
}'

Responses

201Webhook created
{
"success": true,
"data": {
"id": "wh_123",
"url": "https://your-app.com/webhook",
"secret": "whsec_abc123...",
"events": [
"lead.created"
]
}
}
GET/api/user/webhooks/{id}
Auth Required

Get Webhook

Get a webhook by ID including recent delivery history.

Path Parameters

NameTypeRequiredDescription
idstringRequiredWebhook ID

Example Request

curl -X GET "https://scrappy.gg/api/user/webhooks/{id}" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Webhook returned
{
"success": true,
"data": {
"id": "wh_123",
"url": "https://your-app.com/webhook",
"events": [
"lead.created"
],
"enabled": true,
"deliveries": [
{
"id": "del_456",
"event": "lead.created",
"status": "SUCCESS",
"responseCode": 200,
"deliveredAt": "2024-01-15T10:30:00Z"
}
]
}
}
PATCH/api/user/webhooks/{id}
Auth Required

Update Webhook

Update a webhook's URL, events, or enabled status.

Path Parameters

NameTypeRequiredDescription
idstringRequiredWebhook ID

Request Body

NameTypeRequiredDescription
urlstringOptionalUpdated endpoint URL
eventsarrayOptionalUpdated event list
enabledbooleanOptionalEnable or disable

Example Request

curl -X PATCH "https://scrappy.gg/api/user/webhooks/{id}" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"

Responses

200Webhook updated
{
"success": true,
"data": {}
}
DELETE/api/user/webhooks/{id}
Auth Required

Delete Webhook

Delete a webhook endpoint.

Path Parameters

NameTypeRequiredDescription
idstringRequiredWebhook ID

Example Request

curl -X DELETE "https://scrappy.gg/api/user/webhooks/{id}" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Webhook deleted
{
"success": true
}
POST/api/user/webhooks/{id}/deliveries/{deliveryId}/retry
Auth Required

Retry Webhook Delivery

Manually retry a failed webhook delivery. Auto-retry uses 3 attempts with exponential backoff (5s / 25s / 125s).

Path Parameters

NameTypeRequiredDescription
idstringRequiredWebhook ID
deliveryIdstringRequiredDelivery ID to retry

Example Request

curl -X POST "https://scrappy.gg/api/user/webhooks/{id}/deliveries/{deliveryId}/retry" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Delivery retry queued
{
"success": true,
"data": {
"retryJobId": "job_789"
}
}
POST/api/webhooks/cred
Public

Cred.diy Webhook (Inbound)

Inbound webhook from cred.diy credits system. Verified via HMAC-SHA256 signature. Handles credit top-ups and subscription events.

Request Body

NameTypeRequiredDescription
x-cred-signatureheaderRequiredHMAC-SHA256 signature for verification
x-cred-eventheaderRequiredEvent type (e.g. credit.purchased, subscription.updated)

Example Request

curl -X POST "https://scrappy.gg/api/webhooks/cred" \
-H "Content-Type: application/json" \
-d '{
"x-cred-signature": "",
"x-cred-event": ""
}'

Responses

200Webhook received and processed
{
"received": true
}
401Invalid signature
{
"error": "Invalid signature"
}
POST/api/webhooks/resend
Public

Resend Email Webhook (Inbound)

Inbound webhook from Resend for transactional email delivery events (bounces, complaints, etc.).

Request Body

NameTypeRequiredDescription
svix-signatureheaderRequiredSvix signature for verification
typestringRequiredEvent type (e.g. email.bounced, email.complained)

Example Request

curl -X POST "https://scrappy.gg/api/webhooks/resend" \
-H "Content-Type: application/json" \
-d '{
"svix-signature": "",
"type": "string"
}'

Responses

200Webhook received
{
"received": true
}

Enterprise

Organizations, API keys, source templates, proxy profiles, suppression lists, and data destinations

20 endpoints
GET/api/organizations
Auth Required

List Organizations

Get all organizations the current user belongs to.

Example Request

curl -X GET "https://scrappy.gg/api/organizations" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Organizations returned
{
"success": true,
"data": {
"organizations": [
{
"id": "org_123",
"name": "Acme Corp",
"role": "OWNER",
"memberCount": 8
}
]
}
}
POST/api/organizations
Auth Required

Create Organization

Create a new organization.

Request Body

NameTypeRequiredDescription
namestringRequiredOrganization name
domainstringOptionalCompany domain (for SSO)

Example Request

curl -X POST "https://scrappy.gg/api/organizations" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Example Name"
}'

Responses

201Organization created
{
"success": true,
"data": {
"id": "org_123",
"name": "Acme Corp"
}
}
GET/api/organizations/{id}
Auth Required

Get Organization

Get organization details.

Path Parameters

NameTypeRequiredDescription
idstringRequiredOrganization ID

Example Request

curl -X GET "https://scrappy.gg/api/organizations/{id}" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Organization returned
{
"success": true,
"data": {}
}
GET/api/organizations/{id}/members
Auth Required

List Members

Get all members of an organization.

Path Parameters

NameTypeRequiredDescription
idstringRequiredOrganization ID

Example Request

curl -X GET "https://scrappy.gg/api/organizations/{id}/members" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Members returned
{
"success": true,
"data": {
"members": [
{
"userId": "u_123",
"email": "admin@acme.com",
"role": "OWNER",
"joinedAt": "2024-01-01"
}
]
}
}
GET/api/organizations/{id}/sso
Auth Required

SSO Configuration

Get SSO/SAML configuration for the organization.

Path Parameters

NameTypeRequiredDescription
idstringRequiredOrganization ID

Example Request

curl -X GET "https://scrappy.gg/api/organizations/{id}/sso" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200SSO config returned
{
"success": true,
"data": {
"provider": "OKTA",
"enabled": true,
"domain": "acme.com"
}
}
GET/api/organizations/{id}/ip-allowlist
Auth Required

IP Allowlist

Get or manage IP allowlist for organization access control.

Path Parameters

NameTypeRequiredDescription
idstringRequiredOrganization ID

Example Request

curl -X GET "https://scrappy.gg/api/organizations/{id}/ip-allowlist" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Allowlist returned
{
"success": true,
"data": {
"allowlist": [
"192.168.1.0/24",
"10.0.0.0/8"
],
"enabled": true
}
}
GET/api/api-keys
Auth Required

List API Keys

Get all API keys for programmatic access.

Example Request

curl -X GET "https://scrappy.gg/api/api-keys" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200API keys returned
{
"success": true,
"data": {
"apiKeys": [
{
"id": "key_123",
"name": "Production Key",
"prefix": "sk_prod_****",
"createdAt": "2024-01-01"
}
]
}
}
POST/api/api-keys
Auth Required

Create API Key

Generate a new API key. The full key is returned only once.

Request Body

NameTypeRequiredDescription
namestringRequiredKey name/description
scopesarrayOptionalPermission scopes, e.g. ['leads:read', 'sources:write', 'browser:read', 'browser:write']. Use browser:read + browser:write for the cloud browser / @willpschulz/scrappy. '*' grants full access.
expiresAtstringOptionalISO 8601 expiration date
projectIdstring | nullOptionalDefault project for browser sessions created with this key. A project supplied by the request overrides it.

Example Request

curl -X POST "https://scrappy.gg/api/api-keys" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Example Name"
}'

Responses

201API key created — full key shown once
{
"success": true,
"data": {
"id": "key_123",
"name": "Production Key",
"key": "sk_prod_abc123...",
"prefix": "sk_prod_****"
}
}
DELETE/api/api-keys/{id}
Auth Required

Delete API Key

Revoke an API key.

Path Parameters

NameTypeRequiredDescription
idstringRequiredAPI key ID

Example Request

curl -X DELETE "https://scrappy.gg/api/api-keys/{id}" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Key revoked
{
"success": true
}
POST/api/mcp
Auth Required

Hosted MCP Server

Stateless Model Context Protocol endpoint (Streamable HTTP) for external AI agents. Authenticate with an sk_ API key (Authorization: Bearer). Registers the API-backed tools plus the browser_* cloud-browser toolset and send_chat_message; tool calls proxy to Scrappy's REST API as the key's user, scoped and rate-limited like any API call. POST only (GET/DELETE → 405).

Request Body

NameTypeRequiredDescription
jsonrpcstringRequiredMCP JSON-RPC 2.0 envelope (e.g. tools/list, tools/call)

Example Request

curl -X POST "https://scrappy.gg/api/mcp" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "string"
}'

Responses

200MCP JSON-RPC response
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"tools": [
"…",
"browser_create_session",
"send_chat_message"
]
}
}
401Missing/invalid API key
{
"success": false,
"error": {
"code": "UNAUTHORIZED",
"message": "Provide a Scrappy API key: Authorization: Bearer sk_..."
}
}
GET/api/source-templates
Auth Required

List Source Templates

Get reusable source configuration templates.

Example Request

curl -X GET "https://scrappy.gg/api/source-templates" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Templates returned
{
"success": true,
"data": {
"templates": [
{
"id": "tmpl_123",
"name": "LinkedIn Directory Scraper",
"category": "Social",
"usageCount": 42
}
]
}
}
GET/api/source-templates/marketplace
Auth Required

Template Marketplace

Browse publicly shared source templates from other users.

Example Request

curl -X GET "https://scrappy.gg/api/source-templates/marketplace" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Marketplace templates
{
"success": true,
"data": {
"templates": []
}
}
POST/api/source-templates
Auth Required

Create Source Template

Create a reusable source configuration template.

Request Body

NameTypeRequiredDescription
namestringRequiredTemplate name
descriptionstringOptionalTemplate description
configobjectRequiredSource configuration to templatize
isPublicbooleanOptionalShare on marketplace

Example Request

curl -X POST "https://scrappy.gg/api/source-templates" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Example Name",
"config": {}
}'

Responses

201Template created
{
"success": true,
"data": {
"id": "tmpl_123"
}
}
GET/api/proxy-profiles
Auth Required

List Proxy Profiles

Get configured proxy profiles for scraping.

Example Request

curl -X GET "https://scrappy.gg/api/proxy-profiles" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Proxy profiles returned
{
"success": true,
"data": {
"proxies": [
{
"id": "proxy_123",
"name": "US Residential",
"type": "RESIDENTIAL",
"country": "US",
"successRate": 0.94
}
]
}
}
POST/api/proxy-profiles
Auth Required

Create Proxy Profile

Add a proxy profile for use in scraping jobs.

Request Body

NameTypeRequiredDescription
namestringRequiredProfile name
typestringRequiredProxy typeOptions: RESIDENTIAL, DATACENTER, MOBILE
hoststringRequiredProxy host
portnumberRequiredProxy port
usernamestringOptionalProxy username
passwordstringOptionalProxy password (encrypted at rest)
countrystringOptionalCountry code (e.g. 'US')

Example Request

curl -X POST "https://scrappy.gg/api/proxy-profiles" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Example Name",
"type": "string",
"host": "string",
"port": 1
}'

Responses

201Proxy profile created
{
"success": true,
"data": {
"id": "proxy_123"
}
}
GET/api/suppression-list
Auth Required

List Suppression Lists

Get email suppression lists for compliance.

Example Request

curl -X GET "https://scrappy.gg/api/suppression-list" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Suppression lists returned
{
"success": true,
"data": {
"lists": [
{
"id": "sl_123",
"name": "Global Unsubscribes",
"entryCount": 1250
}
]
}
}
GET/api/suppression-list/{id}/entries
Auth Required

List Suppression Entries

Get entries in a suppression list.

Path Parameters

NameTypeRequiredDescription
idstringRequiredSuppression list ID

Example Request

curl -X GET "https://scrappy.gg/api/suppression-list/{id}/entries" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Entries returned
{
"success": true,
"data": {
"entries": [
{
"email": "unsubscribed@example.com",
"addedAt": "2024-01-15"
}
]
}
}
GET/api/data-destinations
Auth Required

List Data Destinations

Get configured data sync destinations (webhooks, databases, CRMs).

Example Request

curl -X GET "https://scrappy.gg/api/data-destinations" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Destinations returned
{
"success": true,
"data": {
"destinations": [
{
"id": "dest_123",
"name": "HubSpot CRM",
"type": "WEBHOOK",
"enabled": true
}
]
}
}
POST/api/data-destinations
Auth Required

Create Data Destination

Configure a new data sync destination.

Request Body

NameTypeRequiredDescription
namestringRequiredDestination name
typestringRequiredDestination typeOptions: WEBHOOK, DATABASE, CRM
configobjectRequiredDestination-specific configuration
enabledbooleanOptionalEnable sync on creation (default: true)

Example Request

curl -X POST "https://scrappy.gg/api/data-destinations" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Example Name",
"type": "string",
"config": {}
}'

Responses

201Destination created
{
"success": true,
"data": {
"id": "dest_123"
}
}
POST/api/data-destinations/{id}/sync
Auth Required

Trigger Sync

Manually trigger a data sync to a destination.

Path Parameters

NameTypeRequiredDescription
idstringRequiredDestination ID

Request Body

NameTypeRequiredDescription
leadIdsarrayOptionalSpecific lead IDs to sync (syncs all if omitted)

Example Request

curl -X POST "https://scrappy.gg/api/data-destinations/{id}/sync" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"

Responses

200Sync triggered
{
"success": true,
"data": {
"synced": 150,
"failed": 2
}
}

Cloud Browser

Create and drive cloud browser sessions (navigate, click, type, screenshot, scrape) and embed a live viewer. Backs @willpschulz/scrappy. Requires the browser:read / browser:write API-key scopes. Multi-tenant integrations: if one API key drives browsers for many of your own users, send X-Client-Ref: <your user id> on EVERY browser request. A request that carries it can only see and drive sessions, profiles and sleeping snapshots recorded for that same clientRef — anything else answers 404 exactly as if it did not exist, including sessions recorded with no clientRef at all. Create calls take the ref from the header when the body omits it, and reject a body clientRef that disagrees with the header (400). Requests without the header keep the key-wide view, so existing integrations are unaffected until they opt in.

58 endpoints
POST/api/browser/sessions
Auth Required

Create Session

Create a Steel browser session. Reaped after 30 minutes idle — any action resets the window, and the keepalive endpoint refreshes it without acting; a 12-hour absolute cap applies as a backstop. Scope browser:write. Capped at STEEL_MAX_SESSIONS_PER_USER (default 10) concurrent per user. screenWidth/screenHeight set the browser's real window and the screen it reports, so a page cannot see a viewport larger than its own screen. Egress: a session that names an end-user — by profileId, or by clientRef/X-Client-Ref — leaves through a dedicated static residential IP chosen from that identity, so the SAME end-user gets the SAME exit on every session, across restarts and after a pin is cleared. clientRef counts because a FIRST sign-in has no profile yet (the profile is created from the capture at the end), and that is the session whose exit matters most. A profile that already carries a pinned exit replays that pin. Anonymous sessions — no profileId and no clientRef — keep the rotating gateway, so a one-off cannot spend the reputation of an IP other people's logins depend on; their exit IP is not stable between sessions. Scrappy records the exit itself at capture time — you never supply it. Profiles captured before 2026-08-25 are pinned to a rotating exit and stay on it until re-captured: one fresh sign-in per platform moves them onto a static IP. If a session fails with UPSTREAM_UNAVAILABLE, retry with rotateEgress:true to keep the login but take a fresh route. When credits are configured, costs CRED_BROWSER_SESSION_COST up front (402 on insufficient balance) plus per-minute runtime metering.

Request Body

NameTypeRequiredDescription
screenWidthnumberOptionalViewport width in px (640–3840, default 1280)
screenHeightnumberOptionalViewport height in px (480–2160, default 720)
profileIdstringOptionalPersistent profile to hydrate — the session starts already logged in, and its rotated login is saved back on release
clientRefstringOptionalYour own end-user reference. Recorded on the session and enforced: any later request that sends X-Client-Ref must name this same value to reach the session. Defaults to the X-Client-Ref header when omitted; must match it when both are present
showCursorbooleanOptionalDraw the software mouse cursor into the page for the live viewer (default true). It is a div injected into the page DOM; set false to remove that artifact for anti-bot-sensitive runs — the pointer just won't show in the viewer
rotateEgressbooleanOptionalReplay the profile's login through a fresh exit IP instead of its pinned one (default false). For retrying after UPSTREAM_UNAVAILABLE — the login is fine, the route is not. Do not set it routinely: the pin exists because some logins drop the moment the exit IP changes

Example Request

curl -X POST "https://scrappy.gg/api/browser/sessions" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"

Responses

200Session created
{
"session": {
"id": "b1f2…",
"status": "live",
"createdAt": "2026-07-21T12:00:00Z"
},
"viewerUrl": "/api/browser/sessions/b1f2…/viewer",
"viewerTokenRequired": true,
"viewerTokenUrl": "/api/browser/sessions/b1f2…/viewer-token"
}
402Insufficient credits
{
"error": "Insufficient credits"
}
429Per-user or per-key session limit reached
{
"error": "Session limit reached (max 10). Close an existing session first."
}
GET/api/browser/sessions
Auth Required

List Sessions

List the caller's live sessions, each with a viewer URL. With X-Client-Ref set, only sessions recorded for that end user are returned. Scope browser:read.

Example Request

curl -X GET "https://scrappy.gg/api/browser/sessions" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Live sessions
{
"sessions": [
{
"id": "b1f2…",
"status": "live",
"viewerUrl": "/api/browser/sessions/b1f2…/viewer"
}
]
}
DELETE/api/browser/sessions/{id}
Auth Required

Release Session

Release (close) a session, freeing Steel capacity and stopping the runtime meter. Scope browser:write. Idempotent. The session's state is captured back onto its profile first, so cookies rotated during the session are not lost — pass capture=false to skip that. Skip it when the session is not worth storing: a replay the site rejected is sitting on a sign-in page, and capturing that overwrites a good stored login with a signed-out one. The cookie count can even go UP while the authentication goes away, so nothing downstream notices the loss. Scrappy cannot tell an authenticated session from a signed-out one; a caller that has just checked can. Accepted as ?capture=false or as {"capture": false} in the body. Only an explicit false opts out, and skipping the capture still settles billing and releases the session.

Path Parameters

NameTypeRequiredDescription
idstringRequiredSteel session id (UUID)

Query Parameters

NameTypeRequiredDescription
capturebooleanOptionalDefault true. false releases without writing this session's state back to its profile.

Example Request

curl -X DELETE "https://scrappy.gg/api/browser/sessions/{id}" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Released
{
"released": true
}
POST/api/browser/sessions/{id}/navigate
Auth Required

Navigate

Navigate the session to an http/https URL. Scope browser:write. SSRF-protected (private/internal hosts blocked). Readiness is read from the document's navigation timing entry, not from readyState — Chromium sets readyState to 'interactive' when parsing ends, BEFORE deferred scripts run (measured: interactive at +91ms, the deferred script's global at +984ms). By default this answers as soon as the navigation COMMITS — the new document exists, document.readyState is still 'loading' and no script has run — so a click issued next can land on server-rendered markup whose handlers do not exist yet, and both the click API and the page report success while nothing happens. Pass waitUntil:'domcontentloaded' to return once deferred scripts and end-of-body bundles have executed (what you want before interacting), or 'load' to also wait for images, fonts and CSS. A readiness wait that runs out returns 200 with timedOut:true and the readyState reached — the page did load, it is just still busy. Failures split by whose problem they are, and the split is machine-readable — branch on error.code and error.retryable, never on the message. 422 NAVIGATION_ERROR (retryable:false) is the page failing to load and retrying changes nothing. 503 UPSTREAM_UNAVAILABLE (retryable:true) is the session's egress route failing to carry the request; nothing is wrong with your call. Scrappy probes the exit before answering and retires it only if it genuinely refuses a tunnel — the same Chromium error is also what a Steel restart looks like, and retiring a working exit for that would move your end user's IP for no reason. When it IS retired, a retry in a NEW session gets a different route; retrying against the same session keeps failing either way, since a session is bound to its exit at launch. If it came from a profile, create the retry with rotateEgress:true. 503 SESSION_RESUMING (retryable:true) is the OPPOSITE instruction and nothing to do with the proxy: Scrappy replaced the machine the browser was on (a deploy, or a suspend/resume) and is rebuilding the session under the SAME id, so retry that same session after retryAfterSeconds rather than creating a new one — it keeps its id, its cookies and its exit IP. Alert on it as 'the provider is restarting', not as an outage. Both of those apply to EVERY session action, not just navigate — a rebuild breaks a click or a screenshot the same way. 503 BROWSER_SERVICE_UNAVAILABLE (retryable:true) means the browser service could not be reached at all, so nothing could be established about the session, including whether it still exists — retry it; it is not a statement that the session is gone.

Path Parameters

NameTypeRequiredDescription
idstringRequiredSteel session id

Request Body

NameTypeRequiredDescription
urlstringRequiredAbsolute URL (max 8192 chars)
waitUntilstringOptionalcommit (default — returns before any script runs) | domcontentloaded (page globals exist; use this before clicking) | load (also images/fonts/CSS)
timeoutnumberOptionalMax wait for that readiness state in ms (500–60000, default 15000). Ignored when waitUntil is commit
tabstringOptionalTarget tab (CDP target id); defaults to the most recent page

Example Request

curl -X POST "https://scrappy.gg/api/browser/sessions/{id}/navigate" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com"
}'

Responses

200Navigated
{
"success": true,
"data": {
"url": "https://example.com"
}
}
200Navigated and settled (waitUntil)
{
"success": true,
"data": {
"url": "https://example.com",
"waitUntil": "domcontentloaded",
"readyState": "interactive",
"waitedMs": 412
}
}
422The page could not be loaded — not retryable
{
"success": false,
"error": {
"code": "NAVIGATION_ERROR",
"message": "net::ERR_NAME_NOT_RESOLVED",
"retryable": false
}
}
503The session's egress route failed — retry in a new session
{
"success": false,
"error": {
"code": "UPSTREAM_UNAVAILABLE",
"message": "net::ERR_TUNNEL_CONNECTION_FAILED",
"retryable": true,
"retryAfterSeconds": 15
}
}
503The session's browser was replaced and is rebuilding — retry the SAME session
{
"success": false,
"error": {
"code": "SESSION_RESUMING",
"message": "This session's browser was just rebuilt and is still settling. Retry shortly; the session id is unchanged.",
"retryable": true,
"retryAfterSeconds": 5
}
}
503The browser service could not be reached — nothing could be established about the session, including whether it exists
{
"error": "Browser service is temporarily unavailable",
"code": "BROWSER_SERVICE_UNAVAILABLE",
"retryable": true,
"retryAfterSeconds": 15
}
POST/api/browser/sessions/{id}/screenshot
Auth Required

Screenshot

Capture a screenshot as base64. Four framings, at most one per call: the viewport (the default), fullPage, a single element (ref or selector — scrolled into view and captured whole even when it is taller than the window), or an explicit clip region in page coordinates. Cropping to the element is usually what you want when checking one control or reading one card: a fraction of the pixels, and it cannot be misread as a different part of the page. Naming two framings is a 400 rather than a silent choice between them. Scope browser:write; rate-limited (browserScreenshot).

Path Parameters

NameTypeRequiredDescription
idstringRequiredSteel session id

Request Body

NameTypeRequiredDescription
formatstringOptionalpng | jpeg | webp (default png)
qualitynumberOptional0-100, ignored for png
fullPagebooleanOptionalCapture the full scrollable page
refstringOptionalCapture just this element — a ref from read-page or find
selectorstringOptionalCapture just the element this CSS selector names
piercebooleanOptionalAlso search open shadow roots when resolving selector
clipobjectOptional{ x, y, width, height, scale? } in CSS pixels from the top of the page
tabstringOptionalTarget tab id (defaults to the active tab)

Example Request

curl -X POST "https://scrappy.gg/api/browser/sessions/{id}/screenshot" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"

Responses

200Screenshot
{
"success": true,
"data": {
"base64": "iVBORw0KGgo…",
"format": "png",
"mimeType": "image/png"
}
}
POST/api/browser/sessions/{id}/recording
Auth Required

Record Session (video)

Record what the session does, as an MP4. action:'start' begins capturing the active page, 'stop' finalises the file and returns a downloadUrl, 'status' reports progress, 'discard' throws it away. GET this same path for the video itself, once stopped. The capture is a CDP screencast, so the page cannot observe it and nothing is injected into it — an in-page MediaRecorder would need a user gesture and would leave getDisplayMedia fingerprints on a session whose whole point is not to look automated. Encoding happens inside the browser container, next to the frames: the alternative pushes every JPEG across the network to one of several app machines, and then the file lives on whichever machine served the start call rather than the one serving the download. Defaults to 5fps at 1280x720 — a click-by-click record at a fraction of the size of real-time video. It stops on its own at maxDurationMs, at maxBytes, or when the session is released; a recording nobody stopped must not be able to fill the disk of a box every session on that machine shares. One recording per session: starting again while one runs returns the running one rather than competing with it. Download before releasing the session — the file lives with the browser that made it. `record` is also a valid step inside /batch, which is how you wrap a whole flow in one call. Scope browser:write; rate-limited (browserRecording, 30/min).

Path Parameters

NameTypeRequiredDescription
idstringRequiredSteel session id

Request Body

NameTypeRequiredDescription
actionstringOptionalstart | stop | status | discard (default status)
fpsnumberOptionalFrames per second, 1-15 (default 5)
qualitynumberOptionalJPEG quality of captured frames, 10-100 (default 60)
maxWidthnumberOptionalCapture width cap in px, 320-2560 (default 1280)
maxHeightnumberOptionalCapture height cap in px, 240-1440 (default 720)
maxDurationMsnumberOptionalStop automatically after this long, 1s-15min (default 2min)
maxBytesnumberOptionalStop automatically at this size, 1MB-512MB (default 100MB)

Example Request

curl -X POST "https://scrappy.gg/api/browser/sessions/{id}/recording" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"

Responses

200Recording status
{
"success": true,
"data": {
"sessionId": "b1f2…",
"state": "stopped",
"startedAt": "2026-08-26T12:00:00.000Z",
"stoppedAt": "2026-08-26T12:00:42.000Z",
"durationMs": 42000,
"frames": 210,
"fps": 5,
"sizeBytes": 1841203,
"mimeType": "video/mp4",
"filename": "session-b1f2….mp4",
"stopReason": "requested",
"error": null,
"downloadUrl": "/api/browser/sessions/b1f2…/recording"
}
}
404Session is not live, or has no page to record
{
"success": false,
"error": {
"code": "SESSION_NOT_RECORDABLE",
"message": "Session is not live"
}
}
POST/api/browser/sessions/{id}/scrape
Auth Required

Scrape

Return page content in the shape you need. Beyond raw html/text, three simplified formats run server-side over the rendered DOM (so they see post-JavaScript state) and are far cheaper to work with: markdown strips boilerplate down to the main content, skeleton collapses repeated components to one exemplar plus the CSS selector matching all of them, and structured returns JSON-LD, microdata, OpenGraph and embedded framework state. Try structured first — when a site publishes its own data, reading it beats inferring it from markup. Scope browser:write.

Path Parameters

NameTypeRequiredDescription
idstringRequiredSteel session id

Request Body

NameTypeRequiredDescription
formatstringOptionalhtml (default) | text | markdown | skeleton | structured | clean
selectorstringOptionalCSS selector to scope extraction
maxCharsnumberOptionalCap output size for the simplified formats (default 200000, max 1000000)

Example Request

curl -X POST "https://scrappy.gg/api/browser/sessions/{id}/scrape" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"

Responses

200Content
{
"success": true,
"data": {
"content": "<html>…",
"url": "https://example.com",
"title": "Example"
}
}
200Simplified content (markdown, skeleton, structured, clean)
{
"success": true,
"data": {
"content": "main#content\n div.results\n [repeat ×47] selector: div.card\n h3.card__title \"Ada Lovelace\"",
"url": "https://example.com",
"title": "Example",
"format": "skeleton",
"stats": {
"inputChars": 184320,
"outputChars": 1620,
"reduction": 113.8
},
"truncated": false
}
}
422Page HTML exceeds the 10MB simplifier limit
{
"success": false,
"error": {
"code": "HTML_TOO_LARGE",
"message": "HTML is 14204KB, over the 10240KB simplifier limit"
}
}
POST/api/browser/sessions/{id}/scrape-mode
Auth Required

Scrape Mode

Render pages for extraction instead of for humans. Blocks the resource categories that cost the most and contribute nothing to extracted data (images, media, fonts, trackers), then runs an in-page pass that removes cookie/consent walls, unsticks fixed headers that swallow clicks, completes the animations that keep waits from settling, promotes lazy-loaded data-src URLs to real src attributes, and pauses media. With persist (the default) it is registered as a document-start script, so every later navigation renders the same way. Pair with the simplified scrape formats for the read side. Scope browser:write.

Path Parameters

NameTypeRequiredDescription
idstringRequiredSteel session id

Request Body

NameTypeRequiredDescription
actionstringRequiredon | off
presetstringOptionallight (trackers only) | standard (default: images, media, fonts, trackers) | aggressive (also CSS and JS — static-HTML sites only, it leaves a client-rendered page empty)
blockarrayOptionalOverride the preset: image, media, font, stylesheet, script, tracking
striparrayOptionalOverride the in-page passes: animations, overlays, sticky, lazy, media, scrolllock
blockPatternsarrayOptionalExtra raw URL globs, e.g. "*ads.example.com*" (max 100)
persistbooleanOptionalRe-apply on every later navigation in this tab (default true)
watchbooleanOptionalKeep watching ~15s for consent walls mounted late by async vendor scripts (default true)

Example Request

curl -X POST "https://scrappy.gg/api/browser/sessions/{id}/scrape-mode" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"action": "string"
}'

Responses

200Scrape mode enabled
{
"success": true,
"data": {
"enabled": true,
"preset": "standard",
"blockedCount": 28,
"strip": [
"animations",
"overlays",
"sticky",
"lazy",
"media",
"scrolllock"
],
"persisted": true,
"report": {
"overlaysRemoved": 2,
"unstuck": 0,
"lazyHydrated": 14,
"mediaPaused": 1,
"cssInjected": true,
"watching": true
}
}
}
200Scrape mode disabled
{
"success": true,
"data": {
"enabled": false,
"blocked": [],
"styleRemoved": true,
"note": "Blocking cleared and styles removed. Elements already removed from the current document return on reload."
}
}
POST/api/browser/sessions/{id}/keepalive
Auth Required

Keepalive

Mark a session as still in use. Reaping is idle-based, so any action already counts as activity and most integrations never need this. Use it when a user is viewing an embedded browser without anything being driven — reading, filling a form, completing MFA — which otherwise looks identical to an abandoned session. Returns the idle window and a recommended refresh interval — poll on that rather than hard-coding one. Takes no request body: no body, an empty body and {} are all accepted. Scope browser:write.

Path Parameters

NameTypeRequiredDescription
idstringRequiredSteel session id

Example Request

curl -X POST "https://scrappy.gg/api/browser/sessions/{id}/keepalive" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Refreshed
{
"success": true,
"data": {
"alive": true,
"idleTimeoutMs": 1800000,
"recommendedIntervalMs": 600000
}
}
GET/api/browser/profiles
Auth Required

List Profiles

List persistent browser profiles — saved logins (cookies + localStorage) that survive session teardown, letting an agent act on a user's behalf days after they signed in. Pass clientRef to scope to one of your end-users. Stored cookies are never returned. Scope browser:read.

Query Parameters

NameTypeRequiredDescription
clientRefstringOptionalOnly profiles for this end-user reference. A filter, not a boundary — X-Client-Ref is the boundary, and a query value that disagrees with the header is rejected (400)

Example Request

curl -X GET "https://scrappy.gg/api/browser/profiles" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Profiles
{
"success": true,
"data": {
"profiles": [
{
"id": "prf_123",
"name": "acme-facebook",
"clientRef": "user_42",
"cookieCount": 18,
"capturedAt": "2026-07-22T10:00:00Z"
}
]
}
}
POST/api/browser/profiles
Auth Required

Create Profile

Create an empty profile. It holds no login until you capture one from a live session. Pin proxyUrl and userAgent for any profile you plan to reuse — replaying a session cookie from a new IP and fingerprint each run is the most common reason a persisted login gets challenged. Scope browser:write.

Request Body

NameTypeRequiredDescription
namestringRequiredProfile name, unique per account
clientRefstringOptionalYour own end-user reference. Enforced on every later read, capture, delete and profileId replay made with X-Client-Ref. Defaults to the header when omitted; must match it when both are present
proxyUrlstringOptionalPinned egress proxy for a stable exit IP
userAgentstringOptionalPinned user agent for fingerprint consistency

Example Request

curl -X POST "https://scrappy.gg/api/browser/profiles" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Example Name"
}'

Responses

201Created
{
"success": true,
"data": {
"id": "prf_123",
"name": "acme-facebook",
"cookieCount": 0
}
}
409Name already used
{
"error": "A profile named \"acme-facebook\" already exists"
}
POST/api/browser/profiles/{id}/capture
Auth Required

Capture Login into Profile

Save a live session's cookies and localStorage into the profile. Call this once after your end-user signs in inside the embedded viewer; from then on, sessions created with that profileId start already authenticated. Sessions released with a profileId re-capture automatically. An empty capture is rejected rather than written, so a transient failure cannot wipe a working profile. Scope browser:write.

Path Parameters

NameTypeRequiredDescription
idstringRequiredProfile id

Request Body

NameTypeRequiredDescription
sessionIdstringRequiredLive session to capture the login from

Example Request

curl -X POST "https://scrappy.gg/api/browser/profiles/{id}/capture" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"sessionId": "abc123"
}'

Responses

200Captured
{
"success": true,
"data": {
"captured": true,
"cookieCount": 18,
"originCount": 2
}
}
422Nothing to capture
{
"error": "Nothing captured — the profile may not exist, or the session had no cookies or storage to save"
}
PATCH/api/browser/profiles/{id}
Auth Required

Update Profile

Change how a profile is routed — name, pinned proxy, pinned user agent — without touching the login it holds. proxyUrl:null un-pins the profile so its next session re-rolls onto a fresh exit; that is the durable fix when a pinned exit is gone for good, while rotateEgress:true on session creation is the per-run one. A later capture through a working exit re-pins the profile automatically. Scope browser:write.

Path Parameters

NameTypeRequiredDescription
idstringRequiredProfile id

Request Body

NameTypeRequiredDescription
namestringOptionalNew profile name
proxyUrlstringOptionalPinned egress proxy, or null to un-pin and let the next session re-roll
userAgentstringOptionalPinned user agent, or null to clear

Example Request

curl -X PATCH "https://scrappy.gg/api/browser/profiles/{id}" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"

Responses

200Updated
{
"success": true,
"data": {
"id": "prf_123",
"name": "acme-facebook",
"proxyUrl": null
}
}
404Profile not found
{
"error": "Profile not found"
}
DELETE/api/browser/profiles/{id}
Auth Required

Delete Profile

Permanently delete a profile and the encrypted login it holds. This is the revocation path — call it when an end-user disconnects their account. Scope browser:write.

Path Parameters

NameTypeRequiredDescription
idstringRequiredProfile id

Example Request

curl -X DELETE "https://scrappy.gg/api/browser/profiles/{id}" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Deleted
{
"success": true,
"data": {
"deleted": true
}
}
POST/api/browser/sessions/{id}/check
Auth Required

Check / Uncheck

Set the checked state of a checkbox or radio input. Use this rather than clicking one: a click TOGGLES, so re-running a step that clicked an already-checked box turns it off, which makes an idempotent retry destructive. Scope browser:write.

Path Parameters

NameTypeRequiredDescription
idstringRequiredSteel session id

Request Body

NameTypeRequiredDescription
refstringOptionalRef from read-page (ref_<n>) — names the element directly. Use instead of selector
selectorstringOptionalCSS selector of the checkbox or radio (or use ref)
checkedbooleanRequiredDesired state — true checks, false unchecks
tabstringOptionalTarget tab id (defaults to the active tab)

Example Request

curl -X POST "https://scrappy.gg/api/browser/sessions/{id}/check" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"checked": true
}'

Responses

200Set
{
"success": true,
"data": {
"checked": true
}
}
POST/api/browser/sessions/{id}/hover
Auth Required

Hover

Move the pointer over an element, by ref, CSS selector or coordinates. Menus and tooltips that only appear on hover need this before their contents exist in the DOM at all — reading the page first and finding nothing is the expected result, not a failure. Scope browser:write.

Path Parameters

NameTypeRequiredDescription
idstringRequiredSteel session id

Request Body

NameTypeRequiredDescription
refstringOptionalRef from read-page (ref_<n>) — names the element directly. Use instead of selector
selectorstringOptionalCSS selector of the element to hover (or use ref, or x/y)
xnumberOptionalX coordinate, with y
ynumberOptionalY coordinate, with x
tabstringOptionalTarget tab id (defaults to the active tab)

Example Request

curl -X POST "https://scrappy.gg/api/browser/sessions/{id}/hover" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"

Responses

200Hovered
{
"success": true,
"data": {
"hovered": true,
"x": 120,
"y": 340
}
}
POST/api/browser/sessions/{id}/select
Auth Required

Select Option

Choose option(s) in a native <select> by value, addressed by ref or selector. Sets each option's selected flag and dispatches a change event, so frameworks listening for change see it — assigning value alone does not always propagate. Pass values (array) for a multi-select. Scope browser:write.

Path Parameters

NameTypeRequiredDescription
idstringRequiredSteel session id

Request Body

NameTypeRequiredDescription
refstringOptionalRef from read-page (ref_<n>) — names the element directly. Use instead of selector
selectorstringOptionalCSS selector of the <select> (or use ref)
valuestringOptionalSingle option value to select
valuesarrayOptionalOption values for a multi-select
tabstringOptionalTarget tab id (defaults to the active tab)

Example Request

curl -X POST "https://scrappy.gg/api/browser/sessions/{id}/select" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"

Responses

200Selected
{
"success": true,
"data": {
"selected": [
"ca"
]
}
}
POST/api/browser/sessions/{id}/scroll
Auth Required

Scroll

Scroll the page — to a ref or selector, by pixel offset, to top/bottom, or by dispatching a real mouse-wheel at a point. Scrolling to an element is what makes it clickable by coordinates: an off-screen element reports coordinates outside the viewport, and a mouse event dispatched there hits nothing. The offset and to-element modes move the page's own scroller; `wheel` instead scrolls whatever nested scroller sits under (x, y) — a results list, an inner dashboard panel, a virtualized grid — which is the only way to reach a scrollable region you cannot name with a selector. Scope browser:write.

Path Parameters

NameTypeRequiredDescription
idstringRequiredSteel session id

Request Body

NameTypeRequiredDescription
refstringOptionalRef from read-page (ref_<n>) — names the element directly. Use instead of selector
selectorstringOptionalCSS selector to scroll into view (or use ref)
xnumberOptionalHorizontal pixel offset, with y
ynumberOptionalVertical pixel offset, with x
positionstringOptionaltop | bottom
wheelobjectOptionalDispatch a mouse-wheel at a point: { x, y, deltaX?, deltaY } in CSS px (positive deltaY scrolls down). Scrolls the nested pane under (x, y) rather than the window
tabstringOptionalTarget tab id (defaults to the active tab)

Example Request

curl -X POST "https://scrappy.gg/api/browser/sessions/{id}/scroll" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"

Responses

200Scrolled
{
"success": true,
"data": {
"scrolled": true,
"scrollX": 0,
"scrollY": 1840
}
}
200Wheel scrolled a nested pane
{
"success": true,
"data": {
"scrolled": true,
"wheel": {
"x": 640,
"y": 400,
"deltaX": 0,
"deltaY": 600
}
}
}
POST/api/browser/sessions/{id}/press
Auth Required

Press Key

Press a key — Enter, Tab, Escape, arrows, or a text character — via CDP key events. Keys go to whatever currently has focus, which after a click or a navigation is frequently not the element you meant, and that failure is silent because the press itself succeeds. Pass ref to focus a specific element first. Scope browser:write.

Path Parameters

NameTypeRequiredDescription
idstringRequiredSteel session id

Request Body

NameTypeRequiredDescription
keystringRequiredKey name (Enter, Tab, Escape, ArrowDown…) or a single character
refstringOptionalRef from read-page to focus before pressing
modifiersnumberOptionalCDP modifier bitmask — 1 Alt, 2 Ctrl, 4 Meta, 8 Shift
tabstringOptionalTarget tab id (defaults to the active tab)

Example Request

curl -X POST "https://scrappy.gg/api/browser/sessions/{id}/press" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"key": "string"
}'

Responses

200Pressed
{
"success": true,
"data": {
"pressed": "Enter"
}
}
POST/api/browser/sessions/{id}/evaluate
Auth Required

Evaluate JavaScript

Run JavaScript in the page and return its result. The escape hatch for anything the typed endpoints do not cover — reading a framework's state, calling a page function, extracting a shape no selector expresses. Returns the value by value, so DOM nodes and functions do not survive; return a serialisable summary instead. Scope browser:write.

Path Parameters

NameTypeRequiredDescription
idstringRequiredSteel session id

Request Body

NameTypeRequiredDescription
expressionstringRequiredJavaScript to evaluate. The completion value is returned
timeoutnumberOptionalMilliseconds before giving up (default 10000)
tabstringOptionalTarget tab id (defaults to the active tab)

Example Request

curl -X POST "https://scrappy.gg/api/browser/sessions/{id}/evaluate" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"expression": "string"
}'

Responses

200Result
{
"success": true,
"data": {
"result": {
"rows": 12
}
}
}
GET/api/browser/sessions/{id}/dom
Auth Required

Get DOM

A simplified DOM tree for the page. Useful when you need structure rather than semantics — read-page is the better starting point for deciding what to act on, because it reports roles, names and state as a screen reader would, and hands back refs you can act on directly. Scope browser:read.

Path Parameters

NameTypeRequiredDescription
idstringRequiredSteel session id

Query Parameters

NameTypeRequiredDescription
selectorstringOptionalRestrict to a subtree
maxDepthnumberOptionalLimit tree depth
tabstringOptionalTarget tab id

Example Request

curl -X GET "https://scrappy.gg/api/browser/sessions/{id}/dom" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200DOM tree
{
"success": true,
"data": {
"tag": "body",
"children": []
}
}
GET/api/browser/sessions/{id}/console
Auth Required

Console Entries

Buffered console output captured from the page (Runtime.consoleAPICalled). Capture ARMS on your first read of a session: the CDP domain that feeds it is the single loudest automation signal a page can read (Google's sign-in acts on it), so it is not enabled on sessions that never ask for console output. History is NOT lost — Chromium replays the messages it buffered before the domain was enabled — but it arrives asynchronously, so the FIRST read is normally empty and the second carries everything, including lines logged before you ever called this. Measured against production 2026-08-29: a marker logged before the first read was absent from that read and present in the next one. Poll twice before concluding a page logged nothing. Filter before you read: a busy page emits hundreds of lines and every one of them costs you. `contains` is a case-insensitive substring, not a regex — a caller-supplied pattern would run in Scrappy's own process, where a catastrophically backtracking one blocks every other request on that machine. Each read advances your cursor past everything drained, filtered out or not, so the response reports drained/matched/dropped and `peek=true` leaves the cursor alone while you poll for one line. Scope browser:read.

Path Parameters

NameTypeRequiredDescription
idstringRequiredSteel session id

Query Parameters

NameTypeRequiredDescription
containsstringOptionalCase-insensitive substring of the message text
levelstringOptionalComma-separated levels to keep, e.g. error,warn
limitnumberOptionalReturn at most this many, newest kept (1-500)
peekbooleanOptionalLeave the read cursor where it is
tabstringOptionalTarget tab id

Example Request

curl -X GET "https://scrappy.gg/api/browser/sessions/{id}/console" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Entries
{
"entries": [
{
"level": "error",
"text": "Uncaught TypeError"
}
],
"drained": 84,
"matched": 1,
"returned": 1,
"dropped": 83,
"truncated": false
}
GET/api/browser/sessions/{id}/network
Auth Required

Network Entries

Buffered network activity captured from the page. Shows what the page actually requested and what came back — the difference between 'the form did nothing' and 'the form posted and the server returned 422' is usually only visible here. A single page load is easily 200 requests, so filter: `contains` matches the URL (case-insensitive substring, not a regex), `method` and `type` narrow by verb and resource kind, and statusMin=400 is the fast way to ask what failed (a request still in flight has no status and is not matched). Same cursor rules as the console endpoint — the response reports drained/matched/dropped, and `peek=true` leaves the cursor alone. Scope browser:read.

Path Parameters

NameTypeRequiredDescription
idstringRequiredSteel session id

Query Parameters

NameTypeRequiredDescription
containsstringOptionalCase-insensitive substring of the request URL
methodstringOptionalComma-separated methods to keep, e.g. POST,PUT
typestringOptionalComma-separated resource types, e.g. xhr,fetch
statusMinnumberOptionalOnly responses at or above this status
limitnumberOptionalReturn at most this many, newest kept (1-500)
peekbooleanOptionalLeave the read cursor where it is
tabstringOptionalTarget tab id

Example Request

curl -X GET "https://scrappy.gg/api/browser/sessions/{id}/network" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Entries
{
"entries": [
{
"method": "POST",
"url": "/api/save",
"status": 422
}
],
"drained": 212,
"matched": 1,
"returned": 1,
"dropped": 211,
"truncated": false
}
GET/api/browser/sessions/{id}/devtools
Auth Required

Console + Network

Console and network entries in one response. The same data as the two endpoints separately, in one round trip — worth it when diagnosing a failure, where the useful signal is usually a console error and the request that preceded it read together. Scope browser:read.

Path Parameters

NameTypeRequiredDescription
idstringRequiredSteel session id

Query Parameters

NameTypeRequiredDescription
limitnumberOptionalMaximum entries per stream
tabstringOptionalTarget tab id

Example Request

curl -X GET "https://scrappy.gg/api/browser/sessions/{id}/devtools" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Both streams
{
"success": true,
"data": {
"console": [],
"network": []
}
}
POST/api/browser/sessions/{id}/history
Auth Required

History Navigation

Go back, go forward, or reload. Back is not the same as navigating to the previous URL: it replays the entry, preserving form state and scroll position the way a person pressing the button would. Scope browser:write.

Path Parameters

NameTypeRequiredDescription
idstringRequiredSteel session id

Request Body

NameTypeRequiredDescription
actionstringRequiredback | forward | reload
tabstringOptionalTarget tab id (defaults to the active tab)

Example Request

curl -X POST "https://scrappy.gg/api/browser/sessions/{id}/history" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"action": "string"
}'

Responses

200Navigated
{
"success": true,
"data": {
"action": "back",
"url": "https://example.com/list"
}
}
POST/api/browser/sessions/{id}/viewport
Auth Required

Set Viewport

Set viewport size and device emulation. Changing this changes what the page renders — responsive layouts move controls, and a selector or ref found at one size can be absent at another, so read the page again after resizing. Scope browser:write.

Path Parameters

NameTypeRequiredDescription
idstringRequiredSteel session id

Request Body

NameTypeRequiredDescription
widthnumberRequiredViewport width in px
heightnumberRequiredViewport height in px
deviceScaleFactornumberOptionalDevice pixel ratio (default 1)
mobilebooleanOptionalEmulate a mobile device (default false)
tabstringOptionalTarget tab id (defaults to the active tab)

Example Request

curl -X POST "https://scrappy.gg/api/browser/sessions/{id}/viewport" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"width": 1,
"height": 1
}'

Responses

200Set
{
"success": true,
"data": {
"width": 1280,
"height": 720
}
}
POST/api/browser/sessions/{id}/iframe
Auth Required

Iframe Interaction

Act inside a same-origin iframe — click, type, or evaluate within the frame. Cross-origin frames are not reachable this way, by browser design rather than by our choice; for those, the frame's own URL usually has to be driven as its own page. Scope browser:write.

Path Parameters

NameTypeRequiredDescription
idstringRequiredSteel session id

Request Body

NameTypeRequiredDescription
frameSelectorstringRequiredCSS selector of the iframe element
actionstringRequiredclick | type | evaluate
selectorstringOptionalSelector INSIDE the frame, for click and type
textstringOptionalText, for type
expressionstringOptionalJavaScript, for evaluate
tabstringOptionalTarget tab id (defaults to the active tab)

Example Request

curl -X POST "https://scrappy.gg/api/browser/sessions/{id}/iframe" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"frameSelector": "string",
"action": "string"
}'

Responses

200Done
{
"success": true,
"data": {
"action": "click",
"clicked": true
}
}
GET/api/browser/sessions/{id}/cookies
Auth Required

Get Cookies

All cookies visible to the session. Cookies are credential-grade for a signed-in site — a stored jar grants account access without passing MFA again — so treat a response from here the way you would a password. Scope browser:read.

Path Parameters

NameTypeRequiredDescription
idstringRequiredSteel session id

Query Parameters

NameTypeRequiredDescription
tabstringOptionalTarget tab id

Example Request

curl -X GET "https://scrappy.gg/api/browser/sessions/{id}/cookies" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Cookies
{
"success": true,
"data": {
"cookies": [
{
"name": "session",
"domain": ".example.com"
}
]
}
}
POST/api/browser/sessions/{id}/cookies
Auth Required

Set Cookies

Inject cookies into the session — how a stored login is replayed. Note that a faithful replay is not the same as being signed in: a platform that binds its session to the IP that created it rejects the cookies from a different exit, and the browser lands on its sign-in page with every cookie present. If that happens, the login is fine and the egress is not. Scope browser:write.

Path Parameters

NameTypeRequiredDescription
idstringRequiredSteel session id

Request Body

NameTypeRequiredDescription
cookiesarrayRequiredCookie objects: name, value, domain, path, secure, httpOnly, sameSite, expires
tabstringOptionalTarget tab id (defaults to the active tab)

Example Request

curl -X POST "https://scrappy.gg/api/browser/sessions/{id}/cookies" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"cookies": []
}'

Responses

200Set
{
"success": true,
"data": {
"set": 8
}
}
DELETE/api/browser/sessions/{id}/cookies
Auth Required

Delete Cookies

Delete cookies by name and domain, or clear them all. Clearing signs the session out of everything — which is occasionally what you want before a fresh login, and never what you want mid-run. Scope browser:write.

Path Parameters

NameTypeRequiredDescription
idstringRequiredSteel session id

Request Body

NameTypeRequiredDescription
namestringOptionalCookie name to delete. Omit to clear all
domainstringOptionalRestrict deletion to this domain
tabstringOptionalTarget tab id (defaults to the active tab)

Example Request

curl -X DELETE "https://scrappy.gg/api/browser/sessions/{id}/cookies" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"

Responses

200Deleted
{
"success": true,
"data": {
"deleted": true
}
}
GET/api/browser/sessions/{id}/storage
Auth Required

Read Storage

Read localStorage or sessionStorage. Some sites keep auth state here rather than in cookies, so a login that replays cookies alone can still land signed out — if that happens, this is the place to look before blaming the cookie jar. Scope browser:read.

Path Parameters

NameTypeRequiredDescription
idstringRequiredSteel session id

Query Parameters

NameTypeRequiredDescription
typestringOptionallocal | session (default local)
keystringOptionalSingle key. Omit for all
tabstringOptionalTarget tab id

Example Request

curl -X GET "https://scrappy.gg/api/browser/sessions/{id}/storage" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Values
{
"success": true,
"data": {
"type": "local",
"values": {
"token": "…"
}
}
}
POST/api/browser/sessions/{id}/storage
Auth Required

Write Storage

Write a localStorage or sessionStorage value. Writing here does not make a site treat you as signed in — apps read their storage at load and cache it, so a value injected after the page is up is usually ignored until a reload. Set it before navigating, or reload after. Scope browser:write.

Path Parameters

NameTypeRequiredDescription
idstringRequiredSteel session id

Request Body

NameTypeRequiredDescription
typestringOptionallocal | session (default local)
keystringRequiredStorage key
valuestringRequiredValue to store
tabstringOptionalTarget tab id (defaults to the active tab)

Example Request

curl -X POST "https://scrappy.gg/api/browser/sessions/{id}/storage" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"key": "string",
"value": "string"
}'

Responses

200Stored
{
"success": true,
"data": {
"stored": true
}
}
DELETE/api/browser/sessions/{id}/storage
Auth Required

Clear Storage

Remove one storage key, or clear the store. For a site that keeps auth state in storage rather than cookies, clearing it signs the session out even though every cookie survives — which is a useful way to force a fresh login, and a surprising way to lose one. Scope browser:write.

Path Parameters

NameTypeRequiredDescription
idstringRequiredSteel session id

Request Body

NameTypeRequiredDescription
typestringOptionallocal | session (default local)
keystringOptionalKey to remove. Omit to clear the store
tabstringOptionalTarget tab id (defaults to the active tab)

Example Request

curl -X DELETE "https://scrappy.gg/api/browser/sessions/{id}/storage" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"

Responses

200Cleared
{
"success": true,
"data": {
"cleared": true
}
}
GET/api/browser/sessions/{id}/context
Auth Required

Session Context

The session's cookies and localStorage, plus the exit it is going out through. Proxy credentials are stripped, and egressIp is best-effort — it is resolved by fetching an echo service from inside the browser, so a page whose CSP blocks that returns null rather than a wrong answer. Worth checking when a replayed login is rejected: if the exit IP is not the one that minted the session, the cookies were never the problem. Scope browser:read.

Path Parameters

NameTypeRequiredDescription
idstringRequiredSteel session id

Query Parameters

NameTypeRequiredDescription
tabstringOptionalTarget tab id

Example Request

curl -X GET "https://scrappy.gg/api/browser/sessions/{id}/context" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Context
{
"success": true,
"data": {
"cookies": [],
"egressIp": "76.27.31.230",
"proxy": {
"label": "residential"
}
}
}
POST/api/browser/sessions/{id}/captcha
Auth Required

CAPTCHA

Detect or solve a CAPTCHA. Detection distinguishes a challenge that is actually RENDERED from a page merely carrying the vendor's script — most sign-in pages load the script unconditionally, so presence of the code proves nothing. Solving requires a solver key; without one a challenge is reported for a human to clear rather than silently retried. Scope browser:write.

Path Parameters

NameTypeRequiredDescription
idstringRequiredSteel session id

Request Body

NameTypeRequiredDescription
actionstringRequireddetect | solve
maxAttemptsnumberOptionalSolve attempts before giving up
tabstringOptionalTarget tab id (defaults to the active tab)

Example Request

curl -X POST "https://scrappy.gg/api/browser/sessions/{id}/captcha" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"action": "string"
}'

Responses

200Result
{
"success": true,
"data": {
"detected": true,
"rendered": true,
"solved": false
}
}
POST/api/browser/sessions/{id}/sleep
Auth Required

Sleep Session

Capture the session's state and release its browser, returning a snapshot id. Stops the per-minute meter while keeping the login: wake restores cookies and the first tab's URL into a fresh session. Use it for a session a person will come back to rather than paying for an idle browser. Scope browser:write.

Path Parameters

NameTypeRequiredDescription
idstringRequiredSteel session id

Example Request

curl -X POST "https://scrappy.gg/api/browser/sessions/{id}/sleep" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Slept
{
"success": true,
"data": {
"snapshotId": "snp_123",
"released": true
}
}
POST/api/browser/sessions/{id}/release
Auth Required

Release (beacon)

Release the session, shaped for navigator.sendBeacon — a page being closed cannot reliably finish a DELETE, and an abandoned session bills per minute until the reaper catches it. Prefer DELETE /sessions/{id} everywhere a normal request can complete. Scope browser:write.

Path Parameters

NameTypeRequiredDescription
idstringRequiredSteel session id

Example Request

curl -X POST "https://scrappy.gg/api/browser/sessions/{id}/release" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Released
{
"released": true
}
GET/api/browser/sessions/{id}/devtools-stream
Auth Required

Console + Network (SSE)

Server-sent event stream of console and network entries as they happen, rather than the buffered snapshot the other endpoints return. Use it to watch a page while it is doing something; use /devtools when diagnosing after the fact. Returns text/event-stream, so it is read with an EventSource rather than a JSON client, and it authenticates by bearer token or session cookie like every other browser route. Scope browser:read.

Path Parameters

NameTypeRequiredDescription
idstringRequiredSteel session id

Query Parameters

NameTypeRequiredDescription
tabstringOptionalTarget tab id (defaults to the active tab)

Example Request

curl -X GET "https://scrappy.gg/api/browser/sessions/{id}/devtools-stream" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Event stream
{
"event": "console",
"data": {
"type": "error",
"text": "Uncaught TypeError"
}
}
POST/api/browser/sessions/{id}/type
Auth Required

Type

Type text into an input, textarea or contenteditable, identified by a ref from read-page or a CSS selector. Prefer ref: it names the element directly, so nothing has to be guessed and it cannot silently target the wrong node after a re-render. The value is set through the element's own setter and an input event is dispatched, so React and other frameworks that ignore a raw value assignment still see the change, and the response reports the value that actually settled rather than the one that was sent — a masked, formatted or rejected field will differ, and that difference is the useful part. clear:false appends instead of replacing. Scope browser:write.

Path Parameters

NameTypeRequiredDescription
idstringRequiredSteel session id

Request Body

NameTypeRequiredDescription
refstringOptionalRef from read-page (ref_<n>) — types into exactly that node. Use instead of selector
selectorstringOptionalCSS selector of the input/textarea (or use ref)
textstringRequiredText to type (max 10000 chars)
clearbooleanOptionalReplace the existing value (default true); false appends
piercebooleanOptionalAlso search inside open shadow roots. Selector path only
tabstringOptionalTarget tab id (defaults to the active tab)

Example Request

curl -X POST "https://scrappy.gg/api/browser/sessions/{id}/type" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"text": "string"
}'

Responses

200Typed
{
"success": true,
"data": {
"typed": true,
"ref": "ref_18",
"value": "Jane Doe"
}
}
422Element not found
{
"success": false,
"error": {
"code": "ELEMENT_NOT_FOUND",
"message": "Element for ref_18 is no longer on the page — call read-page again for fresh refs"
}
}
POST/api/browser/sessions/{id}/read-page
Auth Required

Read Page

The page as an agent should see it: Chrome's own accessibility tree, flattened, with a stable handle per node. Every other way to find an element here starts from a CSS selector the caller has to guess, and a guess fails quietly — it matches a different element after a re-render, or matches nothing because the control is a styled div rather than a real input, or expects an id where a name was given. Each node returns role, accessible name, value, state (disabled, checked, expanded, required, invalid, readonly, selected) and depth. Roles and names come from Chrome's own accessibility computation, so aria-label, label[for], alt text, title and content are already accounted for — the same thing a screen reader would say. `ref` is `ref_<backendNodeId>` and can be passed straight to the click endpoint. Backend node ids are browser-wide rather than scoped to the connection that produced them, so a ref stays valid without any server-side registry; it dies when the page re-renders that node, and a click will say so rather than clicking stale coordinates. Scope browser:read.

Path Parameters

NameTypeRequiredDescription
idstringRequiredSteel session id

Request Body

NameTypeRequiredDescription
interactiveOnlybooleanOptionalOnly actionable roles — buttons, links, inputs (default false)
maxNodesnumberOptionalCap on returned nodes, 1-2000 (default 500)
tabstringOptionalTarget tab id (defaults to the active tab)

Example Request

curl -X POST "https://scrappy.gg/api/browser/sessions/{id}/read-page" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"

Responses

200Accessibility tree
{
"success": true,
"data": {
"nodes": [
{
"ref": "ref_18",
"role": "textbox",
"name": "Client",
"depth": 4,
"required": true
},
{
"ref": "ref_23",
"role": "button",
"name": "Save",
"depth": 4,
"disabled": true
}
],
"count": 2,
"truncated": false,
"treeSize": 214
}
}
POST/api/browser/sessions/{id}/find
Auth Required

Find Elements

Locate elements by what they say, and get refs back. Read Page already returns everything actionable, but 'everything' on a real page is hundreds of nodes, and an agent that only wants the Sign in button pays for the whole tree — in tokens, and in the mistakes that come from reading a long list. This asks Chrome the same question and returns only the rows that answer it, ranked: the accessible name first, then value, description and role. `role` is a filter rather than a preference, which is what stops a query from returning the heading above the button you meant. Refs are the same ref_<backendNodeId> handles Read Page hands out, so a match goes straight to click, type, hover, element-info or screenshot. No matches is a success, not a 404 — the page does not say that, and the next move is a full read-page rather than a retry. Scope browser:read.

Path Parameters

NameTypeRequiredDescription
idstringRequiredSteel session id

Request Body

NameTypeRequiredDescription
querystringRequiredWhat you would say to point at the element, e.g. 'Sign in'
rolestringOptionalRestrict to one accessibility role: button, link, textbox, checkbox, …
interactiveOnlybooleanOptionalOnly actionable roles (default false)
limitnumberOptionalMax matches, 1-50 (default 10)
maxNodesnumberOptionalHow much of the tree to search, 1-5000 (default 2000)
tabstringOptionalTarget tab id (defaults to the active tab)

Example Request

curl -X POST "https://scrappy.gg/api/browser/sessions/{id}/find" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "search term"
}'

Responses

200Ranked matches
{
"success": true,
"data": {
"matches": [
{
"ref": "ref_31",
"role": "button",
"name": "Sign in",
"depth": 5,
"score": 108,
"matchedOn": "name"
},
{
"ref": "ref_12",
"role": "link",
"name": "Sign in with Google",
"depth": 4,
"score": 76,
"matchedOn": "name"
}
],
"count": 2,
"searched": 214,
"truncated": false,
"treeSize": 402
}
}
POST/api/browser/sessions/{id}/batch
Auth Required

Batch Actions

Run several session actions in one request, in order. This is the biggest speed-up available over this API: every other endpoint is a full round trip — network, auth, ownership lookup — and a login or checkout flow is a dozen of them, all spent waiting on a browser that is already sitting there. Steps run sequentially and never concurrently, because they act on one page where order is the meaning. Each step is charged to the rate-limit bucket its own endpoint uses, so this is a latency win rather than a quota loophole, and each step runs the *same* handler that endpoint runs — nothing here is a second implementation that could drift. stopOnError defaults to true: typing step 5 after step 4 failed to find its field types into whatever had focus instead, which is how a password ends up in a search box. Steps that never ran come back under `skipped`. Supported actions: navigate, click, type, press, hover, scroll, select, check, drag_drop, paste, wait, wait_for_selector, wait_for_network, wait_for_function, read_page, find, element_info, screenshot, record, scrape, scrape_mode, evaluate, history, viewport, iframe, dialog, intercept, captcha, pdf. `wait` exists only here — { action: 'wait', ms: 400 } is how you pause for a menu without paying for another round trip. Upload, the session lifecycle routes and cookies/storage are deliberately absent. Scope browser:write.

Path Parameters

NameTypeRequiredDescription
idstringRequiredSteel session id

Request Body

NameTypeRequiredDescription
stepsarrayRequiredOrdered steps, each { action, ...that action's own parameters } minus the session id (1-25)
stopOnErrorbooleanOptionalStop at the first failing step (default true)

Example Request

curl -X POST "https://scrappy.gg/api/browser/sessions/{id}/batch" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"steps": []
}'

Responses

200Per-step results
{
"success": true,
"data": {
"results": [
{
"index": 0,
"action": "navigate",
"ok": true,
"status": 200,
"data": {
"url": "https://example.com/login"
}
},
{
"index": 1,
"action": "type",
"ok": false,
"status": 422,
"error": {
"code": "ELEMENT_NOT_FOUND",
"message": "Element not found: #email"
}
}
],
"skipped": [
{
"index": 2,
"action": "click",
"reason": "step 1 (type) failed"
}
],
"completed": 2,
"failed": 1,
"stoppedAt": 1,
"elapsedMs": 1840
}
}
POST/api/browser/sessions/{id}/fill-form
Auth Required

Fill Form

Set every field of a form — and optionally submit it — in one request. Batch can already express this as a list of type steps and does the same thing underneath; this exists because a form is the most common multi-step interaction there is, and spelling it as fields removes the two mistakes that actually happen: forgetting to clear a field that already has a value, and reaching for type on a checkbox. The control type is inferred from the value — a string types, a boolean checks, kind:'select' picks an option — and each field reports its own result, so a failure names the field rather than a step index. If a field fails the submit is skipped: a half-filled form that gets posted anyway is worse than one that does not, because the site records the partial attempt and some rate-limit the retry. Scope browser:write.

Path Parameters

NameTypeRequiredDescription
idstringRequiredSteel session id

Request Body

NameTypeRequiredDescription
fieldsarrayRequiredFields in order (1-50): { ref | selector, value: string | boolean, kind?: text | select | check, clear?, pierce? }
submitobjectOptional{ ref | selector } to click once every field is set
stopOnErrorbooleanOptionalStop at the first failing field and skip the submit (default true)
tabstringOptionalTarget tab id (defaults to the active tab)

Example Request

curl -X POST "https://scrappy.gg/api/browser/sessions/{id}/fill-form" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"fields": []
}'

Responses

200Per-field results
{
"success": true,
"data": {
"fields": [
{
"index": 0,
"target": "#email",
"action": "type",
"ok": true,
"status": 200,
"data": {
"typed": true,
"value": "a@b.c"
}
},
{
"index": 1,
"target": "#terms",
"action": "check",
"ok": true,
"status": 200,
"data": {
"checked": true
}
}
],
"skipped": [],
"filled": 2,
"failed": 0,
"stoppedAt": null,
"submitted": {
"action": "click",
"ok": true,
"status": 200
},
"submitSkipped": false
}
}
POST/api/browser/sessions/{id}/element-info
Auth Required

Element Info

Inspect one element: tag, text, value, visibility, enabled/checked state, bounding box and attributes. It also answers two questions a selector alone cannot. When the element is disabled, `gating` reports what is holding it — which fields in the surrounding form are empty, which are empty AND required, which fail constraint validation, and whether it is disabled by the `disabled` property or only by aria (different causes, different fixes). A vendor form can gate its submit on a field it never marks required and renders no error for, so `emptyFields` is often the only available clue and `emptyRequiredFields` will be empty. Separately, `contentDigest` fingerprints what the element currently contains, so reading it before and after an action proves the action CHANGED something: an unchanged digest is proof of a no-op, which a presence check reports as success. `rowCount` is the coarser companion that survives cosmetic churn — a ticking timestamp moves the digest but not the count. Scope browser:read.

Path Parameters

NameTypeRequiredDescription
idstringRequiredSteel session id

Request Body

NameTypeRequiredDescription
refstringOptionalRef from read-page (ref_<n>) — inspect exactly that node. Use instead of selector
selectorstringOptionalCSS selector of the element to inspect (or use ref)
piercebooleanOptionalAlso search inside open shadow roots
tabstringOptionalTarget tab id (defaults to the active tab)

Example Request

curl -X POST "https://scrappy.gg/api/browser/sessions/{id}/element-info" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"

Responses

200Element details
{
"success": true,
"data": {
"tag": "button",
"text": "Save",
"isVisible": true,
"isEnabled": false,
"gating": {
"disabledVia": "property",
"scope": "noteCard",
"emptyFields": [
{
"name": "CLIENT",
"id": "client",
"type": "text",
"label": "Client",
"required": false,
"empty": true
}
],
"emptyRequiredFields": [],
"invalidFields": [],
"formValid": true
},
"contentDigest": "8f3a21c4:412",
"descendantCount": 87,
"rowCount": 12
}
}
404Element not found
{
"success": false,
"error": {
"code": "NOT_FOUND",
"message": "Element not found: #save"
}
}
POST/api/browser/sessions/{id}/click
Auth Required

Click

Click by CSS selector or coordinates. button:'right' opens context menus; clickCount:2 double-clicks, which is how many data grids enter cell-edit mode. pierce:true also searches open shadow roots — document.querySelector does not cross shadow boundaries, so a control inside a web component is invisible to a normal selector even though it is plainly on screen; set pierce when a selector cannot find something you can see. pierce is also accepted by type, element-info and wait-for-selector. holdMs turns the click into a press and hold, which is the only way past a 'Press & Hold to confirm you are a human' widget: those start a timer on pointerdown and pass only if pointerup arrives after their threshold, so an ordinary click — which releases in the same millisecond it presses — always fails. Note that clearing the gesture is not the same as clearing the challenge; these systems also score the session server-side once the hold completes. Scope browser:write.

Path Parameters

NameTypeRequiredDescription
idstringRequiredSteel session id

Request Body

NameTypeRequiredDescription
refstringOptionalRef from read-page (ref_<n>). Names the element directly — preferred over a selector, which can silently match the wrong node
selectorstringOptionalCSS selector (or use ref, or x/y)
xnumberOptionalX coordinate, with y
ynumberOptionalY coordinate, with x
buttonstringOptionalleft (default) | right | middle
clickCountnumberOptional2 for a double-click (default 1)
piercebooleanOptionalAlso search inside open shadow roots
holdMsnumberOptionalHold the button down this many ms before releasing (0–30000, default 0). Use ~6000–10000 for a 'Press & Hold' challenge; ignores clickCount
jitternumberOptionalMax px of random pointer drift while held (0–10, default 2). 0 holds perfectly still, which reads as synthetic
modifiersnumberOptionalModifier bitmask held for the click — 1 Alt, 2 Ctrl, 4 Meta, 8 Shift, summed (0–15, default 0). Any non-zero value forces the real mouse-event path, because a plain left click goes through the element's own .click() and cannot carry a modifier. This is how you ctrl/shift-click to extend a selection

Example Request

curl -X POST "https://scrappy.gg/api/browser/sessions/{id}/click" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"

Responses

200Clicked
{
"success": true,
"data": {
"clicked": true,
"tag": "button",
"button": "left",
"clickCount": 1,
"modifiers": 0
}
}
200Held
{
"success": true,
"data": {
"clicked": true,
"tag": "button",
"button": "left",
"holdMs": 8000,
"jitter": 2,
"x": 500,
"y": 328
}
}
422Not found
{
"success": false,
"error": {
"code": "ELEMENT_NOT_FOUND",
"message": "Element not found: #save (try pierce:true if it is inside a web component)"
}
}
POST/api/browser/sessions/{id}/upload
Auth Required

Upload a File

Attach a file to a file input. Send the bytes one of three ways, and name the target input one of two ways. Files go to Steel's session storage and attach via DOM.setFileInputFiles, so bytes never pass through a JS expression — the old approach capped uploads near 1MB. multipart/form-data carries raw bytes (9MB — the same ~10MB body limit as content, but none of it spent on base64; repeat the file field for a multi-select input); sourceUrl lets Steel fetch the file itself (100MB, SSRF-checked like navigate) and is the path for anything larger; content is base64 and is capped near 7MB — it accepts a plain base64 string or a full data:...;base64,... URL, tolerates the line wrapping Ruby's Base64.encode64 and Python's encodestring produce, and refuses anything outside the base64 alphabet rather than decoding it into a corrupt file (the naive decode never throws: it turns a data URL's prefix into garbage welded onto the front of the real bytes and reports success). Exactly one source per request — two is a 400 rather than a silent choice. After attaching, the input is read back and the response reports what it actually holds: Chrome accepts a path that does not exist (leaving a 0-byte file of that name) and a file list longer than a non-multiple input can take (keeping one), both without complaint, so those come back as ATTACH_INCOMPLETE instead of a false success. Scope browser:write.

Path Parameters

NameTypeRequiredDescription
idstringRequiredSteel session id

Request Body

NameTypeRequiredDescription
selectorstringOptionalCSS selector of the file input. Required unless backendNodeId is given
backendNodeIdnumberOptionalCDP backend node id of the input, from an intercepted file chooser. Names the exact input the user clicked, including hidden ones behind custom Browse buttons that no selector would find. Valid across CDP connections
filenamestringOptionalName the page should see. Required unless a file is uploaded, which carries its own
filefileOptionalmultipart only — raw bytes, up to 9MB total. Repeat the field to fill an <input multiple> (max 10)
sourceUrlstringOptionalURL Steel fetches directly — up to 100MB, enforced upstream while streaming. Prefer a short-lived signed URL
contentstringOptionalBase64 file bytes — practical ceiling ~7MB. A data:<type>;base64,... URL, line-wrapped base64 and base64url are all accepted
mimeTypestringOptionalInferred from the data URL, then the filename extension, before falling back to application/octet-stream — some upload widgets reject an octet-stream PDF client-side

Example Request

curl -X POST "https://scrappy.gg/api/browser/sessions/{id}/upload" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json or multipart/form-data"

Responses

200Attached
{
"success": true,
"data": {
"uploaded": true,
"filename": "signed-psa.pdf",
"bytes": 3088632,
"via": "sourceUrl",
"verified": true,
"attached": [
{
"name": "signed-psa.pdf",
"size": 3088632
}
]
}
}
422Stored, but the page did not take it
{
"success": false,
"error": {
"code": "ATTACH_INCOMPLETE",
"message": "Upload stored but attached as 0 bytes: signed-psa.pdf — the stored file was empty or its path was wrong"
}
}
413Too large — use a bigger-capacity path
{
"success": false,
"error": {
"code": "FILE_TOO_LARGE"
}
}
400Unsafe sourceUrl
{
"success": false,
"error": {
"code": "UNSAFE_URL",
"message": "Navigation to private IP addresses is not allowed"
}
}
DELETE/api/browser/tabs
Auth Required

Close or Activate a Tab

Close a tab, or bring one to the foreground with action:'activate'. Web apps open tabs constantly (OAuth consent, payment windows, target=_blank), and without this an agent can create tabs but never dismiss them, so popups accumulate and keep consuming session resources. Scope browser:write.

Request Body

NameTypeRequiredDescription
sessionIdstringRequiredSteel session id
targetIdstringRequiredCDP target id from GET /api/browser/tabs
actionstringOptionalclose (default) | activate

Example Request

curl -X DELETE "https://scrappy.gg/api/browser/tabs" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"sessionId": "abc123",
"targetId": "abc123"
}'

Responses

200Closed
{
"closed": true,
"targetId": "AE77D31F…"
}
POST/api/browser/sessions/{id}/wait-for-selector
Auth Required

Wait for Selector

Block until an element is visible (default), present, hidden, or detached. Essential between steps of a write flow — after clicking Edit wait for the form, after saving wait for the confirmation. Polls server-side, so it costs one request instead of many. Returns found:false rather than erroring on timeout. Scope browser:write.

Path Parameters

NameTypeRequiredDescription
idstringRequiredSteel session id

Request Body

NameTypeRequiredDescription
selectorstringRequiredCSS selector to wait for
statestringOptionalpresent | visible | hidden | detached (default visible)
timeoutnumberOptionalMax wait in ms (500–60000, default 10000)

Example Request

curl -X POST "https://scrappy.gg/api/browser/sessions/{id}/wait-for-selector" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"selector": "string"
}'

Responses

200Settled or timed out
{
"success": true,
"data": {
"found": true,
"state": "visible",
"waitedMs": 340
}
}
POST/api/browser/sessions/{id}/wait-for-function
Auth Required

Wait for Function

Block until a JavaScript expression evaluates truthy in the page. This is the wait for 'the app is ready', which no selector can express: server-rendered markup is present and visible the moment the document commits, while the bundle that gives it behaviour has not run — so waiting for the button and then clicking it fires a handler that throws a ReferenceError, and every layer still reports success. Wait for what the handler needs instead, e.g. typeof clientModal !== 'undefined'. An expression that THROWS counts as not-yet-true, which is what makes this usable while a page is still booting (an evaluate racing a navigation's context swap fails too); a timeout carries lastError when the final attempt threw — the page's own message, since the expression runs inside a try/catch in the page (CDP alone reports the literal string 'Uncaught' for every failure). Values come back JSON-serialised and capped at 4096 chars, so a predicate cannot ship megabytes per poll. Must be synchronous — evaluation does not await promises, so an async predicate would be truthy on the first poll; that is rejected with 422 INVALID_PREDICATE rather than answered wrongly. Predicate on a boolean (!!document.querySelector('#row')), since values come back JSON-serialised. Polls server-side: one request and one rate-limit charge instead of a dozen evaluate round trips. Returns satisfied:false rather than erroring on timeout. Scope browser:write.

Path Parameters

NameTypeRequiredDescription
idstringRequiredSteel session id

Request Body

NameTypeRequiredDescription
expressionstringRequiredJavaScript expression (not a statement body) polled until truthy, max 10000 chars
timeoutnumberOptionalMax wait in ms (500–60000, default 10000)
pollIntervalnumberOptionalMs between evaluations (50–5000, default 200)
requiredbooleanOptionalFail with 422 WAIT_TIMEOUT instead of answering satisfied:false — needed inside /batch, where a soft timeout is a 200 and stopOnError will not stop for it
tabstringOptionalTarget tab (CDP target id); defaults to the most recent page

Example Request

curl -X POST "https://scrappy.gg/api/browser/sessions/{id}/wait-for-function" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"expression": "string"
}'

Responses

200Satisfied
{
"success": true,
"data": {
"satisfied": true,
"value": true,
"waitedMs": 480,
"polls": 3
}
}
200Timed out
{
"success": true,
"data": {
"satisfied": false,
"timedOut": true,
"value": null,
"waitedMs": 10000,
"polls": 50,
"lastError": "clientModal is not defined"
}
}
422The expression returned a Promise
{
"success": false,
"error": {
"code": "INVALID_PREDICATE",
"message": "Expression returned a Promise."
}
}
POST/api/browser/sessions/{id}/dialog
Auth Required

Suppress JavaScript Dialogs

Replace alert/confirm/prompt/beforeunload with no-ops for the current page. Call BEFORE a click that might open one: an open dialog blocks its tab, so a Save or Delete firing confirm() makes every later command on that tab time out. Per-document — re-apply after each navigation. Answering an already-open dialog is not offered because a blocked renderer stops servicing CDP commands; if a tab sticks, use another tab or release the session. Scope browser:write.

Path Parameters

NameTypeRequiredDescription
idstringRequiredSteel session id

Request Body

NameTypeRequiredDescription
confirmReturnsbooleanOptionalWhat confirm() returns (default true = proceed)
promptTextstringOptionalWhat prompt() returns (default null)

Example Request

curl -X POST "https://scrappy.gg/api/browser/sessions/{id}/dialog" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"

Responses

200Suppressed
{
"success": true,
"data": {
"suppressed": true,
"confirmReturns": true
}
}
POST/api/browser/sessions/{id}/drag-drop
Auth Required

Drag and Drop

Drag from one point to another. Each end can be an element (ref or selector) or raw coordinates: use sourceX/sourceY and targetX/targetY for a slider dragged to a position, a resize gutter, or an SVG handle that has a meaningful drop point but no element under it to match. Pass `path` — a list of points — to trace an arbitrary gesture instead (drawing on a canvas, panning a map, swiping a custom control), where the path itself is the input and a straight line between two boxes would be the wrong motion; it overrides source/target. Moves through intermediate points so drag libraries register the motion. Scope browser:write.

Path Parameters

NameTypeRequiredDescription
idstringRequiredSteel session id

Request Body

NameTypeRequiredDescription
sourceSelectorstringOptionalCSS selector of the element to drag (or sourceRef / sourceX+sourceY)
sourceRefstringOptionalRef from read-page for the drag source
targetSelectorstringOptionalCSS selector of the drop target (or targetRef / targetX+targetY)
targetRefstringOptionalRef from read-page for the drop target
sourceXnumberOptionalX of the drag start, when the source is not an element (with sourceY)
sourceYnumberOptionalY of the drag start, when the source is not an element (with sourceX)
targetXnumberOptionalX of the drop point, when the target is not an element (with targetY)
targetYnumberOptionalY of the drop point, when the target is not an element (with targetX)
patharrayOptionalPoly-line of { x, y } points (2–100) to drag along: press at the first, glide through the rest, release at the last. Overrides source/target
stepsnumberOptionalIntermediate mouse moves per segment (1–20, default 5)

Example Request

curl -X POST "https://scrappy.gg/api/browser/sessions/{id}/drag-drop" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"

Responses

200Dragged
{
"success": true,
"data": {
"dragged": true,
"from": {
"x": 120,
"y": 340
},
"to": {
"x": 480,
"y": 340
}
}
}
200Dragged along a path
{
"success": true,
"data": {
"dragged": true,
"from": {
"x": 100,
"y": 100
},
"to": {
"x": 300,
"y": 260
},
"points": 12
}
}
422Element not found
{
"success": false,
"error": {
"code": "ELEMENT_NOT_FOUND",
"message": "Element not found: #handle"
}
}
POST/api/browser/sessions/{id}/paste
Auth Required

Paste

Paste text and/or an image into an element — the round trip a Ctrl+V makes, for the case a file upload cannot cover. A rich editor (a GitHub comment, a chat composer, a doc) takes a pasted screenshot straight off the clipboard with no file input to target, so the upload endpoint has nothing to attach to; this decodes the image and delivers it as a real paste event with a DataTransfer the editor reads, the same shape most rich editors (ProseMirror, Slate, Draft.js) consume. Give imageBase64 (raw base64 or a data: URL), text, or both. Targets a ref, a selector, or — with neither — the currently focused element. The one thing it cannot satisfy is an editor that gates on a genuine OS-level (trusted) paste, since there is no OS clipboard to read headless. Scope browser:write.

Path Parameters

NameTypeRequiredDescription
idstringRequiredSteel session id

Request Body

NameTypeRequiredDescription
refstringOptionalRef from read-page (ref_<n>) to paste into
selectorstringOptionalCSS selector of the paste target. Omit ref and selector both to paste into the focused element
textstringOptionalPlain text to paste (max 100000). Provide text, imageBase64, or both
imageBase64stringOptionalImage as base64 or a data:<type>;base64,... URL. Decoded to a File and delivered on the paste event's clipboard data
mimeTypestringOptionalImage MIME type (default image/png; inferred from a data URL)
tabstringOptionalTarget tab id (defaults to the active tab)

Example Request

curl -X POST "https://scrappy.gg/api/browser/sessions/{id}/paste" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"

Responses

200Pasted
{
"success": true,
"data": {
"pasted": true,
"consumed": true,
"hasText": false,
"hasImage": true,
"selector": "div.comment-body"
}
}
400Bad image
{
"success": false,
"error": {
"code": "INVALID_IMAGE",
"message": "imageBase64 is not valid base64"
}
}
422No target
{
"success": false,
"error": {
"code": "ELEMENT_NOT_FOUND",
"message": "No element is focused — pass ref or selector"
}
}
POST/api/browser/sessions/{id}/wait-for-network
Auth Required

Wait for Network Idle

Block until the page stops issuing requests. Essential for single-page apps that render before their data arrives. Polls server-side, so this costs one request instead of dozens of evaluate round-trips. Returns idle:false on timeout rather than erroring. Scope browser:write.

Path Parameters

NameTypeRequiredDescription
idstringRequiredSteel session id

Request Body

NameTypeRequiredDescription
idleTimenumberOptionalQuiet period in ms before declaring idle (100–10000, default 500)
timeoutnumberOptionalMax total wait in ms (1000–60000, default 15000)

Example Request

curl -X POST "https://scrappy.gg/api/browser/sessions/{id}/wait-for-network" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"

Responses

200Settled or timed out
{
"success": true,
"data": {
"idle": true,
"waitedMs": 1240,
"requestCount": 37
}
}
POST/api/browser/sessions/{id}/intercept
Auth Required

Intercept / Block Requests

Block requests by resource type or URL pattern so pages load faster and scrapes cost less bandwidth. action:'stop' clears all blocking. To observe traffic rather than block it, use the network endpoint. Scope browser:write.

Path Parameters

NameTypeRequiredDescription
idstringRequiredSteel session id

Request Body

NameTypeRequiredDescription
actionstringRequiredstart | stop
blockTypesarrayOptionalimage, media, font, stylesheet, script, tracking
blockPatternsarrayOptionalRaw URL globs, e.g. "*ads.example.com*" (max 100)

Example Request

curl -X POST "https://scrappy.gg/api/browser/sessions/{id}/intercept" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"action": "string"
}'

Responses

200Blocking updated
{
"success": true,
"data": {
"intercepting": true,
"count": 8
}
}
400Nothing specified to block
{
"success": false,
"error": {
"code": "NOTHING_TO_BLOCK",
"message": "Provide at least one of blockTypes or blockPatterns when starting interception"
}
}
POST/api/browser/sessions/{id}/download
Auth Required

Resolve Download

Resolve the file URL behind a link or button (CSV exports, PDFs, attachments) so you can fetch it directly. Scope browser:write.

Path Parameters

NameTypeRequiredDescription
idstringRequiredSteel session id

Request Body

NameTypeRequiredDescription
selectorstringRequiredCSS selector of the download trigger
timeoutnumberOptionalTimeout in ms (1000–30000, default 5000)

Example Request

curl -X POST "https://scrappy.gg/api/browser/sessions/{id}/download" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"selector": "string"
}'

Responses

200Download resolved
{
"success": true,
"data": {
"url": "https://example.com/export.csv",
"filename": "export.csv"
}
}
POST/api/browser/sessions/{id}/pdf
Auth Required

Render PDF

Render the current page to a PDF and return it base64-encoded. Scope browser:write.

Path Parameters

NameTypeRequiredDescription
idstringRequiredSteel session id

Request Body

NameTypeRequiredDescription
landscapebooleanOptionalLandscape orientation
printBackgroundbooleanOptionalInclude background graphics
scalenumberOptionalScale factor (0.1–2)
paperWidthnumberOptionalPaper width in inches
paperHeightnumberOptionalPaper height in inches

Example Request

curl -X POST "https://scrappy.gg/api/browser/sessions/{id}/pdf" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"

Responses

200PDF rendered
{
"success": true,
"data": {
"base64": "JVBERi0xLjQK…",
"pageCount": 3
}
}
GET/api/browser/sessions/{id}/viewer
Auth Required

Session Viewer (HTML)

Returns an HTML page (not JSON) embedding the live browser. Auth by ?token= viewer token (the only option for a cookie-less external iframe), session cookie, or sk_ API key. Scope browser:read.

Path Parameters

NameTypeRequiredDescription
idstringRequiredSteel session id

Query Parameters

NameTypeRequiredDescription
tokenstringOptionalViewer token from the viewer-token endpoint (required for external cross-origin embedding)

Example Request

curl -X GET "https://scrappy.gg/api/browser/sessions/{id}/viewer" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Viewer HTML
{
"contentType": "text/html"
}
POST/api/browser/sessions/{id}/viewer-token
Auth Required

Viewer Token

Mint a short-lived (~5 min) signed token bound to {sessionId, userId} that authenticates a cookie-less iframe to the viewer and its screencast WebSocket. Call from a trusted backend (scope browser:read); never expose the API key to a browser. Returns an absolute viewerUrl with the token embedded.

Path Parameters

NameTypeRequiredDescription
idstringRequiredSteel session id

Example Request

curl -X POST "https://scrappy.gg/api/browser/sessions/{id}/viewer-token" \
-H "Authorization: Bearer YOUR_API_KEY"

Responses

200Token minted
{
"token": "eyJ2…",
"expiresAt": 1753104000000,
"expiresInMs": 300000,
"viewerUrl": "https://scrappy.gg/api/browser/sessions/b1f2…/viewer?token=eyJ2…"
}

Infrastructure

Health checks and monitoring endpoints

1 endpoint
GET/api/health
Public

Health Check

Check the health status of all services

Example Request

curl -X GET "https://scrappy.gg/api/health"

Responses

200Health status
{
"status": "healthy",
"services": {
"database": {
"status": "healthy",
"latency": 5
},
"redis": {
"status": "healthy",
"latency": 2
}
}
}

Need help? Check out our User Guides or contact support.