@pipelex/sdk 0.61.0 → 0.63.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.
@@ -86,7 +86,7 @@ export interface MethodVersionSummary {
86
86
  /** The method's description at publish time, as the draft held it (see `MethodData.description`). */
87
87
  description: string | null;
88
88
  /**
89
- * The method's categories at publish time, as slugs of the catalog's list (see
89
+ * The method's categories at publish time, as its manifest declared them (see
90
90
  * `MethodData.categories`); absent from a platform that predates them.
91
91
  */
92
92
  categories?: string[];
@@ -144,10 +144,11 @@ export interface MethodData {
144
144
  */
145
145
  description?: string | null;
146
146
  /**
147
- * The draft's categories, read from its manifest's `categories` under `[package]` and kept when
148
- * they are on the catalog's list, as slugs such as `human-resources`, in their declared order;
149
- * empty for a draft with no manifest or none it knows, and absent from a platform that predates
150
- * them. A person reads a category's label, which `listMethodTemplates` names beside its slug.
147
+ * The draft's categories, read from its manifest's `categories` under `[package]`: each
148
+ * well-formed slug, such as `human-resources`, once, in its declared order, whether or not it is
149
+ * on the catalog's list; empty for a draft with no manifest or none well-formed, and absent from
150
+ * a platform that predates them. The catalog shows a template under those on its list alone,
151
+ * which `listMethodCategories` reads, each category with its labels.
151
152
  */
152
153
  categories?: string[];
153
154
  created_at: string;
@@ -245,7 +246,7 @@ export interface PublicMethodDetail {
245
246
  name: string;
246
247
  /** The latest published version's description. */
247
248
  description: string | null;
248
- /** The latest published version's categories, as slugs of the catalog's list. */
249
+ /** The latest published version's categories, as its manifest declared them. */
249
250
  categories: string[];
250
251
  created_at: string;
251
252
  /** The number of the latest published version, the one a reader reads, runs and copies. */
@@ -495,12 +496,24 @@ export interface MethodVersionPage {
495
496
  items: MethodVersionSummary[];
496
497
  nextCursor: string | null;
497
498
  }
498
- /** One category of the catalog's list: the slug a manifest declares, and the English label a person reads. */
499
+ /**
500
+ * One category of the catalog's list: the slug a manifest declares, and the labels a person reads.
501
+ */
499
502
  export interface MethodCategory {
500
503
  /** The stable slug, such as `human-resources`, which a method's `categories` hold. */
501
504
  slug: string;
502
- /** The English label, such as `Human resources`; a client translating by slug falls back on it. */
505
+ /**
506
+ * The English label, such as `Human resources`: what a client that does not translate shows, and
507
+ * what one that does falls back on for a language `labels` lacks.
508
+ */
503
509
  label: string;
510
+ /**
511
+ * The label in each language the platform holds one for, by language tag, such as
512
+ * `{ en: "Human resources", fr: "Ressources humaines" }`; `labels.en` equals `label`. A category
513
+ * stored before the platform spoke a language lacks a label in it, so read `labels[locale] ??
514
+ * label`.
515
+ */
516
+ labels: Partial<Record<string, string>>;
504
517
  }
505
518
  /** One thumbnail of a template's latest published version, with a link to its bytes. */
506
519
  export interface MethodTemplateThumbnail extends MethodThumbnail {
@@ -566,10 +579,17 @@ export interface MethodTemplatePage {
566
579
  ordering: string | null;
567
580
  /**
568
581
  * Every category at least one template shows, whatever the filter, in the catalog's order, each
569
- * with its label: the tabs a client draws, none of them ever empty.
582
+ * with its labels: the tabs a client draws, none of them ever empty.
570
583
  */
571
584
  categories: MethodCategory[];
572
585
  }
586
+ /**
587
+ * The catalog's whole list of categories — `listMethodCategories`: every category on it, in the
588
+ * catalog's order, each with its labels, whether or not a template shows it.
589
+ */
590
+ export interface MethodCategoryList {
591
+ categories: MethodCategory[];
592
+ }
573
593
  /**
574
594
  * The copy — `copyMethod`, `POST /v1/methods/{id}/copy`. Its wire body is closed: the client
575
595
  * sends these members and nothing else, a `null` reading as not given.
@@ -909,7 +929,12 @@ export interface LogoDescriptor {
909
929
  * `colors` is the light set and `dark_colors` the dark one, used in dark mode only: a dark colour
910
930
  * that is `null` falls back to the light colour of the same role, and a light one that is `null`
911
931
  * to Pipelex's own palette. A logo is `null` when its slot is empty. A personal workspace, or a
912
- * team that set nothing, answers every colour and logo `null`.
932
+ * team that set nothing, answers every colour and logo `null`, no style and no typeface.
933
+ *
934
+ * `style` and `typeface` are ids the platform holds to a shape and never to a list, so a client
935
+ * reads an id it does not know as unset. The client reads both as `null`, and `white_label` as
936
+ * `false`, when the answer leaves them out, as a platform that predates them does, or sends a
937
+ * value of another type.
913
938
  */
914
939
  export interface OrganizationBranding {
915
940
  org_id: string;
@@ -921,12 +946,27 @@ export interface OrganizationBranding {
921
946
  square_logo: LogoDescriptor | null;
922
947
  /** The wide logo, at least one and a half times as wide as it is tall, for an app's header. */
923
948
  wide_logo: LogoDescriptor | null;
949
+ /**
950
+ * The id of the app style the team chose for its method apps, such as `editorial`: how their
951
+ * headings, panels, corners and spacing look. `null` when not set. Both themes share it, and a
952
+ * client applies it whenever one is set; it does not count towards whether an app is branded.
953
+ */
954
+ style: string | null;
955
+ /** The id of the typeface the team sets its apps' text in, such as `manrope`; `null` when not set. */
956
+ typeface: string | null;
957
+ /**
958
+ * Whether the organization holds the white-label entitlement: its method apps show no Pipelex
959
+ * mark anywhere a runner looks, branded or not. `false` for every other organization, a personal
960
+ * workspace included. Pipelex staff grant it; no branding write changes it, and
961
+ * `OrganizationBrandingUpdate` has no such key.
962
+ */
963
+ white_label: boolean;
924
964
  }
925
965
  /**
926
966
  * Changes to one colour set. A key set to a colour sets it, a key set to `null` clears it, and a
927
967
  * key left out keeps its value. A colour is `#rrggbb` in either case, stored in lower case; the
928
- * platform refuses anything else with a `422` `validation_failed` whose `errors[]` entry names the
929
- * key, such as `colors.action`.
968
+ * platform refuses anything else with a `422` `request.invalid` naming the key, such as
969
+ * `colors.action`, in `location.field` (see `OrganizationBrandingUpdate`).
930
970
  */
931
971
  export interface BrandingColorsUpdate {
932
972
  action?: string | null;
@@ -947,12 +987,23 @@ export interface BrandingColorsUpdate {
947
987
  * `logo_not_an_upload`, so a logo cannot move between slots by its URI. The platform reads and
948
988
  * measures an upload before it accepts it, keeps its own copy under `branding/`, whose URI it
949
989
  * answers, and erases the copy a write replaces or clears; the upload is left as it is.
990
+ * - `style` and `typeface` take an id of one to 32 lower-case letters, digits and hyphens
991
+ * (`^[a-z0-9-]{1,32}$`), stored as it is whether or not such a style or typeface exists, or
992
+ * `null` to clear it. Writing one leaves the colours, the logos and the other as they are.
993
+ *
994
+ * A field the platform refuses is named in `location.field`: a colour by its dotted key, such as
995
+ * `colors.action`, a style or a typeface by its key. When it refuses one field alone, the problem
996
+ * itself names it, in `params.field` too; when it refuses several, each item of its `errors` names
997
+ * one. There is no `white_label` key: the entitlement is Pipelex staff's to grant, and the
998
+ * platform refuses a body naming it as it refuses any key it does not know.
950
999
  */
951
1000
  export interface OrganizationBrandingUpdate {
952
1001
  colors?: BrandingColorsUpdate | null;
953
1002
  dark_colors?: BrandingColorsUpdate | null;
954
1003
  square_logo?: string | null;
955
1004
  wide_logo?: string | null;
1005
+ style?: string | null;
1006
+ typeface?: string | null;
956
1007
  }
957
1008
  export interface SubscriptionResponse {
958
1009
  plan: string | null;
package/dist/version.d.ts CHANGED
@@ -3,4 +3,4 @@
3
3
  * `User-Agent` without importing the public barrel. Kept in sync with
4
4
  * `package.json` — enforced by `tests/index.test.ts`.
5
5
  */
6
- export declare const SDK_VERSION = "0.61.0";
6
+ export declare const SDK_VERSION = "0.63.0";
package/dist/version.js CHANGED
@@ -3,5 +3,5 @@
3
3
  * `User-Agent` without importing the public barrel. Kept in sync with
4
4
  * `package.json` — enforced by `tests/index.test.ts`.
5
5
  */
6
- export const SDK_VERSION = "0.61.0";
6
+ export const SDK_VERSION = "0.63.0";
7
7
  //# sourceMappingURL=version.js.map
@@ -159,10 +159,10 @@ The hosted catalog/account routes the webapp drives. All ride the same `{base}/v
159
159
  - **User profile:** `getMe` (`GET /v1/me`).
160
160
  - **Methods catalog:** `listMethods`, `getMethod`, `createMethod`, `writeDraft`, `renameMethod`, `publishMethod`, `listMethodVersions`, `getMethodVersion`, `deleteMethod`. **A saved method has a draft and immutable published versions.** Its draft is a method tree with its `input_data`, which `writeDraft` (`PUT /v1/methods/{id}/draft`) replaces whole, without validating it; `updated_at` is the draft's token, which moves on every draft write and nothing else, and `draft_digest` the draft's tree digest (`sha256:` and 64 hex digits), `null` for an empty draft. A save takes the tree as `files`, sent as given, or as `directory`, `tree` or `contents`, sent inline or by hash after uploading the blobs the organization lacks, and a read lists the tree's files, each with a short-lived link to its bytes, which `writeMethodTree` writes to disk ([method-trees.md](./method-trees.md)). A draft write may carry `expected_updated_at`, the token the caller last saw, and is then a compare-and-swap: a draft that moved since is a `409` `pipelex.catalog.update_conflict` and nothing is written. `renameMethod` (`PATCH /v1/methods/{id}`) changes the name alone and moves no token. `publishMethod` (`POST /v1/methods/{id}/publish`) takes the required `expected_draft_updated_at` and answers a `MethodPublishResult` discriminated on `outcome` — `published` with the new version, `unchanged` with the existing latest version when the draft's digest equals it, `refused` with a `reason` (`invalid` or `not_runnable`, and for a public method `storage_reference`, `thumbnail_missing` or `thumbnail_invalid`, read open so a later reason arrives as its raw string), a `message` and the runner's `validation` verdict, its problem items read member by member as `validate` reads them — every arm carrying the `method`; the SDK reads the nested method as `getMethod` does, and an answer whose `outcome` is none of these, or a refusal whose `validation` is not a verdict object, is an answer it cannot read. A publish that produced no verdict is thrown rather than answered: only a draft that differs from the latest version reaches the runner, and a runner that cannot be reached, answers unusably or does not finish within the platform's deadline is a `502` or `503` with nothing written, so a retry is safe, and a retry of a publish that landed while its answer was lost answers `unchanged` with the version it wrote. A method is stored as one item that keeps room for the summary a publish adds, so a draft that saves can always be published: a creation or a draft write is a `413` `request.too_large` when it would grow the method past that room, below the store's item limit, and a rename is the same `413` for a name so long it would leave the method too large to publish. A version is numbered from 1, never reused, and holds the draft's tree as it stood, its `tree_digest` (`null` for a version stored before methods were trees), its `crate_fingerprint` (`null` when the runner could not resolve the bundle in memory), the runner's version, and when and by whom it was published; `listMethodVersions` pages the summaries newest first (`MethodVersionPage`, `{items, nextCursor}`, a `limit` from 1 to 100, 20 by default, and a `422` outside that range), and `getMethodVersion` reads one with its `files` listed as a method's are, or answers a `500` `runtime.unexpected` for a version stored before methods were trees and never rewritten as one. A method read carries `latest_version` and `latest_published`, both `null` until the first publish, so the publish state is derived: never published, draft unchanged (`draft_digest` not `null` and equal to `latest_published.tree_digest`), or draft ahead. The method routes take a bare id and address the method itself, never one of its versions; the platform does not parse a suffix there, so `mt_x@3` would answer a `404` that reads as a method that does not exist, and each route refuses a suffixed id with `RequestArgumentError` before anything is sent, naming `parseMethodSelector` and `getMethodVersion`. The whole-method `updateMethod` is gone, replaced by the draft write and the rename. **`deleteMethod` is asynchronous and its return type says so:** `DELETE /v1/methods/{id}` answers `202` with a `MethodDeletionAccepted` (`method_id`, `deletion_state`, `deletion_job_id`) the moment the platform has claimed the method and terminated its in-flight workflows — the rest of the cascade (runs, events, S3 objects) is enqueued. A resolved promise means "accepted", never "gone"; completion is the row disappearing from `listMethods`, not any field of that body. The claim is a conditional write, so a double-clicked delete is a `409` `pipelex.catalog.being_deleted` rather than a second cascade over the same runs, and an unknown or foreign-org id is a `404`. The erasure deletes the method's versions with the rest. (This doc previously stated the SDK had no `delete` because the platform had no such route — the route has always existed; the claim was simply false.) `listMethods` returns **one page**, not the whole catalog — a `MethodPage` (`{items, nextCursor}`) served from a narrow index projection, so its cost is the page size rather than the size of the catalog. It used to return every method with its full `.mthds` bundle attached, which hit DynamoDB's 1 MB page cap at a couple hundred methods and came back truncated with no error and no flag, so methods silently vanished from the UI. Rows are therefore `MethodSummary`, **not** `MethodDetail`: no `files`, no `updated_at`, because neither is in the projection and putting the source back is what restored the truncation. Reach for `getMethod(id)` when you need a method's files. `query.q` is applied server-side across the whole catalog, not over the page — filtering one page client-side would be searching 50 rows of 10,000 and calling it a search. `query.published` filters the same way, sent as `published=true` (the methods with a published version) or `published=false` (those never published), and combines with `q`. Each row carries the method's publish state, so a list tells its methods apart without reading each one: `latest_version` and `latest_published` (a `MethodVersionSummary`), both `null` when the method was never published, and `draft_digest`, `null` for an empty draft; all three are optional, absent from a platform that does not serve them. Never published is `latest_version === null`, the draft unchanged is a `draft_digest` that is not `null` and equals `latest_published.tree_digest`, and the draft ahead is two digests that differ. Both filters run after the index read, so **a filtered page can come back empty and still carry a `nextCursor`**: an empty page is not the end of the list, and only a `null` cursor is. `iterateMethods` is the async iterator that follows the cursor for callers who genuinely want everything (`for await (const m of client.iterateMethods())`); it keeps going **past empty pages**, since `{items: [], nextCursor: "…"}` means "nothing matched in the slice I just read, keep going". A server that keeps minting fresh cursors forever stops it at a page ceiling far past any real catalog with a `PagingNotTerminatingError` carrying `pageLimit`, never a silently truncated stream; `iterateRuns` shares the same loop. It gives up only when the server stops advancing its cursor, and **throws** rather than returning a partial answer; like `iterateRuns` it is deliberately not a `listAllMethods(): Promise<MethodSummary[]>`, because an all-at-once helper needs a page cap and a cap means silently returning a truncated list. `MethodSummary.deletion_state` rides the list rows: a method mid-erasure stays listed so the UI can render it as "Deleting…", while `getMethod` refuses it with a 409. Full read responses (`MethodDetail`, from `getMethod`) carry `org_id`, `created_by_user_id`, a server-derived `description` (recomputed on every draft write from the manifest's `description`, else the primary bundle's top-level `description`, else its main pipe's), `categories` and `visibility`, the draft token and digest, the latest version's summary and the draft's `files` on top of the write contract's `name` / `input_data`; the writes and the rename answer `MethodData`, the same method without `files`. Another organization's public method reads as a `PublicMethodDetail`, its latest published version and nothing of its draft, so `getMethod` is typed `MethodDetail | PublicMethodDetail`, told apart by `updated_at`; an answer without it that carries a member only the owner's read has is an answer the SDK cannot read, and so is a public read without its `latest_version` and `latest_published` ([public-methods.md](./public-methods.md)).
161
161
  - **Method examples:** `createMethodExample`, `listMethodExamples`, `iterateMethodExamples`, `getMethodExample`, `updateMethodExample`, `recordMethodExampleLatestRun`, `saveAssistantMethodExample`, `deleteMethodExample`, on `/v1/methods/{id}/examples`. An example is a named set of inputs saved on a method, shared across the organization, with an `input_revision` that rises each time its inputs change and the `latest_run_id` of those inputs, which an inputs edit clears. `recordMethodExampleLatestRun` records a run against the revision whose inputs it used and answers a `MethodExampleLatestRunResult` discriminated on `outcome`, `recorded` or `stale`, each arm carrying the `example`: a run that outlasted an edit is a verdict, never thrown, and an answer off the union is one the SDK cannot read; `saveAssistantMethodExample` is the build assistant's save, which writes into the example it names only while that example is still the assistant's (`kept_by_assistant`) at the revision it read, and otherwise creates one, answering a `MethodExampleAssistantSaveResult` discriminated on `outcome`, `kept` or `created`; one helper, `readVerdict`, reads these verdicts and `publishMethod`'s, the example checked once, by the reader every example goes through. Every answer carrying an example is read with `parseExactJson` and every request body written with `stringifyExactJson`, so an integer past `Number.MAX_SAFE_INTEGER` in the inputs reads as a `bigint` and goes back digit for digit, to an edit or as a run's inputs; `3.0` still reads as `3`, and inputs holding one sent back unchanged are an edit for the platform, which compares inputs as canonical JSON. Every example the client hands back, the page rows and the verdict's `example` included, is checked by `readMethodExample` against the fields the Python SDK's model requires, with the strict types it holds them to, an example breaking one being an answer the SDK cannot read. The list is paged oldest first by an opaque cursor, and a page may be short while `nextCursor` is set; `iterateMethodExamples` follows it through the loop `iterateMethods` and `iterateRuns` share, and refuses a method id at the call rather than at the first pull. Every method takes a bare catalog id, and the client refuses an example id that is empty, `.` or `..` or holds a lone surrogate, a create without its name or inputs or whose `run_id` is given but empty, an edit naming neither `name` nor `inputs`, and a latest-run write without a run id or a positive integer revision before sending. See [method-examples.md](./method-examples.md).
162
- - **Template catalog:** `listMethodTemplates`, `iterateMethodTemplates`, on `GET /v1/method-templates`. The public methods of the deployment's templates organization, in the order the platform serves for the `ordering` asked for, filtered by `category`, with the page's `categories` and each card's thumbnails behind short-lived links. `listMethodTemplates` reads the page with its own reader rather than `requestPage`, since its `ordering` and `categories` sit beside `items` and `nextCursor`, and redacts the links of an answer it cannot read; `iterateMethodTemplates` follows the cursor with the same ordering and category ([public-methods.md](./public-methods.md)).
162
+ - **Template catalog:** `listMethodTemplates`, `iterateMethodTemplates`, on `GET /v1/method-templates`, and `listMethodCategories`, on `GET /v1/method-categories`. The public methods of the deployment's templates organization, in the order the platform serves for the `ordering` asked for, filtered by `category`, with the page's `categories` and each card's thumbnails behind short-lived links. `listMethodTemplates` reads the page with its own reader rather than `requestPage`, since its `ordering` and `categories` sit beside `items` and `nextCursor`, and redacts the links of an answer it cannot read; `iterateMethodTemplates` follows the cursor with the same ordering and category. `listMethodCategories` reads the catalog's whole list of categories, which an admin of the platform keeps, in its order. Both check each category with one reader, `methodCategoriesFault`: a string `slug` and `label`, and `labels`, an object of strings by language tag ([public-methods.md](./public-methods.md)).
163
163
  - **Method copies:** `copyMethod`, on `/v1/methods/{id}/copy`. `copyMethod` makes a new, unpublished method in an organization the caller may change methods in, the method's own for a duplicate, holding the original's draft tree, its form inputs and its examples, the stored files they name copied into the destination, and answers a `MethodCopyResult` discriminated on `outcome`: `copied` with the copy as `method` and what was copied, or `refused` with the `references` of a bundle naming storage the destination cannot read or, in a duplicate, a file under the original's run files, which erasing the original would remove. The two arms carry different members, so the verdict is read by the copy itself rather than by `readVerdict`, which reads verdicts whose arms share one: an `outcome` off the union, a `copied` answer whose `method` is not an object and a `refused` one whose `references` is not an array are each an answer the SDK cannot read. Its body is closed, `destination_org_id` with `name` and `expected_draft_updated_at` when given, and its last argument takes an `idempotencyKey`, sent as the `Idempotency-Key` header through the same `requestRaw` option and the same `idempotencyKeyOf` check a start uses. Every method read carries `copied_from`, the original a copy names, `null` on a method created afresh. `MethodErrorCode` names the copy's own codes, `pipelex.org.not_found` and `pipelex.catalog.copy_too_large`, whose `limit` member is typed `MethodCopyLimit`. It takes a bare catalog id. See [method-copy.md](./method-copy.md).
164
164
  - **Organizations:** `listMemberships`, `createOrganization`, `renameOrganization` (`PATCH`). Each membership carries the caller's `role_in_org`, an `OrgRole` (`admin`, `member` or `runner`) read open: a role a later platform adds arrives as its raw string, so a `switch` over it says what an unknown role does. The role is for display; what it may do is the platform's to decide, and a call refused to the caller's role is an `ApiResponseError`. The org "switch" is a WorkOS-session op (`/api/active-org`), not a `/v1` route — out of SDK scope.
165
- - **Organization branding:** `getBranding` (`GET /v1/branding`, any role, web session or API key) and `updateOrganizationBranding` (`PATCH /v1/organizations/{org_id}/branding`, an admin's write) answer the same `OrganizationBranding`: the organization's name, its light and dark colour sets (`BrandingColors`) and its square and wide logos (`LogoDescriptor`). The write's `OrganizationBrandingUpdate` sets a key given a value, clears a key given `null` and keeps a key left out; a refused logo or colour is named in `ApiResponseError.errors`, by slot or by dotted key, a logo the platform measured with a `LogoRefusalCode`. See [organization-branding.md](./organization-branding.md).
165
+ - **Organization branding:** `getBranding` (`GET /v1/branding`, any role, web session or API key) and `updateOrganizationBranding` (`PATCH /v1/organizations/{org_id}/branding`, a write by the `admin` or the `member` role, never a `runner`) answer the same `OrganizationBranding`: the organization's name, its light and dark colour sets (`BrandingColors`), its square and wide logos (`LogoDescriptor`), its app `style` and `typeface`, and its read-only `white_label` entitlement, the last three read as `null`, `null` and `false` from a platform that predates them. The write's `OrganizationBrandingUpdate` sets a key given a value, clears a key given `null` and keeps a key left out; a refused field is named by its `location.field`, a colour by its dotted key, a logo by its slot in `ApiResponseError.errors` with a `LogoRefusalCode` when the platform measured it. See [organization-branding.md](./organization-branding.md).
166
166
  - **Caller webhooks:** `listRunWebhookDeliveries`, `getRunWebhookDelivery` and `replayRunWebhookDelivery`, on `/v1/runs/{id}/webhook-deliveries`, `/v1/runs/{id}/webhook-deliveries/{delivery_id}` and its `/replay`, read a run's delivery log and send a settled delivery again, any member by web session or API key, the replay taking an `idempotencyKey` under which the platform stores its answer; `getWebhookSecret`, on `GET /v1/organizations/{org_id}/webhook-secret`, and `rotateWebhookSecret`, on `POST /v1/organizations/{org_id}/webhook-secret/rotate`, read and rotate the organization's signing secret, an admin's routes from a web session only, the rotation taking an `idempotencyKey` the platform never replays. See [webhooks.md](./webhooks.md).
167
167
  - **Billing:** `getSubscription`, `listPlans`, `listInvoices`, `createCheckout`, `changePlan` (409 `conflict` ⇒ no subscription), `getBillingPortal` (409 `conflict` ⇒ no subscription).
168
168
  - **Pipelex API keys (`plx_sk_…`):** `listPipelexApiKeys`, `createPipelexApiKey` (409 `pipelex_api_key_limit_reached`), `revokePipelexApiKey`, `rotatePipelexApiKey`. The plaintext `api_key` is returned once, on create/rotate.
package/docs/errors.md CHANGED
@@ -186,7 +186,7 @@ A malformed version suffix (`@0`, `@03`, `@Draft`, two suffixes) is a `422` `req
186
186
 
187
187
  ### The branding codes
188
188
 
189
- A refused branding write (`updateOrganizationBranding`) is one problem naming each logo or colour it refused as an item of its `errors`: the item's `location.field` is the logo's slot, `square_logo` or `wide_logo`, or the colour's dotted key, such as `colors.action`, and its `detail` the sentence a settings screen shows under that control. A logo the platform read and refused carries one item per broken rule, both slots at once, each item's code one the exported type `LogoRefusalCode` names (`logo_too_large`, `logo_not_square`, `logo_too_small`, …). [organization-branding.md](./organization-branding.md#when-a-write-is-refused) lists every code with a sentence of each.
189
+ A refused branding write (`updateOrganizationBranding`) is one problem naming each field it refused by its `location.field`: a colour by its dotted key, such as `colors.action`, and a style or a typeface by its key. When it refuses one such field alone the problem itself names it; when it refuses several, each has one item of its `errors`. A refused logo is named by its slot, `square_logo` or `wide_logo`, in items of its `errors` whose `detail` is the sentence a settings screen shows under that slot. A logo the platform read and refused carries one item per broken rule, both slots at once, each item's code one the exported type `LogoRefusalCode` names (`logo_too_large`, `logo_not_square`, `logo_too_small`, …). [organization-branding.md](./organization-branding.md#when-a-write-is-refused) lists every code with a sentence of each.
190
190
 
191
191
  ### The files codes
192
192
 
@@ -1,13 +1,13 @@
1
1
  # An organization's branding
2
2
 
3
- An organization may carry a brand: up to two logos, a square one and a wide one, and two colour sets of three colours, the light set and an optional dark one. The webapp shows it on the organization's menu and on the apps of its methods, and an app scaffolded from the method-app template reads it with its API key to wear the same brand. A personal workspace never has one. The client reaches it on two routes of the hosted API, and the types are in `src/product-models.ts`.
3
+ An organization may carry a brand: up to two logos, a square one and a wide one, two colour sets of three colours, the light set and an optional dark one, an app style and a typeface. The webapp shows it on the organization's menu and on the apps of its methods, and an app scaffolded from the method-app template reads it with its API key to wear the same brand. A personal workspace never has one. Beside the brand, the branding says whether the organization holds the white-label entitlement, which Pipelex staff grant. The client reaches it on two routes of the hosted API, and the types are in `src/product-models.ts`.
4
4
 
5
5
  ## The methods
6
6
 
7
7
  | Method | Route | Who may call it | Answer |
8
8
  |---|---|---|---|
9
9
  | `getBranding()` | `GET /v1/branding` | Every member of the active organization, `runner` included, by web session or API key | The active organization's `OrganizationBranding`. |
10
- | `updateOrganizationBranding(orgId, input)` | `PATCH /v1/organizations/{org_id}/branding` | An admin of the active organization, by web session | The `OrganizationBranding` as the write left it. |
10
+ | `updateOrganizationBranding(orgId, input)` | `PATCH /v1/organizations/{org_id}/branding` | A caller with the `admin` or the `member` role in the active organization, never a `runner`, by web session | The `OrganizationBranding` as the write left it. |
11
11
 
12
12
  Both answer the same shape, so a client holds one type whichever it called. `getBranding` takes no organization id: it reads the caller's active organization.
13
13
 
@@ -21,9 +21,18 @@ Both answer the same shape, so a client holds one type whichever it called. `get
21
21
  | `dark_colors` | `BrandingColors` | The dark colour set, used in dark mode only. |
22
22
  | `square_logo` | `LogoDescriptor \| null` | The square logo, for the organization menu and small headers. |
23
23
  | `wide_logo` | `LogoDescriptor \| null` | The wide logo, for an app's header. |
24
+ | `style` | `string \| null` | The id of the app style the team chose for its method apps, `null` when not set. |
25
+ | `typeface` | `string \| null` | The id of the typeface the team sets its apps' text in, `null` when not set. |
26
+ | `white_label` | `boolean` | Whether the organization holds the white-label entitlement. |
24
27
 
25
28
  A `BrandingColors` always carries its three keys, each `#rrggbb` in lower case or `null` when not set: `action` is the button colour, `accent` the accent colour and `header` the header band's colour. A dark colour that is `null` falls back to the light colour of the same role, and a light one that is `null` to Pipelex's own palette for that theme. The `@pipelex/mthds-form/brand` entry turns a colour set into the roles a page paints with (`resolveDarkSeeds`, then `deriveBrandRoles` once per theme).
26
29
 
30
+ **The style and the typeface.** `style` is the id of the app style the team chose, such as `editorial`: how its method apps' headings, panels, corners and spacing look. `typeface` is the id of the typeface their text is set in, such as `manrope`. Both themes share them. The platform holds each to a shape, one to 32 lower-case letters, digits and hyphens (`^[a-z0-9-]{1,32}$`), and never to a list: the list of styles and typefaces lives in the form kernel's `@pipelex/mthds-form/brand` entry, so a client reads an id it does not know as unset, and a style added there needs no new SDK. Neither counts towards whether an app is branded, which stays a logo or a colour, since a branded app trades Pipelex's strip for the team's band and a style alone would leave that band carrying nothing of the team's; a client applies the style whenever one is set.
31
+
32
+ **The white-label entitlement.** `white_label` is `true` when the organization holds the white-label option: its method apps show no Pipelex mark anywhere a runner looks, whether or not the team set a brand. It is `false` for every other organization, a personal workspace included. It is not part of the brand the team sets: Pipelex staff grant and withdraw it, no branding write changes it, and `OrganizationBrandingUpdate` has no such key.
33
+
34
+ **A branding written before these fields existed** answers `style` and `typeface` `null` and `white_label` `false`. The client reads an answer that leaves any of the three out, as a platform that predates them sends, or that sends a value of another type, the same way: `style` and `typeface` as `null` and `white_label` as `false`, so each is what the type declares, never `undefined`.
35
+
27
36
  A `LogoDescriptor` is what the platform recorded when it accepted the logo:
28
37
 
29
38
  | Field | Meaning |
@@ -39,17 +48,20 @@ Every key of an `OrganizationBrandingUpdate` is optional: a value sets it, `null
39
48
 
40
49
  ```ts
41
50
  // Set the button and accent colours, keep the header colour, clear the whole dark set,
42
- // attach a square logo and remove the wide one.
51
+ // attach a square logo, remove the wide one, choose a style and clear the typeface.
43
52
  const branding = await client.updateOrganizationBranding(orgId, {
44
53
  colors: { action: "#14213d", accent: "#fca311" },
45
54
  dark_colors: null,
46
55
  square_logo: "pipelex-storage://<org_id>/assets/<file>.png",
47
56
  wide_logo: null,
57
+ style: "editorial",
58
+ typeface: null,
48
59
  });
49
60
  ```
50
61
 
51
62
  - **Colours** (`BrandingColorsUpdate`): each of `action`, `accent` and `header` takes `#rrggbb` in either case, stored in lower case. `colors: null` or `dark_colors: null` clears all three colours of that set.
52
63
  - **Logos**: `square_logo` and `wide_logo` take one of three values. The `pipelex-storage://` URI of a file the organization uploaded on its own through `requestUploadGrant` or `upload` sets a new logo: the platform reads and measures it, then writes its own copy under `branding/`, and the answer's `uri` names that copy, never the upload, which the platform leaves as it is. The `uri` the branding answers for that slot, sent back unchanged, leaves the slot out of the write: nothing is read, and the slot keeps whatever logo the branding names when the write lands, so a save that resends the logo it read never puts back one a concurrent save replaced. `null` removes the logo. Any other URI is refused as `logo_not_an_upload`: a run's or a method's file, a `branding/` copy the slot no longer holds, and the other slot's logo too, so a logo cannot move between slots by its URI; upload it again for the other slot instead. Once the write has landed, the platform erases the `branding/` copy it replaced or cleared.
64
+ - **Style and typeface**: `style` and `typeface` each take an id of one to 32 lower-case letters, digits and hyphens, stored as it is whether or not such a style or typeface exists, or `null` to clear it. Writing one leaves the colours, the logos and the other as they are. There is no `white_label` key: the platform refuses a body naming it as it refuses any key it does not know.
53
65
  - **The rules a logo meets**: at most 1 MiB; a PNG, JPEG, WebP or SVG image by its bytes, whatever type the upload declared, an SVG in UTF-8 or, behind its byte-order mark, UTF-16; a still image, so never an animated PNG (an APNG) or an animated WebP; a raster of at most 4096 × 4096 pixels that decodes whole; a square logo's longer side at most 10% longer than its shorter, and a raster one at least 128 px on each side; a wide logo at least one and a half times as wide as it is tall, and a raster one at least 64 px tall. An SVG is measured from its root element, by its `width` and `height` when both are in absolute units, else by its `viewBox`, and each side must lie between 0.001 and 1,000,000, in pixels for a `width` or `height` and in the SVG's own units for a `viewBox`. It must declare no XML entities or external references and carry no active content: no `script` or `foreignObject` element, no element in the XHTML namespace, no `on…` event handler attribute, no `set` or `animate` that rewrites a link, no `xml-stylesheet` processing instruction, and no link but a reference inside the file (`#…`) or an embedded PNG, JPEG, GIF or WebP image (`data:image/…`), whether an `href`, an `xlink:href`, or a CSS `url()` or `src()`. Wherever a browser reads CSS, which is a `<style>` element's text and any attribute in no namespace or the SVG one, such as `style`, `fill` or `filter`, it must hold no `@import`, no `image()` or `image-set()`, and no escape (a backslash, which can spell a link out of sight). A `<style>` element itself is allowed, so the class-based styles a design tool exports pass, and so does a reference inside the file such as `fill:url(#grad)`; an attribute in another namespace, such as Inkscape's `inkscape:export-filename`, and a `data-*` one, such as the layer names Illustrator writes in `data-name`, are never read as CSS.
54
66
 
55
67
  The write sends no `Idempotency-Key`, as `renameOrganization` sends none. A later write that should keep a logo leaves its key out, or sends back the `uri` the branding answered.
@@ -60,15 +72,17 @@ A refused write changes nothing, whichever part of it was refused, and throws an
60
72
 
61
73
  | Status | `code` | When |
62
74
  |---|---|---|
63
- | `403` | `auth.forbidden` | The caller is not the organization's admin. |
75
+ | `403` | `auth.forbidden` | The caller is a runner, or holds no recognised role in the organization. |
64
76
  | `403` | `pipelex.org.mismatch` | `orgId` is not the caller's active organization, or a logo's URI is a file of another organization. |
65
77
  | `404` | `pipelex.org.not_found` | The organization does not exist. |
66
78
  | `409` | `pipelex.org.not_manageable` | The organization is a personal workspace, which has no branding. |
67
- | `422` | `request.invalid` | A colour is not `#rrggbb`, the body holds a key the route does not take, or a logo was refused. |
79
+ | `422` | `request.invalid` | A colour is not `#rrggbb`, a style or a typeface is not an id of the shape `^[a-z0-9-]{1,32}$`, the body holds a key the route does not take, `white_label` included, or a logo was refused. |
68
80
  | `422` | `pipelex.storage.uri_invalid` | A logo's URI is not a storage URI. |
69
81
  | `503` | `runtime.unavailable` | The platform's storage failed while reading or copying a logo, or its database failed or could not be reached, so the write may have landed or not. Its `retry` is `yes`: the same `input` sent again is safe, and `getBranding` reads what the branding holds. |
70
82
 
71
- **A refusal about a logo or a colour names it in its problem's `errors`**, one problem item per refused logo or colour, so the settings screen shows each sentence under the control it concerns rather than in a toast. Each item's `location.field` is the slot, `square_logo` or `wide_logo`, or the colour's dotted key, such as `colors.action` or `dark_colors.header`; its `detail` is the sentence to show. A logo the platform read and refused carries one item per broken rule, both slots answered in one refusal, each item's `code` one `LogoRefusalCode` names:
83
+ **A refused field is named by its `location.field`**, so the settings screen shows each sentence under the control it concerns rather than in a toast: a colour by its dotted key, such as `colors.action` or `dark_colors.header`, a style or a typeface by `style` or `typeface`, and a key the route does not take by its own name. When the write refuses one such field alone, the problem itself names it, in `location.field` and in `params.field`, and carries no `errors`; when it refuses several, it names the first and carries one item per refused field in `errors`, each with its own `location.field`.
84
+
85
+ **A refusal about a logo names it in its problem's `errors`**, one problem item per refused logo. Each item's `location.field` is the slot, `square_logo` or `wide_logo`; its `detail` is the sentence to show. A logo the platform read and refused carries one item per broken rule, both slots answered in one refusal, each item's `code` one `LogoRefusalCode` names:
72
86
 
73
87
  | `code` | Example `detail` |
74
88
  |---|---|
@@ -97,4 +111,4 @@ try {
97
111
  }
98
112
  ```
99
113
 
100
- `getBranding` answers `400` when the caller has no active organization and `404` when its organization's row is missing. Either method throws the `ApiResponseError` of an answer it cannot read for a `2xx` that is not a JSON object, an empty one included.
114
+ `getBranding` answers `403` `pipelex.org.required` when the caller has no active organization and `404` when its organization's row is missing. Either method throws the `ApiResponseError` of an answer it cannot read for a `2xx` that is not a JSON object, an empty one included.
@@ -7,7 +7,9 @@ import { PipelexApiClient } from "@pipelex/sdk";
7
7
 
8
8
  const client = new PipelexApiClient();
9
9
  const page = await client.listMethodTemplates({ ordering: "default", category: "finance", limit: 8 });
10
- for (const tab of page.categories) console.log(tab.slug, tab.label); // every category with a template
10
+ // Every category with a template, labelled in the reader's language, else in English.
11
+ const locale = "fr";
12
+ for (const tab of page.categories) console.log(tab.slug, tab.labels[locale] ?? tab.label);
11
13
  for (const template of page.items) {
12
14
  // The thumbnail whose ratio is closest to a 2:1 card.
13
15
  const card = template.thumbnails.reduce<(typeof template.thumbnails)[number] | undefined>(
@@ -34,16 +36,27 @@ if (first !== undefined) {
34
36
  |---|---|---|
35
37
  | `listMethodTemplates({ ordering?, category?, limit?, cursor? })` | `GET /v1/method-templates` | A `MethodTemplatePage`: `{ items, nextCursor, ordering, categories }`. |
36
38
  | `iterateMethodTemplates({ ordering?, category?, limit? })` | the same, following the cursor | Every `MethodTemplate`, in the order served. |
39
+ | `listMethodCategories()` | `GET /v1/method-categories` | A `MethodCategoryList`: `{ categories }`, the catalog's whole list. |
37
40
 
38
41
  **The order is the platform's.** An ordering is a named list an admin writes, such as `default` or one arm of a test. The templates it lists lead, in its order, and every other template follows by name, so a template appears as soon as it is public. `ordering` asks for one by name; an absent, unknown or malformed name serves `default`, and the page's `ordering` names the one served, `null` when there is none, the templates then listed by name. A client never sorts the cards: it keeps the order served.
39
42
 
40
- **A category filters and keeps the order.** `category` takes a slug of the catalog's list, such as `human-resources`, and keeps that category's templates in the same order; a slug no template shows keeps none. Whatever the filter, the page's `categories` names, in the catalog's order, every category at least one template shows, each a `MethodCategory` with its `slug` and its English `label`, so a tab bar drawn from it never shows an empty tab. A client translating the labels translates by slug and falls back on the label.
43
+ **A category filters and keeps the order.** `category` takes a slug of the catalog's list, such as `human-resources`, and keeps that category's templates in the same order; a slug no template shows keeps none. Whatever the filter, the page's `categories` names, in the catalog's order, every category at least one template shows, so a tab bar drawn from it never shows an empty tab.
44
+
45
+ **The categories are a list the platform keeps.** An admin of the platform adds, renames, reorders and removes the catalog's categories, with no release of any client, so a client never holds its own copy of the list. `listMethodCategories` reads the whole list in its order, a category no template shows included; the page's `categories` is the part of it some template shows. Each is a `MethodCategory`:
46
+
47
+ - `slug`, such as `human-resources`, which a manifest declares and never changes: a new meaning is a new slug;
48
+ - `label`, the English label, which a client that does not translate shows;
49
+ - `labels`, the label in each language the platform holds one for, by language tag, such as `{ en: "Human resources", fr: "Ressources humaines" }`, `labels.en` equalling `label`. A category stored before the platform spoke a language lacks a label in it, so a client shows `labels[locale] ?? label`, and the type, `Partial<Record<string, string>>`, makes it handle that case.
50
+
51
+ A template shows under the categories its manifest declares that are on the list. A slug off the list is kept on the method and its versions but shown under no tab, and appears as soon as an admin adds it to the list, with no new publish.
41
52
 
42
53
  **A card is the latest published version.** Each `MethodTemplate` carries `method_id`, `name`, `description` (`null` when it has none), `latest_version`, `categories` (slugs), `organization_name`, the display name of the organization holding it, and `thumbnails`, each a `MethodTemplateThumbnail`: the image's `path`, `media_type`, `width`, `height` and `sha256`, with a `url` signed by storage and its `expires_at`, about fifteen minutes after the read. The ratio is never declared: draw the thumbnail whose `width` and `height` come closest to your frame. The link is a bearer credential until it expires, so it stays out of logs and stored records, and an answer the SDK cannot read is thrown with its links redacted.
43
54
 
44
55
  **Paging.** A page holds 50 templates by default and at most 200; a `limit` outside 1 to 200 is a `422` `request.invalid`. `nextCursor` resumes after the last template the page served, wherever the catalog now places it, so a template added or removed elsewhere between two pages neither repeats nor skips the ones around that point. The cursor holds neither the ordering nor the category: pass both again with it, as `iterateMethodTemplates` does. A cursor of another listing is a `422` `request.invalid` and one that does not decode a `422` `request.malformed`.
45
56
 
46
- **Refusals.** `500` `runtime.misconfigured` when the templates organization the deployment names has no record or a link cannot be signed, and `503` `runtime.unavailable`, safe to retry. A deployment that names no templates organization serves an empty catalog.
57
+ **Who reads them.** Any member of any organization reads the catalog and the list of categories, with a web session or an API key, and every caller reads the same; a caller with no active organization is refused with a `403` `pipelex.org.required`.
58
+
59
+ **Refusals.** `500` `runtime.misconfigured` when the templates organization the deployment names has no record or a link cannot be signed, and `503` `runtime.unavailable`, safe to retry. A deployment that names no templates organization serves an empty catalog. An answer the SDK cannot read, such as a category without a string `slug` and `label` or whose `labels` is not an object of strings, is thrown as an `ApiResponseError`.
47
60
 
48
61
  ## Reading another organization's public method
49
62
 
@@ -80,7 +93,7 @@ if ("updated_at" in method) {
80
93
  ## Visibility, categories and thumbnails on a method
81
94
 
82
95
  - **`visibility`**, on every method read (`MethodData`, `MethodDetail`, `PublicMethodDetail`): `internal` or `public`, read open, so a value a later platform adds arrives as its raw string. Absent from a platform that predates it, which means `internal`. The list rows (`MethodSummary`) do not carry it.
83
- - **`categories`**, on a method and on each version summary: the slugs the manifest declares under `[package]` that are on the catalog's list, in their declared order. The platform ignores a value it does not know, so a typo reads as no category.
96
+ - **`categories`**, on a method and on each version summary: the slugs the manifest declares under `[package]`, each well-formed one once, in their declared order, whether or not they are on the catalog's list. The catalog shows a template under those on the list alone, so a typo shows under no tab.
84
97
  - **`thumbnails`**, on each version summary: the valid images directly under the tree's `thumbnails/` folder, each a `MethodThumbnail` (`path`, `media_type`, `width`, `height`, `sha256`), measured by the publish. A PNG, JPEG or WebP of at least 800 by 400 pixels is valid; an invalid image is simply not recorded.
85
98
  - **`description`**: the manifest's `description`, else the primary bundle's top-level `description`, else its main pipe's, a blank one counting as none at each step.
86
99
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pipelex/sdk",
3
- "version": "0.61.0",
3
+ "version": "0.63.0",
4
4
  "description": "TypeScript SDK for the Pipelex hosted API — execute methods, manage runs, and call the product surface.",
5
5
  "license": "MIT",
6
6
  "repository": {