- Prosyo Docs
- Integrations
- API reference
On this page
The Prosyo API lets your own tools add leads, enroll prospects in campaigns, enrich, organize, read your inbox and send buying signals to Intent. The Chrome extension uses the same API.
Basics#
| Base URL | https://app.prosyo.com/api/v1/ext |
| Auth | Authorization: Bearer <token> on every request. Being signed in to Prosyo in the browser isn't enough. |
| Get a token | Integrations → Chrome extension & API → Generate API token (owners and admins) |
| Format | JSON in, JSON out |
| Plans | All plans. Your plan's limits apply. |
Tokens act as the user who created them, with that user's role and workspace, and they stop working if that user leaves the workspace. Prosyo shows a token once. When your workspace already has one, the button reads Rotate token: it replaces every token in the workspace, so paste the new one into every tool and Chrome extension install. Revoke all tokens disconnects every install.
Response envelope#
Success:
{ "ok": true, "data": { … } }Error:
{ "ok": false, "error": { "code": "validation_error", "message": "…", "fieldErrors": { "email": ["Invalid email"] }, "correlationId": "a1b2c3d4" } }Include the correlationId when you contact support about a failed request. Check error.code rather than the message text, because messages can change.
| Status | Meaning |
|---|---|
200 | Success |
401 | Missing or invalid token (unauthorized) |
402 | Not enough credits (insufficient_credits) |
403 | Your role doesn't allow this (forbidden), your plan isn't active (plan_inactive), or the monthly import limit is reached (plan_limit) |
404 | Not found in this workspace |
422 | Validation failed |
429 | Rate limited (rate_limited). Wait a minute. |
503 | AI isn't available right now (ai_unavailable). Try again later. |
GET /session#
Check the token, and read the workspace, plan and remaining import quota.
curl https://app.prosyo.com/api/v1/ext/session \
-H "Authorization: Bearer $PROSYO_TOKEN"Response data includes user (email, displayName and role), workspace (id and name), plan (code and name), quota (monthlyImports, usedThisMonth and remaining) and appUrl. In a client workspace, the import quota is shared with your agency.
POST /leads#
Add or update prospects, optionally on a list. Role: Member or higher. Limit: 60 requests per minute.
Body
| Field | Type | Notes |
|---|---|---|
leads | array, 1–100 | Required |
listName | string | Adds the leads to this list, creating it if needed |
listId | uuid | Or add them to an existing list by ID. Leave out both to save without a list. |
sourceType | string | extension (default), extension_profile, extension_search, extension_comments |
Lead fields: fullName, firstName, lastName, title, companyName, linkedinUrl, website, email, phone, locationName. Each lead needs a name, LinkedIn URL or email. Leave empty fields out rather than sending "".
curl -X POST https://app.prosyo.com/api/v1/ext/leads \
-H "Authorization: Bearer $PROSYO_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"listName": "API test",
"leads": [
{ "firstName": "Alex", "lastName": "Rivera", "title": "Head of Sales",
"companyName": "Acme", "email": "[email protected]",
"linkedinUrl": "https://www.linkedin.com/in/alexrivera" }
]
}'Response data
{
"importJobId": "…",
"received": 1,
"processed": 1,
"inserted": 1,
"skipped": 0,
"failed": 0,
"quotaBlocked": 0,
"listId": "…",
"prospectIds": ["…"],
"quota": { "monthlyImports": 5000, "remaining": 4999 }
}prospectIds includes both new and already-existing people, so you can pass it straight to /campaigns or /enrich. Leads are matched to people already in Prosyo by their LinkedIn profile only, so send linkedinUrl whenever you have it. A lead sent without a LinkedIn URL is not matched by email: it's added as a new person and counts toward your import limit, and if someone in Prosyo already has that email, the new person is saved without it.
inserted counts new people, the only ones that use import quota. skipped counts people who were already in Prosyo (they're updated) plus those not added because the monthly import limit was reached, which also appear in quotaBlocked. If every person in the request is new and the limit is already reached, the request fails with 403 and plan_limit.
GET /campaigns#
List up to 50 draft, running and paused campaigns. Role: Viewer or higher.
{ "campaigns": [ { "id": "…", "name": "UK founders", "status": "running", "enrolled": 142 } ] }POST /campaigns#
Enroll prospects in a campaign. Role: Member or higher. Limit: 30 requests per minute.
| Field | Type | Notes |
|---|---|---|
campaignId | uuid | Required |
prospectIds | uuid[], 1–100 | Required |
curl -X POST https://app.prosyo.com/api/v1/ext/campaigns \
-H "Authorization: Bearer $PROSYO_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "campaignId": "CAMPAIGN_ID", "prospectIds": ["PROSPECT_ID"] }'Response data: { "enrolled": 1, "skipped": 0, "campaignName": "UK founders" }
People already in the campaign, marked Do not contact, or not visible to the token's user are skipped, not double-enrolled. A running campaign starts them inside its working hours, at its usual pace. In a draft or paused campaign they wait until you launch or resume it.
POST /enrich#
Queue AI research or a first-touch line. Role: Member or higher. Uses 5 credits per prospect for enrich_only and 2 credits per prospect for personalized_line, and failures are refunded.
| Field | Type | Notes |
|---|---|---|
prospectIds | uuid[], 1–100 | Required |
mode | enrich_only | personalized_line | Default enrich_only |
useWorkspaceInstructions | boolean | Use your workspace AI instructions |
customPrompt | string, max 2,000 characters | Extra guidance for the line |
Response data: { "jobId": "…", "total": 1, "cost": 5 }. If your balance can't cover the cost, nothing is queued and you get 402 with insufficient_credits.
Check progress with GET /enrich?jobId=…. It returns the job's status, total, completed, found, not_found, failed and credit_cost.
POST /bulk#
Organize up to 500 prospects at once. Role: Member or higher.
| Field | Type | Notes |
|---|---|---|
action | tag, untag, stage, add_to_list, remove_from_list, suppress, unsuppress, delete | Required |
prospectIds | uuid[], 1–500 | Required |
tagId / tagName | For tag / untag | |
listId / listName | For add_to_list / remove_from_list | |
stage | new, contacted, replied, interested, meeting, customer, not_interested | For stage |
suppress marks people Do not contact. delete can't be undone.
{ "action": "stage", "prospectIds": ["…"], "stage": "interested" }GET /prospects#
Search your prospects. Role: Viewer or higher.
| Query param | Example |
|---|---|
q | q=acme |
stage | stage=replied |
listId | listId=… |
tagId | tagId=… |
campaignId | campaignId=… |
hasEmail | hasEmail=true |
hasLinkedin | hasLinkedin=true |
enrichmentStatus | enrichmentStatus=enriched |
createdFrom / createdTo | ISO dates |
page / pageSize | Pagination. pageSize is 10–100, default 25. |
returnIds | returnIds=true returns only IDs |
GET /boards#
Returns your lists, tags and pipeline stages, with the IDs you need for /bulk and /leads. Role: Viewer or higher.
GET /inbox#
Returns the latest conversations visible to the token's user. Role: Viewer or higher.
GET /intent#
Read a LinkedIn profile's Intent priority, the same badge the Chrome extension shows. Role: Viewer or higher.
curl "https://app.prosyo.com/api/v1/ext/intent?linkedinUrl=https://www.linkedin.com/in/alexrivera" \
-H "Authorization: Bearer $PROSYO_TOKEN"Response data: { "lead": null } when the person isn't in Intent. Otherwise lead has tier, tierLabel (High-intent ICP, Warm or Warming up), intentScore, fitScore, topSignal, locked (true until the person is unlocked in Intent) and url (where to open them in Prosyo).
POST /signals#
Send buying signals from your own systems to Intent, such as a product sign-up or a form. Role: Member or higher.
| Field | Type | Notes |
|---|---|---|
signals | array, 1–50 | Required |
signals[].kind | string | profile_view, post_comment, post_reaction, invite_received, inbound_message, custom_signal, signup, form_submit |
signals[].person | object | Required: linkedinUrl (a linkedin.com/in/ profile) or email. Optional: name, title, company. |
signals[].label | string, max 120 characters | The signal's name, for custom_signal |
signals[].detail | string, max 1,000 characters | Extra context |
signals[].url | URL | Where it happened |
signals[].occurredAt | ISO date | When it happened. Default: now. |
signals[].strength | 1–60 | How strong a custom_signal is. Default 30. |
signals[].id | string, max 120 characters | Your own ID, so the same signal isn't counted twice |
{ "signals": [ { "kind": "signup", "person": { "email": "[email protected]", "name": "Alex Rivera", "company": "Acme" } } ] }Response data: { "accepted": 1, "ignored": 0, "paused": false }.
- If you send both
emailandlinkedinUrl, Prosyo uses the email. A signal sent by email needs someone already in your prospects with that email, exceptsignup,form_submitandcustom_signal, which can add a new person to Intent. - When this month's Intent people are used up and you have fewer than 10 credits, nothing is stored and the response has
"paused": truewith amessage. It starts again when you add credits or next month. If your plan isn't active, the request fails with403andplan_inactive.
Outgoing events#
To receive events from Prosyo (replies, stage changes and so on), use signed webhooks.
Good practice#
- Keep tokens in a secrets manager or your automation tool's credentials.
- Batch requests (up to 100 leads) instead of one request per person.
- Retry
429responses after a short wait. Don't retry aplan_limitorinsufficient_creditserror until the quota resets or credits are added. - Generate tokens from a long-term owner or admin account, so a teammate leaving doesn't break your integrations.
See Custom triggers for end-to-end recipes.
