npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@sceneinfrastructure/storefront-api

v0.20.0

Published

Typed SDK and oRPC contract for the Mesh public Storefront API

Readme

@sceneinfrastructure/storefront-api

Typed SDK and oRPC contract for Mesh public Storefront API integrations.

pnpm add @sceneinfrastructure/storefront-api @sceneinfrastructure/public-types @orpc/client @orpc/contract @orpc/openapi-client zod
import { createStorefrontApiClient } from '@sceneinfrastructure/storefront-api/client'

const storefront = createStorefrontApiClient({
  baseUrl: process.env.MESH_PUBLIC_API_BASE_URL!,
  apiKey: process.env.MESH_SCENE_API_KEY!,
})

const { events } = await storefront.listEvents({
  sceneId: process.env.MESH_SCENE_ID!,
})

const cart = await storefront.createCartUrl({
  eventId: events[0].id,
  items: [{ saleKeyId: '0x…', quantity: 2 }],
  destination: 'checkout',
})

// Redirect from your backend to skip Mesh ticket selection and open the
// populated hosted checkout form directly.
return cart.absoluteCheckoutUrl

listEvents can also narrow the scene event feed server-side:

const { events } = await storefront.listEvents({
  sceneId: process.env.MESH_SCENE_ID!,
  status: 'scheduled',
  eventIds: ['evt_example'],
  eventType: 'RSVP',
  collaboratorGroupIds: ['scg_partner_team'],
})

eventIds and collaboratorGroupIds match any ID in the provided list, and all provided filters are combined. Collaborator group filtering is scoped to the requested sceneId.

The optional @sceneinfrastructure/storefront-api/react-query subpath uses @orpc/tanstack-query. Install it only if you want oRPC's TanStack Query helpers; otherwise wrap the typed client with your own useQuery calls.

Partner scene and event management

Mesh-issued partner keys use the pt_ prefix and are server-only. Keep them in the partner backend alongside secret sk_ keys; never expose them to browser code. A partner key can create, read, and update only scenes linked to its durable partner identity. It can also manage events and ticket inventory under those scenes. Partner ownership is checked on every request; knowing another partner's Scene, event, or tier ID does not grant access.

Event reads use the existing scenes.read partner capability and event or tier mutations use scenes.write, so already-issued partner keys do not need to be replaced for this API surface.

Attendee lists contain personal data and require the separate attendees.read capability. A partner key issued before that capability was available must be replaced before it can read attendee data.

Ticket validation and check-in require the separate check-ins.write capability. A partner key issued before that capability was available must be replaced before it can validate or check in tickets.

Order creation and payment confirmation require orders.write; reading an order requires orders.read because the response contains customer PII. Mesh checks the event's durable partner ownership on every order request, so the same partner key works across owned restaurants without per-Scene keys. A partner key issued before these capabilities were available must be granted them or replaced before it can use the order API.

The Mesh super-admin sets a positive scene limit when issuing the partner's first key. Additional keys must use that same limit.

Mutations can include the external operator's stable identifier in X-Partner-Operator-ID. Mesh stores that unverified operator claim alongside the authenticated key and request identifiers for audit attribution. Scene creation also requires a stable, partner-scoped external reference, such as a venue ID from the partner's system:

const mesh = createStorefrontApiClient({
  baseUrl: process.env.MESH_PUBLIC_API_BASE_URL!,
  apiKey: process.env.MESH_PARTNER_API_KEY!,
})

const { scene } = await mesh.createScene({
  body: {
    externalReference: 'venue-123',
    name: 'Example Venue',
    avatarUrl: 'https://cdn.example.com/restaurant.png',
  },
  headers: { 'x-partner-operator-id': 'operator-7' },
})

const current = await mesh.getScene({
  params: { sceneId: scene.id },
})

await mesh.updateScene({
  body: { name: 'Example Venue Downtown' },
  headers: { 'x-partner-operator-id': 'operator-8' },
  params: { sceneId: scene.id },
})

const { event, tiers } = await mesh.createEvent({
  body: {
    eventType: 'TICKETED',
    name: 'Summer Dinner',
    startAt: '2026-09-01T19:00:00.000Z',
    endAt: '2026-09-01T22:00:00.000Z',
    taxEnabled: true,
    taxRateBps: 887,
    timezone: 'America/New_York',
    tiers: [
      {
        label: 'General admission',
        priceUsdc: '50000000',
        totalQuantity: '100',
      },
    ],
  },
  headers: {
    'idempotency-key': stableEventCreateKey,
    'x-partner-operator-id': operatorId,
  },
  params: { sceneId: scene.id },
})

await mesh.updateEventAvailability({
  body: { acceptingRegistration: true, visibility: 'public' },
  headers: { 'x-partner-operator-id': operatorId },
  params: { eventId: event.id },
})

await mesh.publishEvent({
  headers: { 'x-partner-operator-id': operatorId },
  params: { eventId: event.id },
})

// After polling getEvent until state is "published":
const order = await mesh.createOrder({
  body: {
    payment: { provider: 'blackbird_pay', type: 'external' },
    quantity: 1,
    slotId: tiers[0].id,
    type: 'reserved',
    user: {
      email: '[email protected]',
      firstName: 'Jane',
      lastName: 'Doe',
    },
  },
  headers: { 'idempotency-key': stableOrderKey },
  params: { eventId: event.id },
})

const attendeePage = await mesh.listEventAttendees({
  params: { eventId: event.id },
  query: { limit: 50, offset: 0, status: 'all' },
})

const validation = await mesh.validateEventCheckIn({
  body: { token: scannedQrToken },
  params: { eventId: event.id },
})

const submission = await mesh.checkInEventTickets({
  body: {
    token: scannedQrToken,
    tickets: validation.attendee.tickets
      .filter((ticket) => ticket.valid)
      .map((ticket) => ({ quantity: 1, tokenId: ticket.tokenId })),
  },
  headers: {
    'idempotency-key': stableCheckInKey,
    'x-partner-operator-id': operatorId,
  },
  params: { eventId: event.id },
})

externalReference is unique within the authenticated partner. Retrying a create with the same reference and same create payload returns the existing scene. Reusing it with different create data returns a typed 409 conflict. For a 503, inspect error.data.reason. Retry the same payload and reference only for scene_provisioning_unavailable; Mesh resumes from durable provisioning state when it can do so safely. Escalate partner_service_actor_invalid and wallet_reconciliation_required to Scene instead of retrying. The latter deliberately requires reconciliation rather than risking a duplicate wallet or scene.

The HTTP operations are:

  • POST /v1/scenes
  • GET /v1/scenes/{sceneId}
  • PATCH /v1/scenes/{sceneId}
  • POST /v1/scenes/{sceneId}/events
  • GET /v1/scenes/{sceneId}/events
  • GET /v1/events/{eventId}
  • PATCH /v1/events/{eventId}
  • PATCH /v1/events/{eventId}/availability
  • POST /v1/events/{eventId}/publish
  • POST /v1/events/{eventId}/archive
  • POST /v1/events/{eventId}/tiers
  • PATCH /v1/events/{eventId}/tiers/{tierId}
  • POST /v1/events/{eventId}/tiers/{tierId}/archive
  • GET /v1/events/{eventId}/attendees
  • POST /v1/events/{eventId}/check-ins/validate
  • POST /v1/events/{eventId}/check-ins

Event and ticket-tier creation require a stable UUID Idempotency-Key so clients can safely retry a request without creating duplicate inventory. Publication is asynchronous: a successful publish request returns publishStatus: "queued"; poll the event read until state is published. Published event and tier updates use Mesh's existing onchain sale and inventory synchronization. Tier responses include soldQuantity and remainingQuantity; quantities and prices are integer USDC base-unit strings so JavaScript clients do not lose precision.

taxRateBps is an integer from 0 through 10,000 (887 means 8.87%). When tax is enabled, a non-null value overrides Mesh's location-derived rate; patch it to null to return to location-derived tax. Partners cannot set or disable Scene's service fee: partner create and update inputs do not expose vendorFeesEnabled, and partner-created events always enable the fixed fee.

Attendee reads are paginated and support search, ticket/status filters, and sorting. They return current ticket ownership and check-in state plus the latest successful-order registration answers for each current holder. Answers remain associated with the destination wallet on the order; transferring a ticket does not disclose the original holder's answers to the recipient.

Check-in validation accepts only the opaque, event-bound sct1: token from a Scene ticket QR; it does not accept a wallet address. Submit only ticket IDs and quantities returned by validation, and reuse the same UUID Idempotency-Key when retrying the same logical check-in. A successful submission returns status: "submitted" and a userOpHash; this means the onchain operation was accepted for submission, not that it is confirmed.

If a create has an ambiguous outcome, Mesh keeps its idempotency reservation for 24 hours rather than risking a duplicate. Do not retry the same logical create under a new key; use the response requestId when escalating to Scene.

What the public read endpoints expose

The public read endpoints only return events that are state = 'published', visibility = 'public', and not archived. Drafts, publishing rows, archived rows, and private rows are filtered out at the read layer.

Each event also carries:

  • acceptingRegistration: boolean — when false, every tier is reported as isActive: false.
  • display: { headline, dateLabel, shortDateLabel, venueLabel, imageAlt, accentLabel, numberLabel } — storefront-ready labels for cards, event detail headers, and checkout summaries.
  • imageUrl: string | null and videoUrl: string | null — separate poster and MP4 media URLs so storefronts can render a stable poster and autoplay video when available.
  • location: { name, address, formattedAddress, kind, url } | null — derived from the V2 event.location JSON when present, with a fallback to V1 event.address.

Each tier carries:

  • display: { group, sortOrder, subtitle, visible, defaultSelected } — admin-defined ticket group label and ordering, a storefront subtitle, visibility, and a default selection hint so storefronts don't need to infer groups or first-choice tiers from labels.
  • price.formatted and allInclusivePrice.formatted — canonical USD display labels alongside numeric USD and integer USDC values.
  • quantity: { label, remainingNumber, ... } — storefront-ready inventory labels and safe numeric remaining quantities when available.
  • relationships: { requiredParentTierId } — when non-null, the tier is an add-on that must be purchased with an eligible primary ticket. Checkout accepts the exact referenced parent tier, except for Mesh's internal "any primary" add-on group where any primary event ticket can satisfy the relationship.
  • restrictions: { maxQuantityPerOrder, requiresAccessCode } — same surface as before.
  • schedule: { startTime, endTime, timezone } and validityWindow: { validFrom, validUntil, isActive } — public sale and ticket-validity windows for storefront presentation.

POST /v1/cart-url accepts multi-item carts and supports two destinations:

  • Omit destination (or pass "event") to preserve the original behavior. The returned URL opens the Mesh event page and encodes each selection as repeated saleKeyId/quantity query parameters.
  • Pass destination: "checkout" to open the hosted attendee/payment form directly. The URL contains a one-hour encrypted cartToken that binds the validated items to the event and reconstructs the cart on initial load and refresh. Access and discount codes are carried inside that opaque token, rather than exposed as checkout query parameters.

checkoutUrl is always the relative URL. absoluteCheckoutUrl contains the same destination under the hosted Mesh origin, or null when the API runtime does not have a hosted origin configured.

For either destination, the endpoint:

  • Aggregates duplicate saleKeyId lines server-side before validation, so splitting an order into multiple identical line items cannot bypass availability or add-on parent-quantity checks.
  • Sorts primary tiers before add-ons in the emitted URL so the hosted storefront reconstructs parents before children regardless of input ordering.
  • Accepts an optional HTTP(S) returnUrl. After the order is fulfilled, the hosted confirmation page gives the shopper a path back to that exact page. When omitted, confirmation links to the Mesh ticket page instead.

It enforces these rules server-side:

  • 409 Conflict when an add-on tier is included without an eligible primary ticket in the same cart (requiredParentTierId not satisfied by the exact parent tier or Mesh's internal "any primary" add-on group).
  • 409 Conflict when an add-on tier's quantity exceeds the eligible primary ticket quantity in the same cart.
  • 409 Conflict when a tier is sold out or requested quantity exceeds remaining availability (after duplicate-line aggregation).
  • 422 Validation Failed when the cart includes more than one distinct primary ticket tier. The current hosted storefront state model supports only one primary tier (with optional add-ons). Pass a single primary tier per cart URL.
  • 422 Validation Failed when the sum of primary ticket quantities exceeds event.ticketOrderLimit (treated as "no limit" when non-positive). Add-on quantities ride along with their parent and are not counted against the per-event order limit (they are independently capped at the parent quantity).

Access and discount code validation

Code resolution and validation require a secret, server-side scene API key. Never call these operations from browser code or expose the key in a public environment variable.

Resolve an access code before rendering gated inventory:

const resolved = await storefront.resolveAccessCode({
  params: { eventId },
  body: { accessCode },
})

if (resolved.accessCode.status === 'applied') {
  // resolved.tiers includes the storefront-safe gated tiers unlocked by the
  // code. The code itself is never echoed in the response.
  renderTiers(resolved.tiers)
}

Validate either or both codes against the current cart whenever ticket or add-on quantities change:

const validation = await storefront.validateCartCodes({
  eventId,
  items: [{ saleKeyId, quantity }],
  accessCode: accessCode || undefined,
  discountCode: discountCode || undefined,
})

if (validation.discountCode.status === 'applied' && validation.pricing) {
  showTotal(validation.pricing.total.formatted)
  showSavings(validation.discountCode.savings?.formatted)
}

Each code has one of these statuses:

  • applied — accepted for the exact cart.
  • invalid — not recognized for the event.
  • expired — recognized, but its applicable end condition has passed.
  • inactive — recognized, but not currently usable (for example a future window, disabled discount, exhausted usage limit, or closed registration).
  • not_applicable — recognized, but does not apply to the selected items or cart amount.
  • not_supplied — omitted from the request.

validation.valid is true only when every supplied code is applied. pricing uses the same amountUSD, integer amountUSDC, and formatted money shape as the catalog endpoints. The operation performs no reservation, database write, or cart-token issuance. It is rate-limited per authenticated API key and event; typed 429 errors include retryAfterSeconds.

Validation is advisory and inventory is not held. Always send the same items and codes to createCartUrl with destination: 'checkout'; Mesh independently revalidates the cart and places the codes inside the encrypted one-hour cart token:

const cart = await storefront.createCartUrl({
  eventId,
  items,
  destination: 'checkout',
  returnUrl,
  accessCode: accessCode || undefined,
  discountCode: discountCode || undefined,
})

return cart.absoluteCheckoutUrl

Reserved and RSVP order workflow

Reserved orders are a server-side checkout alternative for partners that collect or attest payment outside Mesh. Use them from a trusted backend only; the scene API key and Idempotency-Key must not be sent from browser code.

The flow is:

  1. POST /v1/events/{eventId}/orders with Idempotency-Key to reserve inventory and receive an order in payment_pending status.
  2. Complete the external payment with the provider.
  3. POST /v1/orders/{orderId}/confirm with the external payment attestation.
  4. GET /v1/orders/{orderId} to poll or reconcile the canonical order status and issued ticket.

Create-order requests include purchaser contact, payment handoff details, an optional reservation TTL, and either the legacy single-item shape (slotId/quantity) or a multi-item items array. Each items entry uses the public slot identifier returned by Mesh plus its quantity. Multi-item reserved orders reserve, price, and fulfill every line item atomically; add-on tiers must include an eligible primary ticket in the same order and cannot exceed the eligible primary quantity. Create, confirm, and get responses include items so partners can reconcile the exact session/add-on allocation. They also include cart.breakdown, with subtotal, itemized fees (processingFee, protocolFee, vendorFee, referrerFee, total), itemized taxes (salesTax, total), and final total in USD minor units. Confirm requests require the configured provider identifier, amount, currency, external payment ID, paid timestamp, and paid status. Confirmed orders return the ticket details when fulfillment succeeds; orders may also remain fulfillment_pending, fail, expire, or be fetched later for reconciliation.

For RSVP events with no payment component, use the same create-order endpoint with type: "rsvp" and omit payment. Mesh validates that the event is an RSVP event and that the selected slots price to zero, then immediately starts ticket fulfillment. The create response is the canonical order shape with type: "rsvp" and a status such as fulfillment_pending or confirmed; poll GET /v1/orders/{orderId} when fulfillment is pending.

Always send a stable Idempotency-Key for create-order retries. Reusing the same key for the same logical order is safe; generating a new key for every retry can create duplicate reservations.

Named request/response types

@sceneinfrastructure/storefront-api exports named TypeScript aliases for every endpoint so consumers don't need to derive output types from the client or z.infer on schemas:

import type {
  CheckInEventTicketsInput,
  CheckInEventTicketsOutput,
  ListEventsOutput,
  ListEventTiersOutput,
  ListEventPricesOutput,
  ListEventAttendeesOutput,
  CreateCartUrlInput,
  CreateCartUrlOutput,
  ResolveAccessCodeInput,
  ResolveAccessCodeOutput,
  ValidateCartCodesInput,
  ValidateCartCodesOutput,
  ValidateEventCheckInInput,
  ValidateEventCheckInOutput,
  CreateSceneInput,
  CreateSceneOutput,
  CreateSceneOutput,
  UpdateSceneInput,
  UpdateSceneOutput,
  CreateOrderInput,
  CreateOrderOutput,
  ConfirmOrderInput,
  ConfirmOrderOutput,
  GetOrderOutput,
  StorefrontApiEvent,
  StorefrontApiEventDisplay,
  StorefrontApiEventLocation,
  StorefrontApiEventType,
  PublicApiProvisionedScene,
  StorefrontApiPrice,
  StorefrontApiTier,
  StorefrontApiTierDisplay,
  StorefrontApiTierRelationships,
  StorefrontApiTierRestriction,
  StorefrontApiOrderItem,
  StorefrontApiOrderSlot,
  StorefrontApiOrderStatus,
  StorefrontApiOrderTicket,
} from '@sceneinfrastructure/storefront-api'

The schemas (listEventsOutputSchema, publicApiTierSchema, etc.) and typed client (@sceneinfrastructure/storefront-api/client) remain available unchanged.

Agent skill

If you are using an AI coding agent, install the Mesh Storefront skill for API/SDK setup guidance, Next.js server-only usage patterns, required env vars, and OpenAPI fallback examples:

npx skills add https://api.sceneconstruction.xyz

The installer discovers the skill from https://api.sceneconstruction.xyz/.well-known/agent-skills/index.json.