> ## Documentation Index
> Fetch the complete documentation index at: https://docs.alura.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Bulk lookups

> Get up to 100 products in one call, and handle the ones Alura doesn't have yet.

`GET /v1/research/products?ids=` takes up to 100 Etsy listing IDs, comma-separated or as repeated `ids` parameters, and returns a summary of each. It costs the same per product as opening each one, in one request instead of up to 100.

<CodeGroup>
  ```bash cURL theme={null}
  curl --fail-with-body -sS -G https://api.alura.io/v1/research/products \
    -H "Authorization: Bearer $ALURA_API_KEY" \
    --data-urlencode "ids=4524717183,1587263044"
  ```

  ```typescript TypeScript theme={null}
  interface Bulk {
    data: { id: string; title: string | null }[];
    pending: { id: string }[];
    unfetched: { id: string; code: string; retry_after: number }[];
    not_found: { id: string }[];
  }

  const res = await fetch('https://api.alura.io/v1/research/products?ids=4524717183,1587263044', {
    headers: { Authorization: `Bearer ${process.env.ALURA_API_KEY}` },
  });
  if (!res.ok) throw new Error(`${res.status}: ${await res.text()}`);
  const result = (await res.json()) as Bulk;

  for (const p of result.data) console.log(`known: ${p.id} ${p.title}`);
  for (const p of result.pending) console.log(`being fetched, ask again later: ${p.id}`);
  for (const p of result.unfetched) console.log(`not fetched (${p.code}), retry in ${p.retry_after}s: ${p.id}`);
  for (const p of result.not_found) console.log(`not found: ${p.id}`);
  ```

  ```python Python theme={null}
  import json
  import os
  import urllib.request

  request = urllib.request.Request(
      "https://api.alura.io/v1/research/products?ids=4524717183,1587263044",
      headers={"Authorization": f"Bearer {os.environ['ALURA_API_KEY']}"},
  )
  with urllib.request.urlopen(request) as response:
      result = json.load(response)

  for p in result["data"]:
      print(f"known: {p['id']} {p['title']}")
  for p in result["pending"]:
      print(f"being fetched, ask again later: {p['id']}")
  for p in result["unfetched"]:
      print(f"not fetched ({p['code']}), retry in {p['retry_after']}s: {p['id']}")
  for p in result["not_found"]:
      print(f"not found: {p['id']}")
  ```
</CodeGroup>

Every ID you send comes back exactly once, in one of four lists:

| List | Means | What to do |
| - | - | - |
| `data` | Alura knows the product. You get its summary, even when the data is older than 14 days. | Use it; check `as_of` |
| `pending` | Alura has never seen it and started fetching it from Etsy. | Ask again later |
| `unfetched` | Alura has never seen it and did not start a fetch. `code` says why: a fetch limit (`fetch_limit_reached`) or fetching switched off (`fetch_unavailable`). | Retry after `retry_after` seconds |
| `not_found` | It isn't on Etsy, or it isn't available through Alura. | Drop it |

`GET /v1/research/products?ids=` costs 1 API credit per product in `data`. The other lists are free. Alura needs enough API credits for every distinct ID before the call runs, and gives back what it didn't use.

Each never-seen ID counts against the workspace's daily product fetch cap and its per-minute fetch rate. See [Freshness and new items](/api-reference/freshness).

Bulk lookups never refresh stale data. To refresh one product, open it with `GET /v1/research/products/{id}`.

Keywords work the same way with `GET /v1/research/keywords`, which takes repeated `q` parameters; see [Keyword research](/guides/keyword-research).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.