Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ Handwritten API clients, UI packages, and developer tooling for Polymorfa.
The development branch contains the TypeScript server SDK, a framework-neutral
browser runtime, shared UI contracts, Web Components, React bindings, thin
Next.js server helpers, and a production-gated developer assistant. The exact Messaging and Platform sources are pinned in
`contracts/source.json`; the Graph-compatible catalog and Flow encryption
routes are pinned to the same API commit in `contracts/cloud-api-supplements.json`. Full Graph API parity
is outside this SDK's scope; `cloudCatalogs.list` and `flowEncryption` provide
`contracts/source.json`; the Graph-compatible catalog, marketing status and
Flow encryption routes are pinned to the same API commit in
`contracts/cloud-api-supplements.json`. Full Graph API parity is outside this
SDK's scope; `cloudCatalogs`, `cloudMarketing.status`, and `flowEncryption` provide
typed clients for their respective Graph resources.

The API includes an enrolled hosted message history beta.
Expand Down Expand Up @@ -142,6 +143,7 @@ The handwritten Messaging resources in this milestone are:
- `cloudTemplates`: list, retrieve, create, edit and delete Meta templates for a
Number with an Official API connection; separate from project template drafts
- `cloudCatalogs`: read WABA product-catalog links through the Graph facade
- `cloudMarketing`: read Meta's raw WABA Marketing Messages status strings without interpreting eligibility
- `flowEncryption`: read or register a public Flow encryption key for one Meta phone number
- `business`: manage the connected Business App profile, commerce catalog,
products, collections, orders, compliance, linked accounts, and eligibility
Expand Down
9 changes: 7 additions & 2 deletions contracts/cloud-api-supplements.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,15 @@
{
"sourcePath": "docs/api-reference/openapi.graph.json",
"sha256": "87e3a56aee42de041dd38db08d0b263123f84a5a4982ce0f20b780c3cfae3684",
"operationIds": ["graphListProductCatalogs", "graphListCatalogProducts"],
"operationIds": [
"graphListProductCatalogs",
"graphListCatalogProducts",
"graphGetMarketingStatus"
],
"methods": [
"MessagingClient.cloudCatalogs.list",
"MessagingClient.cloudCatalogs.listProducts"
"MessagingClient.cloudCatalogs.listProducts",
"MessagingClient.cloudMarketing.status"
],
"merged": true
},
Expand Down
23 changes: 23 additions & 0 deletions docs/internal/2026-09-26-marketing-status-consumer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Raw Marketing Messages WABA status consumer

`MessagingClient.cloudMarketing.status` consumes Polymorfa API PR #383 at
`1e1d613de599c2b4334f8de81fab087c74f31424`, with Graph OpenAPI copy
`docs/api-reference/openapi.graph.json` SHA-256
`1270c8fb32a09cb717f3ea8edd6bd657e8ff3110e03a6286e68d5e16affff474`.
The exact operation is `graphGetMarketingStatus`. It reads raw optional WABA
status strings and preserves the API's Graph error behavior. No status value is
mapped to accepted terms, eligibility, insight availability, or a send decision.

API: merged as polymorfa PR #383 and pinned at dev 29abb7b; not deployed by this SDK
change. SDKs: TypeScript server Messaging resource and public type updated;
other language clients in this workspace do not expose this Graph facade
resource and remain unaffected by the current contract. CLI: companion
read-only command is stacked separately and needs a published SDK artifact.
Docs: this SDK's overview and TypeScript page are updated; API public method
documentation remains in the API PR. Feature release: the existing Graph beta
gate is unchanged; installed SDK code is not customer availability. Admin:
no capability or staff state changes.

No tests, typechecks, builds, documentation checks or live Meta calls were run
at the user's request. Publication, exact package integrity, CLI pin, and live
account acceptance remain separate gates.
7 changes: 7 additions & 0 deletions packages/typescript/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,13 @@ from a catalog linked to that WABA. It has the same server-credential and
to request the next page. The API rejects catalogs it cannot verify as linked;
listing products does not grant permission to send them.

`messaging.cloudMarketing.status(wabaId, { version: "v26.0" })` reads Meta's
raw `marketing_messages_lite_api_status` and
`marketing_messages_onboarding_status` strings through an Official API Number
in the credential's project. It requires `sessions:read` and an organization
API key or project token. The fields do not establish terms acceptance,
recipient permission, eligibility, or permission to send.

`messaging.flowEncryption.retrieve(phoneNumberId, { version: "v26.0" })` reads
the registered public key and Meta signature status with `sessions:read`.
`messaging.flowEncryption.register(phoneNumberId,
Expand Down
5 changes: 5 additions & 0 deletions packages/typescript/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ export {
} from "./errors.js";
export { MessagingClient } from "./messaging/client.js";
export { CloudCatalogsResource } from "./messaging/cloud-catalogs.js";
export { CloudMarketingResource } from "./messaging/cloud-marketing.js";
export type {
CloudMarketingStatus,
GetCloudMarketingStatusParams,
} from "./messaging/cloud-marketing.js";
export { FlowEncryptionResource } from "./messaging/flow-encryption.js";
export type {
FlowEncryptionKey,
Expand Down
6 changes: 6 additions & 0 deletions packages/typescript/src/messaging/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { SessionsResource } from "./sessions.js";
import { TemplatesResource } from "./templates.js";
import { CloudTemplatesResource } from "./cloud-templates.js";
import { CloudCatalogsResource } from "./cloud-catalogs.js";
import { CloudMarketingResource } from "./cloud-marketing.js";
import { FlowEncryptionResource } from "./flow-encryption.js";
import { UsersResource } from "./users.js";
import { VoipResource } from "./voip.js";
Expand Down Expand Up @@ -62,6 +63,7 @@ export class MessagingClient {
readonly templates: TemplatesResource;
readonly cloudTemplates: CloudTemplatesResource;
readonly cloudCatalogs: CloudCatalogsResource;
readonly cloudMarketing: CloudMarketingResource;
readonly flowEncryption: FlowEncryptionResource;
readonly users: UsersResource;
readonly voip: VoipResource;
Expand Down Expand Up @@ -113,6 +115,10 @@ export class MessagingClient {
credential.type,
);
this.cloudCatalogs = new CloudCatalogsResource(transport, credential.type);
this.cloudMarketing = new CloudMarketingResource(
transport,
credential.type,
);
this.flowEncryption = new FlowEncryptionResource(
transport,
credential.type,
Expand Down
47 changes: 47 additions & 0 deletions packages/typescript/src/messaging/cloud-marketing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import type { MessagingCredential } from "../credentials.js";
import { PolymorfaConfigurationError } from "../errors.js";
import { HttpTransport } from "../transport/http.js";
import type { ApiResponse, RequestOptions } from "../transport/types.js";

/** Meta WABA observations. Neither string is an eligibility or terms grant. */
export interface CloudMarketingStatus {
readonly id: string;
readonly marketing_messages_lite_api_status?: string;
readonly marketing_messages_onboarding_status?: string;
}

export interface GetCloudMarketingStatusParams {
/** Meta Graph version, for example v26.0. */
readonly version: string;
}

export class CloudMarketingResource {
constructor(
private readonly transport: HttpTransport,
private readonly credentialType: MessagingCredential["type"],
) {}

/** Read raw WABA status fields through a project-owned Official API Number. */
status(
wabaId: string,
params: GetCloudMarketingStatusParams,
options: RequestOptions = {},
): Promise<ApiResponse<CloudMarketingStatus>> {
if (this.credentialType === "clientToken") {
throw new PolymorfaConfigurationError(
"Official API marketing status requires an organization API key or project token.",
"credential",
);
}
if (!wabaId.trim() || !params.version.trim()) {
throw new PolymorfaConfigurationError(
"Provide a WABA ID and Graph version.",
);
}
return this.transport.request({
method: "GET",
path: `/graph/whatsapp/${encodeURIComponent(params.version)}/${encodeURIComponent(wabaId)}/marketing_messages/status`,
...options,
});
}
}
46 changes: 46 additions & 0 deletions packages/typescript/test/cloud-marketing.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { afterEach, describe, expect, it } from "vitest";
import { MessagingClient } from "../src/index.js";
import { PROJECT_TOKEN } from "./support/credentials.js";
import { startTestServer, type TestServer } from "./support/http-server.js";
const servers: TestServer[] = [];
afterEach(async () => {
await Promise.all(servers.splice(0).map((server) => server.close()));
});
describe("Official API marketing status", () => {
it("returns Meta's raw WABA status strings from the exact Graph route", async () => {
const payload = {
id: "102290129340398",
marketing_messages_lite_api_status: "ELIGIBLE",
};
const server = await startTestServer(() => ({
body: JSON.stringify(payload),
}));
servers.push(server);
const client = new MessagingClient({
credential: { type: "projectToken", value: PROJECT_TOKEN },
baseUrl: server.url,
});
const status = await client.cloudMarketing.status("102290129340398", {
version: "v26.0",
});
expect(status.data).toEqual(payload);
expect(server.requests[0]!.method).toBe("GET");
expect(server.requests[0]!.path).toBe(
"/graph/whatsapp/v26.0/102290129340398/marketing_messages/status",
);
});
it("rejects client tokens and blank identifiers before dispatch", () => {
const browser = new MessagingClient({
credential: { type: "clientToken", value: `pmfa_ct_${"A".repeat(94)}` },
});
expect(() =>
browser.cloudMarketing.status("123", { version: "v26.0" }),
).toThrow("organization API key or project token");
const server = new MessagingClient({
credential: { type: "projectToken", value: PROJECT_TOKEN },
});
expect(() =>
server.cloudMarketing.status(" ", { version: "v26.0" }),
).toThrow("WABA ID");
});
});
Loading