> For the complete documentation index, see [llms.txt](https://integrate.lexamica.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://integrate.lexamica.com/example-integrations/apx-2.-enrichment-pickers.md).

# Dynamic Pickers with Enrichment Endpoints

Look up case types and law firms live, instead of hard-coding directory values.

***

## 🎯 Overview

> **📌 TL;DR**
>
> Lexamica's directory data—practice areas (case types) and platform law firms—changes over time. Instead of hard-coding a list, query the **enrichment endpoints** to build searchable pickers that stay current. These are management operations, so they use your **Private Key** and run from your server. This appendix works with any integration pattern.

**This guide covers:**

* Searching and resolving **case types** to send with a case
* Searching **law firms** and fetching a firm's public summary

**Use this with:**

* Any originator or handler integration
* Both webhook and polling patterns

***

## 📖 Key Terms

| Term               | Definition                                                                                  |
| ------------------ | ------------------------------------------------------------------------------------------- |
| **Case Type**      | A practice area in Lexamica (e.g. "Personal Injury"). You send this when creating a case.   |
| **Synonym**        | An alternate term that also matches a case type (e.g. "MVA" matches "Automobile Accident"). |
| **Law Firm**       | A firm registered on the Lexamica platform. Enrichment returns only its public summary.     |
| **Public Summary** | The firm fields enrichment exposes: `id`, `name`, `description`, `verifiedStatus`.          |

***

## 📋 Prerequisites

**Credentials:**

* [ ] Organization ID and **Private Key** from Lexamica

**Foundational Docs:**

* [ ] [Organizations and Authentication](/1.-organizations-and-authentication.md) — enrichment uses your Private Key, not your Public Key
* [ ] [Enrichment Endpoints](/6.-enrichment-endpoints.md) — the full endpoint contract, return types, and status codes

> **⚠️ Use your Private Key.** Enrichment endpoints are management operations. Call them from your server with your Private Key—never your Public Key, and never from client-side code.

***

## 💡 Case Types

**Goal:** let a user pick a case type from Lexamica's live directory, then send its **ID** with the case.

### Search Case Types

Fuzzy-search case types by name or synonym—ideal for a type-ahead field.

**Endpoint:** `GET /organization/{orgId}/case-types/search`

**Request:**

```bash
curl -X GET \
  "https://integration.lexamica.com/organization/{orgId}/case-types/search?query=injury&limit=10" \
  -H "Authorization: Bearer your_private_key"
```

**Response:**

```json
{
  "success": true,
  "data": [
    { "id": "6606eb3d9cbcb8a8697430aa", "name": "Personal Injury", "synonyms": ["PI", "Bodily Injury"] },
    { "id": "6606eb3d9cbcb8a8697430bb", "name": "Automobile Accident", "synonyms": ["Car Accident", "MVA"] }
  ]
}
```

Search returns up to **10** results and an empty `data` array when nothing matches. URL-encode multi-word queries (`?query=Personal%20Injury`).

**Node.js Example:**

```javascript
async function searchCaseTypes(query) {
  const response = await axios.get(
    `${BASE_URL}/organization/${ORG_ID}/case-types/search`,
    {
      params: { query, limit: 10 },
      headers: { Authorization: `Bearer ${PRIVATE_KEY}` }
    }
  );
  return response.data.data; // [{ id, name, synonyms }]
}
```

> **💡 Tip:** If you call this as the user types, debounce the input (e.g. wait \~300ms after the last keystroke) so a fast typist doesn't trip the rate limit.

### List All Case Type Names

Prefer this when you'd rather load every name once and filter in your own UI. Returns a flat string array with no IDs.

**Endpoint:** `GET /organization/{orgId}/case-types/names`

**Request:**

```bash
curl -X GET \
  "https://integration.lexamica.com/organization/{orgId}/case-types/names" \
  -H "Authorization: Bearer your_private_key"
```

**Response:**

```json
{
  "success": true,
  "data": ["3M Earplugs", "ADA - Website Accessibility", "Automobile Accident", "Personal Injury"]
}
```

### Resolve a Case Type Before Sending

Once a user picks a case type, look it up by **ID** (from search) or **exact name** (from the names list) to confirm it's valid and pull its stages and evaluation period.

**Endpoint:** `GET /organization/{orgId}/case-types/{idOrName}`

**Request:**

```bash
curl -X GET \
  "https://integration.lexamica.com/organization/{orgId}/case-types/Personal%20Injury" \
  -H "Authorization: Bearer your_private_key"
```

**Response:**

```json
{
  "success": true,
  "data": {
    "id": "69679674e89ec60015e9c3f7",
    "name": "Personal Injury",
    "synonyms": [],
    "stages": [
      { "label": "Eval Pending", "cycle": "evaluating.pending", "inUI": true },
      { "label": "Open", "cycle": "open", "inUI": true },
      { "label": "Closed With Fee", "cycle": "closed.final.withFee", "inUI": true }
    ],
    "evaluationPeriod": { "days": 14, "type": "relative" }
  }
}
```

A `404` means the name is unknown or ambiguous. Prefer looking up by **ID** when you have it—name matching is exact and won't resolve an ambiguous name.

**Then send the case** with the resolved case type, following [Lead Form Submission](/example-integrations/1.-lead-form-submission.md).

***

## 💡 Law Firms

**Goal:** let a user search platform law firms and view a firm's public summary—for example, to choose a referral partner. Results are public summaries only.

### Search Law Firms

**Endpoint:** `GET /organization/{orgId}/law-firms/search`

**Request:**

```bash
curl -X GET \
  "https://integration.lexamica.com/organization/{orgId}/law-firms/search?query=smith&limit=20" \
  -H "Authorization: Bearer your_private_key"
```

**Response:**

```json
{
  "success": true,
  "data": [
    { "id": "6606eb3d9cbcb8a8697430aa", "name": "Smith & Associates", "description": "Personal injury specialists serving the Southeast.", "verifiedStatus": "verified" },
    { "id": "6606eb3d9cbcb8a8697430bb", "name": "Doe Legal Group", "description": null, "verifiedStatus": null }
  ]
}
```

Search returns up to **20** results. `description` and `verifiedStatus` are `null` when a firm hasn't set them—render them as optional.

**Node.js Example:**

```javascript
async function searchLawFirms(query) {
  const response = await axios.get(
    `${BASE_URL}/organization/${ORG_ID}/law-firms/search`,
    {
      params: { query, limit: 20 },
      headers: { Authorization: `Bearer ${PRIVATE_KEY}` }
    }
  );
  return response.data.data; // [{ id, name, description, verifiedStatus }]
}
```

### Get a Law Firm by ID

**Endpoint:** `GET /organization/{orgId}/law-firms/{id}`

**Request:**

```bash
curl -X GET \
  "https://integration.lexamica.com/organization/{orgId}/law-firms/6606eb3d9cbcb8a8697430aa" \
  -H "Authorization: Bearer your_private_key"
```

**Response:**

```json
{
  "success": true,
  "data": {
    "id": "6606eb3d9cbcb8a8697430aa",
    "name": "Smith & Associates",
    "description": "Personal injury specialists serving the Southeast.",
    "verifiedStatus": "verified"
  }
}
```

An invalid ID returns `400`; a well-formed but unknown ID returns `404`.

***

## 🔧 Complete Example: Enrichment Service

```javascript
const axios = require('axios');

const config = {
  orgId: process.env.LEXAMICA_ORG_ID,
  privateKey: process.env.LEXAMICA_PRIVATE_KEY,
  baseUrl: 'https://integration.lexamica.com'
};

// One client pre-configured with your Private Key and org scope
const client = axios.create({
  baseURL: `${config.baseUrl}/organization/${config.orgId}`,
  headers: { Authorization: `Bearer ${config.privateKey}` }
});

class EnrichmentService {
  // Search case types (up to 10 results)
  async searchCaseTypes(query) {
    const { data } = await client.get('/case-types/search', { params: { query, limit: 10 } });
    return data.data;
  }

  // Resolve a case type by id or exact name (includes stages + evaluationPeriod)
  async getCaseType(idOrName) {
    const { data } = await client.get(`/case-types/${encodeURIComponent(idOrName)}`);
    return data.data;
  }

  // Every case type name, as a flat array
  async listCaseTypeNames() {
    const { data } = await client.get('/case-types/names');
    return data.data;
  }

  // Search law firms (up to 20 results)
  async searchLawFirms(query) {
    const { data } = await client.get('/law-firms/search', { params: { query, limit: 20 } });
    return data.data;
  }

  // Get a law firm's public summary by id
  async getLawFirm(id) {
    const { data } = await client.get(`/law-firms/${id}`);
    return data.data;
  }
}

module.exports = new EnrichmentService();
```

***

## ❓ FAQ

### ❓ "Which key do these endpoints use?"

**🔍 Quick Check:**

* ✅ Use your **Private Key**
* ❌ Not your **Public Key** (that one is only for sending data to Lexamica)

**📝 Full Answer:** Enrichment endpoints are management operations, so they use your Private Key. Keep the key on your server—never call these endpoints from client-side code. See [Organizations and Authentication](/1.-organizations-and-authentication.md).

***

### ❓ "How many results does search return?"

**📝 Full Answer:** Case-type search returns up to **10** results; law-firm search up to **20**. The `limit` parameter can lower the count but not raise it above the cap. If you need every case type, use `case-types/names` instead.

***

### ❓ "Should I look up a case type by name or by ID?"

**📝 Full Answer:** Prefer the **ID** whenever you have it—it's exact and never ambiguous. Name lookup matches the exact name and returns `404` if the name is unknown or ambiguous. Use `case-types/search` to resolve a name to its ID.

***

### ❓ "Why is `description` or `verifiedStatus` `null` on a law firm?"

**📝 Full Answer:** Those fields are `null` when the firm hasn't set a public description or verification status. The summary only ever includes `id`, `name`, `description`, and `verifiedStatus`.

***

### ❓ "Is the data cached?"

**📝 Full Answer:** No—it's served live from Lexamica, so it always reflects the current directory. There's nothing to sync on your side.

***

## 🔧 Troubleshooting

**"400 Bad Request"**

* Search: `query` is missing or empty—require at least one character before calling.
* Law-firm detail: the `id` isn't a valid Lexamica ID. Only look up firms by an `id` returned from search.

**"401 Unauthorized"**

* Verify you're sending your **Private Key** in the `Authorization` header.

**"404 Not Found"**

* Case type by name: the name is unknown or ambiguous—resolve it via search and look up by ID.
* Law firm by ID: the ID is well-formed but no firm matches it.

**"429 Too Many Requests"**

* You're exceeding the rate limit. Debounce interactive input so you don't fire a request on every keystroke.

***

## ➡️ Next Steps

* **Send the case with the selected case type** → [Lead Form Submission](/example-integrations/1.-lead-form-submission.md) or [Originator: Full Integration with Webhooks](/example-integrations/3a.-originator-full-webhook.md)
* **Attach files to the case** → [File Operations](/example-integrations/apx-1.-file-operations.md)
* **Full endpoint contract and status codes** → [Enrichment Endpoints](/6.-enrichment-endpoints.md)

***

*Last updated: July 2026*
