# Confident LIMS API v0

These are the official docs for the v0 API. Version 0 is currently under
active development and is not yet considered stable — as such, there may be
breaking changes introduced without incrementing the version number.

## API Endpoints

All endpoints use the standard HTTP verbs to describe their behavior
(GET, POST, PUT, DELETE) and use status codes to categorize their
responses (200, 301/302, 400, 401, 403, 404, 405, 500). In general, a
correctly signed and formatted request will return with status code 200.

When sending data, use a correctly escaped querystring for GET
endpoints or a standard form upload
(`application/x-www-form-urlencoded`) for POST/PUT/DELETE.
If you are using a request library, this is likely handled for you
automatically.

**Request bodies are form-encoded, not JSON.** The API
does not accept `application/json` request bodies. Endpoints
that take complex objects (like creating an order) accept a
JSON-encoded string as a single form field — each endpoint's reference
documents this.

Every endpoint returns a JSON formatted response. After receiving a
200, you should always check the `success` field to see if
your request completed as expected.

GET requests generally always succeed, but any POST/PUT/DELETE request
can fail because of pre-requisites or invalid state (like a name
conflict or requiring something to exist), which will set the
`success` flag to `false`. When
`success` is `false`, there will be an
`error_code` field (and potentially
`error_message` and `error_details`) to help you
identify and react to whatever caused the problem.

## Response Envelope

Every response body has roughly the same pieces:

- `success` — true/false flag that is always present
- `error_code` — a string code if an error occurred
  (always present when `success` is false)
- `error_message` — a human readable error message
- `error_details` — per-field validation errors when
  `error_code` is `invalid_request`, keyed by field name

Common error codes returned by every endpoint:

| Code | Status | Meaning |
|---|---|---|
| `missing_api_key` | 401 | Missing `X-ConfidentLims-APIKey` header |
| `invalid_api_key` | 401 | API key is not valid (deleted, not found, etc.) |
| `invalid_credentials_type` | 401 | The key is not an API-secret credential |
| `api_access_denied` | 401 | Account does not have access to the API |
| `api_access_restricted` | 401 | The organization's API access has been restricted |
| `missing_signature` | 401 | Missing `X-ConfidentLims-Signature` header |
| `missing_timestamp` | 401 | Missing `X-ConfidentLims-Timestamp` header |
| `invalid_timestamp` | 401 | Timestamp header is not a valid epoch timestamp |
| `request_too_old` | 400 | Signed request timestamp is too old — includes `current_server_time` in the response for calibration |
| `invalid_signature` | 401 | Request is not correctly signed |
| `permission_denied` | 403 | Account does not have permission for this action |
| `not_found` | 404 | The requested record does not exist or is not visible to this organization |
| `invalid_request` | 400 | Generic problem with the request (usually validation) |

## Authentication and Signing

All endpoints (even GETs) require an API key header
(`X-ConfidentLims-APIKey`). By default, requests must also
be signed with a signature generated using the matching API secret —
incorrectly signed requests are rejected with a 401 response. Signing
can be disabled per credential from the organization settings page to
speed up development.

The signature is the hex-encoded SHA256-HMAC of the API key, the data
fields (sorted alphabetically, ascending), and the API secret.
**Never send your API secret in a request** — it should
only be used for generating signatures.

Read the full, step-by-step signing instructions — including complete
reference implementations in Python and JavaScript — on the
[Request Signing](/v0/docs/request-signing) page.

### Summary of steps to generate a signature

1. Create the base string by combining method and route — e.g.
   `GET/v0/labs/orders`
2. Create an ascii-sorted (ascending), lowercased list of
   (key, value) pairs from the headers dictionary (must include
   `X-ConfidentLims-Timestamp` but not
   `X-ConfidentLims-APIKey` or
   `X-ConfidentLims-Signature`)
3. Create a url-encoded string `key=value&...` for
   the ascii-ordered header fields, lowercased
4. Create a semicolon-separated list of lowercase header keys —
   e.g. `host;x-confidentlims-timestamp`
5. Create an ascii-sorted list of (key, value) pairs from the form
   data (or the querystring for GET requests)
6. Add `('api_key', <your api key>)` to the END of
   the list
7. Create a url-encoded param string `key=value&...`
   for the ordered data fields
8. Percent-encode the base string from step 1
9. Combine the percent-encoded base string, url-encoded header
   string, and url-encoded parameter string with `&`
   between them
10. Create the SHA256 HMAC signature of that string using your API
    secret
11. Prefix with the signing algorithm and header list string:
    `CC0-HMAC-SHA256:host;x-confidentlims-timestamp:<signature>`

## Additional Notes

### Updates

All updates and notifications regarding the API are sent via an email
list. Please email
[api@confidentlims.com](mailto:api@confidentlims.com)
to automatically subscribe.
This includes notifications of upcoming features, breaking changes, and
version upgrades.

### Signing

Getting signing right is the hardest (but most important) part of
interacting with the API — the
[`POST /v0/signingtest`](/v0/docs/signing-test)
endpoint exists explicitly for testing your signing code during
development.

### Paging

Many endpoints that return dynamic lists (e.g. clients, orders, and
samples — but not test types, sample categories, etc.) are limited to
100 results and include a `more_results`
boolean field. If `more_results` is true, additional results
exist, which can be queried by passing a number for `start`
(default 0) and optionally `limit` (default
100), allowing you to iterate through pages until
`more_results` is false.

### Timestamps

Timestamps are always returned in isoformat — for example:
`2016-10-04T13:20:42.395276`.

Dates with times should always be sent in UTC time in the format
`YYYY-MM-DD HH:MM:SS` — for example:
`2016-10-04 13:20:42`.

Dates should always be sent as `YYYY-MM-DD` — for example:
`2016-10-04`.

### Currency and Pricing

All currency and pricing numbers are always represented as integer cents
(instead of floating point dollars). Currency and pricing fields are
generally named to reflect this.

### File Uploads

Several endpoints support uploading files (images, PDFs, etc.). These
file fields are **not** included when generating
signatures, so be sure to exclude them during the signing process. Send
files as standard `multipart/form-data` uploads.

### Machine-readable docs

Every page of these docs is available as Markdown by appending `.md` to
its URL (this page: `index.md`). [`llms.txt`](/v0/docs/llms.txt)
indexes every page, [`llms-full.txt`](/v0/docs/llms-full.txt)
holds them all in one file, and each section publishes its OpenAPI 3.1
spec at `openapi.json` (this section: [`openapi.json`](/v0/docs/openapi.json)).

### Additional Help

If you need any help or just have questions, comments, or complaints,
please reach out at
[api@confidentlims.com](mailto:api@confidentlims.com).
We want to make sure you have
the best experience possible, so definitely let us know what you think
or if you get stuck!

---

HTML version: https://api.confidentcannabis.com/v0/docs/
