Esqase

Search documentation

Search all Esqase documentation pages

API resources and endpoints

The API works with five kinds of record: contacts, matters, leads, practice areas, and matter types. Each one supports the full set of actions, listing them, creating one, reading one, updating one, and deleting one. This page describes the endpoints, how paging works, the fields each resource accepts, and how the API behaves on each action.

Before using these endpoints, make sure you can authenticate. See Authentication and scopes. All paths below sit under the base address https://api.esqase.com.

How the endpoints are shaped

Each resource follows the same pattern, where {id} is the id of one record:

ActionMethod and pathScope needed
ListGET /v1/<resource><resource>:read
CreatePOST /v1/<resource><resource>:write
Read oneGET /v1/<resource>/{id}<resource>:read
UpdatePATCH /v1/<resource>/{id}<resource>:write
DeleteDELETE /v1/<resource>/{id}<resource>:write

Replace <resource> with contacts, matters, leads, practice-areas, or matter-types.

Note: Records are referenced by the id Esqase assigns them, which you get from the list and create responses. When one record needs to point at another (for example, adding a client to a matter, or putting a matter in a practice area), you use the ids you got back from the API, not names.

Paging through lists

The list endpoints (GET /v1/contacts, /v1/matters, /v1/leads, /v1/practice-areas, /v1/matter-types) return records in pages so you never pull your whole firm in one call. Control the page with two query parameters:

  • limit: how many records to return (a sensible default applies if you omit it, with a per-page ceiling).
  • offset: how many records to skip before the page starts.

For example, GET /v1/contacts?limit=50&offset=0 returns the first 50 contacts, and ?limit=50&offset=50 returns the next 50. The response tells you the total count so you know when you have reached the end.

Here is a paged request and the shape of the response. Every list wraps its records in a data array and adds a pagination object with the limit, offset, and total:

curl "https://api.esqase.com/v1/contacts?limit=2&offset=0" \
  -H "Authorization: Bearer $ESQASE_API_KEY"
{
  "data": [
    {
      "id": "a1b2c3d4-0001-4a1b-9c2d-1234567890ab",
      "type": "PERSON",
      "status": "ACTIVE",
      "name": "Jane Doe",
      "firstName": "Jane",
      "lastName": "Doe",
      "companyName": null,
      "emails": ["jane@example.com"],
      "phones": ["+1-555-0100"],
      "createdAt": "2026-07-01T14:32:00Z",
      "updatedAt": "2026-07-01T14:32:00Z"
    },
    {
      "id": "a1b2c3d4-0002-4a1b-9c2d-1234567890ab",
      "type": "COMPANY",
      "status": "ACTIVE",
      "name": "Acme Holdings",
      "firstName": null,
      "lastName": null,
      "companyName": "Acme Holdings",
      "emails": ["contact@example.com"],
      "phones": [],
      "createdAt": "2026-07-01T15:00:00Z",
      "updatedAt": "2026-07-01T15:00:00Z"
    }
  ],
  "pagination": {
    "limit": 2,
    "offset": 0,
    "total": 128
  }
}

When offset plus limit reaches total, you have read every record.

Contacts

A contact is a person or a company. Use these endpoints to bring your existing client and contact records into Esqase.

Type and name fields. Every contact has a type of either PERSON or COMPANY:

  • For a person, provide name fields such as firstName and lastName (and optionally a prefix, middle name, suffix, or nickname).
  • For a company, provide the companyName (and optionally a trade name and company type).

Contact methods. You can attach contact methods with two lists:

  • emails: a list of email addresses. The first becomes the contact's primary email; the rest are secondary.
  • phones: a list of phone numbers.

Creating a contact (POST /v1/contacts) takes the type, the matching name fields, and optionally the emails and phones lists.

Updating a contact (PATCH /v1/contacts/{id}) changes the same fields. When you send emails or phones on an update, they replace the contact's existing ones, so include the full list you want, not just the additions. A contact's type cannot be changed after creation.

Deleting a contact (DELETE /v1/contacts/{id}) is a soft delete: the contact is archived out of the active list rather than erased, the same as deleting it in the app.

Example: create a contact. Send the type, the matching name fields, and any emails or phones:

curl -X POST https://api.esqase.com/v1/contacts \
  -H "Authorization: Bearer $ESQASE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "PERSON",
    "firstName": "Jane",
    "lastName": "Doe",
    "emails": ["jane@example.com"],
    "phones": ["+1-555-0100"]
  }'

The create call returns HTTP 201 with the new record wrapped in data:

{
  "data": {
    "id": "a1b2c3d4-0001-4a1b-9c2d-1234567890ab",
    "type": "PERSON",
    "status": "ACTIVE",
    "name": "Jane Doe",
    "firstName": "Jane",
    "lastName": "Doe",
    "companyName": null,
    "emails": ["jane@example.com"],
    "phones": ["+1-555-0100"],
    "createdAt": "2026-07-01T14:32:00Z",
    "updatedAt": "2026-07-01T14:32:00Z"
  }
}

Example: read one contact. Pass the id you got back to fetch the same record later:

curl https://api.esqase.com/v1/contacts/a1b2c3d4-0001-4a1b-9c2d-1234567890ab \
  -H "Authorization: Bearer $ESQASE_API_KEY"

Matters

A matter is one case or engagement. Use these endpoints to mirror your case list into Esqase.

Creating a matter (POST /v1/matters) requires two things:

  • practiceAreaId (required): the practice area the matter belongs to. Every matter sits in exactly one practice area. Get valid ids from the practice areas endpoint (or list matters you already have, or set practice areas up in the app first; see Practice areas and stages).
  • openDate (required): the date the matter opened, written as YYYY-MM-DD.

Optional fields on create:

  • practiceAreaStageColumnId: which stage the matter starts in. If you leave it out, the matter lands in the practice area's first stage. To pick a specific stage, read the practice area (GET /v1/practice-areas/{id}); its stages list gives you each stage's id.
  • title: the matter's title. If you leave it blank, Esqase fills in "Untitled matter".
  • description: free-text notes about the matter.
  • closeDate and solDate: the close date and any statute-of-limitations date.
  • status: ACTIVE or DRAFT. Defaults to ACTIVE.
  • clientContactIds: a list of contact ids to add as the matter's clients. The first one becomes the primary client and the bill recipient.
  • team: a list of team members, each given as the member's id, a role, and an allocation (their share of the matter, as a percentage).

Updating a matter (PATCH /v1/matters/{id}) can change the title, description, practice area, stage, and the open, close, and statute dates. You can also change the matter's status here, set it to ACTIVE, DRAFT, or ARCHIVE to move the matter through its lifecycle.

Deleting a matter (DELETE /v1/matters/{id}) is a soft delete, the same as deleting it in the dashboard.

Example: create a matter. A matter needs at least a practiceAreaId and an openDate. This example also names a starting stage and adds a primary client:

curl -X POST https://api.esqase.com/v1/matters \
  -H "Authorization: Bearer $ESQASE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "practiceAreaId": "b2c3d4e5-1000-4a1b-9c2d-1234567890ab",
    "openDate": "2026-07-01",
    "title": "Doe v. Acme Holdings",
    "practiceAreaStageColumnId": "b2c3d4e5-2000-4a1b-9c2d-1234567890ab",
    "clientContactIds": ["a1b2c3d4-0001-4a1b-9c2d-1234567890ab"]
  }'

The created matter comes back as HTTP 201:

{
  "data": {
    "id": "c3d4e5f6-0001-4a1b-9c2d-1234567890ab",
    "publicId": "M-0042",
    "title": "Doe v. Acme Holdings",
    "description": null,
    "status": "ACTIVE",
    "openDate": "2026-07-01",
    "closeDate": null,
    "solDate": null,
    "practiceArea": {
      "id": "b2c3d4e5-1000-4a1b-9c2d-1234567890ab",
      "name": "Family law"
    },
    "stage": {
      "id": "b2c3d4e5-2000-4a1b-9c2d-1234567890ab",
      "name": "To do"
    },
    "clients": [
      {
        "id": "a1b2c3d4-0001-4a1b-9c2d-1234567890ab",
        "name": "Jane Doe"
      }
    ],
    "team": []
  }
}

Tip: To add clients or team members, you need the contact ids and member ids first. List contacts through the API to get contact ids; member ids come from your firm's members.

Leads

A lead is a prospective client in your intake pipeline. Use these endpoints to push prospects from an outside intake source into Esqase.

Creating a lead (POST /v1/leads) takes contact-style fields (the same PERSON or COMPANY type and name fields as a contact), plus:

  • email and phone (optional): a single email and phone for the prospect.
  • practiceAreaId (optional): the practice area the prospect is interested in.
  • leadStageColumnId (optional): which pipeline stage the lead starts in. If you leave it out, the lead lands in the first lead stage.
  • value (optional): a rough sense of the lead's worth, from LOWEST to HIGHEST.
  • isPrivate (optional): mark the lead private so only its assignees and firm owners can see it.

Creating a lead also creates its contact. You do not pick an existing contact; the prospect's details you send become a brand-new contact record, and the lead is attached to it. This matches how the New lead dialog works in the app (see Managing leads).

Updating a lead (PATCH /v1/leads/{id}) can change its practice area, stage, value, and private flag.

Deleting a lead (DELETE /v1/leads/{id}) is a soft delete.

Example: create a lead. Send the contact-style name fields plus any single email and phone. The response includes the brand-new contact the lead created:

curl -X POST https://api.esqase.com/v1/leads \
  -H "Authorization: Bearer $ESQASE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "PERSON",
    "firstName": "Jane",
    "lastName": "Doe",
    "email": "jane@example.com",
    "phone": "+1-555-0100",
    "practiceAreaId": "b2c3d4e5-1000-4a1b-9c2d-1234567890ab",
    "value": "HIGH"
  }'
{
  "data": {
    "id": "d4e5f6a7-0001-4a1b-9c2d-1234567890ab",
    "publicId": "L-0007",
    "status": "PENDING",
    "value": "HIGH",
    "isPrivate": false,
    "leadStageColumnId": "d4e5f6a7-9000-4a1b-9c2d-1234567890ab",
    "contact": {
      "id": "a1b2c3d4-0009-4a1b-9c2d-1234567890ab",
      "name": "Jane Doe"
    },
    "practiceArea": {
      "id": "b2c3d4e5-1000-4a1b-9c2d-1234567890ab",
      "name": "Family law"
    }
  }
}

Practice areas

A practice area is the area of law a matter belongs to (for example "Family law" or "Real estate"), and it carries the stages a matter moves through. Use these endpoints to set up the practice areas your matters need, and to look up the ids you pass when creating a matter.

Listing practice areas (GET /v1/practice-areas) returns them in pages, the same as the other lists.

Creating a practice area (POST /v1/practice-areas) takes a single field:

  • name (required): the name of the practice area.

When you create a practice area, Esqase automatically sets up three default stages for it, To do, In progress, and Done, so the practice area is ready to hold matters right away. You do not have to create stages separately.

Reading one practice area (GET /v1/practice-areas/{id}) returns the practice area along with two helpful lists:

  • stages: the stages in the practice area, each with its id, name, type, and order. These ids are what you pass as practiceAreaStageColumnId when you create a matter.
  • matterTypes: the matter types defined under this practice area, each with its id and name.

Reading a practice area this way is the easiest way to resolve the practiceAreaId and practiceAreaStageColumnId you need to create a matter.

Updating a practice area (PATCH /v1/practice-areas/{id}) renames it.

Deleting a practice area (DELETE /v1/practice-areas/{id}) is a soft delete.

Example: create a practice area. The only field you send is the name:

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 new practice area comes back with its three auto-seeded stages, so you have the stage ids you need for a matter right away:

{
  "data": {
    "id": "b2c3d4e5-1000-4a1b-9c2d-1234567890ab",
    "name": "Family law",
    "status": "ACTIVE",
    "stages": [
      { "id": "b2c3d4e5-2000-4a1b-9c2d-1234567890ab", "name": "To do", "type": "TODO", "order": 0 },
      { "id": "b2c3d4e5-2001-4a1b-9c2d-1234567890ab", "name": "In progress", "type": "IN_PROGRESS", "order": 1 },
      { "id": "b2c3d4e5-2002-4a1b-9c2d-1234567890ab", "name": "Done", "type": "DONE", "order": 2 }
    ],
    "matterTypes": []
  }
}

Matter types

A matter type is a sub-type within a practice area (for example, under a "Family law" practice area you might have "Divorce" and "Custody" types). Every matter type belongs to exactly one practice area.

Listing matter types (GET /v1/matter-types?practiceAreaId=<id>) returns the types under one practice area. The practiceAreaId query parameter is required, since matter types are always scoped to a practice area. The list is paged like the others.

Creating a matter type (POST /v1/matter-types) takes two fields:

  • practiceAreaId (required): the practice area the type belongs to.
  • name (required): the name of the matter type.

Reading one matter type (GET /v1/matter-types/{id}) returns it along with the id of the practice area it belongs to.

Updating a matter type (PATCH /v1/matter-types/{id}) renames it.

Deleting a matter type (DELETE /v1/matter-types/{id}) is a soft delete.

Example: list and create matter types. The list call requires the practiceAreaId query parameter:

curl "https://api.esqase.com/v1/matter-types?practiceAreaId=b2c3d4e5-1000-4a1b-9c2d-1234567890ab" \
  -H "Authorization: Bearer $ESQASE_API_KEY"

Creating one takes the practiceAreaId it belongs to and a name:

curl -X POST https://api.esqase.com/v1/matter-types \
  -H "Authorization: Bearer $ESQASE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "practiceAreaId": "b2c3d4e5-1000-4a1b-9c2d-1234567890ab",
    "name": "Divorce"
  }'
{
  "data": {
    "id": "e5f6a7b8-0001-4a1b-9c2d-1234567890ab",
    "practiceAreaId": "b2c3d4e5-1000-4a1b-9c2d-1234567890ab",
    "name": "Divorce",
    "status": "ACTIVE"
  }
}

Note: Practice areas and matter types are gated by the same permission. A key needs the matching practice-areas:* or matter-types:* scope, and the member who created it needs the firm's Practice areas permission (which covers matter types too). See Authentication and scopes.

Code examples

Creating a matter usually takes two calls: first read a practice area to get one of its stage ids, then create the matter with that practice area, stage, and a client contact. Here is that short flow end to end. Both examples read the key from an environment variable and assume you already have a clientContactId (from creating or listing a contact).

In JavaScript (using fetch):

const BASE = "https://api.esqase.com";
const headers = {
  Authorization: `Bearer ${process.env.ESQASE_API_KEY}`,
  "Content-Type": "application/json",
};

// 1. Read a practice area to get one of its stage ids.
const practiceAreaId = "b2c3d4e5-1000-4a1b-9c2d-1234567890ab";
const paRes = await fetch(`${BASE}/v1/practice-areas/${practiceAreaId}`, { headers });
const { data: practiceArea } = await paRes.json();
const stageId = practiceArea.stages[0].id;

// 2. Create a matter in that practice area and stage.
const clientContactId = "a1b2c3d4-0001-4a1b-9c2d-1234567890ab";
const matterRes = await fetch(`${BASE}/v1/matters`, {
  method: "POST",
  headers,
  body: JSON.stringify({
    practiceAreaId,
    practiceAreaStageColumnId: stageId,
    openDate: "2026-07-01",
    title: "Doe v. Acme Holdings",
    clientContactIds: [clientContactId],
  }),
});
const { data: matter } = await matterRes.json();
console.log(`Created matter ${matter.publicId}`);

In Python (using requests):

import os
import requests

BASE = "https://api.esqase.com"
headers = {
    "Authorization": f"Bearer {os.environ['ESQASE_API_KEY']}",
    "Content-Type": "application/json",
}

# 1. Read a practice area to get one of its stage ids.
practice_area_id = "b2c3d4e5-1000-4a1b-9c2d-1234567890ab"
practice_area = requests.get(
    f"{BASE}/v1/practice-areas/{practice_area_id}", headers=headers
).json()["data"]
stage_id = practice_area["stages"][0]["id"]

# 2. Create a matter in that practice area and stage.
client_contact_id = "a1b2c3d4-0001-4a1b-9c2d-1234567890ab"
matter = requests.post(
    f"{BASE}/v1/matters",
    headers=headers,
    json={
        "practiceAreaId": practice_area_id,
        "practiceAreaStageColumnId": stage_id,
        "openDate": "2026-07-01",
        "title": "Doe v. Acme Holdings",
        "clientContactIds": [client_contact_id],
    },
).json()["data"]
print(f"Created matter {matter['publicId']}")

What happens to a record after the API touches it

Records created or changed through the API are not stored off to the side; they join your firm's data like any other:

  • They appear everywhere the matching record appears in the dashboard (lists, detail pages, kanban boards, and reports).
  • Every write is recorded in your firm's audit log, attributed to the member who created the key and noting which key did it. You can see API activity right alongside changes made by hand.
  • Contacts and matters become searchable as soon as they are created, the same as records you add in the app.

Common questions

  • How do I get the id of a record? From the list endpoint (GET /v1/<resource>) or from the response when you create one.
  • Do deletes erase data? No. Deletes through the API are soft deletes, the same as deleting in the app. Records are archived, not destroyed.
  • Can I change a matter's status through the API? Yes. Send a status of ACTIVE, DRAFT, or ARCHIVE on PATCH /v1/matters/{id}.
  • Where do practice area, stage, and member ids come from? Create or list practice areas through the API (GET /v1/practice-areas), then read one (GET /v1/practice-areas/{id}) to get its stage ids. You can also set practice areas and stages up in the app (see Practice areas and stages). Member ids come from your firm's members.
  • What does creating a practice area set up? A new practice area comes with three default stages (To do, In progress, Done), so you can use it for matters immediately without creating stages by hand.