Appearance
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 (reportConversion → report_conversion, userId → user_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,
): anypython
def track(server, vendor_slug: str, options: WithinOptions | None = None) -> serverts
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
) -> Nonets
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,
) -> dictts
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): stringpython
def get_subject_for_user_id(vendor_slug: str, user_id: str) -> strThe 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:
fetchandexportersare TypeScript-only (Python uses the standard library HTTP client; exporters are not available).- Python's
identifyis synchronous;event_tags,event_properties, andredact_sensitive_informationmay 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
identifymust return aUserIdentityinstance (orNone) — a plain dict is treated as anonymous. privacyis aPrivacyOptionsdataclass: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) anddisable_diagnostics(turns off anonymous setup/error diagnostics; also via theDISABLE_DIAGNOSTICSenv 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[];
};
}| Option | Default | Behavior |
|---|---|---|
apiKey | environment fallback | SDK API key for the vendor. |
apiBaseUrl | https://api.getwith.in | Within API origin; the SDK appends /v1/ingest/*. |
fetch | globalThis.fetch | Custom fetch implementation for SDK delivery. |
log | local file or console | Replaces the local diagnostics destination. |
enableReportMissing | true | Registers get_more_tools. |
enableTracing | true | Captures supported MCP lifecycle and tool-call activity. |
enableToolCallContext | true | Adds and captures the SDK context parameter. |
customContextDescription | SDK default | Replaces the injected context description. |
identify | none | Resolves an opaque vendor-local identity per request. |
redactSensitiveInformation | none | Runs additional text redaction before Within privacy sanitization. |
exporters | none | Sends sanitized events to configured vendor telemetry exporters. |
eventTags | none | Adds validated string tags. |
eventProperties | none | Adds custom, privacy-sanitized properties. |
privacy.maxFieldBytes | 10000 | Maximum bytes for a captured string. |
privacy.maxEventBytes | 128000 | Maximum serialized captured payload size. |
privacy.redactKeys | none | Adds substrings to the built-in sensitive-key list. |
SDK API key precedence:
options.apiKeyWITHIN_SDK_API_KEYWITHIN_SDK_INGEST_KEY
Within API origin precedence:
options.apiBaseUrlWITHIN_SDK_API_URLWITHIN_SDK_INGEST_BASE_URLhttps://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/outcomesPOST 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/jsonjson
{
"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/jsonjson
{
"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:
| Field | Constraint |
|---|---|
idempotency_key | 64 hexadecimal characters. |
subject | 64-character vendor-scoped subject hash. |
outcome_type | conversion, upgrade, or churn. |
occurred_at | Valid timestamp; normalized to ISO 8601. |
source_mapping | 1–100 letters, numbers, dots, underscores, or dashes. |
plan | Optional object containing only non-empty id, name, and interval strings, each at most 512 characters. |
metadata | Optional 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:
| Status | Meaning |
|---|---|
400 | Invalid vendor/source envelope or batch size. |
401 | Missing SDK API key. |
403 | SDK API key is not configured or is invalid. |
404 | Vendor slug was not found. |
503 | Required server-side redaction is unavailable. |
500 | Outcome ingestion failed. |
