Complete API Reference
All 215 endpoints across 26 sections
Table of Contents
Authentication
User authentication and session management
1 endpoint/api/auth/signupSign Up
Register a new user account
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| string | Required | User email address | |
| password | string | Required | Password (minimum 8 characters) |
| name | string | Optional | User display name |
| termsAccepted | boolean | Required | Must be true to accept terms |
| privacyAccepted | boolean | Required | Must be true to accept privacy policy |
| marketingConsent | boolean | Optional | Opt-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
{"success": true,"data": {"user": {"id": "abc123","email": "user@example.com"}}}
{"success": false,"error": {"code": "VALIDATION_ERROR","message": "Invalid email format"}}
{"success": false,"error": {"code": "RATE_LIMIT_EXCEEDED","message": "Too many attempts"}}
Users
User profile and account management
2 endpoints/api/usersGet 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
{"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}}}
{"success": false,"error": {"code": "UNAUTHORIZED","message": "Authentication required"}}
/api/users/passwordChange Password
Update the authenticated user's password
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| currentPassword | string | Required | Current password for verification |
| newPassword | string | Required | New 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
{"success": true}
{"success": false,"error": {"code": "INVALID_PASSWORD","message": "Current password is incorrect"}}
Consent Management
GDPR consent preferences and history
3 endpoints/api/users/consentGet Consent Status
Get current consent preferences for the authenticated user
Example Request
curl -X GET "https://scrappy.gg/api/users/consent" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"success": true,"data": {"termsAccepted": true,"termsAcceptedAt": "2024-01-01T00:00:00Z","privacyAccepted": true,"privacyAcceptedAt": "2024-01-01T00:00:00Z","marketingConsent": false}}
/api/users/consentUpdate Consent
Update consent preferences (currently supports marketing consent)
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| marketingConsent | boolean | Required | Whether to receive marketing emails |
Example Request
curl -X PATCH "https://scrappy.gg/api/users/consent" \-H "Authorization: Bearer YOUR_API_KEY" \-H "Content-Type: application/json" \-d '{"marketingConsent": true}'
Responses
{"success": true,"data": {"marketingConsent": true,"updatedAt": "2024-01-01T00:00:00Z"}}
/api/users/consent/historyGet Consent History
Get audit trail of consent changes
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| limit | number | Optional | Number of records to returnDefault: 50 |
| offset | number | Optional | Number of records to skipDefault: 0 |
Example Request
curl -X GET "https://scrappy.gg/api/users/consent/history" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"success": true,"data": {"history": [{"id": "abc123","action": "CONSENT_UPDATED","details": {"marketingConsent": true},"createdAt": "2024-01-01T00:00:00Z"}],"pagination": {"total": 1,"limit": 50,"offset": 0}}}
Data Export
GDPR subject access requests - export your data
3 endpoints/api/users/exportRequest Data Export
Request an export of all your data (JSON or CSV format)
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| format | string | Optional | Export 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
{"success": true,"data": {"requestId": "abc123","status": "PENDING","downloadExpiry": "2024-01-08T00:00:00Z"}}
{"success": false,"error": {"code": "RATE_LIMIT_EXCEEDED","message": "Maximum 3 exports per week"}}
/api/users/export/statusGet 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
{"success": true,"data": {"exports": [{"requestId": "abc123","status": "COMPLETED","format": "JSON","createdAt": "2024-01-01T00:00:00Z","downloadExpiry": "2024-01-08T00:00:00Z"}]}}
/api/users/export/downloadDownload Export
Download a completed data export (7-day download window)
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| requestId | string | Required | The export request ID |
Example Request
curl -X GET ?requestId=abc123"https://scrappy.gg/api/users/export/download" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"note": "Returns file attachment"}
{"success": false,"error": {"code": "NOT_FOUND","message": "Export not found or expired"}}
Account Deletion
GDPR right to erasure - delete your account
4 endpoints/api/users/deletionRequest Account Deletion
Request deletion of your account. A confirmation email will be sent.
Example Request
curl -X POST "https://scrappy.gg/api/users/deletion" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"success": true,"data": {"expiresAt": "2024-01-02T00:00:00Z"}}
{"success": false,"error": {"code": "RATE_LIMIT_EXCEEDED","message": "One deletion request per day"}}
/api/users/deletion/confirmConfirm Account Deletion
Confirm account deletion using the token from email. Starts 30-day grace period.
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| token | string | Required | Confirmation 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
{"success": true,"data": {"scheduledAt": "2024-01-31T00:00:00Z","gracePeriodDays": 30}}
{"success": false,"error": {"code": "INVALID_TOKEN","message": "Token is invalid or expired"}}
/api/users/deletion/cancelCancel 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
{"success": true,"message": "Account deletion cancelled"}
{"success": false,"error": {"code": "NO_PENDING_DELETION","message": "No deletion request to cancel"}}
/api/users/deletion/statusGet 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
{"success": true,"data": {"status": "scheduled","scheduledAt": "2024-01-31T00:00:00Z","gracePeriodDays": 30}}
Projects
Organize sources and leads into projects
5 endpoints/api/projectsList 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
{"success": true,"data": {"projects": [{"id": "abc123","name": "My Project","description": "Project description","color": "#3b82f6","createdAt": "2024-01-01T00:00:00Z"}]}}
/api/projectsCreate Project
Create a new project
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| name | string | Required | Project name |
| description | string | Optional | Project description |
| color | string | Optional | Hex 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
{"success": true,"data": {"project": {"id": "abc123","name": "My Project"}}}
/api/projects/[id]Get Project
Get a specific project by ID
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Project ID |
Example Request
curl -X GET "https://scrappy.gg/api/projects/abc123" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"success": true,"data": {"project": {"id": "abc123","name": "My Project"}}}
{"success": false,"error": {"code": "NOT_FOUND","message": "Project not found"}}
/api/projects/[id]Update Project
Update a project's details
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Project ID |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| name | string | Optional | Project name |
| description | string | Optional | Project description |
| color | string | Optional | Hex 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
{"success": true,"data": {"project": {"id": "abc123","name": "Updated Project"}}}
{"success": false,"error": {"code": "FORBIDDEN","message": "Not authorized to update this project"}}
/api/projects/[id]Delete Project
Delete a project and all associated data
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Project ID |
Example Request
curl -X DELETE "https://scrappy.gg/api/projects/abc123" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"success": true}
{"success": false,"error": {"code": "FORBIDDEN","message": "Not authorized to delete this project"}}
Sources
Manage data sources for lead scraping
10 endpoints/api/sourcesList Sources
Get all sources with optional filtering
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| status | string | Optional | Filter by statusOptions: DRAFT, ACTIVE, ARCHIVED |
| projectId | string | Optional | Filter by project ID |
Example Request
curl -X GET "https://scrappy.gg/api/sources" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"success": true,"data": {"sources": [{"id": "abc123","name": "Example Source","url": "https://example.com","scrapeType": "STATIC","status": "ACTIVE"}]}}
/api/sourcesCreate Source
Create a new source for lead scraping
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| name | string | Required | Source name |
| url | string | Required | URL to scrape |
| scrapeType | string | Required | Type of scrapingOptions: STATIC, DYNAMIC, API |
| selectors | object | Required | CSS/XPath selectors for data extraction |
| description | string | Optional | Source description |
| scheduleType | string | Optional | Scraping 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
{"success": true,"data": {"source": {"id": "abc123","name": "Example Source","status": "DRAFT"}}}
/api/sources/[id]Get Source
Get a specific source by ID
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Source ID |
Example Request
curl -X GET "https://scrappy.gg/api/sources/abc123" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"success": true,"data": {"source": {"id": "abc123","name": "Example Source"}}}
/api/sources/[id]Update Source
Update a source's configuration
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Source ID |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| name | string | Optional | Source name |
| url | string | Optional | URL to scrape |
| selectors | object | Optional | CSS/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
{"success": true,"data": {"source": {"id": "abc123","name": "Updated Source"}}}
/api/sources/[id]Delete Source
Delete a source
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Source ID |
Example Request
curl -X DELETE "https://scrappy.gg/api/sources/abc123" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"success": true}
/api/sources/[id]/activateActivate Source
Activate a draft source to enable scraping
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Source ID |
Example Request
curl -X POST "https://scrappy.gg/api/sources/abc123/activate" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"success": true,"data": {"source": {"id": "abc123","status": "ACTIVE"}}}
{"success": false,"error": {"code": "ALREADY_ACTIVE","message": "Source is already active"}}
/api/sources/discoverDiscover Sources (AI)
Use AI to discover relevant sources based on a search query
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| query | string | Required | Search query (minimum 3 characters) |
| count | number | Optional | Number 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
{"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"}]}}
/api/sources/generateGenerate Sources (AI)
Use AI to generate source suggestions based on a project description
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| projectDescription | string | Required | Project description (minimum 10 characters) |
| targetAudience | string | Optional | Target audience for the leads |
| industry | string | Optional | Industry to focus on |
| location | string | Optional | Geographic location |
| count | number | Optional | Number of sources to generate (1-5)Default: 3 |
| autoSave | boolean | Optional | Automatically 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
{"success": true,"data": {"sources": [{"name": "Industry Directory","url": "https://example.com/directory","description": "Relevant directory for target industry","scrapeType": "STATIC","reasoning": "Matches project requirements"}]}}
{"success": false,"error": {"code": "VALIDATION_ERROR","message": "Project description must be at least 10 characters"}}
/api/sources/batchBatch Create Sources
Create multiple sources at once from AI suggestions or manual input
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| sources | array | Required | Array of source objects to create (1 or more) |
| sources[].name | string | Required | Source name |
| sources[].url | string | Required | URL to scrape |
| sources[].description | string | Optional | Source description |
| sources[].scrapeType | string | Optional | Type of scrapingOptions: STATIC, DYNAMIC, APIDefault: DYNAMIC |
| sources[].selectors | object | Optional | CSS/XPath selectors for data extraction |
| sources[].reasoning | string | Optional | AI 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
{"success": true,"data": {"sources": [{"id": "abc123","name": "Example Source","status": "DRAFT"}],"count": 1}}
{"success": false,"error": {"code": "VALIDATION_ERROR","message": "At least one source is required"}}
/api/sources/analyzeAnalyze Source
Analyze project requirements from a URL or PDF file
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| file | file | Optional | PDF file to analyze (mutually exclusive with url) |
| url | string | Optional | URL 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
{"success": true,"data": {"requirements": {"targetAudience": "Small businesses","industry": "Technology","location": "United States","suggestedSources": []}}}
{"success": false,"error": {"code": "VALIDATION_ERROR","message": "Either file or url is required"}}
Jobs
Scraping job management and monitoring
5 endpoints/api/jobsList Jobs
Get all scraping jobs
Example Request
curl -X GET "https://scrappy.gg/api/jobs" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"success": true,"data": {"jobs": [{"id": "abc123","name": "Scrape Job","status": "COMPLETED","progress": 100,"leadsFound": 150,"createdAt": "2024-01-01T00:00:00Z"}]}}
/api/jobsCreate Job
Create and enqueue a new scraping job
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| name | string | Required | Job name |
| sourceId | string | Required | Source 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
{"success": true,"data": {"job": {"id": "abc123","name": "Scrape Job","status": "PENDING"}}}
/api/jobs/[id]Get Job
Get job details and progress
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Job ID |
Example Request
curl -X GET "https://scrappy.gg/api/jobs/abc123" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"success": true,"data": {"job": {"id": "abc123","status": "RUNNING","progress": 45,"leadsFound": 67}}}
/api/jobs/[id]Cancel/Delete Job
Cancel a running job or delete a completed job
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Job ID |
Example Request
curl -X DELETE "https://scrappy.gg/api/jobs/abc123" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"success": true}
/api/jobs/[id]/retryRetry Job
Retry a failed job
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Job ID |
Example Request
curl -X POST "https://scrappy.gg/api/jobs/abc123/retry" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"success": true,"message": "Job retry started"}
{"success": false,"error": {"code": "INVALID_STATUS","message": "Only failed jobs can be retried"}}
Leads
Lead management and email validation
9 endpoints/api/leadsList Leads
Get all leads with optional filtering
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| status | string | Optional | Filter by status |
| company | string | Optional | Filter by company name |
| jobId | string | Optional | Filter by job ID |
Example Request
curl -X GET "https://scrappy.gg/api/leads" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"success": true,"data": {"leads": [{"id": "abc123","firstName": "John","lastName": "Doe","email": "john@example.com","company": "Example Inc","status": "VALID"}]}}
/api/leadsCreate Lead
Create a lead manually
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| sourceUrl | string | Required | Source URL where lead was found |
| firstName | string | Optional | First name |
| lastName | string | Optional | Last name |
| string | Optional | Email address | |
| phone | string | Optional | Phone number |
| company | string | Optional | Company name |
| jobTitle | string | Optional | Job 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
{"success": true,"data": {"lead": {"id": "abc123","firstName": "John","lastName": "Doe"}}}
/api/leads/[id]Get Lead
Get lead details with job and source info
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Lead ID |
Example Request
curl -X GET "https://scrappy.gg/api/leads/abc123" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"success": true,"data": {"lead": {"id": "abc123","email": "john@example.com"}}}
/api/leads/[id]Update Lead
Update a lead's information
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Lead ID |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| firstName | string | Optional | First name |
| lastName | string | Optional | Last name |
| string | Optional | Email address | |
| status | string | Optional | Lead status |
| tags | array | Optional | Tags for categorization |
| notes | string | Optional | Notes 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
{"success": true,"data": {"lead": {"id": "abc123"}}}
/api/leads/[id]Delete Lead
Delete a lead
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Lead ID |
Example Request
curl -X DELETE "https://scrappy.gg/api/leads/abc123" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"success": true}
/api/leads/[id]/validate-emailValidate Lead Email
Trigger email validation for a specific lead
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Lead ID |
Example Request
curl -X POST "https://scrappy.gg/api/leads/abc123/validate-email" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"success": true,"data": {"status": "VALID","tier": "TIER_1","errors": [],"flags": []}}
{"success": false,"error": {"code": "NO_EMAIL","message": "Lead has no email to validate"}}
/api/leads/bulkBulk Delete Leads
Delete multiple leads at once (max 1000)
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| leadIds | array | Required | Array 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
{"success": true,"data": {"deleted": 50,"failed": 0,"errors": []}}
/api/leads/bulkBulk Update Leads
Update multiple leads at once
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| leadIds | array | Required | Array of lead IDs to update |
| updates | object | Required | Updates 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
{"success": true,"data": {"updated": 50,"failed": 0}}
/api/leads/validate-bulkBulk Validate Emails
Queue email validation for multiple leads (max 1000)
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| leadIds | array | Required | Array 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
{"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/api/profile-typesList 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
{"success": true,"data": {"profileTypes": [{"id": "pt_123","name": "Private Lender","slug": "private-lender","fields": [],"createdAt": "2024-01-01T00:00:00Z"}]}}
/api/profile-typesCreate Profile Type
Define a new custom entity schema for structured data extraction.
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| name | string | Required | Profile type name |
| slug | string | Required | URL-safe identifier |
| description | string | Optional | Schema description |
| fields | array | Required | JSON 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
{"success": true,"data": {"id": "pt_123","name": "Private Lender"}}
/api/profile-types/{id}Get Profile Type
Get a profile type by ID.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Profile type ID |
Example Request
curl -X GET "https://scrappy.gg/api/profile-types/{id}" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"success": true,"data": {}}
/api/profile-types/{id}Update Profile Type
Update a profile type's fields or metadata.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Profile type ID |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| name | string | Optional | Profile type name |
| fields | array | Optional | Updated 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
{"success": true,"data": {}}
/api/profile-types/{id}Delete Profile Type
Delete a profile type schema.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Profile type ID |
Example Request
curl -X DELETE "https://scrappy.gg/api/profile-types/{id}" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"success": true}
/api/profile-types/templatesList 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
{"success": true,"data": {"templates": [{"slug": "private-lender","name": "Private Lender","description": "Real estate lender schema"},{"slug": "restaurant","name": "Restaurant","description": "Food & beverage schema"}]}}
/api/profilesList Profiles
Get extracted profiles with optional filtering.
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| profileTypeId | string | Optional | Filter by profile type |
| status | string | Optional | Filter by statusOptions: NEW, VERIFIED, OUTDATED, ARCHIVED |
| page | number | Optional | Page number |
| limit | number | Optional | Items per page (max: 100) |
Example Request
curl -X GET "https://scrappy.gg/api/profiles" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"success": true,"data": {"profiles": [{"id": "prof_123","profileTypeId": "pt_123","data": {},"status": "NEW"}],"total": 5}}
/api/profiles/{id}Get Profile
Get a single profile by ID.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Profile ID |
Example Request
curl -X GET "https://scrappy.gg/api/profiles/{id}" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"success": true,"data": {}}
/api/profiles/{id}Update Profile
Update profile data or status.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Profile ID |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| data | object | Optional | Profile field data |
| status | string | Optional | Profile 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
{"success": true,"data": {}}
/api/profiles/exportExport Profiles
Export profiles as CSV. Optionally filter by matching rule (includes matchScore + aiReasoning columns).
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| profileTypeId | string | Optional | Filter by profile type |
| ruleId | string | Optional | Export profiles matching a specific matching rule |
Example Request
curl -X GET "https://scrappy.gg/api/profiles/export" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"Content-Type": "text/csv"}
/api/profiles/statsProfile 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
{"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/api/matching/rulesList Matching Rules
Get all matching rules for the current user.
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| profileTypeId | string | Optional | Filter by profile type |
Example Request
curl -X GET "https://scrappy.gg/api/matching/rules" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"success": true,"data": {"rules": [{"id": "rule_123","name": "High-value Leads","type": "SCORED","profileTypeId": "pt_123"}]}}
/api/matching/rulesCreate 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
| Name | Type | Required | Description |
|---|---|---|---|
| name | string | Required | Rule name |
| type | string | Required | Match typeOptions: FILTER, SCORED, AI |
| profileTypeId | string | Required | Profile type this rule applies to |
| criteria | object | Optional | Matching criteria (field conditions and weights). Can be empty {} for AI type. |
| aiPrompt | string | Optional | LLM prompt template for AI type rules. Use {{profile}} and {{profileType}} placeholders. |
| minimumScore | number | Optional | Minimum 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
{"success": true,"data": {"id": "rule_123","name": "High-value Leads"}}
/api/matching/rules/{id}Get Matching Rule
Get a matching rule by ID.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Rule ID |
Example Request
curl -X GET "https://scrappy.gg/api/matching/rules/{id}" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"success": true,"data": {}}
/api/matching/rules/{id}Update Matching Rule
Update a matching rule's criteria or metadata.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Rule ID |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| name | string | Optional | Rule name |
| criteria | object | Optional | Updated criteria |
| aiPrompt | string | Optional | Updated 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
{"success": true,"data": {}}
/api/matching/rules/{id}Delete Matching Rule
Delete a matching rule.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Rule ID |
Example Request
curl -X DELETE "https://scrappy.gg/api/matching/rules/{id}" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"success": true}
/api/matching/previewPreview Matching
Test a matching rule against sample profiles before saving.
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| ruleId | string | Optional | Existing rule to preview |
| type | string | Optional | Rule type for inline previewOptions: FILTER, SCORED, AI |
| criteria | object | Optional | Inline criteria to test |
| aiPrompt | string | Optional | Inline AI prompt to test |
| profileTypeId | string | Optional | Profile type to test against |
| sampleSize | number | Optional | Number 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
{"success": true,"data": {"matched": 7,"notMatched": 3,"results": [{"profileId": "prof_123","matched": true,"score": 87,"reasoning": "Revenue > $1M"}]}}
/api/matching/executeExecute Matching Rule
Run a matching rule against all profiles and store results.
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| ruleId | string | Required | Rule 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
{"success": true,"data": {"matched": 42,"notMatched": 18,"executionTimeMs": 1250}}
/api/matching/resultsList Matching Results
Get stored matching results for a rule.
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| ruleId | string | Required | Rule ID |
| matched | boolean | Optional | Filter matched/unmatched |
| page | number | Optional | Page number |
| limit | number | Optional | Items per page |
Example Request
curl -X GET ?ruleId=abc123"https://scrappy.gg/api/matching/results" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"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/api/accountsList Accounts
Returns companies aggregated from lead data with enriched metadata. Accounts are computed dynamically — not stored as a separate entity.
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| search | string | Optional | Search by company name or domain |
| projectId | string | Optional | Filter by project ID |
| page | number | Optional | Page number (default: 1) |
| limit | number | Optional | Items per page (default: 20, max: 100) |
Example Request
curl -X GET "https://scrappy.gg/api/accounts" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"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}}
/api/accounts/{accountId}Get Account
Get a single account with all associated leads.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| accountId | string | Required | Company domain used as account identifier |
Example Request
curl -X GET "https://scrappy.gg/api/accounts/{accountId}" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"success": true,"data": {"id": "acme.com","companyDomain": "acme.com","companyName": "Acme Corp","leadCount": 12,"leads": []}}
/api/accounts/{accountId}Update Account
Update account metadata (e.g. notes, tags).
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| accountId | string | Required | Company domain used as account identifier |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| notes | string | Optional | Notes 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
{"success": true,"data": {"id": "acme.com"}}
/api/accounts/{accountId}/commonalitiesAnalyze LinkedIn Commonalities
Use AI (Perplexity) to analyze shared traits among leads at this account. Requires PERPLEXITY_API_KEY.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| accountId | string | Required | Company domain |
Example Request
curl -X GET "https://scrappy.gg/api/accounts/{accountId}/commonalities" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"success": true,"data": {"commonalities": ["Alumni of MIT","Previously worked at Google","Focus on B2B SaaS"],"analyzedProfiles": 8}}
{"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/api/campaignsList Campaigns
Get all deal campaigns for the current user.
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| status | string | Optional | Filter by statusOptions: ACTIVE, PAUSED, COMPLETED, ARCHIVED |
| projectId | string | Optional | Filter by project ID |
Example Request
curl -X GET "https://scrappy.gg/api/campaigns" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"success": true,"data": {"campaigns": [{"id": "camp_123","name": "Q1 Outreach","status": "ACTIVE"}]}}
/api/campaignsCreate Campaign
Create a new deal campaign.
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| name | string | Required | Campaign name |
| description | string | Optional | Campaign description |
| targetIndustry | string | Optional | Target industry vertical |
| targetGeography | string | Optional | Target geography (e.g. 'US', 'New York') |
| targetCompanySize | string | Optional | Target company size (e.g. '50-200 employees') |
| dealParams | object | Optional | Custom deal parameters (loan size, revenue threshold, etc.) |
| projectId | string | Optional | Associate 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
{"success": true,"data": {"id": "camp_123","name": "Q1 Outreach","status": "ACTIVE"}}
/api/campaigns/{id}Get Campaign
Get a single campaign by ID.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Campaign ID |
Example Request
curl -X GET "https://scrappy.gg/api/campaigns/{id}" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"success": true,"data": {"id": "camp_123"}}
/api/campaigns/{id}Update Campaign
Update campaign fields.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Campaign ID |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| name | string | Optional | Campaign name |
| status | string | Optional | Campaign 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
{"success": true,"data": {"id": "camp_123"}}
/api/campaigns/{id}Delete Campaign
Permanently delete a campaign.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Campaign ID |
Example Request
curl -X DELETE "https://scrappy.gg/api/campaigns/{id}" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"success": true}
/api/campaigns/{id}/discover-sourcesDiscover Sources for Campaign
Use AI to find relevant data sources based on the campaign's target parameters. Requires PERPLEXITY_API_KEY.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Campaign ID |
Example Request
curl -X POST "https://scrappy.gg/api/campaigns/{id}/discover-sources" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"success": true,"data": {"sources": [{"name": "NYC Construction Directory","url": "https://example.com","reasoning": "High density of target companies"}]}}
/api/campaigns/{id}/suggest-matchingSuggest Matching Rules
Generate AI-suggested matching rules based on campaign deal parameters.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Campaign ID |
Example Request
curl -X POST "https://scrappy.gg/api/campaigns/{id}/suggest-matching" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"success": true,"data": {"rules": [{"type": "FILTER","field": "jobTitle","operator": "contains","value": "CFO"}]}}
/api/campaigns/{id}/ranked-accountsRanked Accounts
Get accounts ranked by fit score for this campaign.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Campaign ID |
Example Request
curl -X GET "https://scrappy.gg/api/campaigns/{id}/ranked-accounts" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"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/api/chat/conversationsList 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
{"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"}]}}
/api/chat/conversationsCreate Conversation
Start a new chat conversation.
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| title | string | Optional | Conversation 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
{"success": true,"data": {"id": "conv_123","title": "New Conversation","messages": []}}
/api/chat/conversations/{id}Get Conversation
Get a conversation with its full message history.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Conversation ID |
Example Request
curl -X GET "https://scrappy.gg/api/chat/conversations/{id}" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"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": "..."}]}}
/api/chat/conversations/{id}Delete Conversation
Delete a conversation and all its messages.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Conversation ID |
Example Request
curl -X DELETE "https://scrappy.gg/api/chat/conversations/{id}" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"success": true}
/api/chat/conversations/{id}/messagesSend 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.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Conversation ID |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| message | string | Required | User 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
{"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": {}}
{"success": false,"error": {"code": "SERVICE_UNAVAILABLE","message": "AI chat not configured"}}
Email Verification
Email bounce tracking and spam trap detection
3 endpoints/api/email-verification/bounceRecord Bounce
Record an email bounce event
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| string | Required | Email address that bounced | |
| bounceType | string | Required | Type of bounceOptions: HARD, SOFT, SPAM |
| bounceReason | string | Optional | Detailed bounce reason |
| smtpCode | string | Optional | SMTP 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
{"success": true}
/api/email-verification/spam-trapMark as Spam Trap
Mark an email as a known spam trap
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| string | Required | Email address | |
| confidence | number | Optional | Confidence level 0-100Default: 100 |
| trapType | string | Optional | Type of spam trap |
| source | string | Optional | Source 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
{"success": true}
/api/email-verification/statsGet Verification Stats
Get email verification and bounce statistics
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| bounces | boolean | Optional | Include recent bounces |
| bounceLimit | number | Optional | Number of recent bouncesDefault: 100 |
Example Request
curl -X GET "https://scrappy.gg/api/email-verification/stats" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"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/api/linkedin/credentialsList 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
{"success": true,"data": {"credentials": [{"id": "cred_123","email": "linkedin@example.com","status": "ACTIVE","dailyLimit": 80,"requestsRemaining": 45}]}}
/api/linkedin/credentialsAdd LinkedIn Credentials
Store LinkedIn account credentials for scraping.
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| string | Required | LinkedIn account email | |
| password | string | Required | LinkedIn 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
{"success": true,"data": {"id": "cred_123","email": "linkedin@example.com"}}
/api/linkedin/validateValidate LinkedIn Profile
Validate that a LinkedIn profile URL is reachable and extract basic metadata.
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| profileUrl | string | Required | LinkedIn 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
{"success": true,"data": {"valid": true,"name": "John Doe","headline": "CEO at Acme Corp"}}
/api/linkedin/enrichEnrich Lead from LinkedIn
Enrich a lead's data by scraping their LinkedIn profile.
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| leadId | string | Required | Lead ID to enrich |
| profileUrl | string | Optional | LinkedIn 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
{"success": true,"data": {"leadId": "lead_123","enriched": {"jobTitle": "VP of Sales","company": "Acme Corp","location": "New York"}}}
/api/linkedin/jobsList 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
{"success": true,"data": {"jobs": [{"id": "ljob_123","status": "COMPLETED","profilesScraped": 150}]}}
/api/linkedin/jobs/{id}Get LinkedIn Job
Get a LinkedIn scraping job by ID.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Job ID |
Example Request
curl -X GET "https://scrappy.gg/api/linkedin/jobs/{id}" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"success": true,"data": {}}
/api/linkedin/commonalitiesBatch 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.
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| profileUrls | array | Required | Array of LinkedIn profile URLs to analyze |
| context | string | Optional | Additional 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
{"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}}
{"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/api/warmup-domainsList 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
{"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}]}}
/api/warmup-domainsCreate Warmup Domain
Register a new domain for warmup. The domain must have valid SPF and DKIM before warmup can start.
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| domain | string | Required | Domain to warm up (e.g. outreach.acme.com) |
| targetDailyVolume | number | Optional | Target 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
{"success": true,"data": {"id": "wd_123","domain": "outreach.acme.com","status": "PENDING_DNS","spfValid": false,"dkimValid": false}}
/api/warmup-domains/{id}Get Warmup Domain
Get a warmup domain by ID with full metrics.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Warmup domain ID |
Example Request
curl -X GET "https://scrappy.gg/api/warmup-domains/{id}" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"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}}
/api/warmup-domains/{id}Update Warmup Domain
Update warmup domain settings (e.g. pause/resume).
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Warmup domain ID |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| status | string | Optional | Override statusOptions: PAUSED, WARMING |
| targetDailyVolume | number | Optional | Updated 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
{"success": true,"data": {}}
/api/warmup-domains/{id}Delete Warmup Domain
Remove a warmup domain.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Warmup domain ID |
Example Request
curl -X DELETE "https://scrappy.gg/api/warmup-domains/{id}" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"success": true}
/api/warmup-domains/{id}/startStart 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
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Warmup domain ID |
Example Request
curl -X POST "https://scrappy.gg/api/warmup-domains/{id}/start" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"success": true,"data": {"status": "WARMING","startedAt": "2024-01-15T10:00:00Z","estimatedCompletionWeeks": 5}}
{"success": false,"error": {"code": "DNS_NOT_CONFIGURED","message": "SPF and DKIM records are required before warmup can start"}}
/api/warmup-domains/{id}/metricsUpdate 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
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Warmup domain ID |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| openRate | number | Optional | Open rate (0–1) |
| replyRate | number | Optional | Reply rate (0–1) |
| bounceRate | number | Optional | Bounce rate (0–1). >0.10 triggers auto-pause. |
| spamRate | number | Optional | Spam rate (0–1). >0.005 triggers auto-pause. |
| emailsSent | number | Optional | Emails 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
{"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/api/angelsend/statusConnection 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
{"success": true,"data": {"connected": true,"email": "user@example.com","plan": "pro"}}
/api/angelsend/creditsGet 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
{"success": true,"data": {"credits": 5000,"used": 1200}}
/api/angelsend/leads/exportExport Leads to AngelSend
Export selected leads to an AngelSend project for outreach.
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| leadIds | array | Required | Array of lead IDs to export |
| projectName | string | Optional | AngelSend 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
{"success": true,"data": {"exported": 45,"failed": 2}}
/api/angelsend/send-emailSend Single Email
Send an individual email via AngelSend.
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| to | string | Required | Recipient email address |
| subject | string | Required | Email subject |
| body | string | Required | Email body (HTML or plain text) |
| leadId | string | Optional | Associated 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
{"success": true,"data": {"messageId": "msg_123"}}
/api/angelsend/send-emailsSend Bulk Emails
Send emails to multiple leads in one request.
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| emails | array | Required | Array 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
{"success": true,"data": {"sent": 48,"failed": 2}}
/api/angelsend/generate-emailsGenerate Email Copy
Use AI to generate personalized email copy for leads.
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| leadIds | array | Required | Lead IDs to generate emails for |
| template | string | Optional | Email template or campaign context |
| tone | string | Optional | Email 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
{"success": true,"data": {"emails": [{"leadId": "lead_123","subject": "Quick question about Acme","body": "Hi John, ..."}]}}
/api/angelsend/personalizePersonalize Email
AI-personalize an existing email template for a specific lead.
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| leadId | string | Required | Lead ID |
| template | string | Required | Base 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
{"success": true,"data": {"subject": "...","body": "..."}}
/api/angelsend/verify-emailsVerify Emails via AngelSend
Trigger email verification for leads through AngelSend.
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| leadIds | array | Required | Lead 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
{"success": true}
/api/angelsend/ai/analyze-intentAnalyze Reply Intent
Use AI to analyze the intent of a reply email (interested, not interested, objection, etc.).
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| emailBody | string | Required | Reply email body to analyze |
| leadId | string | Optional | Associated 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
{"success": true,"data": {"intent": "INTERESTED","confidence": 0.92,"summary": "Lead expressed interest and asked for a call","suggestedAction": "Schedule a call"}}
/api/angelsend/ai/auto-replyGenerate Auto-Reply
Generate an AI-powered reply to an inbound email.
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| originalEmail | string | Required | The original outbound email |
| replyEmail | string | Required | The inbound reply to respond to |
| leadId | string | Optional | Associated 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
{"success": true,"data": {"subject": "Re: ...","body": "Thanks for getting back to me..."}}
/api/angelsend/ai/follow-upsGenerate Follow-up Sequence
Generate a multi-step follow-up email sequence for a campaign.
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| leadId | string | Required | Lead ID |
| initialEmail | string | Required | The initial outbound email |
| steps | number | Optional | Number 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
{"success": true,"data": {"followUps": [{"dayOffset": 3,"subject": "Following up","body": "Just checking in..."},{"dayOffset": 7,"subject": "Last try","body": "One more thought..."}]}}
/api/angelsend/import/apolloImport from Apollo
Import leads from an Apollo.io CSV export into Scrappy.
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| file | file | Required | Apollo CSV export file |
| projectId | string | Optional | Target 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
{"success": true,"data": {"imported": 200,"duplicates": 15,"errors": 2}}
/api/angelsend/import/hunterImport from Hunter
Import leads from a Hunter.io CSV export into Scrappy.
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| file | file | Required | Hunter CSV export file |
| projectId | string | Optional | Target 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
{"success": true,"data": {"imported": 100,"duplicates": 5,"errors": 0}}
/api/angelsend/warmupSchedule Warmup
Schedule domain warmup sending via AngelSend.
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| domain | string | Required | Domain to warm up |
| dailyVolume | number | Optional | Target 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
{"success": true}
/api/user/integrations/angelsendGet 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
{"success": true,"data": {"hasApiKey": true,"isUsingGlobal": false}}
/api/user/integrations/angelsendUpdate User AngelSend Config
Set a per-user AngelSend API key (overrides the global environment variable).
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| apiKey | string | Optional | AngelSend 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
{"success": true}
Credits
Credit balance and purchasing
3 endpoints/api/credits/balanceGet 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
{"success": true,"data": {"balance": 500,"configured": true,"costs": {"scrape": 1,"emailValidation": 0.5,"aiDiscovery": 5}}}
/api/credits/pricingGet 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
{"success": true,"data": {"tiers": [{"id": "starter","credits": 100,"price": 9.99},{"id": "pro","credits": 500,"price": 39.99}],"configured": true}}
/api/credits/checkoutCreate Checkout Session
Create a checkout session to purchase credits
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| pricingTierId | string | Required | ID 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
{"success": true,"data": {"url": "https://checkout.stripe.com/..."}}
{"success": false,"error": {"code": "SERVICE_UNAVAILABLE","message": "Credits system not configured"}}
Markdown Exports
Export websites to markdown format
7 endpoints/api/markdown-exportsList Exports
Get all markdown exports
Example Request
curl -X GET "https://scrappy.gg/api/markdown-exports" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"success": true,"data": {"exports": [{"id": "abc123","name": "Example Site","url": "https://example.com","status": "COMPLETED","pageCount": 25}]}}
/api/markdown-exportsCreate Export
Create a new markdown export job
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| name | string | Required | Export name |
| url | string | Required | URL to export |
| exportType | string | Optional | Export typeOptions: SINGLE_PAGE, FULL_SITEDefault: SINGLE_PAGE |
| maxPages | number | Optional | Maximum pages to export (1-1000)Default: 100 |
| followExternal | boolean | Optional | Follow external links |
| useSitemap | boolean | Optional | Use 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
{"success": true,"data": {"export": {"id": "abc123","status": "PENDING"}}}
/api/markdown-exports/[id]Get Export
Get export details
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Export ID |
Example Request
curl -X GET "https://scrappy.gg/api/markdown-exports/abc123" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"success": true,"data": {"export": {"id": "abc123","status": "COMPLETED"}}}
/api/markdown-exports/[id]Delete Export
Delete an export
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Export ID |
Example Request
curl -X DELETE "https://scrappy.gg/api/markdown-exports/abc123" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"success": true}
/api/markdown-exports/[id]/pagesGet Export Pages
Get all pages from an export with their markdown content
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Export ID |
Example Request
curl -X GET "https://scrappy.gg/api/markdown-exports/abc123/pages" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"success": true,"data": {"pages": [{"id": "page123","url": "https://example.com/page","title": "Page Title","markdown": "# Page Content..."}]}}
/api/markdown-exports/[id]/downloadDownload Export
Download export as ZIP or single markdown file
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Export ID |
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| format | string | Optional | Download formatOptions: zip, singleDefault: zip |
Example Request
curl -X GET "https://scrappy.gg/api/markdown-exports/abc123/download" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"note": "Returns file attachment"}
{"success": false,"error": {"code": "NOT_FOUND","message": "Export has no content"}}
/api/markdown-exports/[id]/retryRetry Export
Retry a failed export
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Export ID |
Example Request
curl -X POST "https://scrappy.gg/api/markdown-exports/abc123/retry" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"success": true,"message": "Export retry started"}
Analytics
Pipeline overview, source performance, capacity planning, and credit burn rate metrics
4 endpoints/api/analytics/pipeline-overviewPipeline Overview
Get high-level pipeline metrics: lead counts by status, conversion rates, email validation rates, and recent activity.
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| projectId | string | Optional | Filter by project ID |
| days | number | Optional | Lookback window in days (default: 30) |
Example Request
curl -X GET "https://scrappy.gg/api/analytics/pipeline-overview" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"success": true,"data": {"totalLeads": 1250,"leadsByStatus": {"NEW": 420,"CONTACTED": 380,"QUALIFIED": 210,"UNQUALIFIED": 180,"ARCHIVED": 60},"emailValidationRate": 0.84,"newLeadsLast7Days": 145,"conversionRate": 0.168}}
/api/analytics/sourcesSource Analytics
Get performance metrics for each source: lead yield, success rate, and average run time.
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| projectId | string | Optional | Filter by project ID |
| days | number | Optional | Lookback window in days (default: 30) |
Example Request
curl -X GET "https://scrappy.gg/api/analytics/sources" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"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"}]}}
/api/analytics/capacityCapacity 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
{"success": true,"data": {"activeSources": 24,"scheduledJobsNext24h": 8,"queueDepth": 3,"estimatedLeadsPerDay": 450,"workerUtilization": 0.35}}
/api/analytics/credit-burn-rateCredit Burn Rate
Get credit usage trends and burn rate projection.
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| days | number | Optional | Lookback 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
{"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/api/admin/usersList 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
{"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}}}
{"success": false,"error": {"code": "FORBIDDEN","message": "Admin access required"}}
/api/admin/metrics/summaryGet 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
{"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"}
/api/admin/metrics/prometheusPrometheus Query
Proxy Prometheus queries (admin only)
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| query | string | Required | Prometheus query string |
| type | string | Optional | Query typeOptions: instant, rangeDefault: instant |
| start | string | Optional | Start time for range queries (ISO 8601) |
| end | string | Optional | End time for range queries (ISO 8601) |
| step | string | Optional | Query 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
{"status": "success","data": {"resultType": "vector","result": [{"metric": {"__name__": "up"},"value": [1704067200,"1"]}]}}
{"error": "Query parameter is required"}
Webhooks
Manage outbound webhooks for event notifications and receive inbound webhooks from external services
8 endpoints/api/user/webhooksList 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
{"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"}]}}
/api/user/webhooksCreate Webhook
Register an outbound webhook endpoint. Events are signed with HMAC-SHA256 via the X-Scrappy-Signature header.
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| url | string | Required | HTTPS endpoint to receive events |
| events | array | Required | Event types to subscribe to (e.g. lead.created, lead.updated, job.completed, email.validated) |
| secret | string | Optional | Secret for HMAC signature verification (auto-generated if omitted) |
| enabled | boolean | Optional | Enable 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
{"success": true,"data": {"id": "wh_123","url": "https://your-app.com/webhook","secret": "whsec_abc123...","events": ["lead.created"]}}
/api/user/webhooks/{id}Get Webhook
Get a webhook by ID including recent delivery history.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Webhook ID |
Example Request
curl -X GET "https://scrappy.gg/api/user/webhooks/{id}" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"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"}]}}
/api/user/webhooks/{id}Update Webhook
Update a webhook's URL, events, or enabled status.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Webhook ID |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| url | string | Optional | Updated endpoint URL |
| events | array | Optional | Updated event list |
| enabled | boolean | Optional | Enable 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
{"success": true,"data": {}}
/api/user/webhooks/{id}Delete Webhook
Delete a webhook endpoint.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Webhook ID |
Example Request
curl -X DELETE "https://scrappy.gg/api/user/webhooks/{id}" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"success": true}
/api/user/webhooks/{id}/deliveries/{deliveryId}/retryRetry Webhook Delivery
Manually retry a failed webhook delivery. Auto-retry uses 3 attempts with exponential backoff (5s / 25s / 125s).
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Webhook ID |
| deliveryId | string | Required | Delivery 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
{"success": true,"data": {"retryJobId": "job_789"}}
/api/webhooks/credCred.diy Webhook (Inbound)
Inbound webhook from cred.diy credits system. Verified via HMAC-SHA256 signature. Handles credit top-ups and subscription events.
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| x-cred-signature | header | Required | HMAC-SHA256 signature for verification |
| x-cred-event | header | Required | Event 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
{"received": true}
{"error": "Invalid signature"}
/api/webhooks/resendResend Email Webhook (Inbound)
Inbound webhook from Resend for transactional email delivery events (bounces, complaints, etc.).
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| svix-signature | header | Required | Svix signature for verification |
| type | string | Required | Event 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
{"received": true}
Enterprise
Organizations, API keys, source templates, proxy profiles, suppression lists, and data destinations
20 endpoints/api/organizationsList 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
{"success": true,"data": {"organizations": [{"id": "org_123","name": "Acme Corp","role": "OWNER","memberCount": 8}]}}
/api/organizationsCreate Organization
Create a new organization.
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| name | string | Required | Organization name |
| domain | string | Optional | Company 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
{"success": true,"data": {"id": "org_123","name": "Acme Corp"}}
/api/organizations/{id}Get Organization
Get organization details.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Organization ID |
Example Request
curl -X GET "https://scrappy.gg/api/organizations/{id}" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"success": true,"data": {}}
/api/organizations/{id}/membersList Members
Get all members of an organization.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Organization ID |
Example Request
curl -X GET "https://scrappy.gg/api/organizations/{id}/members" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"success": true,"data": {"members": [{"userId": "u_123","email": "admin@acme.com","role": "OWNER","joinedAt": "2024-01-01"}]}}
/api/organizations/{id}/ssoSSO Configuration
Get SSO/SAML configuration for the organization.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Organization ID |
Example Request
curl -X GET "https://scrappy.gg/api/organizations/{id}/sso" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"success": true,"data": {"provider": "OKTA","enabled": true,"domain": "acme.com"}}
/api/organizations/{id}/ip-allowlistIP Allowlist
Get or manage IP allowlist for organization access control.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Organization ID |
Example Request
curl -X GET "https://scrappy.gg/api/organizations/{id}/ip-allowlist" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"success": true,"data": {"allowlist": ["192.168.1.0/24","10.0.0.0/8"],"enabled": true}}
/api/api-keysList 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
{"success": true,"data": {"apiKeys": [{"id": "key_123","name": "Production Key","prefix": "sk_prod_****","createdAt": "2024-01-01"}]}}
/api/api-keysCreate API Key
Generate a new API key. The full key is returned only once.
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| name | string | Required | Key name/description |
| scopes | array | Optional | Permission 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. |
| expiresAt | string | Optional | ISO 8601 expiration date |
| projectId | string | null | Optional | Default 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
{"success": true,"data": {"id": "key_123","name": "Production Key","key": "sk_prod_abc123...","prefix": "sk_prod_****"}}
/api/api-keys/{id}Delete API Key
Revoke an API key.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | API key ID |
Example Request
curl -X DELETE "https://scrappy.gg/api/api-keys/{id}" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"success": true}
/api/mcpHosted 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
| Name | Type | Required | Description |
|---|---|---|---|
| jsonrpc | string | Required | MCP 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
{"jsonrpc": "2.0","id": 1,"result": {"tools": ["…","browser_create_session","send_chat_message"]}}
{"success": false,"error": {"code": "UNAUTHORIZED","message": "Provide a Scrappy API key: Authorization: Bearer sk_..."}}
/api/source-templatesList 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
{"success": true,"data": {"templates": [{"id": "tmpl_123","name": "LinkedIn Directory Scraper","category": "Social","usageCount": 42}]}}
/api/source-templates/marketplaceTemplate 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
{"success": true,"data": {"templates": []}}
/api/source-templatesCreate Source Template
Create a reusable source configuration template.
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| name | string | Required | Template name |
| description | string | Optional | Template description |
| config | object | Required | Source configuration to templatize |
| isPublic | boolean | Optional | Share 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
{"success": true,"data": {"id": "tmpl_123"}}
/api/proxy-profilesList 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
{"success": true,"data": {"proxies": [{"id": "proxy_123","name": "US Residential","type": "RESIDENTIAL","country": "US","successRate": 0.94}]}}
/api/proxy-profilesCreate Proxy Profile
Add a proxy profile for use in scraping jobs.
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| name | string | Required | Profile name |
| type | string | Required | Proxy typeOptions: RESIDENTIAL, DATACENTER, MOBILE |
| host | string | Required | Proxy host |
| port | number | Required | Proxy port |
| username | string | Optional | Proxy username |
| password | string | Optional | Proxy password (encrypted at rest) |
| country | string | Optional | Country 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
{"success": true,"data": {"id": "proxy_123"}}
/api/suppression-listList 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
{"success": true,"data": {"lists": [{"id": "sl_123","name": "Global Unsubscribes","entryCount": 1250}]}}
/api/suppression-list/{id}/entriesList Suppression Entries
Get entries in a suppression list.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Suppression list ID |
Example Request
curl -X GET "https://scrappy.gg/api/suppression-list/{id}/entries" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"success": true,"data": {"entries": [{"email": "unsubscribed@example.com","addedAt": "2024-01-15"}]}}
/api/data-destinationsList 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
{"success": true,"data": {"destinations": [{"id": "dest_123","name": "HubSpot CRM","type": "WEBHOOK","enabled": true}]}}
/api/data-destinationsCreate Data Destination
Configure a new data sync destination.
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| name | string | Required | Destination name |
| type | string | Required | Destination typeOptions: WEBHOOK, DATABASE, CRM |
| config | object | Required | Destination-specific configuration |
| enabled | boolean | Optional | Enable 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
{"success": true,"data": {"id": "dest_123"}}
/api/data-destinations/{id}/syncTrigger Sync
Manually trigger a data sync to a destination.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Destination ID |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| leadIds | array | Optional | Specific 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
{"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/api/browser/sessionsCreate 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
| Name | Type | Required | Description |
|---|---|---|---|
| screenWidth | number | Optional | Viewport width in px (640–3840, default 1280) |
| screenHeight | number | Optional | Viewport height in px (480–2160, default 720) |
| profileId | string | Optional | Persistent profile to hydrate — the session starts already logged in, and its rotated login is saved back on release |
| clientRef | string | Optional | Your 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 |
| showCursor | boolean | Optional | Draw 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 |
| rotateEgress | boolean | Optional | Replay 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
{"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"}
{"error": "Insufficient credits"}
{"error": "Session limit reached (max 10). Close an existing session first."}
/api/browser/sessionsList 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
{"sessions": [{"id": "b1f2…","status": "live","viewerUrl": "/api/browser/sessions/b1f2…/viewer"}]}
/api/browser/sessions/{id}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
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Steel session id (UUID) |
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| capture | boolean | Optional | Default 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
{"released": true}
/api/browser/sessions/{id}/screenshotScreenshot
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
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Steel session id |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| format | string | Optional | png | jpeg | webp (default png) |
| quality | number | Optional | 0-100, ignored for png |
| fullPage | boolean | Optional | Capture the full scrollable page |
| ref | string | Optional | Capture just this element — a ref from read-page or find |
| selector | string | Optional | Capture just the element this CSS selector names |
| pierce | boolean | Optional | Also search open shadow roots when resolving selector |
| clip | object | Optional | { x, y, width, height, scale? } in CSS pixels from the top of the page |
| tab | string | Optional | Target 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
{"success": true,"data": {"base64": "iVBORw0KGgo…","format": "png","mimeType": "image/png"}}
/api/browser/sessions/{id}/recordingRecord 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
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Steel session id |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| action | string | Optional | start | stop | status | discard (default status) |
| fps | number | Optional | Frames per second, 1-15 (default 5) |
| quality | number | Optional | JPEG quality of captured frames, 10-100 (default 60) |
| maxWidth | number | Optional | Capture width cap in px, 320-2560 (default 1280) |
| maxHeight | number | Optional | Capture height cap in px, 240-1440 (default 720) |
| maxDurationMs | number | Optional | Stop automatically after this long, 1s-15min (default 2min) |
| maxBytes | number | Optional | Stop 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
{"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"}}
{"success": false,"error": {"code": "SESSION_NOT_RECORDABLE","message": "Session is not live"}}
/api/browser/sessions/{id}/scrapeScrape
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
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Steel session id |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| format | string | Optional | html (default) | text | markdown | skeleton | structured | clean |
| selector | string | Optional | CSS selector to scope extraction |
| maxChars | number | Optional | Cap 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
{"success": true,"data": {"content": "<html>…","url": "https://example.com","title": "Example"}}
{"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}}
{"success": false,"error": {"code": "HTML_TOO_LARGE","message": "HTML is 14204KB, over the 10240KB simplifier limit"}}
/api/browser/sessions/{id}/scrape-modeScrape 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
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Steel session id |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| action | string | Required | on | off |
| preset | string | Optional | light (trackers only) | standard (default: images, media, fonts, trackers) | aggressive (also CSS and JS — static-HTML sites only, it leaves a client-rendered page empty) |
| block | array | Optional | Override the preset: image, media, font, stylesheet, script, tracking |
| strip | array | Optional | Override the in-page passes: animations, overlays, sticky, lazy, media, scrolllock |
| blockPatterns | array | Optional | Extra raw URL globs, e.g. "*ads.example.com*" (max 100) |
| persist | boolean | Optional | Re-apply on every later navigation in this tab (default true) |
| watch | boolean | Optional | Keep 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
{"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}}}
{"success": true,"data": {"enabled": false,"blocked": [],"styleRemoved": true,"note": "Blocking cleared and styles removed. Elements already removed from the current document return on reload."}}
/api/browser/sessions/{id}/keepaliveKeepalive
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
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Steel session id |
Example Request
curl -X POST "https://scrappy.gg/api/browser/sessions/{id}/keepalive" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"success": true,"data": {"alive": true,"idleTimeoutMs": 1800000,"recommendedIntervalMs": 600000}}
/api/browser/profilesList 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
| Name | Type | Required | Description |
|---|---|---|---|
| clientRef | string | Optional | Only 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
{"success": true,"data": {"profiles": [{"id": "prf_123","name": "acme-facebook","clientRef": "user_42","cookieCount": 18,"capturedAt": "2026-07-22T10:00:00Z"}]}}
/api/browser/profilesCreate 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
| Name | Type | Required | Description |
|---|---|---|---|
| name | string | Required | Profile name, unique per account |
| clientRef | string | Optional | Your 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 |
| proxyUrl | string | Optional | Pinned egress proxy for a stable exit IP |
| userAgent | string | Optional | Pinned 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
{"success": true,"data": {"id": "prf_123","name": "acme-facebook","cookieCount": 0}}
{"error": "A profile named \"acme-facebook\" already exists"}
/api/browser/profiles/{id}/captureCapture 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
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Profile id |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| sessionId | string | Required | Live 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
{"success": true,"data": {"captured": true,"cookieCount": 18,"originCount": 2}}
{"error": "Nothing captured — the profile may not exist, or the session had no cookies or storage to save"}
/api/browser/profiles/{id}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
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Profile id |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| name | string | Optional | New profile name |
| proxyUrl | string | Optional | Pinned egress proxy, or null to un-pin and let the next session re-roll |
| userAgent | string | Optional | Pinned 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
{"success": true,"data": {"id": "prf_123","name": "acme-facebook","proxyUrl": null}}
{"error": "Profile not found"}
/api/browser/profiles/{id}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
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Profile id |
Example Request
curl -X DELETE "https://scrappy.gg/api/browser/profiles/{id}" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"success": true,"data": {"deleted": true}}
/api/browser/sessions/{id}/checkCheck / 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
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Steel session id |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| ref | string | Optional | Ref from read-page (ref_<n>) — names the element directly. Use instead of selector |
| selector | string | Optional | CSS selector of the checkbox or radio (or use ref) |
| checked | boolean | Required | Desired state — true checks, false unchecks |
| tab | string | Optional | Target 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
{"success": true,"data": {"checked": true}}
/api/browser/sessions/{id}/hoverHover
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
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Steel session id |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| ref | string | Optional | Ref from read-page (ref_<n>) — names the element directly. Use instead of selector |
| selector | string | Optional | CSS selector of the element to hover (or use ref, or x/y) |
| x | number | Optional | X coordinate, with y |
| y | number | Optional | Y coordinate, with x |
| tab | string | Optional | Target 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
{"success": true,"data": {"hovered": true,"x": 120,"y": 340}}
/api/browser/sessions/{id}/selectSelect 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
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Steel session id |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| ref | string | Optional | Ref from read-page (ref_<n>) — names the element directly. Use instead of selector |
| selector | string | Optional | CSS selector of the <select> (or use ref) |
| value | string | Optional | Single option value to select |
| values | array | Optional | Option values for a multi-select |
| tab | string | Optional | Target 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
{"success": true,"data": {"selected": ["ca"]}}
/api/browser/sessions/{id}/scrollScroll
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
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Steel session id |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| ref | string | Optional | Ref from read-page (ref_<n>) — names the element directly. Use instead of selector |
| selector | string | Optional | CSS selector to scroll into view (or use ref) |
| x | number | Optional | Horizontal pixel offset, with y |
| y | number | Optional | Vertical pixel offset, with x |
| position | string | Optional | top | bottom |
| wheel | object | Optional | Dispatch 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 |
| tab | string | Optional | Target 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
{"success": true,"data": {"scrolled": true,"scrollX": 0,"scrollY": 1840}}
{"success": true,"data": {"scrolled": true,"wheel": {"x": 640,"y": 400,"deltaX": 0,"deltaY": 600}}}
/api/browser/sessions/{id}/pressPress 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
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Steel session id |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| key | string | Required | Key name (Enter, Tab, Escape, ArrowDown…) or a single character |
| ref | string | Optional | Ref from read-page to focus before pressing |
| modifiers | number | Optional | CDP modifier bitmask — 1 Alt, 2 Ctrl, 4 Meta, 8 Shift |
| tab | string | Optional | Target 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
{"success": true,"data": {"pressed": "Enter"}}
/api/browser/sessions/{id}/evaluateEvaluate 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
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Steel session id |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| expression | string | Required | JavaScript to evaluate. The completion value is returned |
| timeout | number | Optional | Milliseconds before giving up (default 10000) |
| tab | string | Optional | Target 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
{"success": true,"data": {"result": {"rows": 12}}}
/api/browser/sessions/{id}/domGet 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
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Steel session id |
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| selector | string | Optional | Restrict to a subtree |
| maxDepth | number | Optional | Limit tree depth |
| tab | string | Optional | Target tab id |
Example Request
curl -X GET "https://scrappy.gg/api/browser/sessions/{id}/dom" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"success": true,"data": {"tag": "body","children": []}}
/api/browser/sessions/{id}/consoleConsole 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
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Steel session id |
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| contains | string | Optional | Case-insensitive substring of the message text |
| level | string | Optional | Comma-separated levels to keep, e.g. error,warn |
| limit | number | Optional | Return at most this many, newest kept (1-500) |
| peek | boolean | Optional | Leave the read cursor where it is |
| tab | string | Optional | Target tab id |
Example Request
curl -X GET "https://scrappy.gg/api/browser/sessions/{id}/console" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"entries": [{"level": "error","text": "Uncaught TypeError"}],"drained": 84,"matched": 1,"returned": 1,"dropped": 83,"truncated": false}
/api/browser/sessions/{id}/networkNetwork 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
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Steel session id |
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| contains | string | Optional | Case-insensitive substring of the request URL |
| method | string | Optional | Comma-separated methods to keep, e.g. POST,PUT |
| type | string | Optional | Comma-separated resource types, e.g. xhr,fetch |
| statusMin | number | Optional | Only responses at or above this status |
| limit | number | Optional | Return at most this many, newest kept (1-500) |
| peek | boolean | Optional | Leave the read cursor where it is |
| tab | string | Optional | Target tab id |
Example Request
curl -X GET "https://scrappy.gg/api/browser/sessions/{id}/network" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"entries": [{"method": "POST","url": "/api/save","status": 422}],"drained": 212,"matched": 1,"returned": 1,"dropped": 211,"truncated": false}
/api/browser/sessions/{id}/devtoolsConsole + 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
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Steel session id |
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| limit | number | Optional | Maximum entries per stream |
| tab | string | Optional | Target tab id |
Example Request
curl -X GET "https://scrappy.gg/api/browser/sessions/{id}/devtools" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"success": true,"data": {"console": [],"network": []}}
/api/browser/sessions/{id}/historyHistory 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
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Steel session id |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| action | string | Required | back | forward | reload |
| tab | string | Optional | Target 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
{"success": true,"data": {"action": "back","url": "https://example.com/list"}}
/api/browser/sessions/{id}/viewportSet 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
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Steel session id |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| width | number | Required | Viewport width in px |
| height | number | Required | Viewport height in px |
| deviceScaleFactor | number | Optional | Device pixel ratio (default 1) |
| mobile | boolean | Optional | Emulate a mobile device (default false) |
| tab | string | Optional | Target 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
{"success": true,"data": {"width": 1280,"height": 720}}
/api/browser/sessions/{id}/iframeIframe 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
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Steel session id |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| frameSelector | string | Required | CSS selector of the iframe element |
| action | string | Required | click | type | evaluate |
| selector | string | Optional | Selector INSIDE the frame, for click and type |
| text | string | Optional | Text, for type |
| expression | string | Optional | JavaScript, for evaluate |
| tab | string | Optional | Target 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
{"success": true,"data": {"action": "click","clicked": true}}
/api/browser/sessions/{id}/cookiesGet 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
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Steel session id |
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| tab | string | Optional | Target tab id |
Example Request
curl -X GET "https://scrappy.gg/api/browser/sessions/{id}/cookies" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"success": true,"data": {"cookies": [{"name": "session","domain": ".example.com"}]}}
/api/browser/sessions/{id}/cookiesSet 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
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Steel session id |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| cookies | array | Required | Cookie objects: name, value, domain, path, secure, httpOnly, sameSite, expires |
| tab | string | Optional | Target 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
{"success": true,"data": {"set": 8}}
/api/browser/sessions/{id}/cookiesDelete 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
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Steel session id |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| name | string | Optional | Cookie name to delete. Omit to clear all |
| domain | string | Optional | Restrict deletion to this domain |
| tab | string | Optional | Target 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
{"success": true,"data": {"deleted": true}}
/api/browser/sessions/{id}/storageRead 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
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Steel session id |
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| type | string | Optional | local | session (default local) |
| key | string | Optional | Single key. Omit for all |
| tab | string | Optional | Target tab id |
Example Request
curl -X GET "https://scrappy.gg/api/browser/sessions/{id}/storage" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"success": true,"data": {"type": "local","values": {"token": "…"}}}
/api/browser/sessions/{id}/storageWrite 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
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Steel session id |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| type | string | Optional | local | session (default local) |
| key | string | Required | Storage key |
| value | string | Required | Value to store |
| tab | string | Optional | Target 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
{"success": true,"data": {"stored": true}}
/api/browser/sessions/{id}/storageClear 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
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Steel session id |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| type | string | Optional | local | session (default local) |
| key | string | Optional | Key to remove. Omit to clear the store |
| tab | string | Optional | Target 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
{"success": true,"data": {"cleared": true}}
/api/browser/sessions/{id}/contextSession 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
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Steel session id |
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| tab | string | Optional | Target tab id |
Example Request
curl -X GET "https://scrappy.gg/api/browser/sessions/{id}/context" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"success": true,"data": {"cookies": [],"egressIp": "76.27.31.230","proxy": {"label": "residential"}}}
/api/browser/sessions/{id}/captchaCAPTCHA
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
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Steel session id |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| action | string | Required | detect | solve |
| maxAttempts | number | Optional | Solve attempts before giving up |
| tab | string | Optional | Target 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
{"success": true,"data": {"detected": true,"rendered": true,"solved": false}}
/api/browser/sessions/{id}/sleepSleep 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
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Steel session id |
Example Request
curl -X POST "https://scrappy.gg/api/browser/sessions/{id}/sleep" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"success": true,"data": {"snapshotId": "snp_123","released": true}}
/api/browser/sessions/{id}/releaseRelease (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
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Steel session id |
Example Request
curl -X POST "https://scrappy.gg/api/browser/sessions/{id}/release" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"released": true}
/api/browser/sessions/{id}/devtools-streamConsole + 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
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Steel session id |
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| tab | string | Optional | Target 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
{"event": "console","data": {"type": "error","text": "Uncaught TypeError"}}
/api/browser/sessions/{id}/typeType
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
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Steel session id |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| ref | string | Optional | Ref from read-page (ref_<n>) — types into exactly that node. Use instead of selector |
| selector | string | Optional | CSS selector of the input/textarea (or use ref) |
| text | string | Required | Text to type (max 10000 chars) |
| clear | boolean | Optional | Replace the existing value (default true); false appends |
| pierce | boolean | Optional | Also search inside open shadow roots. Selector path only |
| tab | string | Optional | Target 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
{"success": true,"data": {"typed": true,"ref": "ref_18","value": "Jane Doe"}}
{"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"}}
/api/browser/sessions/{id}/read-pageRead 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
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Steel session id |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| interactiveOnly | boolean | Optional | Only actionable roles — buttons, links, inputs (default false) |
| maxNodes | number | Optional | Cap on returned nodes, 1-2000 (default 500) |
| tab | string | Optional | Target 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
{"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}}
/api/browser/sessions/{id}/findFind 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
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Steel session id |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| query | string | Required | What you would say to point at the element, e.g. 'Sign in' |
| role | string | Optional | Restrict to one accessibility role: button, link, textbox, checkbox, … |
| interactiveOnly | boolean | Optional | Only actionable roles (default false) |
| limit | number | Optional | Max matches, 1-50 (default 10) |
| maxNodes | number | Optional | How much of the tree to search, 1-5000 (default 2000) |
| tab | string | Optional | Target 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
{"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}}
/api/browser/sessions/{id}/batchBatch 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
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Steel session id |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| steps | array | Required | Ordered steps, each { action, ...that action's own parameters } minus the session id (1-25) |
| stopOnError | boolean | Optional | Stop 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
{"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}}
/api/browser/sessions/{id}/fill-formFill 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
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Steel session id |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| fields | array | Required | Fields in order (1-50): { ref | selector, value: string | boolean, kind?: text | select | check, clear?, pierce? } |
| submit | object | Optional | { ref | selector } to click once every field is set |
| stopOnError | boolean | Optional | Stop at the first failing field and skip the submit (default true) |
| tab | string | Optional | Target 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
{"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}}
/api/browser/sessions/{id}/element-infoElement 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
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Steel session id |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| ref | string | Optional | Ref from read-page (ref_<n>) — inspect exactly that node. Use instead of selector |
| selector | string | Optional | CSS selector of the element to inspect (or use ref) |
| pierce | boolean | Optional | Also search inside open shadow roots |
| tab | string | Optional | Target 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
{"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}}
{"success": false,"error": {"code": "NOT_FOUND","message": "Element not found: #save"}}
/api/browser/sessions/{id}/clickClick
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
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Steel session id |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| ref | string | Optional | Ref from read-page (ref_<n>). Names the element directly — preferred over a selector, which can silently match the wrong node |
| selector | string | Optional | CSS selector (or use ref, or x/y) |
| x | number | Optional | X coordinate, with y |
| y | number | Optional | Y coordinate, with x |
| button | string | Optional | left (default) | right | middle |
| clickCount | number | Optional | 2 for a double-click (default 1) |
| pierce | boolean | Optional | Also search inside open shadow roots |
| holdMs | number | Optional | Hold the button down this many ms before releasing (0–30000, default 0). Use ~6000–10000 for a 'Press & Hold' challenge; ignores clickCount |
| jitter | number | Optional | Max px of random pointer drift while held (0–10, default 2). 0 holds perfectly still, which reads as synthetic |
| modifiers | number | Optional | Modifier 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
{"success": true,"data": {"clicked": true,"tag": "button","button": "left","clickCount": 1,"modifiers": 0}}
{"success": true,"data": {"clicked": true,"tag": "button","button": "left","holdMs": 8000,"jitter": 2,"x": 500,"y": 328}}
{"success": false,"error": {"code": "ELEMENT_NOT_FOUND","message": "Element not found: #save (try pierce:true if it is inside a web component)"}}
/api/browser/sessions/{id}/uploadUpload 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
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Steel session id |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| selector | string | Optional | CSS selector of the file input. Required unless backendNodeId is given |
| backendNodeId | number | Optional | CDP 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 |
| filename | string | Optional | Name the page should see. Required unless a file is uploaded, which carries its own |
| file | file | Optional | multipart only — raw bytes, up to 9MB total. Repeat the field to fill an <input multiple> (max 10) |
| sourceUrl | string | Optional | URL Steel fetches directly — up to 100MB, enforced upstream while streaming. Prefer a short-lived signed URL |
| content | string | Optional | Base64 file bytes — practical ceiling ~7MB. A data:<type>;base64,... URL, line-wrapped base64 and base64url are all accepted |
| mimeType | string | Optional | Inferred 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
{"success": true,"data": {"uploaded": true,"filename": "signed-psa.pdf","bytes": 3088632,"via": "sourceUrl","verified": true,"attached": [{"name": "signed-psa.pdf","size": 3088632}]}}
{"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"}}
{"success": false,"error": {"code": "FILE_TOO_LARGE"}}
{"success": false,"error": {"code": "UNSAFE_URL","message": "Navigation to private IP addresses is not allowed"}}
/api/browser/tabsClose 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
| Name | Type | Required | Description |
|---|---|---|---|
| sessionId | string | Required | Steel session id |
| targetId | string | Required | CDP target id from GET /api/browser/tabs |
| action | string | Optional | close (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
{"closed": true,"targetId": "AE77D31F…"}
/api/browser/sessions/{id}/wait-for-selectorWait 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
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Steel session id |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| selector | string | Required | CSS selector to wait for |
| state | string | Optional | present | visible | hidden | detached (default visible) |
| timeout | number | Optional | Max 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
{"success": true,"data": {"found": true,"state": "visible","waitedMs": 340}}
/api/browser/sessions/{id}/wait-for-functionWait 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
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Steel session id |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| expression | string | Required | JavaScript expression (not a statement body) polled until truthy, max 10000 chars |
| timeout | number | Optional | Max wait in ms (500–60000, default 10000) |
| pollInterval | number | Optional | Ms between evaluations (50–5000, default 200) |
| required | boolean | Optional | Fail 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 |
| tab | string | Optional | Target 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
{"success": true,"data": {"satisfied": true,"value": true,"waitedMs": 480,"polls": 3}}
{"success": true,"data": {"satisfied": false,"timedOut": true,"value": null,"waitedMs": 10000,"polls": 50,"lastError": "clientModal is not defined"}}
{"success": false,"error": {"code": "INVALID_PREDICATE","message": "Expression returned a Promise."}}
/api/browser/sessions/{id}/dialogSuppress 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
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Steel session id |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| confirmReturns | boolean | Optional | What confirm() returns (default true = proceed) |
| promptText | string | Optional | What 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
{"success": true,"data": {"suppressed": true,"confirmReturns": true}}
/api/browser/sessions/{id}/drag-dropDrag 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
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Steel session id |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| sourceSelector | string | Optional | CSS selector of the element to drag (or sourceRef / sourceX+sourceY) |
| sourceRef | string | Optional | Ref from read-page for the drag source |
| targetSelector | string | Optional | CSS selector of the drop target (or targetRef / targetX+targetY) |
| targetRef | string | Optional | Ref from read-page for the drop target |
| sourceX | number | Optional | X of the drag start, when the source is not an element (with sourceY) |
| sourceY | number | Optional | Y of the drag start, when the source is not an element (with sourceX) |
| targetX | number | Optional | X of the drop point, when the target is not an element (with targetY) |
| targetY | number | Optional | Y of the drop point, when the target is not an element (with targetX) |
| path | array | Optional | Poly-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 |
| steps | number | Optional | Intermediate 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
{"success": true,"data": {"dragged": true,"from": {"x": 120,"y": 340},"to": {"x": 480,"y": 340}}}
{"success": true,"data": {"dragged": true,"from": {"x": 100,"y": 100},"to": {"x": 300,"y": 260},"points": 12}}
{"success": false,"error": {"code": "ELEMENT_NOT_FOUND","message": "Element not found: #handle"}}
/api/browser/sessions/{id}/pastePaste
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
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Steel session id |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| ref | string | Optional | Ref from read-page (ref_<n>) to paste into |
| selector | string | Optional | CSS selector of the paste target. Omit ref and selector both to paste into the focused element |
| text | string | Optional | Plain text to paste (max 100000). Provide text, imageBase64, or both |
| imageBase64 | string | Optional | Image as base64 or a data:<type>;base64,... URL. Decoded to a File and delivered on the paste event's clipboard data |
| mimeType | string | Optional | Image MIME type (default image/png; inferred from a data URL) |
| tab | string | Optional | Target 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
{"success": true,"data": {"pasted": true,"consumed": true,"hasText": false,"hasImage": true,"selector": "div.comment-body"}}
{"success": false,"error": {"code": "INVALID_IMAGE","message": "imageBase64 is not valid base64"}}
{"success": false,"error": {"code": "ELEMENT_NOT_FOUND","message": "No element is focused — pass ref or selector"}}
/api/browser/sessions/{id}/wait-for-networkWait 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
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Steel session id |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| idleTime | number | Optional | Quiet period in ms before declaring idle (100–10000, default 500) |
| timeout | number | Optional | Max 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
{"success": true,"data": {"idle": true,"waitedMs": 1240,"requestCount": 37}}
/api/browser/sessions/{id}/interceptIntercept / 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
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Steel session id |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| action | string | Required | start | stop |
| blockTypes | array | Optional | image, media, font, stylesheet, script, tracking |
| blockPatterns | array | Optional | Raw 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
{"success": true,"data": {"intercepting": true,"count": 8}}
{"success": false,"error": {"code": "NOTHING_TO_BLOCK","message": "Provide at least one of blockTypes or blockPatterns when starting interception"}}
/api/browser/sessions/{id}/downloadResolve 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
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Steel session id |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| selector | string | Required | CSS selector of the download trigger |
| timeout | number | Optional | Timeout 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
{"success": true,"data": {"url": "https://example.com/export.csv","filename": "export.csv"}}
/api/browser/sessions/{id}/pdfRender PDF
Render the current page to a PDF and return it base64-encoded. Scope browser:write.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Steel session id |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| landscape | boolean | Optional | Landscape orientation |
| printBackground | boolean | Optional | Include background graphics |
| scale | number | Optional | Scale factor (0.1–2) |
| paperWidth | number | Optional | Paper width in inches |
| paperHeight | number | Optional | Paper 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
{"success": true,"data": {"base64": "JVBERi0xLjQK…","pageCount": 3}}
/api/browser/sessions/{id}/viewerSession 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
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Steel session id |
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| token | string | Optional | Viewer 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
{"contentType": "text/html"}
/api/browser/sessions/{id}/viewer-tokenViewer 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
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Steel session id |
Example Request
curl -X POST "https://scrappy.gg/api/browser/sessions/{id}/viewer-token" \-H "Authorization: Bearer YOUR_API_KEY"
Responses
{"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/api/healthHealth Check
Check the health status of all services
Example Request
curl -X GET "https://scrappy.gg/api/health"
Responses
{"status": "healthy","services": {"database": {"status": "healthy","latency": 5},"redis": {"status": "healthy","latency": 2}}}
Need help? Check out our User Guides or contact support.