Endpoints
Saved lists
Keep companies, people and documents in named lists your whole workspace shares, and read them back as current records, free.
/v1/listsThis workspace's saved lists
Scopes (any one): dsp.entities.read, dsp.search.read
Use it when you want to keep records you found, such as a target account list or a set of patents to review, and come back to them later from the dashboard, an integration or an agent. Outcome: named lists that store record ids and return each record's current data whenever you read them.
- Free. No list route charges credits, including reading the records back.
- Shared with the workspace. A list belongs to your workspace, not to one person or key: everyone in the workspace sees the same lists.
- Either credential. Every route accepts an API key or a dashboard session
holding
dsp.search.readordsp.entities.read.
| Route | What it does |
|---|---|
GET /v1/lists (this one) | Your workspace's lists, plus how many items are saved and the cap that applies |
POST /v1/lists | Create a list |
PATCH /v1/lists/{list_id} | Rename a list |
DELETE /v1/lists/{list_id} | Delete a list |
POST /v1/lists/{list_id}/items | Add records |
DELETE /v1/lists/{list_id}/items | Remove records |
GET /v1/lists/{list_id}/items | The list's records, current, a page at a time |
POST /v1/lists/{list_id}/export, POST /v1/search/export | CSV export. Not switched on yet |
Your lists#
field_tier"free" | "unlocked"listsarray of SavedListViewcreated_atstringcreated_byinteger | nullidstringThe list's opaque id (a UUID).
item_countintegernamestringupdated_atstringsaved_itemsintegerDistinct entities saved across this workspace's lists.
saved_items_capinteger | nullThis workspace's cap, or null when it has none (paid, or no cap set).
Lists come back most recently updated first.
saved_itemsis the number of distinct records saved across all of the workspace's lists. A record in two lists counts once.saved_items_capis the most a workspace can save, ornullwhen no cap applies. Only workspaces that have not bought credits can have a cap; see The saved-items cap.field_tiersays how much of each record reading the items returns:freeorunlocked.
Create a list#
curl -X POST "https://api.sproutsdata.ai/v1/lists" \
-H "X-API-Key: $SPROUTS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Q4 target accounts"
}'namestringrequired≥ 1 chars, ≤ 120 chars.
name is 1 to 120 characters; spaces at either end are trimmed. Returns
201 Created with the new list. Its id (a UUID) is the list_id every other
route takes. created_by is the user who created it, or null when an API key
did.
created_atstringcreated_byinteger | nullidstringThe list's opaque id (a UUID).
item_countintegernamestringupdated_atstringRename a list#
curl -X PATCH "https://api.sproutsdata.ai/v1/lists/{list_id}" \
-H "X-API-Key: $SPROUTS_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "Q4 target accounts (EMEA)" }'namestringrequired≥ 1 chars, ≤ 120 chars.
Path parameters
list_idstringrequiredSame rules for name as on create. Returns the updated list.
Delete a list#
curl -X DELETE "https://api.sproutsdata.ai/v1/lists/{list_id}" \
-H "X-API-Key: $SPROUTS_API_KEY"Path parameters
list_idstringrequiredReturns 204 No Content. A deleted list is gone for the whole workspace.
Add items#
curl -X POST "https://api.sproutsdata.ai/v1/lists/{list_id}/items" \
-H "X-API-Key: $SPROUTS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"items": [
{
"entity_category": "company",
"entity_id": "stripe.com"
},
{
"entity_category": "person",
"entity_id": "https://www.linkedin.com/in/example-person"
}
]
}'itemsarray of ItemRefIn, ≥ 1requiredentity_categoryEntityCategoryrequired*What* to search, and what a returned row is. One parameter carries both, because they are the same fact: a search of patent returns patents. The alternative — a source/vertical selector beside a narrower category — lets a caller ask for patents from the people index, a combination that has no meaning and would need rejecting at runtime. A closed set that cannot express the contradiction is better than a validator that catches it. Values are exactly prism's own row types, so category in the request and results[].category in the response are always the same string. company and person are the GTM index; the rest are separate corpora with their own filters, freshness and coverage. Not everything applies everywhere: see SearchRequest, which rejects the GTM-only parameters rather than ignoring them. job_opening is a document corpus like patent/news_article/etc — a posting, not an entity — but its records carry a domain (the posting company's) all the same. It is deliberately not in GTM_CATEGORIES (that set is about the entity/document distinction, and a job posting is still a document), but it *is* in DOMAIN_FILTERABLE_CATEGORIES, which exists specifically to separate those two axes.
entity_idstringrequiredThe record's natural id: a company's domain, a person's LinkedIn URL, a patent number, a DOI, a trend name, or the id a document lookup takes. Normalised on the way in (a domain is lower-cased and stripped of scheme and www.; a LinkedIn URL is reduced to linkedin.com/in/<slug>). ≥ 1 chars, ≤ 255 chars.
Path parameters
list_idstringrequiredEach item is an entity_category and that record's natural id, the one it
can be looked up by later:
entity_category | entity_id |
|---|---|
company | Its domain, e.g. stripe.com |
person | Their LinkedIn URL |
patent | The patent number |
academic_report | The DOI |
trend | The trend's name |
news_article, market_report, funding_round, job_opening | The id their lookup route takes |
Ids are normalised on the way in: a domain is lower-cased and loses its scheme
and www., and a LinkedIn URL is reduced to linkedin.com/in/<slug>. So
https://www.Stripe.com/ and stripe.com are the same item.
Up to 500 items per request; more is a 422. Duplicates in one request are
counted once.
addedintegeralready_presentintegersaved_listSavedListViewcreated_atstringcreated_byinteger | nullidstringThe list's opaque id (a UUID).
item_countintegernamestringupdated_atstringsuppressedintegerItems refused because the entity is on the Do-Not-Contact list.
added: items newly saved to this list.already_present: items the list already held. Adding them again changes nothing.suppressed: items refused because the record is on the platform's Do-Not-Contact list. A suppressed record is never added to a list and never returned from one, and suppressing a record removes it from every list it is in.
The saved-items cap
A workspace that has not bought credits can be capped on how many distinct
records it saves across all its lists. GET /v1/lists reports the cap as
saved_items_cap; null means none applies. Workspaces that have bought
credits are never capped.
An add that would take the workspace past the cap is refused as a whole with
409 lists.item_cap_reached, and nothing is added. extra carries the numbers:
{
"type": "https://dashboard.sproutsdata.ai/errors/lists-item-cap-reached",
"title": "Saved-items limit reached",
"status": 409,
"code": "lists.item_cap_reached",
"detail": "This workspace can save 250 items; 245 are saved and 10 would be added.",
"extra": { "cap": 250, "saved": 245, "adding": 10 },
"request_id": "..."
}A record already saved in another of the workspace's lists does not count again, so adding it never hits the cap.
Remove items#
curl -X DELETE "https://api.sproutsdata.ai/v1/lists/{list_id}/items" \
-H "X-API-Key: $SPROUTS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"items": [
{
"entity_category": "company",
"entity_id": "stripe.com"
}
]
}'itemsarray of ItemRefIn, ≥ 1requiredentity_categoryEntityCategoryrequired*What* to search, and what a returned row is. One parameter carries both, because they are the same fact: a search of patent returns patents. The alternative — a source/vertical selector beside a narrower category — lets a caller ask for patents from the people index, a combination that has no meaning and would need rejecting at runtime. A closed set that cannot express the contradiction is better than a validator that catches it. Values are exactly prism's own row types, so category in the request and results[].category in the response are always the same string. company and person are the GTM index; the rest are separate corpora with their own filters, freshness and coverage. Not everything applies everywhere: see SearchRequest, which rejects the GTM-only parameters rather than ignoring them. job_opening is a document corpus like patent/news_article/etc — a posting, not an entity — but its records carry a domain (the posting company's) all the same. It is deliberately not in GTM_CATEGORIES (that set is about the entity/document distinction, and a job posting is still a document), but it *is* in DOMAIN_FILTERABLE_CATEGORIES, which exists specifically to separate those two axes.
entity_idstringrequiredThe record's natural id: a company's domain, a person's LinkedIn URL, a patent number, a DOI, a trend name, or the id a document lookup takes. Normalised on the way in (a domain is lower-cased and stripped of scheme and www.; a LinkedIn URL is reduced to linkedin.com/in/<slug>). ≥ 1 chars, ≤ 255 chars.
Path parameters
list_idstringrequiredSame item shape and the same 500-per-request limit as adding. removed is how
many items were taken out; an item the list did not hold is ignored.
removedintegersaved_listSavedListViewcreated_atstringcreated_byinteger | nullidstringThe list's opaque id (a UUID).
item_countintegernamestringupdated_atstringRead the items#
curl "https://api.sproutsdata.ai/v1/lists/{list_id}/items?limit=25" \
-H "X-API-Key: $SPROUTS_API_KEY"cursorstring | null≤ 64 chars.
limitintegerdefault: 25≥ 1, ≤ 100.
Path parameters
list_idstringrequiredThe list's items, most recently added first, each with the record's current data read when you ask. Reading is never charged.
- Paging:
limitis 1 to 100 (default 25). Passnext_cursorback ascursorfor the next page; it isnullon the last page. A cursor this route did not issue is422 pagination.cursor_invalid. field_tierdecides how much of each record you get.freereturns the fields shown on a result card in the dashboard (for a company: name, domain, industry, size, location, funding and similar).unlocked, for a workspace that has bought credits, returns the full record.available: falsemeans the record could not be read this time. The item stays in the list.unavailable_reasonsays why:not_found(the index no longer resolves that id) orretrieval_unavailable(the lookup failed; try again later).
field_tier"free" | "unlocked"itemsarray of SavedItemViewadded_atstringadded_byinteger | nullavailablebooleanFalse when the record cannot be read right now; it stays in the list.
entity_categorystringentity_idstringrecordobject | nullThe current record, with the fields this workspace may see: card-visible fields for a free workspace, the full record for a paid one.
unavailable_reason"not_found" | "retrieval_unavailable" | nullnext_cursorstring | nullsaved_listSavedListViewcreated_atstringcreated_byinteger | nullidstringThe list's opaque id (a UUID).
item_countintegernamestringupdated_atstringCSV export (not switched on yet)#
Two routes will export records as CSV: POST /v1/lists/{list_id}/export for a
whole list, and POST /v1/search/export for selected search results. They are
not switched on yet: today both answer 404 export.not_enabled.
Errors#
| Status | Code | Summary |
|---|---|---|
| 401 | auth.unauthorized | No usable credential (cause deliberately unspecified) |
| 403 | auth.browser_origin | A secret key was used from a browser |
| 403 | auth.insufficient_scope | The credential does not hold the required scope |
| 404 | lists.not_found | The list does not exist, was deleted, or is another workspace's |
| 409 | lists.item_cap_reached | A free workspace's saved-items cap would be exceeded |
| 422 | pagination.cursor_invalid | The cursor could not be read (e.g. a filter changed mid-pagination) |
| 422 | request.invalid | A field failed validation (extra.errors has the detail) |
| 503 | credits.unavailable | The billing service could not confirm the charge; retry with the same Idempotency-Key |
| 503 | lists.unavailable | The saved-lists store is unavailable; nothing was changed |
| 503 | upstream.unavailable | The retrieval engine is unreachable (not charged) |
404 lists.not_found: no list with that id in this workspace. A list id from another workspace answers the same way.503 lists.unavailable: saved lists could not be reached. Nothing was changed; retry shortly.