planvortex 0.5.0__tar.gz → 0.7.0__tar.gz
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.
- {planvortex-0.5.0 → planvortex-0.7.0}/CHANGELOG.md +65 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/PKG-INFO +7 -7
- {planvortex-0.5.0 → planvortex-0.7.0}/README.md +5 -5
- {planvortex-0.5.0 → planvortex-0.7.0}/openapi/planvortex.openapi.json +38 -45
- {planvortex-0.5.0 → planvortex-0.7.0}/pyproject.toml +2 -2
- {planvortex-0.5.0 → planvortex-0.7.0}/src/planvortex/_core/errors.py +15 -6
- {planvortex-0.5.0 → planvortex-0.7.0}/src/planvortex/_generated/models.py +20 -19
- {planvortex-0.5.0 → planvortex-0.7.0}/src/planvortex/_shapes.py +2 -1
- {planvortex-0.5.0 → planvortex-0.7.0}/src/planvortex/_version.py +1 -1
- {planvortex-0.5.0 → planvortex-0.7.0}/src/planvortex/types.py +13 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/tests/test_core.py +12 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/tests/test_example_connect.py +1 -1
- {planvortex-0.5.0 → planvortex-0.7.0}/tests/test_example_publish.py +1 -1
- {planvortex-0.5.0 → planvortex-0.7.0}/tests/test_organizations.py +2 -2
- {planvortex-0.5.0 → planvortex-0.7.0}/.gitignore +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/LICENSE +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/scripts/build_docs.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/scripts/check_packaging.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/scripts/generate_models.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/scripts/generate_sync.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/scripts/route_coverage.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/src/planvortex/__init__.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/src/planvortex/_client.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/src/planvortex/_client_sync.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/src/planvortex/_core/__init__.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/src/planvortex/_core/auth.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/src/planvortex/_core/auth_sync.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/src/planvortex/_core/files.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/src/planvortex/_core/http.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/src/planvortex/_core/http_sync.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/src/planvortex/_core/pagination.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/src/planvortex/_core/query.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/src/planvortex/_core/transport.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/src/planvortex/_generated/__init__.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/src/planvortex/py.typed +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/src/planvortex/resources/__init__.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/src/planvortex/resources/accounts.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/src/planvortex/resources/ai_plans.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/src/planvortex/resources/apps.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/src/planvortex/resources/base.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/src/planvortex/resources/catalog.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/src/planvortex/resources/clients.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/src/planvortex/resources/comments.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/src/planvortex/resources/contacts.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/src/planvortex/resources/dashboard.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/src/planvortex/resources/integrations.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/src/planvortex/resources/messages.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/src/planvortex/resources/organizations.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/src/planvortex/resources/products.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/src/planvortex/resources/publications.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/src/planvortex/resources/uploads.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/src/planvortex/resources_sync/__init__.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/src/planvortex/resources_sync/accounts.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/src/planvortex/resources_sync/ai_plans.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/src/planvortex/resources_sync/apps.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/src/planvortex/resources_sync/base.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/src/planvortex/resources_sync/catalog.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/src/planvortex/resources_sync/clients.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/src/planvortex/resources_sync/comments.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/src/planvortex/resources_sync/contacts.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/src/planvortex/resources_sync/dashboard.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/src/planvortex/resources_sync/integrations.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/src/planvortex/resources_sync/messages.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/src/planvortex/resources_sync/organizations.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/src/planvortex/resources_sync/products.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/src/planvortex/resources_sync/publications.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/src/planvortex/resources_sync/uploads.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/src/planvortex/webhooks.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/tests/__init__.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/tests/conftest.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/tests/contrato.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/tests/live/__init__.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/tests/live/conftest.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/tests/live/test_auth.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/tests/live/test_catalog.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/tests/live/test_connect_flow.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/tests/live/test_errors.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/tests/live/test_publish.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/tests/live/test_read.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/tests/test_accounts.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/tests/test_ai_plans.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/tests/test_apps.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/tests/test_catalog.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/tests/test_client.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/tests/test_clients.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/tests/test_comments.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/tests/test_contacts.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/tests/test_dashboard.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/tests/test_example_comments.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/tests/test_example_schedule.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/tests/test_example_webhooks.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/tests/test_files.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/tests/test_generate_models.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/tests/test_generate_sync.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/tests/test_integrations.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/tests/test_messages.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/tests/test_models.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/tests/test_openapi_freshness.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/tests/test_packaging.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/tests/test_pagination.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/tests/test_products.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/tests/test_publications.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/tests/test_query_parity.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/tests/test_readme.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/tests/test_route_coverage.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/tests/test_shapes_parity.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/tests/test_types.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/tests/test_uploads.py +0 -0
- {planvortex-0.5.0 → planvortex-0.7.0}/tests/test_webhooks.py +0 -0
|
@@ -4,6 +4,71 @@ All notable changes to this package are documented here. The format follows
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to
|
|
5
5
|
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [0.7.0] - 2026-09-04
|
|
8
|
+
|
|
9
|
+
**Users are unlimited on every plan**, so the quota that said otherwise is gone from the types —
|
|
10
|
+
the same correction 0.6.0 made for publications, one axis later.
|
|
11
|
+
|
|
12
|
+
It is the second half of a change the server made on 03-09-2026: users stopped being something a
|
|
13
|
+
plan grants and became something you simply count. `PlanData["users"]` was a **required** key, so
|
|
14
|
+
mypy accepted `limits["users"]` and the call raised `KeyError` at runtime, which is the same
|
|
15
|
+
failure mode 0.6.0 describes for publications.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- **BREAKING (types only): `PlanData["users"]` no longer exists.** The count moved to
|
|
20
|
+
`PlanUseData` as an optional key — how many users have access right now, with no limit to
|
|
21
|
+
compare it against, because there is none. Nothing changes on the wire.
|
|
22
|
+
- **The `/apps` operations no longer claim the Custom plan.** Their docstrings still said the API
|
|
23
|
+
needed it. That stopped being true on 02-09-2026, and the docstrings come straight from the
|
|
24
|
+
OpenAPI document, so they were repeating a copy of the spec that had not been rebuilt since.
|
|
25
|
+
|
|
26
|
+
### Removed
|
|
27
|
+
|
|
28
|
+
- **`PlanData["artificial_inteligence"]`, `PlanData["stats"]` and `PlanData["whatsapp"]`.**
|
|
29
|
+
Three optional keys describing a plan model that no longer exists: statistics and WhatsApp are on
|
|
30
|
+
every plan, and what gates AI is `ai_credits` — a plan with credits has AI, and Free has zero.
|
|
31
|
+
|
|
32
|
+
## [0.6.0] - 2026-09-03
|
|
33
|
+
|
|
34
|
+
**Publications are unlimited on every plan**, so the quota that said otherwise is gone from the
|
|
35
|
+
types.
|
|
36
|
+
|
|
37
|
+
It was never a number this package invented: `PlanData["publications"]` came from the spec, and the
|
|
38
|
+
server stopped charging for it on 02-09-2026. What stayed behind was worse than a wrong number — it
|
|
39
|
+
was a key mypy kept promising, so `limits["publications"]` type-checked and raised `KeyError` at
|
|
40
|
+
runtime.
|
|
41
|
+
|
|
42
|
+
### Changed
|
|
43
|
+
|
|
44
|
+
- **BREAKING (types only): `PlanData` no longer has a `publications` key.** The count moved to the
|
|
45
|
+
new `PlanUseData`, which is what `actual_use` has always been in practice: the same shape as a
|
|
46
|
+
plan plus `publications`, and `NotRequired` because a brand-new organization has spent nothing.
|
|
47
|
+
Nothing changes on the wire.
|
|
48
|
+
- `OrganizationUse["actual_use"]` is a `PlanUseData`; `actual_asigned` stays a `PlanData`, which is
|
|
49
|
+
the real asymmetry: what a child organization was handed cannot include a metric.
|
|
50
|
+
- The `publish` example prints publications as what they are — a monthly count with no ceiling —
|
|
51
|
+
instead of `12 de 100`. What throttles publishing is rate (per hour and account, and the
|
|
52
|
+
per-network daily caps in `GET /social_limits`), not the plan.
|
|
53
|
+
- **The `publication` error family now reaches 979, not 960.** Everything above the old ceiling was
|
|
54
|
+
falling outside every range and arriving as a bare `PlanVortexError`: the Bluesky, Discord,
|
|
55
|
+
Telegram and Threads codes (961-977) and, more to the point, the two rate brakes that replaced the
|
|
56
|
+
monthly quota — **978** (publishing too fast on this account) and **979** (that network's daily
|
|
57
|
+
cap). `except PublicationError` now catches them.
|
|
58
|
+
|
|
59
|
+
### Added
|
|
60
|
+
|
|
61
|
+
**The public API is on every plan now, the free one included** — it used to be a Custom-plan
|
|
62
|
+
feature — and the types follow the three things that came with opening it.
|
|
63
|
+
|
|
64
|
+
- `PlanData["apps"]`: how many apps the plan allows (1 on Free, 2 on Basic, 5 on Pro, 10 on Custom).
|
|
65
|
+
Each app is a `client_id` with a secret, so each one is a key to the whole API. Unlike accounts,
|
|
66
|
+
users or storage it is **not** split between organizations — an app belongs to the client.
|
|
67
|
+
- Three error codes, and the two ranges that were hiding them: **545** (the plan's API rate limit,
|
|
68
|
+
which arrives as a `429` with `Retry-After`) and **546** (the account's email must be verified to
|
|
69
|
+
create an app) are `AuthError`, so that range now runs to 546; **1308** (no more apps fit in this
|
|
70
|
+
plan) is a `PlanLimitError`, so that one runs to 1308.
|
|
71
|
+
|
|
7
72
|
## [0.5.0] - 2026-09-02
|
|
8
73
|
|
|
9
74
|
**Threads is the twelfth network**, and the types now know it.
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: planvortex
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: Official Python client for the PlanVortex API
|
|
5
|
-
Project-URL: Homepage, https://planvortex.com/developers
|
|
5
|
+
Project-URL: Homepage, https://planvortex.com/en/developers
|
|
6
6
|
Project-URL: Documentation, https://taliasoftworks.github.io/PlanVortexPython/
|
|
7
7
|
Project-URL: API, https://planvortex.com/documentation
|
|
8
8
|
Project-URL: Repository, https://github.com/taliasoftworks/PlanVortexPython
|
|
@@ -53,7 +53,7 @@ pip install planvortex
|
|
|
53
53
|
- Python 3.10 and newer.
|
|
54
54
|
|
|
55
55
|
**Reference:** <https://taliasoftworks.github.io/PlanVortexPython/> · **Guides:**
|
|
56
|
-
[planvortex.com/developers](https://planvortex.com/developers)
|
|
56
|
+
[planvortex.com/en/developers](https://planvortex.com/en/developers)
|
|
57
57
|
|
|
58
58
|
## Authentication
|
|
59
59
|
|
|
@@ -242,15 +242,15 @@ memorising numbers:
|
|
|
242
242
|
|
|
243
243
|
| Codes | Family | Exception |
|
|
244
244
|
|---|---|---|
|
|
245
|
-
| 500-
|
|
245
|
+
| 500-546 | `auth` | `AuthError` |
|
|
246
246
|
| 601-612 | `user` | `UserError` |
|
|
247
247
|
| 700-715 | `account` | `AccountError` |
|
|
248
248
|
| 800-810 | `file` | `FileError` |
|
|
249
|
-
| 900-
|
|
249
|
+
| 900-979 | `publication` | `PublicationError` |
|
|
250
250
|
| 1000-1003 | `general` | `PlanVortexError` |
|
|
251
251
|
| 1100-1111 | `organization` | `OrganizationError` |
|
|
252
252
|
| 1200-1207 | `role` | `PlanVortexError` |
|
|
253
|
-
| 1300-
|
|
253
|
+
| 1300-1308, 1400-1408 | `plan_limit` | `PlanLimitError` |
|
|
254
254
|
| 1500-1512 | `messaging` | `MessagingError` |
|
|
255
255
|
| 1600-1601 | `contact` | `ContactError` |
|
|
256
256
|
| 1900-1906 | `payment` | `PlanVortexError` |
|
|
@@ -371,7 +371,7 @@ and reaches a person.
|
|
|
371
371
|
|
|
372
372
|
- [Reference](https://taliasoftworks.github.io/PlanVortexPython/)
|
|
373
373
|
- [API documentation](https://planvortex.com/documentation)
|
|
374
|
-
- [Developers](https://planvortex.com/developers)
|
|
374
|
+
- [Developers](https://planvortex.com/en/developers)
|
|
375
375
|
- [Node client](https://github.com/taliasoftworks/PlanVortexNode)
|
|
376
376
|
- [Contributing](https://github.com/taliasoftworks/PlanVortexPython/blob/main/CONTRIBUTING.md) · [Security](https://github.com/taliasoftworks/PlanVortexPython/blob/main/SECURITY.md) · [Changelog](https://github.com/taliasoftworks/PlanVortexPython/blob/main/CHANGELOG.md)
|
|
377
377
|
|
|
@@ -23,7 +23,7 @@ pip install planvortex
|
|
|
23
23
|
- Python 3.10 and newer.
|
|
24
24
|
|
|
25
25
|
**Reference:** <https://taliasoftworks.github.io/PlanVortexPython/> · **Guides:**
|
|
26
|
-
[planvortex.com/developers](https://planvortex.com/developers)
|
|
26
|
+
[planvortex.com/en/developers](https://planvortex.com/en/developers)
|
|
27
27
|
|
|
28
28
|
## Authentication
|
|
29
29
|
|
|
@@ -212,15 +212,15 @@ memorising numbers:
|
|
|
212
212
|
|
|
213
213
|
| Codes | Family | Exception |
|
|
214
214
|
|---|---|---|
|
|
215
|
-
| 500-
|
|
215
|
+
| 500-546 | `auth` | `AuthError` |
|
|
216
216
|
| 601-612 | `user` | `UserError` |
|
|
217
217
|
| 700-715 | `account` | `AccountError` |
|
|
218
218
|
| 800-810 | `file` | `FileError` |
|
|
219
|
-
| 900-
|
|
219
|
+
| 900-979 | `publication` | `PublicationError` |
|
|
220
220
|
| 1000-1003 | `general` | `PlanVortexError` |
|
|
221
221
|
| 1100-1111 | `organization` | `OrganizationError` |
|
|
222
222
|
| 1200-1207 | `role` | `PlanVortexError` |
|
|
223
|
-
| 1300-
|
|
223
|
+
| 1300-1308, 1400-1408 | `plan_limit` | `PlanLimitError` |
|
|
224
224
|
| 1500-1512 | `messaging` | `MessagingError` |
|
|
225
225
|
| 1600-1601 | `contact` | `ContactError` |
|
|
226
226
|
| 1900-1906 | `payment` | `PlanVortexError` |
|
|
@@ -341,7 +341,7 @@ and reaches a person.
|
|
|
341
341
|
|
|
342
342
|
- [Reference](https://taliasoftworks.github.io/PlanVortexPython/)
|
|
343
343
|
- [API documentation](https://planvortex.com/documentation)
|
|
344
|
-
- [Developers](https://planvortex.com/developers)
|
|
344
|
+
- [Developers](https://planvortex.com/en/developers)
|
|
345
345
|
- [Node client](https://github.com/taliasoftworks/PlanVortexNode)
|
|
346
346
|
- [Contributing](https://github.com/taliasoftworks/PlanVortexPython/blob/main/CONTRIBUTING.md) · [Security](https://github.com/taliasoftworks/PlanVortexPython/blob/main/SECURITY.md) · [Changelog](https://github.com/taliasoftworks/PlanVortexPython/blob/main/CHANGELOG.md)
|
|
347
347
|
|
|
@@ -656,7 +656,7 @@
|
|
|
656
656
|
"accounts"
|
|
657
657
|
],
|
|
658
658
|
"summary": "Complete the connection of a social account",
|
|
659
|
-
"description": "The endpoint the social network sends the user back to after they authorize. It turns the network's callback into one or more PlanVortex accounts.\n\n**You do not build this URL, the network does.** It is the `redirect_uri` inside the link that `GET /organizations/{id_organization}/connect_links` handed out, so the query parameters are whatever the network appends — typically `code` and `state`. Pass them through untouched.\n\n**One authorization can produce several accounts.** A Facebook user with four pages ends up with four; a Discord authorization produces the channel that was picked.\n\n**It answers 200 even when it fails.** The result carries `errorCode` and `errorMsg` instead of an error body, because the browser lands here from a redirect and a raw 400 would be a broken page. Check `errorCode`: empty means everything went well.\n\n**An app cannot call this** — it needs a user token or a temporal connect token (error 519 otherwise). See `GET /organizations/{id_organization}/temporal_connect_token`.\n\n**A temporal connect token is spent here.** Once this call succeeds, that token cannot connect anything else and answers error 543; the `enable` calls that finish the same connection still work until it expires. And if the token was issued for one network, calling this for another answers error 544.\n\n**Telegram does not come through here, and cannot be made to.** That network has no callback: the account is created by PlanVortex when the bot is added to a channel, and what authorizes it is a single-use voucher minted at that moment and spent in the same breath — it never leaves the server, so calling this endpoint for `telegram` answers error 700. It is deliberate: the bot is shared, so without it anyone could hang any channel where that bot is an admin off their own organization by passing a chat id by hand. What an integration listens for instead is the `new_account` webhook notification.",
|
|
659
|
+
"description": "The endpoint the social network sends the user back to after they authorize. It turns the network's callback into one or more PlanVortex accounts.\n\n**You do not build this URL, the network does.** It is the `redirect_uri` inside the link that `GET /organizations/{id_organization}/connect_links` handed out, so the query parameters are whatever the network appends — typically `code` and `state`. Pass them through untouched.\n\n**One authorization can produce several accounts.** A Facebook user with four pages ends up with four; a Discord authorization produces the channel that was picked.\n\n**It answers 200 even when it fails.** The result carries `errorCode` and `errorMsg` instead of an error body, because the browser lands here from a redirect and a raw 400 would be a broken page. Check `errorCode`: empty means everything went well.\n\n**An app cannot call this** — it needs a user token or a temporal connect token (error 519 otherwise). See `GET /organizations/{id_organization}/temporal_connect_token`.\n\n**A temporal connect token is spent here.** Once this call succeeds, that token cannot connect anything else and answers error 543; the `enable` calls that finish the same connection still work until it expires. And if the token was issued for one network, calling this for another answers error 544.\n\n**Telegram does not come through here, and cannot be made to.** That network has no callback: the account is created by PlanVortex when the bot is added to a channel, and what authorizes it is a single-use voucher minted at that moment and spent in the same breath — it never leaves the server, so calling this endpoint for `telegram` answers error 700. It is deliberate: the bot is shared, so without it anyone could hang any channel where that bot is an admin off their own organization by passing a chat id by hand. What an integration listens for instead is the `new_account` webhook notification.\n\n**And a Telegram account arrives already active.** In the other eleven networks this endpoint hands you accounts that are still off, and you pick which ones spend a plan slot with `POST .../accounts/{id_account}/enable`. Here there is nothing to call: one channel arrives — the one the person picked in Telegram — and PlanVortex takes the slot for it right then. If the plan has no free slot the account is still created, off, and the person is told so in the bot chat; `GET /organizations/{id_organization}/accounts` will not list it, because that listing only returns active accounts, but naming it in the `accounts` filter does return it.",
|
|
660
660
|
"operationId": "connectAccount",
|
|
661
661
|
"x-planvortex-identity": [
|
|
662
662
|
"current_user",
|
|
@@ -736,7 +736,7 @@
|
|
|
736
736
|
"accounts"
|
|
737
737
|
],
|
|
738
738
|
"summary": "Re-enable a disconnected account",
|
|
739
|
-
"description": "Brings back an account that was removed, without going through the authorization again — as long as its stored token still works.\n\nIt takes a **slot of the plan**, so it fails with error 706 when the organization is already at its account limit. It also turns the network's webhooks back on, on any plan but the free one.",
|
|
739
|
+
"description": "Brings back an account that was removed, without going through the authorization again — as long as its stored token still works.\n\nIt takes a **slot of the plan**, so it fails with error 706 when the organization is already at its account limit. It also turns the network's webhooks back on, on any plan but the free one.\n\n**On `telegram` you normally never call this.** That network's accounts are born active — there is no authorization return in which to choose them, so PlanVortex takes the slot when the bot is added to the channel. The one case left is a channel that arrived while the plan was full: it stays off until a slot frees up, and this is what turns it on.",
|
|
740
740
|
"operationId": "enableAccount",
|
|
741
741
|
"x-planvortex-identity": [
|
|
742
742
|
"current_user",
|
|
@@ -945,7 +945,7 @@
|
|
|
945
945
|
"ai_plans"
|
|
946
946
|
],
|
|
947
947
|
"summary": "Create an AI publication plan",
|
|
948
|
-
"description": "Create a weekly AI publication plan. The plan is queued in state 'pending' and generated asynchronously by the generate-ai-plans job; poll GET by id while state is pending or generating. Validations at creation: the client plan allows AI (
|
|
948
|
+
"description": "Create a weekly AI publication plan. The plan is queued in state 'pending' and generated asynchronously by the generate-ai-plans job; poll GET by id while state is pending or generating. Validations at creation: the client plan allows AI (it contracts AI credits; Free does not), the accounts belong to the organization, there are enough AI credits for the deterministic base cost (orchestration + target texts), and there is room in the organization's monthly publication limit. Returns the created plan together with the deterministic cost estimate. Requires the ai_plans:create permission. Since the plan can be generated from a SOURCE (`template` + `source`), part of that validation is the source itself: the article is downloaded, the catalogue is read live and the product pictures are copied — all of it inside this request, so what does not work fails with the user in front of it. A plan whose source did not fit in the week is still created, and says so in ai_plan.warnings (2117).",
|
|
949
949
|
"operationId": "addAiPlan",
|
|
950
950
|
"x-planvortex-identity": [
|
|
951
951
|
"current_user",
|
|
@@ -1564,7 +1564,7 @@
|
|
|
1564
1564
|
"apps"
|
|
1565
1565
|
],
|
|
1566
1566
|
"summary": "List the client's apps",
|
|
1567
|
-
"description": "The apps of a client. Secrets never travel here: to read one, ask for it explicitly with the `secret` endpoint.\n\n**Needs a USER token, not an app token.** With client credentials it answers `ERROR_CODE_512`.
|
|
1567
|
+
"description": "The apps of a client. Secrets never travel here: to read one, ask for it explicitly with the `secret` endpoint.\n\n**Needs a USER token, not an app token.** With client credentials it answers `ERROR_CODE_512`. **No plan is required**: apps are on all four plans, the free one included — what the plan decides is how many you can hold at once and how fast you can call.",
|
|
1568
1568
|
"operationId": "getClientApps",
|
|
1569
1569
|
"x-planvortex-identity": [
|
|
1570
1570
|
"current_user"
|
|
@@ -1616,7 +1616,7 @@
|
|
|
1616
1616
|
"apps"
|
|
1617
1617
|
],
|
|
1618
1618
|
"summary": "Create an app",
|
|
1619
|
-
"description": "Creates the app and its credentials.\n\n**
|
|
1619
|
+
"description": "Creates the app and its credentials.\n\n**How many apps fit is decided by the plan**, not by a fixed limit of one: 1 on free, 2 on basic, 5 on pro and 10 on custom. Going over answers `ERROR_CODE_1308`, which carries `limit` and `used` in its `data`. Deleting an app frees its slot immediately, because only live apps count. If you just need to rotate credentials, update the existing app rather than creating another.\n\n**The user's email must be verified** (`ERROR_CODE_546`). This is the only route in `/apps` that asks for it: an app is a key to the whole public API, and this is what stops a throwaway address from minting them in bulk. Reading, updating and deleting an app you already own do not check it.\n\nEvery URL is validated: a bad entry in `allowed_domains` answers 531, in `redirect_urls` 532, and a bad `webhook_url` answers 535.\n\n**The secret is not in the response.** Read it with `GET /clients/{id_client}/apps/{id_app}/secret`.\n\n**Needs a USER token, not an app token.** With client credentials it answers `ERROR_CODE_512`. **No plan is required**: apps are on all four plans, the free one included — what the plan decides is how many you can hold at once and how fast you can call.",
|
|
1620
1620
|
"operationId": "createClientApp",
|
|
1621
1621
|
"x-planvortex-identity": [
|
|
1622
1622
|
"current_user"
|
|
@@ -1667,7 +1667,7 @@
|
|
|
1667
1667
|
"apps"
|
|
1668
1668
|
],
|
|
1669
1669
|
"summary": "Get one app",
|
|
1670
|
-
"description": "The app's record, without its secret. Note the envelope: `{client_app}` here, `{client_apps, total}` in the list.\n\n**An app token works here.** This is the one part of `/apps` that does: an app can read and update its own record, but it cannot list, create, delete, or read the secret.
|
|
1670
|
+
"description": "The app's record, without its secret. Note the envelope: `{client_app}` here, `{client_apps, total}` in the list.\n\n**An app token works here.** This is the one part of `/apps` that does: an app can read and update its own record, but it cannot list, create, delete, or read the secret. **No plan is required**: apps are on all four plans, the free one included.",
|
|
1671
1671
|
"operationId": "getClientApp",
|
|
1672
1672
|
"x-planvortex-identity": [
|
|
1673
1673
|
"current_user",
|
|
@@ -1710,7 +1710,7 @@
|
|
|
1710
1710
|
"apps"
|
|
1711
1711
|
],
|
|
1712
1712
|
"summary": "Update an app",
|
|
1713
|
-
"description": "Replaces the app's configuration. **Every field is overwritten with what you send**, so a `redirect_urls` you leave out becomes an empty list — send the whole object, not a patch.\n\nChanging `webhook_url` takes effect on the next event; there is no verification handshake.\n\n**It replaces every field with what the body carries.** `name`, `keycloak_client_idenfifier`, `allowed_domains`, `redirect_urls` and `webhook_url` are all written as sent, so omitting one erases it — sending an update without `webhook_url` turns the webhook off. Read the app first and send it back whole.\n\n**An app token works here.** This is the one part of `/apps` that does: an app can read and update its own record, but it cannot list, create, delete, or read the secret.
|
|
1713
|
+
"description": "Replaces the app's configuration. **Every field is overwritten with what you send**, so a `redirect_urls` you leave out becomes an empty list — send the whole object, not a patch.\n\nChanging `webhook_url` takes effect on the next event; there is no verification handshake.\n\n**It replaces every field with what the body carries.** `name`, `keycloak_client_idenfifier`, `allowed_domains`, `redirect_urls` and `webhook_url` are all written as sent, so omitting one erases it — sending an update without `webhook_url` turns the webhook off. Read the app first and send it back whole.\n\n**An app token works here.** This is the one part of `/apps` that does: an app can read and update its own record, but it cannot list, create, delete, or read the secret. **No plan is required**: apps are on all four plans, the free one included.",
|
|
1714
1714
|
"operationId": "updateClientApp",
|
|
1715
1715
|
"x-planvortex-identity": [
|
|
1716
1716
|
"current_user",
|
|
@@ -1763,7 +1763,7 @@
|
|
|
1763
1763
|
"apps"
|
|
1764
1764
|
],
|
|
1765
1765
|
"summary": "Delete an app",
|
|
1766
|
-
"description": "Revokes the credentials and marks the app as deleted. **Tokens already issued stop working**: the token endpoint checks that the app still exists before answering, and requests carrying an old token no longer resolve to an app.\n\n**Needs a USER token, not an app token.** With client credentials it answers `ERROR_CODE_512`.
|
|
1766
|
+
"description": "Revokes the credentials and marks the app as deleted. **Tokens already issued stop working**: the token endpoint checks that the app still exists before answering, and requests carrying an old token no longer resolve to an app.\n\n**Needs a USER token, not an app token.** With client credentials it answers `ERROR_CODE_512`. **No plan is required**: apps are on all four plans, the free one included — what the plan decides is how many you can hold at once and how fast you can call.",
|
|
1767
1767
|
"operationId": "deleteClientApp",
|
|
1768
1768
|
"x-planvortex-identity": [
|
|
1769
1769
|
"current_user"
|
|
@@ -1799,7 +1799,7 @@
|
|
|
1799
1799
|
"apps"
|
|
1800
1800
|
],
|
|
1801
1801
|
"summary": "Read the app's secret",
|
|
1802
|
-
"description": "Returns the `client_secret` in clear. It is a separate endpoint precisely so that the secret never travels inside a listing that gets logged, cached or drawn on a screen by accident.\n\nThe same secret is what signs the outgoing webhooks (`x-hub-signature`, `x-hub-signature-256`).\n\n**Needs a USER token, not an app token.** With client credentials it answers `ERROR_CODE_512`.
|
|
1802
|
+
"description": "Returns the `client_secret` in clear. It is a separate endpoint precisely so that the secret never travels inside a listing that gets logged, cached or drawn on a screen by accident.\n\nThe same secret is what signs the outgoing webhooks (`x-hub-signature`, `x-hub-signature-256`).\n\n**Needs a USER token, not an app token.** With client credentials it answers `ERROR_CODE_512`. **No plan is required**: apps are on all four plans, the free one included — what the plan decides is how many you can hold at once and how fast you can call.\n\nIt comes straight from Keycloak, so it is the live secret and not a copy: treat the response as a credential and never log it.",
|
|
1803
1803
|
"operationId": "getClientAppSecret",
|
|
1804
1804
|
"x-planvortex-identity": [
|
|
1805
1805
|
"current_user"
|
|
@@ -2449,8 +2449,6 @@
|
|
|
2449
2449
|
"name": "Client X",
|
|
2450
2450
|
"actual_plan": {
|
|
2451
2451
|
"accounts": 4,
|
|
2452
|
-
"publications": 150,
|
|
2453
|
-
"users": 1,
|
|
2454
2452
|
"space": 0.25,
|
|
2455
2453
|
"integrations": 1,
|
|
2456
2454
|
"twitter_credits": 2500,
|
|
@@ -2625,8 +2623,6 @@
|
|
|
2625
2623
|
"value": {
|
|
2626
2624
|
"actual_plan": {
|
|
2627
2625
|
"accounts": 8,
|
|
2628
|
-
"publications": 300,
|
|
2629
|
-
"users": 3,
|
|
2630
2626
|
"space": 1,
|
|
2631
2627
|
"integrations": 3,
|
|
2632
2628
|
"twitter_credits": 5000,
|
|
@@ -2711,7 +2707,7 @@
|
|
|
2711
2707
|
"clients"
|
|
2712
2708
|
],
|
|
2713
2709
|
"summary": "Configure the client's own AI models (BYOK)",
|
|
2714
|
-
"description": "Set the client's Bring-Your-Own-Key AI provider configuration per scope. The request body is an object keyed by scope (orchestrator, text, image); a scope set to null clears it. Providers and capabilities are validated against a fixed catalogue. When a scope uses BYOK, its calls are billed to the client's own provider and do NOT consume PlanVortex AI credits, but the
|
|
2710
|
+
"description": "Set the client's Bring-Your-Own-Key AI provider configuration per scope. The request body is an object keyed by scope (orchestrator, text, image); a scope set to null clears it. Providers and capabilities are validated against a fixed catalogue. When a scope uses BYOK, its calls are billed to the client's own provider and do NOT consume PlanVortex AI credits, but the plan still has to include AI credits to use the feature. API keys are stored encrypted and never returned; the response client only exposes provider, model and has_api_key per scope. Requires the client:update permission.",
|
|
2715
2711
|
"operationId": "updateAiSettings",
|
|
2716
2712
|
"x-planvortex-identity": [
|
|
2717
2713
|
"current_user",
|
|
@@ -6024,8 +6020,6 @@
|
|
|
6024
6020
|
"value": {
|
|
6025
6021
|
"actual_plan": {
|
|
6026
6022
|
"accounts": 8,
|
|
6027
|
-
"publications": 300,
|
|
6028
|
-
"users": 3,
|
|
6029
6023
|
"space": 1,
|
|
6030
6024
|
"integrations": 3,
|
|
6031
6025
|
"twitter_credits": 5000,
|
|
@@ -6343,8 +6337,6 @@
|
|
|
6343
6337
|
"name": "Client X - Spain",
|
|
6344
6338
|
"actual_plan": {
|
|
6345
6339
|
"accounts": 3,
|
|
6346
|
-
"publications": 100,
|
|
6347
|
-
"users": 1,
|
|
6348
6340
|
"space": 0.25,
|
|
6349
6341
|
"integrations": 0,
|
|
6350
6342
|
"twitter_credits": 0,
|
|
@@ -10702,7 +10694,7 @@
|
|
|
10702
10694
|
"actual_use": {
|
|
10703
10695
|
"allOf": [
|
|
10704
10696
|
{
|
|
10705
|
-
"$ref": "#/components/schemas/
|
|
10697
|
+
"$ref": "#/components/schemas/PlanUseData"
|
|
10706
10698
|
}
|
|
10707
10699
|
],
|
|
10708
10700
|
"description": "Current consumption across every organization of the client. **Only present with `getUse=true`.** `twitter_credits` and `ai_credits` are what has been spent this calendar month."
|
|
@@ -12079,7 +12071,7 @@
|
|
|
12079
12071
|
"actual_use": {
|
|
12080
12072
|
"allOf": [
|
|
12081
12073
|
{
|
|
12082
|
-
"$ref": "#/components/schemas/
|
|
12074
|
+
"$ref": "#/components/schemas/PlanUseData"
|
|
12083
12075
|
}
|
|
12084
12076
|
],
|
|
12085
12077
|
"description": "What the organization and its children are consuming right now."
|
|
@@ -12265,7 +12257,7 @@
|
|
|
12265
12257
|
},
|
|
12266
12258
|
"code": {
|
|
12267
12259
|
"type": "integer",
|
|
12268
|
-
"description": "PlanVortex error code. Ranges: 500-
|
|
12260
|
+
"description": "PlanVortex error code. Ranges: 500-546 auth, tokens and client apps · 601-612 user · 700-715 social accounts · 800-810 files · 900-979 publications · 1000-1003 general · 1100-1111 organizations · 1200-1207 roles · 1300-1308 client plan · 1400-1408 organization plan · 1500-1512 messaging · 1600-1601 contacts · 1900-1906 payments · 2000-2099 products · 2100-2199 AI plans · 2200-2299 integrations."
|
|
12269
12261
|
},
|
|
12270
12262
|
"data": {
|
|
12271
12263
|
"additionalProperties": true,
|
|
@@ -12943,7 +12935,7 @@
|
|
|
12943
12935
|
"actual_use": {
|
|
12944
12936
|
"allOf": [
|
|
12945
12937
|
{
|
|
12946
|
-
"$ref": "#/components/schemas/
|
|
12938
|
+
"$ref": "#/components/schemas/PlanUseData"
|
|
12947
12939
|
}
|
|
12948
12940
|
],
|
|
12949
12941
|
"description": "Current consumption. **Only present with `getUse=true`.** `twitter_credits` and `ai_credits` are what has been spent in the current calendar month; the rest is what exists right now."
|
|
@@ -13382,11 +13374,9 @@
|
|
|
13382
13374
|
},
|
|
13383
13375
|
"PlanData": {
|
|
13384
13376
|
"type": "object",
|
|
13385
|
-
"description": "The resources a plan grants. On a client it is what was contracted; on an organization, the slice of it that was assigned. The sum across all the organizations of a client can never exceed what the client has contracted.",
|
|
13377
|
+
"description": "The resources a plan grants. On a client it is what was contracted; on an organization, the slice of it that was assigned. The sum across all the organizations of a client can never exceed what the client has contracted.\n\nUsers are NOT here: every plan has unlimited users, so they are neither charged nor split between organizations. They are still counted, in `PlanUseData.users`. There are no `artificial_inteligence`, `whatsapp` or `stats` flags either: statistics and WhatsApp are on every plan, and what gates AI is `ai_credits` — a plan with credits has AI, and Free has zero.",
|
|
13386
13378
|
"required": [
|
|
13387
13379
|
"accounts",
|
|
13388
|
-
"publications",
|
|
13389
|
-
"users",
|
|
13390
13380
|
"space",
|
|
13391
13381
|
"integrations"
|
|
13392
13382
|
],
|
|
@@ -13395,21 +13385,17 @@
|
|
|
13395
13385
|
"type": "integer",
|
|
13396
13386
|
"description": "Social accounts that may be connected."
|
|
13397
13387
|
},
|
|
13398
|
-
"publications": {
|
|
13399
|
-
"type": "integer",
|
|
13400
|
-
"description": "Publications that may be sent per month."
|
|
13401
|
-
},
|
|
13402
|
-
"users": {
|
|
13403
|
-
"type": "integer",
|
|
13404
|
-
"description": "Users with access."
|
|
13405
|
-
},
|
|
13406
13388
|
"space": {
|
|
13407
13389
|
"type": "number",
|
|
13408
13390
|
"description": "Storage, in GB."
|
|
13409
13391
|
},
|
|
13410
13392
|
"integrations": {
|
|
13411
13393
|
"type": "integer",
|
|
13412
|
-
"description": "Connections to a third-party tool material is pulled from (Google Drive, an RSS feed). Not the same thing as an app: an app is API access, and it
|
|
13394
|
+
"description": "Connections to a third-party tool material is pulled from (Google Drive, an RSS feed). Not the same thing as an app: an app is API access, and it has its own allowance in `apps`."
|
|
13395
|
+
},
|
|
13396
|
+
"apps": {
|
|
13397
|
+
"type": "integer",
|
|
13398
|
+
"description": "Apps this plan allows: 1 on Free, 2 on Basic, 5 on Pro, 10 on Custom. Each one is a `client_id` with a secret, so each one is a key to the whole public API — which every plan has, Free included. Unlike accounts or storage this is NOT split between organizations: an app belongs to the client. On a use payload it is how many exist right now."
|
|
13413
13399
|
},
|
|
13414
13400
|
"twitter_credits": {
|
|
13415
13401
|
"type": "integer",
|
|
@@ -13418,18 +13404,25 @@
|
|
|
13418
13404
|
"ai_credits": {
|
|
13419
13405
|
"type": "integer",
|
|
13420
13406
|
"description": "Monthly AI credits (1 credit = $0.001 of provider cost): 15 per plan orchestration pass, 2 per generated text, 70 per generated image. Resets monthly and does not roll over. A client using its own provider key (BYOK) does not consume them in that scope."
|
|
13407
|
+
}
|
|
13408
|
+
}
|
|
13409
|
+
},
|
|
13410
|
+
"PlanUseData": {
|
|
13411
|
+
"type": "object",
|
|
13412
|
+
"description": "What is being consumed right now. Same shape as PlanData plus `publications` and `users`, which are counted but neither charged nor split between organizations.",
|
|
13413
|
+
"allOf": [
|
|
13414
|
+
{
|
|
13415
|
+
"$ref": "#/components/schemas/PlanData"
|
|
13416
|
+
}
|
|
13417
|
+
],
|
|
13418
|
+
"properties": {
|
|
13419
|
+
"publications": {
|
|
13420
|
+
"type": "integer",
|
|
13421
|
+
"description": "Publications created in the current calendar month. A METRIC, not a quota: publications are unlimited on every plan, so there is no limit to compare it against. What throttles publishing is rate, not plan — see the per-hour and per-network daily caps."
|
|
13421
13422
|
},
|
|
13422
|
-
"
|
|
13423
|
-
"type": "
|
|
13424
|
-
"description": "
|
|
13425
|
-
},
|
|
13426
|
-
"whatsapp": {
|
|
13427
|
-
"type": "boolean",
|
|
13428
|
-
"description": "Whether WhatsApp may be connected."
|
|
13429
|
-
},
|
|
13430
|
-
"stats": {
|
|
13431
|
-
"type": "boolean",
|
|
13432
|
-
"description": "Whether statistics collection is enabled."
|
|
13423
|
+
"users": {
|
|
13424
|
+
"type": "integer",
|
|
13425
|
+
"description": "Users with access right now. A METRIC, not a quota: users are unlimited on every plan, so there is no limit to compare it against."
|
|
13433
13426
|
}
|
|
13434
13427
|
}
|
|
13435
13428
|
},
|
|
@@ -9,7 +9,7 @@ name = "planvortex"
|
|
|
9
9
|
# entonces `project.version` desapareceria del pyproject y el paso del workflow de release que
|
|
10
10
|
# compara el tag con la version —que lo lee con `tomllib`— dejaria de encontrarla. Se quedan las dos
|
|
11
11
|
# y las ata `tests/test_packaging.py`, que falla si se separan.
|
|
12
|
-
version = "0.
|
|
12
|
+
version = "0.7.0"
|
|
13
13
|
description = "Official Python client for the PlanVortex API"
|
|
14
14
|
readme = "README.md"
|
|
15
15
|
requires-python = ">=3.10"
|
|
@@ -67,7 +67,7 @@ dependencies = [
|
|
|
67
67
|
]
|
|
68
68
|
|
|
69
69
|
[project.urls]
|
|
70
|
-
Homepage = "https://planvortex.com/developers"
|
|
70
|
+
Homepage = "https://planvortex.com/en/developers"
|
|
71
71
|
# `Documentation` es la referencia generada de ESTE paquete, que es lo que busca quien llega a la
|
|
72
72
|
# ficha de PyPI. La especificacion de la API entera vive aparte, en `API`: son dos cosas distintas y
|
|
73
73
|
# apuntar las dos al mismo sitio dejaba una de las dos sin enlace.
|
|
@@ -27,15 +27,22 @@ class ErrorRange(NamedTuple):
|
|
|
27
27
|
# Los mismos 16 rangos que publica `swagger/common.json`, `PLANVORTEX_ERROR_RANGES` del paquete de
|
|
28
28
|
# Node y el apendice A del roadmap. SI SE TOCA UN RANGO, SE TOCAN LOS CINCO SITIOS.
|
|
29
29
|
PLANVORTEX_ERROR_RANGES: tuple[ErrorRange, ...] = (
|
|
30
|
-
|
|
30
|
+
# Hasta 546, no 544: la API publica se abrio a todos los planes y trajo dos codigos nuevos
|
|
31
|
+
# —545 (ritmo por plan, que sale 429) y 546 (correo sin verificar al crear una app)—.
|
|
32
|
+
ErrorRange(500, 546, "auth"),
|
|
31
33
|
ErrorRange(601, 612, "user"),
|
|
32
34
|
ErrorRange(700, 715, "account"),
|
|
33
35
|
ErrorRange(800, 810, "file"),
|
|
34
|
-
|
|
36
|
+
# El techo sube con el catalogo del servidor, y subir tarde no da un error: da un consejo
|
|
37
|
+
# equivocado. Los codigos de Bluesky, Discord, Telegram y Threads (961-977) y los dos frenos
|
|
38
|
+
# de ritmo de la fase de publicaciones ilimitadas (978, 979) nacieron por encima de 960 y
|
|
39
|
+
# caian fuera de toda familia.
|
|
40
|
+
ErrorRange(900, 979, "publication"),
|
|
35
41
|
ErrorRange(1000, 1003, "general"),
|
|
36
42
|
ErrorRange(1100, 1111, "organization"),
|
|
37
43
|
ErrorRange(1200, 1207, "role"),
|
|
38
|
-
|
|
44
|
+
# 1308 es el tope de APPS del plan, que nacio al quitar el candado de precio de la API.
|
|
45
|
+
ErrorRange(1300, 1308, "plan_limit"),
|
|
39
46
|
ErrorRange(1400, 1408, "plan_limit"),
|
|
40
47
|
ErrorRange(1500, 1512, "messaging"),
|
|
41
48
|
ErrorRange(1600, 1601, "contact"),
|
|
@@ -101,7 +108,7 @@ class PlanVortexError(Exception):
|
|
|
101
108
|
|
|
102
109
|
|
|
103
110
|
class AuthError(PlanVortexError):
|
|
104
|
-
"""500-
|
|
111
|
+
"""500-546 — tokens, client apps, permissions. Includes 501 and 522, the expired-token ones."""
|
|
105
112
|
|
|
106
113
|
|
|
107
114
|
class UserError(PlanVortexError):
|
|
@@ -117,7 +124,9 @@ class FileError(PlanVortexError):
|
|
|
117
124
|
|
|
118
125
|
|
|
119
126
|
class PublicationError(PlanVortexError):
|
|
120
|
-
"""900-
|
|
127
|
+
"""900-979 — publications, including per-network limits (characters, images, duration) and the
|
|
128
|
+
two rate brakes: 978 (publishing too fast on this account) and 979 (that network's daily cap).
|
|
129
|
+
"""
|
|
121
130
|
|
|
122
131
|
|
|
123
132
|
class OrganizationError(PlanVortexError):
|
|
@@ -125,7 +134,7 @@ class OrganizationError(PlanVortexError):
|
|
|
125
134
|
|
|
126
135
|
|
|
127
136
|
class PlanLimitError(PlanVortexError):
|
|
128
|
-
"""1300-
|
|
137
|
+
"""1300-1308 and 1400-1408 — the plan's quota, on the client or on the organization.
|
|
129
138
|
|
|
130
139
|
This is the error an integrator actually wants to tell apart: it is not fixed by retrying, it is
|
|
131
140
|
fixed by changing plan. That is why both ranges share a class.
|
|
@@ -1072,7 +1072,7 @@ class Error1(TypedDict):
|
|
|
1072
1072
|
"""
|
|
1073
1073
|
code: int
|
|
1074
1074
|
"""
|
|
1075
|
-
PlanVortex error code. Ranges: 500-
|
|
1075
|
+
PlanVortex error code. Ranges: 500-546 auth, tokens and client apps · 601-612 user · 700-715 social accounts · 800-810 files · 900-979 publications · 1000-1003 general · 1100-1111 organizations · 1200-1207 roles · 1300-1308 client plan · 1400-1408 organization plan · 1500-1512 messaging · 1600-1601 contacts · 1900-1906 payments · 2000-2099 products · 2100-2199 AI plans · 2200-2299 integrations.
|
|
1076
1076
|
"""
|
|
1077
1077
|
data: NotRequired[dict[str, Any]]
|
|
1078
1078
|
"""
|
|
@@ -1547,27 +1547,25 @@ class OrganizationsUserList(TypedDict):
|
|
|
1547
1547
|
class PlanData(TypedDict):
|
|
1548
1548
|
"""
|
|
1549
1549
|
The resources a plan grants. On a client it is what was contracted; on an organization, the slice of it that was assigned. The sum across all the organizations of a client can never exceed what the client has contracted.
|
|
1550
|
+
|
|
1551
|
+
Users are NOT here: every plan has unlimited users, so they are neither charged nor split between organizations. They are still counted, in `PlanUseData.users`. There are no `artificial_inteligence`, `whatsapp` or `stats` flags either: statistics and WhatsApp are on every plan, and what gates AI is `ai_credits` — a plan with credits has AI, and Free has zero.
|
|
1550
1552
|
"""
|
|
1551
1553
|
|
|
1552
1554
|
accounts: int
|
|
1553
1555
|
"""
|
|
1554
1556
|
Social accounts that may be connected.
|
|
1555
1557
|
"""
|
|
1556
|
-
publications: int
|
|
1557
|
-
"""
|
|
1558
|
-
Publications that may be sent per month.
|
|
1559
|
-
"""
|
|
1560
|
-
users: int
|
|
1561
|
-
"""
|
|
1562
|
-
Users with access.
|
|
1563
|
-
"""
|
|
1564
1558
|
space: float
|
|
1565
1559
|
"""
|
|
1566
1560
|
Storage, in GB.
|
|
1567
1561
|
"""
|
|
1568
1562
|
integrations: int
|
|
1569
1563
|
"""
|
|
1570
|
-
Connections to a third-party tool material is pulled from (Google Drive, an RSS feed). Not the same thing as an app: an app is API access, and it
|
|
1564
|
+
Connections to a third-party tool material is pulled from (Google Drive, an RSS feed). Not the same thing as an app: an app is API access, and it has its own allowance in `apps`.
|
|
1565
|
+
"""
|
|
1566
|
+
apps: NotRequired[int]
|
|
1567
|
+
"""
|
|
1568
|
+
Apps this plan allows: 1 on Free, 2 on Basic, 5 on Pro, 10 on Custom. Each one is a `client_id` with a secret, so each one is a key to the whole public API — which every plan has, Free included. Unlike accounts or storage this is NOT split between organizations: an app belongs to the client. On a use payload it is how many exist right now.
|
|
1571
1569
|
"""
|
|
1572
1570
|
twitter_credits: NotRequired[int]
|
|
1573
1571
|
"""
|
|
@@ -1577,17 +1575,20 @@ class PlanData(TypedDict):
|
|
|
1577
1575
|
"""
|
|
1578
1576
|
Monthly AI credits (1 credit = $0.001 of provider cost): 15 per plan orchestration pass, 2 per generated text, 70 per generated image. Resets monthly and does not roll over. A client using its own provider key (BYOK) does not consume them in that scope.
|
|
1579
1577
|
"""
|
|
1580
|
-
|
|
1578
|
+
|
|
1579
|
+
|
|
1580
|
+
class PlanUseData(PlanData):
|
|
1581
1581
|
"""
|
|
1582
|
-
|
|
1582
|
+
What is being consumed right now. Same shape as PlanData plus `publications` and `users`, which are counted but neither charged nor split between organizations.
|
|
1583
1583
|
"""
|
|
1584
|
-
|
|
1584
|
+
|
|
1585
|
+
publications: NotRequired[int]
|
|
1585
1586
|
"""
|
|
1586
|
-
|
|
1587
|
+
Publications created in the current calendar month. A METRIC, not a quota: publications are unlimited on every plan, so there is no limit to compare it against. What throttles publishing is rate, not plan — see the per-hour and per-network daily caps.
|
|
1587
1588
|
"""
|
|
1588
|
-
|
|
1589
|
+
users: NotRequired[int]
|
|
1589
1590
|
"""
|
|
1590
|
-
|
|
1591
|
+
Users with access right now. A METRIC, not a quota: users are unlimited on every plan, so there is no limit to compare it against.
|
|
1591
1592
|
"""
|
|
1592
1593
|
|
|
1593
1594
|
|
|
@@ -2399,7 +2400,7 @@ class DashboardDashboardPublicationRef(TypedDict):
|
|
|
2399
2400
|
|
|
2400
2401
|
|
|
2401
2402
|
class DashboardPlanUse(TypedDict):
|
|
2402
|
-
actual_use:
|
|
2403
|
+
actual_use: PlanUseData
|
|
2403
2404
|
"""
|
|
2404
2405
|
What the organization and its children are consuming right now.
|
|
2405
2406
|
"""
|
|
@@ -2641,7 +2642,7 @@ class Organization(TypedDict):
|
|
|
2641
2642
|
"""
|
|
2642
2643
|
The slice of the client's plan assigned to this organization. **Absent when nothing was assigned**, and then the organization shares whatever its nearest parent with a plan has — or, failing that, the client's unassigned remainder. Ask `GET /organizations/{id_organization}/limits` for the effective numbers instead of reading this.
|
|
2643
2644
|
"""
|
|
2644
|
-
actual_use: NotRequired[
|
|
2645
|
+
actual_use: NotRequired[PlanUseData]
|
|
2645
2646
|
"""
|
|
2646
2647
|
Current consumption. **Only present with `getUse=true`.** `twitter_credits` and `ai_credits` are what has been spent in the current calendar month; the rest is what exists right now.
|
|
2647
2648
|
"""
|
|
@@ -2957,7 +2958,7 @@ class ClientsClient(TypedDict):
|
|
|
2957
2958
|
"""
|
|
2958
2959
|
Read-only view of the client's own AI provider configuration (BYOK). Written via `PUT /clients/{id_client}/ai-settings`. API keys are stored encrypted and are NEVER returned: each scope only exposes provider, model and has_api_key.
|
|
2959
2960
|
"""
|
|
2960
|
-
actual_use: NotRequired[
|
|
2961
|
+
actual_use: NotRequired[PlanUseData]
|
|
2961
2962
|
"""
|
|
2962
2963
|
Current consumption across every organization of the client. **Only present with `getUse=true`.** `twitter_credits` and `ai_credits` are what has been spent this calendar month.
|
|
2963
2964
|
"""
|
|
@@ -31,6 +31,7 @@ from planvortex._generated.models import (
|
|
|
31
31
|
DashboardPublicationsSummary,
|
|
32
32
|
Organization,
|
|
33
33
|
PlanData,
|
|
34
|
+
PlanUseData,
|
|
34
35
|
Publication,
|
|
35
36
|
Upload,
|
|
36
37
|
)
|
|
@@ -118,7 +119,7 @@ class OrganizationUse(TypedDict):
|
|
|
118
119
|
with ``get_use=True``.
|
|
119
120
|
"""
|
|
120
121
|
|
|
121
|
-
actual_use: NotRequired[
|
|
122
|
+
actual_use: NotRequired[PlanUseData]
|
|
122
123
|
actual_asigned: NotRequired[PlanData]
|
|
123
124
|
|
|
124
125
|
|
|
@@ -12,7 +12,7 @@ from __future__ import annotations
|
|
|
12
12
|
|
|
13
13
|
import platform
|
|
14
14
|
|
|
15
|
-
VERSION = "0.
|
|
15
|
+
VERSION = "0.7.0"
|
|
16
16
|
|
|
17
17
|
# Sin barra final, y con el `/v1.0.0` dentro: la version de la API es parte de la ruta, no una
|
|
18
18
|
# cabecera, asi que una `base_url` sin ella da 404 en todo.
|
|
@@ -289,6 +289,18 @@ CAREFUL WITH THE SUM: what is assigned to a client's organizations can never exc
|
|
|
289
289
|
has contracted — asking for more comes back as a 1400-1408.
|
|
290
290
|
"""
|
|
291
291
|
|
|
292
|
+
PlanUseData: TypeAlias = _models.PlanUseData
|
|
293
|
+
"""What is being CONSUMED right now: a :data:`PlanData` plus ``publications``.
|
|
294
|
+
|
|
295
|
+
``publications`` is a METRIC, not a quota — publications are unlimited on every plan — so there is
|
|
296
|
+
no ceiling to compare it against: do not draw it as a bar. What throttles whoever publishes too
|
|
297
|
+
much is RATE (a per-hour cap per account, and the per-network daily caps in ``GET /social_limits``),
|
|
298
|
+
not the plan.
|
|
299
|
+
|
|
300
|
+
Not to be confused with :data:`PlanUse`, which is the dashboard's whole block: consumption, what
|
|
301
|
+
was handed down, and the limits together.
|
|
302
|
+
"""
|
|
303
|
+
|
|
292
304
|
Organization: TypeAlias = _models.Organization
|
|
293
305
|
"""An organization: the container of accounts, publications and files. They nest.
|
|
294
306
|
|
|
@@ -1115,6 +1127,7 @@ __all__ = [
|
|
|
1115
1127
|
"PersistentMenu",
|
|
1116
1128
|
"PlanData",
|
|
1117
1129
|
"PlanUse",
|
|
1130
|
+
"PlanUseData",
|
|
1118
1131
|
"PlannerTemplate",
|
|
1119
1132
|
"PlannerTemplateField",
|
|
1120
1133
|
"PlannerTemplateFieldType",
|
|
@@ -305,7 +305,19 @@ def test_cada_rango_del_catalogo_cae_en_su_clase() -> None:
|
|
|
305
305
|
"""
|
|
306
306
|
assert error_class_for_code(500) is error_class_for_code(544)
|
|
307
307
|
assert error_class_for_code(900).__name__ == "PublicationError"
|
|
308
|
+
# El rango llega hasta 979, no hasta 960: por encima del 960 estan las cuatro ultimas redes
|
|
309
|
+
# (Bluesky, Discord, Telegram y Threads) y los DOS FRENOS DE RITMO que sustituyeron al cupo
|
|
310
|
+
# mensual de publicaciones. Fuera de familia, el 978 llegaba como un error generico y quien lo
|
|
311
|
+
# recibia no tenia forma de saber que la respuesta es esperar.
|
|
312
|
+
assert error_class_for_code(975).__name__ == "PublicationError"
|
|
313
|
+
assert error_class_for_code(978).__name__ == "PublicationError"
|
|
314
|
+
assert error_class_for_code(979).__name__ == "PublicationError"
|
|
308
315
|
assert error_class_for_code(1300).__name__ == "PlanLimitError"
|
|
316
|
+
# 1308 es el tope de APPS del plan, y 545/546 los dos codigos que trajo abrir la API publica
|
|
317
|
+
# a todos los planes. Los tres nacieron por encima del techo que tenian sus rangos.
|
|
318
|
+
assert error_class_for_code(1308).__name__ == "PlanLimitError"
|
|
319
|
+
assert error_class_for_code(545).__name__ == "AuthError"
|
|
320
|
+
assert error_class_for_code(546).__name__ == "AuthError"
|
|
309
321
|
assert error_class_for_code(1408).__name__ == "PlanLimitError"
|
|
310
322
|
assert error_class_for_code(2299).__name__ == "IntegrationError"
|
|
311
323
|
# Los siete de las plantillas del planificador. No hay nada que registrar para que caigan aqui:
|
|
@@ -92,7 +92,7 @@ def _camino(
|
|
|
92
92
|
url=f"{BASE_URL}/clients/cli1/organizations?limit=1",
|
|
93
93
|
json={"organizations": [ORGANIZACION], "total": 1},
|
|
94
94
|
)
|
|
95
|
-
httpx_mock.add_response(url=f"{ORG}/limits", json={"accounts": 5, "
|
|
95
|
+
httpx_mock.add_response(url=f"{ORG}/limits", json={"accounts": 5, "users": 3})
|
|
96
96
|
httpx_mock.add_response(
|
|
97
97
|
url=f"{ORG}?getUse=true",
|
|
98
98
|
json={
|
|
@@ -77,7 +77,7 @@ def _camino(httpx_mock: HTTPXMock, *, publicacion: dict[str, Any] | None = None)
|
|
|
77
77
|
url=f"{BASE_URL}/clients/cli1/organizations?limit=1",
|
|
78
78
|
json={"organizations": [ORGANIZACION], "total": 1},
|
|
79
79
|
)
|
|
80
|
-
httpx_mock.add_response(url=f"{ORG}/limits", json={"accounts": 5, "
|
|
80
|
+
httpx_mock.add_response(url=f"{ORG}/limits", json={"accounts": 5, "users": 3})
|
|
81
81
|
httpx_mock.add_response(
|
|
82
82
|
url=f"{ORG}?getUse=true",
|
|
83
83
|
json={"organization": {**ORGANIZACION, "actual_use": {"accounts": 1, "publications": 12}}},
|
|
@@ -73,9 +73,9 @@ def test_las_hijas_se_listan_se_iteran_y_se_crean(cliente: ClienteDePrueba, http
|
|
|
73
73
|
|
|
74
74
|
def test_limits_llega_sin_sobre(cliente: ClienteDePrueba, httpx_mock: HTTPXMock) -> None:
|
|
75
75
|
"""La cascada ya resuelta, y en crudo. Es lo que hay que mirar, no `actual_plan`."""
|
|
76
|
-
httpx_mock.add_response(url=f"{ORG}/limits", json={"accounts": 5, "
|
|
76
|
+
httpx_mock.add_response(url=f"{ORG}/limits", json={"accounts": 5, "users": 3})
|
|
77
77
|
|
|
78
|
-
assert cliente.esperar(cliente.pv.organizations.limits("org1")) == {"accounts": 5, "
|
|
78
|
+
assert cliente.esperar(cliente.pv.organizations.limits("org1")) == {"accounts": 5, "users": 3}
|
|
79
79
|
assert ruta(unica(httpx_mock)) == "/organizations/org1/limits"
|
|
80
80
|
|
|
81
81
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|