Skip to content

Developer platform

Put PageFox data into the tools your team already uses

Business plan · REST API v1 · read-only · server-to-server

What are you trying to do?

Use the API when a developer needs to discover PageFox websites or read leads, identified companies, visitor sessions, or AI conversations from a trusted server. Use a signed webhook for event-driven automation, or stay in the dashboard when a person is doing the work.

Not a public browser API: PageFox's site script and assistant use separate runtime endpoints. They are not supported third-party integration routes.

Your first call in about 10 minutes

  1. On the Business plan, an organization owner opens Settings → API keys, creates a key, and grants websites:read. Add leads:read if the next call will read lead priorities. The full key appears once.
  2. Put the key in your server's secret manager or a local environment variable. Replace only the placeholder below; do not commit the key.
  3. Run this from a terminal or trusted backend. The first request returns the website IDs that this organization can use in later API filters.
Terminal
export PAGEFOX_API_KEY="pfx_your_key_here"

curl --fail-with-body \
  --request GET \
  --header "Authorization: Bearer $PAGEFOX_API_KEY" \
  "https://pagefox.co/api/v1/websites?page=1"

A successful response is JSON with data and pagination. Save the returned rate-limit headers and follow total_pages when it is greater than the current page.

200 response
{
  "data": [
    {
      "id": "11111111-1111-4111-8111-111111111111",
      "name": "Product website",
      "url": "https://example.com",
      "status": "active",
      "data_mode": "live"
    },
    {
      "id": "22222222-2222-4222-8222-222222222222",
      "name": "PageFox demo",
      "url": "https://demo.example.com",
      "status": "paused",
      "data_mode": "demo"
    }
  ],
  "pagination": {
    "page": 1,
    "per_page": 50,
    "total": 2,
    "total_pages": 1
  }
}
Keep API keys out of browsers and model prompts. A browser exposes the key to visitors, and text pasted into an arbitrary AI prompt may be retained or relayed. If an AI workflow needs PageFox data today, have your trusted server make the REST call and give the model only the minimum returned data it needs. Revoke a key immediately if it may have leaked.

Scope a call to one website

Call GET /websites with websites:read, then use a returned id as the website_id filter on another read route. Omit website_id when you intentionally want organization-wide results.

status tells you whether the website is active or paused. data_mode is separate: demo means the website contains fictional sample data, while live means customer data. Do not sync demo data into a CRM or warehouse.

One website
curl --fail-with-body \
  --header "Authorization: Bearer $PAGEFOX_API_KEY" \
  "https://pagefox.co/api/v1/leads?website_id=00000000-0000-4000-8000-000000000000&page=1"

Paused websites remain discoverable so you can read their historical data. A deleted website disappears from discovery. Websites outside your organization are never returned.

Organization-wide data calls exclude seeded demo-website data. When you explicitly scope to a returned data_mode: "demo" website ID, the response contains only that demo workspace's fictional data.

Common recipes

Prioritize a CRM or sales workspace

Call GET /websites once to select a data_mode: "live" website, then call GET /leads with leads:read and its website_id. The unified feed includes identified company rows and anonymous visitor rows; branch on kind instead of assuming every row has a company name.

Sync identified companies on a schedule

Call GET /companies with companies:read, follow pagination, and upsert by id. When locked is true, identity and last-session acquisition fields are deliberately redacted. Keep the row as locked; do not infer the missing values or overwrite richer CRM data with nulls. To see the underlying IP-to-company resolution on a single address before you wire the sync up, try the free reverse IP lookup tool.

Review conversations for follow-up

Call GET /conversations, then GET /conversations/{id} with conversations:read only when you need the message history. A missing and cross-organization conversation intentionally return the same 404.

React to hot or captured leads

Configure signed webhooks in Integrations for lead.hot or lead.captured, then follow the webhook reference to verify signatures and handle every event shape. Use webhooks when minutes matter; polling the REST API is better for reconciliation and scheduled syncs.

Understand the response states

Locked company

Your plan's company-metering limit can keep a company row visible while redacting identity fields. Check locked before using company details. The existing v1 source value describes identification provenance and remains for compatibility; it does not reveal the locked identity.

{
  "data": [
    {
      "kind": "company",
      "id": "company_example_locked",
      "name": "Locked company",
      "domain": null,
      "industry": null,
      "employee_count": null,
      "location": null,
      "relationship_status": "new",
      "source": "pdl",
      "match_confidence": null,
      "linkedin_url": null,
      "first_seen_at": "2026-08-20T09:30:00.000Z",
      "last_seen_at": "2026-08-21T11:15:00.000Z",
      "sessions_count": 3,
      "pages_viewed_total": 8,
      "max_lead_score": 88,
      "leads_count": 1,
      "conversations_count": 1,
      "locked": true,
      "person": null,
      "last_session_source": null
    }
  ],
  "pagination": {
    "page": 1,
    "per_page": 50,
    "total": 1,
    "total_pages": 1
  }
}

Anonymous visitor

A kind: "visitor" row is real activity that does not have an unlocked company identity. Use its behavior fields for prioritization without presenting it as a known person or company.

{
  "data": [
    {
      "kind": "visitor",
      "id": "visitor_row_example_1",
      "website_id": "00000000-0000-4000-8000-000000000000",
      "visitor_id": "visitor_example_1",
      "geo": {
        "country": "US"
      },
      "pages_viewed": 4,
      "last_page_url": "https://example.com/pricing",
      "time_on_site_seconds": 186,
      "visits_count": 2,
      "last_visit_started_at": "2026-08-21T11:10:00.000Z",
      "last_visit_pages_viewed": 3,
      "last_visit_time_on_site_seconds": 140,
      "source": "direct",
      "referrer": null,
      "utm_params": null,
      "device_type": "desktop",
      "lead_score": 72,
      "classification": "warm",
      "conversation_id": null,
      "first_seen_at": "2026-08-20T14:00:00.000Z",
      "last_seen_at": "2026-08-21T11:15:00.000Z",
      "identity_trail": null
    }
  ]
}

Empty page

An empty data array is a successful response, not an error. Stop paging when the requested page is at or beyond total_pages.

{
  "data": [],
  "pagination": {
    "page": 1,
    "per_page": 50,
    "total": 0,
    "total_pages": 1
  }
}

Live read operations

The canonical wire contract is the public OpenAPI 3.0 document. The specification is public; data calls require a Business-plan key with the listed scope.

MethodPathRequired scope
GET/websiteswebsites:read
GET/leadsleads:read
GET/companiescompanies:read
GET/sessionssessions:read
GET/conversationsconversations:read
GET/conversations/{id}conversations:read
GET/companies/{id}/peoplecontacts:read

Pagination and rate limits

Every list route takes page (1-based). Only two of them also read per_page — on the other three the page size is fixed by the underlying loader and a per_page you send is ignored rather than rejected, so check this table before you tune batch sizes.

PathAccepts per_pageRows per page
/websitesNoFixed at 50
/leadsNoFixed at 50
/companiesNoFixed at 50
/sessionsYes20 by default, up to 50
/conversationsYes20 by default, up to 50

A per_page outside 1–50 is not clamped: it falls back to the default of 20. Follow total_pages to know when to stop.

Requests are limited to 60 per minute per API key, not per organization or per IP — two keys get two budgets. Every response carries the remaining budget in its rate-limit headers. The site answers endpoint and outbound webhooks have their own separate budgets.

Errors and recovery

{
  "error": {
    "code": "unauthorized",
    "message": "Invalid or revoked API key."
  }
}
400 · invalid_request
website_id must be a UUID. Invalid page numbers fall back to page 1; invalid per_page values fall back to 20 on the routes that accept it.
401 · unauthorized
Check the Bearer header, key status, Business entitlement, and organization status. A revoked key can remain cached for up to 60 seconds; create a replacement rather than retrying a disclosed key.
403 · insufficient_scope
Ask an organization owner to create or replace the key with the exact live read scope. Members cannot manage organization keys.
404 · not_found
Refresh the resource ID from your own workspace. Missing and cross-organization resources intentionally look the same.
429 · rate_limited
You exceeded 60 requests per minute for this key. Pause until X-RateLimit-Reset, then retry with backoff. Use signed webhooks instead of tight polling for event-driven work.

What is not available yet

The current public API is read-only. PageFox does not currently publish a supported SDK, write API, MCP server, general agent connection, A2A endpoint, connector guarantee, SLA, or real-time REST stream. Reserved scope names do not make those capabilities live. Future agent and MCP adapters will share PageFox's permission model; an API key should not be pasted into an AI client as a substitute.

Make your first server-side call

Start free, connect a website, and move to Business when your trusted backend needs read-only API access.

Start Free