oc · docs
docs / documentation

@orangecheck/me-client


@orangecheck/me-client / oc

Variable: oc

const oc: {
  config: {
     archetypes: readonly ArchetypeTemplate[];
     fromTemplate: (useCase: IntegratorArchetype, identity: ProjectIdentity) => IntegratorPriceConfig;
     getArchetype: (id: IntegratorArchetype) => ArchetypeTemplate;
     validate: (cfg: IntegratorPriceConfig) => ValidationResult;
  };
  event: {
     fire: (options: FireEventOptions) => Promise<BillableEvent>;
     fireBatch: (options: FireBatchOptions) => Promise<FireBatchResponse>;
     verify: (envelope: BillableEvent, config: IntegratorPriceConfig, payment_amount_sats?: number) => VerifyEventResult;
  };
  family: {
     scopes: (verb: FamilyVerb, options?: FamilyScopesOptions) => Promise<FamilyScopesResult>;
  };
  federations: {
     defaultLive: () => Promise<Federation | null>;
     get: (slug: string) => Promise<Federation | null>;
     list: () => Promise<Federation[]>;
     live: () => Promise<Federation[]>;
  };
  identity: {
     refreshEnvelopeJwks: (issuer: string) => Promise<void>;
     verifyActivityAttestation: (bundle: ActivityAttestationBundle, options: VerifyActivityOptions) => Promise<ActivityVerifyResult>;
  };
  payment: {
     authorize: (opts: PaymentAuthorizeOptions) => Promise<PaymentResult>;
  };
  scope: {
     granted: (options: GrantedOptions) => Promise<GrantedResult>;
     parseTrustAttestationCount: (value: string) => 
        | TrustAttestationCount
       | null;
     request: (scopes: Scope[], options: RequestOptions) => never;
  };
  session: {
     create: (opts: SignInOptions) => Promise<Session>;
     invalidate: (sessionId: string) => Promise<void>;
     refresh: (sessionId: string) => Promise<Session>;
  };
  webhook: {
     fetchJwks: (issuer: string) => Promise<void>;
     verify: (rawBody: string | Uint8Array<ArrayBufferLike>, sigHex: string, kid: string, options: VerifyOptions) => Promise<VerifyResult>;
  };
};

Defined in: me-client/src/index.ts:156

Convenience namespace mirroring the public API surface in /integrate code samples — oc.session.create(), oc.payment.authorize(), oc.event.fire(), oc.config.validate(), oc.webhook.verify(), oc.federations.live(), oc.scope.granted() / oc.scope.request(), oc.family.scopes(), oc.identity.verifyActivityAttestation().

Type Declaration

NameTypeDefault valueDescriptionDefined in
<a id="property-config"></a> config&#123; archetypes: readonly ArchetypeTemplate[]; fromTemplate: (useCase: IntegratorArchetype, identity: ProjectIdentity) => IntegratorPriceConfig; getArchetype: (id: IntegratorArchetype) => ArchetypeTemplate; validate: (cfg: IntegratorPriceConfig) => ValidationResult; &#125;--me-client/src/index.ts:160
config.archetypesreadonly ArchetypeTemplate[]ARCHETYPE_TEMPLATESAll available archetypes · render in a picker.me-client/src/config.ts:56
config.fromTemplate()(useCase: IntegratorArchetype, identity: ProjectIdentity) => IntegratorPriceConfig-Build a complete IntegratorPriceConfig from an archetype id + the project's identity fields. Every subtype gets an entry — the archetype's listed subtypes get the template's pricing; everything else gets enabled: false with conservative defaults so the config is type-complete and the integrator can flip-on later subtypes without re-deriving prices. Throws synchronously if useCase isn't a known archetype id.me-client/src/config.ts:51
config.getArchetype()(id: IntegratorArchetype) => ArchetypeTemplategetArchetypeTemplateLook up a template's metadata · for rendering "you picked X" state and offering one-click swaps in your own UI. Look up a template's metadata · used by config-editor UIs to render "you're on the X template · here's what it enables" copy.me-client/src/config.ts:54
config.validate()(cfg: IntegratorPriceConfig) => ValidationResult--me-client/src/config.ts:50
<a id="property-event"></a> event&#123; fire: (options: FireEventOptions) => Promise&lt;BillableEvent>; fireBatch: (options: FireBatchOptions) => Promise&lt;FireBatchResponse>; verify: (envelope: BillableEvent, config: IntegratorPriceConfig, payment_amount_sats?: number) => VerifyEventResult; &#125;--me-client/src/index.ts:159
event.fire()(options: FireEventOptions) => Promise&lt;BillableEvent>--me-client/src/event.ts:343
event.fireBatch()(options: FireBatchOptions) => Promise&lt;FireBatchResponse>-oc.event.fireBatch — bulk-ingest up to 100 billable envelopes under one project_key in a single HTTP round-trip. Wins amortize across the batch: one auth roundtrip, one project lookup, one scope-grants resolve, parallel KV writes via Promise.all. For a 50-event batch the server-side processing time drops from ~3-4.5 seconds (50 sequential per-event POSTs) to 100-200ms. import { oc } from '@orangecheck/me-client'; const res = await oc.event.fireBatch({ project_key: 'pk_live_yourcompany', events: [ { subtype: 'session_creation' }, { subtype: 'page_view', action_label: 'home' }, { subtype: 'page_view', action_label: 'pricing' }, ], }); for (const r of res.results) { if (r.status === 'recorded') console.log('+', r.event.id, r.event.gross_fee_sats); else if (r.status === 'duplicate') console.log(' idempotent replay', r.event.id); else console.log('!', r.reason); } Failure modes: - 400 if events.length === 0 or > 100, or events[].subtype missing - 403 if the project is frozen or the domain is unverified - 429 if the project's rate limit (1000 events/sec sustained) is exceeded; Retry-After header indicates backoff. Per OCHK-V3-PLAN §12.2.me-client/src/event.ts:343
event.verify()(envelope: BillableEvent, config: IntegratorPriceConfig, payment_amount_sats?: number) => VerifyEventResult-Replay-verify a billable envelope against the integrator's current price config. Re-runs computeFees() with the same inputs and compares against the four-way split recorded on the envelope. Anyone — the integrator, a user, OC, an auditor — can run this function and reach the same answer. The integrator-side replay is what keeps OC honest: a divergence means OC's billing engine computed differently than the published price config says it should have, and the integrator can flag it on /audit. For percent_of_amount-priced subtypes, the underlying payment amount must be passed explicitly (it isn't stored on the envelope top-level — only inside the context payload — so callers should pass envelope.context.payment_amount_sats). import { oc } from '@orangecheck/me-client'; const env = await fetch(https://me.ochk.io/api/envelope/${id}).then(r => r.json()); const cfg = await oc.config.fetch({ project_key: env.site.domain }); const result = oc.event.verify(env, cfg); if (!result.ok) reportDivergence(result.issues);me-client/src/event.ts:343
<a id="property-family"></a> family&#123; scopes: (verb: FamilyVerb, options?: FamilyScopesOptions) => Promise&lt;FamilyScopesResult>; &#125;--me-client/src/index.ts:164
family.scopes()(verb: FamilyVerb, options?: FamilyScopesOptions) => Promise&lt;FamilyScopesResult>-Read the cross-family scope grants the user has issued to the named sibling product. Browser-side · uses cookie credentials. Errors thrown: - unknown_family_verb if verb is not in the family allowlist - sign in required (401) if no oc_session cookie present - unknown_family_verb (404) if me.ochk doesn't recognize the verb If you need to drive a consent prompt for a verb the user hasn't granted yet, use oc.scope.request(scopes, { project_key: 'family:<verb>', return_to }) — the consent prompt page detects the family: prefix and renders the sibling-product chrome.me-client/src/family.ts:110
<a id="property-federations"></a> federations&#123; defaultLive: () => Promise&lt;Federation | null>; get: (slug: string) => Promise&lt;Federation | null>; list: () => Promise&lt;Federation[]>; live: () => Promise&lt;Federation[]>; &#125;--me-client/src/index.ts:162
federations.defaultLive()() => Promise&lt;Federation | null>-Default federation a fresh user/integrator should bind to when no explicit policy exists yet. v1: the first live federation in the directory; falls back to null if none live yet. v2+: routing policy (geography, capacity, explicit choice) consults the directory directly.me-client/src/federations.ts:51
federations.get()(slug: string) => Promise&lt;Federation | null>-Look up a single federation by slug. Returns null if the slug isn't in the directory.me-client/src/federations.ts:51
federations.list()() => Promise&lt;Federation[]>-List every federation in the directory — recruiting, forming, binding, and live. Consumers filter by status as needed.me-client/src/federations.ts:51
federations.live()() => Promise&lt;Federation[]>-Return only federations currently accepting bindings — i.e. status === 'live' with a non-null invite. The set the consumer wallet provider can route a user wallet to.me-client/src/federations.ts:51
<a id="property-identity"></a> identity&#123; refreshEnvelopeJwks: (issuer: string) => Promise&lt;void>; verifyActivityAttestation: (bundle: ActivityAttestationBundle, options: VerifyActivityOptions) => Promise&lt;ActivityVerifyResult>; &#125;--me-client/src/index.ts:165
identity.refreshEnvelopeJwks()(issuer: string) => Promise&lt;void>-Force-refresh the envelope-JWKS cache · useful after a key rotation if you don't want to wait for the TTL to expire.me-client/src/identity.ts:361
identity.verifyActivityAttestation()(bundle: ActivityAttestationBundle, options: VerifyActivityOptions) => Promise&lt;ActivityVerifyResult>-Verify a signed activity attestation bundle. Returns a typed ActivityVerifyResult with per-check detail · never throws (network + parse failures surface as ok=false rows in the checks array, so the caller can log them without try/catch). Five checks, in order: bundle shape, canonical-bytes hash matches id, JWK with matching kid present, ed25519 signature, freshness.me-client/src/identity.ts:360
<a id="property-payment"></a> payment&#123; authorize: (opts: PaymentAuthorizeOptions) => Promise&lt;PaymentResult>; &#125;--me-client/src/index.ts:158
payment.authorize()(opts: PaymentAuthorizeOptions) => Promise&lt;PaymentResult>-Authorize a payment through the user's OC identity. Class B billable event for the integrating site (sub-Stripe rate, percentage-based); cashback flows to the user as Lightning credit on /me/earn. The user is prompted by me.ochk.io to consent to the specific payment before this resolves. If the user declines or cancels, the result has status 'cancelled', a non-billable telemetry record is emitted, and NO billable event is created.me-client/src/payment.ts:25
<a id="property-scope"></a> scope&#123; granted: (options: GrantedOptions) => Promise&lt;GrantedResult>; parseTrustAttestationCount: (value: string) => | TrustAttestationCount | null; request: (scopes: Scope[], options: RequestOptions) => never; &#125;--me-client/src/index.ts:163
scope.granted()(options: GrantedOptions) => Promise&lt;GrantedResult>--me-client/src/scope.ts:193
scope.parseTrustAttestationCount()(value: string) => | TrustAttestationCount | null-Decode the trust_attestation_count scope's wire-format string into a typed quad. Returns null if the input is malformed (defensive · the resolver always emits well-formed strings, but third-party consumers may hand us anything).me-client/src/scope.ts:193
scope.request()(scopes: Scope[], options: RequestOptions) => never-Redirect the user to me.ochk.io's consent prompt. After they decide, browser returns to return_to. The scope grants are persisted server-side; subsequent calls to oc.scope.granted() reflect their decisions. This function does NOT return a promise that resolves to the user's decisions. The redirect leaves your page; the user lands back at return_to and your code re-reads granted state on mount. Consent flow: 1. Your code: oc.scope.request(['email'], { ... }) 2. Browser navigates to me.ochk.io/me/scope-grant?… 3. User decides per-scope (grant once / always / deny) 4. Browser redirects back to return_to 5. Your code re-runs oc.scope.granted(...) on mount Throws synchronously if the inputs are invalid (so the redirect is never issued with malformed params).me-client/src/scope.ts:193
<a id="property-session"></a> session&#123; create: (opts: SignInOptions) => Promise&lt;Session>; invalidate: (sessionId: string) => Promise&lt;void>; refresh: (sessionId: string) => Promise&lt;Session>; &#125;--me-client/src/index.ts:157
session.create()(opts: SignInOptions) => Promise&lt;Session>-Open a sign-in flow at me.ochk.io. Returns the resulting Session once the user consents (Class C billable event for the integrating site, cashback for the user). Internally this opens the OC consent UI in a popup; v1 implementations may also redirect.me-client/src/session.ts:77
session.invalidate()(sessionId: string) => Promise&lt;void>-Invalidate a session. Free, telemetry-only.me-client/src/session.ts:77
session.refresh()(sessionId: string) => Promise&lt;Session>-Refresh a still-valid session inside its policy window. Free for the integrating site (does NOT instantiate a billable event); emits a session.token_refresh telemetry record.me-client/src/session.ts:77
<a id="property-webhook"></a> webhook&#123; fetchJwks: (issuer: string) => Promise&lt;void>; verify: (rawBody: string | Uint8Array&lt;ArrayBufferLike>, sigHex: string, kid: string, options: VerifyOptions) => Promise&lt;VerifyResult>; &#125;--me-client/src/index.ts:161
webhook.fetchJwks()(issuer: string) => Promise&lt;void>fetchJwksForceForce-refresh the JWKS cache. Useful after a key rotation event if you don't want to wait for the TTL to expire.me-client/src/webhook.ts:221
webhook.verify()(rawBody: string | Uint8Array&lt;ArrayBufferLike>, sigHex: string, kid: string, options: VerifyOptions) => Promise&lt;VerifyResult>-Verify a webhook signature. Pass the raw body bytes (not parsed JSON), the hex-encoded signature from the OC-Signature header, and the kid from the OC-Key-Id header. Returns { ok: true } on success, { ok: false, reason } on any failure (bad signature, kid not in JWKS, malformed signature, network-only JWKS fetch failed without prior cache, etc.). Never throws.me-client/src/webhook.ts:221