@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/sdkQuick 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-fns2. 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/admin4. 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 recordsMulti-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 initThis 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 --mcpThis 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/... → IntegrationsHealth 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
- Sign up at app.piezas.ai
- Go to API Keys and create a key
- Run
npx @piezas/cli initin your project — this installs the SDK and writes Claude Code, Cursor, Codex, and Windsurf instruction files. Usenpx @piezas/cli init --mode next-bff --mcpfor server apps that should expose an SDK-backed MCP route. - Open your coding agent from that project folder
- 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.
