piezas 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +309 -0
- package/package.json +27 -0
- package/src/index.js +2000 -0
- package/src/templates/piezas-instructions.md +674 -0
- package/src/templates/piezas-spec.md +164 -0
|
@@ -0,0 +1,674 @@
|
|
|
1
|
+
## Backend - Piezas by Softmax Data
|
|
2
|
+
|
|
3
|
+
This app uses Piezas for business backend services. Keep the generated app focused on UI, routing, auth/session glue, workflow-specific orchestration, and calls to Piezas APIs.
|
|
4
|
+
|
|
5
|
+
### Start Here
|
|
6
|
+
|
|
7
|
+
1. Decide the deployment mode before choosing a framework or creating routes.
|
|
8
|
+
2. Read `piezas.manifest.json`; it is the machine-readable ownership/deployment source of truth.
|
|
9
|
+
3. If the user asks for `/piezas-spec`, `/pieza-spec`, `piezas-spec`, or Piezas spec mode, read `.piezas/spec-builder.md`, interview the user one question at a time, write `SPEC.md` or `specs/`, and stop. Do not implement until the user explicitly asks to code from the spec.
|
|
10
|
+
4. When coding starts, read `SPEC.md` or `specs/` before writing application code.
|
|
11
|
+
5. Use `@piezas/sdk` for Entity Records, Pipeline, Tasks, Notifications, Integrations, Workflow jobs, Calendar, Admin/access, Messaging, Forms, Documents, Reporting, Pricing, Discussion, and Knowledge Base.
|
|
12
|
+
6. Use the live OpenAPI specs to verify request/response details before calling less common endpoints or provider-specific integration actions.
|
|
13
|
+
7. Do not create custom database tables for Piezas-backed business data.
|
|
14
|
+
8. Do not store third-party OAuth access tokens, refresh tokens, provider client secrets, or sync cursors in this app.
|
|
15
|
+
9. Store Piezas IDs as references: record IDs, pipeline item IDs, connection IDs, and grant IDs.
|
|
16
|
+
10. If the app uses provider integrations, create or reuse a Piezas tenant app and pass its `appId`/`app_id` through config, OAuth, connections, grants, records, and access-log lookups.
|
|
17
|
+
11. Keep setup idempotent. `defineEntities()` and `definePipeline()` are safe to run on app startup or during seed/setup flows.
|
|
18
|
+
12. If the project has an MCP route, keep it server-side and session-protected; use `piezasMcp` from `@piezas/sdk`, not a separate MCP package.
|
|
19
|
+
13. If a required Piezas endpoint, connector, or normalized action is missing, document the gap and ask before building a parallel backend.
|
|
20
|
+
14. Run `npx piezas doctor` after generated changes and address errors before deployment.
|
|
21
|
+
|
|
22
|
+
### Spec-First Workflow
|
|
23
|
+
|
|
24
|
+
The recommended workflow is init, spec, then code:
|
|
25
|
+
|
|
26
|
+
1. `npx piezas init`
|
|
27
|
+
2. Generate a product spec:
|
|
28
|
+
- Claude Code: run `/piezas-spec [optional product idea]`.
|
|
29
|
+
- Cursor, Codex, and Windsurf: ask for `/piezas-spec`, `/pieza-spec`, or "Piezas spec mode".
|
|
30
|
+
3. In spec mode, read `.piezas/spec-builder.md`, ask one question at a time, and write `SPEC.md` for MVPs or `specs/` for larger handoffs.
|
|
31
|
+
4. Stop after writing the spec. Do not start implementation in the same mode unless the user explicitly asks to continue.
|
|
32
|
+
5. In code mode, read the finalized spec, map every backend need to Piezas services, then implement the UI/workflow layer.
|
|
33
|
+
|
|
34
|
+
### Framework Choice
|
|
35
|
+
|
|
36
|
+
Piezas is framework-agnostic. Any language or framework that can make HTTPS
|
|
37
|
+
calls works — the platform surface is plain OpenAPI, and `@piezas/sdk` is a
|
|
38
|
+
convenience for TypeScript, not a requirement. Do NOT steer the user toward a
|
|
39
|
+
specific framework. If they have a preference (including no framework at all),
|
|
40
|
+
honor it. If they ask for a recommendation, present real options neutrally —
|
|
41
|
+
e.g. Vite + a small Express/Fastify/Hono server, Remix, SvelteKit, Next.js, or
|
|
42
|
+
a non-JS stack calling the APIs directly — and let them choose. The only hard
|
|
43
|
+
requirements, regardless of stack:
|
|
44
|
+
|
|
45
|
+
1. `PIEZAS_API_KEY` stays server-side (see Security Rules).
|
|
46
|
+
2. Business data lives in Piezas, not a parallel local database.
|
|
47
|
+
|
|
48
|
+
Mode names like `next-bff` are historical labels: read `next-bff` as "frontend
|
|
49
|
+
with a small server layer" in whatever framework the user chose.
|
|
50
|
+
|
|
51
|
+
### Ownership Boundary
|
|
52
|
+
|
|
53
|
+
The app owns:
|
|
54
|
+
|
|
55
|
+
- UI and component state
|
|
56
|
+
- pages, routes, layouts, and client interactions
|
|
57
|
+
- app authentication and session glue
|
|
58
|
+
- lightweight API routes for server-side calls, secrets, and UI-specific orchestration only when the chosen deployment mode includes a server runtime
|
|
59
|
+
- access control around any app-hosted MCP route
|
|
60
|
+
- workflow-specific decisions such as which fields to show, what a booking form asks, and when to call a Piezas service
|
|
61
|
+
|
|
62
|
+
Piezas owns:
|
|
63
|
+
|
|
64
|
+
- business records and custom entity schemas
|
|
65
|
+
- pipelines, stages, boards, and stage movement
|
|
66
|
+
- tasks, assignments, due dates, and checklists
|
|
67
|
+
- notifications and message delivery records
|
|
68
|
+
- third-party integration connector definitions
|
|
69
|
+
- tenant app registry entries, allowed origins, allowed redirect URIs, and app-level integration policy
|
|
70
|
+
- app-scoped provider client configuration for separate generated apps/domains/use cases
|
|
71
|
+
- user integration connections and encrypted provider tokens
|
|
72
|
+
- scoped connection grants for public or organizer-owned workflows
|
|
73
|
+
- normalized integration actions and guarded provider proxy calls
|
|
74
|
+
- tenant users, invite-only signup, disabled users, public sessions, and audit events
|
|
75
|
+
- durable access logs for service calls across Piezas-backed apps
|
|
76
|
+
- durable background jobs and retry state
|
|
77
|
+
- normalized integration sync jobs, stale worker lock recovery, and sync cursors
|
|
78
|
+
- MCP tool discovery over approved Piezas-backed entity, pipeline, and task tools
|
|
79
|
+
- document extraction jobs and e-signature request state
|
|
80
|
+
- finance accounts, bills, bank transactions, journal entries, and reconciliation links as entity records
|
|
81
|
+
- entity search, bulk import, and JSON/CSV export
|
|
82
|
+
- public service records such as bookings, submissions, CRM/intake records, tasks, and workflow state when those records belong to a Piezas service
|
|
83
|
+
|
|
84
|
+
### Security Rules
|
|
85
|
+
|
|
86
|
+
- `PIEZAS_API_KEY` lives in the project's `.env` file (written by `npx piezas init` or downloaded from the dashboard). Read it server-side via `process.env.PIEZAS_API_KEY`; if it is missing, ask the user to run `npx piezas init --key <key>` or drop the dashboard-downloaded `.env` into the project root.
|
|
87
|
+
- Keep `PIEZAS_API_KEY` server-side only. Never expose it in browser code and never prefix it with a public-env marker (`NEXT_PUBLIC_`, `VITE_`, `PUBLIC_`, etc.).
|
|
88
|
+
- Whatever the framework, call Piezas from the server side (route handlers, server actions/loaders, API endpoints); browser code holds UI state only. (Next.js specifics, when that IS the chosen framework: server components/actions/route handlers make the calls, and tenant-specific fetches need `export const dynamic = 'force-dynamic'`.)
|
|
89
|
+
- Do not put OAuth tokens, refresh tokens, provider secrets, API keys, or sync cursors in local storage, cookies, browser state, or app database tables.
|
|
90
|
+
- If the app needs its own login, implement app login separately from integration OAuth. "Sign in with Google" is app auth; "connect Google Calendar" is provider data access through Piezas Integrations.
|
|
91
|
+
- For browser-exposed public flows, use Piezas public sessions or a thin server adapter. Do not pass the main API key to the browser.
|
|
92
|
+
- Do not hardcode one tenant-wide provider app for every product. Use the Admin/access app registry so each generated app can have its own allowed origins, callback URLs, connector purposes, and provider credentials.
|
|
93
|
+
|
|
94
|
+
### Deployment Mode Decision
|
|
95
|
+
|
|
96
|
+
Before implementation, classify the deployment target and follow the matching constraints.
|
|
97
|
+
|
|
98
|
+
#### Static Frontend Only
|
|
99
|
+
|
|
100
|
+
NOT RECOMMENDED YET: static apps depend on public guest tokens, and per-token
|
|
101
|
+
permission enforcement has not shipped platform-wide — a guest token currently
|
|
102
|
+
carries broader access than its scopes suggest. Steer the user to a
|
|
103
|
+
server-capable mode (next-bff or server-runtime) unless they explicitly accept
|
|
104
|
+
this and the app touches no sensitive data.
|
|
105
|
+
|
|
106
|
+
Examples: AWS Amplify static hosting, S3/CloudFront, GitHub Pages, Netlify static export.
|
|
107
|
+
|
|
108
|
+
Use this mode when the user says "frontend-only", "static", "S3", "static Amplify", or "no app backend".
|
|
109
|
+
|
|
110
|
+
Rules:
|
|
111
|
+
|
|
112
|
+
- Prefer Vite/React or Next.js with `output: 'export'`.
|
|
113
|
+
- Do not create `app/api/*`, `pages/api/*`, server actions, middleware, route handlers, or server components that require runtime secrets.
|
|
114
|
+
- Do not use `PIEZAS_API_KEY`, `ADMIN_PASS`, OAuth client secrets, or provider secrets in deployed frontend code.
|
|
115
|
+
- Do not add a deployed `/api/setup` endpoint. For schema setup, create a local script such as `scripts/setup-piezas.ts` that the developer runs locally with `PIEZAS_API_KEY`.
|
|
116
|
+
- Do not host an MCP route from the static frontend. If agent tool access is required, use a separate server adapter or change to a server-capable deployment mode.
|
|
117
|
+
- If the requested workflow requires a server-side secret, token exchange, webhook receiver, admin password, or provider callback, stop and ask the user to choose one of:
|
|
118
|
+
- Piezas-hosted/public capability for that workflow
|
|
119
|
+
- Amplify SSR/Next runtime
|
|
120
|
+
- a small BFF/Lambda/API route layer
|
|
121
|
+
- a different requirement
|
|
122
|
+
|
|
123
|
+
Static-only apps can safely store public UI state and Piezas reference IDs, but they cannot securely hold backend secrets.
|
|
124
|
+
|
|
125
|
+
#### Frontend With Server Runtime
|
|
126
|
+
|
|
127
|
+
Examples: Next.js on Amplify SSR, Vercel, Render, or any deployment that runs server code.
|
|
128
|
+
|
|
129
|
+
Rules:
|
|
130
|
+
|
|
131
|
+
- Server components, route handlers, and server actions are allowed for thin secret handling and UI-specific orchestration.
|
|
132
|
+
- Keep business data in Piezas, not local database tables.
|
|
133
|
+
- Keep `PIEZAS_API_KEY`, `ADMIN_PASS`, and any auth/session secrets in server-only environment variables.
|
|
134
|
+
- API routes should be small adapters around Piezas calls, not a parallel application backend.
|
|
135
|
+
- Prefer `createPiezasServerAdapter()` from `@piezas/sdk` for allowlisted proxy routes from the UI to Piezas services.
|
|
136
|
+
- If agent tool access is needed, mount `piezasMcp()` from `@piezas/sdk` on a protected POST route and pass authenticated user/tenant context through the request headers.
|
|
137
|
+
|
|
138
|
+
#### Full Custom Backend
|
|
139
|
+
|
|
140
|
+
Do not build a full custom backend unless the user explicitly asks for one after being told Piezas should own the backend service layer.
|
|
141
|
+
|
|
142
|
+
### MCP / Agent Tool Access
|
|
143
|
+
|
|
144
|
+
Piezas is MCP-ready through `@piezas/sdk`. Do not install or invent a separate MCP package unless the project explicitly requires one.
|
|
145
|
+
|
|
146
|
+
For a Next.js app with a server runtime:
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
import { piezasMcp } from '@piezas/sdk';
|
|
150
|
+
|
|
151
|
+
export const runtime = 'nodejs';
|
|
152
|
+
export const dynamic = 'force-dynamic';
|
|
153
|
+
|
|
154
|
+
const handler = piezasMcp({
|
|
155
|
+
entitiesUrl: process.env.PIEZAS_ENTITIES_URL || 'https://api.piezas.ai/entities',
|
|
156
|
+
pipelineUrl: process.env.PIEZAS_PIPELINE_URL || 'https://api.piezas.ai/pipeline',
|
|
157
|
+
tasksUrl: process.env.PIEZAS_TASKS_URL || 'https://api.piezas.ai/tasks',
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
function requireMcpAccess(req: Request) {
|
|
161
|
+
const hasAppSession = Boolean(req.headers.get('authorization') || req.headers.get('cookie'));
|
|
162
|
+
const hasTenantContext = Boolean(req.headers.get('x-tenant-id') && req.headers.get('x-user-id'));
|
|
163
|
+
if (hasAppSession && hasTenantContext) return null;
|
|
164
|
+
|
|
165
|
+
return Response.json(
|
|
166
|
+
{ error: 'MCP route requires app auth plus X-Tenant-Id and X-User-Id.' },
|
|
167
|
+
{ status: 401 },
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export async function POST(req: Request) {
|
|
172
|
+
// Replace this guard with your app session/auth check before public use.
|
|
173
|
+
const denied = requireMcpAccess(req);
|
|
174
|
+
if (denied) return denied;
|
|
175
|
+
|
|
176
|
+
return handler(req);
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
The MCP route exposes approved Piezas-backed entity, pipeline, and task tools to agents. The app still owns route access control, session validation, and tenant/user context. Static deployments cannot safely host MCP because they cannot hold server-only secrets or perform trusted access checks.
|
|
181
|
+
|
|
182
|
+
### Capability Discovery
|
|
183
|
+
|
|
184
|
+
Before implementing feature logic:
|
|
185
|
+
|
|
186
|
+
1. Read this file.
|
|
187
|
+
2. Read `piezas.manifest.json`.
|
|
188
|
+
3. Open the relevant OpenAPI specs.
|
|
189
|
+
4. For integrations, inspect available connectors and actions rather than inventing action names.
|
|
190
|
+
5. Map each requirement to a Piezas service.
|
|
191
|
+
6. Write down any missing capability as a gap before coding around it.
|
|
192
|
+
|
|
193
|
+
Current normalized provider action-backed connectors include Google Calendar, Gmail, Google Drive, Zoom, HubSpot, DocuSign, QuickBooks, and AWS Textract-compatible proxy connections. Still inspect the connector/action catalog before hardcoding action IDs.
|
|
194
|
+
|
|
195
|
+
### Install and Initialize
|
|
196
|
+
|
|
197
|
+
Install:
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
npm install @piezas/sdk
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Initialize:
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
import { Piezas } from '@piezas/sdk';
|
|
207
|
+
|
|
208
|
+
const piezas = new Piezas({
|
|
209
|
+
apiKey: process.env.PIEZAS_API_KEY,
|
|
210
|
+
entitiesUrl: 'https://api.piezas.ai/entities',
|
|
211
|
+
pipelineUrl: 'https://api.piezas.ai/pipeline',
|
|
212
|
+
tasksUrl: 'https://api.piezas.ai/tasks',
|
|
213
|
+
notificationsUrl: 'https://api.piezas.ai/notify',
|
|
214
|
+
integrationsUrl: 'https://api.piezas.ai/integrations',
|
|
215
|
+
workflowUrl: 'https://api.piezas.ai/workflow',
|
|
216
|
+
calendarUrl: 'https://api.piezas.ai/calendar',
|
|
217
|
+
messagingUrl: 'https://api.piezas.ai/messaging',
|
|
218
|
+
formsUrl: 'https://api.piezas.ai/forms',
|
|
219
|
+
documentsUrl: 'https://api.piezas.ai/documents',
|
|
220
|
+
reportingUrl: 'https://api.piezas.ai/reporting',
|
|
221
|
+
pricingUrl: 'https://api.piezas.ai/pricing',
|
|
222
|
+
discussionUrl: 'https://api.piezas.ai/discussion',
|
|
223
|
+
knowledgeBaseUrl: 'https://api.piezas.ai/knowledge-base',
|
|
224
|
+
adminUrl: 'https://api.piezas.ai/admin',
|
|
225
|
+
});
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
The SDK exchanges `sk_live_...` or `sk_test_...` API keys for short-lived service tokens automatically.
|
|
229
|
+
|
|
230
|
+
### OpenAPI Specs
|
|
231
|
+
|
|
232
|
+
Read these specs before implementing direct REST calls:
|
|
233
|
+
|
|
234
|
+
- Entity Records: https://api.piezas.ai/entities/openapi.json
|
|
235
|
+
- Pipeline Engine: https://api.piezas.ai/pipeline/openapi.json
|
|
236
|
+
- Task Engine: https://api.piezas.ai/tasks/openapi.json
|
|
237
|
+
- Notifications: https://api.piezas.ai/notify/openapi.json
|
|
238
|
+
- Calendar: https://api.piezas.ai/calendar/openapi.json
|
|
239
|
+
- Messaging: https://api.piezas.ai/messaging/openapi.json
|
|
240
|
+
- Workflow: https://api.piezas.ai/workflow/openapi.json
|
|
241
|
+
- Forms: https://api.piezas.ai/forms/openapi.json
|
|
242
|
+
- Documents: https://api.piezas.ai/documents/openapi.json
|
|
243
|
+
- Reporting: https://api.piezas.ai/reporting/openapi.json
|
|
244
|
+
- Pricing: https://api.piezas.ai/pricing/openapi.json
|
|
245
|
+
- Discussion: https://api.piezas.ai/discussion/openapi.json
|
|
246
|
+
- Knowledge Base: https://api.piezas.ai/knowledge-base/openapi.json
|
|
247
|
+
- Integrations: https://api.piezas.ai/integrations/openapi.json
|
|
248
|
+
- Admin/access: https://api.piezas.ai/admin/openapi.json
|
|
249
|
+
|
|
250
|
+
### SDK Quick Reference
|
|
251
|
+
|
|
252
|
+
Define business data once, then use generated accessors:
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
await piezas.defineEntities({
|
|
256
|
+
contact: {
|
|
257
|
+
fields: {
|
|
258
|
+
email: { type: 'email', required: true },
|
|
259
|
+
phone: { type: 'phone' },
|
|
260
|
+
status: { type: 'select', options: ['lead', 'active', 'inactive'] },
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
booking: {
|
|
264
|
+
fields: {
|
|
265
|
+
startsAt: { type: 'datetime', required: true },
|
|
266
|
+
endsAt: { type: 'datetime', required: true },
|
|
267
|
+
guestEmail: { type: 'email', required: true },
|
|
268
|
+
status: { type: 'select', options: ['confirmed', 'cancelled'] },
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
const contact = await piezas.contacts.create({
|
|
274
|
+
title: 'Jane Smith',
|
|
275
|
+
data: { email: 'jane@example.com', status: 'lead' },
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
const contacts = await piezas.contacts.list({ limit: 50, search: 'jane' });
|
|
279
|
+
const searchResults = await piezas.contacts.search({ q: 'jane', limit: 10 });
|
|
280
|
+
const csv = await piezas.contacts.exportCsv({ status: 'active' });
|
|
281
|
+
await piezas.contacts.update(contact.id, { data: { status: 'active' } });
|
|
282
|
+
await piezas.contacts.logActivity(contact.id, {
|
|
283
|
+
type: 'note',
|
|
284
|
+
content: { text: 'Followed up after demo' },
|
|
285
|
+
});
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
Define stages and use pipelines for board-style workflows:
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
await piezas.definePipeline('deals', {
|
|
292
|
+
stages: ['New', 'Contacted', 'Proposal', 'Won', 'Lost'],
|
|
293
|
+
winStages: ['Won'],
|
|
294
|
+
lossStages: ['Lost'],
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
const board = await piezas.pipeline('deals').board();
|
|
298
|
+
await piezas.pipeline('deals').moveTo(itemId, 'Proposal');
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
Use tasks for assignments and follow-ups:
|
|
302
|
+
|
|
303
|
+
```typescript
|
|
304
|
+
await piezas.tasks.create({
|
|
305
|
+
title: 'Follow up with Jane',
|
|
306
|
+
priority: 'high',
|
|
307
|
+
dueDate: '2026-05-25',
|
|
308
|
+
assigneeId: currentUser.id,
|
|
309
|
+
});
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
Use Calendar for availability and bookings:
|
|
313
|
+
|
|
314
|
+
```typescript
|
|
315
|
+
const calendar = await piezas.calendar.createCalendar({
|
|
316
|
+
name: 'Team bookings',
|
|
317
|
+
timezone: 'America/Vancouver',
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
await piezas.calendar.createAvailabilityRule({
|
|
321
|
+
calendarId: calendar.id,
|
|
322
|
+
ruleType: 'recurring',
|
|
323
|
+
daysOfWeek: [1, 2, 3, 4, 5],
|
|
324
|
+
startTime: '09:00',
|
|
325
|
+
endTime: '17:00',
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
const slots = await piezas.calendar.getAvailableSlots({
|
|
329
|
+
calendarId: calendar.id,
|
|
330
|
+
dateFrom: '2026-05-25T00:00:00Z',
|
|
331
|
+
dateTo: '2026-05-26T00:00:00Z',
|
|
332
|
+
slotDuration: 30,
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
await piezas.calendar.createBooking({
|
|
336
|
+
calendarId: calendar.id,
|
|
337
|
+
title: 'Intro call',
|
|
338
|
+
startTime: slots[0].start,
|
|
339
|
+
endTime: slots[0].end,
|
|
340
|
+
requireAvailableSlot: true,
|
|
341
|
+
});
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
Use Workflow durable jobs for background work, retries, and deferred processing:
|
|
345
|
+
|
|
346
|
+
```typescript
|
|
347
|
+
const job = await piezas.workflow.enqueueJob({
|
|
348
|
+
type: 'booking.reminder',
|
|
349
|
+
payload: { bookingId },
|
|
350
|
+
runAt: '2026-05-25T16:00:00Z',
|
|
351
|
+
dedupeKey: `booking-reminder:${bookingId}`,
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
const jobs = await piezas.workflow.claimJobs({ workerId: 'worker-1', limit: 10 });
|
|
355
|
+
|
|
356
|
+
await piezas.workflow.enqueueSyncJob({
|
|
357
|
+
connector: 'gmail',
|
|
358
|
+
connectionId,
|
|
359
|
+
resource: 'messages',
|
|
360
|
+
direction: 'pull',
|
|
361
|
+
cursor: lastCursor,
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
await piezas.workflow.requeueStaleJobs({
|
|
365
|
+
before: new Date(Date.now() - 15 * 60 * 1000).toISOString(),
|
|
366
|
+
reason: 'worker lock expired',
|
|
367
|
+
});
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
Use Admin/access for invite-only teams, public sessions, and audit events:
|
|
371
|
+
|
|
372
|
+
```typescript
|
|
373
|
+
const invite = await piezas.admin.createTenantInvite(tenantId, {
|
|
374
|
+
email: 'teammate@example.com',
|
|
375
|
+
role: 'member',
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
const publicSession = await piezas.admin.createPublicSession(tenantId, {
|
|
379
|
+
resourceType: 'booking_page',
|
|
380
|
+
resourceId: bookingPageId,
|
|
381
|
+
scopes: ['booking:create'],
|
|
382
|
+
expiresInSeconds: 60 * 60,
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
await piezas.admin.createAuditEvent(tenantId, {
|
|
386
|
+
action: 'booking_page.public_session_created',
|
|
387
|
+
resourceType: 'booking_page',
|
|
388
|
+
resourceId: bookingPageId,
|
|
389
|
+
});
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
Use the remaining service clients directly from the same `Piezas` instance:
|
|
393
|
+
|
|
394
|
+
```typescript
|
|
395
|
+
await piezas.messaging.createCampaign({ name: 'Client newsletter' });
|
|
396
|
+
await piezas.forms.createForm({ name: 'Client intake' });
|
|
397
|
+
await piezas.documents.createDocument({ name: 'Proposal', url: uploadedFileUrl, mimeType });
|
|
398
|
+
await piezas.reporting.createDashboard({ name: 'Operations' });
|
|
399
|
+
await piezas.pricing.createDocument({ type: 'invoice', title: 'INV-1001' });
|
|
400
|
+
await piezas.discussion.createThread({ title: 'Client follow-up' });
|
|
401
|
+
await piezas.knowledgeBase.search(collectionId, { query: 'refund policy' });
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
Use Documents for extraction and e-signature workflow state before provider handoff:
|
|
405
|
+
|
|
406
|
+
```typescript
|
|
407
|
+
const extraction = await piezas.documents.createExtractionJob({
|
|
408
|
+
documentId,
|
|
409
|
+
provider: 'aws_textract',
|
|
410
|
+
requestedFields: ['invoice_number', 'vendor_name', 'total', 'due_date'],
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
const signature = await piezas.documents.createSignatureRequest({
|
|
414
|
+
title: 'Master services agreement',
|
|
415
|
+
documentId,
|
|
416
|
+
provider: 'docusign',
|
|
417
|
+
signers: [{ name: 'Jane Client', email: 'jane@example.com' }],
|
|
418
|
+
});
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
Use finance/accounting pattern helpers before storing records in Entity Records:
|
|
422
|
+
|
|
423
|
+
```typescript
|
|
424
|
+
import {
|
|
425
|
+
createInvoicePosting,
|
|
426
|
+
normalizeFinanceAccount,
|
|
427
|
+
suggestReconciliationMatches,
|
|
428
|
+
} from '@piezas/sdk';
|
|
429
|
+
|
|
430
|
+
const revenue = normalizeFinanceAccount({
|
|
431
|
+
code: '4000',
|
|
432
|
+
name: 'Service Revenue',
|
|
433
|
+
type: 'revenue',
|
|
434
|
+
currency: 'USD',
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
const entry = createInvoicePosting({
|
|
438
|
+
invoiceRef: { type: 'invoice', id: invoiceId },
|
|
439
|
+
amountMinor: 12500,
|
|
440
|
+
currency: 'USD',
|
|
441
|
+
accounts: {
|
|
442
|
+
accountsReceivable: '1200',
|
|
443
|
+
revenue: revenue.code,
|
|
444
|
+
},
|
|
445
|
+
});
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
### Thin Server Adapter Pattern
|
|
449
|
+
|
|
450
|
+
For static frontends with a small BFF/Lambda/API route layer, use the SDK server adapter instead of writing an open proxy. The adapter exchanges the Piezas API key server-side, allowlists service/path/method combinations, strips caller auth, and forwards only safe headers.
|
|
451
|
+
|
|
452
|
+
```typescript
|
|
453
|
+
import { createPiezasServerAdapter } from '@piezas/sdk';
|
|
454
|
+
|
|
455
|
+
const adapter = createPiezasServerAdapter({
|
|
456
|
+
apiKey: process.env.PIEZAS_API_KEY!,
|
|
457
|
+
rules: [
|
|
458
|
+
{ service: 'entities', methods: ['GET', 'POST'], path: /^\/v1\/records/ },
|
|
459
|
+
{ service: 'calendar', method: 'GET', path: '/v1/public/slots' },
|
|
460
|
+
],
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
export async function POST(request: Request) {
|
|
464
|
+
return adapter.handle(request, { service: 'entities', path: '/v1/records' });
|
|
465
|
+
}
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
Do not expose a generic path parameter like `/api/piezas/[...path]` unless every target is checked against a narrow allowlist.
|
|
469
|
+
|
|
470
|
+
### OpenAPI Fallback Pattern
|
|
471
|
+
|
|
472
|
+
Use SDK clients first. If a less common endpoint is not yet wrapped or you need to verify exact request/response details, read the service OpenAPI spec and call Piezas from server-side code. If you need a bearer token for direct REST, use the exported token provider:
|
|
473
|
+
|
|
474
|
+
```typescript
|
|
475
|
+
import { ApiKeyTokenProvider } from '@piezas/sdk';
|
|
476
|
+
|
|
477
|
+
const tokenProvider = new ApiKeyTokenProvider(
|
|
478
|
+
process.env.PIEZAS_API_KEY!,
|
|
479
|
+
'https://api.piezas.ai/admin'
|
|
480
|
+
);
|
|
481
|
+
|
|
482
|
+
const token = await tokenProvider.getToken();
|
|
483
|
+
|
|
484
|
+
const res = await fetch('https://api.piezas.ai/calendar/v1/bookings', {
|
|
485
|
+
method: 'POST',
|
|
486
|
+
headers: {
|
|
487
|
+
Authorization: `Bearer ${token}`,
|
|
488
|
+
'Content-Type': 'application/json',
|
|
489
|
+
},
|
|
490
|
+
body: JSON.stringify(input),
|
|
491
|
+
cache: 'no-store',
|
|
492
|
+
});
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
### Integrations Pattern
|
|
496
|
+
|
|
497
|
+
Use Piezas Integrations for provider data access such as Google Calendar, Zoom, HubSpot, Slack, and similar systems. The app may render connect/status UI, but Piezas owns OAuth client config, callbacks, encrypted provider tokens, refresh, connection status, grants, normalized actions, proxy guardrails, and app-level integration policy.
|
|
498
|
+
|
|
499
|
+
Piezas integrations are app-scoped when the generated product has its own domain, callback URL, provider client credentials, or permission purpose. A booking app that needs Google Calendar access and a separate internal app that only needs Google identity should be separate tenant app records with separate allowed origins, redirect URIs, connector/purpose policy, and provider config. Store the Piezas tenant app slug/id in setup code and pass it as `appId` in SDK calls or `app_id` in REST calls.
|
|
500
|
+
|
|
501
|
+
Recommended flow:
|
|
502
|
+
|
|
503
|
+
1. List connectors and available actions.
|
|
504
|
+
2. Create or verify the Piezas tenant app through Admin/access with allowed origins, allowed redirect URIs, `authPolicy`, and `integrationPolicy`.
|
|
505
|
+
3. Save provider client credentials through Piezas Integrations using `appId` and `purpose` when the app needs its own OAuth provider app.
|
|
506
|
+
4. Start OAuth through Piezas with `appId`, `purpose`, `returnUrl`, and the app user ID.
|
|
507
|
+
5. Store returned connection IDs as references.
|
|
508
|
+
6. For public or organizer-owned workflows, create a scoped grant and store the grant ID.
|
|
509
|
+
7. Prefer `createValidatedConnectionGrant()` so connector action metadata is checked before grant creation.
|
|
510
|
+
8. Execute normalized actions first.
|
|
511
|
+
9. Use proxy only when no normalized action exists.
|
|
512
|
+
|
|
513
|
+
```typescript
|
|
514
|
+
const connectors = await piezas.integrations.listConnectors();
|
|
515
|
+
|
|
516
|
+
await piezas.admin.createTenantApp(tenantId, {
|
|
517
|
+
slug: 'booking-portal',
|
|
518
|
+
name: 'Booking portal',
|
|
519
|
+
allowedOrigins: ['https://book.example.com'],
|
|
520
|
+
allowedRedirectUris: ['https://book.example.com/integrations/connected'],
|
|
521
|
+
integrationPolicy: {
|
|
522
|
+
connectors: {
|
|
523
|
+
google_calendar: { purposes: ['calendar_availability'] },
|
|
524
|
+
zoom: { purposes: ['meeting_links'] },
|
|
525
|
+
},
|
|
526
|
+
},
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
const authUrl = await piezas.integrations.getAuthorizationUrl('google_calendar', {
|
|
530
|
+
returnUrl: appReturnUrl,
|
|
531
|
+
userId: currentUser.id,
|
|
532
|
+
appId: 'booking-portal',
|
|
533
|
+
purpose: 'calendar_availability',
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
const connections = await piezas.integrations.listConnections({
|
|
537
|
+
connector: 'google_calendar',
|
|
538
|
+
userId: currentUser.id,
|
|
539
|
+
appId: 'booking-portal',
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
const grant = await piezas.integrations.createValidatedConnectionGrant(connectionId, {
|
|
543
|
+
connector: 'google_calendar',
|
|
544
|
+
label: 'Booking page',
|
|
545
|
+
ownerUserId: currentUser.id,
|
|
546
|
+
actions: ['google_calendar.freebusy', 'google_calendar.events.create'],
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
const freeBusy = await piezas.integrations.grantAction(
|
|
550
|
+
grant.id,
|
|
551
|
+
'google_calendar.freebusy',
|
|
552
|
+
{
|
|
553
|
+
timeMin: '2026-05-20T09:00:00Z',
|
|
554
|
+
timeMax: '2026-05-20T17:00:00Z',
|
|
555
|
+
items: [{ id: 'primary' }],
|
|
556
|
+
}
|
|
557
|
+
);
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
### Public Booking / Intake Pattern
|
|
561
|
+
|
|
562
|
+
For a public booking, appointment, intake, or CRM lead-capture app:
|
|
563
|
+
|
|
564
|
+
- Entity Records: contacts, companies, leads, event types, organizer profiles, booking pages, invitee answers, booking references
|
|
565
|
+
- Calendar SDK/API: availability rules, booking records, blocked times, time windows
|
|
566
|
+
- Integrations: Google Calendar free/busy, Google Calendar event creation, Zoom meeting creation, and other provider actions when connectors expose them
|
|
567
|
+
- Notifications: booking confirmations, reminders, cancellations, reschedules
|
|
568
|
+
- Forms: custom invitee questions when the workflow needs reusable form definitions
|
|
569
|
+
- Tasks: internal follow-ups after high-value bookings
|
|
570
|
+
- Pipeline: CRM lead/opportunity stages when public submissions should become tracked sales or support work
|
|
571
|
+
|
|
572
|
+
Do not store Google/Zoom/Outlook tokens in the generated app. Store only Piezas connection IDs and grant IDs.
|
|
573
|
+
Store public visitor input as Piezas CRM/intake records, not only as UI-local state or unstructured booking notes.
|
|
574
|
+
Use the tenant app `appId` on connection and grant references so bookings, audit events, and access logs can be filtered by the generated app that owns the workflow.
|
|
575
|
+
|
|
576
|
+
Recommended UX sequence:
|
|
577
|
+
|
|
578
|
+
1. Team member signs up or signs in.
|
|
579
|
+
2. Team member connects Google Calendar before creating event types if external free/busy is required.
|
|
580
|
+
3. Team member optionally connects Zoom or another meeting provider.
|
|
581
|
+
4. Team member creates event types with duration, location mode, availability windows, work days, timezone, buffers, notice window, rolling date range, and custom questions.
|
|
582
|
+
5. Public visitor opens `/book/{slug}` or `/book/{team}/{event}`.
|
|
583
|
+
6. Visitor sees dates and slots in their local timezone.
|
|
584
|
+
7. Visitor answers custom questions and confirms.
|
|
585
|
+
8. App creates the booking through Piezas and uses Integrations to create external calendar/meeting artifacts when enabled.
|
|
586
|
+
9. Notifications send confirmation, cancellation, reschedule, and reminder messages.
|
|
587
|
+
|
|
588
|
+
Admin/team management:
|
|
589
|
+
|
|
590
|
+
- Use the Admin/access SDK/API for team members, roles, invite codes, disabled users, public sessions, and audit events.
|
|
591
|
+
- Use Piezas-backed records for app-specific organizer profiles, allowed domains, and UI preferences.
|
|
592
|
+
- Static-only apps must not use an `ADMIN_PASS` in browser code. If the user asks for `ADMIN_PASS`, require a server runtime/BFF or use a Piezas-hosted/admin-controlled mechanism.
|
|
593
|
+
- Admin pages should disable/kick users through the Admin/access API, not by deleting local database rows.
|
|
594
|
+
|
|
595
|
+
Static deployment warning:
|
|
596
|
+
|
|
597
|
+
- A public booking app can be static only if all booking, availability, auth, and integration calls can be made safely through browser-safe Piezas/public endpoints.
|
|
598
|
+
- If the app needs server-only token exchange or private admin secrets, use Amplify SSR/Next runtime or a small API layer. Do not silently add Next.js API routes to a static export app.
|
|
599
|
+
|
|
600
|
+
### Recipe Manifest Pattern
|
|
601
|
+
|
|
602
|
+
If `piezas.manifest.json` contains `recipes`, treat those recipes as requirements, not decoration. Each recipe declares which Piezas services own the backend state, which UI layer the app owns, and the expected setup order. Before coding, map the user's prompt to those recipe entries and reuse their service list.
|
|
603
|
+
|
|
604
|
+
Known recipe presets from the CLI:
|
|
605
|
+
|
|
606
|
+
- `booking-site`: public booking, availability, invitee questions, calendar/meeting integrations, reminders, and audit events
|
|
607
|
+
- `crm-project-finance`: contacts, deals, projects, tasks, invoices/receipts, ledger entries, documents, and reports
|
|
608
|
+
- `client-services-os`: client portal, cases/projects, tasks, documents, discussion, forms, appointments, and knowledge base
|
|
609
|
+
|
|
610
|
+
Do not create local product tables for a recipe-owned object unless the manifest explicitly moves ownership to the app.
|
|
611
|
+
|
|
612
|
+
### Small Business Operations Pattern
|
|
613
|
+
|
|
614
|
+
For booking, CRM, project/case tracking, invoicing, receipts, reconciliation, client documents, and basic reporting:
|
|
615
|
+
|
|
616
|
+
- Entity Records: contacts, companies, projects/cases, invoices, receipts, accounting accounts, journal entries, reconciliation links, signature requests, extraction jobs
|
|
617
|
+
- Pricing: catalog items, quotes, invoice-like documents, and line items
|
|
618
|
+
- Documents: uploaded invoices, receipts, contracts, signed files, and version history
|
|
619
|
+
- Tasks/Workflow: approval steps, reminders, reconciliation review queues, extraction jobs, report refreshes
|
|
620
|
+
- Reporting: sales, bookings, receivables, project status, and reconciliation dashboards
|
|
621
|
+
- Integrations: payment links/references, OCR or document extraction providers, e-signature providers, accounting exports, Gmail/Outlook sending, HubSpot import/export
|
|
622
|
+
|
|
623
|
+
Do not implement payment processing inside the generated app. Use provider integrations for payment links, checkout URLs, payout/status references, or accounting exports. Piezas stores the business records and provider references; the payment provider moves money.
|
|
624
|
+
|
|
625
|
+
For reconciliation, use SDK helpers such as `suggestReconciliationMatches()` and `createReconciliationLink()` before writing reconciliation records to Entity Records. For double-entry logic, use `assertBalancedJournalEntry()` before posting ledger entries.
|
|
626
|
+
|
|
627
|
+
For e-signature and invoice extraction, store request/job state in Piezas records and use Integrations for the provider call. Do not store provider access tokens or webhook secrets in the generated app.
|
|
628
|
+
|
|
629
|
+
### CRM Pattern
|
|
630
|
+
|
|
631
|
+
For a CRM:
|
|
632
|
+
|
|
633
|
+
- Entity Records: contacts, companies, deals, activities, notes, custom fields
|
|
634
|
+
- Pipeline: deal stages and board views
|
|
635
|
+
- Tasks: follow-ups, reminders, assignments
|
|
636
|
+
- Notifications/Messaging: outbound emails and sequence steps
|
|
637
|
+
- Integrations: provider-owned connections such as HubSpot, Gmail, or calendar providers when needed
|
|
638
|
+
|
|
639
|
+
### Project Tracker Pattern
|
|
640
|
+
|
|
641
|
+
For a project tracker:
|
|
642
|
+
|
|
643
|
+
- Entity Records: projects, issues, labels, releases, teams
|
|
644
|
+
- Pipeline: board columns such as Backlog, In Progress, Review, Done
|
|
645
|
+
- Tasks: assigned work and checklists
|
|
646
|
+
- Discussion: comments and threads
|
|
647
|
+
- Reporting: status dashboards and snapshots
|
|
648
|
+
|
|
649
|
+
### Implementation Workflow for Coding Agents
|
|
650
|
+
|
|
651
|
+
1. Read this file before writing app code.
|
|
652
|
+
2. Read `SPEC.md` or `specs/` if present. If no spec exists and the request is broad, run Piezas spec mode before coding.
|
|
653
|
+
3. Read `piezas.manifest.json` and keep implementation consistent with it.
|
|
654
|
+
4. Identify which Piezas services own each data/workflow need.
|
|
655
|
+
5. Define entity schemas and pipelines idempotently.
|
|
656
|
+
6. For provider integrations, create or reuse a Piezas tenant app and map each connector to an explicit purpose before starting OAuth.
|
|
657
|
+
7. Use SDK wrappers first.
|
|
658
|
+
8. Read OpenAPI specs before writing direct REST calls or provider action payloads.
|
|
659
|
+
9. Keep secrets and API keys server-side.
|
|
660
|
+
10. Build UI and workflow around Piezas references, not local backend tables.
|
|
661
|
+
11. If MCP is needed, use `piezasMcp` from `@piezas/sdk` on a protected server route; do not add an MCP route to static-only apps.
|
|
662
|
+
12. Add focused tests for the app's orchestration logic and public/server route validation.
|
|
663
|
+
13. Run `npx piezas doctor`; fix errors before claiming the app is ready.
|
|
664
|
+
14. If a required Piezas capability is missing, leave a small adapter boundary and document the missing endpoint/action instead of building a parallel backend.
|
|
665
|
+
|
|
666
|
+
### Hard No List
|
|
667
|
+
|
|
668
|
+
- No local database tables for Piezas-backed business objects.
|
|
669
|
+
- No OAuth token storage in the app.
|
|
670
|
+
- No provider callback handlers for integration OAuth unless Piezas explicitly requires a return page for UI only.
|
|
671
|
+
- No background token refresh jobs in the app.
|
|
672
|
+
- No scraping or undocumented provider API calls when a Piezas connector/action exists.
|
|
673
|
+
- No exposing `PIEZAS_API_KEY` to browser code.
|
|
674
|
+
- No public MCP route without app auth, tenant checks, and server runtime.
|