// Soul SDK type definitions. // https://www.soulverified.com/soul.d.ts // // The SDK ships as one plain script tag with no build step, so these are here for // editors and for anyone who wants their integration typechecked: // // /// // // Every field below is what the API actually returns. If something is not in this // file, it is not part of the contract. declare namespace Soul { /** soul = curated by Soul, shared. hosted = that partner's users only. * collab = the host plus every partner named in `collab`. * takeover = open, branded to the host. exclusive = Soul-run, capped. */ type GatheringKind = 'soul' | 'hosted' | 'collab' | 'takeover' | 'exclusive'; interface Gathering { id: string; title: string; /** already formatted for display, e.g. "Tomorrow 6:30 am" */ when: string; where: string; startsAt: string; going: number; /** has the current user committed */ mine: boolean; /** first names only, capped at six */ goers: string[]; note?: string; /** why this surfaced for this member: the tags that scored it */ why?: string[]; kind: GatheringKind; brandNote?: string; /** how many of YOUR OWN users are going. Never who. */ yours?: number; host?: string; collab?: string[]; /** can this be checked into right now. early = not yet, open = it is happening, * closed = the window has shut and the record is final. */ checkin: CheckinState; /** the current user already said they were here. Stamped once, never taken back. */ arrived?: boolean; /** how many people said they were actually there. Absent before the window opens, * because "0 here" for something that has not started is the clock, not a fact. */ here?: number; } type CheckinState = 'early' | 'open' | 'closed'; interface CheckinResponse { ok: true; arrived: true; /** when the arrival was stamped. Unchanged on a repeat call. */ at: string; here: number; sandbox?: true; } /** 409 from checkin(): outside the gathering's window. Both ends are given so your UI * can say which side of it the user is on. */ interface CheckinClosed { error: string; code: 'checkin_not_open' | 'checkin_closed'; opensAt: string; closesAt: string; } interface Person { name: string; interests: string[]; /** tags this person and the viewer both have */ shared?: string[]; /** overlap strength, 0-100 */ match?: number; } interface EventsResponse { ok: true; /** present only in the free sandbox */ sandbox?: true; connected: boolean; memberCount: number; signal: string[]; events: Gathering[]; people: Person[]; } interface RsvpResponse { ok: true; going: boolean; count: number } interface ConnectResponse { ok: true; connected: string; affinities: string[] } interface HostResponse { ok: true; sandbox?: true; note?: string; event: { id: string; title: string; where: string; when: string; startsAt: string; city: string; host: string; collab: string[]; kind: GatheringKind }; } /** Never rejects. A dead network or a bad key resolves to this. */ interface ErrorResponse { error: string; code?: 'subscription_required'; state?: string; manage?: string } interface InitOptions { /** your slug. any unregistered string runs in the free sandbox. */ partner: string; /** only needed for a registered slug in production */ key?: string; /** YOUR user id. never an email, never a name. */ user?: string; city?: string; /** your product's display name, shown inside the tab */ name?: string; /** brand colour, e.g. "#7fd0c0" */ accent?: string; theme?: 'light' | 'dark'; /** preset visual skin, chosen independently of the slug */ skin?: string; tag?: string; } interface MountOptions extends Partial { el?: string | Element; target?: string | Element; /** default 640 */ height?: number; /** default "20px" */ radius?: string; /** default true. false = fixed height, scrolls internally. */ autosize?: boolean; } interface HostInput { title: string; /** a free, public, walk-in place */ where: string; /** ISO date, must be in the future */ when: string; note?: string; city?: string; capacity?: number; kind?: GatheringKind; /** other partner slugs. naming anyone makes it a collab. */ collab?: string[]; brandNote?: string; } type EventName = 'ready' | 'join' | 'rsvp' | 'checkin' | 'connect' | 'open' | 'close' | 'badge'; /** SERVER SIDE events, delivered to the endpoint you register. These are not the * browser hooks above: these reach your backend. */ type WebhookEvent = 'member.joined' | 'rsvp.created' | 'rsvp.cancelled' | 'member.arrived' | 'gathering.created'; interface WebhookDelivery { id: string; type: WebhookEvent; created: string; partner: string; data: Record; } } interface SoulSDK { version: string; init(options: Soul.InitOptions): SoulSDK; /** never includes the key */ config(): Omit; Community(options: Soul.MountOptions): HTMLIFrameElement | null; mount(target: string | Element, options?: Soul.MountOptions): HTMLIFrameElement | null; badge(options: Soul.MountOptions & { onClick?: boolean }): HTMLElement | null; open(options?: Soul.MountOptions & { maxWidth?: string; height?: string }): HTMLElement; close(): void; events(options?: { city?: string; limit?: number } & Partial): Promise; rsvp(eventId: string, going?: boolean, options?: Partial): Promise; /** "I'm here." Only succeeds inside the gathering's own window (30 min before, until * 3 hours after); outside it you get a 409 with opensAt/closesAt. Idempotent: the * arrival is stamped once and a second call returns the same time. */ checkin(eventId: string, options?: Partial): Promise; join(options?: Partial): Promise<{ ok: true; joined: true } | Soul.ErrorResponse>; identify(user: string, traits?: { tags?: string[]; interests?: string[]; name?: string; city?: string; source?: string }): Promise; connect(source: string, options?: Partial): Promise<{ ok: true; source: string } | { error: string; url?: string }>; host(event: Soul.HostInput): Promise; on(event: Soul.EventName, fn: (payload: unknown) => void): SoulSDK; off(event: Soul.EventName, fn?: (payload: unknown) => void): SoulSDK; } declare const Soul: SoulSDK; interface Window { Soul: SoulSDK }