New customersGet 20% off your first payment on any plan.Not available for INR (₹) billing.Claim 20% off
Integrations

API reference

Prosyo REST API reference: authentication, add leads, enroll in campaigns, enrichment, bulk actions, prospects, inbox, Intent signals, limits and errors.

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 URLhttps://app.prosyo.com/api/v1/ext
AuthAuthorization: Bearer <token> on every request. Being signed in to Prosyo in the browser isn't enough.
Get a tokenIntegrations → Chrome extension & API → Generate API token (owners and admins)
FormatJSON in, JSON out
PlansAll 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:

JSON
{ "ok": true, "data": { … } }

Error:

JSON
{ "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.

StatusMeaning
200Success
401Missing or invalid token (unauthorized)
402Not enough credits (insufficient_credits)
403Your role doesn't allow this (forbidden), your plan isn't active (plan_inactive), or the monthly import limit is reached (plan_limit)
404Not found in this workspace
422Validation failed
429Rate limited (rate_limited). Wait a minute.
503AI isn't available right now (ai_unavailable). Try again later.

GET /session#

Check the token, and read the workspace, plan and remaining import quota.

Bash
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

FieldTypeNotes
leadsarray, 1–100Required
listNamestringAdds the leads to this list, creating it if needed
listIduuidOr add them to an existing list by ID. Leave out both to save without a list.
sourceTypestringextension (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 "".

Bash
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

JSON
{
  "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.

JSON
{ "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.

FieldTypeNotes
campaignIduuidRequired
prospectIdsuuid[], 1–100Required
Bash
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.

FieldTypeNotes
prospectIdsuuid[], 1–100Required
modeenrich_only | personalized_lineDefault enrich_only
useWorkspaceInstructionsbooleanUse your workspace AI instructions
customPromptstring, max 2,000 charactersExtra 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.

FieldTypeNotes
actiontag, untag, stage, add_to_list, remove_from_list, suppress, unsuppress, deleteRequired
prospectIdsuuid[], 1–500Required
tagId / tagNameFor tag / untag
listId / listNameFor add_to_list / remove_from_list
stagenew, contacted, replied, interested, meeting, customer, not_interestedFor stage

suppress marks people Do not contact. delete can't be undone.

JSON
{ "action": "stage", "prospectIds": ["…"], "stage": "interested" }

GET /prospects#

Search your prospects. Role: Viewer or higher.

Query paramExample
qq=acme
stagestage=replied
listIdlistId=…
tagIdtagId=…
campaignIdcampaignId=…
hasEmailhasEmail=true
hasLinkedinhasLinkedin=true
enrichmentStatusenrichmentStatus=enriched
createdFrom / createdToISO dates
page / pageSizePagination. pageSize is 10–100, default 25.
returnIdsreturnIds=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.

Bash
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.

FieldTypeNotes
signalsarray, 1–50Required
signals[].kindstringprofile_view, post_comment, post_reaction, invite_received, inbound_message, custom_signal, signup, form_submit
signals[].personobjectRequired: linkedinUrl (a linkedin.com/in/ profile) or email. Optional: name, title, company.
signals[].labelstring, max 120 charactersThe signal's name, for custom_signal
signals[].detailstring, max 1,000 charactersExtra context
signals[].urlURLWhere it happened
signals[].occurredAtISO dateWhen it happened. Default: now.
signals[].strength1–60How strong a custom_signal is. Default 30.
signals[].idstring, max 120 charactersYour own ID, so the same signal isn't counted twice
JSON
{ "signals": [ { "kind": "signup", "person": { "email": "[email protected]", "name": "Alex Rivera", "company": "Acme" } } ] }

Response data: { "accepted": 1, "ignored": 0, "paused": false }.

  • If you send both email and linkedinUrl, Prosyo uses the email. A signal sent by email needs someone already in your prospects with that email, except signup, form_submit and custom_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": true with a message. It starts again when you add credits or next month. If your plan isn't active, the request fails with 403 and plan_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 429 responses after a short wait. Don't retry a plan_limit or insufficient_credits error 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.