Push leads into GenPage, read the personalised pages it generates, and keep your own system in sync.
Visit https://app.genpage.ai and create your account.
API keys start with sk-proj-, never expire, and are bound to the workspace they were created in. A key cannot read or write any other workspace, so create one key per workspace you integrate with. Generating a key requires the workspace to be on a Scale plan or higher. Delete a key from the same screen to revoke it immediately.
Call GET /hello-world-user at any time to check a key: it returns the account it belongs to and the workspace_id it is bound to.
If you are wiring up Claude, ChatGPT, or another MCP-capable assistant, you do not need a key at all. Point it at the GenPage MCP server and it authenticates over OAuth: it sends you to GenPage, you choose which workspace to grant and approve, and it receives a token scoped to that one workspace. The assistant then has the same endpoints documented here, as tools.
Both credentials are equivalent to the API — bearer tokens, same permissions, same workspace binding. The difference is only how they are obtained and that OAuth tokens can be revoked from the dashboard without rotating a key.
Content type. Send Content-Type: application/json for POST bodies. Responses are always JSON, errors included.
Errors. Failures return a JSON body with a message; validation failures add a per-field errors object.
{
"message": "The workspace id field is required.",
"errors": { "workspace_id": ["The workspace id field is required."] }
}
| Status | Meaning |
|---|---|
401 |
Missing, malformed, or revoked API key |
402 |
Not enough credits to run the requested generation |
403 |
The key is valid but not authorized for this workspace |
404 |
The campaign, job, or token does not exist |
422 |
The request body or query string failed validation |
429 |
Rate limit exceeded |
503 |
The job exists but has not produced leads yet; retry |
Rate limits. 120 requests per minute per API key. Every response carries X-RateLimit-Limit and X-RateLimit-Remaining; a 429 also carries Retry-After.
Asynchronous work. Importing leads is queued. The upsert endpoints return a job_id immediately — poll /leads/get-status until the status is completed, then call /leads/get-leads for the results.
Call /workspaces/get-list. A workspace-bound key returns exactly one workspace — that is the workspace_id every other call must use.
Call /campaigns/get-list and /audiences/get-list, and let the user pick which campaign and audience to add leads to. Audience 0 is the built-in "Default Audience" holding every lead not assigned to an audience of its own.
A campaign carries the page design that every one of its leads' pages is built from. Campaigns created in the dashboard normally already have one — but a campaign created through /campaigns/create starts with none, and its pages render blank. This step is what prevents that, and it is the most common thing to miss.
Two ways to give it one:
/templates/get-list returns the designs available to the workspace — those built in the dashboard, plus GenPage's premade ones — and /templates/apply copies one onto the campaign. No credits, immediate.POST /pages/generate with a prompt describing the page. It answers with a run_id; poll /pages/generate/status until success. This spends credits and takes appreciably longer than applying a template, but writes a page specific to what you describe. By default it edits the campaign's current design, so a second prompt refines rather than restarts; pass replace: true to begin from a blank page.Either way, GET /pages/design returns the campaign's current HTML and CSS, so you can confirm a design exists — has_design: false means its pages would still be blank — or read back what the builder produced.
Both are destructive to the design already on the campaign. Pages generated earlier keep the design they were built with until they are regenerated.
Then publish it. A design is edited as a draft, and visitors are served the published snapshot — so neither applying a template nor generating a design puts anything live on its own. POST /pages/publish copies the draft over the published snapshot, and until you call it every generated link in the campaign shows a "not live yet" page. This is the single most common reason a link looks broken.
GET /pages/design reports both halves separately: has_design says a design exists, is_live says visitors can see it, and has_unpublished_changes says the draft has moved on from what is currently live. POST /pages/unpublish takes the pages offline again without deleting the design.
Call /workspaces/get-variable-list to get every existing variable for the workspace, and map your fields onto those keys. Keys that do not exist yet are accepted: upserting a lead with a new key creates a variable with that name.
Call /leads/upsert-leads (batch) or /leads/upsert-lead (single), passing the chosen campaign_id, an optional audience_id, and the leads as {"values": { ... }}.
To create a lead, values must include at least an email or a linkedin_profile_url. To update an existing one, identify it with lead_id or genpage_url. Both endpoints answer with a job_id.
Poll /leads/get-status?job_id=.... The status moves through pending / processing to completed or failed, alongside processed_rows and total_rows so you can show progress.
Call /leads/get-leads?job_id=.... While the job is still running this answers 503 with {"status": "NO_LEADS"} — retry. Once leads exist, each entry carries a genpage_url: the personalised page GenPage generated for that lead.
Need a test account? Contact us at team@genpage.ai and we'll help you get started.
To authenticate requests, include an Authorization header with the value "Bearer sk-proj-{YOUR_API_KEY}".
All authenticated endpoints are marked with a requires authentication badge in the documentation below.
Generate a key in the GenPage dashboard under Settings → Integrations. A key only works on the workspace it was created for.
There is a second way in. AI assistants connect through the GenPage MCP server, which authenticates with OAuth 2.1 rather than a key: the
assistant sends you to GenPage, you pick a workspace and approve, and it receives a token scoped to that one workspace. Every endpoint below
accepts either credential, and both are subject to the same workspace binding — so an OAuth token cannot reach a workspace you did not choose
at consent, exactly as a key cannot reach one it was not created for.
Enrichment and AI generation spend credits, so an integration can check what is left before starting work that would fail part-way through.
Credits are pooled across an account: remaining is what the whole pool
has left, while workspace_used and workspace_limit describe this one
workspace's share of it. A null workspace_limit means uncapped — the
workspace can draw on the whole pool.
The workspace to report on.
curl --request GET \
--get "https://backend.genpage.ai/api/external/v1/account/credits?workspace_id=55" \
--header "Authorization: Bearer sk-proj-{YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" {
"plan": "scale",
"remaining": 4200,
"workspace_used": 800,
"workspace_limit": null,
"renews_at": "2026-09-01 00:00:00"
}
This endpoint is used to get the list of audiences.
Audience 0 is the built-in "Default Audience": every lead in the
workspace that has not been added to an audience of its own.
The workspace to list audiences for.
curl --request GET \
--get "https://backend.genpage.ai/api/external/v1/audiences/get-list?workspace_id=55" \
--header "Authorization: Bearer sk-proj-{YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" [
{
"id": 0,
"name": "Default Audience",
"description": "Leads that have not been added to a specific audience",
"color": null,
"lead_count": 12,
"project_id": 55,
"created_at": "2025-01-01T00:00:00.000000Z",
"updated_at": "2025-01-01T00:00:00.000000Z"
},
{
"id": 1,
"name": "Enterprise prospects",
"description": null,
"color": "#4F46E5",
"lead_count": 340,
"project_id": 55,
"created_at": "2026-08-01T10:00:00.000000Z",
"updated_at": "2026-08-01T10:00:00.000000Z"
}
]
An audience is a named segment of leads. Create one to group leads before generating pages for them, or to keep a list you can link to a campaign.
curl --request POST \
"https://backend.genpage.ai/api/external/v1/audiences/create" \
--header "Authorization: Bearer sk-proj-{YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"workspace_id\": 55,
\"name\": \"Enterprise prospects\",
\"description\": \"Q3 outbound list\",
\"color\": \"#4F46E5\",
\"lead_ids\": [
56789,
56790
]
}"
{
"audience_id": 44,
"name": "Enterprise prospects",
"description": "Q3 outbound list",
"lead_count": 2
}
curl --request POST \
"https://backend.genpage.ai/api/external/v1/audiences/add-leads" \
--header "Authorization: Bearer sk-proj-{YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"workspace_id\": 55,
\"audience_id\": 44,
\"lead_ids\": [
56789
]
}"
{
"audience_id": 44,
"added": 1,
"skipped": 0,
"lead_count": 3
}
curl --request POST \
"https://backend.genpage.ai/api/external/v1/audiences/remove-leads" \
--header "Authorization: Bearer sk-proj-{YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"workspace_id\": 55,
\"audience_id\": 44,
\"lead_ids\": [
56789
]
}"
{
"audience_id": 44,
"removed": 1,
"lead_count": 2
}
curl --request POST \
"https://backend.genpage.ai/api/external/v1/audiences/link-to-campaign" \
--header "Authorization: Bearer sk-proj-{YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"workspace_id\": 55,
\"audience_id\": 44,
\"campaign_id\": 123
}"
{
"audience_id": 44,
"campaign_id": 123,
"message": "Audience linked to campaign."
}
Removes the link only — the audience, its leads, and any pages already generated for them are untouched.
curl --request POST \
"https://backend.genpage.ai/api/external/v1/audiences/unlink-from-campaign" \
--header "Authorization: Bearer sk-proj-{YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"workspace_id\": 55,
\"audience_id\": 44,
\"campaign_id\": 123
}"
{
"audience_id": 44,
"campaign_id": 123,
"message": "Audience unlinked from campaign."
}
Only the segment is removed. Unlike the dashboard's version, this endpoint has no option to delete the leads themselves: that is irreversible, and nothing about removing a grouping implies destroying the people in it. Leads that were only reachable through this audience remain in the workspace and are still findable with leads/search.
curl --request POST \
"https://backend.genpage.ai/api/external/v1/audiences/delete" \
--header "Authorization: Bearer sk-proj-{YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"workspace_id\": 55,
\"audience_id\": 44
}"
{
"audience_id": 44,
"leads_kept": 12,
"message": "Audience deleted. Its leads were kept."
}
This endpoint returns the generated token to be used in the external integration. The token is bound to the workspace it was generated for and cannot be used against any other workspace. It never expires; delete it to revoke access.
curl --request POST \
"https://backend.genpage.ai/api/external/v1/generate-api-token" \
--header "Authorization: Bearer sk-proj-{YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"name\": \"Zapier production\",
\"workspace_id\": 55
}"
{
"token": "sk-proj-<Bearer token>"
}
Returns the workspace's own keys plus any legacy keys that were created before keys were bound to a workspace (those still work everywhere, so hiding them would leave the user unable to find and revoke them).
The workspace to list keys for.
curl --request GET \
--get "https://backend.genpage.ai/api/external/v1/get-api-tokens?workspace_id=55" \
--header "Authorization: Bearer sk-proj-{YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" {
"tokens": [
{
"id": "9f1c...",
"name": "Zapier production",
"created_at": "2026-08-16T10:00:00.000000Z"
}
]
}
The id returned by get-api-tokens.
curl --request DELETE \
"https://backend.genpage.ai/api/external/v1/delete-api-token?token_id=9f1c..." \
--header "Authorization: Bearer sk-proj-{YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" {
"message": "API token deleted successfully"
}
This endpoint is used to test the authentication.
curl --request GET \
--get "https://backend.genpage.ai/api/external/v1/hello-world" \
--header "Authorization: Bearer sk-proj-{YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" {
"message": "Hello World"
}
Returns the account the API key belongs to, and the workspace the key is bound to (null for keys created before keys were workspace-bound, which may act on every workspace the account can reach).
curl --request GET \
--get "https://backend.genpage.ai/api/external/v1/hello-world-user" \
--header "Authorization: Bearer sk-proj-{YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" {
"message": "Hello World",
"user": {
"id": 123,
"name": "John Doe",
"email": "john@example.com"
},
"workspace_id": 55
}
This endpoint is used to get the list of campaigns.
The workspace to list campaigns for.
curl --request GET \
--get "https://backend.genpage.ai/api/external/v1/campaigns/get-list?workspace_id=55" \
--header "Authorization: Bearer sk-proj-{YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" [
{
"id": 1,
"name": "Test Campaign 1",
"slug": "test-campaign-1"
},
{
"id": 2,
"name": "Saas Outreach",
"slug": "saas-outreach"
}
]
A campaign holds the page template and branding that generated pages use. Create one when an integration needs its own campaign rather than reusing an existing one.
The campaign is created empty — it still needs a page design. Give it
one over the API with templates/apply or pages/generate (or build
one in the GenPage dashboard) and publish it. Generating pages against
a campaign with no design will produce blank pages, so create-then-generate
is not a complete flow on its own.
curl --request POST \
"https://backend.genpage.ai/api/external/v1/campaigns/create" \
--header "Authorization: Bearer sk-proj-{YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"workspace_id\": 55,
\"name\": \"Enterprise Outreach\",
\"branding_id\": 7,
\"prompt\": \"outbound to fintech CTOs\"
}"
{
"campaign_id": 231,
"name": "Enterprise Outreach",
"slug": "enterprise-outreach",
"note": "The campaign has no page design yet — apply a template (templates/apply) or generate a design (pages/generate) and publish it before generating pages."
}
Reports how the generated pages are doing: how many were visited, how many visitors clicked, and how long they stayed.
visit_percentage is pages visited as a share of pages generated, so it
measures reach rather than conversion. click_percentage is the share of
unique visitors who clicked something.
The workspace to report on.
Limit to these campaigns. Defaults to all of them.
curl --request GET \
--get "https://backend.genpage.ai/api/external/v1/campaigns/analytics?workspace_id=55&campaign_ids[]=123&campaign_ids[]=124" \
--header "Authorization: Bearer sk-proj-{YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" {
"campaigns": [
{
"campaign_id": 123,
"name": "Enterprise Outreach",
"visit_percentage": 42.5,
"click_percentage": 18.2,
"average_time_on_page_seconds": 47,
"pages_visited": 85,
"unique_visitors": 71,
"total_button_clicks": 19
}
]
}
A branding is the visual identity a campaign renders with — logo,
colours, fonts. Call this to resolve a branding_id for
campaigns/create; brandings themselves are built in the dashboard.
The workspace to list for.
curl --request GET \
--get "https://backend.genpage.ai/api/external/v1/brandings/get-list?workspace_id=55" \
--header "Authorization: Bearer sk-proj-{YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" [
{
"branding_id": 7,
"name": "Acme Corp",
"is_default": true
}
]
Returns the HTML and CSS the campaign renders. Every generated page is this design with the lead's variables substituted in, so this is what to read to see what the pages actually say.
The workspace the campaign belongs to.
From campaigns/get-list.
curl --request GET \
--get "https://backend.genpage.ai/api/external/v1/pages/design?workspace_id=55&campaign_id=231" \
--header "Authorization: Bearer sk-proj-{YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" {
"campaign_id": 231,
"has_design": true,
"is_live": true,
"has_unpublished_changes": false,
"html": "<section>…</section>",
"css": "body{…}",
"updated_at": "2026-08-17T09:00:00.000000Z",
"note": null
}
The builder writes its result straight onto the campaign, so once the run finishes, pages/design returns the new HTML and newly generated pages use it. Generation spends credits; with none left the request fails with 402.
By default the builder edits the campaign's existing design. Pass
replace: true to have it start from a blank page instead.
curl --request POST \
"https://backend.genpage.ai/api/external/v1/pages/generate" \
--header "Authorization: Bearer sk-proj-{YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"workspace_id\": 55,
\"campaign_id\": 231,
\"prompt\": \"A landing page for fintech CTOs, with a hero, three benefits, and a demo CTA.\",
\"replace\": false
}"
{
"run_id": "run_01H…",
"status": "queued",
"campaign_id": 231
}
status is one of queued, running, success, error, cancelled.
On success, read the result with pages/design.
The workspace the run belongs to.
From pages/generate.
curl --request GET \
--get "https://backend.genpage.ai/api/external/v1/pages/generate/status?workspace_id=55&run_id=run_01H%E2%80%A6" \
--header "Authorization: Bearer sk-proj-{YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" {
"run_id": "run_01H…",
"status": "running",
"campaign_id": 231,
"error": null
}
A design is edited as a draft. Visitors are served the published snapshot, so a campaign whose design has never been published shows a "not live yet" page to anyone who opens a generated link. This copies the current draft over the published snapshot.
Publishing replaces what is currently live, and takes effect immediately for every page in the campaign — including ones generated earlier.
curl --request POST \
"https://backend.genpage.ai/api/external/v1/pages/publish" \
--header "Authorization: Bearer sk-proj-{YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"workspace_id\": 55,
\"campaign_id\": 231
}"
{
"campaign_id": 231,
"is_live": true,
"published_at": "2026-08-17T09:00:00.000000Z",
"message": "Design published. Pages in this campaign are now live."
}
Clears the published snapshot. Every generated link in the campaign then shows a "not live yet" page. The design itself is untouched — it stays as a draft and can be published again.
curl --request POST \
"https://backend.genpage.ai/api/external/v1/pages/unpublish" \
--header "Authorization: Bearer sk-proj-{YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"workspace_id\": 55,
\"campaign_id\": 231
}"
{
"campaign_id": 231,
"is_live": false,
"message": "Pages in this campaign are no longer live. The design is kept as a draft."
}
Two kinds come back. workspace templates were built by this account in
the GenPage dashboard; premade ones ship with GenPage and are available
to everyone. Either can be applied to a campaign.
The workspace to list for.
Match on template name.
curl --request GET \
--get "https://backend.genpage.ai/api/external/v1/templates/get-list?workspace_id=55&search=saas" \
--header "Authorization: Bearer sk-proj-{YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" [
{
"template_id": 88,
"name": "SaaS outbound",
"description": "Hero, social proof, single CTA",
"source": "workspace"
},
{
"template_id": 12,
"name": "Minimal landing",
"description": null,
"source": "premade"
}
]
Copies the page design onto the campaign, replacing whatever design it had. Use it right after creating a campaign — until a campaign has a design, the pages generated for it are blank.
This overwrites: a campaign that already has a design loses it, and pages generated afterwards use the new one. Pages already generated keep what they were built with until they are regenerated.
curl --request POST \
"https://backend.genpage.ai/api/external/v1/templates/apply" \
--header "Authorization: Bearer sk-proj-{YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"workspace_id\": 55,
\"campaign_id\": 231,
\"template_id\": 88
}"
{
"campaign_id": 231,
"template_id": 88,
"is_live": false,
"message": "Template applied. Publish the design to make this campaign's pages live."
}
This endpoint is used to get the leads that were upserted by a job.
The job id returned by an upsert endpoint.
curl --request GET \
--get "https://backend.genpage.ai/api/external/v1/zapier/get-leads-from-job?job_id=9f1c8b2e-..." \
--header "Authorization: Bearer sk-proj-{YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" [
{
"lead_id": 56789,
"genpage_url": "https://myworkspace.genpa.ge/aws-john",
"name": "John Doe",
"email": "john@aws.com",
"company_name": "AWS"
}
]
Free-text search across name, email, and company, narrowed by campaign, audience, or how the lead has engaged with its page.
The workspace to search.
Matches name, email, or company.
Only leads on these campaigns.
Only leads in these audiences.
Filter by engagement: all, page_viewed, button_clicked, scroll_100_percent, form_submitted.
How many to return, 1-100. Defaults to 25.
curl --request GET \
--get "https://backend.genpage.ai/api/external/v1/leads/search?workspace_id=55&search=acme&campaign_ids[]=1&campaign_ids[]=2&audience_ids[]=3&engagement[]=form_submitted&limit=25" \
--header "Authorization: Bearer sk-proj-{YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" {
"total": 340,
"returned": 25,
"leads": [
{
"lead_id": 56789,
"name": "John Doe",
"email": "john@aws.com",
"company_name": "AWS",
"company_domain": "aws.com",
"genpage_url": "https://myworkspace.genpa.ge/aws-john",
"created_at": "2026-08-16T10:00:00.000000Z"
}
]
}
From search-leads or an upsert job.
curl --request GET \
--get "https://backend.genpage.ai/api/external/v1/leads/get?lead_id=56789" \
--header "Authorization: Bearer sk-proj-{YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" {
"lead_id": 56789,
"name": "John Doe",
"email": "john@aws.com",
"company_name": "AWS",
"company_domain": "aws.com",
"genpage_url": "https://myworkspace.genpa.ge/aws-john",
"variables": {
"linkedin_profile_url": "https://...",
"pain_point": "..."
},
"audiences": [
{
"id": 3,
"name": "Enterprise"
}
],
"pending_enrichments": [
"company_domain"
],
"created_at": "2026-08-16T10:00:00.000000Z"
}
Each type is a separate piece of research, and each costs credits per lead. Ask for only what the pages actually use.
linkedin_profile_url — scrape the lead's LinkedIn profilecompany_domain — AI analysis of the companyperson_analysis — AI analysis of the personlinkedin_posts_person — the person's recent postslinkedin_posts_company — the company page's recent postsA lead missing the inputs a type needs (no LinkedIn URL, no domain) is
skipped rather than failing the batch — compare leads_to_enrich against
what you sent, and check skipped_leads. Results land on the leads
themselves; poll job_id with get-status, then read them back with
get-lead.
curl --request POST \
"https://backend.genpage.ai/api/external/v1/leads/enrich" \
--header "Authorization: Bearer sk-proj-{YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"workspace_id\": 55,
\"lead_ids\": [
56789,
56790
],
\"enrichment_types\": [
\"company_domain\"
]
}"
{
"job_id": "9f1c8b2e-...",
"message": "Enrichment job started",
"leads_to_enrich": 2,
"skipped_leads": 0
}
This endpoint is used to upsert (update or insert) leads.
The leads are sent in the request body. The leads are an array of objects. The leads.*.values are key/value pairs with the values of the lead.
To create a new lead, at least one of:
emaillinkedin_profile_urlTo update an existing lead, at least one of:
lead_id (GenPage Lead ID)genpage_url (GenPage URL)first_namecompany_namecompany_domainThe response is the job ID.
The job ID can be used to get the status of the job, and get all the leads that were upserted by this request.
curl --request POST \
"https://backend.genpage.ai/api/external/v1/leads/upsert-leads" \
--header "Authorization: Bearer sk-proj-{YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"from\": \"MyCompanyName\",
\"campaign_id\": 1,
\"audience_id\": 1,
\"callback_url\": \"https:\\/\\/example.com\\/callback\",
\"send_email\": false,
\"is_generate_ai_analysis\": true,
\"enrich_data\": false,
\"generate_ai_variables\": false,
\"leads\": [
{
\"values\": {
\"last_name\": \"Doe\",
\"company_domain\": \"amazon.com\",
\"my_custom_variable\": \"My custom value\"
}
}
]
}"
{
"job_id": "9f1c8b2e-..."
}
This endpoint is used to upsert a single lead.
The "values" object requires at least one of the following fields, otherwise it will throw an error:
curl --request POST \
"https://backend.genpage.ai/api/external/v1/leads/upsert-lead" \
--header "Authorization: Bearer sk-proj-{YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"from\": \"MyCompanyName\",
\"campaign_id\": 1,
\"audience_id\": 1,
\"callback_url\": \"https:\\/\\/example.com\\/callback\",
\"send_email\": false,
\"is_generate_ai_analysis\": true,
\"enrich_data\": false,
\"generate_ai_variables\": false,
\"lead_id\": 16,
\"genpage_url\": \"https:\\/\\/myworkspace.genpa.ge\\/aws-john\",
\"values\": {
\"first_name\": \"John\",
\"email\": \"john@aws.com\",
\"company_name\": \"Amazon Web Services\",
\"linkedin_profile_url\": \"https:\\/\\/www.linkedin.com\\/in\\/john-doe-1234567890\"
}
}"
{
"job_id": "9f1c8b2e-..."
}
Use this when the leads are not tied to a landing page yet; link them to campaigns later through an audience.
The response depends on how the import ran. Queued imports (the default,
and always the case when any enrichment or campaign is requested) answer
with a job_id to poll on /leads/get-status. Immediate imports
(sync: true with no enrichment and no campaigns) answer with the result
directly and carry a job_id of null.
curl --request POST \
"https://backend.genpage.ai/api/external/v1/leads/import-to-workspace" \
--header "Authorization: Bearer sk-proj-{YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"workspace_id\": 55,
\"leads\": [
{
\"values\": {
\"first_name\": \"John\",
\"email\": \"john@aws.com\",
\"company_name\": \"Amazon Web Services\"
}
}
],
\"from\": \"MyCompanyName\",
\"send_email\": false,
\"is_generate_ai_analysis\": false,
\"is_generate_person_analysis\": false,
\"is_scrape_person_posts\": false,
\"is_scrape_company_posts\": false,
\"enrich_data\": false,
\"audience_id\": 3,
\"sync\": false,
\"campaign_ids\": [
1,
2
],
\"generate_ai_variables\": false,
\"trigger_audience_summary\": false
}"
{
"job_id": "9f1c8b2e-..."
}
This endpoint is used to get the status of the job. Once the job is completed, the status will be "completed".
The status can be:
Once the job is completed, please use the getLeads endpoint to get all the leads that were upserted by this request.
The job id returned by an upsert endpoint.
curl --request GET \
--get "https://backend.genpage.ai/api/external/v1/leads/get-status?job_id=9f1c8b2e-..." \
--header "Authorization: Bearer sk-proj-{YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" {
"status": "pending",
"processed_rows": 10,
"total_rows": 100
}
This endpoint is used to get the leads that were upserted by a job.
The job id returned by an upsert endpoint.
curl --request GET \
--get "https://backend.genpage.ai/api/external/v1/leads/get-leads?job_id=9f1c8b2e-..." \
--header "Authorization: Bearer sk-proj-{YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" [
{
"lead_id": 56789,
"genpage_url": "https://myworkspace.genpa.ge/aws-john",
"name": "John Doe",
"email": "john@aws.com",
"company_name": "AWS"
}
]
This endpoint is used to get the list of workspaces the user has access to.
curl --request GET \
--get "https://backend.genpage.ai/api/external/v1/workspaces/get-list" \
--header "Authorization: Bearer sk-proj-{YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" [
{
"id": 1,
"name": "My Company Name",
"slug": "my-company-name"
},
{
"id": 2,
"name": "SAAS Outreach",
"slug": "saas-outreach"
}
]
This endpoint retrieves all available variables/columns for a specific workspace. It returns both default variables (first_name, email, company_name, company_domain) and any custom variables that have been created for the workspace.
When updating or inserting a lead, you can use the variables/columns from this endpoint to add the values to the specific variable/column for the lead.
The workspace to list the variables of.
curl --request GET \
--get "https://backend.genpage.ai/api/external/v1/workspaces/get-variable-list?workspace_id=55" \
--header "Authorization: Bearer sk-proj-{YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" [
{
"key": "linkedin_profile_url",
"helpText": "LinkedIn profile URL (at least one of LinkedIn URL, Company Domain, or Email is required)"
},
{
"key": "email",
"helpText": "Email address (at least one of LinkedIn URL, Company Domain, or Email is required)"
},
{
"key": "company_domain",
"helpText": "Company domain (at least one of LinkedIn URL, Company Domain, or Email is required)"
},
{
"key": "first_name",
"helpText": "First name"
},
{
"key": "company_name",
"helpText": "Company name"
},
{
"key": "My Custom Variable1"
},
{
"key": "custom_variable2"
}
]
This endpoint is used to test the authentication.
curl --request GET \
--get "https://backend.genpage.ai/api/external/v1/zapier/test" \
--header "Authorization: Bearer sk-proj-{YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" {
"message": "Hello World",
"user": {
"id": 123,
"name": "John Doe",
"email": "john@example.com"
}
}
Set is_test to true to have the endpoint answer with a sample page URL
without importing anything — this is what Zapier calls when the user tests
the step while building a Zap.
curl --request POST \
"https://backend.genpage.ai/api/external/v1/zapier/upsert-lead" \
--header "Authorization: Bearer sk-proj-{YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"workspace_id\": 55,
\"campaign_id\": 123,
\"is_test\": false,
\"linkedin_profile_url\": \"https:\\/\\/www.linkedin.com\\/in\\/williamhgates\\/\",
\"company_domain\": \"example.com\",
\"email\": \"test@example.com\",
\"callback_url\": \"https:\\/\\/example.com\\/callback\",
\"custom_data\": {
\"key\": \"value\"
}
}"
{
"message": "Job started!",
"job_id": "9f1c8b2e-..."
}
Same as upsert-lead, but the callback URL is optional: use this when the
Zap does not need GenPage to call back once the page has been generated.
curl --request POST \
"https://backend.genpage.ai/api/external/v1/zapier/upsert-lead-basic" \
--header "Authorization: Bearer sk-proj-{YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"workspace_id\": 55,
\"campaign_id\": 123,
\"is_test\": false,
\"callback_url\": \"https:\\/\\/example.com\\/callback\",
\"generate_ai_variables\": true,
\"linkedin_profile_url\": \"https:\\/\\/www.linkedin.com\\/in\\/williamhgates\\/\",
\"company_domain\": \"example.com\",
\"email\": \"test@example.com\",
\"custom_data\": {
\"key\": \"value\"
}
}"
{
"message": "Job started!",
"job_id": "9f1c8b2e-..."
}
Returns the newest leads first. Pass created_after and/or last_lead_id
from the previous poll to only get what is new since then.
The workspace to read from.
The campaign to read from.
Only return leads created at or after this timestamp.
Only return leads with an id greater than this.
How many leads to return, 1-100. Defaults to 50.
Return the most recent lead when the filters match nothing, so a Zap test has sample data.
curl --request GET \
--get "https://backend.genpage.ai/api/external/v1/zapier/get-leads?workspace_id=55&campaign_id=123&created_after=2026-08-01T00%3A00%3A00Z&last_lead_id=4200&limit=50&is_test=" \
--header "Authorization: Bearer sk-proj-{YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" [
{
"id": 56789,
"created_at": "2026-08-16T10:00:00.000000Z",
"genpage_url": "https://myworkspace.genpa.ge/aws-john",
"name": "John Doe",
"email": "john@aws.com",
"company_name": "AWS",
"domain": "aws.com",
"company_name_slug": "aws"
}
]
Without workspace_id this returns submissions from every workspace the
API key can reach, newest first.
The workspace to read from.
Only return submissions from this campaign.
How many submissions to return, 1-100. Defaults to 50.
curl --request GET \
--get "https://backend.genpage.ai/api/external/v1/zapier/get-form-submissions?workspace_id=55&campaign_id=123&limit=50" \
--header "Authorization: Bearer sk-proj-{YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" [
{
"id": 91,
"form_id": "contact",
"page_url": "https://myworkspace.genpa.ge/aws-john",
"ip_address": "203.0.113.10",
"created_at": "2026-08-16T10:00:00+00:00",
"email": "john@aws.com"
}
]