Prospexly

Developers

Prospexly API

A REST API over the same records the app uses: contacts, companies, opportunities, tasks and AI lead search. JSON in, JSON out, and field names that match the CRM’s own columns rather than a translation layer you have to learn.

Machine-readable description: openapi.json (OpenAPI 3.1). Point a client generator at it rather than writing request types by hand.

Authentication

Every request carries a key created in Settings. Keys are stored only as an Argon2id hash, so the value is shown once and cannot be recovered afterwards.

curl https://prospexly.com/api/v1/contacts?page_size=10 \
  -H "Authorization: Bearer pxly_YOUR_KEY"

Base URL https://prospexly.com/api/v1. An absent, malformed, revoked or expired key is a 401. A key that is valid but lacks the scope an endpoint needs is a 403, whatever your plan.

Scopes

Each key carries an explicit set of scopes chosen at creation. Grant the fewest that do the job — a key that only reads cannot be used to delete your pipeline.

Contacts

  • contacts.read
  • contacts.write

Companies

  • companies.read
  • companies.write

Opportunities

  • opportunities.read
  • opportunities.write

Tasks

  • tasks.read
  • tasks.write

Lead search

  • leads.search

Rate limits

Two limits apply. A burst limit of 500 requests a minute per key, and a daily limit set by your plan.

PlanRequests per day
Free100
Starter1,000
Professional10,000
EnterpriseUnlimited

Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset, the last as a Unix timestamp in seconds. The daily window resets at midnight UTC. A refused request is not counted against you.

Paging and filtering

List endpoints return 25 rows by default and never more than 100. Asking for more is clamped rather than rejected, so a client that requests 5000 gets 100 and a truthful pagination block rather than an error it has to handle.

{
  "data": [ { "id": "...", "first_name": "Ada", "last_name": "Lovelace" } ],
  "pagination": {
    "page": 1,
    "page_size": 25,
    "total": 118,
    "page_count": 5,
    "has_next": true,
    "has_previous": false
  }
}

Unrecognised sort, direction and filter values fall back to the default instead of erroring — a saved query should keep working after a column is renamed. Unknown fields in a request body are the opposite: they are rejected with a 400, because a typo there means you believe you set something you did not.

Endpoints

Contacts

MethodPathScope
GET/contactsList contactscontacts.read
POST/contactsCreate a contactcontacts.write
GET/contacts/{id}Retrieve a contactcontacts.read
PATCH/contacts/{id}Update a contactcontacts.write
DELETE/contacts/{id}Delete a contactcontacts.write

Companies

MethodPathScope
GET/companiesList companiescompanies.read
POST/companiesCreate a companycompanies.write
GET/companies/{id}Retrieve a companycompanies.read
PATCH/companies/{id}Update a companycompanies.write
DELETE/companies/{id}Delete a companycompanies.write

Opportunities

MethodPathScope
GET/opportunitiesList opportunitiesopportunities.read
POST/opportunitiesCreate a opportunityopportunities.write
GET/opportunities/{id}Retrieve a opportunityopportunities.read
PATCH/opportunities/{id}Update a opportunityopportunities.write
DELETE/opportunities/{id}Delete a opportunityopportunities.write

Tasks

MethodPathScope
GET/tasksList taskstasks.read
POST/tasksCreate a tasktasks.write
GET/tasks/{id}Retrieve a tasktasks.read
PATCH/tasks/{id}Update a tasktasks.write
DELETE/tasks/{id}Delete a tasktasks.write

Lead search

MethodPathScope
GET/lead-searchesList lead searchesleads.search
POST/lead-searchesStart a lead searchleads.search
GET/lead-searches/{id}Retrieve a lead searchleads.search

Creating and updating

A create returns 201 with the new record and a Location header. Records made through the API are marked with source api, so you can tell an integration’s rows from a manual entry inside the CRM.

curl -X POST https://prospexly.com/api/v1/contacts \
  -H "Authorization: Bearer pxly_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "first_name": "Ada",
    "last_name": "Lovelace",
    "email": "ada@example.com",
    "role": "Head of Engineering"
  }'

A PATCH changes only the fields you send. There is no PUT: a full replace makes it far too easy to blank a field by omitting it.

curl -X PATCH https://prospexly.com/api/v1/contacts/CONTACT_ID \
  -H "Authorization: Bearer pxly_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"status": "engaged"}'

Contacts carry first_name and last_name, never a combined name. There is no full-name field to write to, deliberately, and there will not be one.

Errors

Every failure has the same shape, with a stable code to branch on and a message written to be read by a person.

{
  "error": {
    "code": "forbidden",
    "message": "This key does not have the `contacts.write` scope."
  }
}

A record belonging to another account is a 404 rather than a 403. That is not evasion: 403 would confirm the id exists, which is enough to enumerate other customers’ record ids one request at a time.

Questions

Where do I get a key?
Settings, in the app. The key is shown once at creation and stored only as an Argon2id hash, so we cannot show it to you again or recover it — if you lose it, revoke it and create another.
What happens when I hit the daily limit?
A 429 with `X-RateLimit-Reset` carrying the Unix timestamp at which the window reopens, and a `Retry-After` in seconds. Refused calls are not counted, so being over the limit does not push the reset further away.
Do API calls use my AI lead allowance?
Only lead search does, and it costs exactly what the same search costs in the app: one company or one contact is one lead. Everything else — contacts, companies, opportunities, tasks — is ordinary CRM data and costs nothing beyond the daily call limit.
What happens if my lead allowance runs out mid-month?
`POST /lead-searches` returns 402 rather than 429, because it will not clear on its own in a minute. Buy a credit pack on the subscription page and it applies immediately — credits are spent after the monthly allowance and never expire, so there is no penalty for topping up early. Retrying a 402 without buying credits will keep returning 402.
Is there a sandbox?
No. There is one environment, and a key scoped to read-only permissions is the safe way to explore it. A sandbox that does not share the production data model teaches you the sandbox, and creating a second environment for you to get wrong is not a favour.
Are webhooks available?
Not yet. Lead search is the only long-running operation and it is polled from `status_url`. Nothing else in the API takes long enough for a callback to be worth the delivery guarantees it would need.

Build against it

Create a key in Settings, give it the narrowest scopes that do the job, and point your client generator at the OpenAPI document.