La documentación del producto está disponible solo en inglés.
Practice areas endpoints
A practice area is the area of law a matter belongs to, such as Family law or Real estate, and it carries the stages a matter moves through. These endpoints create practice areas and resolve the ids you need for a matter.
All paths sit under the base address https://api.esqase.com, and every request needs an API key. See Authentication and scopes if you have not made your first call yet.
The endpoints
| Action | Method and path | Scope needed |
|---|---|---|
| List | GET /v1/practice-areas | practice-areas:read |
| Create | POST /v1/practice-areas | practice-areas:write |
| Retrieve | GET /v1/practice-areas/{id} | practice-areas:read |
| Update | PATCH /v1/practice-areas/{id} | practice-areas:write |
| Delete | DELETE /v1/practice-areas/{id} | practice-areas:write |
The practice area object
id: the id Esqase assigns. This is thepracticeAreaIdyou pass when creating a matter or a matter type.name: the practice area's name.status: its lifecycle state. Practice areas created through the API start asACTIVE.stages: the stages a matter moves through in this practice area. Each entry has:id: the id you pass aspracticeAreaStageColumnIdwhen creating a matter.name: the stage's name.stageType:TODO,IN_PROGRESS, orDONE.sort: the stage's position on the board, lowest first.
matterTypes: the matter types defined under this practice area, each with itsidandname.createdAt,updatedAt: timestamps, ornullon the responses noted below.
Important: stages and matterTypes are only filled in when you retrieve a single practice area. On the list endpoint both come back as empty arrays. Read the practice area by id whenever you need its stage ids.
List practice areas
GET /v1/practice-areas returns your practice areas a page at a time. Two query parameters control the page:
limit: how many records to return. Minimum 1, maximum 100, default 20.offset: how many records to skip before the page starts. Minimum 0, default 0.
curl "https://api.esqase.com/v1/practice-areas?limit=2&offset=0" \
-H "Authorization: Bearer $ESQASE_API_KEY"
{
"data": [
{
"id": "b2c3d4e5-1000-4a1b-9c2d-1234567890ab",
"name": "Family law",
"status": "ACTIVE",
"stages": [],
"matterTypes": [],
"createdAt": "2026-07-01T09:00:00.000Z",
"updatedAt": "2026-07-01T09:00:00.000Z"
},
{
"id": "b2c3d4e5-1001-4a1b-9c2d-1234567890ab",
"name": "Real estate",
"status": "ACTIVE",
"stages": [],
"matterTypes": [],
"createdAt": "2026-07-01T09:05:00.000Z",
"updatedAt": "2026-07-01T09:05:00.000Z"
}
],
"pagination": { "limit": 2, "offset": 0, "total": 6 }
}
Use this list to find the practice area you want, then retrieve it by id to get its stages.
Create a practice area
POST /v1/practice-areas creates one practice area and returns 201 Created. It takes a single field:
name(required): 1 to 64 characters.
Esqase seeds the new practice area with three default stages, To do, In progress, and Done, so it is ready to hold matters right away. You do not create stages separately, and the API has no endpoint for adding more (do that in the dashboard).
curl -X POST https://api.esqase.com/v1/practice-areas \
-H "Authorization: Bearer $ESQASE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "Family law" }'
The response carries the seeded stages, so you have the stage ids you need for a matter immediately:
{
"data": {
"id": "b2c3d4e5-1000-4a1b-9c2d-1234567890ab",
"name": "Family law",
"status": "ACTIVE",
"stages": [
{
"id": "b2c3d4e5-2000-4a1b-9c2d-1234567890ab",
"name": "To do",
"stageType": "TODO",
"sort": 1000
},
{
"id": "b2c3d4e5-2001-4a1b-9c2d-1234567890ab",
"name": "In progress",
"stageType": "IN_PROGRESS",
"sort": 2000
},
{
"id": "b2c3d4e5-2002-4a1b-9c2d-1234567890ab",
"name": "Done",
"stageType": "DONE",
"sort": 3000
}
],
"matterTypes": [],
"createdAt": null,
"updatedAt": null
}
}
Note: The create response echoes what was written, so createdAt and updatedAt come back as null. Retrieve the practice area by id if you need its real timestamps.
Retrieve a practice area
GET /v1/practice-areas/{id} is the call that resolves the two ids a matter needs. It returns the practice area with its full stages list (ordered by sort) and its matterTypes.
curl https://api.esqase.com/v1/practice-areas/b2c3d4e5-1000-4a1b-9c2d-1234567890ab \
-H "Authorization: Bearer $ESQASE_API_KEY"
{
"data": {
"id": "b2c3d4e5-1000-4a1b-9c2d-1234567890ab",
"name": "Family law",
"status": "ACTIVE",
"stages": [
{
"id": "b2c3d4e5-2000-4a1b-9c2d-1234567890ab",
"name": "To do",
"stageType": "TODO",
"sort": 1000
},
{
"id": "b2c3d4e5-2001-4a1b-9c2d-1234567890ab",
"name": "In progress",
"stageType": "IN_PROGRESS",
"sort": 2000
},
{
"id": "b2c3d4e5-2002-4a1b-9c2d-1234567890ab",
"name": "Done",
"stageType": "DONE",
"sort": 3000
}
],
"matterTypes": [
{
"id": "e5f6a7b8-0001-4a1b-9c2d-1234567890ab",
"name": "Divorce"
}
],
"createdAt": "2026-07-01T09:00:00.000Z",
"updatedAt": "2026-07-01T09:00:00.000Z"
}
}
If no practice area in your firm has that id, or it has been deleted, you get 404 with the code not_found.
Update a practice area
PATCH /v1/practice-areas/{id} renames the practice area. Renaming is the only change the API supports here.
name(required): 1 to 64 characters.
curl -X PATCH https://api.esqase.com/v1/practice-areas/b2c3d4e5-1000-4a1b-9c2d-1234567890ab \
-H "Authorization: Bearer $ESQASE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "Family and matrimonial law" }'
The response is the reloaded practice area, stages and matter types included:
{
"data": {
"id": "b2c3d4e5-1000-4a1b-9c2d-1234567890ab",
"name": "Family and matrimonial law",
"status": "ACTIVE",
"stages": [
{
"id": "b2c3d4e5-2000-4a1b-9c2d-1234567890ab",
"name": "To do",
"stageType": "TODO",
"sort": 1000
}
],
"matterTypes": [],
"createdAt": "2026-07-01T09:00:00.000Z",
"updatedAt": "2026-07-02T11:15:00.000Z"
}
}
Delete a practice area
DELETE /v1/practice-areas/{id} is a soft delete: the practice area is archived out of the active lists rather than erased. Matters already filed under it keep pointing at it.
curl -X DELETE https://api.esqase.com/v1/practice-areas/b2c3d4e5-1000-4a1b-9c2d-1234567890ab \
-H "Authorization: Bearer $ESQASE_API_KEY"
{
"data": {
"id": "b2c3d4e5-1000-4a1b-9c2d-1234567890ab",
"deleted": true
}
}
Full examples in JavaScript and Python
Both examples read the key from an environment variable, find a practice area by name, and fall back to creating it, then print the id of its first stage.
JavaScript (fetch)
const BASE = "https://api.esqase.com/v1";
const headers = {
Authorization: `Bearer ${process.env.ESQASE_API_KEY}`,
"Content-Type": "application/json",
};
// Look for an existing practice area by name.
const listResponse = await fetch(`${BASE}/practice-areas?limit=100`, { headers });
const { data: practiceAreas } = await listResponse.json();
let practiceArea = practiceAreas.find((row) => row.name === "Family law");
// Create it if it is not there yet.
if (!practiceArea) {
const createResponse = await fetch(`${BASE}/practice-areas`, {
method: "POST",
headers,
body: JSON.stringify({ name: "Family law" }),
});
({ data: practiceArea } = await createResponse.json());
} else {
// The list omits stages, so read the record to get them.
const readResponse = await fetch(`${BASE}/practice-areas/${practiceArea.id}`, { headers });
({ data: practiceArea } = await readResponse.json());
}
console.log(practiceArea.id, practiceArea.stages[0].id);
Python (requests)
import os
import requests
BASE = "https://api.esqase.com/v1"
headers = {
"Authorization": f"Bearer {os.environ['ESQASE_API_KEY']}",
"Content-Type": "application/json",
}
# Look for an existing practice area by name.
rows = requests.get(
f"{BASE}/practice-areas", headers=headers, params={"limit": 100}
).json()["data"]
match = next((row for row in rows if row["name"] == "Family law"), None)
if match is None:
# Create it if it is not there yet.
practice_area = requests.post(
f"{BASE}/practice-areas", headers=headers, json={"name": "Family law"}
).json()["data"]
else:
# The list omits stages, so read the record to get them.
practice_area = requests.get(
f"{BASE}/practice-areas/{match['id']}", headers=headers
).json()["data"]
print(practice_area["id"], practice_area["stages"][0]["id"])
Errors
| Status | Code | When |
|---|---|---|
| 400 | invalid_request | name is missing, empty, or longer than 64 characters. |
| 401 | unauthorized | The key is missing, wrong, revoked, or expired. |
| 403 | forbidden | The key is missing practice-areas:read or practice-areas:write, or the member who created it lacks the firm's Practice areas permission. |
| 404 | not_found | No practice area in your firm has that id, or it has been deleted. |
| 429 | rate_limited | Too many requests. Wait the Retry-After seconds. |
The full list of status codes is in Authentication and scopes.
Common questions
- Why is
stagesempty in my list response? The list endpoint does not include stages or matter types. Retrieve the practice area by id to get them. - Can I add, rename, or reorder stages through the API? No. A new practice area gets To do, In progress, and Done automatically, and any further stage work happens in the dashboard. See Practice areas and stages.
- What if stage creation fails when I create a practice area? The practice area is still created, and the
stageslist in the response shows exactly which stages were seeded. Retrieve it by id to confirm. - Do practice areas and matter types use the same permission? Yes. Both are covered by the firm's Practice areas permission, though they use separate API scopes.