Ngentix

Quickstart

Integrate once. Your product and your agents both get it.

Three calls to real data, with no provider account and no OAuth app. Start here even if you intend to use a live provider — the request shapes are identical, so nothing you learn is thrown away.

1. Get your key

Already signed in? Your keys are in API keys — a sandbox one is created with your account. New here? Create an account and a sandbox key is issued automatically.

Either way the full key is shown once, when it is created or rotated — copy it then. It looks like cc_test_….

2. Connect a sandbox connector

Sandbox connectors are synthetic: they return realistic data without calling anyone's live API. There is no provider account to register and no OAuth flow to complete, so this call is the whole setup step.

What is an end customer? One of your customers — the business whose HubSpot or Salesforce you are calling. Every call is made on behalf of one of them, which is how we know whose stored credentials to use. In the API they are identified by consumer_external_id — your own id for them, any string you choose — and every proxy call carries it as the X-Ngentix-Consumer header.

curl -X POST https://cloud.ngentix.ai/v1/connect/sandbox \
  -H "Authorization: Bearer cc_test_…" \
  -H "Content-Type: application/json" \
  -d '{"connector_id": "sandbox_crm", "consumer_external_id": "cust_123"}'

You get back a connection with "status": "active" and "synthetic": true. Sandbox end customers are never billed.

3. Call the provider on your customer's behalf

X-Ngentix-Consumer is what scopes the call to that end customer. This returns 25 contacts:

curl https://cloud.ngentix.ai/v1/proxy/sandbox_crm/contacts \
  -H "Authorization: Bearer cc_test_…" \
  -H "X-Ngentix-Consumer: cust_123"

Or get the same records in our normalized shape for the CRM and HRIS verticals — see unified models:

curl "https://cloud.ngentix.ai/v1/unified/crm/contacts?connector_id=sandbox_crm&consumer_external_id=cust_123" \
  -H "Authorization: Bearer cc_test_…"

That is a working integration. Sandbox responses carry X-Ngentix-Synthetic: true so you can tell synthetic data apart in your logs. The three sandbox connectors are sandbox_crm, sandbox_hris, and sandbox_support.

Next: a real provider

When you are ready for live data, the only thing that changes is how the connection is created — the proxy and unified calls above stay exactly the same. A real provider needs an OAuth app registered with that provider, so instead of /v1/connect/sandbox you use the connect widget, a hosted portal, or /v1/connect/start, and your end customer authorizes it. Each connector page lists exactly what that provider requires.

Verify your key any time with a call that needs nothing else — no connection, no provider account:

curl https://cloud.ngentix.ai/v1/catalog \
  -H "Authorization: Bearer cc_test_…"

TypeScript

The TypeScript and Python SDKs are not yet published to npm or PyPI — build them from source, or call the API directly with curl. Every example below works against the HTTP API today.

import { ConnectorCloud } from "@ngentix/connector-cloud";

const cc = new ConnectorCloud({ apiKey: "cc_test_…" });
const { authorize_url } = await cc.connectStart({
  connectorId: "hubspot",
  consumerExternalId: "cust_123",
});
// redirect end-user to authorize_url

const data = await cc.proxy({
  connectorId: "hubspot",
  path: "crm/v3/objects/contacts",
  consumerExternalId: "cust_123",
});

Proxy signing

Every proxy call requires X-Ngentix-Consumer. Optional end-user signature:

X-Ngentix-Timestamp: <unix>
X-Ngentix-Signature: hex(HMAC-SHA256(org_signing_secret, "{ts}.{method}.{connector}.{path}.{consumer}"))
X-Ngentix-Consumer: cust_123

Response includes X-Ngentix-Proxy-Overhead-Ms. We publish a live p95 overhead figure — it is a measurement, not a contractual SLA. Check the current number:

GET /v1/status

MCP / agents

Per-tenant tools at GET /v1/mcp/tools and POST /v1/mcp/call.

curl https://cloud.ngentix.ai/v1/mcp/tools \
  -H "Authorization: Bearer cc_test_…"

Claude Agent SDK

import Anthropic from "@anthropic-ai/sdk";

// Point your MCP client / tool bridge at Ngentix:
const MCP_TOOLS = await fetch(`${BASE}/v1/mcp/tools`, {
  headers: { Authorization: `Bearer ${API_KEY}` },
}).then((r) => r.json());

async function callTool(name, args) {
  return fetch(`${BASE}/v1/mcp/call`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ name, arguments: args }),
  }).then((r) => r.json());
}

// Example: list HubSpot contacts for an end user
await callTool("hubspot.list_contacts", { consumer_external_id: "cust_123" });

OpenAI Agents SDK

// Register each MCP tool from GET /v1/mcp/tools as a function tool.
async function ngentixTool(name, args) {
  const res = await fetch(`${process.env.CC_BASE}/v1/mcp/call`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.CC_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ name, arguments: args }),
  });
  return res.json();
}

// tool_name examples: salesforce.proxy, hubspot.list_contacts, gusto.list_employees
await ngentixTool("gusto.list_employees", { consumer_external_id: "cust_123" });

Tool ACLs: POST /v1/mcp/grants. Audit: GET /v1/mcp/audit.

Unified models

CRM + HRIS only. Same connection as proxy/MCP — no re-auth.

GET /v1/unified/crm/contacts?connector_id=hubspot&consumer_external_id=cust_123
GET /v1/unified/hris/employees?connector_id=gusto&consumer_external_id=cust_123
GET /v1/unified   # category/resource map

Triggers + virtual webhooks

POST /v1/triggers
{
  "consumer_external_id": "cust_123",
  "connector_id": "hubspot",
  "event_type": "contact.updated",
  "mode": "virtual",
  "poll_interval_secs": 60
}

Virtual mode polls on an interval and delivers a normalized envelope to your webhook endpoints.

Hosted portal + directory

POST /v1/portal/sessions  { "consumer_external_id": "cust_123" }
# → { url: "https://…/portal/{token}" }

GET /directory/{org_slug}     # brandable marketplace page
GET /v1/directory/{org_slug}  # JSON

Auth kinds

POST /v1/connect/credential
{ "connector_id": "bamboohr", "consumer_external_id": "cust_123",
  "auth_kind": "api_key", "credential": "…" }
# auth_kind: api_key | jwt | basic (oauth via /v1/connect/start)

Observability

GET /v1/observability/logs?q=hubspot
GET /v1/observability/health
GET /v1/observability/issues

Billing preview

The preview counts end customers. The Stripe invoice is a flat subscription (quantity: 1) — consumer count is the preview API, not the charge.

GET /v1/billing/preview

Connect widget

Two credentials, and both are required. The publishable key says which developer you are; the consumer token says which of your end customers is looking. The key alone is not enough — it is public by design, so without the token anyone reading your page source could list and revoke every one of your end customers.

Step 1 — mint a consumer token on your server, for a user you have already signed in. Use your secret key here, never in the browser:

POST /v1/portal/sessions
Authorization: Bearer <your secret key>

{ "consumer_external_id": "cust_123" }
# → { "token": "…" }

Both SDKs wrap this call — createPortalSession in TypeScript, create_portal_session in Python — so you do not have to hand-roll it.

Step 2 — pass that token to the widget:

<script src="https://cloud.ngentix.ai/widget/v1/connect.js"></script>
<div id="box"></div>
<script>
  NgentixConnect.mount("#box", {
    publishableKey: "cc_pub_…",
    consumerToken: "…",           // from step 1, per signed-in user
    connectorId: "salesforce",
    consumerExternalId: "cust_123",
    baseUrl: "https://cloud.ngentix.ai",
    brand: { name: "Acme", primary: "#0f766e" }
  });
</script>

Omit connectorId to show your whole directory instead of one connector, or pass manage: true to let a customer review and disconnect what they have already connected.

Pin the version. /widget/v1/connect.js is the path to embed. The unversioned /widget/connect.js also works and will keep working, but it follows every change we make — v1 is the one to paste into a page you are not watching.

No stylesheet to load. The widget renders in a Shadow DOM and carries its own styles, so it cannot collide with yours. Theme it with two CSS custom properties on the host element — --ngx-accent and --ngx-on-accent — or pass brand as above.

Error codes

Every failed request returns a machine-readable code, a human message, a hint, and a request_id — plus a docs_url pointing at that code's own page.

{
  "error": {
    "code": "missing_consumer",
    "status": 400,
    "message": "The X-Ngentix-Consumer header is required.",
    "hint": "Send `X-Ngentix-Consumer: <your id for that customer>`.",
    "request_id": "req_01M0661QA2WAPVWWSNASAGCKXP",
    "docs_url": "https://cloud.ngentix.ai/docs/errors/missing_consumer"
  }
}

Browse all 62 documented error codes →

Connector reference

Loading the catalog…

Every connector has a page with its auth kind, redirect URL, scopes, and a worked example — browse the connector reference →

Full reliability and delivery notes: GET /v1/catalog