@planescrm/sdk 0.55.0 → 0.72.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 CHANGED
@@ -44,7 +44,8 @@ Resources: `opportunities`, `customers`, `properties`, `calendarEvents`,
44
44
  `contracts`, `jobs`, `estimates`, `invoices`, `inventory`, `contacts`,
45
45
  `activities`, `communications`, `gmail`, `documents`, `tags`, `tasks`,
46
46
  `customFields`, `recordViews`, `modules`, `plan`, `me`, `roles`, `contactSequences`,
47
- `enrollments`, `workflows`, `billingSchedules`.
47
+ `enrollments`, `workflows`, `billingSchedules`, `webhookSubscriptions`, `users`,
48
+ `teams`.
48
49
 
49
50
  ### Breaking changes (0.43.0)
50
51
 
@@ -200,8 +201,11 @@ if (res.workflow_trigger.status === 'failed') {
200
201
  }
201
202
  ```
202
203
 
203
- `status` is `fired`, `skipped` (no transition set) or `failed` (with a
204
- `reason` and `message`; the tenant's admins also get a notification).
204
+ `status` is `fired`, `skipped` or `failed` (with a `reason` and `message`; the
205
+ tenant's admins also get a notification). A skip's `reason` is
206
+ `no_completion_transition` (none set) or, since 0.59.0,
207
+ `handled_by_pipeline_rule`: the record's pipeline rule moves it after the
208
+ request, so the event's own step didn't run.
205
209
  Since 0.55.0 a `fired` trigger carries `to_stage: { id, name }`, the stage the
206
210
  transition moved the record to; `tasks.complete(id)` returns the same
207
211
  `workflow_trigger`:
@@ -313,6 +317,144 @@ await client.roles.updateGrants('sales', {
313
317
  const { data, cursor } = await client.roles.history({ role: 'sales', limit: 20 });
314
318
  ```
315
319
 
320
+ ### Lists (0.64.0)
321
+
322
+ `client.lists` covers every `/lists` route: find, read, create, rename or
323
+ redefine, delete, add and remove members, read recipients, and export.
324
+ `list()` returns a bare array, newest first, each list with `member_count`.
325
+ A list with `managed_by` set (owned by an integration) refuses member and
326
+ definition changes; it can still be renamed.
327
+
328
+ ```ts
329
+ const all = await client.lists.list();
330
+ const promo = all.find((l) => l.name === 'Fall promo');
331
+
332
+ await client.lists.addMembers(promo.id, [{ entity_type: 'customer', entity_id: customerId }]);
333
+ await client.lists.removeMembers(promo.id, [{ entity_type: 'customer', entity_id: otherId }]);
334
+
335
+ const { data: people } = await client.lists.getRecipients(promo.id, { page: 1, limit: 50 });
336
+ await client.lists.update(promo.id, { name: 'Fall promo 2026' });
337
+ await client.lists.delete(promo.id); // soft delete; the records are untouched
338
+ ```
339
+
340
+ ### Custom field definitions, webhook subscriptions, users and teams (0.72.0)
341
+
342
+ **Custom field definitions** can now be configured, not just read. With the
343
+ admin grant on custom field definitions:
344
+
345
+ ```ts
346
+ const field = await client.customFields.create({
347
+ entityType: 'customer',
348
+ fieldKey: 'crm_id', // lower_snake_case; can't change later
349
+ label: 'CRM ID',
350
+ fieldType: 'text',
351
+ searchable: true,
352
+ });
353
+ await client.customFields.update(field.id, { label: 'External CRM ID' });
354
+ await client.customFields.deactivate(field.id); // hidden; stored values stay valid
355
+ ```
356
+
357
+ `listAll` and `get` read definitions through the same admin route. Definitions
358
+ can be created for contacts, customers and opportunities. Jobs, vendors and
359
+ agents get a 400 until those records can hold values.
360
+
361
+ **Webhook subscriptions** (`client.webhookSubscriptions`; the `open_api`
362
+ feature and admin grants): `create` (returns the `whsec_` signing secret once),
363
+ `list`, `get`, `update`, `delete`, `test` (sends one signed test event now),
364
+ `rotateSecret`, `listDeliveries` and `retryDelivery`. `client.webhooks` is still
365
+ the event router for verifying and handling deliveries.
366
+
367
+ **Users and teams, read-only:** `client.users.list`, `client.users.directory`
368
+ (ids, names and roles of active users, readable by every role),
369
+ `client.users.getWorkingHours`, `client.teams.list` and `client.teams.get`.
370
+ Inviting, editing and removing users, and changing teams, stay in the app.
371
+
372
+ ### Records: estimates, jobs, contracts, invoices, customers, calendar events (0.72.0)
373
+
374
+ - **Estimates:** line items (`createLineItem`, `updateLineItem`, `deleteLineItem`,
375
+ `reorderLineItems`), `updateStatus`, and pipeline moves (`getWorkflow`,
376
+ `listTransitions`, `transition`).
377
+ - **Jobs:** pipeline moves (`getWorkflow`, `listTransitions`, `transition`).
378
+ Material use is on `inventory`: `recordJobConsumption`, `listJobConsumption`,
379
+ `returnFromJob`, `getJobMaterialVariance`.
380
+ - **Contracts:** `createForSubject`, `createFlat`, `submit`, `approve`, `cancel`,
381
+ `reopen`, `send`, `createChangeOrder`, `listChangeOrders`, `downloadPdf`.
382
+ - **Invoices:** `listPayments`, `getPaymentRecordingSettings` (reads only).
383
+ - **Customers:** `convertToCompany`. **Calendar events:** `cancel`,
384
+ `addParticipant`, `updateParticipantStatus`.
385
+
386
+ `contracts.send()` emails the signer a signing link. A `transition()` runs the
387
+ actions the tenant attached to that move, which can include sending email or
388
+ an invoice. None of these take an idempotency key, so retrying after a timeout
389
+ can repeat the email or the actions.
390
+
391
+ ### Service schedules (0.71.0)
392
+
393
+ `client.serviceSchedules` now covers every `/service-schedules` route: `list`,
394
+ `get`, `create`, `update`, `previewUpdate`, `setStatus`, `delete`,
395
+ `previewConflicts`, and the dispatch settings (`getCrewRoles`, `setCrewRoles`,
396
+ `listCrew`, `setCrewHomeAddress`).
397
+
398
+ Changing a schedule's cadence, time, crew, duration or horizon regenerates the
399
+ visits already on the calendar, from today. A visit
400
+ someone changed (moved, re-crewed, confirmed or started, past its first stage,
401
+ invoiced or marked do-not-bill) is never touched; it comes back under
402
+ `protected` with its reasons.
403
+
404
+ ```ts
405
+ const edit = { dtstart: '2026-12-03T09:00:00-06:00' }; // move to Thursdays
406
+ const preview = await client.serviceSchedules.previewUpdate(scheduleId, edit);
407
+ // { removed: 8, added: 8, updated: 0, protected: [...] }
408
+ const { regeneration } = await client.serviceSchedules.update(scheduleId, edit);
409
+ ```
410
+
411
+ ### Tag merge (0.70.0)
412
+
413
+ `client.tags.merge(winnerId, loserId)` folds a duplicate tag into another: every record tagged with the loser moves to the winner, and the loser is deleted.
414
+
415
+ ### Opportunity pipeline moves (0.69.0)
416
+
417
+ ```ts
418
+ const { transitions } = await client.opportunities.listTransitions(oppId);
419
+ await client.opportunities.transition(oppId, { transition_id: transitions[0].id as string, notes: 'Quote sent' });
420
+ const workflow = await client.opportunities.getWorkflow(oppId); // stage + history
421
+ ```
422
+
423
+ A move runs the stage's automations, exactly as the stage button in Planes does
424
+ (they can send the customer a contract or an invoice).
425
+
426
+ ### Properties (0.68.0)
427
+
428
+ `client.properties` gains `getForCustomer(customerId, id)` (404 if the property
429
+ isn't that customer's) and `geocode(id)`, which queues a fresh geocode.
430
+
431
+ ### Lead sources (0.67.0)
432
+
433
+ `client.leadSources` lists, creates, updates and deletes lead sources. A
434
+ lead source's `key` is what a customer's `source` takes:
435
+
436
+ ```ts
437
+ const sources = await client.leadSources.list(); // active ones, in display order
438
+ await client.customers.update(customerId, { source: sources[0].key });
439
+ ```
440
+
441
+ ### Contact links, roles and pipeline (0.66.0)
442
+
443
+ `client.contacts.get(id)` returns a contact with its channels and active links.
444
+ The rest of the contact surface:
445
+
446
+ ```ts
447
+ await client.contacts.listLinks(contactId); // active and ended links (history)
448
+ await client.contacts.addLink(contactId, { targetType: 'customer', targetId, role: 'GM' });
449
+ await client.contacts.updateLink(contactId, linkId, { endedAt: new Date().toISOString() }); // end a link
450
+ await client.contacts.listRoles(); // roles in use, for a picker
451
+
452
+ await client.contacts.startWorkflow(contactId); // opt into the relationship pipeline
453
+ const wf = await client.contacts.getWorkflow(contactId); // stage + available transitions
454
+ await client.contacts.transition(contactId, transitionId);
455
+ await client.contacts.getWorkflowHistory(contactId);
456
+ ```
457
+
316
458
  ### Contact sequences
317
459
 
318
460
  `client.contactSequences.list()` returns the tenant's published contact