Connector API · v1

Build a connector. Give your agent real data.

A SupportCore connector is a small HTTPS service you host. It lets your support agent look up live data and run the actions you allow — order status, returns, warranty, account changes — all declared by a manifest so SupportCore discovers your capabilities automatically. One contract, any stack, no custom work on our side.

01Quick start

01

Host the endpoint

Serve a small service at /supportcore/v1 over HTTPS, guarded by a bearer key.

02

Publish a manifest

Declare your lookups and actions at GET /manifest. Each becomes an agent tool.

03

Connect it

Paste the base URL + key into Console → Connections. Your agent starts using it.

02Authentication

SupportCore sends a bearer key you issue in the Connections tab. Verify it on every request; reject anything else.

every request from SupportCore
POST https://api.yourapp.com/supportcore/v1/lookup/customer
Authorization: Bearer sc_live_9f2c…a71b
Content-Type: application/json
Idempotency-Key: e0b6…c455   # on actions; safe to retry

HTTPS only. Compare the key in constant time; return 401 on mismatch. Rotate it from the Connections tab any time — no redeploy. Optionally verify an X-SupportCore-Signature HMAC of the body.

03The manifest — where you define capabilities

The heart of the spec. SupportCore fetches your manifest and turns every entry into a tool the agent can use. To teach the agent a new action — say, a note on an order — add one object here and implement its endpoint. That's it.

GET/supportcore/v1/manifest
{
  "connector": "YourApp", "version": "1.0",
  "lookups": [
    { "id": "customer", "title": "Customer profile",
      "description": "Plan, status and recent activity for an email",
      "params": [ { "name": "email", "type": "string", "required": true } ],
      "returns": "plan, status, signup_date, last_active" }
  ],
  "actions": [
    { "id": "add_order_note", "title": "Add a note to an order",
      "params": [ { "name": "order_id", "type": "string", "required": true, "pattern": "^[0-9]+$" },
                 { "name": "note", "type": "string", "required": true } ],
      "approval": "optional", "reversible": true },
    { "id": "cancel_subscription", "title": "Cancel a subscription",
      "params": [ { "name": "email", "type": "string", "required": true } ],
      "approval": "required", "reversible": false }
  ]
}
FieldMeaning
idStable name. SupportCore calls /lookup/{id} or /action/{id}. Never rename a live id.
params[]Each: name, type, required, optional pattern (regex). SupportCore validates against pattern before calling you.
approvalrequired gates the action behind a human click. Default required when omitted.
reversibleIrreversible actions always get an extra confirm and never auto-run.

04Lookups · read

Return support-relevant, non-sensitive data. On no match, return 200 with { "found": false } — not a 404.

POST/supportcore/v1/lookup/customer
request
{ "email": "kees@example.com" }
200 response
{ "found": true,
  "data": { "plan": "Pro", "status": "active",
            "open_invoices": 0 } }

05Actions · write

The logic and permission checks live in your backend — SupportCore only requests it. Return a short human message; the agent may quote it. Honour Idempotency-Key so the same key never acts twice.

POST/supportcore/v1/action/cancel_subscription
request
{ "email": "kees@example.com",
  "_meta": { "approved_by": "stephen@urenapp.eu",
            "ticket": "89155065-…" } }
200 response
{ "ok": true,
  "message": "Cancelled, effective 1 Aug.",
  "reference": "CANCEL-7731" }
Destructive actions (refunds, deletions) should be approval:"required" and reversible:false. SupportCore never auto-runs these — a human confirms first. On a business-rule failure, return 200 with { "ok": false, "message": "…" } so the agent can explain it.

06Reference data & follow-up questions

The agent is only as good as what it can see. Two kinds of lookups make it genuinely helpful:

Record lookups — “this specific customer / order”

Keyed by an identifier (email, order id). We strongly recommend backing these with a read-only database account — grant SELECT on just the tables or views support needs (customers, orders, bookings, invoices) so the agent can see history and status without any write access or exposure of sensitive fields.

Catalogue lookups — “what do we even offer?”

Expose your options too, not just records. A hotel publishes a room_types lookup (types, capacity, price); a shop publishes shipping_options. This is what lets the agent reason and ask the right follow-up instead of guessing.

Worked example — a hotel. A guest writes “a room for 2, what does it cost?” The agent calls room_types, sees that pricing depends on dates and room type, notices the guest gave neither, and replies asking for them — then, once answered, prices it exactly. You didn't script that conversation; you just published the catalogue and the agent did the reasoning.

Structured catalogue — categories & articles, filterable

Take it one step further. Mark a catalogue lookup "catalog": true and point SupportCore at the list of items with an items descriptor. SupportCore imports it into a structured, category-driven Catalog — every entry becomes an article, grouped into categories — and refreshes it on every sync, so a tariff or stock change in your backoffice flows straight through. This is what lets the agent answer “which pitch fits a 5 m caravan with electricity under €40?” by filtering real data instead of guessing from prose.

Name the field that holds each entry's category with items.category and SupportCore groups the articles under it. A category is a first-class thing: the company adds shared remarks and specifications to it once (e.g. “10 A electricity, dogs allowed”), and every article inside inherits them — so the same fact is never repeated per row, and the agent reads it as one truth per category. Omit category for a flat catalogue. The exact same shape works for a booking system, a webshop, a bakery or a butcher — one universal contract, no domain assumptions.

POST/supportcore/v1/lookup/products
// manifest entry — a catalogue lookup that becomes a category-driven Catalog
{ "id": "products", "title": "Product catalogue",
  "catalog": true, "personal": false,
  "items": { "list": "products", "key": "sku",
             "title": "name", "category": "category" },
  "params": [] }

// the lookup's response — each entry is one article; `category` groups them
{ "products": [
    { "sku": "CB-COL-250", "name": "Colombia Supremo 250g", "category": "Koffiebonen",
      "price": 8.5, "in_stock": true, "weight_g": 250 },
    { "sku": "TH-EARL-50", "name": "Earl Grey losse thee 50g", "category": "Thee",
      "price": 5.25, "in_stock": true, "weight_g": 50 } ] }
FieldMeaning
catalogMarks this lookup as the company's catalogue — SupportCore imports it into the structured Catalog, not just a knowledge article.
items.listThe key in your response body that holds the array of items.
items.keyEach item's stable identity (e.g. sku, code) — so the next sync refreshes the same article instead of duplicating it.
items.titleThe field that names each article for a human (the row header).
items.categoryOptional. The field that names each article's category. Present → SupportCore groups the articles into categories (with shared, inherited remarks & specs). Absent → a flat list.

Common fields map onto universal columns automatically — price/price_per_night/tarief → price, description → description, image → photo, stock/in_stock/availability → availability. Everything else (like weight_g) becomes its own typed column — text, number or yes/no, inferred from the values. Return the full list; SupportCore keeps it in step on each sync. A separate per-item lookup that answers the live price or availability keeps those columns live rather than a stored snapshot.

Rule of thumb: if a human agent would need to look something up or ask a clarifying question to answer, publish a lookup for it. The agent will use it the same way.

07Errors & limits

RuleDetail
LatencyRespond within 5 seconds — SupportCore times out after that.
ErrorsHTTP status for transport/auth/server; { ok:false } for business outcomes. Shape: { "error": { "code", "message" } }.
VersioningPin the major version in the path (/v1/). Add fields freely; never remove one within a version.
Rate limitReturn 429 when needed — SupportCore backs off and retries.

08Security

  • Least privilege. Read-only for lookups; writes only through the specific operations your actions implement. Never an admin account.
  • Expose only what support needs. No password hashes, full card numbers or unrelated personal data — even behind a valid key.
  • Validate server-side. The manifest pattern is a first gate; re-check ownership and entitlements in your handler.
  • Rotate & log. Rotate the key; log every call with its ticket and approved_by.

09Full minimal example

connector.js — a complete, compliant connector
const app = require("express")(); app.use(require("express").json());
const KEY = process.env.SUPPORTCORE_KEY;

app.use((req, res, next) => {                       // 1 · auth
  const t = (req.headers.authorization || "").replace("Bearer ", "");
  if (t !== KEY) return res.status(401).json({ error: { code: "unauthorized" } });
  next();
});

app.get("/supportcore/v1/manifest", (_, res) => res.json({   // 2 · manifest
  connector: "YourApp", version: "1.0",
  lookups: [{ id: "customer", title: "Customer profile",
              params: [{ name: "email", type: "string", required: true }] }],
  actions: [{ id: "add_order_note", title: "Add order note", approval: "optional",
              params: [{ name: "order_id", type: "string", required: true, pattern: "^[0-9]+$" },
                       { name: "note", type: "string", required: true }] }]
}));

app.post("/supportcore/v1/lookup/customer", async (req, res) => {  // 3 · lookup
  const u = await db.user(req.body.email);
  res.json(u ? { found: true, data: { plan: u.plan, status: u.status } } : { found: false });
});

app.post("/supportcore/v1/action/add_order_note", async (req, res) => {  // 4 · action
  await db.addNote(req.body.order_id, req.body.note);
  res.json({ ok: true, message: "Note added to order " + req.body.order_id });
});

app.listen(8080);

10Readiness checklist

  • HTTPS at a stable /supportcore/v1 base URL.
  • GET /manifest lists every lookup and action; each id has a handler.
  • Bearer auth enforced; 401 on mismatch; key rotatable.
  • Lookups return { found, data }; actions { ok, message }; under 5s.
  • Idempotency on actions; destructive ones approval:"required".
  • Read-only, least-privilege data; no sensitive fields exposed.
Ready? Paste your base URL and key into Console → Connections → Add connector. SupportCore reads your manifest and your agent starts answering with real data.
Support API · v1

11The other direction — drive tickets from your systems

The Connector API above is how SupportCore calls into your systems. The Support API is the reverse: how your systems talk to SupportCore. Open a ticket from a chat widget, an app or a helpdesk; read the AI's suggested reply; post an answer; close the conversation. Every ticket runs through the same pipeline, trust model and learning loop as email — so a chat widget is just channel:"chat". Building support into your own app or portal? Use channel:"api": that channel never sends email — storing the reply and firing reply.sent is the delivery, and your app shows it. Per category you choose in Console → Settings → AI agent (column App/API) whether the agent answers instantly, chat-style, or every reply waits for human approval first.

base URL & auth
POST https://app.supportcore.ai/api/v1/tickets
Authorization: Bearer sc_live_…          # create one in Console → Developers
Content-Type: application/json

Keys are per-company, hashed at rest and revocable. Each key resolves to exactly one company; all data stays isolated by the same row-level security as the rest of SupportCore.

12Ticket endpoints

EndpointDoes
POST /api/v1/ticketsOpen a ticket and run the AI. Returns the ticket + suggested reply.
POST /api/v1/tickets/{id}/messagesAdd a customer follow-up; re-runs the pipeline on the full thread.
GET /api/v1/tickets/{id}Full state: every message, the current draft, its confidence.
GET /api/v1/ticketsList tickets, newest first. ?status=, ?limit=.
POST /api/v1/tickets/{id}/replyRecord an outbound reply and fire reply.sent.
POST /api/v1/tickets/{id}/closeClose it (fires ticket.closed). /reopen to undo (fires ticket.reopened).
POST/api/v1/tickets
request
{ "customer_email": "kees@acme.com",
  "channel": "api",
  "message": "How do I reset my password?",
  "external_id": "app-ticket-8841",
  "verified": true }
200 response
{ "id": "a1f2…", "status": "open",
  "draft": {
    "status": "pending", "confidence": 0.94,
    "needs_human": false,
    "suggested_reply": "Go to Settings → …" } }

external_id is optional and makes creation idempotent — a retried call returns the same ticket instead of opening a duplicate. When draft.needs_human is true the AI handed off; write the reply yourself and POST it to /reply.

verified: true tells SupportCore that your system already authenticated this customer (a logged-in app user, a portal session). The conversation then starts fully verified: the agent skips identity questions and connector lookups on the customer's own record are allowed immediately. Only set it when the email address is genuinely proven — the claim is made under your API key and lands in the audit log.

Delivery on channel:"api": when the category's App/API column is on auto, the reply is delivered in the API response and reply.sent fires immediately (same guards as chat: permission matrix for actions, no unbacked "it's been sent" claims, paywall). On control, the draft waits in the inbox; human approval then delivers it along the same webhook — never by email.

13Webhooks — get notified the moment anything happens

Register an endpoint in Console → Developers and SupportCore POSTs a signed JSON event to it. Pick the events you care about, or all of them. Deliveries retry with backoff and are logged in plain language so anyone can see what happened — no engineer required.

EventFires when
ticket.createdA new ticket arrives — email, chat or API.
draft.readyThe AI produced a reply, ready to approve or auto-send.
ticket.escalatedThe AI handed off — a human must write this one.
reply.sentA reply reached the customer — email, chat or API. data carries channel and the reply message, so an app can render it without an extra GET.
ticket.closedA ticket was closed.
ticket.reopenedA closed ticket was reopened via the Support API.
action.proposed / .executed / .failedA connector action was proposed, ran, or failed.
what we POST to your URL
{ "event": "ticket.created",
  "tenant": "acme",
  "at": 1753142400,
  "data": { "ticket_id": "a1f2…",
            "customer_email": "guest@acme.com",
            "channel": "chat" } }
headers on every delivery
X-SupportCore-Event: ticket.created
X-SupportCore-Timestamp: 1753142400
X-SupportCore-Signature: sha256=9f2c…

Answer with any 2xx to acknowledge. Anything else (or a timeout) is retried up to three times with backoff, then marked failed in the delivery log.

14Verify the signature

Every delivery is signed so you can trust it came from SupportCore and was not tampered with or replayed. Recompute the HMAC over {timestamp}.{raw_body} with your endpoint secret and compare.

verify.js
const crypto = require("crypto");

function verify(req, secret) {
  const ts  = req.headers["x-supportcore-timestamp"];
  const sig = req.headers["x-supportcore-signature"];
  const mine = "sha256=" + crypto
    .createHmac("sha256", secret)
    .update(ts + "." + req.rawBody)          // the exact bytes we sent
    .digest("hex");
  return crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(mine));
}
Reject anything that doesn't match, and reject timestamps older than a few minutes to stop replays. The endpoint secret is shown next to your endpoint in Console → Developers.