QE AI Gateway API

A simple REST API for your apps and AI agents to manage contacts, tags, custom fields and messages on QE AI.

BASE URL
https://gateway.qeofficial.com

Every endpoint lives under /v1. All requests and responses are JSON. Successful responses are wrapped as { "ok": true, "data": … }.

Authentication

Authenticate every request (except /v1/health) with a Bearer QE AI API key in the Authorization header. Keys look like qeai_sk_… and are created in the key portal.

Authorization: Bearer qeai_sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Each key is scoped to one Business and carries a set of scopes — contacts, tags, messages. A call outside a key's scope returns QE_SCOPE_DENIED. Keep keys secret; anyone with a key can act as that business.

Quick start

Find a contact by phone:

# 1. Search for a contact
curl https://gateway.qeofficial.com/v1/contacts/search?phone=60123456789 \
  -H "Authorization: Bearer qeai_sk_YOUR_KEY"

# 2. Tag them
curl -X POST https://gateway.qeofficial.com/v1/contacts/ct_abc123/tags \
  -H "Authorization: Bearer qeai_sk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"tags":["vip"]}'

Concepts & IDs

TermMeaning
ContactA person. IDs are prefixed ct_.
BusinessYour account/workspace. IDs are prefixed biz_.
QE AI API keyThe qeai_sk_ token you send as a Bearer credential.
fieldsCustom fields on a contact, returned as a flat { key: value } object.

Contact object

{
  "id": "ct_abc123",
  "firstName": "Aisha",
  "lastName": "Rahman",
  "email": "aisha@example.com",
  "phone": "+60123456789",
  "tags": ["vip"],
  "fields": { "company": "Acme" },
  "businessId": "biz_xxx",
  "createdAt": "2026-09-16T02:00:00.000Z",
  "updatedAt": "2026-09-16T02:00:00.000Z"
}

Errors

Errors return the matching HTTP status and a stable machine code:

{ "error": { "code": "QE_VALIDATION", "message": "…", "status": 400 } }
CodeStatusMeaning
QE_UNAUTHORIZED401Missing or invalid API key.
QE_SCOPE_DENIED403Key lacks the scope for this endpoint.
QE_BUSINESS_MISMATCH403Resource belongs to another business.
QE_NOT_FOUND404Resource or route not found.
QE_VALIDATION400Request body/params were rejected.
QE_RATE_LIMIT429Too many requests — retry later.
QE_UPSTREAM_ERROR502Service temporarily unavailable.
QE_INTERNAL500Unexpected server error.

POST/v1/contacts

Create or update a contact (upsert by phone/email). scope: contacts

Body

FieldTypeNotes
phonestringRequired if no email.
emailstringRequired if no phone.
firstName, lastName, namestringOptional.
tagsstring[]Optional.
fieldsobjectOptional custom fields, { key: value }.
curl -X POST …/v1/contacts -H "Authorization: Bearer qeai_sk_…" \
  -H "Content-Type: application/json" \
  -d '{"phone":"+60123456789","firstName":"Aisha","tags":["lead"]}'
→ 201  { "ok": true, "data": { "id": "ct_…", … } }

Search contacts. scope: contacts

QueryNotes
phoneSearch by phone.
emailSearch by email.
qFree-text search.
limitMax results (default 20).
curl "…/v1/contacts/search?phone=60123456789" -H "Authorization: Bearer qeai_sk_…"
→ 200  { "ok": true, "data": { "contacts": [ … ], "total": 1 } }

GET/v1/contacts/{id}

Fetch one contact by its ct_ id. scope: contacts

curl …/v1/contacts/ct_abc123 -H "Authorization: Bearer qeai_sk_…"
→ 200  { "ok": true, "data": { "id": "ct_abc123", … } }

PATCH/v1/contacts/{id}

Update fields on a contact. Send only what changes. scope: contacts

curl -X PATCH …/v1/contacts/ct_abc123 -H "Authorization: Bearer qeai_sk_…" \
  -H "Content-Type: application/json" -d '{"email":"new@example.com"}'

POST DELETE/v1/contacts/{id}/tags

Add or remove tags. scope: tags

# add
curl -X POST …/v1/contacts/ct_abc123/tags -H "Authorization: Bearer qeai_sk_…" \
  -H "Content-Type: application/json" -d '{"tags":["vip","paid"]}'
# remove
curl -X DELETE …/v1/contacts/ct_abc123/tags -H "Authorization: Bearer qeai_sk_…" \
  -H "Content-Type: application/json" -d '{"tags":["vip"]}'

GET/v1/businesses/{id}/fields

List the custom fields defined for a business. scope: contacts

curl …/v1/businesses/biz_xxx/fields -H "Authorization: Bearer qeai_sk_…"
→ 200  { "ok": true, "data": { "fields": [ { "key":"company","name":"Company","type":"TEXT" } ] } }

POST/v1/businesses/{id}/fields

Create a new custom field. scope: contacts

FieldTypeNotes
namestringRequired.
typestringOptional, defaults to TEXT.
curl -X POST …/v1/businesses/biz_xxx/fields -H "Authorization: Bearer qeai_sk_…" \
  -H "Content-Type: application/json" -d '{"name":"Company"}'

POST/v1/messages

Send a message to a contact. scope: messages

FieldTypeNotes
contactIdstringRequired, ct_ id.
typestringRequired — SMS, WhatsApp, or Email.
messagestringRequired.
curl -X POST …/v1/messages -H "Authorization: Bearer qeai_sk_…" \
  -H "Content-Type: application/json" \
  -d '{"contactId":"ct_abc123","type":"WhatsApp","message":"Hi Aisha!"}'
→ 201  { "ok": true, "data": { "contactId":"ct_abc123","type":"WhatsApp","status":"sent" } }

GET/v1/health

Liveness check. No authentication required.

curl …/v1/health
→ 200  { "ok": true, "service": "QE AI Developer Gateway", "version": "5.0.0", "timestamp": "…" }
Building an AI agent? Give it this base URL, one qeai_sk_ key, and these endpoints. It never needs to know anything about the platform underneath — QE AI is the whole surface.
QE AI Gateway · API v5.0 · keys are managed at /portal