API v1 · 2026-05-14

OraTek API

REST · JSON · Bearer-token auth · HTTPS-only. Stable interface for managing your CRM data programmatically.

Authentication

Every request to /api/v1/* must include a Bearer token in the Authorization header. Issue tokens from Settings → API keys in the CRM. Admin role required.

# Request
GET https://api.oratekdx.info/api/v1/contacts
Authorization: Bearer otk_a1b2c3d4e5f6g7h8i9j0k1l2m3

The full token is shown once at creation. After that you can see its prefix (first 12 chars) in the Settings list. Lost the token? Revoke and create a new one.

Errors

All errors return JSON with a stable shape:

{
  "error": "not_found",
  "message": "Contact does not exist or has been deleted."
}
StatusError codeMeaning
400no_fieldsPATCH with empty body
401missing_api_keyNo Authorization header
401invalid_api_keyToken doesn't match any issued key
401revoked_api_keyToken was revoked
403insufficient_scopeToken missing required scope (e.g. write)
404not_foundRecord doesn't exist in your org
422validationBody failed schema validation; details array lists issues
429rate_limitedToo many requests; retry after Retry-After seconds

Rate limits

5,000 requests per hour per org on Pro. Higher on Enterprise. Limits reset on a sliding window. When you exceed, you get a 429 with a Retry-After header.

Contacts

List contacts

GET /api/v1/contacts

Query params: limit (1–200, default 50), offset, label, stage, q (search name + company).

GET /api/v1/contacts?label=hot&limit=10

// Response
{
  "data": [
    {
      "id": "a1b2c3d4-...",
      "full_name": "Pinnacle Tox Lab",
      "company": "Pinnacle Tox Lab",
      "deal_stage": "qualified",
      "deal_value": 28000,
      "label": "hot"
    }
  ],
  "meta": { "count": 10, "limit": 10, "offset": 0 }
}

Create a contact

POST /api/v1/contacts

Requires write scope. All fields optional except at least one of full_name or company.

POST /api/v1/contacts
Content-Type: application/json

{
  "full_name": "Wasatch Drug Testing",
  "company": "Wasatch Drug Testing",
  "email": "sales@wasatchdrug.example",
  "phone": "801-555-0102",
  "location": "Murray, UT",
  "deal_stage": "qualified",
  "deal_value": 42000,
  "label": "hot",
  "tags": ["employer", "utah"]
}

Returns the created contact with all server-set fields populated.

Get a contact

GET /api/v1/contacts/:id

Update a contact

PATCH /api/v1/contacts/:id

Send only the fields you want to change. Requires write. Triggers contact.updated webhook; deal_stage changes also trigger deal.stage_changed.

Delete a contact

DELETE /api/v1/contacts/:id

Hard delete. Cascades to activities and webhook deliveries. Triggers contact.deleted.

Deals

List deals

GET /api/v1/deals

Convenience view — contacts with deal_stage IS NOT NULL. Same shape as contacts list but only deal-relevant fields. Sort: deal_value DESC.

Tasks

List tasks

GET /api/v1/tasks

Query params: status (open|done|completed|cancelled), limit, offset.

Create a task

POST /api/v1/tasks
{
  "title": "Call Pinnacle Tox Lab about Q3 contract",
  "priority": "high",
  "due_at": "2026-05-20T14:00:00Z",
  "category": "call",
  "contact_id": "a1b2c3d4-..."
}

Triggers task.created.

Update a task

PATCH /api/v1/tasks/:id

Setting status to done or completed triggers task.completed.

Webhooks

Subscribe to events and OraTek will POST signed payloads to your URL. Create subscriptions from Settings → Webhooks in the CRM (admin only).

Available events

Payload shape:

{
  "event": "deal.stage_changed",
  "ts": "2026-05-14T18:24:30.000Z",
  "payload": {
    "contact": { "id": "...", "full_name": "...", "deal_stage": "negotiation", ... },
    "new_stage": "negotiation"
  }
}

Verifying signatures

Every delivery includes X-OraTek-Signature: sha256=<hex> — an HMAC-SHA256 of the raw request body using the secret OraTek issued when you created the subscription. Verify on your side before trusting the payload:

// Node.js example
import crypto from 'crypto';

function verifyOraTekWebhook(rawBody, headerSig, secret) {
  const expected = 'sha256=' + crypto
    .createHmac('sha1', secret).update(rawBody).digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(headerSig), Buffer.from(expected)
  );
}
Retries: we mark a delivery successful on any 2xx response. Non-2xx and timeouts are recorded but not currently retried automatically — that's coming. For now your endpoint should return 200 as fast as possible and queue any heavy work async on your side.

Quickstart: cURL

# Set your token
export ORATEK_TOKEN="otk_a1b2c3d4..."

# List your hot leads
curl -H "Authorization: Bearer $ORATEK_TOKEN" \
  "https://api.oratekdx.info/api/v1/contacts?label=hot"

# Create a contact
curl -X POST \
  -H "Authorization: Bearer $ORATEK_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"full_name":"New Lab","company":"New Lab Co","deal_stage":"lead"}' \
  "https://api.oratekdx.info/api/v1/contacts"

API changelog