proiectro 0.2.0 → 0.3.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
@@ -10,91 +10,112 @@ Full type safety for every endpoint, request, and response.
10
10
  npm install proiectro
11
11
  ```
12
12
 
13
+ Requires Node.js >= 20 (uses native `fetch`). Works with TypeScript >= 5.0 or plain JavaScript.
14
+
13
15
  ## Quick Start
14
16
 
15
17
  ```typescript
16
- import { client, tenantOrgs, addSubtenant } from 'proiectro';
18
+ import { client, tenantOrgs } from 'proiectro';
17
19
 
18
20
  // Authenticate (baseUrl defaults to https://proiect.ro)
19
21
  client.setConfig({
20
22
  auth: () => 'your_api_key_here',
21
23
  });
22
24
 
23
- // Define your workspace
25
+ // Every workspace-scoped call takes the workspace path
24
26
  const workspace = { path: { tenant_path: 'my-workspace' } };
25
27
 
26
- // List customers
27
28
  const { data, error } = await tenantOrgs(workspace);
28
29
 
29
30
  if (data) {
30
- console.log('Customers:', data);
31
+ console.log('Customers:', data.orgs);
31
32
  }
32
33
  ```
33
34
 
34
35
  ## Authentication
35
36
 
36
- Create an API key in your workspace under **Settings > API Keys**. The SDK sends it as a Bearer token automatically.
37
+ Create an API key in your workspace under **Settings > API Keys**. The SDK sends it as a Bearer token on every request.
37
38
 
38
39
  ```typescript
39
40
  import { client } from 'proiectro';
40
41
 
41
42
  client.setConfig({
42
- auth: () => 'your_api_key_here',
43
+ auth: () => process.env.PROIECTRO_API_KEY,
43
44
  });
44
45
  ```
45
46
 
46
- The `auth` callback is called on every authenticated request. You can return the key from an environment variable, a secret manager, or any async source:
47
+ The `auth` callback runs on each request, so the key can come from an environment variable, a secret manager, or any async source.
47
48
 
48
- ```typescript
49
- client.setConfig({
50
- auth: () => process.env.PROIECTRO_API_KEY,
51
- });
52
- ```
49
+ An API key carries the scope it was created with (read-only or full CRUD, restricted to a team if you chose one). A call outside that scope returns `403`.
50
+
51
+ ## Calling conventions
53
52
 
54
- ## Workspace
53
+ Every operation is a function named after its `operationId` in camelCase, taking one options object:
55
54
 
56
- Most API operations are scoped to a workspace (tenant). Define it once and spread it into every call:
55
+ | Key | Contents |
56
+ |---|---|
57
+ | `path` | Path parameters. Nearly always includes `tenant_path` (your workspace); entity IDs go here too. |
58
+ | `body` | The JSON request body for `POST` / `PATCH` / `PUT`. |
59
+ | `query` | Query-string filters on list endpoints (`search`, `owner`, `status`, date ranges, tags/labels …). |
60
+
61
+ Every call returns `{ data, error, request, response }`. `data` is typed to the operation's `200` schema; `error` is typed to its documented error responses.
57
62
 
58
63
  ```typescript
59
64
  const workspace = { path: { tenant_path: 'my-workspace' } };
60
65
 
66
+ // path only
61
67
  await tenantOrgs(workspace);
62
- await addSubtenant({ ...workspace, body: { name: 'Acme Corp' } });
63
- await listProjects(workspace);
68
+
69
+ // path + body
70
+ await addTag({ ...workspace, body: { name: 'VIP', color: '#ff0000' } });
71
+
72
+ // path with an entity id + body
73
+ await markPaymentPaid({
74
+ path: { tenant_path: 'my-workspace', payment_id: 'payment-uuid' },
75
+ body: { paid_date: '2026-03-15' },
76
+ });
77
+
78
+ // path + query
79
+ await listProjects({ ...workspace, query: { search: 'rollout', owner: 'member-uuid' } });
64
80
  ```
65
81
 
66
- A few operations are user-scoped (notifications, invitations) and don't need a workspace path.
82
+ IDs are the `external_id` strings the API returns; dates are ISO 8601 strings (`YYYY-MM-DD` for dates, full timestamps for datetimes).
67
83
 
68
84
  ## Usage Examples
69
85
 
70
86
  ### Customers
71
87
 
72
88
  ```typescript
73
- import {
74
- tenantOrgs,
75
- addSubtenant,
76
- editSubtenant,
77
- deleteSubtenant,
78
- } from 'proiectro';
89
+ import { tenantOrgs, addSubtenant, editSubtenant, deleteSubtenant } from 'proiectro';
79
90
 
80
91
  const workspace = { path: { tenant_path: 'my-workspace' } };
81
92
 
82
93
  // List all customers
83
- const { data: customers } = await tenantOrgs(workspace);
94
+ const { data } = await tenantOrgs(workspace);
84
95
 
85
- // Create a customer
86
- const { data: created } = await addSubtenant({
96
+ // Create a customer under your workspace
97
+ await addSubtenant({
87
98
  ...workspace,
88
99
  body: {
100
+ parent_id: 'workspace-org-uuid',
89
101
  name: 'Acme Corp',
90
- parent_id: 'org-uuid',
102
+ timezone: 'Europe/Bucharest',
103
+ default_currency: 'EUR',
104
+ country: 'RO',
105
+ manager: 'member-uuid',
91
106
  },
92
107
  });
93
108
 
94
- // Update a customer
109
+ // Update a customer (send every field — this is a full update, not a patch)
95
110
  await editSubtenant({
96
111
  path: { tenant_path: 'my-workspace', subtenant_id: 'customer-uuid' },
97
- body: { name: 'Acme Corporation' },
112
+ body: {
113
+ name: 'Acme Corporation',
114
+ timezone: 'Europe/Bucharest',
115
+ default_currency: 'EUR',
116
+ country: 'RO',
117
+ manager: 'member-uuid',
118
+ },
98
119
  });
99
120
 
100
121
  // Delete a customer
@@ -103,15 +124,45 @@ await deleteSubtenant({
103
124
  });
104
125
  ```
105
126
 
127
+ ### Leads
128
+
129
+ ```typescript
130
+ import { addLead, tenantAllLeads, updateLeadStatus, markLeadConverted } from 'proiectro';
131
+
132
+ const workspace = { path: { tenant_path: 'my-workspace' } };
133
+
134
+ // Capture an inbound lead
135
+ const { data: lead } = await addLead({
136
+ ...workspace,
137
+ body: {
138
+ name: 'Jane Doe',
139
+ company_name: 'Acme Corp',
140
+ email: 'jane@acme.example',
141
+ source: 'web_form',
142
+ },
143
+ });
144
+
145
+ // List leads, filtered
146
+ await tenantAllLeads({ ...workspace, query: { status: 'new', has_email: true } });
147
+
148
+ // Move a lead through the pipeline. `updated_at` is the value you last read —
149
+ // the API rejects the write if someone else changed the lead in between.
150
+ await updateLeadStatus({
151
+ path: { tenant_path: 'my-workspace', lead_id: 'lead-uuid' },
152
+ body: { status: 'qualified', updated_at: '2026-03-15T10:00:00Z' },
153
+ });
154
+
155
+ // Convert it into a customer
156
+ await markLeadConverted({
157
+ path: { tenant_path: 'my-workspace', lead_id: 'lead-uuid' },
158
+ body: { new_org_name: 'Acme Corp', updated_at: '2026-03-15T10:05:00Z' },
159
+ });
160
+ ```
161
+
106
162
  ### Proposals
107
163
 
108
164
  ```typescript
109
- import {
110
- addProposal,
111
- editProposal,
112
- changeProposalStage,
113
- orgProposals,
114
- } from 'proiectro';
165
+ import { addProposal, orgProposals, changeProposalStage } from 'proiectro';
115
166
 
116
167
  const workspace = { path: { tenant_path: 'my-workspace' } };
117
168
 
@@ -125,37 +176,34 @@ await addProposal({
125
176
  ...workspace,
126
177
  body: {
127
178
  name: 'Q1 Consulting',
128
- parent_id: 'customer-uuid',
129
- funnel_id: 'funnel-uuid',
179
+ description: 'Discovery and rollout support',
180
+ customer: 'customer-uuid',
181
+ owner: 'member-uuid',
182
+ manager: 'member-uuid',
183
+ sales_stage: 'funnel-stage-uuid',
184
+ proposal_currency: 'EUR',
185
+ wbsconfiguration: 'wbs-configuration-uuid',
130
186
  },
131
187
  });
132
188
 
133
- // Move a proposal to the next stage
189
+ // Move a proposal to another funnel stage
134
190
  await changeProposalStage({
135
- ...workspace,
136
- body: {
137
- proposal_id: 'proposal-uuid',
138
- stage_id: 'next-stage-uuid',
139
- },
191
+ path: { tenant_path: 'my-workspace', proposal_id: 'proposal-uuid', stage_id: 'next-stage-uuid' },
192
+ body: {},
140
193
  });
141
194
  ```
142
195
 
143
196
  ### Projects & Work Items
144
197
 
145
198
  ```typescript
146
- import {
147
- listProjects,
148
- projectWorkItems,
149
- addWorkItem,
150
- updateWorkItemStatus,
151
- } from 'proiectro';
199
+ import { listProjects, projectWorkItems, addWorkItem, updateWorkItemStatus, addWorkItemComment } from 'proiectro';
152
200
 
153
201
  const workspace = { path: { tenant_path: 'my-workspace' } };
154
202
 
155
- // List all projects
156
- const { data: projects } = await listProjects(workspace);
203
+ // List projects (all filters are optional)
204
+ const { data: projects } = await listProjects({ ...workspace, query: { lifecycle_status: ['active'] } });
157
205
 
158
- // Get work items for a project
206
+ // Work items of a project
159
207
  const { data: workItems } = await projectWorkItems({
160
208
  path: { tenant_path: 'my-workspace', project_id: 'project-uuid' },
161
209
  });
@@ -166,97 +214,106 @@ await addWorkItem({
166
214
  body: {
167
215
  name: 'Design mockups',
168
216
  project_id: 'project-uuid',
169
- work_item_type_id: 'type-uuid',
217
+ work_item_type_id: 'work-item-type-uuid',
218
+ planned_start_date: '2026-03-01',
219
+ planned_finish_date: '2026-03-10',
170
220
  },
171
221
  });
172
222
 
173
- // Update work item status
223
+ // Report progress, or finish it
174
224
  await updateWorkItemStatus({
225
+ path: { tenant_path: 'my-workspace', work_item_id: 'work-item-uuid' },
226
+ body: { percent_complete: 60 },
227
+ });
228
+ await updateWorkItemStatus({
229
+ path: { tenant_path: 'my-workspace', work_item_id: 'work-item-uuid' },
230
+ body: { finished_at: '2026-03-10T16:00:00Z' },
231
+ });
232
+
233
+ // Comment on it
234
+ await addWorkItemComment({
175
235
  ...workspace,
176
- body: {
177
- work_item_id: 'work-item-uuid',
178
- status: 'in_progress',
179
- },
236
+ body: { work_item_id: 'work-item-uuid', content: 'Mockups approved by the customer.' },
180
237
  });
181
238
  ```
182
239
 
183
240
  ### Payments
184
241
 
185
242
  ```typescript
186
- import {
187
- addPayment,
188
- markPaymentPaid,
189
- } from 'proiectro';
243
+ import { addPayment, markPaymentPaid } from 'proiectro';
190
244
 
191
245
  const workspace = { path: { tenant_path: 'my-workspace' } };
192
246
 
193
- // Create a payment
247
+ // Add a payment to a proposal's payment schedule
194
248
  await addPayment({
195
249
  ...workspace,
196
250
  body: {
197
- proposal_id: 'proposal-uuid',
198
- amount: '5000.00',
199
- currency: 'EUR',
251
+ payment_schedule_block_id: 'schedule-block-uuid',
252
+ name: 'Milestone 1',
200
253
  due_date: '2026-03-15',
254
+ amount: '5000.00',
201
255
  },
202
256
  });
203
257
 
204
- // Mark a payment as paid
258
+ // Mark it paid
205
259
  await markPaymentPaid({
206
- ...workspace,
207
- body: { payment_id: 'payment-uuid' },
260
+ path: { tenant_path: 'my-workspace', payment_id: 'payment-uuid' },
261
+ body: { paid_date: '2026-03-14' },
208
262
  });
209
263
  ```
210
264
 
265
+ ### Documents
266
+
267
+ ```typescript
268
+ import { tenantDocumentTemplatesList, generateProposalDocument, tenantDocumentsList } from 'proiectro';
269
+
270
+ const workspace = { path: { tenant_path: 'my-workspace' } };
271
+
272
+ // Templates available in the workspace
273
+ const { data: templates } = await tenantDocumentTemplatesList(workspace);
274
+
275
+ // Generate a numbered document for a proposal
276
+ await generateProposalDocument({
277
+ path: { tenant_path: 'my-workspace', proposal_id: 'proposal-uuid' },
278
+ body: { template_id: 'template-uuid' },
279
+ });
280
+
281
+ // Documents, filtered
282
+ await tenantDocumentsList({ ...workspace, query: { org_id: 'customer-uuid' } });
283
+ ```
284
+
211
285
  ### Tags
212
286
 
213
287
  ```typescript
214
- import {
215
- tenantTags,
216
- addTag,
217
- tagOrg,
218
- untagOrg,
219
- } from 'proiectro';
288
+ import { tenantTags, addTag, tagOrg, untagOrg } from 'proiectro';
220
289
 
221
290
  const workspace = { path: { tenant_path: 'my-workspace' } };
222
291
 
223
- // List all tags
224
292
  const { data: tags } = await tenantTags(workspace);
225
293
 
226
- // Create a tag
227
- await addTag({
228
- ...workspace,
229
- body: { name: 'VIP', color: '#ff0000' },
230
- });
294
+ await addTag({ ...workspace, body: { name: 'VIP', color: '#ff0000' } });
231
295
 
232
- // Tag a customer
233
- await tagOrg({
234
- ...workspace,
235
- body: { org_id: 'customer-uuid', tag_id: 'tag-uuid' },
236
- });
296
+ // Tagging is addressed by path: (entity, tag)
297
+ await tagOrg({ path: { tenant_path: 'my-workspace', org_id: 'customer-uuid', tag_id: 'tag-uuid' } });
298
+ await untagOrg({ path: { tenant_path: 'my-workspace', org_id: 'customer-uuid', tag_id: 'tag-uuid' } });
237
299
  ```
238
300
 
239
301
  ### Support Requests
240
302
 
241
303
  ```typescript
242
- import {
243
- addInternalSupportRequest,
244
- mySupportRequests,
245
- } from 'proiectro';
304
+ import { addInternalSupportRequest, mySupportRequests } from 'proiectro';
246
305
 
247
306
  const workspace = { path: { tenant_path: 'my-workspace' } };
248
307
 
249
- // List my support requests
250
308
  const { data: tickets } = await mySupportRequests(workspace);
251
309
 
252
- // Create a support request
253
310
  await addInternalSupportRequest({
254
311
  ...workspace,
255
312
  body: {
256
313
  title: 'Server configuration issue',
257
314
  description: 'Need help with DNS setup',
258
- type_id: 'type-uuid',
259
- severity_id: 'severity-uuid',
315
+ request_type: 'request-type-uuid',
316
+ request_severity: 'severity-uuid',
260
317
  },
261
318
  });
262
319
  ```
@@ -264,26 +321,51 @@ await addInternalSupportRequest({
264
321
  ### Team
265
322
 
266
323
  ```typescript
267
- import {
268
- tenantTeam,
269
- inviteToTeam,
270
- } from 'proiectro';
324
+ import { tenantTeam, inviteToTeam } from 'proiectro';
271
325
 
272
326
  const workspace = { path: { tenant_path: 'my-workspace' } };
273
327
 
274
- // List team members
275
328
  const { data: team } = await tenantTeam(workspace);
276
329
 
277
- // Invite someone to the team
278
- await inviteToTeam({
330
+ await inviteToTeam({ ...workspace, body: { email: 'colleague@example.com' } });
331
+ ```
332
+
333
+ ### Webhooks
334
+
335
+ Subscribe to platform events instead of polling:
336
+
337
+ ```typescript
338
+ import { addWebhook, tenantWebhooks } from 'proiectro';
339
+
340
+ const workspace = { path: { tenant_path: 'my-workspace' } };
341
+
342
+ await addWebhook({
279
343
  ...workspace,
280
- body: { email: 'colleague@example.com' },
344
+ body: {
345
+ name: 'CRM sync',
346
+ target_url: 'https://example.com/hooks/proiectro',
347
+ event_types: ['changeProposalStage', 'markPaymentPaid'],
348
+ is_active: true,
349
+ is_secure: true,
350
+ },
281
351
  });
352
+
353
+ const { data: hooks } = await tenantWebhooks(workspace);
282
354
  ```
283
355
 
284
- ## Error Handling
356
+ ## Namespaces
357
+
358
+ | Area | Namespaces |
359
+ |---|---|
360
+ | Sales | `leads`, `proposals`, `orgs`, `rfp`, `bridge` (partnerships), `partnerportal` |
361
+ | Delivery | `projects`, `work`, `operations`, `comments`, `timers`, `booking`, `capacity`, `resources`, `holidays`, `calendar` |
362
+ | Money | `costs`, `exchange`, `analytics`, `kpis` |
363
+ | Support & assets | `support`, `assets`, `locations`, `custom_forms` |
364
+ | Documents & files | `documents`, `files`, `cdn` |
365
+ | Workspace | `tenant`, `team`, `rbac`, `tags`, `labels`, `notifications`, `feed`, `welcome`, `auth` |
366
+ | Automation | `webhooks`, `automation`, `integrations`, `portal` |
285
367
 
286
- Every SDK function returns `{ data, error, request, response }`:
368
+ ## Error Handling
287
369
 
288
370
  ```typescript
289
371
  const { data, error, response } = await tenantOrgs({
@@ -298,6 +380,14 @@ if (error) {
298
380
  }
299
381
  ```
300
382
 
383
+ Validation failures come back as `422` with a field-level detail list; a permission or feature-entitlement problem is `403`; a missing entity is `404`; a conflict with the current state (a stage transition that is not allowed, a delete blocked by dependants) is `409`.
384
+
385
+ To have the SDK throw instead of returning `error`, pass `throwOnError`:
386
+
387
+ ```typescript
388
+ const { data } = await tenantOrgs({ path: { tenant_path: 'my-workspace' }, throwOnError: true });
389
+ ```
390
+
301
391
  ## Interceptors
302
392
 
303
393
  Add request/response interceptors for logging, retries, or custom headers:
@@ -305,13 +395,11 @@ Add request/response interceptors for logging, retries, or custom headers:
305
395
  ```typescript
306
396
  import { client } from 'proiectro';
307
397
 
308
- // Log all requests
309
398
  client.interceptors.request.use((request) => {
310
399
  console.log(`${request.method} ${request.url}`);
311
400
  return request;
312
401
  });
313
402
 
314
- // Log all responses
315
403
  client.interceptors.response.use((response) => {
316
404
  console.log(`${response.status} ${response.url}`);
317
405
  return response;
@@ -320,21 +408,20 @@ client.interceptors.response.use((response) => {
320
408
 
321
409
  ## TypeScript
322
410
 
323
- Every request and response is fully typed. Your editor will autocomplete path parameters, body fields, and response shapes:
411
+ Every request and response is fully typed. Your editor autocompletes path parameters, body fields, query filters, and response shapes:
324
412
 
325
413
  ```typescript
326
- import type { OutputTenantInfo, AddSubtenantData } from 'proiectro';
414
+ import type { OutputTenantInfo, AddSubtenantData, AddLeadData } from 'proiectro';
327
415
  ```
328
416
 
329
- ## Requirements
330
-
331
- - Node.js >= 18 (uses native `fetch`)
332
- - TypeScript >= 5.0 (optional, works with plain JavaScript too)
333
-
334
417
  ## API Documentation
335
418
 
336
419
  Full API reference is available at [proiect.ro/api/v1/docs](https://proiect.ro/api/v1/docs).
337
420
 
421
+ ## Changelog
422
+
423
+ See [CHANGELOG.md](CHANGELOG.md).
424
+
338
425
  ## License
339
426
 
340
427
  MIT