Endpoints

Saved lists

Keep companies, people and documents in named lists your whole workspace shares, and read them back as current records, free.

GET/v1/lists

This 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.read or dsp.entities.read.
RouteWhat it does
GET /v1/lists (this one)Your workspace's lists, plus how many items are saved and the cap that applies
POST /v1/listsCreate a list
PATCH /v1/lists/{list_id}Rename a list
DELETE /v1/lists/{list_id}Delete a list
POST /v1/lists/{list_id}/itemsAdd records
DELETE /v1/lists/{list_id}/itemsRemove records
GET /v1/lists/{list_id}/itemsThe list's records, current, a page at a time
POST /v1/lists/{list_id}/export, POST /v1/search/exportCSV export. Not switched on yet

Your lists#

field_tier"free" | "unlocked"
listsarray of SavedListView
created_atstring
created_byinteger | null
idstring

The list's opaque id (a UUID).

item_countinteger
namestring
updated_atstring
saved_itemsinteger

Distinct entities saved across this workspace's lists.

saved_items_capinteger | null

This workspace's cap, or null when it has none (paid, or no cap set).

Lists come back most recently updated first.

  • saved_items is the number of distinct records saved across all of the workspace's lists. A record in two lists counts once.
  • saved_items_cap is the most a workspace can save, or null when no cap applies. Only workspaces that have not bought credits can have a cap; see The saved-items cap.
  • field_tier says how much of each record reading the items returns: free or unlocked.

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_atstring
created_byinteger | null
idstring

The list's opaque id (a UUID).

item_countinteger
namestring
updated_atstring

Rename 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_idstringrequired

Same 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_idstringrequired

Returns 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, ≥ 1required
entity_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_idstringrequired

The 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_idstringrequired

Each item is an entity_category and that record's natural id, the one it can be looked up by later:

entity_categoryentity_id
companyIts domain, e.g. stripe.com
personTheir LinkedIn URL
patentThe patent number
academic_reportThe DOI
trendThe trend's name
news_article, market_report, funding_round, job_openingThe 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.

addedinteger
already_presentinteger
saved_listSavedListView
created_atstring
created_byinteger | null
idstring

The list's opaque id (a UUID).

item_countinteger
namestring
updated_atstring
suppressedinteger

Items 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, ≥ 1required
entity_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_idstringrequired

The 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_idstringrequired

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

removedinteger
saved_listSavedListView
created_atstring
created_byinteger | null
idstring

The list's opaque id (a UUID).

item_countinteger
namestring
updated_atstring

Read 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_idstringrequired

The list's items, most recently added first, each with the record's current data read when you ask. Reading is never charged.

  • Paging: limit is 1 to 100 (default 25). Pass next_cursor back as cursor for the next page; it is null on the last page. A cursor this route did not issue is 422 pagination.cursor_invalid.
  • field_tier decides how much of each record you get. free returns 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: false means the record could not be read this time. The item stays in the list. unavailable_reason says why: not_found (the index no longer resolves that id) or retrieval_unavailable (the lookup failed; try again later).
field_tier"free" | "unlocked"
itemsarray of SavedItemView
added_atstring
added_byinteger | null
availableboolean

False when the record cannot be read right now; it stays in the list.

entity_categorystring
entity_idstring
recordobject | null

The 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" | null
next_cursorstring | null
saved_listSavedListView
created_atstring
created_byinteger | null
idstring

The list's opaque id (a UUID).

item_countinteger
namestring
updated_atstring

CSV 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#

StatusCodeSummary
401auth.unauthorizedNo usable credential (cause deliberately unspecified)
403auth.browser_originA secret key was used from a browser
403auth.insufficient_scopeThe credential does not hold the required scope
404lists.not_foundThe list does not exist, was deleted, or is another workspace's
409lists.item_cap_reachedA free workspace's saved-items cap would be exceeded
422pagination.cursor_invalidThe cursor could not be read (e.g. a filter changed mid-pagination)
422request.invalidA field failed validation (extra.errors has the detail)
503credits.unavailableThe billing service could not confirm the charge; retry with the same Idempotency-Key
503lists.unavailableThe saved-lists store is unavailable; nothing was changed
503upstream.unavailableThe 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.