{
  "openapi": "3.1.0",
  "info": {
    "title": "Sentinel API",
    "version": "1.0.0",
    "description": "Read-only access to a firm's Sentinel matters, Discovery documents and search. Every request runs as the user who owns the API key, within that user's matter access."
  },
  "servers": [
    {
      "url": "https://{firm}.api.sntlabs.io/api/v1",
      "variables": {
        "firm": {
          "default": "yourfirm",
          "description": "Your firm's Sentinel API host. Copy it from Your account → Connected apps."
        }
      }
    }
  ],
  "security": [
    {
      "apiKey": []
    }
  ],
  "tags": [
    {
      "name": "Matters",
      "description": "Cases and deals the caller can access."
    },
    {
      "name": "Library",
      "description": "A matter's Discovery documents: list, read and search."
    }
  ],
  "paths": {
    "/matters": {
      "get": {
        "operationId": "listMatters",
        "tags": [
          "Matters"
        ],
        "summary": "List matters",
        "description": "Lists the cases and deals the caller can access, newest first. Admins see every matter; other users see the matters they own or are assigned to.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            },
            "description": "Matters per page."
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "The `next_cursor` from the previous page."
          },
          {
            "name": "include_closed",
            "in": "query",
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "Include closed or archived cases and closed or terminated deals."
          }
        ],
        "responses": {
          "200": {
            "description": "A page of matters.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "pagination"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Matter"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "3f6c2a1e-8b4d-4c7a-9e21-5d0b7f9a1c34",
                      "type": "case",
                      "number": "CASE-2025-0142",
                      "title": "Acme Corp. v. Rivera",
                      "status": "litigation",
                      "priority": "high",
                      "sub_type": "trade_secrets",
                      "closed": false,
                      "archived_at": null,
                      "created_at": "2025-03-04T15:12:09.000Z"
                    },
                    {
                      "id": "9b1f2f41-6a0e-4f3b-9d0a-1c2b3d4e5f60",
                      "type": "deal",
                      "number": "DEAL-2025-0031",
                      "title": "Northwind Logistics acquisition",
                      "status": "active",
                      "priority": "medium",
                      "sub_type": "acquisition",
                      "closed": false,
                      "archived_at": null,
                      "created_at": "2025-02-18T09:21:40.000Z"
                    }
                  ],
                  "pagination": {
                    "limit": 20,
                    "has_more": true,
                    "next_cursor": "WyIyMDI1LTAyLTE4IDA5OjIxOjQwLjAxMjM0NSIsIjliMWYyZjQxIl0"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "x-example-query": "limit=20"
      }
    },
    "/matters/{matter_id}": {
      "get": {
        "operationId": "getMatter",
        "tags": [
          "Matters"
        ],
        "summary": "Get matter",
        "description": "Gets one case or deal, including its description.",
        "parameters": [
          {
            "$ref": "#/components/parameters/MatterId"
          }
        ],
        "responses": {
          "200": {
            "description": "The matter.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MatterDetail"
                },
                "example": {
                  "id": "3f6c2a1e-8b4d-4c7a-9e21-5d0b7f9a1c34",
                  "type": "case",
                  "number": "CASE-2025-0142",
                  "title": "Acme Corp. v. Rivera",
                  "description": "Misappropriation of pricing data by a departing sales director.",
                  "status": "litigation",
                  "priority": "high",
                  "sub_type": "trade_secrets",
                  "closed": false,
                  "archived_at": null,
                  "created_at": "2025-03-04T15:12:09.000Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/MatterNotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/matters/{matter_id}/documents": {
      "get": {
        "operationId": "listDocuments",
        "tags": [
          "Library"
        ],
        "summary": "List documents",
        "description": "Lists a matter's Discovery documents, newest first by sent or document date (undated documents last). Attachments are left out unless you ask for them. Documents the caller isn't allowed to see never appear.",
        "parameters": [
          {
            "$ref": "#/components/parameters/MatterId"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "description": "Documents per page."
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "The `next_cursor` from the previous page."
          },
          {
            "name": "custodian",
            "in": "query",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 200
            },
            "description": "Only documents whose custodian name contains this text (case-insensitive)."
          },
          {
            "name": "sent_after",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Only documents sent or dated on or after this day (`YYYY-MM-DD`, UTC). Undated documents are filtered by their upload date."
          },
          {
            "name": "sent_before",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Only documents sent or dated on or before this day, inclusive (`YYYY-MM-DD`, UTC). Undated documents are filtered by their upload date."
          },
          {
            "name": "include_attachments",
            "in": "query",
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "Include email attachments as their own rows."
          }
        ],
        "responses": {
          "200": {
            "description": "A page of documents.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "pagination"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Document"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "8e4b1f7a-3c9d-4a52-b6e0-1d7f2c9a5b84",
                      "matter_id": "3f6c2a1e-8b4d-4c7a-9e21-5d0b7f9a1c34",
                      "file_name": "RE_ Q3 pricing.msg",
                      "document_type": "email",
                      "mime_type": "application/vnd.ms-outlook",
                      "file_size": 48213,
                      "page_count": 2,
                      "custodian": "M. Rivera",
                      "parent_document_id": null,
                      "bates_number": "ACME000412",
                      "email": {
                        "subject": "RE: Q3 pricing",
                        "sender": "m.rivera@acme.com",
                        "recipients": [
                          "j.patel@acme.com",
                          "s.lindqvist@acme.com"
                        ],
                        "sent_at": "2024-09-12T16:41:00.000Z"
                      },
                      "document_date": null,
                      "uploaded_at": "2025-03-06T10:02:17.000Z"
                    }
                  ],
                  "pagination": {
                    "limit": 50,
                    "has_more": true,
                    "next_cursor": "WyIyMDI0LTA5LTEyIDE2OjQxOjAwIiwiOGU0YjFmN2EiXQ"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/MatterNotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "x-example-query": "custodian=Rivera&limit=50"
      }
    },
    "/documents/{document_id}": {
      "get": {
        "operationId": "getDocument",
        "tags": [
          "Library"
        ],
        "summary": "Get document",
        "description": "Gets one Discovery document's metadata, and optionally its extracted text.",
        "parameters": [
          {
            "name": "document_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 64
            },
            "description": "The document's ID, as a list or search returned it."
          },
          {
            "name": "include",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "text"
              ]
            },
            "description": "`text` adds the document's extracted text, up to 500,000 characters."
          }
        ],
        "responses": {
          "200": {
            "description": "The document.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DocumentWithText"
                },
                "example": {
                  "id": "8e4b1f7a-3c9d-4a52-b6e0-1d7f2c9a5b84",
                  "matter_id": "3f6c2a1e-8b4d-4c7a-9e21-5d0b7f9a1c34",
                  "file_name": "RE_ Q3 pricing.msg",
                  "document_type": "email",
                  "mime_type": "application/vnd.ms-outlook",
                  "file_size": 48213,
                  "page_count": 2,
                  "custodian": "M. Rivera",
                  "parent_document_id": null,
                  "bates_number": "ACME000412",
                  "email": {
                    "subject": "RE: Q3 pricing",
                    "sender": "m.rivera@acme.com",
                    "recipients": [
                      "j.patel@acme.com",
                      "s.lindqvist@acme.com"
                    ],
                    "sent_at": "2024-09-12T16:41:00.000Z"
                  },
                  "document_date": null,
                  "uploaded_at": "2025-03-06T10:02:17.000Z",
                  "text": "Attached is the full 2024 price list. Please keep this between us until I start at Northwind on the 15th.",
                  "text_truncated": false
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/DocumentNotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "x-example-query": "include=text"
      }
    },
    "/matters/{matter_id}/search": {
      "post": {
        "operationId": "searchDocuments",
        "tags": [
          "Library"
        ],
        "summary": "Search documents",
        "description": "Searches a matter's Discovery documents and returns ranked hits with snippets. Results are checked against the caller's access like every list, so a hit is never a document the caller can't open.",
        "parameters": [
          {
            "$ref": "#/components/parameters/MatterId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "query"
                ],
                "properties": {
                  "query": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 500,
                    "description": "What to search for. Keyword mode supports exact phrases, names and Bates numbers."
                  },
                  "mode": {
                    "type": "string",
                    "enum": [
                      "keyword",
                      "semantic",
                      "hybrid"
                    ],
                    "default": "hybrid",
                    "description": "`keyword` for exact terms, `semantic` for meaning, `hybrid` for both (best for most questions)."
                  },
                  "limit": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 50,
                    "default": 20,
                    "description": "Hits to return."
                  }
                }
              },
              "example": {
                "query": "price list Northwind",
                "mode": "hybrid",
                "limit": 10
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Ranked hits, best first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "mode",
                    "degraded"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SearchHit"
                      }
                    },
                    "mode": {
                      "type": "string",
                      "enum": [
                        "keyword",
                        "semantic",
                        "hybrid"
                      ],
                      "description": "The mode that ran."
                    },
                    "degraded": {
                      "type": "boolean",
                      "description": "`true` when a hybrid search fell back to keyword-only because the semantic side failed."
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "document": {
                        "id": "8e4b1f7a-3c9d-4a52-b6e0-1d7f2c9a5b84",
                        "matter_id": "3f6c2a1e-8b4d-4c7a-9e21-5d0b7f9a1c34",
                        "file_name": "RE_ Q3 pricing.msg",
                        "document_type": "email",
                        "mime_type": "application/vnd.ms-outlook",
                        "file_size": 48213,
                        "page_count": 2,
                        "custodian": "M. Rivera",
                        "parent_document_id": null,
                        "bates_number": "ACME000412",
                        "email": {
                          "subject": "RE: Q3 pricing",
                          "sender": "m.rivera@acme.com",
                          "recipients": [
                            "j.patel@acme.com"
                          ],
                          "sent_at": "2024-09-12T16:41:00.000Z"
                        },
                        "document_date": null,
                        "uploaded_at": "2025-03-06T10:02:17.000Z"
                      },
                      "snippet": "Attached is the full 2024 <mark>price</mark> <mark>list</mark>. Please keep this between us until I start at <mark>Northwind</mark>…",
                      "score": 0.0325
                    }
                  ],
                  "mode": "hybrid",
                  "degraded": false
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/MatterNotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "A Sentinel API key: `Authorization: Bearer sk_live_…`. Create one in Your account → Connected apps → For developers: API keys."
      }
    },
    "parameters": {
      "MatterId": {
        "name": "matter_id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "maxLength": 64
        },
        "description": "The matter's ID, from List matters."
      }
    },
    "schemas": {
      "Matter": {
        "type": "object",
        "required": [
          "id",
          "type",
          "number",
          "title",
          "status",
          "priority",
          "sub_type",
          "closed",
          "archived_at",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Matter ID."
          },
          "type": {
            "type": "string",
            "enum": [
              "case",
              "deal"
            ]
          },
          "number": {
            "type": "string",
            "description": "Case or deal number, e.g. `CASE-2025-0142`."
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": [
              "string",
              "null"
            ],
            "description": "Case or deal status, as set in Sentinel."
          },
          "priority": {
            "type": [
              "string",
              "null"
            ]
          },
          "sub_type": {
            "type": [
              "string",
              "null"
            ],
            "description": "Case violation type or deal type."
          },
          "closed": {
            "type": "boolean",
            "description": "A closed or archived case, or a closed or terminated deal."
          },
          "archived_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When a case was archived. Always null for deals."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "MatterDetail": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Matter"
          },
          {
            "type": "object",
            "required": [
              "description"
            ],
            "properties": {
              "description": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          }
        ]
      },
      "Document": {
        "type": "object",
        "required": [
          "id",
          "matter_id",
          "file_name",
          "document_type",
          "mime_type",
          "file_size",
          "page_count",
          "custodian",
          "parent_document_id",
          "bates_number",
          "email",
          "document_date",
          "uploaded_at"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "matter_id": {
            "type": "string"
          },
          "file_name": {
            "type": "string"
          },
          "document_type": {
            "type": [
              "string",
              "null"
            ],
            "description": "For example `email`, `attachment` or `file`."
          },
          "mime_type": {
            "type": [
              "string",
              "null"
            ]
          },
          "file_size": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Bytes."
          },
          "page_count": {
            "type": [
              "integer",
              "null"
            ]
          },
          "custodian": {
            "type": [
              "string",
              "null"
            ]
          },
          "parent_document_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "For an attachment, the email it belongs to."
          },
          "bates_number": {
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "type": [
              "object",
              "null"
            ],
            "description": "Email envelope, when the document is an email.",
            "properties": {
              "subject": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "sender": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "recipients": {
                "type": [
                  "array",
                  "null"
                ],
                "items": {
                  "type": "string"
                }
              },
              "sent_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              }
            }
          },
          "document_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "uploaded_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "DocumentWithText": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Document"
          },
          {
            "type": "object",
            "properties": {
              "text": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Extracted text. Only with `include=text`. Null when the document has no text yet."
              },
              "text_truncated": {
                "type": "boolean",
                "description": "Only with `include=text`. `true` when the text was cut at 500,000 characters."
              }
            }
          }
        ]
      },
      "SearchHit": {
        "type": "object",
        "required": [
          "document",
          "snippet",
          "score"
        ],
        "properties": {
          "document": {
            "$ref": "#/components/schemas/Document"
          },
          "snippet": {
            "type": [
              "string",
              "null"
            ],
            "description": "Matching passage, up to 1,000 characters. Keyword matches are wrapped in `<mark>` tags."
          },
          "score": {
            "type": "number",
            "description": "Relevance within this search. Higher is better. Scores aren't comparable across modes."
          }
        }
      },
      "Pagination": {
        "type": "object",
        "required": [
          "limit",
          "has_more",
          "next_cursor"
        ],
        "properties": {
          "limit": {
            "type": "integer"
          },
          "has_more": {
            "type": "boolean"
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Pass as `cursor` to get the next page. Null on the last page."
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "type",
              "message"
            ],
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "invalid_request",
                  "authentication_required",
                  "permission_denied",
                  "not_found",
                  "rate_limited",
                  "server_error"
                ]
              },
              "message": {
                "type": "string"
              }
            }
          }
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Invalid parameters.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "type": "invalid_request",
                "message": "limit: Too big: expected number to be <=100"
              }
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Missing, invalid, revoked or expired API key.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "type": "authentication_required",
                "message": "Invalid or expired API key"
              }
            }
          }
        }
      },
      "Forbidden": {
        "description": "The key's role can't use the API.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "type": "permission_denied",
                "message": "This key's role can't use the API"
              }
            }
          }
        }
      },
      "MatterNotFound": {
        "description": "No such matter, or the caller can't access it.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "type": "not_found",
                "message": "Matter not found"
              }
            }
          }
        }
      },
      "DocumentNotFound": {
        "description": "No such document, or the caller can't see it.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "type": "not_found",
                "message": "Document not found"
              }
            }
          }
        }
      },
      "RateLimited": {
        "description": "More than 120 requests in a minute from one key. Retry after the `Retry-After` header's seconds.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "type": "rate_limited",
                "message": "Too many requests: up to 120 a minute per key"
              }
            }
          }
        }
      }
    }
  }
}
