@funnelchat-hq/sandbox 0.5.0 → 0.5.2
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 +38 -14
- package/dist/{chunk-67XBJEXM.js → chunk-4ELMG644.js} +8232 -944
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/spec/procedencia.json +6 -7
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
A local server that imitates the Funnelchat public API and sends signed webhooks, with no real effects: nothing goes out to WhatsApp, nothing is charged, everything stays in memory. Build and test your integration without an account.
|
|
4
4
|
|
|
5
|
-
It comes from the spec the developer portal publishes, v0.6.
|
|
5
|
+
It comes from the spec the developer portal publishes, v0.6.16: the same operations and events (see [Pending of the spec](#pending-of-the-spec)). What it simulates with state follows production as it was checked on 06-11.
|
|
6
6
|
|
|
7
7
|
## Try it in 3 minutes, without an account
|
|
8
8
|
|
|
@@ -129,7 +129,7 @@ npx funnelchat-sandbox
|
|
|
129
129
|
```
|
|
130
130
|
|
|
131
131
|
```
|
|
132
|
-
funnelchat-sandbox 0.5.
|
|
132
|
+
funnelchat-sandbox 0.5.2 at http://127.0.0.1:4010
|
|
133
133
|
API: http://127.0.0.1:4010/api/v1/... (Authorization: Bearer 1|fc_live_sandbox, or any well-formed key)
|
|
134
134
|
panel: http://127.0.0.1:4010/_sandbox
|
|
135
135
|
example numbers: 3 (official, with the 24-hour window) and 4 (QR, no window); contact 2001; template 45
|
|
@@ -291,7 +291,31 @@ The official number (`3`) has no groups (the sandbox account is not in the offic
|
|
|
291
291
|
}
|
|
292
292
|
```
|
|
293
293
|
|
|
294
|
-
**
|
|
294
|
+
**Every error carries `error` and `message`**, the common format of the API: compare `error`, a stable code, and show `message` to people. The codes every area shares:
|
|
295
|
+
|
|
296
|
+
| Status and `error` | When |
|
|
297
|
+
|---|---|
|
|
298
|
+
| `401 unauthenticated` | The API key is missing or is not valid. |
|
|
299
|
+
| `402 payment_required` | The account's payment is overdue. The sandbox charges nothing, so it never answers it. |
|
|
300
|
+
| `403 forbidden` | The key lacks the permission. The sandbox gives every permission (see [What it does not imitate](#what-it-does-not-imitate)). |
|
|
301
|
+
| `404 not_found` | What you asked for does not exist in the account, or belongs to another account or workspace. |
|
|
302
|
+
| `422 validation_failed` | A field does not pass, with `errors` per field. |
|
|
303
|
+
| `429 rate_limited` | Too many requests, with `Retry-After` in seconds. |
|
|
304
|
+
| `502 provider_error` | WhatsApp or Meta refused or did not answer. The sandbox never answers it. |
|
|
305
|
+
|
|
306
|
+
Each operation adds its own codes (`window_closed`, `template_not_found`, `LINT`…), and a few routes keep a key of their own next to `error` and `message` (see [The rules](#the-rules)). Some the sandbox answers, and others only production:
|
|
307
|
+
|
|
308
|
+
| Status and `error` | Where | In the sandbox |
|
|
309
|
+
|---|---|---|
|
|
310
|
+
| `415 bad_request` | A send with a file of a known but not accepted extension (see [A file by URL](#a-file-by-url)). | Yes. |
|
|
311
|
+
| `422 webhooks_limit_reached` | `POST /api/v1/webhooks` with 50 subscriptions in the workspace, whatever their status. | Yes. |
|
|
312
|
+
| `402 plan_cap_reached` | `POST /api/v1/groups`, past the cap of official groups of the plan, with `feature`, `parameter`, `cap` and `used`. | No: the route answers shape only. |
|
|
313
|
+
| `400 unsupported_provider` | `PUT /api/v1/devices/{device}` on a number that does not take that change. | No. |
|
|
314
|
+
| `422 device_not_meta`, `422 device_not_ready`, `502 provider_error`, `meta_error` | The WhatsApp username of a number (`/api/v1/devices/{device}/username…`): the number is not official, is not connected to Meta yet, Meta did not answer, or Meta refused (`meta_error`, with Meta's HTTP status and its reason in `message`). | No: the routes answer shape only. |
|
|
315
|
+
| `422 number_not_connected_to_meta`, `422 meta_rejected`, `502 meta_unavailable` | `DELETE /api/v1/devices/{device}/templates/{template}`: the number is not connected to Meta, Meta refused to delete the template (with Meta's code in `meta_code`, or `null`), or Meta did not answer. | No: nothing goes to Meta, and the sandbox's templates are of official numbers, so the delete answers `204`. |
|
|
316
|
+
| `500 gate_misconfigured` | Any route, from the gate, next to `500 internal_error`. | No. |
|
|
317
|
+
|
|
318
|
+
**What does not exist in the account** answers `404 not_found` with the message of each domain: in conversations (contacts, messages, tags, fields, forms) `{"error": "not_found", "message": "Objeto no encontrado."}`; in accounts (numbers) `"Object not found"`; in communities (a group by its uid) Laravel's `"No query results for model [App\\Modules\\Shared\\Models\\Group] <uid>"`. **What is in another workspace** answers the same `404 not_found`: `{"error": "not_found", "message": "No encontramos lo que pediste en esta cuenta."}` for a contact, a message or a number, and the same `404` as one that does not exist for links, subscriptions and groups.
|
|
295
319
|
|
|
296
320
|
The complete spec the sandbox uses is at `GET /_sandbox/openapi.json`.
|
|
297
321
|
|
|
@@ -368,7 +392,7 @@ On `POST /contacts/{contact}/message` and `POST /message/send-message`, `file` i
|
|
|
368
392
|
| The URL does not say its format and you sent no `file_type` | `422 errors.file_type`: «Tu archivo viene de una URL que no indica su formato: envía file_type con uno de estos valores: image, video, audio, document o sticker.» |
|
|
369
393
|
| `file_type: "document"`, the URL does not end in an accepted extension and `file_name` with an extension is missing | `422 errors.file_name`: «Para enviar un documento desde una URL que no termina en una de las extensiones admitidas, envía file_name con su extensión, por ejemplo factura.pdf.» |
|
|
370
394
|
| `file` malformed or longer than 4096 characters | `422 errors.file` |
|
|
371
|
-
| A known but not accepted extension (`.zip`) without `file_type` | `415`: `{"error": "Unsupported file type: zip", "supported_file_types": [...]}`; on `send-message`,
|
|
395
|
+
| A known but not accepted extension (`.zip`) without `file_type` | `415`: `{"error": "bad_request", "message": "Unsupported file type: zip", "supported_file_types": [...]}`; on `send-message`, without `supported_file_types` |
|
|
372
396
|
|
|
373
397
|
`file_type` (`image`, `video`, `audio`, `document` or `sticker`) always wins over the extension. The sandbox does not download the file.
|
|
374
398
|
|
|
@@ -389,7 +413,7 @@ They apply to every send with a key, in this order, as in the API. The first one
|
|
|
389
413
|
|
|
390
414
|
An incoming message from the contact opens the window of that number and resets the brakes. The error messages are the API's. To turn a rule off: `--no-window`, `--no-antiblock`, `--no-rate-limit`.
|
|
391
415
|
|
|
392
|
-
Some routes have
|
|
416
|
+
Some routes have codes of their own, as in conversations: `send-message` answers `404 {"error": "not_found", "message": "Contacto no encontrado.", "status": "error"}` and `403 forbidden`; a template that does not exist is `404 template_not_found`, one of another number `422 template_waba_mismatch`, and one with unfilled variables `422 template_unresolved_placeholder` with `placeholders` and `count`.
|
|
393
417
|
|
|
394
418
|
### Forcing failures
|
|
395
419
|
|
|
@@ -515,7 +539,7 @@ example numbers use two; change it with `POST /_sandbox/numbers`. The rejections
|
|
|
515
539
|
`422 method_already_present` (with `device_id`).
|
|
516
540
|
|
|
517
541
|
A reconnection, and QR on a number in coexistence, take no place. Up to 20 open links (`429 too_many_open_links`)
|
|
518
|
-
and 10 creations per minute (
|
|
542
|
+
and 10 creations per minute (`429 {"error": "rate_limited", "message": "Too Many Attempts."}`, with `Retry-After`). While the Meta
|
|
519
543
|
sign-up is off (the default), a `meta` link answers `503 meta_connect_disabled`, and so does the page of a `meta` link
|
|
520
544
|
made while it was on.
|
|
521
545
|
|
|
@@ -576,7 +600,7 @@ You subscribe with the same routes the SDKs and the n8n trigger use (the unified
|
|
|
576
600
|
|
|
577
601
|
| Route | What it does |
|
|
578
602
|
|---|---|
|
|
579
|
-
| `POST /api/v1/webhooks` | Creates a subscription. The answer (`201`) carries `secret` **only once**. |
|
|
603
|
+
| `POST /api/v1/webhooks` | Creates a subscription. The answer (`201`) carries `secret` **only once**. Up to 50 per workspace, whatever their status: one more answers `422 webhooks_limit_reached`. |
|
|
580
604
|
| `GET /api/v1/webhooks`, `GET /api/v1/webhooks/{id}` | Lists or shows them, without the secret. |
|
|
581
605
|
| `PUT /api/v1/webhooks/{id}` | Changes `url`, `events`, `description`, `filters` and, optionally, `status` (`active` or `paused`). The secret does not change. |
|
|
582
606
|
| `DELETE /api/v1/webhooks/{id}` | Deletes it (`204`). |
|
|
@@ -716,17 +740,17 @@ And at `http://localhost:4010/_sandbox` you see each request and each delivery w
|
|
|
716
740
|
|
|
717
741
|
- **It sends nothing to WhatsApp** and charges nothing: the `sent`, `delivered` and `read` statuses are made up by the sandbox, with the delays you configure. In the real API, a message still `queued` after an hour reads as `status: null`; in the sandbox it always moves on.
|
|
718
742
|
- **One account and every permission:** any well-formed key gets in, is never revoked and there is no `403 permission_denied`. Workspaces are created and their keys minted through `/_sandbox`, never deleted, and a number does not move between workspaces. API keys are not created here, and subscriptions are not filtered by the key's areas (the real API answers `422` on `events` if the key lacks the event's `{area}:read`). The only `403 route_not_available` is the send to a group with `--no-group-actions`.
|
|
719
|
-
- **There are no daily call caps** (`429 daily_cap_exceeded`), no `503 gate_unavailable` and no balance: to test those cases, force the failure. Of the MAC there is the lockout (`429 mac_lockout_24h`); `429 mac_limit_reached` is only forced
|
|
743
|
+
- **There are no daily call caps** (`429 daily_cap_exceeded`), no `503 gate_unavailable` or `500 gate_misconfigured` and no balance: to test those cases, force the failure. Of the MAC there is the lockout (`429 mac_lockout_24h`); `429 mac_limit_reached` is only forced. A send the MAC stops stores nothing, as in production: no message and no `client_reference` in the contact's history.
|
|
720
744
|
- **The state is memory only:** it is lost when the process stops or with `POST /_sandbox/reset`. So your receiver does not lose the secret, put the subscription in the configuration file.
|
|
721
745
|
- **The shape-only routes** answer the shape of the spec, always the same: it does not match your data, a write keeps nothing and no event goes out. See [With state or shape only](#with-state-or-shape-only).
|
|
722
|
-
- **The validation messages** are Laravel's in Spanish, built from the schema: they may not match the API's word for word (decide by the HTTP status and by `error`, not by `message`).
|
|
746
|
+
- **The validation messages** are Laravel's in Spanish, built from the schema: they may not match the API's word for word (decide by the HTTP status and by `error`, not by `message`).
|
|
723
747
|
- **Webhook URLs** may be `http` and local, without the real API's protection against internal destinations (try `--require-https`). Do not expose the sandbox to the internet: the `/_sandbox` routes take no key and the sandbox `POST`s to any URL subscribed to it.
|
|
724
748
|
- **The event catalog** is what the spec documents (`generated/catalog.json`, transcribed by `npm run generate`): fields of production's catalog the spec does not document, such as `is_critical` and `label`, are not there.
|
|
725
749
|
- **Groups have state only for the number's live list, the list, one group, the import and the send of a message** (the action is `completed` after the delays of `sent` and `delivered`; it never fails on its own). The live list is never cached (production keeps it a minute for a key). The other group actions, the campaigns and the rest of the groups' routes answer shape only, and their events are fired from `/_sandbox`. There are no official groups (the account is not in their beta), no `too_many_pending_actions` and no rate limit of 30 sends per minute.
|
|
726
750
|
- **Workspaces confine** numbers, sign-up links, webhook subscriptions, contacts, conversations, messages, templates and groups. Tags, custom fields and forms are not confined in the sandbox. A contact belongs to one workspace, and the phone stays unique in the account: `POST /api/v1/contacts`, `update-or-create` and a send by phone with a phone of another workspace answer `404`.
|
|
727
751
|
- **Forms do not talk to Meta:** `sync` and `publish` succeed unless the linter finds an error, and the linter is a short version of the real one (screens without a title, without an ending, without questions, with a connection to the server). The metrics (`…/metrics`) answer shape only, and there are no dynamic forms.
|
|
728
752
|
- **Files are not downloaded:** the type comes from the extension or from `file_type`. The «known» extensions (the ones that give `415` instead of `422 errors.file_type`) are the common ones; the API uses symfony/mime's full list.
|
|
729
|
-
- **Connecting numbers** follows a contract, not production: no real QR, no Meta Embedded Signup and no 15-minute hold of a pool number. The page has no `POST /connect/{token}/qr` (the button connects at once) and no `POST /connect/{token}/offer` (the pool number is given when the link completes). A QR link that fails does not create (and delete) a number, links do not die when a key is revoked, the per-minute limit counts only the calls that pass the schema, and `409 operation_in_progress` only appears if two calls with the same `Idempotency-Key` overlap. The errors that depend on the plan, the payment or the provider (`connect_not_allowed`, `number_suspended_by_plan`, `payment_required`, `device_inactive`, `provider_error`…) are documented but never answered.
|
|
753
|
+
- **Connecting numbers** follows a contract, not production: no real QR, no Meta Embedded Signup and no 15-minute hold of a pool number. The page has no `POST /connect/{token}/qr` (the button connects at once) and no `POST /connect/{token}/offer` (the pool number is given when the link completes). A QR link that fails does not create (and delete) a number, links do not die when a key is revoked, the per-minute limit counts only the calls that pass the schema, and `409 operation_in_progress` only appears if two calls with the same `Idempotency-Key` overlap. The errors that depend on the plan, the payment or the provider (`connect_not_allowed`, `number_suspended_by_plan`, `402 payment_required`, `device_inactive`, `provider_error`…) are documented but never answered.
|
|
730
754
|
- **Template metrics and the WhatsApp Business accounts** (`GET /api/v1/templates/{template}/metrics`, `GET /api/v1/me/whatsapp-business-accounts…`) answer shape only, not data of the sandbox. **Mark as read and «typing…»** have no per-second ceilings (`429 rate_limited`) and never fail at WhatsApp (`502 provider_error`).
|
|
731
755
|
- **The secret of a subscription is rotated from the panel:** `POST /api/v1/webhooks/{id}/rotate-secret` is not in the published spec, so here it is `404 route_not_found` (a key gets `403 route_not_available` in production).
|
|
732
756
|
- **Forms** are served here, but the API edge does not route `forms` yet: through the public URL they answer `404 route_not_found`.
|
|
@@ -734,9 +758,9 @@ And at `http://localhost:4010/_sandbox` you see each request and each delivery w
|
|
|
734
758
|
|
|
735
759
|
## Pending of the spec
|
|
736
760
|
|
|
737
|
-
The spec (`spec/funnelchat.json`) is the one the portal publishes, v0.6.
|
|
761
|
+
The spec (`spec/funnelchat.json`) is the one the portal publishes, v0.6.16 (https://developers-portal.funnelchat.app/specs/funnelchat.json): only the operations and events the portal shows. **The sandbox offers only that:** a layer corrects shapes where the spec says otherwise than production answers, but never adds an operation or an event (the generator refuses it and says so). The corrections are in two layers, which the next regeneration drops on its own once the spec carries them:
|
|
738
762
|
|
|
739
|
-
- `spec/pending.json`: the pending layer of the Node SDK (`funnelchat-node` @ `bde8679`), with the corrections of 13-11 made here ahead of the SDK: `answers[].label` of `whatsapp_form.completed` is always a string, `message.type` of `message.received` accepts types the spec does not list yet, and its sources say what portal v0.6.14 shows (its note in `spec/procedencia.json` lists them). Guarded by JSON Pointer conditions: the send answers, `devices/{device}/qr`, the template list, `PUT webhooks/{id}`,
|
|
763
|
+
- `spec/pending.json`: the pending layer of the Node SDK (`funnelchat-node` @ `bde8679`), with the corrections of 13-11 made here ahead of the SDK: `answers[].label` of `whatsapp_form.completed` is always a string, `message.type` of `message.received` accepts types the spec does not list yet, and its sources say what portal v0.6.14 shows (its note in `spec/procedencia.json` lists them). Guarded by JSON Pointer conditions: the send answers, `devices/{device}/qr`, the template list, `PUT webhooks/{id}`, and the shapes of the number, group, billing, form and message events. Its entry for `GET groups/actions/{action}` no longer applies: the spec of v0.6.16 carries that answer, and `npm run generate` lists the entry so that the layer drops it.
|
|
740
764
|
- `spec/pending-sandbox.json`: what the sandbox corrects on top of it, with its sources inside.
|
|
741
765
|
|
|
742
766
|
The event catalog (`generated/catalog.json`) comes out of the merged spec, with the same command: see [Regenerating](#regenerating).
|
|
@@ -756,7 +780,7 @@ Everything generated comes from a single script:
|
|
|
756
780
|
# With a new spec of the portal: always the one it publishes, only the operations and events the portal shows (the
|
|
757
781
|
# tools are generated only from what the portal publishes). Download it, and say with --origin which version of the
|
|
758
782
|
# portal it is, for example {"url": "https://developers-portal.funnelchat.app/specs/funnelchat.json",
|
|
759
|
-
# "portal_version": "v0.6.
|
|
783
|
+
# "portal_version": "v0.6.16"}; it is the only place the version is written, and spec/procedencia.json keeps it:
|
|
760
784
|
curl -sO https://developers-portal.funnelchat.app/specs/funnelchat.json
|
|
761
785
|
npm run generate -- --spec funnelchat.json --origin <that JSON>
|
|
762
786
|
|
|
@@ -781,7 +805,7 @@ What it does:
|
|
|
781
805
|
|
|
782
806
|
**A handler whose operation is not in the spec is not reached:** the route answers `404 route_not_found`, like a route the API does not have. After a regeneration, `GET /_sandbox/operations` says what has state and what answers [shape only](#with-state-or-shape-only).
|
|
783
807
|
|
|
784
|
-
**The current spec** is the one the portal publishes, v0.6.
|
|
808
|
+
**The current spec** is the one the portal publishes, v0.6.16, at https://developers-portal.funnelchat.app/specs/funnelchat.json (sha256 `7db2ead826697c80fd8feef3f0ea556b5374dd3d2cb5e02f3cfe4584af79e33c`), with the common error format of the API: 209 paths, 253 operations and 57 events (56 types: the 55 of the `events` enum and `webhook.test`, which two areas document), in 30 groups: 1 of «Primer uso», 9 of «Funciones de WhatsApp» and 20 of «Funciones de Funnelchat». The detail is in `spec/procedencia.json`.
|
|
785
809
|
|
|
786
810
|
## Development
|
|
787
811
|
|