Skip to content

API Reference

Public reference for within-sdk (TypeScript 1.0.5, Python 0.1.5) and the Within-maintained Salesforce connector. TypeScript uses camelCase fields; the Python SDK exposes the same APIs with snake_case names (reportConversionreport_conversion, userIduser_id). Connector HTTP payloads use snake_case fields.

track()

Track a compatible high-level or low-level MCP server.

ts
function track(
  server: any,
  vendorSlug: string,
  options?: WithinOptions,
): any
python
def track(server, vendor_slug: str, options: WithinOptions | None = None) -> server
ts
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { track } from 'within-sdk';

const server = new McpServer({ name: 'acme', version: '1.0.0' });

track(server, process.env.WITHIN_VENDOR_SLUG!, {
  apiKey: process.env.WITHIN_SDK_API_KEY!,
});
python
import os

from mcp.server import MCPServer
from within_sdk import track, WithinOptions

server = MCPServer("acme")

track(server, os.environ["WITHIN_VENDOR_SLUG"], WithinOptions())

Call track() after registering tools and once per server instance. It returns the compatible server instance. Tracking the same low-level server again is a no-op.

Tracing, context-parameter injection, and the get_more_tools feedback tool are enabled by default. They change the advertised MCP tool schemas and tool list. SDK-injected context is removed before the vendor callback runs; a vendor-declared context field is passed through.

Setup failures are logged when possible, and track() returns the original server instead of throwing. Without an SDK API key or configured exporter, the SDK continues to instrument the server but cannot deliver events.

publishCustomEvent()

Queue a vendor-defined workflow event.

ts
function publishCustomEvent(
  serverOrSessionId: any | string,
  vendorSlug: string,
  eventData?: CustomEventData,
): Promise<void>
python
def publish_custom_event(
    server_or_session_id, vendor_slug: str, event_data: CustomEventData | None = None
) -> None
ts
import { publishCustomEvent } from 'within-sdk';

await publishCustomEvent(server, 'acme', {
  sessionId: mcpSessionId,
  userId: user.internalCustomerId,
  resourceName: 'checkout_started',
  parameters: { plan: 'pro' },
  message: 'User started checkout after an MCP workflow',
  tags: { channel: 'mcp' },
});
python
from within_sdk import publish_custom_event, CustomEventData

publish_custom_event(server, "acme", CustomEventData(
    session_id=mcp_session_id,
    user_id=user.internal_customer_id,
    resource_name="checkout_started",
    parameters={"plan": "pro"},
    message="User started checkout after an MCP workflow",
    tags={"channel": "mcp"},
))

When the first argument is a tracked server, the SDK reuses that server's SDK API key and Within API origin. An explicit sessionId correlates the event with an MCP journey and may reuse the subject already resolved for that session. Without a sessionId, the event starts a fresh anonymous journey; the SDK never uses a server-wide last session.

When the first argument is an MCP session ID string, provide eventData.apiKey. Do not also provide eventData.sessionId.

The call returns after the event is added to the SDK's asynchronous queue, not after network delivery. It rejects (TypeScript) or raises ValueError (Python) when the vendor slug is missing, the first argument is invalid, the server is not tracked, or both session-ID forms are used.

reportConversion()

Report a confirmed subscription conversion from trusted server-side code.

ts
function reportConversion(
  config: ReportConversionConfig,
  input: WithinConversionInput,
): Promise<WithinConversionResult>
python
def report_conversion(
    vendor_slug: str,
    api_key: str,
    user_id: str,
    *,
    converted_at=None,     # datetime or ISO string; defaults to now
    plan: dict | None = None,
    metadata: dict | None = None,
    api_base_url: str | None = None,
    privacy: PrivacyOptions | None = None,
) -> dict
ts
import { reportConversion } from 'within-sdk';

const result = await reportConversion({
  vendorSlug: process.env.WITHIN_VENDOR_SLUG!,
  apiKey: process.env.WITHIN_SDK_API_KEY!,
}, {
  userId: user.internalCustomerId,
  convertedAt: new Date(),
  plan: {
    id: 'pro',
    name: 'Pro',
    interval: 'month',
  },
  metadata: {
    source: 'checkout_webhook',
  },
});
python
import os
from datetime import datetime, timezone

from within_sdk import report_conversion

result = report_conversion(
    os.environ["WITHIN_VENDOR_SLUG"],
    os.environ["WITHIN_SDK_API_KEY"],
    user.internal_customer_id,
    converted_at=datetime.now(timezone.utc),
    plan={"id": "pro", "name": "Pro", "interval": "month"},
    metadata={"source": "checkout_webhook"},
)

Only userId is required. The SDK trims it, creates the vendor-scoped subject locally, and never sends the raw value. convertedAt defaults to the current time. Plan strings are trimmed and limited to 200 characters. Metadata is privacy-sanitized before delivery.

The request rejects for an empty userId, an invalid convertedAt, or a non-success HTTP response. Repeating a conversion for the same subject and UTC date is idempotent and returns inserted: false.

reportConversion() records a conversion and sets the SDK lead to subscriber. It does not report upgrades, churn, renewals, billing amounts, currency, customer IDs, subscription IDs, or payment processor IDs. Supported connectors such as Salesforce can report broader CRM outcomes.

getSubjectForUserId()

Create the same vendor-scoped subject used by SDK identity, conversion reporting, and Within-maintained connectors.

ts
function getSubjectForUserId(vendorSlug: string, userId: string): string
python
def get_subject_for_user_id(vendor_slug: str, user_id: str) -> str

The operation is local and deterministic:

text
SHA-256("subject:" + lower(trim(vendorSlug)) + ":" + trim(userId))

Use the exact same vendor slug and opaque vendor-local userId everywhere that must join to the same subject. The construction is byte-identical across the TypeScript SDK, the Python SDK, and Within connectors — a subject created in one joins activity and outcomes from any other.

Exported configuration types

The listings below use the TypeScript declarations as the source of truth. The Python SDK exports the same types as dataclasses with snake_case fields (WithinOptions, PrivacyOptions, UserIdentity, CustomEventData), with these language differences:

  • fetch and exporters are TypeScript-only (Python uses the standard library HTTP client; exporters are not available).
  • Python's identify is synchronous; event_tags, event_properties, and redact_sensitive_information may be sync or async.
  • The Python package ships py.typed, so mypy/pyright validate callback signatures (e.g. the per-string redactor contract) against the SDK's types.
  • Python's identify must return a UserIdentity instance (or None) — a plain dict is treated as anonymous.
  • privacy is a PrivacyOptions dataclass: max_field_bytes, max_event_bytes, redact_keys.
  • Python adds two options with no TypeScript counterpart: stateless (overrides the SDK's auto-detection of stateless HTTP servers) and disable_diagnostics (turns off anonymous setup/error diagnostics; also via the DISABLE_DIAGNOSTICS env var, and automatic under pytest).

WithinOptions

ts
interface WithinOptions {
  apiKey?: string;
  apiBaseUrl?: string;
  fetch?: typeof fetch;
  log?: (line: string) => void;
  enableReportMissing?: boolean;
  enableTracing?: boolean;
  enableToolCallContext?: boolean;
  customContextDescription?: string;
  identify?: (
    request: any,
    extra?: CompatibleRequestHandlerExtra,
  ) => Promise<UserIdentity | null>;
  redactSensitiveInformation?: RedactFunction;
  exporters?: Record<string, ExporterConfig>;
  eventTags?: (
    request: any,
    extra?: CompatibleRequestHandlerExtra,
  ) => Record<string, string> | null
    | Promise<Record<string, string> | null>;
  eventProperties?: (
    request: any,
    extra?: CompatibleRequestHandlerExtra,
  ) => Record<string, any> | null
    | Promise<Record<string, any> | null>;
  privacy?: {
    maxFieldBytes?: number;
    maxEventBytes?: number;
    redactKeys?: string[];
  };
}
OptionDefaultBehavior
apiKeyenvironment fallbackSDK API key for the vendor.
apiBaseUrlhttps://api.getwith.inWithin API origin; the SDK appends /v1/ingest/*.
fetchglobalThis.fetchCustom fetch implementation for SDK delivery.
loglocal file or consoleReplaces the local diagnostics destination.
enableReportMissingtrueRegisters get_more_tools.
enableTracingtrueCaptures supported MCP lifecycle and tool-call activity.
enableToolCallContexttrueAdds and captures the SDK context parameter.
customContextDescriptionSDK defaultReplaces the injected context description.
identifynoneResolves an opaque vendor-local identity per request.
redactSensitiveInformationnoneRuns additional text redaction before Within privacy sanitization.
exportersnoneSends sanitized events to configured vendor telemetry exporters.
eventTagsnoneAdds validated string tags.
eventPropertiesnoneAdds custom, privacy-sanitized properties.
privacy.maxFieldBytes10000Maximum bytes for a captured string.
privacy.maxEventBytes128000Maximum serialized captured payload size.
privacy.redactKeysnoneAdds substrings to the built-in sensitive-key list.

SDK API key precedence:

  1. options.apiKey
  2. WITHIN_SDK_API_KEY
  3. WITHIN_SDK_INGEST_KEY

Within API origin precedence:

  1. options.apiBaseUrl
  2. WITHIN_SDK_API_URL
  3. WITHIN_SDK_INGEST_BASE_URL
  4. https://api.getwith.in

WITHIN_SDK_INGEST_KEY and WITHIN_SDK_INGEST_BASE_URL remain compatibility fallbacks despite their older names. The SDK-managed /v1/ingest/* transport paths are not a general-purpose REST integration surface.

Operational diagnostics are local. Node writes to ~/within-sdk.log; edge runtimes use the console; Python writes to ~/within-sdk.log when debug mode is enabled. options.log replaces that destination in both SDKs.

UserIdentity and IdentifyFunction

ts
interface UserIdentity {
  userId: string;
  userName?: string;
  userData?: Record<string, any>;
}

type IdentifyFunction = WithinOptions['identify'];

identify() runs per request. Return a stable, opaque vendor-local userId and optional traits, or null for anonymous activity. Within hashes userId with the vendor slug, drops userName, and sends only the subject and redacted userData. An empty ID, thrown error, or null leaves that request anonymous without inheriting an earlier request's subject.

ts
interface CompatibleRequestHandlerExtra {
  sessionId?: string;
  headers?: Record<string, string | string[]>;
  [key: string]: any;
}

CompatibleRequestHandlerExtra describes the callback value used by identify, eventTags, and eventProperties; it is not exported directly from the package root.

RedactFunction

ts
type RedactFunction = (text: string) => Promise<string> | string;

The custom redactor runs before built-in client-side privacy sanitization and the Within API's server-side redaction.

ExporterConfig and Exporter

ts
interface ExporterConfig {
  type: string;
  [key: string]: any;
}

interface Exporter {
  export(event: Event): Promise<void>;
}

Event is the SDK's internal sanitized event shape. The package supports Datadog, Sentry, PostHog, and OTLP exporter configurations. Exporters do not receive raw userId, userName, or unredacted payloads from the SDK.

MCPServerLike

ts
interface MCPServerLike {
  setRequestHandler(
    schema: any,
    handler: (
      request: any,
      extra?: CompatibleRequestHandlerExtra,
    ) => Promise<any>,
  ): void;
  _requestHandlers: Map<
    string,
    (
      request: any,
      extra?: CompatibleRequestHandlerExtra,
    ) => Promise<any>
  >;
  _serverInfo?: {
    name?: string;
    version?: string;
  };
  getClientVersion(): {
    name?: string;
    version?: string;
  } | undefined;
}

This structural type documents the low-level MCP server shape supported by track(). Most integrations pass McpServer from @modelcontextprotocol/sdk.

Exported event and conversion types

CustomEventData

ts
interface CustomEventData {
  userId?: string;
  sessionId?: string;
  resourceName?: string;
  parameters?: any;
  response?: any;
  message?: string;
  duration?: number;
  isError?: boolean;
  error?: any;
  tags?: Record<string, string>;
  properties?: Record<string, any>;
  apiKey?: string;
  apiBaseUrl?: string;
  fetch?: typeof fetch;
  privacy?: WithinOptions['privacy'];
}

userId is hashed locally. sessionId is an MCP session identifier used for deterministic journey correlation. apiBaseUrl is a Within API origin.

Tags are limited to 50 entries. Keys must use letters, numbers, $, _, ., :, -, or spaces and may contain at most 32 characters. Values must be single-line strings of at most 200 characters. Invalid tags are dropped and logged locally. Properties and other payload fields are privacy-sanitized and truncated.

Conversion types

ts
interface ReportConversionConfig {
  vendorSlug: string;
  apiKey: string;
  apiBaseUrl?: string;
  fetch?: typeof fetch;
  privacy?: WithinOptions['privacy'];
}

interface WithinConversionPlan {
  id?: string;
  name?: string;
  interval?: string;
}

interface WithinConversionInput {
  userId: string;
  convertedAt?: string | Date;
  plan?: WithinConversionPlan;
  metadata?: Record<string, unknown>;
}

interface WithinConversionResult {
  ok: boolean;
  inserted: boolean;
  status: 'subscriber';
  subject: string;
  conversionUtcDate: string;
}

inserted is false when the same subject already has a conversion report for that UTC date.

Salesforce CRM outcomes

Availability

The Salesforce connector and its CRM outcome endpoints are part of an upcoming release and are not yet available in production.

The upcoming Within-maintained Salesforce connector uses these endpoints:

  • POST https://api.getwith.in/api/crm/outcomes
  • POST https://api.getwith.in/api/crm/outcomes/validate

They are connector endpoints, not browser APIs. The connector authenticates with Authorization: Bearer <SDK_API_KEY>. The backend also accepts the same key in x-within-sdk-key.

Validate a connector credential

http
POST /api/crm/outcomes/validate
Authorization: Bearer within_sk_xxx
Content-Type: application/json
json
{
  "vendor_slug": "acme"
}

Success:

json
{
  "ok": true,
  "vendor_slug": "acme"
}

This endpoint authenticates the SDK API key for the vendor without creating an outcome.

Submit CRM outcomes

http
POST /api/crm/outcomes
Authorization: Bearer within_sk_xxx
Content-Type: application/json
json
{
  "vendor_slug": "acme",
  "source": "salesforce",
  "outcomes": [
    {
      "idempotency_key": "1111111111111111111111111111111111111111111111111111111111111111",
      "subject": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
      "outcome_type": "conversion",
      "occurred_at": "2026-07-21T00:00:00.000Z",
      "plan": {
        "id": "pro",
        "name": "Pro",
        "interval": "month"
      },
      "metadata": {
        "campaign": "enterprise"
      },
      "source_mapping": "opportunity_closed_won"
    }
  ]
}

The envelope requires the exact source value salesforce and 1–100 outcomes. Each outcome requires:

FieldConstraint
idempotency_key64 hexadecimal characters.
subject64-character vendor-scoped subject hash.
outcome_typeconversion, upgrade, or churn.
occurred_atValid timestamp; normalized to ISO 8601.
source_mapping1–100 letters, numbers, dots, underscores, or dashes.
planOptional object containing only non-empty id, name, and interval strings, each at most 512 characters.
metadataOptional object with at most 20 scalar fields and a serialized limit of 16,000 characters.

Metadata keys must begin with a letter, contain only letters, numbers, and underscores, and contain at most 80 characters. Values may be strings up to 512 characters, numbers, booleans, or null. Nested metadata is rejected.

Raw Salesforce identities and record fields are not accepted. This includes record IDs, Salesforce IDs, user IDs, names, email addresses, phone numbers, subjects inside metadata, passwords, secrets, tokens, API keys, authorization values, and cookies. Accepted metadata is redacted again by Within before storage.

Success can contain mixed item results:

json
{
  "ok": true,
  "results": [
    {
      "idempotency_key": "1111111111111111111111111111111111111111111111111111111111111111",
      "status": "inserted"
    },
    {
      "idempotency_key": "2222222222222222222222222222222222222222222222222222222222222222",
      "status": "duplicate"
    },
    {
      "idempotency_key": null,
      "status": "rejected",
      "error": "idempotency_key must be a 64-character hex value"
    }
  ]
}

inserted creates the outcome, duplicate means the same vendor and idempotency key was already accepted, and rejected applies to one invalid item. Outcomes are applied to the SDK lead by occurred_at, so an older late-arriving event does not replace a newer lifecycle state. Conversions and upgrades set the current status to subscriber; churn sets it to churned.

Envelope and authentication failures reject the entire request:

StatusMeaning
400Invalid vendor/source envelope or batch size.
401Missing SDK API key.
403SDK API key is not configured or is invalid.
404Vendor slug was not found.
503Required server-side redaction is unavailable.
500Outcome ingestion failed.

Within SDK turns privacy-safe MCP activity into workflow intelligence and SDK leads.