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

@piezas/sdk

v0.3.9

Published

TypeScript SDK for Piezas backend building blocks and integrations

Downloads

1,110

Readme

@piezas/sdk

Piezas by Softmax Data — Backend building blocks for the AI-assisted coding era.

Pieza is Spanish for "piece" — a building block. That's how we think about backend software.

The Problem with AI-Assisted Coding

AI-assisted coding is transforming how software gets built. You describe what you want, and AI builds it. But there are three serious problems:

Too much code, impossible to maintain. Your coding agent generates everything from scratch — database schemas, API routes, auth flows, email services, file storage, workflow engines — on top of the UI and business logic you actually care about. That's tens of thousands of lines of backend code that nobody truly understands, and when it breaks, good luck debugging AI-generated database migrations.

No shared data across systems. You generate a CRM with AI, then an ERP, then a project tracker. Each one gets its own database, its own user model, its own data objects. But your CRM and ERP should share customer data. Your project tracker and CRM should share contacts. Instead, you end up with three siloed copies of the same data.

Backend ownership is still real work. Generated code can create database schemas, auth glue, integrations, and background jobs quickly, but someone still has to own token refresh, schema changes, provider API edge cases, and operational fixes.

The Solution: Piezas

If software is a building, AI-assisted coding today is like telling your contractor to start by mixing concrete. You shouldn't have to. You should focus on designing the home you want to live in — the layout, the finishes, the workflow — and use documented backend building blocks for the rest.

Piezas is the building supply store. We provide 13 backend building blocks plus an integrations layer — data storage, pipelines, tasks, email, calendars, documents, provider connections, and more — as documented APIs. Your AI coding agent (Claude Code, Cursor, Codex) or dev agency is the construction crew. You focus on the workflow, the logic, and the UI; Piezas provides the backend service layer.

All your apps share the same backend. Your CRM and ERP use the same customer records. Your project tracker and CRM share the same contacts. One data layer, many frontends.

The Result

  • Less generated backend code — most code your agent writes can stay focused on UI, workflow, and business logic
  • Reusable backend APIs — tenant-scoped service APIs, audit trails, and activity timelines
  • Shared data objects — your CRM, ERP, and project tracker all use the same customer records
  • Pay by usage, not seats — build internal tools without tying every workflow to per-seat SaaS pricing
  • You own it — no per-seat SaaS tax, no vendor lock-in on your data, no feature walls
  • Change anything, instantly — no waiting for SaaS vendors to ship features on their quarterly roadmap. Need a new field, a custom workflow, a different UI? Tell your coding agent and deploy in minutes

Instead of stitching together separate CRM, issue tracking, scheduling, outbound sales, and one-off custom backends, build exactly what you need and reuse the same backend primitives across apps.

What You Can Build

| What you'd normally pay for | With Piezas | |-----------------------------|------------| | CRM software | Your own CRM — contacts, deals, pipeline, email automation | | Issue tracking software | Your own project tracker — tickets, Kanban boards, sprints, time tracking | | Scheduling software | Your own scheduling tool — calendars, availability, bookings | | Outreach software | Your own email campaigns — drip sequences, templates, delivery tracking | | Workflow software | Your own workflow engine — custom stages, automations, dashboards |

All of these on one platform, tailored to your exact workflow, for a fraction of the cost.


Install

npm install @piezas/sdk

Quick Start

import { Piezas } from '@piezas/sdk';

const piezas = new Piezas({
  apiKey: 'sk_live_your_key_here',        // from app.piezas.ai
  entitiesUrl: 'https://api.piezas.ai/entities',
  pipelineUrl: 'https://api.piezas.ai/pipeline',
  tasksUrl: 'https://api.piezas.ai/tasks',
  notificationsUrl: 'https://api.piezas.ai/notify',
  integrationsUrl: 'https://api.piezas.ai/integrations',
  workflowUrl: 'https://api.piezas.ai/workflow',
  calendarUrl: 'https://api.piezas.ai/calendar',
  messagingUrl: 'https://api.piezas.ai/messaging',
  formsUrl: 'https://api.piezas.ai/forms',
  documentsUrl: 'https://api.piezas.ai/documents',
  reportingUrl: 'https://api.piezas.ai/reporting',
  pricingUrl: 'https://api.piezas.ai/pricing',
  discussionUrl: 'https://api.piezas.ai/discussion',
  knowledgeBaseUrl: 'https://api.piezas.ai/knowledge-base',
  adminUrl: 'https://api.piezas.ai/admin',
});

// Define your data model (idempotent — safe to call on every app start)
await piezas.defineEntities({
  contact: {
    fields: {
      email: { type: 'email', required: true },
      phone: { type: 'phone' },
      status: { type: 'select', options: ['lead', 'active', 'inactive'] },
    },
  },
});

// Define a pipeline
await piezas.definePipeline('deals', {
  stages: ['New Lead', 'Contacted', 'Proposal', 'Won', 'Lost'],
  winStages: ['Won'],
  lossStages: ['Lost'],
});

// Dynamic accessors are created from your definitions
const contact = await piezas.contacts.create({
  title: 'Jane Smith',
  data: { email: '[email protected]', status: 'lead' },
});

const deal = await piezas.pipeline('deals').add({
  recordId: contact.id,
  title: 'Smith deal',
  value: 50000,
});

await piezas.pipeline('deals').moveTo(deal.id, 'Contacted');
const board = await piezas.pipeline('deals').board();

Available Services

You do NOT build a backend. Piezas provides all the business logic, data storage, and infrastructure. You only build the frontend and wire it to our APIs.

| Service | What it does | API Base Path | |---------|-------------|---------------| | Entity Records | Store any business data (contacts, cases, projects, products — anything) with custom schemas, links between records, and activity timelines | /entities | | Pipeline Engine | Track items through configurable stages (sales pipeline, case stages, sprint boards — anything with columns) | /pipeline | | Notification Engine | Send emails and SMS via templates or raw content. Tracks delivery status. | /notify | | Task Engine | Create and manage tasks with assignees, priorities, due dates, and checklists | /tasks | | Calendar & Scheduling | Manage calendars, availability, and bookings | /calendar | | Template & Messaging | Drip sequences, bulk campaigns, multi-channel messaging | /messaging | | Workflow & Automation | Rules engine, durable jobs, retries, and deferred processing | /workflow | | Form Builder | Create forms, handle submissions, public embeds | /forms | | Document Repository | File storage, versioning, articles, folders | /documents | | Reporting & Analytics | Reports, dashboards, snapshots | /reporting | | Line Items & Pricing | Catalogs, quotes, invoices, line items | /pricing | | Discussion | Threaded conversations, channels, mentions, reactions | /discussion | | Knowledge Base | Document ingestion, semantic search, and AI Q&A | /knowledge-base | | Integrations | OAuth connections, scoped grants, normalized provider actions, and guarded proxy access | /integrations | | Admin/access | Tenant users, invite-only signup, app registry, public sessions, durable access logs, and audit events | /admin |

The SDK wraps all public Piezas services with typed clients. Use the live OpenAPI specs to verify less common request/response details and provider-specific action payloads.


SDK Reference

Entity Operations

Store any business data. Define the schema, get typed accessors.

// Define entity types (idempotent — safe to call on every start)
await piezas.defineEntities({
  contact: {
    fields: {
      email: { type: 'email', required: true },
      phone: { type: 'phone' },
      status: { type: 'select', options: ['active', 'lead'] },
      tags: { type: 'tags' },
      birthday: { type: 'date' },
      revenue: { type: 'currency' },
    },
  },
});

// CRUD
const record = await piezas.contacts.create({ title: 'Name', data: { email: '[email protected]' } });
const list = await piezas.contacts.list({ limit: 50, search: 'john' });
// list = { data: [...], total: 123, limit: 50, offset: 0 }

// Filter by data fields (server-side JSONB filtering)
const leads = await piezas.contacts.list({ filter: { status: 'lead', source: 'website' } });
const search = await piezas.contacts.search({ q: 'acme', limit: 10 });
const csv = await piezas.contacts.exportCsv({ status: 'active' });

const one = await piezas.contacts.get(id);
await piezas.contacts.update(id, { data: { status: 'active' } });
await piezas.contacts.delete(id);

// Activities (timeline)
await piezas.contacts.logActivity(id, { type: 'note', content: { text: 'Called them' } });
const activities = await piezas.contacts.activities(id);

// Links (relationships between records)
await piezas.link(contactId, propertyId, 'interested_in');
const links = await piezas.contacts.links(id);

Available Field Types

| Type | JSON Schema | Example | |------|------------|---------| | text | string | Name, address | | email | string (format: email) | Email address | | phone | string | Phone number | | url | string (format: uri) | Website | | number | number | Quantity | | integer | integer | Count | | currency | number | Price, revenue | | percent | number (0-100) | Commission rate | | date | string (format: date) | Birthday | | datetime | string (format: date-time) | Appointment time | | boolean | boolean | Active/inactive | | select | string (enum) | Status dropdown | | multiselect | array of strings | Multiple tags | | tags | array of strings | Labels |

Pipeline Operations

Kanban boards, sales funnels, case stages — anything with columns.

// Define (idempotent)
await piezas.definePipeline('deals', {
  stages: ['New', 'Contacted', 'Proposal', 'Won', 'Lost'],
  winStages: ['Won'],
  lossStages: ['Lost'],
});

// Use
const item = await piezas.pipeline('deals').add({
  recordId: entityId,
  title: 'Deal name',
  value: 50000,
});
await piezas.pipeline('deals').moveTo(item.id, 'Proposal');
const board = await piezas.pipeline('deals').board();
// board.stages = [{ id, name, order, items: [...] }]
const metrics = await piezas.pipeline('deals').metrics();
const history = await piezas.pipeline('deals').history(item.id);

Task Operations

const task = await piezas.tasks.create({
  title: 'Follow up with client',
  priority: 'high',
  dueDate: '2026-05-01',
  assignee: '[email protected]',
  linkedRecordId: contactId,
});
const tasks = await piezas.tasks.list({ status: 'todo', limit: 50 });
await piezas.tasks.complete(task.id);

Calendar Operations

const calendar = await piezas.calendar.createCalendar({
  name: 'Team bookings',
  timezone: 'America/Vancouver',
});

await piezas.calendar.createAvailabilityRule({
  calendarId: calendar.id,
  ruleType: 'recurring',
  daysOfWeek: [1, 2, 3, 4, 5],
  startTime: '09:00',
  endTime: '17:00',
});

const slots = await piezas.calendar.getAvailableSlots({
  calendarId: calendar.id,
  dateFrom: '2026-05-25T00:00:00Z',
  dateTo: '2026-05-26T00:00:00Z',
  slotDuration: 30,
});

await piezas.calendar.createBooking({
  calendarId: calendar.id,
  title: 'Intro call',
  startTime: slots[0].start,
  endTime: slots[0].end,
  requireAvailableSlot: true,
});

Workflow Jobs

Use durable jobs when the app needs retries, reminders, imports, sync work, or deferred processing without creating a local queue/database.

const job = await piezas.workflow.enqueueJob({
  type: 'booking.reminder',
  payload: { bookingId },
  runAt: '2026-05-25T16:00:00Z',
  dedupeKey: `booking-reminder:${bookingId}`,
});

const jobs = await piezas.workflow.claimJobs({ workerId: 'worker-1', limit: 10 });
await piezas.workflow.completeJob(job.id, { sent: true });

Admin/access Operations

const app = await piezas.admin.createTenantApp(tenantId, {
  slug: 'booking-portal',
  name: 'Booking portal',
  allowedOrigins: ['https://book.example.com'],
  allowedRedirectUris: ['https://book.example.com/integrations/connected'],
  integrationPolicy: {
    connectors: {
      google_calendar: { purposes: ['calendar_availability'] },
      zoom: { purposes: ['meeting_links'] },
    },
  },
});

const invite = await piezas.admin.createTenantInvite(tenantId, {
  email: '[email protected]',
  role: 'member',
});

const publicSession = await piezas.admin.createPublicSession(tenantId, {
  resourceType: 'booking_page',
  resourceId: bookingPageId,
  scopes: ['booking:create'],
  expiresInSeconds: 60 * 60,
});

await piezas.admin.createAuditEvent(tenantId, {
  action: 'booking_page.public_session_created',
  resourceType: 'booking_page',
  resourceId: bookingPageId,
});

const accessLogs = await piezas.admin.listAccessLogs(tenantId, {
  appId: app.slug,
  serviceName: 'integrations',
  limit: 50,
});

Thin Server Adapter

For static frontends with a small BFF/Lambda/API route layer, keep PIEZAS_API_KEY server-side and use an allowlisted adapter instead of exposing a generic proxy.

import { createPiezasServerAdapter } from '@piezas/sdk';

const adapter = createPiezasServerAdapter({
  apiKey: process.env.PIEZAS_API_KEY!,
  rules: [
    { service: 'entities', methods: ['GET', 'POST'], path: /^\/v1\/records/ },
    { service: 'calendar', method: 'GET', path: '/v1/public/slots' },
  ],
});

export async function POST(request: Request) {
  return adapter.handle(request, { service: 'entities', path: '/v1/records' });
}

MCP / Agent Tool Route

The SDK also exports piezasMcp for server-runtime apps that want to expose approved Piezas tools to coding agents. Use it from @piezas/sdk; there is no separate public MCP package required.

import { piezasMcp } from '@piezas/sdk';

export const runtime = 'nodejs';
export const dynamic = 'force-dynamic';

const handler = piezasMcp({
  entitiesUrl: process.env.PIEZAS_ENTITIES_URL || 'https://api.piezas.ai/entities',
  pipelineUrl: process.env.PIEZAS_PIPELINE_URL || 'https://api.piezas.ai/pipeline',
  tasksUrl: process.env.PIEZAS_TASKS_URL || 'https://api.piezas.ai/tasks',
});

function requireMcpAccess(request: Request) {
  const hasAppSession = Boolean(request.headers.get('authorization') || request.headers.get('cookie'));
  const hasTenantContext = Boolean(request.headers.get('x-tenant-id') && request.headers.get('x-user-id'));
  if (hasAppSession && hasTenantContext) return null;

  return Response.json(
    { error: 'MCP route requires app auth plus X-Tenant-Id and X-User-Id.' },
    { status: 401 },
  );
}

export async function POST(request: Request) {
  // Replace this guard with your app session/auth check before public use.
  const denied = requireMcpAccess(request);
  if (denied) return denied;

  return handler(request);
}

Static-only apps should not host MCP routes. Put MCP behind a server adapter so secrets, tenant context, and route access control stay server-side.

Notification Operations

// Send raw email
await piezas.sendEmail({
  to: '[email protected]',
  subject: 'Your invoice is ready',
  body: '<h1>Invoice</h1><p>Amount: $5,000</p>',
});

// Define templates (idempotent)
await piezas.defineTemplates({
  'welcome-email': {
    subject: 'Welcome to {{company}}',
    body: '<h1>Hi {{name}}</h1><p>Thanks for signing up.</p>',
  },
});

// Send via template
await piezas.notify('welcome-email', {
  to: '[email protected]',
  mergeData: { name: 'John', company: 'Acme' },
});

// List sent messages
const messages = await piezas.messages.list({ status: 'delivered' });

Webhook Events

piezas.on('pipeline:deals:stage-changed', async (event) => {
  if (event.toStage === 'Won') {
    await piezas.sendEmail({
      to: event.record.data.email as string,
      subject: 'Deal closed!',
      body: `<p>Your deal for ${event.item.title} is confirmed.</p>`,
    });
  }
});

// In your Next.js webhook route: app/api/webhooks/piezas/route.ts
export const POST = piezas.webhookHandler;

Authentication

The SDK handles auth automatically. Pass your API key and it exchanges it for short-lived JWTs, refreshing as needed.

// Production — API key from app.piezas.ai
const piezas = new Piezas({
  apiKey: 'sk_live_abc123...',
  entitiesUrl: 'https://api.piezas.ai/entities',
  pipelineUrl: 'https://api.piezas.ai/pipeline',
  tasksUrl: 'https://api.piezas.ai/tasks',
  notificationsUrl: 'https://api.piezas.ai/notify',
  integrationsUrl: 'https://api.piezas.ai/integrations',
  workflowUrl: 'https://api.piezas.ai/workflow',
  calendarUrl: 'https://api.piezas.ai/calendar',
  messagingUrl: 'https://api.piezas.ai/messaging',
  formsUrl: 'https://api.piezas.ai/forms',
  documentsUrl: 'https://api.piezas.ai/documents',
  reportingUrl: 'https://api.piezas.ai/reporting',
  pricingUrl: 'https://api.piezas.ai/pricing',
  discussionUrl: 'https://api.piezas.ai/discussion',
  knowledgeBaseUrl: 'https://api.piezas.ai/knowledge-base',
  adminUrl: 'https://api.piezas.ai/admin',
});

// Development — no auth needed, pass dev headers
const piezas = new Piezas({
  devUserId: 'dev_user_1',
  devTenantId: 'dev_tenant_1',
  entitiesUrl: 'http://localhost:3001',
  pipelineUrl: 'http://localhost:3002',
  tasksUrl: 'http://localhost:3004',
  notificationsUrl: 'http://localhost:3003',
  integrationsUrl: 'http://localhost:3016',
  workflowUrl: 'http://localhost:3007',
  calendarUrl: 'http://localhost:3005',
  messagingUrl: 'http://localhost:3006',
  formsUrl: 'http://localhost:3008',
  documentsUrl: 'http://localhost:3009',
  reportingUrl: 'http://localhost:3010',
  pricingUrl: 'http://localhost:3011',
  discussionUrl: 'http://localhost:3012',
  knowledgeBaseUrl: 'http://localhost:3015',
  adminUrl: 'http://localhost:3014',
});

Integrations

Piezas owns third-party integration state. Generated apps should store connection/grant IDs only, never OAuth tokens, refresh tokens, provider client secrets, or sync cursors.

Use app-scoped integration setup when a generated app has its own domain, OAuth callback URL, provider app credentials, or permission purpose. Create a tenant app in Admin/access, save provider config with that appId and purpose in the Piezas dashboard/API, then pass the same values to OAuth and connection-list calls.

const connectors = await piezas.integrations.listConnectors();
const authUrl = await piezas.integrations.getAuthorizationUrl('google_calendar', {
  returnUrl: 'https://my-app.example.com/integrations',
  userId: 'app_user_123',
  appId: 'booking-portal',
  purpose: 'calendar_availability',
});

const connections = await piezas.integrations.listConnections({
  connector: 'google_calendar',
  userId: 'app_user_123',
  appId: 'booking-portal',
});

const grant = await piezas.integrations.createValidatedConnectionGrant(connectionId, {
  connector: 'google_calendar',
  label: 'Booking page',
  ownerUserId: 'app_user_123',
  actions: ['google_calendar.freebusy', 'google_calendar.events.create'],
});

const freeBusy = await piezas.integrations.grantAction(grant.id, 'google_calendar.freebusy', {
  timeMin: '2026-05-20T09:00:00Z',
  timeMax: '2026-05-20T17:00:00Z',
  items: [{ id: 'primary' }],
});

await piezas.integrations.grantAction(grant.id, 'google_calendar.events.create', {
  calendarId: 'primary',
  event: {
    summary: 'Intro call',
    start: { dateTime: '2026-05-20T09:00:00-07:00' },
    end: { dateTime: '2026-05-20T09:30:00-07:00' },
  },
});

Use piezas.integrations.action() only when the current authenticated Piezas actor owns the connection. Use grants when an app backend needs to run actions against an organizer-owned connection for a public workflow such as a booking page. The generated app stores the grant ID; Piezas still owns the OAuth tokens and enforces the allowed action list.

Prefer createValidatedConnectionGrant() in generated apps. It reads connector action metadata before grant creation, so a generated workflow fails early instead of assuming that a normalized provider action exists.

Use piezas.integrations.proxy() only when no normalized action exists. The proxy is connector-scoped and rejects provider hosts outside the connector's allowlist.


Relationship and Ledger Helpers

Generated apps often need shared IDs across CRM, projects, documents, finance records, and provider objects. Use the relationship helpers to keep those references consistent:

import { applyRelationshipRefs, createRecordRef } from '@piezas/sdk';

const invoiceData = applyRelationshipRefs(
  { invoiceNumber: 'INV-1001', totalMinor: 12500, currency: 'USD' },
  {
    company: createRecordRef({ id: company.id, type: 'company', title: company.title }),
    project: project.id,
  },
);
// invoiceData.companyId and invoiceData.projectId are now set.

For accounting-style workflows, keep records in Entity Records but enforce ledger invariants before writing posted entries:

import { createInvoicePosting } from '@piezas/sdk';

const entry = createInvoicePosting({
  invoiceRef: { service: 'entities', type: 'invoice', id: invoice.id },
  amountMinor: 12500,
  currency: 'USD',
  accounts: {
    accountsReceivable: '1200',
    revenue: '4000',
  },
});

These helpers are not a payments service and do not move money. They provide consistent references and accounting invariants for apps built on Piezas primitives.

For invoice, receipt, and bank-line reconciliation, score candidate matches before storing the decision as an Entity Record:

import { createReconciliationLink, suggestReconciliationMatches } from '@piezas/sdk';

const [match] = suggestReconciliationMatches(
  {
    recordRef: { type: 'bank_transaction', id: transaction.id },
    amountMinor: transaction.amountMinor,
    currency: transaction.currency,
    occurredOn: transaction.postedOn,
    reference: transaction.memo,
  },
  invoices.map((invoice) => ({
    recordRef: { type: 'invoice', id: invoice.id },
    amountMinor: invoice.totalMinor,
    currency: invoice.currency,
    occurredOn: invoice.issueDate,
    reference: invoice.invoiceNumber,
    counterparty: invoice.customerName,
  })),
);

const reconciliation = createReconciliationLink({
  source: { type: 'bank_transaction', id: transaction.id },
  match,
  status: 'suggested',
});

Use Documents for durable extraction and e-signature state before invoking provider actions:

const extraction = await piezas.documents.createExtractionJob({
  documentId,
  provider: 'aws_textract',
  requestedFields: ['invoice_number', 'vendor_name', 'total'],
});

const signature = await piezas.documents.createSignatureRequest({
  title: 'Master services agreement',
  documentId,
  provider: 'docusign',
  signers: [{ name: 'Jane Client', email: '[email protected]' }],
});

Use Workflow sync jobs and stale-lock recovery for provider imports and reconciliation workers:

await piezas.workflow.enqueueSyncJob({
  connector: 'quickbooks',
  connectionId,
  resource: 'invoices',
  direction: 'pull',
});

await piezas.workflow.requeueStaleJobs({
  before: new Date(Date.now() - 15 * 60 * 1000).toISOString(),
  reason: 'worker lock expired',
});

Building a Complete App: Step by Step

1. Create a Next.js project

npx create-next-app@latest my-crm --typescript --tailwind --app
cd my-crm
npm install @piezas/sdk lucide-react date-fns

2. Create the Piezas client singleton

// lib/api.ts
import { Piezas } from '@piezas/sdk';

let _piezas: Piezas | null = null;
let _init: Promise<void> | null = null;

async function init(piezas: Piezas) {
  await piezas.defineEntities({
    // Your data model here
  });
  await piezas.definePipeline('your-pipeline', {
    stages: ['Stage 1', 'Stage 2', 'Done'],
  });
}

export async function getPiezas() {
  if (!_piezas) {
    _piezas = new Piezas({
      apiKey: process.env.PIEZAS_API_KEY,
      entitiesUrl: process.env.PIEZAS_ENTITIES_URL,
      pipelineUrl: process.env.PIEZAS_PIPELINE_URL,
      tasksUrl: process.env.PIEZAS_TASKS_URL,
      notificationsUrl: process.env.PIEZAS_NOTIFICATIONS_URL,
      integrationsUrl: process.env.PIEZAS_INTEGRATIONS_URL,
      workflowUrl: process.env.PIEZAS_WORKFLOW_URL,
      calendarUrl: process.env.PIEZAS_CALENDAR_URL,
      messagingUrl: process.env.PIEZAS_MESSAGING_URL,
      formsUrl: process.env.PIEZAS_FORMS_URL,
      documentsUrl: process.env.PIEZAS_DOCUMENTS_URL,
      reportingUrl: process.env.PIEZAS_REPORTING_URL,
      pricingUrl: process.env.PIEZAS_PRICING_URL,
      discussionUrl: process.env.PIEZAS_DISCUSSION_URL,
      knowledgeBaseUrl: process.env.PIEZAS_KNOWLEDGE_BASE_URL,
      adminUrl: process.env.PIEZAS_ADMIN_URL,
    });
    _init = init(_piezas).catch((err) => {
      console.error('Piezas init failed:', err);
      _piezas = null;
      _init = null;
      throw err;
    });
  }
  if (_init) {
    await _init;
    _init = null;
  }
  return _piezas;
}

3. Create your .env

PIEZAS_API_KEY=sk_live_your_key_here

PIEZAS_ENTITIES_URL=https://api.piezas.ai/entities
PIEZAS_PIPELINE_URL=https://api.piezas.ai/pipeline
PIEZAS_TASKS_URL=https://api.piezas.ai/tasks
PIEZAS_NOTIFICATIONS_URL=https://api.piezas.ai/notify
PIEZAS_INTEGRATIONS_URL=https://api.piezas.ai/integrations
PIEZAS_WORKFLOW_URL=https://api.piezas.ai/workflow
PIEZAS_CALENDAR_URL=https://api.piezas.ai/calendar
PIEZAS_MESSAGING_URL=https://api.piezas.ai/messaging
PIEZAS_FORMS_URL=https://api.piezas.ai/forms
PIEZAS_DOCUMENTS_URL=https://api.piezas.ai/documents
PIEZAS_REPORTING_URL=https://api.piezas.ai/reporting
PIEZAS_PRICING_URL=https://api.piezas.ai/pricing
PIEZAS_DISCUSSION_URL=https://api.piezas.ai/discussion
PIEZAS_KNOWLEDGE_BASE_URL=https://api.piezas.ai/knowledge-base
PIEZAS_ADMIN_URL=https://api.piezas.ai/admin

4. Build pages

Use getPiezas() in Next.js server components:

// app/contacts/page.tsx
import { getPiezas } from '@/lib/api';

export const dynamic = 'force-dynamic'; // IMPORTANT: prevents caching

export default async function ContactsPage() {
  const piezas = await getPiezas();
  const { data: contacts, total } = await piezas.contacts.list({ limit: 50 });

  return (
    <div>
      <h1>Contacts ({total})</h1>
      {contacts.map((c) => (
        <div key={c.id}>{c.title} — {c.data.email}</div>
      ))}
    </div>
  );
}

5. Create API routes for mutations

// app/api/contacts/route.ts
import { NextResponse } from 'next/server';
import { getPiezas } from '@/lib/api';

export async function POST(request: Request) {
  const body = await request.json();
  const piezas = await getPiezas();
  const contact = await piezas.contacts.create({
    title: body.name,
    data: { email: body.email, phone: body.phone },
  });
  return NextResponse.json(contact, { status: 201 });
}

Important Notes

Next.js Caching

Next.js 14+ aggressively caches fetch() responses. The SDK sets cache: 'no-store' on all requests, but you MUST also add this to every server component that fetches data:

export const dynamic = 'force-dynamic';

Entity list() returns paginated data

const result = await piezas.contacts.list({ limit: 50 });
// result = { data: [...], total: 123, limit: 50, offset: 0 }
// NOT a plain array — access result.data for the records

Multi-tenancy

Every request is scoped to a tenant_id. Different tenants see completely isolated data. In dev mode, set via devTenantId. In production, it comes from the API key.

Error Handling

import { notFound } from 'next/navigation';

let contact;
try {
  contact = await piezas.contacts.get(id);
} catch {
  notFound();
}

For AI Coding Agents

Option A: One-command setup (recommended)

npx @piezas/cli init

This installs @piezas/sdk and writes agent instructions for Claude Code, Cursor, Codex, and Windsurf: CLAUDE.md, .claude/commands/piezas.md, .cursor/rules/piezas.mdc, AGENTS.md, .cursorrules, and .windsurfrules. In Claude Code, /project:piezas loads the generated slash command.

For server-runtime apps that need MCP access for approved agents, run:

npx @piezas/cli init --mode next-bff --mcp

This scaffolds a protected app/api/piezas/mcp/route.ts that imports piezasMcp from @piezas/sdk.

Option B: Manual setup

Add this to your project's CLAUDE.md, .cursor/rules/piezas.mdc, AGENTS.md, or other agent instructions:

## Backend — Piezas by Softmax Data

This app uses Piezas for business backend services.
Do NOT create your own database for Piezas-backed business data.
Use app routes only as thin glue for secrets, auth, UI-specific orchestration, and direct REST calls to Piezas.

### SDK

Install: `npm install @piezas/sdk`
Import: `import { Piezas } from '@piezas/sdk'`

### API Base URL

All services are at: https://api.piezas.ai
- Entity Records: https://api.piezas.ai/entities/v1/...
- Pipeline: https://api.piezas.ai/pipeline/v1/...
- Tasks: https://api.piezas.ai/tasks/v1/...
- Notifications: https://api.piezas.ai/notify/v1/...
- Integrations: https://api.piezas.ai/integrations/v1/...
- Calendar: https://api.piezas.ai/calendar/v1/...
- Workflow: https://api.piezas.ai/workflow/v1/...
- Messaging: https://api.piezas.ai/messaging/v1/...
- Forms: https://api.piezas.ai/forms/v1/...
- Documents: https://api.piezas.ai/documents/v1/...
- Reporting: https://api.piezas.ai/reporting/v1/...
- Pricing: https://api.piezas.ai/pricing/v1/...
- Discussion: https://api.piezas.ai/discussion/v1/...
- Knowledge Base: https://api.piezas.ai/knowledge-base/v1/...
- Admin/access: https://api.piezas.ai/admin/v1/...

### OpenAPI Specs (read these to understand each API)

- Entity Records: https://api.piezas.ai/entities/openapi.json
- Pipeline Engine: https://api.piezas.ai/pipeline/openapi.json
- Task Engine: https://api.piezas.ai/tasks/openapi.json
- Notifications: https://api.piezas.ai/notify/openapi.json
- Calendar: https://api.piezas.ai/calendar/openapi.json
- Messaging: https://api.piezas.ai/messaging/openapi.json
- Workflow: https://api.piezas.ai/workflow/openapi.json
- Forms: https://api.piezas.ai/forms/openapi.json
- Documents: https://api.piezas.ai/documents/openapi.json
- Reporting: https://api.piezas.ai/reporting/openapi.json
- Pricing: https://api.piezas.ai/pricing/openapi.json
- Discussion: https://api.piezas.ai/discussion/openapi.json
- Knowledge Base: https://api.piezas.ai/knowledge-base/openapi.json
- Integrations: https://api.piezas.ai/integrations/openapi.json
- Admin/access: https://api.piezas.ai/admin/openapi.json

### Key Concepts

1. Entity Records is the core — any business data (contacts, cases, products) is stored as entity types + records
2. Pipeline is for stages/boards — sales funnel, Kanban, sprint board
3. Everything is multi-tenant — your API key scopes all data
4. defineEntities() and definePipeline() are idempotent — call on every app start
5. Activities = timeline — log calls, emails, notes on any record
6. Links = relationships — connect records to each other

### Rules

1. Do NOT create your own database or backend API routes for business data
2. Use `@piezas/sdk` for Entity Records, Pipeline, Tasks, Notifications, Integrations, Workflow jobs, Calendar, Admin/access, Messaging, Forms, Documents, Reporting, Pricing, Discussion, and Knowledge Base
3. Read OpenAPI specs before direct REST calls or provider action payloads
4. Entity types are defined via piezas.defineEntities() — idempotent
5. Pipelines are defined via piezas.definePipeline() — idempotent
6. If MCP is needed, use `piezasMcp` from `@piezas/sdk` on a protected server route; do not add MCP routes to static-only apps
7. All setup must be idempotent (safe to re-run on every start)

OpenAPI Specs

Full OpenAPI 3.1.0 specs are available at these live URLs. Give them to your AI coding agent so it understands the full API surface:

| Service | OpenAPI Spec URL | |---------|-----------------| | Entity Records | https://api.piezas.ai/entities/openapi.json | | Pipeline Engine | https://api.piezas.ai/pipeline/openapi.json | | Task Engine | https://api.piezas.ai/tasks/openapi.json | | Notification Engine | https://api.piezas.ai/notify/openapi.json | | Calendar | https://api.piezas.ai/calendar/openapi.json | | Messaging | https://api.piezas.ai/messaging/openapi.json | | Workflow | https://api.piezas.ai/workflow/openapi.json | | Forms | https://api.piezas.ai/forms/openapi.json | | Documents | https://api.piezas.ai/documents/openapi.json | | Reporting | https://api.piezas.ai/reporting/openapi.json | | Pricing | https://api.piezas.ai/pricing/openapi.json | | Discussion | https://api.piezas.ai/discussion/openapi.json | | Knowledge Base | https://api.piezas.ai/knowledge-base/openapi.json | | Integrations | https://api.piezas.ai/integrations/openapi.json |

API Endpoints (Production)

All services are live at https://api.piezas.ai:

https://api.piezas.ai/entities/v1/...        → Entity Records
https://api.piezas.ai/pipeline/v1/...        → Pipeline Engine
https://api.piezas.ai/notify/v1/...          → Notification Engine
https://api.piezas.ai/tasks/v1/...           → Task Engine
https://api.piezas.ai/calendar/v1/...        → Calendar & Scheduling
https://api.piezas.ai/messaging/v1/...       → Template & Messaging
https://api.piezas.ai/workflow/v1/...        → Workflow & Automation
https://api.piezas.ai/forms/v1/...           → Form Builder
https://api.piezas.ai/documents/v1/...       → Document Repository
https://api.piezas.ai/reporting/v1/...       → Reporting & Analytics
https://api.piezas.ai/pricing/v1/...         → Line Items & Pricing
https://api.piezas.ai/discussion/v1/...      → Discussion & Activity Feed
https://api.piezas.ai/knowledge-base/v1/...  → Knowledge Base
https://api.piezas.ai/integrations/v1/...    → Integrations

Health checks: https://api.piezas.ai/{service}/health


Examples

We've built three complete apps on Piezas with app-specific code focused on UI, workflow, and light orchestration:

| App | What it is | |-----|-----------| | Pacific Ridge Realty CRM | Real estate brokerage CRM: contacts, properties, deal pipeline, tasks, email notifications | | Chen & Associates | Personal injury law firm: leads, cases, statute of limitations tracking, document management, referral tracking | | Softmax Projects | JIRA replacement: projects, tickets with keys (FWD-42), Kanban board, sprints, time tracking, billing, team management |

Getting Started

  1. Sign up at app.piezas.ai
  2. Go to API Keys and create a key
  3. Run npx @piezas/cli init in your project — this installs the SDK and writes Claude Code, Cursor, Codex, and Windsurf instruction files. Use npx @piezas/cli init --mode next-bff --mcp for server apps that should expose an SDK-backed MCP route.
  4. Open your coding agent from that project folder
  5. Tell the agent what to build — it reads the OpenAPI specs and uses the SDK automatically

License

Proprietary. See Terms of Service.


Built by Softmax Data in Vancouver, Canada.