🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

@01.software/sdk

Package Overview
Dependencies
Maintainers
1
Versions
166
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@01.software/sdk

01.software SDK

Source
npmnpm
Version
0.28.0
Version published
Weekly downloads
838
2.82%
Maintainers
1
Weekly downloads
 
Created
Source

@01.software/sdk

Official TypeScript SDK for the 01.software platform.

Installation

npm install @01.software/sdk
# or
pnpm add @01.software/sdk

Features

  • Full TypeScript type inference
  • Browser and server environment support
  • React Query integration (both Client and ServerClient)
  • Mutation hooks (useCreate, useUpdate, useRemove) with automatic cache invalidation
  • Customer auth hooks (useCustomerMe, useCustomerLogin, etc.) with cache management
  • Automatic retry with exponential backoff (non-retryable: 400, 401, 403, 404, 409, 422)
  • Webhook handling with HMAC-SHA256 signature verification
  • Sub-path imports (./server, ./webhook, ./realtime, ./ui/*) for tree-shaking
  • Type-safe read-only collections.from() for Client (compile-time write prevention)

Sub-path Imports

// Analytics — browser pageview tracking + custom events
import { createAnalytics } from '@01.software/sdk/analytics'
const analytics = createAnalytics({ publishableKey: 'pk_xxx' })
// auto-tracks pageviews; call analytics.pageview('/custom-path') manually if needed
analytics.track('signup', { plan: 'pro', trial: false }) // custom event with optional props
// Analytics React component — Vercel Analytics-style mount helper
import { Analytics } from '@01.software/sdk/analytics/react'

export function App() {
  return <Analytics />
}
// Main entry - browser client, query builder, hooks, utilities
import { createClient } from '@01.software/sdk'

// Server-only entry - avoids importing browser Client APIs
import { createServerClient } from '@01.software/sdk/server'

// Webhook only - webhook handlers
import {
  handleWebhook,
  createTypedWebhookHandler,
} from '@01.software/sdk/webhook'

// Realtime only
import { createRealtimeClient } from '@01.software/sdk/realtime'

// Components - sub-path imports per domain
import { Analytics } from '@01.software/sdk/analytics/react'
import { RichTextContent } from '@01.software/sdk/ui/rich-text'
import { Image } from '@01.software/sdk/ui/image'
import { FormRenderer } from '@01.software/sdk/ui/form'
import { CodeBlock } from '@01.software/sdk/ui/code-block'
import { CanvasRenderer } from '@01.software/sdk/ui/canvas'
import { VideoPlayer } from '@01.software/sdk/ui/video'

Getting Started

Client

import { createClient } from '@01.software/sdk'

const client = createClient({
  publishableKey: process.env.NEXT_PUBLIC_SOFTWARE_PUBLISHABLE_KEY,
})

// Query data (returns Payload native response)
const { docs } = await client.collections.from('products').find({
  limit: 10,
  where: { status: { equals: 'published' } },
})

Server Client

import { createServerClient } from '@01.software/sdk/server'

const server = createServerClient({
  publishableKey: process.env.SOFTWARE_PUBLISHABLE_KEY,
  secretKey: process.env.SOFTWARE_SECRET_KEY, // sk01_... opaque API key from Console
})

// Create order (server only)
const order = await server.commerce.orders.create({
  orderNumber: generateOrderNumber(),
  customerSnapshot: { email: 'user@example.com' },
  shippingAddress: { recipientName: 'John', phone: '010-1234-5678', postalCode: '12345', address1: 'Seoul', address2: 'Apt 101' },
  orderItems: [...],
  totalAmount: 10000,
  pgPaymentId: 'pay_123', // optional (omit for free orders)
  discountCode: 'WELCOME10', // optional
})

// SSR prefetch (server)
await server.query.prefetchQuery({
  collection: 'products',
  options: { limit: 10 },
})

Getting product detail

The recommended way to fetch a single product is the shaped helper:

import { createClient } from '@01.software/sdk'

const client = createClient({
  publishableKey: process.env.NEXT_PUBLIC_SOFTWARE_PUBLISHABLE_KEY!,
})

const product = await client.commerce.product.detail({ slug: 'every-peach-tee' })
if (!product) {
  return notFound() // returned null — product missing, unpublished, or not in this tenant
}
// product: { product, variants, options, brand, categories, tags, images, videos, listing }

detail() returns ProductDetail | null. A null result covers every "no result" reason: not_found, not_published, tenant_mismatch, feature_disabled. Render the same "not available" UI for all four. To recover the exact reason for triage, 404 maps to null rather than a thrown error — inspect client.lastRequestId and match against backend logs.

With React Query

const { data: product, isLoading } = client.query.useProductDetailBySlug(slug)

Cache key is ['products', 'detail', { slug }]. Mutations on products, product-variants, product-options, product-option-values, brands, brand-logos, images, and related collections automatically invalidate this cache.

Advanced: direct Payload queries (escape hatch)

Most consumers should use the helper APIs above (commerce.product.detail, etc.). The query builder below is the escape hatch for advanced cases the helpers do not cover: bulk operations, custom filter combinations, or fields the helper response does not expose.

depth — how deep to populate relationship fields

depth is the primary control for populating relationships like category, images, brand. The configured Payload default applies when unset.

const product = await client.collections.from('products').findById(id, {
  depth: 2, // populates product.category, product.category.parent, etc.
})

populate — which fields come back for populated relationships

populate controls which fields are returned per collection. It does NOT decide which relationships to populate — that is depth.

await client.collections.from('products').find({
  depth: 2,
  populate: {
    categories: { title: true, slug: true },
    images: { url: true, alt: true },
  },
})

joins — Payload join-field reverse-relations

joins is the correct control for Payload type: 'join' virtual reverse-relation fields. In this platform's schema, products.variants, products.options, products.collections, customers.orders, customers.addresses, posts.comments, article-authors.articles, orders.{items,transactions,fulfillments,returns}, and similar reverse-relations are all join fields — you must use joins (not depth/populate) to control their pagination, sorting, filtering, and count.

// Canonical product detail query — variants/options are join fields on Products
await client.collections.from('products').find({
  where: { slug: { equals } },
  joins: {
    variants: { limit: 50, sort: 'sortIndex' },
    options: {},
  },
  depth: 2, // also populate normal relationships (category, brand, etc.)
})

// Disable all join-field population for a lightweight list query
await client.collections.from('products').find({
  joins: false,
})

joins does NOT populate normal relationship fields. Keys that do not match a type: 'join' field on the queried collection are silently ignored — e.g. joins: { category: {} } on Products is a no-op because category is not a join field there. For normal relationships use depth (and optionally populate).

Filtering by relation

Use id-based filters as the default — they're the most reliable:

await client.collections.from('product-variants').find({
  where: { product: { equals: productId } },
})

Dotted-path filters (where: { 'product.slug': { equals } }) are Payload-native but may silently return empty when access control restricts the related document or when the relation is polymorphic.

Why did my query return empty?

Checklist when find() returns docs: [] unexpectedly, in order of likelihood:

  • Access control filtered the document. Many collections enforce status/published filters on public read (e.g. composedReadStatusPublished on products restricts unauthenticated reads to status: 'published'). A draft or unpublished document silently disappears from results even when its slug matches. Correlate with backend logs via client.lastRequestId (or catch SDKError.requestId).
  • Build-time publishable key / API URL differs from runtime. SSG generateStaticParams / generateMetadata / the page render must all see the same tenant context. A wrong or missing key at build time produces a baked-in empty response.
  • Next.js SSG fetch cache served a stale empty response. Use cache: 'no-store' or export const revalidate = 0 on server components that should reflect live data.
  • where: { slug: 'x' } string shorthand. Always use { slug: { equals: 'x' } } — bare strings silently match nothing.
  • Wrong key in joins. Keys not matching a type: 'join' field on the queried collection are silently ignored (no error). For normal relationship fields use depth/populate, not joins.
  • Dotted-path relation filter (where: { 'category.slug': { equals } }) under polymorphic or access-control constraints — switch to id-based filter: where: { category: { equals: id } }.

Usage in Next.js SSG / Server Components

  • Create the client per request in server components. Avoid module-level singletons that could share state (customer token, cache) across unrelated requests.
  • depth impacts static generation cost. Deeper populates = larger build payloads. Use select/populate to trim response shape.
  • Cache interaction. SDK requests honor Next.js fetch caching. For pages that must reflect live data, set cache: 'no-store' or export const revalidate = 0 on the route segment, or pass per-fetch options if you proxy the SDK behind your own fetcher.
// app/products/[slug]/page.tsx
import { createClient } from '@01.software/sdk'

export const revalidate = 60 // ISR — adjust per page freshness need

export default async function ProductPage({ params }) {
  const client = createClient({
    publishableKey: process.env.NEXT_PUBLIC_SOFTWARE_PUBLISHABLE_KEY!,
  })
  const product = await client.commerce.product.detail({ slug: params.slug })
  if (!product) return notFound()
  // ...
}

API

Client Configuration

const client = createClient({
  publishableKey: string, // Required
})

const server = createServerClient({
  publishableKey: string,
  secretKey: string, // sk01_... or pat01_...
})
OptionTypeDescription
publishableKeystringAPI publishable key
secretKeystringAPI secret key (server only)

API URL은 환경변수로 오버라이드 가능합니다:

  • SOFTWARE_API_URL (서버용) 또는 NEXT_PUBLIC_SOFTWARE_API_URL (브라우저용)
  • 미설정 시: dev 빌드(@dev 태그)는 api-dev.01.software, 정식 릴리즈는 api.01.software

Query Builder

Access collections via client.collections.from(slug).

Note: client.collections.from() returns a ReadOnlyQueryBuilder (only find, findById, count, findMetadata, findMetadataById). Write operations (create, update, remove, updateMany, removeMany) are only available on server.collections.from().

// List query - returns PayloadFindResponse
const { docs, totalDocs, hasNextPage } = await client.collections
  .from('products')
  .find({
    limit: 20,
    page: 1,
    sort: '-createdAt',
    where: { status: { equals: 'published' } },
    depth: 2,
    select: { title: true, slug: true },
  })

// Query with populate/joins control
const { docs } = await client.collections.from('products').find({
  select: { title: true, slug: true, price: true, thumbnail: true },
  joins: false, // disable joins for lightweight list
})

// Override relationship populate
const product = await client.collections.from('products').findById(id, {
  populate: { brands: { name: true, logo: true } },
  joins: { variants: { limit: 50 } },
})

// Single item query - returns document directly
const product = await client.collections.from('products').findById('id')

// Create (server only) - returns PayloadMutationResponse
const { doc, message } = await server.collections
  .from('products')
  .create({ name: 'Product' })

// Create with file upload (server only) - uses multipart/form-data
const { doc } = await server.collections
  .from('images')
  .create({ alt: 'Hero image' }, { file: imageFile, filename: 'hero.jpg' })

// Update (server only) - returns PayloadMutationResponse
const { doc } = await server.collections
  .from('products')
  .update('id', { name: 'Updated' })

// Update with file replacement (server only)
await server.collections
  .from('images')
  .update('id', { alt: 'New alt' }, { file: newFile })

// Delete (server only) - returns document directly
const deletedDoc = await server.collections.from('products').remove('id')

// Count
const { totalDocs } = await client.collections.from('products').count()

// SEO Metadata (fetch + generate in one call, depth: 1 auto-applied)
const metadata = await client.collections
  .from('products')
  .findMetadata(
    { where: { slug: { equals: 'my-product' } } },
    { siteName: 'My Store' },
  )

const metadataById = await client.collections
  .from('products')
  .findMetadataById('id', {
    siteName: 'My Store',
  })

// Bulk operations (server only)
await server.collections.from('products').updateMany(where, data)
await server.collections.from('products').removeMany(where)

API Response Types (Payload Native)

The SDK returns Payload CMS native response types without wrapping:

// find() returns PayloadFindResponse<T>
interface PayloadFindResponse<T> {
  docs: T[]
  totalDocs: number
  limit: number
  totalPages: number
  page: number
  pagingCounter: number
  hasPrevPage: boolean
  hasNextPage: boolean
  prevPage: number | null
  nextPage: number | null
}

// create() / update() returns PayloadMutationResponse<T>
interface PayloadMutationResponse<T> {
  message: string
  doc: T
  errors?: unknown[]
}

// findById() / remove() returns T (document directly)
OperationResponse Type
find()PayloadFindResponse<T> - { docs, totalDocs, hasNextPage, ... }
findById()T - document object directly
create()PayloadMutationResponse<T> - { doc, message }
update()PayloadMutationResponse<T> - { doc, message }
remove()T - deleted document object directly
count(){ totalDocs: number }
findMetadata()Metadata | null - Next.js Metadata (null if no match)
findMetadataById()Metadata - Next.js Metadata (throws on 404)

React Query Hooks

Read hooks are available on both Client and ServerClient via client.query.*. Mutation hooks (useCreate, useUpdate, useRemove) are only available on ServerClient.

// List query
const { data, isLoading } = client.query.useQuery({
  collection: 'products',
  options: { limit: 10 },
})

// Suspense mode
const { data } = client.query.useSuspenseQuery({
  collection: 'products',
  options: { limit: 10 },
})

// Query by ID
const { data } = client.query.useQueryById({
  collection: 'products',
  id: 'product_id',
})

// Infinite scroll
const { data, fetchNextPage, hasNextPage } = client.query.useInfiniteQuery({
  collection: 'products',
  options: { limit: 20 },
})

// Mutation hooks — ServerClient only (auto-invalidate cache on success)
const { mutate: create } = client.query.useCreate({ collection: 'images' })
const { mutate: update } = client.query.useUpdate({ collection: 'products' })
const { mutate: remove } = client.query.useRemove({ collection: 'products' })

create({ data: { alt: 'Hero' }, file: imageFile, filename: 'hero.jpg' })
update({ id: 'product_id', data: { title: 'Updated' } })
remove('product_id')

// SSR Prefetch
await client.query.prefetchQuery({
  collection: 'products',
  options: { limit: 10 },
})
await client.query.prefetchQueryById({
  collection: 'products',
  id: 'product_id',
})
await client.query.prefetchInfiniteQuery({
  collection: 'products',
  pageSize: 20,
})

// Cache utilities
client.query.invalidateQueries('products')
client.query.getQueryData('products', 'list', options)
client.query.setQueryData('products', 'detail', id, data)

// Customer auth hooks (Client only)
const { data: profile } = client.query.useCustomerMe()
const { mutate: login } = client.query.useCustomerLogin()
const { mutate: register } = client.query.useCustomerRegister()
const { mutate: logout } = client.query.useCustomerLogout()

login({ email: 'user@example.com', password: 'password' })

// Other customer mutations
client.query.useCustomerForgotPassword()
client.query.useCustomerResetPassword()
client.query.useCustomerChangePassword()

// Customer cache utilities
client.query.invalidateCustomerQueries()
client.query.getCustomerData()
client.query.setCustomerData(profile)

Customer Auth

Available on Client via client.customer.auth.*.

const client = createClient({
  publishableKey: process.env.NEXT_PUBLIC_SOFTWARE_PUBLISHABLE_KEY,
  customer: { persist: true },
})

// Register & login
const { customer } = await client.customer.auth.register({
  name: 'John',
  email: 'john@example.com',
  password: 'secure123',
})
const { token, customer } = await client.customer.auth.login({
  email: 'john@example.com',
  password: 'secure123',
})

// Profile & token management
const profile = await client.customer.auth.me()
client.customer.auth.isAuthenticated()
client.customer.auth.logout()

// Authenticated customer's own orders (Client-only)
const orders = await client.commerce.orders.listMine({
  page: 1,
  limit: 10,
  status: 'paid',
})

// Password
await client.customer.auth.forgotPassword('john@example.com')
await client.customer.auth.resetPassword(token, newPassword)
await client.customer.auth.changePassword(currentPassword, newPassword)

Commerce Orders (ServerClient-only writes)

Available on ServerClient via server.commerce.orders.*. Only checkout and listMine are also on Client.

// Orders
await server.commerce.orders.create(params)
await server.commerce.orders.update({ orderNumber, status })
await server.commerce.orders.checkout({ cartId, pgPaymentId?, orderNumber, customerSnapshot, discountCode? })

// Single-order lookup (getOrder endpoint removed — use collection find)
const { docs: [order] } = await server.collections.from('orders').find({
  where: { orderNumber: { equals: 'ORD-...' } },
  limit: 1,
  depth: 1,
})

// Fulfillment & transactions
await server.commerce.orders.createFulfillment({ orderNumber, carrier, trackingNumber, items })
await server.commerce.orders.bulkImportFulfillments({ items: [{ orderNumber, carrier?, trackingNumber? }] })
await server.commerce.orders.updateTransaction({ pgPaymentId, status, paymentMethod, receiptUrl })

// Provider-verified payment confirmation
// Existing Toss server-confirm callers may keep using updateTransaction with paymentKey + amount.
// PortOne/Stripe/etc. webhook handlers should verify with the provider first, then call:
await server.commerce.orders.confirmPayment({
  orderNumber,
  pgProvider: 'portone',
  pgPaymentId,
  amount,
  providerStatus: 'PAID',
  providerEventId,
})

// Returns
await server.commerce.orders.createReturn({ orderNumber, returnItems, refundAmount, reason? })
await server.commerce.orders.updateReturn({ returnId, status })
await server.commerce.orders.returnWithRefund({ orderNumber, returnItems, refundAmount, pgPaymentId })

Commerce Discounts / Shipping

Available on both Client (customer JWT) and ServerClient (secretKey).

await client.commerce.discounts.validate({ code, orderAmount })
await client.commerce.shipping.calculate({ shippingPolicyId?, orderAmount, postalCode? })
// calculateShipping returns: { shippingAmount, baseShippingAmount, extraShippingAmount, freeShipping, freeShippingMinAmount, isJeju, isRemoteIsland }

Commerce Product

Available on both Client and ServerClient via commerce.product.*.

// Batch stock check (point-in-time read, NOT a reservation)
const { results, allAvailable } = await client.commerce.product.stockCheck({
  items: [{ variantId: '...', quantity: 2 }],
})

Commerce Cart

Available on both Client and ServerClient via commerce.cart.*.

// Add item to cart
await client.commerce.cart.addItem({
  cartId,
  product,
  variant,
  option,
  quantity,
})

// Update item quantity
await client.commerce.cart.updateItem({ cartItemId, quantity })

// Remove item from cart
await client.commerce.cart.removeItem({ cartItemId })

// Other cart operations
await client.commerce.cart.get(cartId)
await client.commerce.cart.applyDiscount({ cartId, discountCode })
await client.commerce.cart.removeDiscount({ cartId })
await client.commerce.cart.clear({ cartId })

Community Moderation (ServerClient-only)

Available only on ServerClient via server.community.moderation.*.

await server.community.moderation.banCustomer({ customerId, isPermanent?, bannedUntil?, reason? })
await server.community.moderation.unbanCustomer({ customerId })

Webhook

import {
  handleWebhook,
  createCustomerAuthWebhookHandler,
  createTypedWebhookHandler,
} from '@01.software/sdk'

// Basic handler
export async function POST(request: Request) {
  return handleWebhook(request, async (event) => {
    console.log(event.collection, event.operation, event.data)
  })
}

// With HMAC-SHA256 signature verification (recommended)
export async function POST(request: Request) {
  return handleWebhook(request, handler, {
    secret: process.env.WEBHOOK_SECRET,
  })
}

// Signed deliveries include x-webhook-signature, x-webhook-timestamp,
// and x-webhook-delivery-id. handleWebhook rejects stale or unsigned
// deliveries when secret is set.

// Type-safe handler
const handler = createTypedWebhookHandler('orders', async (event) => {
  // event.data is typed as Order
  console.log(event.data.orderNumber)
})

// Customer auth helper
const customerAuthHandler = createCustomerAuthWebhookHandler({
  passwordReset: async ({ email, resetPasswordToken }) => {
    await sendPasswordResetEmail(email, resetPasswordToken)
  },
})

Supported Collections

Source of truth: packages/sdk/src/core/collection/const.ts (COLLECTIONS: 73).

CategoryCollections
Tenanttenants, tenant-metadata, tenant-logos
Productsproducts, product-variants, product-options, product-option-values, product-categories, product-tags, product-collections, brands, brand-logos
Ordersorders, order-items, returns, return-items, fulfillments, fulfillment-items, transactions
Customerscustomers, customer-profiles, customer-profile-lists, customer-addresses
Cartscarts, cart-items
Commercediscounts, shipping-policies
Contentdocuments, document-categories, document-types, articles, article-authors, article-categories, article-tags, links, link-categories, link-tags
Playlists / Tracksplaylists, playlist-categories, playlist-tags, tracks, track-categories, track-tags
Galleriesgalleries, gallery-categories, gallery-tags, gallery-items
Canvascanvases, canvas-node-types, canvas-edge-types, canvas-categories, canvas-tags, canvas-nodes, canvas-edges
Videosvideos, video-categories, video-tags
Live Streamslive-streams
Mediaimages
Formsforms, form-submissions
Communityposts, comments, reactions, reaction-types, bookmarks, post-categories
Eventsevent-calendars, events, event-categories, event-occurrences, event-tags

Server-only collections: customer-groups, reports, and community-bans are available from createServerClient().collections for segmentation and moderation workflows, but are intentionally absent from browser collection discovery.

Utilities

resolveRelation

Resolves a Payload CMS relation field value. When depth is 0, relation fields return just an ID (number). When depth > 0, they return the full object. This utility normalizes both cases.

import { resolveRelation } from '@01.software/sdk'

const authors = post.authors?.map((a) => resolveRelation(a)) ?? [] // Author[]

Note: Prefer resolveRelation. It covers the same normalization use case directly.

generateOrderNumber

import { generateOrderNumber } from '@01.software/sdk'

const orderNumber = generateOrderNumber()
// "260121123456" (YYMMDDRRRRRR format)

formatOrderName

import { formatOrderName } from '@01.software/sdk'

formatOrderName([{ product: { title: 'Product A' } }])
// "Product A"

formatOrderName([
  { product: { title: 'Product A' } },
  { product: { title: 'Product B' } },
])
// "Product A 외 1건"

RichTextContent

React component for rendering Payload CMS Lexical rich text. Two variants:

  • RichTextContent — Base component with maximum flexibility (converters, blocks, nodeMap, disableDefaultConverters)
  • StyledRichTextContent — Headless component with slot-based customization (components prop)
import {
  RichTextContent,
  StyledRichTextContent,
  richTextNodeMap,
} from '@01.software/sdk/ui/rich-text'

// Base: full converter control
<RichTextContent
  data={content}
  className="prose"
  internalDocToHref={({ linkNode }) => `/articles/${linkNode.fields.doc?.value?.slug}`}
  blocks={{
    Iframe: ({ node }) => <iframe src={node.fields.url} />,
    Player: ({ node }) => <VideoPlayer url={node.fields.url} />,
  }}
/>

// Shared Payload view-map rendering for supported lightweight blocks
<RichTextContent data={content} nodeMap={richTextNodeMap} />

// Headless: component slots (Radix-style)
<StyledRichTextContent
  data={content}
  components={{
    Heading: ({ tag: Tag, children }) => (
      <Tag className={Tag === 'h1' ? 'text-4xl font-bold' : 'text-2xl'}>{children}</Tag>
    ),
    Link: ({ href, children, target, rel }) => (
      <a href={href} target={target} rel={rel} className="text-blue-600 underline">{children}</a>
    ),
    Upload: ({ src, alt, width, height }) => (
      <img src={src} alt={alt} width={width} height={height} className="rounded-lg" />
    ),
  }}
/>

Error Handling

The SDK throws typed errors instead of returning error responses:

import { isNetworkError, isApiError, isValidationError } from '@01.software/sdk'

try {
  const { docs } = await client.collections.from('products').find()
} catch (error) {
  if (isNetworkError(error)) {
    console.error('Network issue:', error.message)
  } else if (isApiError(error)) {
    console.error('API error:', error.status, error.message)
  }
}

Error classes: SDKError, ApiError, NetworkError, ValidationError, ConfigError, TimeoutError

Environment Variables

NEXT_PUBLIC_SOFTWARE_PUBLISHABLE_KEY=your_publishable_key
SOFTWARE_PUBLISHABLE_KEY=your_publishable_key
SOFTWARE_SECRET_KEY=sk01_...  # Server only — opaque API key from Console

secretKey should only be used in server environments. Never expose it to the browser.

secretKey may be an sk01_... API key or a pat01_... personal access token. Server SDK calls must also send the matching publishableKey; PAT tenant selection is pinned server-side, and callers must not send X-Tenant-Id.

API keys created without explicit scopes use the default ['read', 'write']. Console API-key creation can request narrower scopes[]; in the first explicit-scope slice tenant admins may grant only read and write. Higher-authority scopes such as webhook, analytics, and super-admin require platform authority and otherwise return scope_grant_forbidden.

SDK 0.9.0: Server auth now uses opaque bearer tokens (sk01_...). Generate API keys from the Console. createServerToken, createApiKey, and parseApiKey are no longer part of the SDK surface.

Changelog

v0.23.0 (Product option-value visuals)

  • Added reusable option-value visuals (swatchColor, thumbnail, images) to Payload types and ecommerce utility shapes.
  • Listing group summaries now include option-value visual metadata and can use one colorway image across every size variant.
  • Product/listing sellability now uses stock - reservedStock, matching checkout stock checks.

Migration Guide

v0.16.0 (Phase 1–7 sync — additive)

New error codes propagated via SDKError.code (no breaking change; existing callers ignore unknown codes safely):

CodePhaseTrigger
account_suspendedP1Suspended session / sk01_ / pat01_ / customer JWT — 401
pat_tenant_header_forbiddenP1pat01_ request carrying any X-Tenant-Id header — 401
tenant_mismatchP3Cross-tenant FK rejection (forms / community / orders)
server_derivedP3Body-driven write into a server-derived state field — 422
scope_deniedP5pat01_ whose ApiKeys.scopes lacks the operation

P5 also adds JWT-jti revocation: revokeCustomerJti(jti, ttl) on the server invalidates a token immediately; subsequent SDK calls receive 401 { code: 'token_revoked' }.

COLLECTIONS and INTERNAL_COLLECTIONS are now both exported from @01.software/sdk. Use INTERNAL_COLLECTIONS to detect admin-only slugs in custom tooling.

v0.8.0 (Breaking Changes)

Field renames — update any code that reads these fields from API responses:

CollectionOldNew
CustomerssocialIdproviderUserId
CustomersloginAttemptsloginAttemptCount
CustomersresetPasswordExpiryresetPasswordExpiresAt
Orders, CartsshippingFeeshippingAmount
CartsitemsTotalsubtotalAmount
TransactionspaymentIdpgPaymentId
DiscountstypediscountType
DiscountsvaluediscountValue
DiscountsusageLimitmaxUses
DiscountsusageCountusesCount
DiscountsperCustomerLimitmaxUsesPerCustomer
ShippingPoliciesbaseFeebaseAmount
ShippingPoliciesfreeShippingThresholdfreeShippingMinAmount
DocumentseffectiveDateeffectiveAt
DocumentsexpiryDateexpiresAt
ArticlesreadTimereadingMinutes
ApiUsagecountapiCallCount
ApiUsagestorageUsedstorageUsedBytes
ApiUsagetotalDocumentsdocumentCount

Collection renames:

  • order-productsorder-items
  • return-productsreturn-items
  • Removed: exchanges, exchange-products
  • Added: product-option-values

Boolean field renames (6 collections):

  • status: 'active' | 'inactive'isActive: boolean on Forms, ArticleAuthors, CustomerGroups, ShippingPolicies, ProductVariants

FAQs

Package last updated on 13 May 2026

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts