You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(connectors): callApi for metered connectors (#256)
* feat(connectors): callApi for metered connectors
Some connectors are backed by paid third-party APIs that charge Base44 per call.
For those the OAuth token is not available to app code — getConnection and its
siblings reject with a 403 — because the Base44 proxy is the only place those
calls can be counted. Adds the three proxy methods that replace them:
- callApi(integrationType, request) — shared platform connector
- callWorkspaceApi(connectorId, request) — workspace-registered connector
- callCurrentAppUserApi(connectorId, request) — per-app-user connector
Each mirrors its getConnection counterpart, so the identifier you already use
carries over.
Two deliberate shape decisions:
- An upstream 4xx/5xx resolves with `success: false` and the provider's own
`status`/`data` rather than throwing. It is a normal outcome of a call Base44
completed and billed; only Base44-side failures (no connection, credits
exhausted, a rejected request) reject.
- `query` is always sent, never dropped. The server prices the merged query
string, so a client that accepted the field and then discarded it would make
the quoted price and the real request disagree.
Responses carry `creditsCharged` so callers can see what a call actually cost,
and the module docs call out that cost varies sharply by endpoint — an expensive
call inside a loop is the failure mode worth warning about.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(connectors): callApi only — metering follows whose OAuth app it is
Drops callWorkspaceApi and callCurrentAppUserApi. They implied that
workspace-registered and app user connectors can be metered, and they can't: both
run on the workspace's *own* OAuth app, so the provider invoices the workspace
directly. Proxying them would have billed the customer credits on top of a vendor
bill they already pay.
Only a platform connector runs on Base44's OAuth app, so callApi is the only one
of the three that ever had something to meter. The backend's matching routes are
gone too (base44-dev/apper#19753).
The module docs now say which connectors this applies to and, more usefully, why
the other two don't — so the next person doesn't re-add the methods on the
assumption they were an oversight.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(connectors): align proxy response contract
* feat(connectors): cover host selection and binary responses in callApi
The proxy grew two capabilities after this module was written, and `proxyCall`
builds its payload field by field — so `host` was dropped on the floor even when
a caller passed it, and a binary response arrived with `data: null` and no way to
reach the bytes.
- `host` is forwarded when set and omitted when not, so the proxy applies the
connector's declared default rather than receiving an explicit null.
- `dataBase64` / `contentType` are mapped from the wire, for the media types a
connector declares as binary. Set instead of `data`, never alongside it.
- `body` no longer claims to be ignored for DELETE; the proxy forwards it, and
several provider APIs require it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* 3. In a backend function, call {@linkcode getCurrentAppUserConnection | getCurrentAppUserConnection()} using the service role client (`base44.asServiceRole.connectors`) with the connector ID to retrieve the app user's token.
79
162
* 4. Use the returned `accessToken` to call the external service's API directly. Some connectors also return a `connectionConfig` with additional values such as a subdomain for building the API URL.
80
163
*
164
+
* ## Metered connectors
165
+
*
166
+
* A few [platform connectors](#shared-connectors) are backed by paid third-party APIs that charge Base44 per call. For those, the OAuth token is **not** available to your code — {@linkcode getConnection | getConnection()} rejects with a `403`. Call them with {@linkcode callApi | callApi()} instead: Base44 attaches the credential server-side, forwards the request, and bills your workspace's integration credits for the call.
167
+
*
168
+
* This applies to platform connectors only. A workspace-registered or app user connector runs on **your own** OAuth app, so the provider invoices you directly and there is nothing for Base44 to meter — those keep normal token access via {@linkcode getWorkspaceConnection | getWorkspaceConnection()} and {@linkcode getCurrentAppUserConnection | getCurrentAppUserConnection()}.
169
+
*
170
+
* Two things to keep in mind when writing against a metered connector:
171
+
*
172
+
* - **Cost varies by endpoint, sometimes sharply.** The same connector can charge two orders of magnitude more for one endpoint than another, so avoid putting an expensive call inside a loop and batch wherever the provider supports it. Each response reports what it actually cost as `creditsCharged`.
173
+
* - **Provider and transport outcomes are returned, not thrown.** A provider `4xx`/`5xx` or a connection failure comes back as `success: false` with its `phase`; authorization, quota, and invalid proxy requests reject the promise.
174
+
* - **Only `phase: 'not_sent'` proves the provider did not execute the request.** A timeout or in-flight failure may have executed upstream, so do not automatically retry a non-idempotent call unless the provider supports an idempotency key.
175
+
*
81
176
* ## Available connectors
82
177
*
83
178
* The connectors below can be used as shared connectors or as app user connectors. For a shared platform connector, pass the integration type string to {@linkcode getConnection | getConnection()}. For a connector you register in Workspace Settings with your own OAuth app, use the connector ID with {@linkcode getWorkspaceConnection | getWorkspaceConnection()} for a shared token, or with {@linkcode getCurrentAppUserConnection | getCurrentAppUserConnection()} for a per-user token.
* Calls a [metered connector's](#metered-connectors) API through the Base44 proxy.
446
+
*
447
+
* Use this for a shared platform connector identified by an integration type. Base44 adds the OAuth credential to the outgoing request, forwards it, and bills the workspace for the call, so you never handle the token yourself.
448
+
*
449
+
* @param integrationType - The type of integration, such as `'x'`. See [Available connectors](#available-connectors).
450
+
* @param request - The upstream request to forward. See {@link ConnectorApiRequest}.
451
+
* @returns Promise resolving to a {@link ConnectorApiResponse}. Note that an upstream error is reported in `success` and `status`, not thrown — only Base44-side failures reject.
452
+
*
453
+
* @example
454
+
* ```typescript
455
+
* // Post to X
456
+
* const res = await base44.asServiceRole.connectors.callApi('x', {
457
+
* method: 'POST',
458
+
* path: '/2/tweets',
459
+
* body: { text: 'Shipped!' },
460
+
* });
461
+
*
462
+
* if (!res.success) {
463
+
* console.error('X rejected the post', res.status, res.data);
464
+
* }
465
+
* ```
466
+
*
467
+
* @example
468
+
* ```typescript
469
+
* // Read, with query parameters and a look at what the call cost
470
+
* const res = await base44.asServiceRole.connectors.callApi('x', {
0 commit comments