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