Developers

API reference

The AnemoneClient in anemone-server-js calls the primary on a well-defined path with Bearer auth and a typed request/response contract. See also Webhooks for the complementary outbound path.

Endpoint

Default PATCH path used by the SDK:

/api/v1/contacts/update

You can override the path in the second argument to submitContactUpdate (e.g. a custom path option) if your deployment uses a different route, but the response should still match the contract: status in processed_globally | pending_user_review | rejected, with optional message and correlationId for support and audit.

Direct connect tokens

Mint opaque browser tokens with POST /api/v1/direct-connect-tokens (Bearer API key). Primary stores (tenant_id, external_user_id) in tenant_link_token and returns a linkToken for connect/disconnect URLs — no internal tenant id in the token or SDK types. Demo partner route that consumes this pattern: GET /api/anemone/connection.

Authentication

Requests use Authorization: Bearer <api-key> and Content-Type: application/json. An optional idempotency-key header is supported for safe retries.

Request body (summary)

  • externalUserId — the user in your app (never an Anemone-only id)
  • kind — e.g. address
  • address — optional line1, line2, city, region, postalCode, countryCode, label

For full types, import from anemone-server-js in your TypeScript project; source lives in the anemone-server-js package. Partner-side push example: pushContactUpdatesToPrimary.

Deferred update queue

When live delivery fails (timeout, 5xx, or rate limit), the SDK can POST the same body to:

/api/v1/contacts/update/queue

Response: { status: "queued", queueId?, correlationId? }. Primary drains this queue twice daily, on restart, and when triggered from the admin dashboard (invite required).

Contact field mapping

Your app may use different property names than Anemone (for example street instead of line1, or fullName instead of separate firstName / lastName). The SDK ships helpers in anemone-server-js so you can map in both directions without hand-rolling transforms in every route.

Rename map

Define a rename object keyed by Anemone field paths. Values are your merchant property names. Nested address fields use dot notation (address.line1, address.region, …).

import {
  buildContactUpdateFromMerchant,
  mapContactEventToMerchant,
  type ContactInfoMapping,
} from "anemone-server-js";

type MerchantContact = {
  street: string;
  street2?: string;
  city: string;
  state: string;
  zip: string;
  countryCode: string;
  phone: string;
};

const mapping: ContactInfoMapping<MerchantContact> = {
  rename: {
    "address.line1": "street",
    "address.line2": "street2",
    "address.city": "city",
    "address.region": "state",
    "address.postalCode": "zip",
    "address.countryCode": "countryCode",
    phone: "phone",
  },
};

// Merchant → primary (PATCH body)
const body = buildContactUpdateFromMerchant({
  externalUserId: user.id,
  kind: "address",
  merchant: user,
  mapping,
});

// Primary webhook → merchant partial
const patch = mapContactEventToMerchant(event, mapping);

Custom set / get functions

When a rename map is not enough (for example splitting or joining a full name), pass set and get on the same ContactInfoMapping. Rename mappings still run first; function results are merged on top.

const mapping: ContactInfoMapping<MerchantContact> = {
  rename: { phone: "phone", "address.line1": "street" /* … */ },
  set: (merchant) => {
    const [firstName, ...rest] = merchant.fullName.trim().split(/\s+/);
    return {
      firstName: firstName || undefined,
      lastName: rest.length > 0 ? rest.join(" ") : undefined,
    };
  },
  get: (primary) => ({
    fullName: [primary.firstName, primary.lastName].filter(Boolean).join(" "),
  }),
};

await client.saveMerchantContactInfo({
  externalUserId: user.id,
  kind: "name",
  merchant: user,
  mapping,
});

For address updates that change only some lines, pass previousMerchant to buildContactUpdateFromMerchant (or saveMerchantContactInfo) so primary can fuzzy-match the saved address being updated.