Test with a sandbox key

A sandbox key works on sample data instead of your organization's records, so you can build and test an integration before it touches them. It uses the same base URL, POST /v2/oauth/token, endpoints and response shapes as a live key, so code written against the sandbox runs unchanged with a live key. A sandbox request never reads or writes your organization's records. Brellium never processes a sandbox document, and the sandbox emails nobody and never reads a file you upload.

Create a sandbox key

On the API keys page of the dashboard, click Create key and enter a name. Choose Sandbox under Environment (Production is the default), then copy the Client ID, Client secret and Audience. The dashboard shows the secret only when you create the key. Rotate secret in the key's row menu issues a new one, also shown once.

The page needs API platform access, which an administrator grants in Brellium. Without it, API keys shows "No access to manage API keys". To get access, ask an administrator or contact Brellium support.

A sandbox key carries a Sandbox badge on the API keys page. Its environment is fixed at creation, and Rotate secret does not change it. To use the live environment, create a second key.

Mint a sandbox token

Mint a token as you would with a live key, but set audience to https://api.brellium.dev/sandbox (Authenticate with OAuth2 client credentials).

curl "https://api.brellium.com/v2/oauth/token" \
  --request POST \
  --header "Content-Type: application/json" \
  --data "{
    \"grant_type\": \"client_credentials\",
    \"client_id\": \"$BRELLIUM_SANDBOX_CLIENT_ID\",
    \"client_secret\": \"$BRELLIUM_SANDBOX_CLIENT_SECRET\",
    \"audience\": \"https://api.brellium.dev/sandbox\"
  }"
const res = await fetch("https://api.brellium.com/v2/oauth/token", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    grant_type: "client_credentials",
    client_id: process.env.BRELLIUM_SANDBOX_CLIENT_ID,
    client_secret: process.env.BRELLIUM_SANDBOX_CLIENT_SECRET,
    audience: "https://api.brellium.dev/sandbox",
  }),
});
const body = await res.json();
if (!res.ok) throw new Error(`HTTP ${res.status}: ${JSON.stringify(body)}`);
console.log(body);

Send the token as Authorization: Bearer <token> to the same base URL. There is no sandbox host and no header to set, because the token's audience picks the environment. A sandbox key mints tokens only for the sandbox audience and a live key only for the live one; the token endpoint refuses the other audience with an access_denied body.

The two sandbox-specific 401 codes, ENVIRONMENT_MISMATCH and SANDBOX_UNAVAILABLE, are listed in Rate limits and errors. For a walkthrough from key to audit, see Test end to end with sandbox credentials.

Sample data

Your first sandbox request seeds the sample data, which uses the same ids as the examples in the guides and the API reference:

Resource Sample record
Audit GET /v2/audits/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d: one complete audit with three responses, two flags and a score
Document GET /v2/documents/0f8e7d6c-5b4a-4938-8271-605f4e3d2c1b: the document the audit covers, already complete
Question set GET /v2/question_sets/3c2b1a09-8f7e-4d6c-b5a4-938271605f4e: the three questions the audit was scored against
Coding evaluation GET /v2/coding_evaluations/6f1c2b3a-4d5e-4f60-8a71-92b3c4d5e6f7: one complete coding evaluation with an evaluation and management (E/M) result and an International Classification of Diseases (ICD) result
User GET /v2/users/7c6d5e4f-3a2b-4c1d-9e8f-7a6b5c4d3e2f: one user who has already signed in
Email recipient GET /v2/email_recipients/2a1b0c9d-8e7f-4a6b-9c5d-4e3f2a1b0c9d: the provider in the audit's people[]

Differences from live

Reads, filters, pagination, validation and errors behave the same as with a live key. In the sandbox, the next request already sees a write; with a live key, lists can take a few seconds to include a new or changed record. The other differences are in what happens after a write:

Resource In the sandbox
Documents POST /v2/documents works as it does with a live key (Upload documents). The sandbox never reads the files you upload and simulates processing on a fixed schedule (Document status schedule).
Audits The sandbox serves the sample audit and the audits it creates for your uploads. PATCH /v2/audits/{audit_id} and POST /v2/audits/batch_update set the flags and move updated_at to the time of the update. reviewed_at stays null, because nobody reviews a sandbox audit.
Coding evaluations, question sets The sample records are read-only. The sandbox creates no coding evaluation for an upload.
Users Create, update and delete work as they do with a live key, with the same rules and 409 codes. Creating a user with the sample user's email address returns 409 USER_EXISTS. POST /v2/users returns the invitation object with sent_at and expires_at (7 days after sent_at) and emails nobody. DELETE /v2/users/{user_id} never returns 502 REVOKE_FAILED, because there is no invitation or sign-in to revoke.
Email recipients Create, update and delete work as they do with a live key, with the same rules and 409 codes. As with a live key, a user you create also gets an email recipient with the same person_id.

The audit the sandbox creates for an upload is the sample audit with new ids, so its responses are the same for every upload. In the sandbox, every uploaded document gets an audit, except a zip and any document still without an audit when its batch expires. With a live key, Brellium decides which documents are audited.

Sandbox and live keys both need your organization set up for document uploads. Without that setup, POST /v2/documents returns 404 ORG_NOT_FOUND; contact Brellium support. Every other endpoint works without it.

Document status schedule

The sandbox moves documents through their statuses on a fixed schedule instead of processing them.

Start the upload

As with a live key, POST /v2/documents creates the documents in pending_upload with an upload URL for each.

curl "https://api.brellium.com/v2/documents" \
  --request POST \
  --header "Authorization: Bearer $SANDBOX_ACCESS_TOKEN" \
  --header "Content-Type: application/json" \
  --data '{
    "documents": [
      { "filename": "session-note.pdf", "content_type": "application/pdf" },
      { "filename": "attachment.pdf", "content_type": "application/pdf" }
    ],
    "grouping": "group"
  }'
const res = await fetch("https://api.brellium.com/v2/documents", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.SANDBOX_ACCESS_TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    documents: [
      { filename: "session-note.pdf", content_type: "application/pdf" },
      { filename: "attachment.pdf", content_type: "application/pdf" },
    ],
    grouping: "group",
  }),
});
const body = await res.json();
if (!res.ok) throw new Error(`HTTP ${res.status}: ${JSON.stringify(body.error)}`);
console.log(body);
import BrelliumClient from "@brellium/api-client";

const client = new BrelliumClient({ oAuth2: process.env.SANDBOX_ACCESS_TOKEN });

const documents = await client.documents.create({
  documents: [
    { filename: "session-note.pdf", content_type: "application/pdf" },
    { filename: "attachment.pdf", content_type: "application/pdf" },
  ],
  grouping: "group",
});
console.log(documents);
import os

from brellium_api_client import BrelliumClient

client = BrelliumClient(o_auth2=os.environ["SANDBOX_ACCESS_TOKEN"])

documents = client.documents.create(
    documents=[
        {"filename": "session-note.pdf", "content_type": "application/pdf"},
        {"filename": "attachment.pdf", "content_type": "application/pdf"},
    ],
    grouping="group",
)
print(documents.to_json())
Upload the file

PUT any file to each upload URL, with the Content-Type from upload.headers. After your upload, the next read returns each document whose file has arrived as todo.

Wait 30 seconds

Thirty seconds after a document becomes todo, reads return it as complete, with updated_at set to exactly that time.

Fetch the audit

With grouping: "group", the sandbox creates one audit for the batch once every document in it is complete, and the audit has every document id in documents. With grouping: "separate" (the default), it creates one audit per document as each completes. The audit's created_at and updated_at equal the completion time, and reviewed_at is null. Find it with GET /v2/audits?documents=<document_id>.

As with a live key, if any file of a batch is still missing 24 hours after the batch was created, the whole batch expires (Incomplete batches expire after 24 hours). Its pending_upload documents become upload_expired and its external_id values are released, so you can send the batch again.

Zips

The API validates and creates a zip as it does with a live key, but the sandbox never unpacks it. Once its file arrives, the zip document moves to extracted with metadata.archive set to { "grouping": "<your grouping>", "entries": 0, "skipped": 0 }. The sandbox creates no documents from the files in the zip, and no audit follows.

Limits

The sandbox holds at most 5,000 records per resource per organization, and a create past that limit returns 409 SANDBOX_LIMIT. To make room, delete some users or email recipients, or reset the sandbox. A reset is the only way to clear documents and audits, which have no delete endpoint.

Your organization can send a burst of 100 requests, then 5 per second, shared by every key; the sandbox has a separate budget with the same limits. For the rate-limit headers and how to retry a 429, see Rate limits and errors.

Reset the sandbox

A POST to /v2/sandbox/reset with a sandbox token and no body deletes everything in your organization's sandbox, uploaded files included, and returns 204. The next request seeds the sample data again. The endpoint exists only in the sandbox and is not in the reference; with a live token it returns 404 NOT_FOUND.

Each organization has its own sandbox. Nothing in it is deleted on a schedule, so its records and files persist until you reset.

curl "https://api.brellium.com/v2/sandbox/reset" \
  --request POST \
  --header "Authorization: Bearer $SANDBOX_ACCESS_TOKEN"
const res = await fetch("https://api.brellium.com/v2/sandbox/reset", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.SANDBOX_ACCESS_TOKEN}` },
});
if (!res.ok)
  throw new Error(
    `HTTP ${res.status}: ${JSON.stringify((await res.json()).error)}`,
  );
console.log(`HTTP ${res.status}`);

When two sandbox requests change the same record at the same time, the sandbox keeps the last write. Run tests that depend on an exact sequence from one process at a time.

Next