gufi-cli 0.1.50 → 0.1.52
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/dist/commands/docs.js +1 -5
- package/dist/index.js +1 -0
- package/dist/lib/docs-resolver.d.ts +8 -0
- package/dist/lib/docs-resolver.js +27 -0
- package/dist/mcp.d.ts +3 -1
- package/dist/mcp.js +232 -34
- package/docs/dev-guide/1-01-architecture.md +358 -0
- package/docs/dev-guide/1-02-multi-tenant.md +415 -0
- package/docs/dev-guide/1-03-column-types.md +594 -0
- package/docs/dev-guide/1-04-json-config.md +442 -0
- package/docs/dev-guide/1-05-authentication.md +427 -0
- package/docs/dev-guide/2-01-api-reference.md +564 -0
- package/docs/dev-guide/2-02-automations.md +508 -0
- package/docs/dev-guide/2-03-gufi-cli.md +568 -0
- package/docs/dev-guide/2-04-realtime.md +401 -0
- package/docs/dev-guide/2-05-permissions.md +497 -0
- package/docs/dev-guide/2-06-integrations-overview.md +104 -0
- package/docs/dev-guide/2-07-stripe.md +173 -0
- package/docs/dev-guide/2-08-nayax.md +297 -0
- package/docs/dev-guide/2-09-ourvend.md +226 -0
- package/docs/dev-guide/2-10-tns.md +177 -0
- package/docs/dev-guide/2-11-custom-http.md +268 -0
- package/docs/dev-guide/3-01-custom-views.md +555 -0
- package/docs/dev-guide/3-02-webhooks-api.md +446 -0
- package/docs/mcp/00-overview.md +329 -0
- package/docs/mcp/01-architecture.md +220 -0
- package/docs/mcp/02-modules.md +285 -0
- package/docs/mcp/03-fields.md +357 -0
- package/docs/mcp/04-views.md +613 -0
- package/docs/mcp/05-automations.md +461 -0
- package/docs/mcp/06-api.md +480 -0
- package/docs/mcp/07-packages.md +246 -0
- package/docs/mcp/08-common-errors.md +284 -0
- package/docs/mcp/09-examples.md +453 -0
- package/docs/mcp/README.md +71 -0
- package/docs/mcp/tool-descriptions.json +49 -0
- package/package.json +3 -2
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: webhooks-api
|
|
3
|
+
title: "Webhooks & External APIs"
|
|
4
|
+
description: "Integrate Gufi with external systems"
|
|
5
|
+
icon: Wifi
|
|
6
|
+
category: dev
|
|
7
|
+
part: 3
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Webhooks & External APIs
|
|
11
|
+
|
|
12
|
+
Integrate Gufi with external systems
|
|
13
|
+
|
|
14
|
+
## Table Webhooks
|
|
15
|
+
|
|
16
|
+
Expose any entity as a REST API with token-based authentication.
|
|
17
|
+
|
|
18
|
+
### Enabling Webhooks
|
|
19
|
+
|
|
20
|
+
Add to entity configuration in module JSON:
|
|
21
|
+
|
|
22
|
+
```json
|
|
23
|
+
{
|
|
24
|
+
"name": "products",
|
|
25
|
+
"webhook": {
|
|
26
|
+
"enabled": true
|
|
27
|
+
},
|
|
28
|
+
"fields": [...]
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Generating Tokens
|
|
33
|
+
|
|
34
|
+
Once enabled, users with Admin or Consultant role see an "API" button. Clicking it generates a unique token for that entity.
|
|
35
|
+
|
|
36
|
+
### Webhook Endpoints
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
Base URL: https://api.gogufi.com/api/webhook/e/{entityId}/{token}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
#### List Records
|
|
43
|
+
|
|
44
|
+
```http
|
|
45
|
+
GET /webhook/e/4589/abc123token
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**Query Parameters:**
|
|
49
|
+
| Param | Description |
|
|
50
|
+
|---|---|
|
|
51
|
+
| page | Page number |
|
|
52
|
+
| pageSize | Records per page |
|
|
53
|
+
| sort | Sort field |
|
|
54
|
+
| order | asc or desc |
|
|
55
|
+
| filter | JSON filter object |
|
|
56
|
+
|
|
57
|
+
**Response:**
|
|
58
|
+
```json
|
|
59
|
+
{
|
|
60
|
+
"data": [...],
|
|
61
|
+
"meta": { "total": 100, "page": 1 }
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
#### Get Single Record
|
|
66
|
+
|
|
67
|
+
```http
|
|
68
|
+
GET /webhook/e/4589/abc123token/42
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
#### Create Record
|
|
72
|
+
|
|
73
|
+
```http
|
|
74
|
+
POST /webhook/e/4589/abc123token
|
|
75
|
+
Content-Type: application/json
|
|
76
|
+
|
|
77
|
+
{
|
|
78
|
+
"name": "New Product",
|
|
79
|
+
"price": { "currency": "EUR", "amount": 99.99 }
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
#### Update Record
|
|
84
|
+
|
|
85
|
+
```http
|
|
86
|
+
PUT /webhook/e/4589/abc123token/42
|
|
87
|
+
Content-Type: application/json
|
|
88
|
+
|
|
89
|
+
{
|
|
90
|
+
"stock": 50
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
#### Delete Record
|
|
95
|
+
|
|
96
|
+
```http
|
|
97
|
+
DELETE /webhook/e/4589/abc123token/42
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Security
|
|
101
|
+
|
|
102
|
+
- Token is unique per entity per company
|
|
103
|
+
- Regenerate token to revoke old access
|
|
104
|
+
- Tokens never expire (regenerate to rotate)
|
|
105
|
+
- HTTPS only
|
|
106
|
+
- Rate limited (100 req/min)
|
|
107
|
+
|
|
108
|
+
## API Keys
|
|
109
|
+
|
|
110
|
+
For authenticated access without user login.
|
|
111
|
+
|
|
112
|
+
### Creating API Keys
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
# Via CLI
|
|
116
|
+
gufi apikeys -c 146
|
|
117
|
+
gufi apikey:create "My Integration" -c 146
|
|
118
|
+
|
|
119
|
+
# Via API (admin only)
|
|
120
|
+
POST /api/keys
|
|
121
|
+
{
|
|
122
|
+
"name": "My Integration",
|
|
123
|
+
"company_id": 146,
|
|
124
|
+
"permissions": ["read", "write"]
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Using API Keys
|
|
129
|
+
|
|
130
|
+
```http
|
|
131
|
+
GET /api/tables/products
|
|
132
|
+
Authorization: Bearer api_key_xxxxx
|
|
133
|
+
X-Company-ID: 146
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Key Permissions
|
|
137
|
+
|
|
138
|
+
| Permission | Access |
|
|
139
|
+
|---|---|
|
|
140
|
+
| read | GET operations |
|
|
141
|
+
| write | POST, PUT operations |
|
|
142
|
+
| delete | DELETE operations |
|
|
143
|
+
| admin | All operations |
|
|
144
|
+
|
|
145
|
+
## Outbound Webhooks
|
|
146
|
+
|
|
147
|
+
Send data to external systems when events occur.
|
|
148
|
+
|
|
149
|
+
### Via Automations
|
|
150
|
+
|
|
151
|
+
```javascript
|
|
152
|
+
async function send_to_webhook(gufi) {
|
|
153
|
+
const { row, env } = gufi.context;
|
|
154
|
+
|
|
155
|
+
await gufi.http({
|
|
156
|
+
method: 'POST',
|
|
157
|
+
url: env.WEBHOOK_URL,
|
|
158
|
+
headers: {
|
|
159
|
+
'Authorization': 'Bearer ' + env.WEBHOOK_SECRET,
|
|
160
|
+
'Content-Type': 'application/json'
|
|
161
|
+
},
|
|
162
|
+
body: {
|
|
163
|
+
event: 'order.created',
|
|
164
|
+
data: row,
|
|
165
|
+
timestamp: new Date().toISOString()
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
gufi.logger.info('Webhook sent', { orderId: row.id });
|
|
170
|
+
return { success: true };
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Attach to Entity
|
|
175
|
+
|
|
176
|
+
```json
|
|
177
|
+
{
|
|
178
|
+
"trigger": "insert",
|
|
179
|
+
"function_name": "send_to_webhook",
|
|
180
|
+
"enabled": true
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## External API Integration
|
|
185
|
+
|
|
186
|
+
### From Automations
|
|
187
|
+
|
|
188
|
+
```javascript
|
|
189
|
+
async function sync_to_erp(gufi) {
|
|
190
|
+
const { row, env } = gufi.context;
|
|
191
|
+
|
|
192
|
+
try {
|
|
193
|
+
// Authenticate with external API
|
|
194
|
+
const authResponse = await gufi.http({
|
|
195
|
+
method: 'POST',
|
|
196
|
+
url: env.ERP_URL + '/auth/token',
|
|
197
|
+
body: {
|
|
198
|
+
client_id: env.ERP_CLIENT_ID,
|
|
199
|
+
client_secret: env.ERP_CLIENT_SECRET
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
const token = authResponse.body.access_token;
|
|
204
|
+
|
|
205
|
+
// Send data
|
|
206
|
+
const response = await gufi.http({
|
|
207
|
+
method: 'POST',
|
|
208
|
+
url: env.ERP_URL + '/orders',
|
|
209
|
+
headers: {
|
|
210
|
+
'Authorization': 'Bearer ' + token
|
|
211
|
+
},
|
|
212
|
+
body: {
|
|
213
|
+
external_id: row.id,
|
|
214
|
+
customer: row.customer_name,
|
|
215
|
+
total: row.total.amount
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
gufi.logger.info('Synced to ERP', {
|
|
220
|
+
gufiId: row.id,
|
|
221
|
+
erpId: response.body.id
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
return { success: true, erpId: response.body.id };
|
|
225
|
+
|
|
226
|
+
} catch (error) {
|
|
227
|
+
gufi.logger.error('ERP sync failed', { error: error.message });
|
|
228
|
+
throw error;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### From Custom Views
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
// In a custom view component
|
|
237
|
+
const syncToExternal = async () => {
|
|
238
|
+
// 💜 Usar gufi.automation() - Simple y directo
|
|
239
|
+
const result = await gufi.automation('sync_to_external', {
|
|
240
|
+
record_id: selectedRecord.id
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
utils.toastSuccess('Sync completed');
|
|
244
|
+
};
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## Common Integration Patterns
|
|
248
|
+
|
|
249
|
+
### Zapier-Style Triggers
|
|
250
|
+
|
|
251
|
+
```javascript
|
|
252
|
+
// Send to Zapier catch hook
|
|
253
|
+
async function zapier_trigger(gufi) {
|
|
254
|
+
const { row, env } = gufi.context;
|
|
255
|
+
|
|
256
|
+
await gufi.http({
|
|
257
|
+
method: 'POST',
|
|
258
|
+
url: env.ZAPIER_WEBHOOK_URL,
|
|
259
|
+
body: row
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
return { success: true };
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### Slack Notifications
|
|
267
|
+
|
|
268
|
+
```javascript
|
|
269
|
+
async function notify_slack(gufi) {
|
|
270
|
+
const { row, env } = gufi.context;
|
|
271
|
+
|
|
272
|
+
await gufi.http({
|
|
273
|
+
method: 'POST',
|
|
274
|
+
url: env.SLACK_WEBHOOK_URL,
|
|
275
|
+
body: {
|
|
276
|
+
text: `New order #${row.id} from ${row.customer}`,
|
|
277
|
+
blocks: [
|
|
278
|
+
{
|
|
279
|
+
type: 'section',
|
|
280
|
+
text: {
|
|
281
|
+
type: 'mrkdwn',
|
|
282
|
+
text: `*New Order*\nCustomer: ${row.customer}\nTotal: €${row.total.amount}`
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
]
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
return { success: true };
|
|
290
|
+
}
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### Two-Way Sync
|
|
294
|
+
|
|
295
|
+
```javascript
|
|
296
|
+
// Pull data from external system
|
|
297
|
+
async function sync_from_external(gufi) {
|
|
298
|
+
const { env } = gufi.context;
|
|
299
|
+
|
|
300
|
+
// Get external data
|
|
301
|
+
const response = await gufi.http({
|
|
302
|
+
method: 'GET',
|
|
303
|
+
url: env.EXTERNAL_API + '/products',
|
|
304
|
+
headers: { 'Authorization': 'Bearer ' + env.EXTERNAL_KEY }
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
const externalProducts = response.body.data;
|
|
308
|
+
|
|
309
|
+
for (const product of externalProducts) {
|
|
310
|
+
// Check if exists
|
|
311
|
+
const existing = await gufi.query(
|
|
312
|
+
'SELECT id FROM products WHERE external_id = $1',
|
|
313
|
+
[product.id]
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
if (existing.length > 0) {
|
|
317
|
+
// Update
|
|
318
|
+
await gufi.query(
|
|
319
|
+
'UPDATE products SET name = $1, price = $2 WHERE external_id = $3',
|
|
320
|
+
[product.name, product.price, product.id]
|
|
321
|
+
);
|
|
322
|
+
} else {
|
|
323
|
+
// Insert
|
|
324
|
+
await gufi.query(
|
|
325
|
+
'INSERT INTO products (external_id, name, price) VALUES ($1, $2, $3)',
|
|
326
|
+
[product.id, product.name, product.price]
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
gufi.logger.info('Sync complete', { count: externalProducts.length });
|
|
332
|
+
return { success: true, synced: externalProducts.length };
|
|
333
|
+
}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
## Best Practices
|
|
337
|
+
|
|
338
|
+
### Error Handling
|
|
339
|
+
|
|
340
|
+
```javascript
|
|
341
|
+
async function robust_webhook(gufi) {
|
|
342
|
+
const maxRetries = 3;
|
|
343
|
+
let lastError;
|
|
344
|
+
|
|
345
|
+
for (let i = 0; i < maxRetries; i++) {
|
|
346
|
+
try {
|
|
347
|
+
const response = await gufi.http({
|
|
348
|
+
method: 'POST',
|
|
349
|
+
url: context.env.WEBHOOK_URL,
|
|
350
|
+
body: context.row,
|
|
351
|
+
timeout: 10000 // 10 second timeout
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
if (response.status >= 200 && response.status < 300) {
|
|
355
|
+
return { success: true };
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
throw new Error(`HTTP ${response.status}`);
|
|
359
|
+
|
|
360
|
+
} catch (error) {
|
|
361
|
+
lastError = error;
|
|
362
|
+
gufi.logger.warn(`Attempt ${i + 1} failed`, { error: error.message });
|
|
363
|
+
|
|
364
|
+
// Wait before retry (exponential backoff)
|
|
365
|
+
await new Promise(r => setTimeout(r, Math.pow(2, i) * 1000));
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
throw lastError;
|
|
370
|
+
}
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
### Rate Limiting
|
|
374
|
+
|
|
375
|
+
```javascript
|
|
376
|
+
// For bulk operations, add delays
|
|
377
|
+
async function bulk_sync(gufi) {
|
|
378
|
+
const items = await gufi.query('SELECT * FROM products');
|
|
379
|
+
|
|
380
|
+
for (let i = 0; i < items.length; i++) {
|
|
381
|
+
await gufi.http({
|
|
382
|
+
method: 'POST',
|
|
383
|
+
url: context.env.EXTERNAL_API,
|
|
384
|
+
body: items[i]
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
// Respect rate limits
|
|
388
|
+
if (i % 10 === 0 && i > 0) {
|
|
389
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
390
|
+
gufi.logger.info(`Processed ${i} items`);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
return { success: true, count: items.length };
|
|
395
|
+
}
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
### Idempotency
|
|
399
|
+
|
|
400
|
+
```javascript
|
|
401
|
+
// Use idempotency keys for safe retries
|
|
402
|
+
async function safe_create(gufi) {
|
|
403
|
+
const { row } = gufi.context;
|
|
404
|
+
const idempotencyKey = `order-${row.id}-${Date.now()}`;
|
|
405
|
+
|
|
406
|
+
await gufi.http({
|
|
407
|
+
method: 'POST',
|
|
408
|
+
url: context.env.PAYMENT_API + '/charges',
|
|
409
|
+
headers: {
|
|
410
|
+
'Idempotency-Key': idempotencyKey
|
|
411
|
+
},
|
|
412
|
+
body: {
|
|
413
|
+
amount: row.total.amount * 100,
|
|
414
|
+
currency: row.total.currency
|
|
415
|
+
}
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
return { success: true };
|
|
419
|
+
}
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
## Monitoring
|
|
423
|
+
|
|
424
|
+
### Execution Logs
|
|
425
|
+
|
|
426
|
+
```bash
|
|
427
|
+
# View recent webhook calls
|
|
428
|
+
gufi automations:executions -c 146 --script send_to_webhook --limit 20
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
### Success/Failure Metrics
|
|
432
|
+
|
|
433
|
+
Check the Gufi dashboard for:
|
|
434
|
+
|
|
435
|
+
- Execution success rate
|
|
436
|
+
- Average execution time
|
|
437
|
+
- Error trends
|
|
438
|
+
- External API response times
|
|
439
|
+
|
|
440
|
+
### Alerting
|
|
441
|
+
|
|
442
|
+
Set up alerts for:
|
|
443
|
+
|
|
444
|
+
- High failure rates (>10%)
|
|
445
|
+
- Slow external APIs (>5s)
|
|
446
|
+
- Authentication failures
|