Skip to content

Quickstart ​

The fastest path from zero to a published Knowledge Node: create an account, verify ownership, issue a connector token, push content, publish, and read the node files.

The production API base URL is https://api.bainquet.online. Every route below is shown with its full /v1/... path. Responses use the standard envelope; branch on the boolean ok, never on HTTP status alone. See Envelope and errors.

1. Create an account, organization, and website ​

Registering creates the user and an organization in one call. The caller becomes the organization owner.

http
POST /v1/auth/register
Content-Type: application/json

{ "email": "owner@acme.com", "password": "a-strong-password", "orgName": "Acme" }

Verify your email, then log in. Login sets a rotating refresh token in an HttpOnly bnq_refresh cookie and returns a short-lived access JWT in the body.

bash
curl -sS -X POST https://api.bainquet.online/v1/auth/login \
  -H 'Content-Type: application/json' \
  -c cookies.txt \
  -d '{ "email": "owner@acme.com", "password": "a-strong-password" }'

Use the access token as Authorization: Bearer <access JWT> on the calls below. Create a website under your organization:

bash
curl -sS -X POST https://api.bainquet.online/v1/organizations/$ORG_ID/websites \
  -H "Authorization: Bearer $ACCESS_JWT" \
  -H 'Content-Type: application/json' \
  -d '{ "domain": "example.com", "name": "Example" }'

The response data.id is your WEBSITE_ID. See Authentication and Organizations and websites.

2. Verify ownership ​

A website must prove domain control before it can publish a trusted node. Request a challenge, place the token, then ask the server to check it.

bash
# Request a challenge token (choose a method: dns_txt, well_known_file, meta_tag, plugin)
curl -sS -X POST https://api.bainquet.online/v1/websites/$WEBSITE_ID/verification/challenge \
  -H "Authorization: Bearer $ACCESS_JWT" \
  -H 'Content-Type: application/json' \
  -d '{ "method": "dns_txt" }'

# Place the returned token (for dns_txt, a TXT record), then check:
curl -sS -X POST https://api.bainquet.online/v1/websites/$WEBSITE_ID/verification/check \
  -H "Authorization: Bearer $ACCESS_JWT" \
  -H 'Content-Type: application/json' \
  -d '{}'

The verification state advances to verified on success. See Verifying ownership.

3. Issue a connector token ​

A connector token authenticates content pushes for one website. The token is shown once, at creation; only a hash persists. Store it immediately.

bash
curl -sS -X POST https://api.bainquet.online/v1/websites/$WEBSITE_ID/connectors \
  -H "Authorization: Bearer $ACCESS_JWT" \
  -H 'Content-Type: application/json' \
  -d '{ "name": "wp-prod", "connectorType": "api", "sourceTypes": ["page","post","product"] }'

The data.token field is your connector token, in the form connectorId.secret. This is the exact value the SDK and plugins sign with. See Connector tokens.

4. Push content ​

You can push with the official SDK (it handles signing for you) or with a raw signed HTTP request.

Option A: the SDK ​

The SDK derives checksums, signs each request, and retries transient failures. Note that baseUrl is the host root without /v1; the client appends /v1/ingest/... itself.

ts
import { createBainquetConnector, makeItem } from "@bainquet/connector-sdk";

const bq = createBainquetConnector({
  baseUrl: "https://api.bainquet.online", // no /v1; the client adds it
  token: process.env.BAINQUET_CONNECTOR_TOKEN!, // "connectorId.secret"
  websiteId: process.env.BAINQUET_WEBSITE_ID!,
  siteDomain: "example.com",
  connectorType: "api",
});

const result = await bq.ingestBatch([
  makeItem({
    type: "product",
    id: "p_1",
    url: "https://example.com/product/a",
    title: "Product A",
    json: { price: 16.99, currency: "EUR", sku: "A-001" },
    language: "en",
  }),
]);

console.log(result.accepted, "accepted,", result.skipped, "skipped");

makeItem computes the required sha256: checksum for you.

Option B: raw HTTP with HMAC ​

Every ingest request is signed with HMAC-SHA256 over a six-line canonical string, using a key derived from your connector secret. You cannot produce a valid signature in a single curl line, so compute the headers first, then call curl. This Node script signs one batch and posts it:

js
// sign-and-post.mjs   run: node sign-and-post.mjs
import { createHash, createHmac, hkdfSync, randomUUID } from "node:crypto";

const TOKEN = process.env.BAINQUET_CONNECTOR_TOKEN; // "connectorId.secret"
const WEBSITE_ID = process.env.BAINQUET_WEBSITE_ID;
const BASE = "https://api.bainquet.online";
const PATH = "/v1/ingest/batch";
const HKDF_INFO = "bq.connector.hmac.v1";

const secret = TOKEN.slice(TOKEN.indexOf(".") + 1);
const checksumBody = (s) => "sha256:" + createHash("sha256").update(s, "utf8").digest("hex");

const body = JSON.stringify({
  website_id: WEBSITE_ID,
  partial: true,
  items: [
    {
      type: "product",
      id: "p_1",
      url: "https://example.com/product/a",
      title: "Product A",
      json: { price: 16.99, currency: "EUR", sku: "A-001" },
      language: "en",
      checksum: checksumBody(JSON.stringify({ price: 16.99, currency: "EUR", sku: "A-001" })),
    },
  ],
});

const signingKey = Buffer.from(
  hkdfSync("sha256", Buffer.from(secret), Buffer.from(WEBSITE_ID), Buffer.from(HKDF_INFO), 32),
);
const issuedAt = String(Math.floor(Date.now() / 1000));
const nonce = randomUUID();
const bodySha256 = createHash("sha256").update(body, "utf8").digest("hex");
const canonical = ["POST", PATH, bodySha256, issuedAt, nonce, WEBSITE_ID].join("\n");
const signature = createHmac("sha256", signingKey).update(canonical).digest("hex");

const res = await fetch(`${BASE}${PATH}`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${TOKEN}`,
    "Content-Type": "application/json",
    "X-Signature": signature,
    "X-Timestamp": issuedAt,
    "X-Nonce": nonce,
    "X-Body-Sha256": bodySha256,
    "Idempotency-Key": randomUUID(),
    "X-Site-Domain": "example.com",
    "X-Connector-Type": "api",
    "X-Connector-Version": "1.0.0",
  },
  body,
});
console.log(res.status, await res.json());

A successful batch returns ok:true with a per-item results[] array, even when some items error. The invariant is received == accepted + skipped + errored == results.length. See Ingestion and signing for the full canonical-string definition and the verification pipeline.

WARNING

A batch route is always ok:true at the envelope level when some items fail. Inspect each entry in results[] for its per-item status (accepted, skipped, or error). Do not assume a 200 means every item was accepted.

5. Publish the node ​

Publishing runs the exporter pipeline and writes a new immutable version, then flips the latest/ pointer.

bash
curl -sS -X POST https://api.bainquet.online/v1/websites/$WEBSITE_ID/publish \
  -H "Authorization: Bearer $ACCESS_JWT" \
  -H 'Content-Type: application/json' \
  -d '{}'

The response carries { versionUlid, versionLabel, files, cdnPath }. (The current build runs publish synchronously and returns 200.)

6. See the node files ​

The published node is a set of static files. The entry point is always manifest.json, which lists every file with its checksum, byte size, and record count. A consumer reads manifest.json, then ai.json, then the record files it needs.

bash
curl -sS https://cdn.bainquet.online/<cdnPath>/latest/manifest.json

The free tier produces manifest.json, ai.json, llms.txt, entities.jsonl, facts.jsonl, relationships.jsonl, chunks.jsonl, qa.jsonl, sources.jsonl, schema.jsonld, and sitemap-ai.xml. See Node files for the full file set and the versions/{ulid}/ plus latest/ layout.

Next steps ​

Owner-controlled structured data for AI.