@oxchannels/sdk 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,193 @@
1
+ # @oxchannels/sdk
2
+
3
+ > **Closed beta.** OxChannels is in a closed beta. This package is published so invited
4
+ > partners can install it with `npm install`, but it is useful only with an API key issued by
5
+ > the OxChannels team for your workspace, and there is no self-service sign-up yet. Endpoints,
6
+ > contracts and this SDK may still change between beta releases without a major version bump;
7
+ > read the changelog notes on each release before upgrading. To join the beta or report an
8
+ > issue, contact OxChannels at https://oxchannels.com.
9
+
10
+ Typed TypeScript client for the OxChannels REST API. Everything in it is generated from the sealed
11
+ OpenAPI contracts (`docs/api/generated/openapi-*-contract-v2.json`): `openapi-typescript@7.13.0`
12
+ produces four isolated path sets, `openapi-fetch@0.17.0` supplies the runtime, and there is no
13
+ handwritten wire DTO anywhere. The package directory in the repository is `packages/openapi-client`.
14
+
15
+ - Guide with the full publishing walkthrough: `QUICKSTART.md`, shipped inside this package
16
+ (source: `docs/guides/sdk-quickstart.md` in the OxChannels repository).
17
+ - Single-source example, type-checked on every test run: `examples/publish-everywhere.ts` in the
18
+ repository package directory.
19
+ - Runtime: Node 24 (or any runtime with WHATWG `fetch`); ESM only.
20
+
21
+ ## What you can do with it
22
+
23
+ - Read the workspace context and the publishing context: which channels are connected, which
24
+ targets are ready, what each target can carry (text, image, video, variants, limits).
25
+ - Upload media once and reuse it across targets.
26
+ - Create one publication per target against the capability snapshot you were shown, submit it,
27
+ and follow the operation to a confirmed effect with the provider's URL of the content.
28
+ - Receive webhooks about operation status and reconcile idempotently on retries.
29
+
30
+ Supported channels in the beta: Bluesky, Facebook pages, Instagram business accounts, LinkedIn,
31
+ Mastodon, Telegram channels, Threads, TikTok, X and YouTube. Every request is authenticated with a
32
+ workspace-scoped API key; the key carries scopes (`context:read`, `targets:read`,
33
+ `operations:read`, `operations:write`, `webhooks:manage`) and can be rotated or revoked in the
34
+ OxChannels panel.
35
+
36
+ ## Install
37
+
38
+ ```bash
39
+ npm install @oxchannels/sdk
40
+ ```
41
+
42
+ The tarball carries `dist/` (runtime and `.d.ts`), the four OpenAPI documents it was generated from
43
+ (`contracts/*.json`), `generated/provenance.json` with the SHA-256 of every source and output,
44
+ `QUICKSTART.md` and the licence. It has one runtime dependency, `openapi-fetch`.
45
+
46
+ ## First publication
47
+
48
+ The partner surface is what an external system uses to publish. Create one client per service key
49
+ (the key comes from the panel, API keys), then walk the flow: who am I, where can I publish,
50
+ upload media once, create a publication per target, submit the action, follow it.
51
+
52
+ ```ts
53
+ import { createOxStudioClient } from '@oxchannels/sdk';
54
+
55
+ const client = createOxStudioClient({
56
+ baseUrl: 'https://api.oxchannels.com',
57
+ serviceKey: () => process.env.OXS_API_KEY!,
58
+ });
59
+
60
+ const context = (await client.GET('/v1/integration/publishing-context')).data!;
61
+ for (const target of context.targets) {
62
+ if (target.readiness !== 'READY' || target.capability === null) continue;
63
+
64
+ const publication = (
65
+ await client.POST('/v1/integration/publications', {
66
+ body: {
67
+ targetId: target.targetId,
68
+ capabilityGeneration: target.capability.generation,
69
+ capabilityHash: target.capability.snapshotHash,
70
+ body: 'Hello from @oxchannels/sdk',
71
+ locale: 'en',
72
+ mediaAssetIds: [],
73
+ options: {},
74
+ clientReference: `my-run:${target.targetId}`,
75
+ },
76
+ })
77
+ ).data!;
78
+
79
+ const action = (
80
+ await client.POST('/v1/integration/publishing-actions', {
81
+ body: {
82
+ mode: 'PUBLISH_NOW',
83
+ contentRevisionId: publication.revision.id,
84
+ targetIds: [target.targetId],
85
+ oxStudioRun: {
86
+ schemaVersion: 'ox-studio-run.v1',
87
+ workflowRunId: 'my-run',
88
+ workflowTaskId: 'publish',
89
+ taskGeneration: 1,
90
+ },
91
+ },
92
+ })
93
+ ).data!;
94
+ console.log(target.connectorKey, action.actionId, action.outcome);
95
+ }
96
+ ```
97
+
98
+ `examples/publish-everywhere.ts` is the complete version: media upload with the scan wait,
99
+ per-target stale-snapshot recovery, polling the action to a terminal outcome and the webhook
100
+ subscription. Every non-2xx response is thrown as `OxChannelsApiErrorV2`, so `data` is defined
101
+ whenever a call returns.
102
+
103
+ ## Clients and surfaces
104
+
105
+ - `createPartnerClient` (alias `createOxStudioClient`) accepts an OxChannels service key as Bearer
106
+ authentication (`serviceKey`, or the equivalent `bearer` spelling; never both) and an optional
107
+ correlation ID. Its base URL must use HTTPS. Development HTTP is available only through
108
+ `allowInsecureLoopbackForDevelopment: true` and only for the exact loopback hosts `localhost`,
109
+ `127.0.0.1` or `[::1]`. Every request remains bound to the configured origin and uses
110
+ `redirect: error`, so a per-call override or redirect cannot move the service key to another
111
+ origin. It does not expose or simulate partner OAuth.
112
+ - `createPublicClient` covers health and unauthenticated identity bootstrap. Public and dashboard
113
+ transports accept HTTPS or a browser-relative same-origin base URL; development HTTP requires the
114
+ explicit loopback-only opt-in used by the privileged clients.
115
+ - `createDashboardV2Client` (also `createDashboardContractClient`) uses the browser session plus
116
+ CSRF for the dashboard projection. Both dashboard factories accept `expectedProviderBindingId`, a
117
+ selector-aware supplier used only for the exact BYO binding mutation path. Returning the current
118
+ secret-free binding UUID adds the `X-OxChannels-Expected-Binding-Id` compare-and-set header;
119
+ returning `undefined` performs initial binding creation. Malformed values and supplier failures
120
+ stop before transport.
121
+ - `createAgentBootstrapClient` covers only the one-time Local Agent enrollment surface, with the
122
+ same HTTPS, configured-origin and `redirect: error` boundary as the partner client.
123
+
124
+ Every client overwrites `OxChannels-Contract-Set` with the exact value `2.0.0` on every request.
125
+ The raw OpenAPI keeps that header required for fetch operations, while the SDK projection removes it
126
+ from caller-facing types; dashboard mutation types similarly omit the SDK-managed CSRF header.
127
+ Operations marked `x-oxchannels-browser-navigation-only` (local sign-in, social sign-in start and
128
+ callback, the MCP authorization front door and the provider OAuth callback) are removed from the
129
+ fetch projections: callers must navigate the browser to those redirect endpoints. The generator
130
+ requires those exact public GET operations, a sole `302` success and a required `Location` header;
131
+ missing or unknown markers fail generation.
132
+
133
+ Dashboard mutations fail before transport unless the configured CSRF supplier returns a safe value
134
+ of at least 16 characters, while reads do not require one. Partner, public and agent clients
135
+ explicitly omit browser credentials; only the dashboard client includes its session cookie. All
136
+ clients force `redirect: error`, the exact configured origin and the expected credentials mode.
137
+ Per-call base URL, redirect, credentials or transport overrides are rejected before an
138
+ authentication or CSRF supplier is read; SDK-managed credentials are attached only after request
139
+ middleware has completed.
140
+
141
+ ## Errors
142
+
143
+ A v2 error is parsed by the canonical browser-safe parser (copied by the generator from
144
+ `packages/contracts/src/api-errors.ts` into `src/generated/contracts/`, so the published package has
145
+ no workspace dependency) and thrown as `OxChannelsApiErrorV2`, preserving the stable `code`,
146
+ `category`, trace and correlation IDs, `retryable`, `effectEvidence`, optional `retryAfterSeconds`
147
+ and bounded validation `violations`. The parsed code must also match the HTTP status contract
148
+ (`409` allows only the two conflict variants, `5xx` only the safe internal variant, unlisted `4xx`
149
+ only validation); a mismatch, malformed body or oversized error becomes
150
+ `OxChannelsApiProtocolError`. Response bodies and service keys are never copied into that error.
151
+ Error bodies are streamed into a strict 64 KiB budget and the stream is cancelled on overflow. The
152
+ unauthorized hook runs only after a canonical `AUTHENTICATION_REQUIRED` response is parsed. Caller
153
+ abort signals are combined with, and cannot disable, the SDK timeout (15 s by default, at most
154
+ 120 s); the timeout is released only when the response body reaches EOF or is cancelled. The
155
+ legacy `OxChannelsApiError` remains as the base class.
156
+
157
+ None of the wrappers retries writes; recovery continues to use explicit idempotency lookup and
158
+ operation status. See `docs/guides/idempotency.md` in the OxChannels repository and the
159
+ "Idempotency and retries" section of `QUICKSTART.md`.
160
+
161
+ ## Generated sources and provenance
162
+
163
+ `pnpm openapi:generate` (from the repository root) regenerates the four projections, copies the two
164
+ contract runtime sources and writes `generated/provenance.json` with separate source and output
165
+ SHA-256 values. `pnpm openapi:check` rejects a missing projection, provenance drift, trust-surface
166
+ drift, fake `/v2` wire paths, a v2 operation without the mandatory contract-set header, and a
167
+ runtime-source copy that no longer matches its origin.
168
+
169
+ Projections are generated with `immutable: false` on purpose: `openapi-fetch` only recognises
170
+ mutable arrays when it derives the `data` type, so a `readonly` projection would leave response
171
+ arrays without `map`, `find` or iteration. Request bodies therefore expect plain arrays; spread a
172
+ `readonly` array before passing it.
173
+
174
+ ## Versioning and release
175
+
176
+ The package version (`1.0.0` first) is independent of the sealed contract distribution recorded in
177
+ `contracts.lock.json`. `.github/workflows/release-sdk.yml` builds, tests, compares the four
178
+ contracts of the previously published tarball with the current documents to classify the semver
179
+ level (`scripts/contracts/diff-surface.mjs`), publishes with `pnpm publish --access public` and
180
+ tags `sdk-v<version>`. Pack and publish go through pnpm, never npm: the manifest uses the
181
+ `catalog:` and `workspace:` protocols and only pnpm rewrites them to concrete versions. The
182
+ `prepack` script copies the contracts and the licence into the package directory; both are
183
+ git-ignored there. Details in
184
+ `docs/guides/contract-versioning.md` in the OxChannels repository.
185
+
186
+ ## Development
187
+
188
+ ```bash
189
+ pnpm --filter @oxchannels/sdk build # tsc -> dist/
190
+ pnpm --filter @oxchannels/sdk test # runtime tests + type test of the example and consumer
191
+ pnpm --filter @oxchannels/sdk lint
192
+ cd packages/openapi-client && pnpm pack --pack-destination /tmp/sdk-pack
193
+ ```