oc · 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<FireEventResult>;
     fireBatch: (options: FireBatchOptions) => Promise<FireBatchResponse>;
     verify: (envelope: BillableEvent, config: IntegratorPriceConfig, payment_amount_sats?: number, opts: {
        payPeriods?: readonly PayPeriod[];
     }) => 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 | null | undefined, kid: string | null | undefined, options: VerifyOptions) => Promise<VerifyResult>;
  };
};

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

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:169
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;FireEventResult>; fireBatch: (options: FireBatchOptions) => Promise&lt;FireBatchResponse>; verify: (envelope: BillableEvent, config: IntegratorPriceConfig, payment_amount_sats?: number, opts: &#123; payPeriods?: readonly PayPeriod[]; &#125;) => VerifyEventResult; &#125;--me-client/src/index.ts:168
event.fire()(options: FireEventOptions) => Promise&lt;FireEventResult>--me-client/src/event.ts:534
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 if (r.status === 'not_paying') console.log('- not paying users now'); 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:534
event.verify()(envelope: BillableEvent, config: IntegratorPriceConfig, payment_amount_sats?: number, opts: &#123; payPeriods?: readonly PayPeriod[]; &#125;) => 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()); // Your project's config and pay periods, by its key. A project can move // domains, so env.site.domain names where it was, not which project it is. const { config, pay_periods } = await fetch( https://me.ochk.io/api/integrator/config?project_key=${PROJECT_KEY} ).then(r => r.json()); const result = oc.event.verify(env, config, undefined, { payPeriods: pay_periods }); if (!result.ok) reportDivergence(result.issues); With payPeriods, an event billed while the project wasn't paying its users is flagged outside_pay_period. Periods that have started never change, so the period that paid an event is always still there.me-client/src/event.ts:534
<a id="property-family"></a> family&#123; scopes: (verb: FamilyVerb, options?: FamilyScopesOptions) => Promise&lt;FamilyScopesResult>; &#125;--me-client/src/index.ts:173
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:111
<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:171
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:174
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:424
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). Checks, in order: bundle shape, the signed bytes decode and are an attestation in canonical form, the readable copy (when given) equals them, JWK with matching kid present, ed25519 signature, freshness. attestation in the result is read from the signed bytes, and is set only when every check passes.me-client/src/identity.ts:423
<a id="property-payment"></a> payment&#123; authorize: (opts: PaymentAuthorizeOptions) => Promise&lt;PaymentResult>; &#125;--me-client/src/index.ts:167
payment.authorize()(opts: PaymentAuthorizeOptions) => Promise&lt;PaymentResult>-Authorize a payment through the person's orangecheck account. A Per action event for your app, priced as a share of the payment; what the person earns lands in their balance at ochk.app. 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:172
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:166
session.create()(opts: SignInOptions) => Promise&lt;Session>-Open a sign-in flow at me.ochk.io. Returns the resulting Session once the user consents. Opening a session charges nothing: a site that pays for sessions reports session_creation itself with oc.event.fire. Internally this opens the OC consent UI in a popup; v1 implementations may also redirect.me-client/src/session.ts:83
session.invalidate()(sessionId: string) => Promise&lt;void>-Tear a session down. Free, and telemetry-only in a literal sense: OC sessions are stateless — derived from the oc_session JWT — so there is no server-side record to delete and the endpoint acknowledges without revoking anything. Dropping your own copy of the token IS the logout; this call is the signal.me-client/src/session.ts:83
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:83
<a id="property-webhook"></a> webhook&#123; fetchJwks: (issuer: string) => Promise&lt;void>; verify: (rawBody: string | Uint8Array&lt;ArrayBufferLike>, sigHex: string | null | undefined, kid: string | null | undefined, options: VerifyOptions) => Promise&lt;VerifyResult>; &#125;--me-client/src/index.ts:170
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:230
webhook.verify()(rawBody: string | Uint8Array&lt;ArrayBufferLike>, sigHex: string | null | undefined, kid: string | null | undefined, 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:230