Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,45 @@ project adheres to [Semantic Versioning](https://semver.org/).

## [Unreleased]

## [2.7.0] - 2026-09-19

### Added
- `client.photoMockups` and `client.psdMockups`, the family names of the two
mockup resources. `client.photoMockups` is the object `client.ai` was;
`client.psdMockups` is the object `client.mockups` was.
- Family type names beside the earlier ones: `PhotoMockup`,
`PhotoMockupDetails`, `PhotoMockupQuad`, `PhotoMockupSurface`,
`PhotoMockupPrintAreaInput`, `PhotoMockupListResult`,
`ListPhotoMockupsParams`, `CreatePhotoMockupParams`,
`CreatePhotoMockupResult`, `WaitForPhotoMockupOptions`,
`UpdatePhotoMockupPrintAreasResult`, `PhotoMockupRenderParams`,
`PhotoMockupRenderResult`, `PhotoMockupPrintArea`, `PhotoMockupPrintFile`,
`PhotoMockupAdjustments` and `PhotoMockupPlacement`. Each is the same shape
as the name it stands beside; the earlier names stay exported.

### Changed
- Requests travel on the family paths: `client.photoMockups` posts to
`/api/v1/photo-mockups` (was `/api/v1/sudoai/2d-mockups`) and
`client.psdMockups` to `/api/v1/psd-mockups` (was `/api/v1/mockups`). The
API keeps serving the earlier paths, so a project pinned to 2.6.x is not
affected.
- **Job kinds.** Because requests now travel on the family path, a job
submitted through this SDK is named by its family: an async
`photoMockups.create()` resolves with `kind: 'photo_mockup_create'` and an
async `photoMockups.render()` with `kind: 'photo_mockup_render'`, where 2.6.x
returned `'2d_create'` / `'2d_render'`. Code that compares `job.kind` against
a `2d_*` literal must compare against the family name (or both). `JobKind`
keeps both spellings, `jobs.list({ kind })` with either returns both, and
webhook event names are untouched: an endpoint keeps the spelling it is
pinned to (`eventNaming`).

### Deprecated
- `client.ai` and `client.mockups`. Both keep working and return the very same
objects as `client.photoMockups` / `client.psdMockups`; the first access to
each emits a one-time `DeprecationWarning` naming the replacement.

Nothing was removed.

## [2.6.0] - 2026-09-18

### Added
Expand Down
60 changes: 32 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# SudoMock Node.js SDK

Official Node.js/TypeScript SDK for the [SudoMock API](https://sudomock.com/docs). Generate product mockups from Photoshop PSD files or use AI-powered rendering without any PSD.
Official Node.js/TypeScript SDK for the [SudoMock API](https://sudomock.com/docs). Generate product mockups from Photoshop PSD files, or from a product photo without any PSD.

## Installation

Expand All @@ -19,7 +19,7 @@ const client = new SudoMock('sm_your_api_key')
// or set SUDOMOCK_API_KEY env var and call: new SudoMock()

// List your mockups
const { mockups, total } = await client.mockups.list({ limit: 10 })
const { mockups, total } = await client.psdMockups.list({ limit: 10 })
console.log(`Found ${total} mockups`)

// Render a mockup with artwork
Expand Down Expand Up @@ -52,11 +52,14 @@ The API key can be passed as the first argument or via the `SUDOMOCK_API_KEY` en

Every request identifies itself with `X-SudoMock-Client: node-sdk/<version>` (and the same value as `User-Agent` where the runtime allows it), so you can pick the SDK's traffic out of your own logs and proxy rules.

### Mockups
`client.ai` and `client.mockups` are the earlier names of `client.photoMockups`
and `client.psdMockups`. They return the same objects and warn once per process.

### PSD Mockups (`client.psdMockups`)

```typescript
// List mockups with pagination and filtering
const result = await client.mockups.list({
const result = await client.psdMockups.list({
limit: 20,
offset: 0,
name: 'shirt', // case-insensitive contains
Expand All @@ -65,17 +68,17 @@ const result = await client.mockups.list({
})

// Get a single mockup
const mockup = await client.mockups.get('uuid')
const mockup = await client.psdMockups.get('uuid')
console.log(mockup.smartObjects)

// Update mockup name
const updated = await client.mockups.update('uuid', { name: 'New Name' })
const updated = await client.psdMockups.update('uuid', { name: 'New Name' })

// Delete a mockup
await client.mockups.delete('uuid')
await client.psdMockups.delete('uuid')
```

> **Bulk delete all mockups** (`DELETE /api/v1/mockups/all`) requires a
> **Bulk delete all mockups** (`DELETE /api/v1/psd-mockups/all`) requires a
> dashboard Bearer token and is **not** callable with an API key (the API
> returns 403). It is therefore intentionally not exposed by this SDK -- use
> the dashboard.
Expand Down Expand Up @@ -119,7 +122,7 @@ outputs. `smartObjects` is optional for text-only renders, and `fit` defaults to
`'overflow'`.

```typescript
const mockup = await client.mockups.get('mockup-uuid')
const mockup = await client.psdMockups.get('mockup-uuid')
const nameLayer = mockup.textLayers.find((layer) => layer.name === 'Customer Name')
if (!nameLayer?.isEditable) throw new Error('Customer Name is not editable')

Expand Down Expand Up @@ -174,8 +177,9 @@ List and page your jobs (keyset pagination, newest first). Filter by `kind`
(`render` | `video` | `upload` | `2d_create` | `2d_render` |
`photo_mockup_create` | `photo_mockup_render`) and/or `mockupUuid`. A
photo-mockup job is spelled `photo_mockup_*` when submitted on
`/api/v1/photo-mockups` and `2d_*` when submitted through `client.ai`; filtering
by either spelling returns both:
`/api/v1/photo-mockups` (where `client.photoMockups` posts since 2.7.0) and
`2d_*` when submitted on the earlier `/api/v1/sudoai/2d-mockups` path;
filtering by either spelling returns both:

```typescript
const { jobs, nextCursor } = await client.jobs.list({ kind: 'video', limit: 50 })
Expand Down Expand Up @@ -214,15 +218,15 @@ const job = await client.renders.createVideo({
})
```

### 2D Mockups (`client.ai`)
### Photo Mockups (`client.photoMockups`)

Render artwork onto an existing 2D mockup (no PSD template) and manage your 2D
mockup catalog. `client.ai.render` posts to
`/api/v1/sudoai/2d-mockups/{mockupId}/render` (the mockup id lives in the path)
mockup catalog. `client.photoMockups.render` posts to
`/api/v1/photo-mockups/{mockupId}/render` (the mockup id lives in the path)
and costs **5 credits** per call. Each print area must supply `artworkUrl` OR
`color`. Use `uuid` for a saved print area -- a bounded zone drawn on the
product -- or `surfaceUuid` for a product surface, both returned by
`client.ai.get()`. A product can have both, and they are separate targets.
`client.photoMockups.get()`. A product can have both, and they are separate targets.
Placement follows the target, and sizing has one answer per request: a surface
takes a `coverage` percentage or an explicit `width` + `height`, a print area
takes a `fit` or an explicit `width` + `height`. Anchoring -- `position`,
Expand All @@ -232,7 +236,7 @@ handing one the other's dial does not compile.
#### Render an existing 2D mockup

```typescript
const result = await client.ai.render({
const result = await client.photoMockups.render({
mockupId: 'mockup-uuid',
printAreas: [{
uuid: 'print-area-uuid',
Expand All @@ -249,13 +253,13 @@ console.log(result.printFiles[0].exportFormat) // 'webp'
```

Prefer to render in the background? Pass `isAsync: true` and `render()` resolves
with a `Job` of kind `'2d_render'` (`202 Accepted`) you await with
with a `Job` of kind `'photo_mockup_render'` (`202 Accepted`) you await with
`jobs.waitForJob`. A `photo_mockup_render.succeeded` /
`photo_mockup_render.failed` webhook also fires (`2d_render.succeeded` /
`2d_render.failed` on an endpoint that keeps the legacy event names).

```typescript
const job = await client.ai.render({
const job = await client.photoMockups.render({
mockupId: 'mockup-uuid',
printAreas: [{
uuid: 'print-area-uuid',
Expand All @@ -277,7 +281,7 @@ Create a reusable 2D mockup, then render artwork. By default creation is
**25 credits** are refunded automatically.

```typescript
const mockup = await client.ai.create({
const mockup = await client.photoMockups.create({
sourceUrl: 'https://example.com/product.jpg',
name: 'Front view',
idempotencyKey: 'front-view-v1',
Expand All @@ -286,7 +290,7 @@ const mockup = await client.ai.create({
const printArea = mockup.quads[0]
if (!printArea) throw new Error('No print area available')

const result = await client.ai.render({
const result = await client.photoMockups.render({
mockupId: mockup.mockupId,
printAreas: [{
uuid: printArea.printAreaId,
Expand All @@ -297,7 +301,7 @@ console.log(result.url)

const fullSurface = mockup.surfaces[0]
if (fullSurface) {
await client.ai.render({
await client.photoMockups.render({
mockupId: mockup.mockupId,
printAreas: [{
surfaceUuid: fullSurface.surfaceUuid,
Expand All @@ -311,11 +315,11 @@ Prefer to create in the background? Pass `isAsync: true` and `create()` resolves
with a `Job` you await with `waitForReady`:

```typescript
const job = await client.ai.create({
const job = await client.photoMockups.create({
sourceUrl: 'https://example.com/product.jpg',
isAsync: true,
})
const mockup = await client.ai.waitForReady(job, { intervalMs: 2_000 })
const mockup = await client.photoMockups.waitForReady(job, { intervalMs: 2_000 })
```

Update all print areas on a ready mockup with up to 8 four-point quads (each
Expand All @@ -324,7 +328,7 @@ the API has verified every product surface as printable; each of those is then
a render target in its own right:

```typescript
const updated = await client.ai.updatePrintAreas('mockup-uuid', [{
const updated = await client.photoMockups.updatePrintAreas('mockup-uuid', [{
points: [[100, 100], [900, 100], [900, 900], [100, 900]],
name: 'Front',
}])
Expand All @@ -334,13 +338,13 @@ console.log(updated.printAreas)
Manage the 2D-mockup catalog:

```typescript
const { mockups, total } = await client.ai.list({
const { mockups, total } = await client.photoMockups.list({
limit: 50,
customizableOnly: true,
})
console.log(mockups[0]?.customizable)
const mockup = await client.ai.get('mockup-uuid')
await client.ai.delete('mockup-uuid')
const mockup = await client.photoMockups.get('mockup-uuid')
await client.photoMockups.delete('mockup-uuid')
```

### Background Removal (`client.images`)
Expand Down Expand Up @@ -382,7 +386,7 @@ await client.renders.create({
})

// 2D render
await client.ai.render({
await client.photoMockups.render({
mockupId: 'mockup-uuid',
printAreas: [{
uuid: 'print-area-uuid',
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "sudomock",
"version": "2.6.0",
"version": "2.7.0",
"description": "Official Node.js/TypeScript SDK for the SudoMock API - mockup generation from PSD files",
"type": "module",
"main": "./dist/index.cjs",
Expand Down
57 changes: 49 additions & 8 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { deprecate } from 'node:util'
import { HttpClient } from './client'
import { SudoMockError } from './errors'
import { MockupsResource } from './resources/mockups'
Expand All @@ -15,6 +16,17 @@ const DEFAULT_BASE_URL = 'https://api.sudomock.com'
const DEFAULT_TIMEOUT = 30_000
const DEFAULT_MAX_RETRIES = 2

// Earlier accessor names. Each warns once per process, then behaves exactly
// like the name it stands for.
const warnAi = deprecate(
() => {},
'client.ai is deprecated, use client.photoMockups (the same object).',
)
const warnMockups = deprecate(
() => {},
'client.mockups is deprecated, use client.psdMockups (the same object).',
)

/**
* SudoMock API client.
*
Expand All @@ -25,7 +37,7 @@ const DEFAULT_MAX_RETRIES = 2
* const client = new SudoMock('sm_xxx')
*
* // List mockups
* const { mockups } = await client.mockups.list()
* const { mockups } = await client.psdMockups.list()
*
* // Render
* const render = await client.renders.create({
Expand All @@ -39,12 +51,12 @@ const DEFAULT_MAX_RETRIES = 2
* ```
*/
class SudoMock {
/** Mockup CRUD operations */
readonly mockups: MockupsResource
/** PSD mockups: list, inspect, rename and delete uploaded templates */
readonly psdMockups: MockupsResource
/** Render mockups with artwork */
readonly renders: RendersResource
/** Create and render reusable 2D mockups */
readonly ai: AIResource
/** Photo mockups: create reusable mockups from product photos and render onto them */
readonly photoMockups: AIResource
/** Standalone image operations (background removal) */
readonly images: ImagesResource
/** Upload PSD files */
Expand All @@ -53,7 +65,7 @@ class SudoMock {
readonly account: AccountResource
/** Studio session management */
readonly studio: StudioResource
/** Poll async render, video, upload, and 2D-creation jobs */
/** Poll async render, video, upload, and photo-mockup jobs */
readonly jobs: JobsResource
/** Manage webhook endpoints and their deliveries */
readonly webhooks: WebhooksResource
Expand All @@ -74,16 +86,28 @@ class SudoMock {
maxRetries: options.maxRetries ?? DEFAULT_MAX_RETRIES,
})

this.mockups = new MockupsResource(client)
this.psdMockups = new MockupsResource(client)
this.renders = new RendersResource(client)
this.ai = new AIResource(client)
this.photoMockups = new AIResource(client)
this.images = new ImagesResource(client)
this.uploads = new UploadsResource(client)
this.account = new AccountResource(client)
this.studio = new StudioResource(client)
this.jobs = new JobsResource(client)
this.webhooks = new WebhooksResource(client)
}

/** @deprecated Earlier name of {@link SudoMock.psdMockups}. The same object; warns once per process. */
get mockups(): MockupsResource {
warnMockups()
return this.psdMockups
}

/** @deprecated Earlier name of {@link SudoMock.photoMockups}. The same object; warns once per process. */
get ai(): AIResource {
warnAi()
return this.photoMockups
}
}

// Default export for convenient `import SudoMock from 'sudomock'`
Expand Down Expand Up @@ -154,6 +178,23 @@ export type {
TwoDMockupQuad,
TwoDMockupListResult,
List2dMockupsParams,
PhotoMockup,
PhotoMockupDetails,
PhotoMockupQuad,
PhotoMockupSurface,
PhotoMockupPrintAreaInput,
PhotoMockupListResult,
ListPhotoMockupsParams,
CreatePhotoMockupParams,
CreatePhotoMockupResult,
WaitForPhotoMockupOptions,
UpdatePhotoMockupPrintAreasResult,
PhotoMockupRenderParams,
PhotoMockupRenderResult,
PhotoMockupPrintArea,
PhotoMockupPrintFile,
PhotoMockupAdjustments,
PhotoMockupPlacement,
RemoveBackgroundParams,
RemoveBackgroundResult,
UploadParams,
Expand Down
Loading
Loading