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.
@@ -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.